From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Mattias =?UTF-8?Q?Engdeg=C3=A5rd?= Newsgroups: gmane.emacs.bugs Subject: bug#45198: 28.0.50; Sandbox mode Date: Sat, 17 Apr 2021 20:59:06 +0200 Message-ID: <78F41D0B-D2F6-444C-9B5C-9C50CFF2CFBD@acm.org> References: <5818DFAA-3A9C-4335-BAAF-1227A02C290A@acm.org> <19511709-E42B-4ABD-9823-39EA08A79B1F@gmail.com> <83v98kvr7y.fsf@gnu.org> <9A5BCDF3-6543-46C0-AB56-2311392FC549@gmail.com> Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.17\)) Content-Type: multipart/mixed; boundary="Apple-Mail=_64FD6A78-046E-4E8B-8A46-7F258849914F" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="20604"; mail-complaints-to="usenet@ciao.gmane.io" Cc: alan@idiocy.org, 45198@debbugs.gnu.org, stefan@marxist.se, Philipp , joaotavora@gmail.com To: Stefan Monnier Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sat Apr 17 21:00:27 2021 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lXqB9-0005ES-Gt for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 17 Apr 2021 21:00:27 +0200 Original-Received: from localhost ([::1]:36266 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lXqB8-0001cy-JK for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 17 Apr 2021 15:00:26 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:32986) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lXqAl-0001ch-50 for bug-gnu-emacs@gnu.org; Sat, 17 Apr 2021 15:00:03 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:33042) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lXqAk-0007ja-Sd for bug-gnu-emacs@gnu.org; Sat, 17 Apr 2021 15:00:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1lXqAk-0006Mp-Qh for bug-gnu-emacs@gnu.org; Sat, 17 Apr 2021 15:00:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Mattias =?UTF-8?Q?Engdeg=C3=A5rd?= Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 17 Apr 2021 19:00:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 45198 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 45198-submit@debbugs.gnu.org id=B45198.161868595924372 (code B ref 45198); Sat, 17 Apr 2021 19:00:02 +0000 Original-Received: (at 45198) by debbugs.gnu.org; 17 Apr 2021 18:59:19 +0000 Original-Received: from localhost ([127.0.0.1]:44584 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lXqA3-0006L2-7S for submit@debbugs.gnu.org; Sat, 17 Apr 2021 14:59:19 -0400 Original-Received: from mail1470c50.megamailservers.eu ([91.136.14.70]:55406 helo=mail102c50.megamailservers.eu) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lXqA0-0006Ko-Uw for 45198@debbugs.gnu.org; Sat, 17 Apr 2021 14:59:18 -0400 X-Authenticated-User: mattiase@bredband.net DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=megamailservers.eu; s=maildub; t=1618685950; bh=b5FxU/n930a4X9c88pTflNr9aY8MlAuUkgdLo4uM7c8=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=KNYdLciklRVgntmONMTGk8HVxuktMc0yfeV8EW0LYTaGT6V+2ScKXP5gMVO8Em3lx VFJbmCJj4MhwSxBGMm9dI9ctlA88HyCrJlH1KqeECJD0sVxEtNaqnfZv5JxOsFSz/S xINn/9YWBVvbEAzFL6JRQvkgNoYEf+oKTdFFMNqA= Feedback-ID: mattiase@acm.or Original-Received: from stanniol.lan (c-b952e353.032-75-73746f71.bbcust.telenor.se [83.227.82.185]) (authenticated bits=0) by mail102c50.megamailservers.eu (8.14.9/8.13.1) with ESMTP id 13HIx6Et030602; Sat, 17 Apr 2021 18:59:08 +0000 In-Reply-To: X-Mailer: Apple Mail (2.3445.104.17) X-CTCH-RefID: str=0001.0A742F21.607B2FFE.0009, ss=1, re=0.000, recu=0.000, reip=0.000, cl=1, cld=1, fgs=0 X-CTCH-VOD: Unknown X-CTCH-Spam: Unknown X-CTCH-Score: 0.000 X-CTCH-Flags: 0 X-CTCH-ScoreCust: 0.000 X-CSC: 0 X-CHA: v=2.3 cv=KN0k82No c=1 sm=1 tr=0 a=von4qPfY+hyqc0zmWf0tYQ==:117 a=von4qPfY+hyqc0zmWf0tYQ==:17 a=M51BFTxLslgA:10 a=iRZporoAAAAA:8 a=a2_K_BZEX7mNdVaJnwQA:9 a=CjuIK1q_8ugA:10 a=ENOmLqmOUNYA:10 a=wlUJtMPZQSMOdi7ZWyMA:9 a=B2y7HmGcmWMA:10 a=NOBgFS-JBQ2l-kSd6-zu:22 X-Origin-Country: SE X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:204246 Archived-At: --Apple-Mail=_64FD6A78-046E-4E8B-8A46-7F258849914F Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii 17 apr. 2021 kl. 20.21 skrev Stefan Monnier : >> Very reasonable. Or would you prefer having the sandboxing in = flymake? >=20 > AFAICT this question refers to a minor implementation detail ;-) Of course, sorry. Looks like I forgot to attach the updated patch in a previous message. = Here it is. --Apple-Mail=_64FD6A78-046E-4E8B-8A46-7F258849914F Content-Disposition: attachment; filename=0001-Add-macOS-sandboxing-bug-45198.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="0001-Add-macOS-sandboxing-bug-45198.patch" Content-Transfer-Encoding: quoted-printable =46rom=2078906da41140dc33119b419a410c4a4f0a3aee80=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20=3D?UTF-8?q?Mattias=3D20Engdeg=3DC3=3DA5rd?=3D=20= =0ADate:=20Sat,=2017=20Apr=202021=2020:53:39=20+0200=0A= Subject:=20[PATCH]=20Add=20macOS=20sandboxing=20(bug#45198)=0A=0AThis=20= is=20the=20corresponding=20low-level=20sandboxing=20facility=20= corresponding=0Ato=20the=20recently=20added=20Seccomp=20for=20Linux.=20=20= `darwin-sandbox-init`=20gives=0Adirect=20access=20to=20the=20system=20= sandboxing=20call;=20`darwin-sandbox-enter`=20is=0Aa=20wrapper=20that=20= takes=20a=20list=20of=20directories=20under=20which=20files=20can=20be=0A= read.=20=20These=20should=20be=20considered=20internal=20mechanisms=20= for=20now.=0A=0A*=20lisp/subr.el=20(darwin-sandbox-enter):=0A*=20= src/sysdep.c=20(Fdarwin_sandbox_init):=20New=20functions.=0A*=20= test/src/emacs-tests.el=20(emacs-tests/darwin-sandbox):=20New=20test.=0A= ---=0A=20lisp/subr.el=20=20=20=20=20=20=20=20=20=20=20=20|=2018=20= +++++++++++=0A=20src/sysdep.c=20=20=20=20=20=20=20=20=20=20=20=20|=2032=20= +++++++++++++++++++=0A=20test/src/emacs-tests.el=20|=2070=20= +++++++++++++++++++++++++++++++++++++++++=0A=203=20files=20changed,=20= 120=20insertions(+)=0A=0Adiff=20--git=20a/lisp/subr.el=20b/lisp/subr.el=0A= index=20c2be26a15f..196512c0c6=20100644=0A---=20a/lisp/subr.el=0A+++=20= b/lisp/subr.el=0A@@=20-6262,4=20+6262,22=20@@=20= internal--format-docstring-line=0A=20This=20is=20intended=20for=20= internal=20use=20only."=0A=20=20=20(internal--fill-string-single-line=20= (apply=20#'format=20string=20objects)))=0A=20=0A+(when=20(eq=20= system-type=20'darwin)=0A+=20=20(defun=20darwin-sandbox-enter=20(dirs)=0A= +=20=20=20=20"Enter=20a=20sandbox=20only=20permitting=20reading=20files=20= under=20DIRS.=0A+DIRS=20is=20a=20list=20of=20directory=20names.=20=20= Most=20other=20operations=20such=20as=0A+writing=20files=20and=20network=20= access=20are=20disallowed.=0A+Existing=20open=20descriptors=20can=20= still=20be=20used=20freely.=0A+=0A+This=20is=20not=20a=20supported=20= interface=20and=20is=20for=20internal=20use=20only."=0A+=20=20=20=20= (darwin-sandbox-init=0A+=20=20=20=20=20(concat=20"(version=201)\n"=0A+=20= =20=20=20=20=20=20=20=20=20=20=20=20"(deny=20default)\n"=0A+=20=20=20=20=20= =20=20=20=20=20=20=20=20;;=20Emacs=20seems=20to=20need=20/dev/null;=20= allowing=20it=20does=20no=20harm.=0A+=20=20=20=20=20=20=20=20=20=20=20=20= =20"(allow=20file-read*=20(path=20\"/dev/null\"))\n"=0A+=20=20=20=20=20=20= =20=20=20=20=20=20=20(mapconcat=20(lambda=20(dir)=0A+=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20= "(allow=20file-read*=20(subpath=20%S))\n"=20dir))=0A+=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20dirs=20""))))=0A+=20=20= )=0A+=0A=20;;;=20subr.el=20ends=20here=0Adiff=20--git=20a/src/sysdep.c=20= b/src/sysdep.c=0Aindex=20d940acc4e0..44e8b82bc6=20100644=0A---=20= a/src/sysdep.c=0A+++=20b/src/sysdep.c=0A@@=20-4286,8=20+4286,40=20@@=20= str_collate=20(Lisp_Object=20s1,=20Lisp_Object=20s2,=0A=20}=0A=20#endif=09= /*=20WINDOWSNT=20*/=0A=20=0A+#ifdef=20DARWIN_OS=0A+=0A+/*=20This=20= function=20prototype=20is=20not=20in=20the=20platform=20header=20files.=0A= +=20=20=20See=20= https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0= .pdf=0A+=20=20=20and=20= https://chromium.googlesource.com/chromium/src/+/master/sandbox/mac/seatbe= lt_sandbox_design.md=20*/=0A+int=20sandbox_init_with_parameters(const=20= char=20*profile,=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20uint64_t=20flags,=0A+=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20const=20char=20*const=20parameters[],=0A+=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20char=20**errorbuf);=0A+=0A+DEFUN=20("darwin-sandbox-init",=20= Fdarwin_sandbox_init,=20Sdarwin_sandbox_init,=0A+=20=20=20=20=20=20=201,=20= 1,=200,=0A+=20=20=20=20=20=20=20doc:=20/*=20Enter=20a=20sandbox=20whose=20= permitted=20access=20is=20curtailed=20by=20PROFILE.=0A+Already=20open=20= descriptors=20can=20be=20used=20freely.=0A+PROFILE=20is=20a=20string=20= in=20the=20macOS=20sandbox=20profile=20language,=0A+a=20set=20of=20rules=20= in=20a=20Lisp-like=20syntax.=0A+=0A+This=20is=20not=20a=20supported=20= interface=20and=20is=20for=20internal=20use=20only.=20*/)=0A+=20=20= (Lisp_Object=20profile)=0A+{=0A+=20=20CHECK_STRING=20(profile);=0A+=20=20= char=20*err=20=3D=20NULL;=0A+=20=20if=20(sandbox_init_with_parameters=20= (SSDATA=20(profile),=200,=20NULL,=20&err)=20!=3D=200)=0A+=20=20=20=20= error=20("sandbox=20error:=20%s",=20err);=0A+=20=20return=20Qnil;=0A+}=0A= +=0A+#endif=09/*=20DARWIN_OS=20*/=0A+=0A=20void=0A=20syms_of_sysdep=20= (void)=0A=20{=0A=20=20=20defsubr=20(&Sget_internal_run_time);=0A+#ifdef=20= DARWIN_OS=0A+=20=20defsubr=20(&Sdarwin_sandbox_init);=0A+#endif=0A=20}=0A= diff=20--git=20a/test/src/emacs-tests.el=20b/test/src/emacs-tests.el=0A= index=2009f9a248ef..c1a741c359=20100644=0A---=20= a/test/src/emacs-tests.el=0A+++=20b/test/src/emacs-tests.el=0A@@=20= -210,4=20+210,74=20@@=20emacs-tests/bwrap/allows-stdout=0A=20=20=20=20=20= =20=20=20=20=20=20(should=20(eql=20status=200)))=0A=20=20=20=20=20=20=20=20= =20(should=20(equal=20(string-trim=20(buffer-string))=20"Hi"))))))=0A=20=0A= +(defun=20emacs-tests--darwin-run-sandboxed-emacs=20(sandbox-dirs=20= body)=0A+=20=20"Run=20Emacs=20and=20evaluate=20BODY,=20only=20allowing=20= reads=20from=20SANDBOX-DIRS.=0A+If=20SANDBOX-DIRS=20is=20`no-sandbox',=20= then=20run=20without=20sandbox.=0A+Return=20(EXIT-STATUS=20.=20OUTPUT),=20= where=20OUTPUT=20is=20stderr=20and=20stdout."=0A+=20=20(let=20((emacs=20= (expand-file-name=20invocation-name=20invocation-directory))=0A+=20=20=20= =20=20=20=20=20(process-environment=20nil))=0A+=20=20=20=20= (with-temp-buffer=0A+=20=20=20=20=20=20(let*=20((prog=20`(progn=0A+=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20,@(and=20= (not=20(eq=20sandbox-dirs=20'no-sandbox))=0A+=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= `((darwin-sandbox-enter=20',sandbox-dirs)))=0A+=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20,@body))=0A+=20=20=20=20=20=20=20=20= =20=20=20=20=20(res=20(call-process=20emacs=20nil=20t=20nil=0A+=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20"--quick"=20"--batch"=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20= "--eval=3D%S"=20prog))))=0A+=20=20=20=20=20=20=20=20(cons=20res=20= (buffer-string))))))=0A+=0A+(ert-deftest=20emacs-tests/darwin-sandbox=20= ()=0A+=20=20(skip-unless=20(eq=20system-type=20'darwin))=0A+=20=20= (emacs-tests--with-temp-file=20test-file=20("test")=0A+=20=20=20=20(let=20= ((some-text=20"abcdef")=0A+=20=20=20=20=20=20=20=20=20=20(other-text=20= "ghijkl")=0A+=20=20=20=20=20=20=20=20=20=20(test-file=20(file-truename=20= test-file)))=20=20=20;=20resolve=20symlinks=0A+=20=20=20=20=20=20= (with-temp-buffer=0A+=20=20=20=20=20=20=20=20(insert=20some-text)=0A+=20=20= =20=20=20=20=20=20(write-file=20test-file))=0A+=0A+=20=20=20=20=20=20;;=20= Read=20the=20file=20without=20allowing=20its=20directory=20--=20should=20= fail.=0A+=20=20=20=20=20=20(let=20((res-out=20= (emacs-tests--darwin-run-sandboxed-emacs=0A+=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20nil=0A+=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20`((find-file-literally=20,test-file)=0A= +=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (message=20"OK:=20%s"=20(buffer-string))))))=0A+=20=20=20=20=20=20=20=20= (ert-info=20((cdr=20res-out)=20:prefix=20"output:=20")=0A+=20=20=20=20=20= =20=20=20=20=20(should-not=20(equal=20(car=20res-out)=200))=0A+=20=20=20=20= =20=20=20=20=20=20(should-not=20(string-search=20some-text=20(cdr=20= res-out)))))=0A+=0A+=20=20=20=20=20=20;;=20Read=20the=20file=20allowing=20= its=20directory=20--=20should=20succeed.=0A+=20=20=20=20=20=20(let=20= ((res-out=20(emacs-tests--darwin-run-sandboxed-emacs=0A+=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(list=20= (file-name-directory=20test-file))=0A+=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20`((find-file-literally=20,test-file)=0A+=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (message=20"OK:=20%s"=20(buffer-string))))))=0A+=20=20=20=20=20=20=20=20= (should=20(equal=20res-out=20(cons=200=20(format=20"OK:=20%s\n"=20= some-text)))))=0A+=0A+=20=20=20=20=20=20;;=20Write=20to=20the=20file=20= allowing=20directory=20reads=20--=20should=20fail.=0A+=20=20=20=20=20=20= (let=20((res-out=20(emacs-tests--darwin-run-sandboxed-emacs=0A+=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(list=20= (file-name-directory=20test-file))=0A+=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20`((with-temp-buffer=0A+=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(insert=20= ,other-text)=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20(write-file=20,test-file))))))=0A+=20=20=20=20=20=20= =20=20(ert-info=20((cdr=20res-out)=20:prefix=20"output:=20")=0A+=20=20=20= =20=20=20=20=20=20=20(should-not=20(equal=20(car=20res-out)=200))=0A+=20=20= =20=20=20=20=20=20=20=20;;=20The=20file=20should=20be=20unchanged.=0A+=20= =20=20=20=20=20=20=20=20=20(let=20((contents=20(with-temp-buffer=0A+=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(insert-file-contents-literally=20test-file)=0A+=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (buffer-string))))=0A+=20=20=20=20=20=20=20=20=20=20=20=20(should=20= (equal=20contents=20some-text)))))=0A+=0A+=20=20=20=20=20=20;;=20Write=20= to=20the=20file=20without=20sandbox=20--=20should=20succeed.=0A+=20=20=20= =20=20=20(let=20((res-out=20(emacs-tests--darwin-run-sandboxed-emacs=0A+=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= 'no-sandbox=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20`((with-temp-buffer=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(insert=20,other-text)=0A+=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (write-file=20,test-file))))))=0A+=20=20=20=20=20=20=20=20(ert-info=20= ((cdr=20res-out)=20:prefix=20"output:=20")=0A+=20=20=20=20=20=20=20=20=20= =20(should=20(equal=20(car=20res-out)=200))=0A+=20=20=20=20=20=20=20=20=20= =20;;=20The=20file=20should=20be=20changed.=0A+=20=20=20=20=20=20=20=20=20= =20(let=20((contents=20(with-temp-buffer=0A+=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (insert-file-contents-literally=20test-file)=0A+=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (buffer-string))))=0A+=20=20=20=20=20=20=20=20=20=20=20=20(should=20= (equal=20contents=20other-text))))))))=0A+=0A=20;;;=20emacs-tests.el=20= ends=20here=0A--=20=0A2.21.1=20(Apple=20Git-122.3)=0A=0A= --Apple-Mail=_64FD6A78-046E-4E8B-8A46-7F258849914F--