9front - general discussion about 9front
 help / color / mirror / Atom feed
From: Stanley Lieber <sl@stanleylieber.com>
To: 9front@9front.org
Subject: Re: [9front] [PATCH] Unmount to remove sharp devices.
Date: Wed, 11 May 2022 12:11:28 -0400	[thread overview]
Message-ID: <E811DF5E-82ED-48AB-9C36-C75948E4C5C4@stanleylieber.com> (raw)
In-Reply-To: <51d86b8f-ac14-3460-0fea-979c93c45877@posixcafe.org>

in some cases, would it make more sense to invoke auth/newns at the listener level, rather than every time rc-httpd is called?

al

> On May 11, 2022, at 10:49 AM, Jacob Moody <moody@mail.posixcafe.org> wrote:
> 
> Hello,
> 
> Another minor iteration. This tweaks the rc-httpd sandboxing
> and documents it better in the man page.
> 
> thanks,
> moody
> 
> diff 9126ee3eea90d639f4e877c01400248581d10f65 uncommitted
> --- a/lib/namespace
> +++ b/lib/namespace
> @@ -1,5 +1,6 @@
> # root
> mount -aC #s/boot /root $rootspec
> +bind $rootdir'/rc' /rc
> bind -a $rootdir /
> 
> # kernel devices
> --- a/lib/namespace.ftp
> +++ b/lib/namespace.ftp
> @@ -8,5 +8,6 @@
> # bind a personal incoming directory below incoming
> bind -c /usr/none/incoming /usr/web/incoming/none
> 
> +permit |MedIa/
> # this cuts off everything not mounted below /usr/web
> bind /usr/web /
> --- /tmp/diff100001081045
> +++ b/lib/namespace.rc-httpd
> @@ -1,0 +1,19 @@
> +mount -aC #s/boot /root $rootspec
> +
> +# kernel devices
> +bind #c /dev
> +bind #d /fd
> +bind -c #e /env
> +bind #p /proc
> +
> +bind /root/$cputype/bin /bin
> +bind /root/rc /rc
> +bind -a /rc/bin /bin
> +
> +permit Mcde|ps/
> +
> +. /root/cfg/$sysname/namespace.rc-httpd
> +
> +unmount /root
> +block Mcs
> +unmount #c /dev
> --- a/rc/bin/service/!tcp80
> +++ b/rc/bin/service/!tcp80
> @@ -1,2 +1,2 @@
> #!/bin/rc
> -exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +exec auth/newns -n /lib/namespace.rc-httpd /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> --- a/sys/man/3/cons
> +++ b/sys/man/3/cons
> @@ -90,10 +90,30 @@
> .PP
> The
> .B drivers
> -file contains, one per line, a listing of the drivers configured in the kernel, in the format
> +file contains, one per line, a listing of available kernel drivers, in the format
> .IP
> .EX
> #c cons
> +.EE
> +.PP
> +A process can forfeit access to a driver, for itself and it's future children, through a write to
> +.B drivers.
> +A message is one of:
> +.IP "block \f2drivers\fP"
> +block access to the listed
> +.I drivers.
> +.IP "permit \f2drivers\fP"
> +permit access to just the provided
> +.I drivers.
> +.PP
> +Drivers are identified by their short hand, the first column returned on reads,
> +without the leading sharp. The following blocks access to
> +.IR env (3)
> +and
> +.IR sd (3):
> +.IP
> +.EX
> +block se
> .EE
> .PP
> The
> --- a/sys/man/6/namespace
> +++ b/sys/man/6/namespace
> @@ -59,6 +59,15 @@
> .I new
> is missing.
> .TP
> +.BI block \ drivers
> +Removes access to the listed kernel
> +.I drivers.
> +Devices are identified by their sharp name.
> +.TP
> +.BI permit \ drivers
> +Permit access to only the listed kernel
> +.I drivers.
> +.TP
> .BR clear
> Clear the name space with
> .BR rfork(RFCNAMEG) .
> @@ -80,4 +89,5 @@
> .SH "SEE ALSO"
> .IR bind (1),
> .IR namespace (4),
> -.IR init (8)
> +.IR init (8),
> +.IR cons (3)
> --- a/sys/man/8/rc-httpd
> +++ b/sys/man/8/rc-httpd
> @@ -85,6 +85,36 @@
> .I REMOTE_USER
> variable provides a user identification string supplied by the
> client as part of user authentication.
> +.SH STARTUP
> +.I Rc-httpd
> +is run from a file in the directory scanned by
> +.IR listen (8),
> +or called as an argument to aux/listen1.
> +The program's standard error may be captured to a log file:
> +.RS
> +.EX
> +exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +.EE
> +.RE
> +.PP
> +It is recommended to provide a strict namespace for
> +.I rc-httpd.
> +.B /lib/namespace.rc-httpd
> +provides an example of such a namespace, and serves as
> +a default.
> +.PP
> +When using the default,
> +.B /cfg/$sysname/namespace.rc-httpd
> +is sourced to allow for binding of files to a place
> +.B select-handler
> +may expect to find them.
> +.PP
> +.I Rc-httpd
> +can be run using
> +.IR auth/newns (8)
> +to switch to the restrictive namespace before starting.
> +.B /rc/bin/service/!tcp80
> +provides a sample invocation.
> .SH EXAMPLES
> The following examples demonstrate possible ways to configure
> .BR select-handler.
> @@ -153,15 +183,19 @@
> }
> .EE
> .RE
> -.SH STARTUP
> -.I Rc-httpd
> -is run from a file in the directory scanned by
> -.IR listen (8),
> -or called as an argument to aux/listen1.
> -The program's standard error may be captured to a log file:
> +.PP
> +An example
> +.B /cfg/$sysname/namespace.rc-httpd:
> .RS
> .EX
> -exec /rc/bin/rc-httpd/rc-httpd >>[2]/sys/log/www
> +# #s/boot is present on /root.
> +# /root is unmounted after we finish.
> +
> +# srv is unused, we can use it as our base FS_ROOT.
> +bind /root/usr/moody/www /srv
> +
> +# use a specific select-handler.
> +bind -b /root/usr/moody/lib/rc-httpd /rc/bin/rc-httpd
> .EE
> .RE
> .SH FILES
> @@ -190,6 +224,10 @@
> .B /rc/bin/service/tcp80
> .TP
> .B /sys/log/www
> +.TP
> +.B /lib/namespace.rc-httpd
> +.TP
> +.B /cfg/$sysname/namespace.rc-httpd
> .SH SOURCE
> .B /rc/bin/rc-httpd
> .SH "SEE ALSO"
> --- a/sys/src/9/boot/boot.c
> +++ b/sys/src/9/boot/boot.c
> @@ -19,6 +19,7 @@
>    if(await(buf, sizeof(buf)) < 0)
>        goto Err;
> 
> +    bind("/root/rc", "/rc", MREPL);
>    bind(root, "/", MAFTER);
> 
>    buf[0] = '/';
> --- a/sys/src/9/port/chan.c
> +++ b/sys/src/9/port/chan.c
> @@ -1272,7 +1272,7 @@
> Chan*
> namec(char *aname, int amode, int omode, ulong perm)
> {
> -    int len, n, t, nomount;
> +    int len, n, t, nomount, devunmount;
>    Chan *c;
>    Chan *volatile cnew;
>    Path *volatile path;
> @@ -1292,6 +1292,24 @@
>    name = aname;
> 
>    /*
> +     * When unmounting, the name parameter must be accessed
> +     * using Aopen in order to get the real chan from
> +     * something like /srv/cs or /fd/0. However when sandboxing,
> +     * unmounting a sharp from a union is a valid operation even
> +     * if the device is blocked.
> +     */
> +    if(amode == Aunmount){
> +        /*
> +         * Doing any walks down the device could leak information
> +         * about the existence of files.
> +         */
> +        if(name[0] == '#' && utflen(name) == 2)
> +            devunmount = 1;
> +        amode = Aopen;
> +    } else
> +        devunmount = 0;
> +
> +    /*
>     * Find the starting off point (the current slash, the root of
>     * a device tree, or the current dot) as well as the name to
>     * evaluate starting there.
> @@ -1313,24 +1331,13 @@
>            up->genbuf[n++] = *name++;
>        }
>        up->genbuf[n] = '\0';
> -        /*
> -         *  noattach is sandboxing.
> -         *
> -         *  the OK exceptions are:
> -         *    |  it only gives access to pipes you create
> -         *    d  this process's file descriptors
> -         *    e  this process's environment
> -         *  the iffy exceptions are:
> -         *    c  time and pid, but also cons and consctl
> -         *    p  control of your own processes (and unfortunately
> -         *       any others left unprotected)
> -         */
>        n = chartorune(&r, up->genbuf+1)+1;
> -        if(up->pgrp->noattach && utfrune("|decp", r)==nil)
> -            error(Enoattach);
>        t = devno(r, 1);
>        if(t == -1)
>            error(Ebadsharp);
> +        if(!devunmount && !driversallowed(up->pgrp, r))
> +            error(Enoattach);
> +
>        c = devtab[t]->attach(up->genbuf+n);
>        break;
> 
> --- a/sys/src/9/port/devcons.c
> +++ b/sys/src/9/port/devcons.c
> @@ -39,6 +39,18 @@
>    CMrdb,        "rdb",        0,
> };
> 
> +enum
> +{
> +    CMblock,
> +    CMpermit,
> +};
> +
> +Cmdtab drivermsg[] =
> +{
> +    CMblock,    "block",    0,
> +    CMpermit,    "permit",    0,
> +};
> +
> void
> printinit(void)
> {
> @@ -332,7 +344,7 @@
>    "cons",        {Qcons},    0,        0660,
>    "consctl",    {Qconsctl},    0,        0220,
>    "cputime",    {Qcputime},    6*NUMSIZE,    0444,
> -    "drivers",    {Qdrivers},    0,        0444,
> +    "drivers",    {Qdrivers},    0,        0666,
>    "hostdomain",    {Qhostdomain},    DOMLEN,        0664,
>    "hostowner",    {Qhostowner},    0,        0664,
>    "kmesg",    {Qkmesg},    0,        0440,
> @@ -583,9 +595,15 @@
>    case Qdrivers:
>        b = smalloc(READSTR);
>        k = 0;
> -        for(i = 0; devtab[i] != nil; i++)
> +        
> +        rlock(&up->pgrp->ns);
> +        for(i = 0; devtab[i] != nil; i++){
> +            if(up->pgrp->notallowed[i/(sizeof(u64int)*8)] & 1<<i%(sizeof(u64int)*8))
> +                continue;
>            k += snprint(b+k, READSTR-k, "#%C %s\n",
>                devtab[i]->dc, devtab[i]->name);
> +        }
> +        runlock(&up->pgrp->ns);
>        if(waserror()){
>            free(b);
>            nexterror();
> @@ -676,6 +694,26 @@
>        error(Eperm);
>        break;
> 
> +    case Qdrivers:
> +        cb = parsecmd(a, n);
> +
> +        if(waserror()) {
> +            free(cb);
> +            nexterror();
> +        }
> +        ct = lookupcmd(cb, drivermsg, nelem(drivermsg));
> +        switch(ct->index) {
> +        case CMblock:
> +            driverscmd(up->pgrp, 0, cb->nf-1, cb->f+1);
> +            break;
> +        case CMpermit:
> +            driverscmd(up->pgrp, 1, cb->nf-1, cb->f+1);
> +            break;
> +        }
> +        poperror();
> +        free(cb);
> +        break;
> +
>    case Qreboot:
>        if(!iseve())
>            error(Eperm);
> @@ -935,6 +973,64 @@
>        break;
>    }
>    return n+1;
> +}
> +
> +void
> +driverscmd(Pgrp *pgrp, int invert, int argc, char *argv[])
> +{
> +    int i, t, w;
> +    char *p;
> +    Rune r;
> +    u64int mask[nelem(pgrp->notallowed)];
> +
> +    if(invert)
> +        memset(mask, 0xFF, sizeof mask);
> +    else
> +        memset(mask, 0, sizeof mask);
> +
> +    w = sizeof mask[0] * 8;
> +    for(i=0; i < argc; i++)
> +        for(p = argv[i]; *p != '\0';){
> +            p += chartorune(&r, p);
> +            t = devno(r, 1);
> +            if(t == -1)
> +                continue;
> +            if(invert)
> +                mask[t/w] &= ~(1<<t%w);
> +            else
> +                mask[t/w] |= 1<<t%w;
> +        }
> +
> +    wlock(&pgrp->ns);
> +    for(i=0; i < nelem(pgrp->notallowed); i++)
> +        pgrp->notallowed[i] |= mask[i];
> +    wunlock(&pgrp->ns);
> +}
> +
> +int
> +driversallowed(Pgrp *pgrp, int r)
> +{
> +    int t, w, b;
> +
> +    t = devno(r, 1);
> +    if(t == -1)
> +        return 0;
> +
> +    w = sizeof(u64int) * 8;
> +    rlock(&pgrp->ns);
> +    b = !(pgrp->notallowed[t/w] & 1<<t%w);
> +    runlock(&pgrp->ns);
> +    return b;
> +}
> +
> +int
> +canmount(Pgrp *pgrp)
> +{
> +    /*
> +     * Devmnt is not usable directly from user procs, so
> +     * having it removed is interpreted to block any mounts.
> +     */
> +    return driversallowed(pgrp, 'M');
> }
> 
> void
> --- a/sys/src/9/port/devroot.c
> +++ b/sys/src/9/port/devroot.c
> @@ -105,6 +105,7 @@
>    addrootdir("net");
>    addrootdir("net.alt");
>    addrootdir("proc");
> +    addrootdir("rc");
>    addrootdir("root");
>    addrootdir("srv");
>    addrootdir("shr");
> --- a/sys/src/9/port/devshr.c
> +++ b/sys/src/9/port/devshr.c
> @@ -464,7 +464,7 @@
>        cclose(c);
>        return nc;    
>    case Qcroot:
> -        if(up->pgrp->noattach)
> +        if(!canmount(up->pgrp))
>            error(Enoattach);
>        if((perm & DMDIR) == 0 || mode != OREAD)
>            error(Eperm);
> @@ -498,7 +498,7 @@
>        sch->shr = shr;
>        break;
>    case Qcshr:
> -        if(up->pgrp->noattach)
> +        if(!canmount(up->pgrp))
>            error(Enoattach);
>        if((perm & DMDIR) != 0 || mode != OWRITE)
>            error(Eperm);
> @@ -731,7 +731,7 @@
>    Mhead *h;
>    Mount *m;
> 
> -    if(up->pgrp->noattach)
> +    if(!canmount(up->pgrp))
>        error(Enoattach);
>    sch = tosch(c);
>    if(sch->level != Qcmpt)
> --- a/sys/src/9/port/mkdevc
> +++ b/sys/src/9/port/mkdevc
> @@ -78,6 +78,9 @@
>    if(ARGC < 2)
>        exit "usage"
> 
> +    if(ndev >= 256)
> +        exit "device count will overflow Pgrp.notallowed"
> +
>    printf "#include \"u.h\"\n";
>    printf "#include \"../port/lib.h\"\n";
>    printf "#include \"mem.h\"\n";
> --- a/sys/src/9/port/portdat.h
> +++ b/sys/src/9/port/portdat.h
> @@ -121,6 +121,7 @@
>    Amount,                /* to be mounted or mounted upon */
>    Acreate,            /* is to be created */
>    Aremove,            /* will be removed by caller */
> +    Aunmount,            /* unmount arg[0] */
> 
>    COPEN    = 0x0001,        /* for i/o */
>    CMSG    = 0x0002,        /* the message channel for a mount */
> @@ -484,7 +485,7 @@
> {
>    Ref;
>    RWlock    ns;            /* Namespace n read/one write lock */
> -    int    noattach;
> +    u64int    notallowed[4];        /* Room for 256 devices */
>    Mhead    *mnthash[MNTHASH];
> };
> 
> --- a/sys/src/9/port/portfns.h
> +++ b/sys/src/9/port/portfns.h
> @@ -413,6 +413,9 @@
> ushort        nhgets(void*);
> ulong        µs(void);
> long        lcycles(void);
> +void        driverscmd(Pgrp*,int,int,char**);
> +int        driversallowed(Pgrp*, int);
> +int        canmount(Pgrp*);
> 
> #pragma varargck argpos iprint    1
> #pragma    varargck argpos    panic    1
> --- a/sys/src/9/port/sysfile.c
> +++ b/sys/src/9/port/sysfile.c
> @@ -1048,7 +1048,7 @@
>            nexterror();
>        }
> 
> -        if(up->pgrp->noattach)
> +        if(!canmount(up->pgrp))
>            error(Enoattach);
> 
>        ac = nil;
> @@ -1160,14 +1160,8 @@
>        nexterror();
>    }
>    if(name != nil) {
> -        /*
> -         * This has to be namec(..., Aopen, ...) because
> -         * if arg[0] is something like /srv/cs or /fd/0,
> -         * opening it is the only way to get at the real
> -         * Chan underneath.
> -         */
>        validaddr((uintptr)name, 1, 0);
> -        cmounted = namec(name, Aopen, OREAD, 0);
> +        cmounted = namec(name, Aunmount, OREAD, 0);
>    }
>    cunmount(cmount, cmounted);
>    poperror();
> --- a/sys/src/9/port/sysproc.c
> +++ b/sys/src/9/port/sysproc.c
> @@ -34,6 +34,7 @@
>    Egrp *oeg;
>    ulong pid, flag;
>    Mach *wm;
> +    char *devs;
> 
>    flag = va_arg(list, ulong);
>    /* Check flags before we commit */
> @@ -44,6 +45,11 @@
>    if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG))
>        error(Ebadarg);
> 
> +    /*
> +     * Code using RFNOMNT expects to block all but
> +     * the following devices.
> +     */
> +    devs = "|decp";
>    if((flag&RFPROC) == 0) {
>        if(flag & (RFMEM|RFNOWAIT))
>            error(Ebadarg);
> @@ -60,12 +66,12 @@
>            up->pgrp = newpgrp();
>            if(flag & RFNAMEG)
>                pgrpcpy(up->pgrp, opg);
> -            /* inherit noattach */
> -            up->pgrp->noattach = opg->noattach;
> +            /* inherit notallowed */
> +            memmove(up->pgrp->notallowed, opg->notallowed, sizeof up->pgrp->notallowed);
>            closepgrp(opg);
>        }
>        if(flag & RFNOMNT)
> -            up->pgrp->noattach = 1;
> +            driverscmd(up->pgrp, 1, 1, &devs);
>        if(flag & RFREND) {
>            org = up->rgrp;
>            up->rgrp = newrgrp();
> @@ -177,8 +183,8 @@
>        p->pgrp = newpgrp();
>        if(flag & RFNAMEG)
>            pgrpcpy(p->pgrp, up->pgrp);
> -        /* inherit noattach */
> -        p->pgrp->noattach = up->pgrp->noattach;
> +        /* inherit notallowed */
> +        memmove(p->pgrp->notallowed, up->pgrp->notallowed, sizeof p->pgrp->notallowed);
>    }
>    else {
>        p->pgrp = up->pgrp;
> @@ -185,7 +191,7 @@
>        incref(p->pgrp);
>    }
>    if(flag & RFNOMNT)
> -        p->pgrp->noattach = 1;
> +        driverscmd(p->pgrp, 1, 1, &devs);
> 
>    if(flag & RFREND)
>        p->rgrp = newrgrp();
> --- a/sys/src/libauth/newns.c
> +++ b/sys/src/libauth/newns.c
> @@ -14,8 +14,8 @@
> static int    setenv(char*, char*);
> static char    *expandarg(char*, char*);
> static int    splitargs(char*, char*[], char*, int);
> -static int    nsfile(char*, Biobuf *, AuthRpc *);
> -static int    nsop(char*, int, char*[], AuthRpc*);
> +static int    nsfile(char*, Biobuf *, AuthRpc *, int);
> +static int    nsop(char*, int, char*[], AuthRpc*, int);
> static int    catch(void*, char*);
> 
> int newnsdebug;
> @@ -35,7 +35,7 @@
> {
>    Biobuf *b;
>    char home[4*ANAMELEN];
> -    int afd, cdroot;
> +    int afd, cdroot, dfd;
>    char *path;
>    AuthRpc *rpc;
> 
> @@ -51,6 +51,10 @@
>    }
>    /* rpc != nil iff afd >= 0 */
> 
> +    dfd = open("#c/drivers", OWRITE|OCEXEC);
> +    if(dfd < 0 && newnsdebug)
> +        fprint(2, "open #c/drivers: %r\n");
> +
>    if(file == nil){
>        if(!newns){
>            werrstr("no namespace file specified");
> @@ -70,7 +74,8 @@
>        setenv("home", home);
>    }
> 
> -    cdroot = nsfile(newns ? "newns" : "addns", b, rpc);
> +    cdroot = nsfile(newns ? "newns" : "addns", b, rpc, dfd);
> +    close(dfd);
>    Bterm(b);
>    freecloserpc(rpc);
> 
> @@ -87,7 +92,7 @@
> }
> 
> static int
> -nsfile(char *fn, Biobuf *b, AuthRpc *rpc)
> +nsfile(char *fn, Biobuf *b, AuthRpc *rpc, int dfd)
> {
>    int argc;
>    char *cmd, *argv[NARG+1], argbuf[MAXARG*NARG];
> @@ -103,7 +108,7 @@
>            continue;
>        argc = splitargs(cmd, argv, argbuf, NARG);
>        if(argc)
> -            cdroot |= nsop(fn, argc, argv, rpc);
> +            cdroot |= nsop(fn, argc, argv, rpc, dfd);
>    }
>    atnotify(catch, 0);
>    return cdroot;
> @@ -143,7 +148,7 @@
> }
> 
> static int
> -nsop(char *fn, int argc, char *argv[], AuthRpc *rpc)
> +nsop(char *fn, int argc, char *argv[], AuthRpc *rpc, int dfd)
> {
>    char *argv0;
>    ulong flags;
> @@ -181,7 +186,7 @@
>        b = Bopen(argv[0], OREAD|OCEXEC);
>        if(b == nil)
>            return 0;
> -        cdroot |= nsfile(fn, b, rpc);
> +        cdroot |= nsfile(fn, b, rpc, dfd);
>        Bterm(b);
>    }else if(strcmp(argv0, "clear") == 0 && argc == 0){
>        rfork(RFCNAMEG);
> @@ -212,6 +217,14 @@
>    }else if(strcmp(argv0, "cd") == 0 && argc == 1){
>        if(chdir(argv[0]) == 0 && *argv[0] == '/')
>            cdroot = 1;
> +    }else if(argc >= 1 && (strcmp(argv0, "permit") == 0 || strcmp(argv0, "block") == 0)){
> +        //We should not silently fail if we can not honor a permit/block
> +        //due to the parent namespace missing #c/drivers.
> +        if(dfd <= 0)
> +            sysfatal("%s requested, but could not open #c/drivers", argv0);
> +        for(i=0; i < argc; i++)
> +            if(fprint(dfd, "%s %s\n", argv0, argv[i]) < 0 && newnsdebug)
> +                fprint(2, "%s: %s %s %r\n", fn, argv0, argv[i]);
>    }
>    return cdroot;
> }
> 


  reply	other threads:[~2022-05-11 16:14 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-04 14:09 Jacob Moody
2022-05-04 15:05 ` ori
2022-05-04 15:31 ` ori
2022-05-04 16:15   ` Stanley Lieber
2022-05-04 17:41   ` Lyndon Nerenberg (VE7TFX/VE6BBM)
2022-05-04 17:55     ` Jacob Moody
2022-05-05  1:59       ` Alex Musolino
2022-05-05 16:07         ` Jacob Moody
2022-05-08  2:55           ` Jacob Moody
2022-05-11 14:47             ` Jacob Moody
2022-05-11 16:11               ` Stanley Lieber [this message]
2022-05-12  4:29                 ` Jacob Moody
2022-05-12  3:18             ` ori
2022-05-12  5:10               ` Jacob Moody
2022-05-12 14:21                 ` ori
2022-05-23  5:42                   ` Jacob Moody
2022-05-23 17:06                     ` cinap_lenrek
2022-05-23 17:37                       ` Jacob Moody
2022-05-25 19:03                         ` Jacob Moody
2022-05-25 20:53                           ` hiro
2022-05-25 21:20                             ` Jacob Moody
2022-05-26  5:55                               ` Jacob Moody
2022-05-26 23:36                                 ` unobe
2022-05-27  0:33                                   ` Jacob Moody
2022-05-27  3:25                                     ` unobe
2022-05-26  3:13                             ` ori
2022-05-27  1:11                           ` Lyndon Nerenberg (VE7TFX/VE6BBM)
2022-05-27  2:25                             ` Frank D. Engel, Jr.

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=E811DF5E-82ED-48AB-9C36-C75948E4C5C4@stanleylieber.com \
    --to=sl@stanleylieber.com \
    --cc=9front@9front.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).