services: setuid: More configurable setuid support.

OpenSubmitted by Christopher Lemmer Webber.
Details
3 participants
  • Christopher Lemmer Webber
  • Ludovic Courtès
  • Maxim Cournoyer
Owner
unassigned
Severity
normal
C
C
Christopher Lemmer Webber wrote on 17 Nov 2020 00:29
(address . guix-patches@gnu.org)
874klog9tk.fsf@dustycloud.org
This patch allows for configuring the specific user, group, and whetherto set the setuid and setgid bits.
See also: https://lists.gnu.org/archive/html/guix-devel/2020-11/msg00369.html
But I thought I'd open this here so we could track changes since this istechnically independent of the postfix stuff. Anyway, patch attached.One change since the last email above is that I added support forstring-based username/groups.
This also needs documentation, I suppose, so that should be done.But it would be good to know if this patch looks like it's on the "rightpath" or not.
From eadac673fb22132c555a4e1cee57a6308ecfdad4 Mon Sep 17 00:00:00 2001From: Christopher Lemmer Webber <cwebber@dustycloud.org>Date: Sun, 15 Nov 2020 16:58:52 -0500Subject: [PATCH] services: setuid: More configurable setuid support.
New record <setuid-program> with fields for setting the specific user andgroup, as well as specifically selecting the setuid and setgid bits, for aprogram within the setuid-program-service.
* gnu/services.scm (<setuid-program>): New record type. (setuid-program, make-setuid-program, setuid-program?) (setuid-program-program, stuid-program-setuid?, setuid-program-setgid?) (setuid-program-user, setuid-program-group): New variables, export them. (setuid-program-entry): New variable, a procedure used for the service-extension of activation-service-type as set up by setuid-program-service-type. Unpacks the <setuid-program> record, handing off within the gexp to activate-setuid-programs. (setuid-program-service-type): Make use of setuid-program-entry.* gnu/build/activation.scm (activate-setuid-programs): Update to expect a ftagged list for each program entry, pre-unpacked from the <setuid-program> record before being handed to this procedure.--- gnu/build/activation.scm | 46 +++++++++++++++++++++---------------- gnu/services.scm | 49 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 22 deletions(-)
Toggle diff (137 lines)diff --git a/gnu/build/activation.scm b/gnu/build/activation.scmindex 4b67926e88..fd17ce0434 100644--- a/gnu/build/activation.scm+++ b/gnu/build/activation.scm@@ -229,13 +229,6 @@ they already exist." (define (activate-setuid-programs programs) "Turn PROGRAMS, a list of file names, into setuid programs stored under %SETUID-DIRECTORY."- (define (make-setuid-program prog)- (let ((target (string-append %setuid-directory- "/" (basename prog))))- (copy-file prog target)- (chown target 0 0)- (chmod target #o6555)))- (format #t "setting up setuid programs in '~a'...~%" %setuid-directory) (if (file-exists? %setuid-directory)@@ -247,18 +240,33 @@ they already exist." string<?)) (mkdir-p %setuid-directory)) - (for-each (lambda (program)- (catch 'system-error- (lambda ()- (make-setuid-program program))- (lambda args- ;; If we fail to create a setuid program, better keep going- ;; so that we don't leave %SETUID-DIRECTORY empty or- ;; half-populated. This can happen if PROGRAMS contains- ;; incorrect file names: <https://bugs.gnu.org/38800>.- (format (current-error-port)- "warning: failed to make '~a' setuid-root: ~a~%"- program (strerror (system-error-errno args))))))+ (for-each (match-lambda+ [('setuid-program src-path setuid? setgid? user group)+ (let ((uid (match user+ [(? string?) (passwd:uid (getpwnam user))]+ [(? integer?) user]))+ (gid (match group+ [(? string?) (group:gid (getgrnam user))]+ [(? integer?) group])))+ (catch 'system-error+ (lambda ()+ (let ((target (string-append %setuid-directory+ "/" (basename src-path)))+ (mode (+ #o0555 ; base permissions+ (if setuid? #o4000 0) ; setuid bit+ (if setgid? #o2000 0)))) ; setgid bit+ (copy-file src-path target)+ (chown target uid gid)+ (chmod target mode)))+ (lambda args+ ;; If we fail to create a setuid program, better keep going+ ;; so that we don't leave %SETUID-DIRECTORY empty or+ ;; half-populated. This can happen if PROGRAMS contains+ ;; incorrect file names: <https://bugs.gnu.org/38800>.+ (format (current-error-port)+ "warning: failed to make '~a' setuid-root: ~a~%"+ (setuid-program-program program)+ (strerror (system-error-errno args))))))]) programs)) (define (activate-special-files special-files)diff --git a/gnu/services.scm b/gnu/services.scmindex 4b30399adc..a5b4734152 100644--- a/gnu/services.scm+++ b/gnu/services.scm@@ -87,6 +87,14 @@ ambiguous-target-service-error-service ambiguous-target-service-error-target-type + setuid-program+ setuid-program?+ setuid-program-program+ setuid-program-setuid?+ setuid-program-setgid?+ setuid-program-user+ setuid-program-group+ system-service-type provenance-service-type sexp->system-provenance@@ -773,13 +781,48 @@ directory." FILES must be a list of name/file-like object pairs." (service etc-service-type files)) +(define-record-type* <setuid-program> setuid-program make-setuid-program+ setuid-program?+ ;; Path to program to link with setuid permissions+ (program setuid-program-program) ;string+ ;; Whether to set user setuid bit+ (setuid? setuid-program-setuid? ;boolean+ (default #t))+ ;; Whether to set user setgid bit+ (setgid? setuid-program-setgid? ;boolean+ (default #t))+ ;; The user this should be set to (defaults to root)+ (user setuid-program-user ;integer or string+ (default 0))+ ;; Group we want to set this to (defaults to root)+ (group setuid-program-group ;integer or string+ (default 0)))++(define (setuid-program-entry programs)+ #~(activate-setuid-programs+ ;; convert into a tagged list structure as expected by+ ;; activate-setuid-programs+ (list #$@(map (match-lambda+ [(? setuid-program? sp)+ #~(list 'setuid-program+ #$(setuid-program-program sp)+ #$(setuid-program-setuid? sp)+ #$(setuid-program-setgid? sp)+ #$(setuid-program-user sp)+ #$(setuid-program-group sp))]+ ;; legacy, non-<setuid-program> structure+ [program+ ;; TODO: Spit out a warning here?+ #~(list 'setuid-program+ #$program+ #t #t 0 0)])+ programs))))+ (define setuid-program-service-type (service-type (name 'setuid-program) (extensions (list (service-extension activation-service-type- (lambda (programs)- #~(activate-setuid-programs- (list #$@programs))))))+ setuid-program-entry))) (compose concatenate) (extend append) (description-- 2.29.1
L
L
Ludovic Courtès wrote on 17 Nov 2020 10:46
(name . Christopher Lemmer Webber)(address . cwebber@dustycloud.org)(address . 44700@debbugs.gnu.org)
87r1oss4dg.fsf@gnu.org
Hello!
Christopher Lemmer Webber <cwebber@dustycloud.org> skribis:
Toggle quote (22 lines)>>From eadac673fb22132c555a4e1cee57a6308ecfdad4 Mon Sep 17 00:00:00 2001> From: Christopher Lemmer Webber <cwebber@dustycloud.org>> Date: Sun, 15 Nov 2020 16:58:52 -0500> Subject: [PATCH] services: setuid: More configurable setuid support.>> New record <setuid-program> with fields for setting the specific user and> group, as well as specifically selecting the setuid and setgid bits, for a> program within the setuid-program-service.>> * gnu/services.scm (<setuid-program>): New record type.> (setuid-program, make-setuid-program, setuid-program?)> (setuid-program-program, stuid-program-setuid?, setuid-program-setgid?)> (setuid-program-user, setuid-program-group): New variables, export them.> (setuid-program-entry): New variable, a procedure used for the> service-extension of activation-service-type as set up by> setuid-program-service-type. Unpacks the <setuid-program> record,> handing off within the gexp to activate-setuid-programs.> (setuid-program-service-type): Make use of setuid-program-entry.> * gnu/build/activation.scm (activate-setuid-programs): Update to expect a> ftagged list for each program entry, pre-unpacked from the <setuid-program>> record before being handed to this procedure.
This looks like the right approach to me!
Toggle quote (19 lines)> + (for-each (match-lambda> + [('setuid-program src-path setuid? setgid? user group)> + (let ((uid (match user> + [(? string?) (passwd:uid (getpwnam user))]> + [(? integer?) user]))> + (gid (match group> + [(? string?) (group:gid (getgrnam user))]> + [(? integer?) group])))> + (catch 'system-error> + (lambda ()> + (let ((target (string-append %setuid-directory> + "/" (basename src-path)))> + (mode (+ #o0555 ; base permissions> + (if setuid? #o4000 0) ; setuid bit> + (if setgid? #o2000 0)))) ; setgid bit> + (copy-file src-path target)> + (chown target uid gid)> + (chmod target mode)))
Nitpick: I’d write “program” or “source” instead of “src-path” and avoidsquare brackets for consistency with the rest of the code base (youspent time in Racket-land, didn’t you? ;-)).
Toggle quote (20 lines)> +(define (setuid-program-entry programs)> + #~(activate-setuid-programs> + ;; convert into a tagged list structure as expected by> + ;; activate-setuid-programs> + (list #$@(map (match-lambda> + [(? setuid-program? sp)> + #~(list 'setuid-program> + #$(setuid-program-program sp)> + #$(setuid-program-setuid? sp)> + #$(setuid-program-setgid? sp)> + #$(setuid-program-user sp)> + #$(setuid-program-group sp))]> + ;; legacy, non-<setuid-program> structure> + [program> + ;; TODO: Spit out a warning here?> + #~(list 'setuid-program> + #$program> + #t #t 0 0)])> + programs))))
Maybe what we could do is rename ‘operating-system-setuid-programs’ to’%operating-system-setuid-programs’, keep that internal, and add a new‘operating-system-setuid-programs’ that calls the other one and“canonicalizes” list entries so that they’re all <setuid-program>records.
It would call:
(warning log (G_ "representing setuid programs with strings is \deprecated; use 'setuid-program' instead~%"))
WDYT?
Could you also update the “Setuid Programs” section of the manual?
In a subsequent commit, we need to adjust all the services that extend‘setuid-program-service-type’ so they pass a <setuid-program> and not astring.
Thanks!
Ludo’.
M
M
Maxim Cournoyer wrote on 17 Nov 2020 17:29
(name . Christopher Lemmer Webber)(address . cwebber@dustycloud.org)(address . 44700@debbugs.gnu.org)
875z64aqvw.fsf@gmail.com
Hello Christopher,
Christopher Lemmer Webber <cwebber@dustycloud.org> writes:
Toggle quote (24 lines)> This patch allows for configuring the specific user, group, and whether> to set the setuid and setgid bits.>> See also:> https://lists.gnu.org/archive/html/guix-devel/2020-11/msg00369.html>> But I thought I'd open this here so we could track changes since this is> technically independent of the postfix stuff. Anyway, patch attached.> One change since the last email above is that I added support for> string-based username/groups.>> This also needs documentation, I suppose, so that should be done.> But it would be good to know if this patch looks like it's on the "right> path" or not.>> From eadac673fb22132c555a4e1cee57a6308ecfdad4 Mon Sep 17 00:00:00 2001> From: Christopher Lemmer Webber <cwebber@dustycloud.org>> Date: Sun, 15 Nov 2020 16:58:52 -0500> Subject: [PATCH] services: setuid: More configurable setuid support.>> New record <setuid-program> with fields for setting the specific user and> group, as well as specifically selecting the setuid and setgid bits, for a> program within the setuid-program-service.
Please make this a full sentence, e.g. "This adds a new record [...]".
Toggle quote (12 lines)>> * gnu/services.scm (<setuid-program>): New record type.> (setuid-program, make-setuid-program, setuid-program?)> (setuid-program-program, stuid-program-setuid?, setuid-program-setgid?)> (setuid-program-user, setuid-program-group): New variables, export them.> (setuid-program-entry): New variable, a procedure used for the> service-extension of activation-service-type as set up by> setuid-program-service-type. Unpacks the <setuid-program> record,> handing off within the gexp to activate-setuid-programs.> (setuid-program-service-type): Make use of setuid-program-entry.> * gnu/build/activation.scm (activate-setuid-programs): Update to expect a> ftagged list for each program entry, pre-unpacked from the <setuid-program>
^tagged
Toggle quote (2 lines)> record before being handed to this procedure.
The doc needs to be updated, as well as the current uses in the codebase.
Toggle quote (21 lines)> ---> gnu/build/activation.scm | 46 +++++++++++++++++++++----------------> gnu/services.scm | 49 +++++++++++++++++++++++++++++++++++++---> 2 files changed, 73 insertions(+), 22 deletions(-)>> diff --git a/gnu/build/activation.scm b/gnu/build/activation.scm> index 4b67926e88..fd17ce0434 100644> --- a/gnu/build/activation.scm> +++ b/gnu/build/activation.scm> @@ -229,13 +229,6 @@ they already exist."> (define (activate-setuid-programs programs)> "Turn PROGRAMS, a list of file names, into setuid programs stored under> %SETUID-DIRECTORY."> - (define (make-setuid-program prog)> - (let ((target (string-append %setuid-directory> - "/" (basename prog))))> - (copy-file prog target)> - (chown target 0 0)> - (chmod target #o6555)))> -
I think it'd be nicer to keep that procedure here and extend it with thelogic added below, for readability.
Toggle quote (21 lines)> (format #t "setting up setuid programs in '~a'...~%"> %setuid-directory)> (if (file-exists? %setuid-directory)> @@ -247,18 +240,33 @@ they already exist."> string<?))> (mkdir-p %setuid-directory))>> - (for-each (lambda (program)> - (catch 'system-error> - (lambda ()> - (make-setuid-program program))> - (lambda args> - ;; If we fail to create a setuid program, better keep going> - ;; so that we don't leave %SETUID-DIRECTORY empty or> - ;; half-populated. This can happen if PROGRAMS contains> - ;; incorrect file names: <https://bugs.gnu.org/38800>.> - (format (current-error-port)> - "warning: failed to make '~a' setuid-root: ~a~%"> - program (strerror (system-error-errno args))))))> + (for-each (match-lambda> + [('setuid-program src-path setuid? setgid? user group)
^There's a convention to not use square brackets inthe Guix code base, for uniformity.
Toggle quote (7 lines)> + (let ((uid (match user> + [(? string?) (passwd:uid (getpwnam user))]> + [(? integer?) user]))> + (gid (match group> + [(? string?) (group:gid (getgrnam user))]> + [(? integer?) group])))
The above code raise an un-handled exception, for example if the user orgroup used doesn't exist. It should be moved to the aboveMAKE-SETUID-PROGRAM procedure and called inside the guard.
Toggle quote (18 lines)> + (catch 'system-error> + (lambda ()> + (let ((target (string-append %setuid-directory> + "/" (basename src-path)))> + (mode (+ #o0555 ; base permissions> + (if setuid? #o4000 0) ; setuid bit> + (if setgid? #o2000 0)))) ; setgid bit> + (copy-file src-path target)> + (chown target uid gid)> + (chmod target mode)))> + (lambda args> + ;; If we fail to create a setuid program, better keep going> + ;; so that we don't leave %SETUID-DIRECTORY empty or> + ;; half-populated. This can happen if PROGRAMS contains> + ;; incorrect file names: <https://bugs.gnu.org/38800>.> + (format (current-error-port)> + "warning: failed to make '~a' setuid-root: ~a~%"
The above message should be adapted to say "failed to make ~ssetuid/setgid: ~a~%"
Toggle quote (39 lines)> + (setuid-program-program program)> + (strerror (system-error-errno args))))))])> programs))>> (define (activate-special-files special-files)> diff --git a/gnu/services.scm b/gnu/services.scm> index 4b30399adc..a5b4734152 100644> --- a/gnu/services.scm> +++ b/gnu/services.scm> @@ -87,6 +87,14 @@> ambiguous-target-service-error-service> ambiguous-target-service-error-target-type>> + setuid-program> + setuid-program?> + setuid-program-program> + setuid-program-setuid?> + setuid-program-setgid?> + setuid-program-user> + setuid-program-group> +> system-service-type> provenance-service-type> sexp->system-provenance> @@ -773,13 +781,48 @@ directory."> FILES must be a list of name/file-like object pairs."> (service etc-service-type files))>> +(define-record-type* <setuid-program> setuid-program make-setuid-program> + setuid-program?> + ;; Path to program to link with setuid permissions> + (program setuid-program-program) ;string> + ;; Whether to set user setuid bit> + (setuid? setuid-program-setuid? ;boolean> + (default #t))> + ;; Whether to set user setgid bit> + (setgid? setuid-program-setgid? ;boolean> + (default #t))
This departs from the previous default (not setgid was set). It'sprobably more explicit to be set to #f as default, since the service isstill named 'setuid-program-service-type', so having it do gid stuff bydefault could come as a surprise.
Toggle quote (22 lines)> + ;; The user this should be set to (defaults to root)> + (user setuid-program-user ;integer or string> + (default 0))> + ;; Group we want to set this to (defaults to root)> + (group setuid-program-group ;integer or string> + (default 0)))> +(define (setuid-program-entry programs)> + #~(activate-setuid-programs> + ;; convert into a tagged list structure as expected by> + ;; activate-setuid-programs> + (list #$@(map (match-lambda> + [(? setuid-program? sp)> + #~(list 'setuid-program> + #$(setuid-program-program sp)> + #$(setuid-program-setuid? sp)> + #$(setuid-program-setgid? sp)> + #$(setuid-program-user sp)> + #$(setuid-program-group sp))]> + ;; legacy, non-<setuid-program> structure> + [program> + ;; TODO: Spit out a warning here?
A deprecation message should be printed, urging the users to use the newinterface, yes.
Toggle quote (17 lines)> + #~(list 'setuid-program> + #$program> + #t #t 0 0)])> + programs))))> +> (define setuid-program-service-type> (service-type (name 'setuid-program)> (extensions> (list (service-extension activation-service-type> - (lambda (programs)> - #~(activate-setuid-programs> - (list #$@programs))))))> + setuid-program-entry)))> (compose concatenate)> (extend append)> (description
With the above comments, this looks like a good change to me! I haven'ttested it yet, but intend to do so when I have a chance!
Thank you for working on it,
Maxim
C
C
Christopher Lemmer Webber wrote on 17 Nov 2020 17:31
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 44700@debbugs.gnu.org)
87sg98djxq.fsf@dustycloud.org
Ludovic Courtès writes:
Toggle quote (51 lines)> Hello!>> Christopher Lemmer Webber <cwebber@dustycloud.org> skribis:>>>>From eadac673fb22132c555a4e1cee57a6308ecfdad4 Mon Sep 17 00:00:00 2001>> From: Christopher Lemmer Webber <cwebber@dustycloud.org>>> Date: Sun, 15 Nov 2020 16:58:52 -0500>> Subject: [PATCH] services: setuid: More configurable setuid support.>>>> New record <setuid-program> with fields for setting the specific user and>> group, as well as specifically selecting the setuid and setgid bits, for a>> program within the setuid-program-service.>>>> * gnu/services.scm (<setuid-program>): New record type.>> (setuid-program, make-setuid-program, setuid-program?)>> (setuid-program-program, stuid-program-setuid?, setuid-program-setgid?)>> (setuid-program-user, setuid-program-group): New variables, export them.>> (setuid-program-entry): New variable, a procedure used for the>> service-extension of activation-service-type as set up by>> setuid-program-service-type. Unpacks the <setuid-program> record,>> handing off within the gexp to activate-setuid-programs.>> (setuid-program-service-type): Make use of setuid-program-entry.>> * gnu/build/activation.scm (activate-setuid-programs): Update to expect a>> ftagged list for each program entry, pre-unpacked from the <setuid-program>>> record before being handed to this procedure.>> This looks like the right approach to me!>>> + (for-each (match-lambda>> + [('setuid-program src-path setuid? setgid? user group)>> + (let ((uid (match user>> + [(? string?) (passwd:uid (getpwnam user))]>> + [(? integer?) user]))>> + (gid (match group>> + [(? string?) (group:gid (getgrnam user))]>> + [(? integer?) group])))>> + (catch 'system-error>> + (lambda ()>> + (let ((target (string-append %setuid-directory>> + "/" (basename src-path)))>> + (mode (+ #o0555 ; base permissions>> + (if setuid? #o4000 0) ; setuid bit>> + (if setgid? #o2000 0)))) ; setgid bit>> + (copy-file src-path target)>> + (chown target uid gid)>> + (chmod target mode)))>> Nitpick: I’d write “program” or “source” instead of “src-path” and avoid> square brackets for consistency with the rest of the code base (you> spent time in Racket-land, didn’t you? ;-)).
Sounds good. And yes, Racket influence is shining through, oops!
Toggle quote (26 lines)>> +(define (setuid-program-entry programs)>> + #~(activate-setuid-programs>> + ;; convert into a tagged list structure as expected by>> + ;; activate-setuid-programs>> + (list #$@(map (match-lambda>> + [(? setuid-program? sp)>> + #~(list 'setuid-program>> + #$(setuid-program-program sp)>> + #$(setuid-program-setuid? sp)>> + #$(setuid-program-setgid? sp)>> + #$(setuid-program-user sp)>> + #$(setuid-program-group sp))]>> + ;; legacy, non-<setuid-program> structure>> + [program>> + ;; TODO: Spit out a warning here?>> + #~(list 'setuid-program>> + #$program>> + #t #t 0 0)])>> + programs))))>> Maybe what we could do is rename ‘operating-system-setuid-programs’ to> ’%operating-system-setuid-programs’, keep that internal, and add a new> ‘operating-system-setuid-programs’ that calls the other one and> “canonicalizes” list entries so that they’re all <setuid-program>> records.
"rename"? There is no operating-system-setuid-programs so I'm not surewhat you mean to rename from... setuid-program-entry, or presumablyactivate-setuid-programs...?
Toggle quote (5 lines)> It would call:>> (warning log (G_ "representing setuid programs with strings is \> deprecated; use 'setuid-program' instead~%"))
Aha, I wasn't sure what to use for deprecation warnings actually, sothis is helpful, thanks!
Toggle quote (4 lines)> WDYT?>> Could you also update the “Setuid Programs” section of the manual?
Happy to do it.
Toggle quote (4 lines)> In a subsequent commit, we need to adjust all the services that extend> ‘setuid-program-service-type’ so they pass a <setuid-program> and not a> string.
Yes... let's worry about that once this interface is hammered out. :)
Glad it seems like the general approach was right though!
Toggle quote (3 lines)> Thanks!>> Ludo’.
L
L
Ludovic Courtès wrote on 17 Nov 2020 21:48
(name . Christopher Lemmer Webber)(address . cwebber@dustycloud.org)(address . 44700@debbugs.gnu.org)
87d00bpv5h.fsf@gnu.org
Hi Chris!
Christopher Lemmer Webber <cwebber@dustycloud.org> skribis:
Toggle quote (2 lines)> Ludovic Courtès writes:
[...]
Toggle quote (10 lines)>> Maybe what we could do is rename ‘operating-system-setuid-programs’ to>> ’%operating-system-setuid-programs’, keep that internal, and add a new>> ‘operating-system-setuid-programs’ that calls the other one and>> “canonicalizes” list entries so that they’re all <setuid-program>>> records.>> "rename"? There is no operating-system-setuid-programs so I'm not sure> what you mean to rename from... setuid-program-entry, or presumably> activate-setuid-programs...?
I’m referring to the <operating-system> accessor called‘operating-system-setuid-programs’, in (gnu system).
Does that make sense?
Thanks,Ludo’.
C
C
Christopher Lemmer Webber wrote on 14 Apr 19:06 +0200
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 44700@debbugs.gnu.org)
87v98o94ob.fsf@dustycloud.org
Ludovic Courtès writes:
Toggle quote (21 lines)> Hi Chris!>> Christopher Lemmer Webber <cwebber@dustycloud.org> skribis:>>> Ludovic Courtès writes:>> [...]>>>> Maybe what we could do is rename ‘operating-system-setuid-programs’ to>>> ’%operating-system-setuid-programs’, keep that internal, and add a new>>> ‘operating-system-setuid-programs’ that calls the other one and>>> “canonicalizes” list entries so that they’re all <setuid-program>>>> records.>>>> "rename"? There is no operating-system-setuid-programs so I'm not sure>> what you mean to rename from... setuid-program-entry, or presumably>> activate-setuid-programs...?>> I’m referring to the <operating-system> accessor called> ‘operating-system-setuid-programs’, in (gnu system).
I think it makes sense from the fog of my memory of this issue. But I'malso going to note: I haven't gotten to this in a while, and I feelguilty about that. :(
I'm very overwhelmed right now. If nobody picks this up where I left itoff I probably can, but I am probably blocked for the next couple ofmonths with urgent tasks... which is a shame for something that lookedso close to landing. If anyone wants to get this to the last mile andaddress Ludo's feedback they are welcome to in the meanwhile.
?