Hi,

I further developed my approach to allow running smtpd with fewer privileges. This diff does two things:

- always run lmtp deliveries as SMTPD_USER. The change to mda_unpriv.c is needed, because otherwise all mails would be delivered to SMTPD_USER. - add two internal flags NOPRIV and NEEDPRIV. NOPRIV can be configured by the simple directive "no-priv". NEEDPRIV gets set on all delivery methods / options requiring setuid() to run as the receipient user. A configuration error is produced on any conflict betweed NEEDPRIV and NOPRIV.
  In case of a NOPRIV run smtpd will drop root privileges.
  This will break .forward and alias filters.

The change to the lmtp delivery has benefits even without the second change. With the second change my smtpd now runs without root privileges. The NEEDPRIV/NOPRIV options are meant to allow restricting of the privileges of other delivery methods.

I am now looking for OKs on the first change to do unprivileged lmtp deliveries and feedback on the general approach of the second change.


Christopher



Index: mda_unpriv.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/mda_unpriv.c,v
retrieving revision 1.6
diff -u -p -r1.6 mda_unpriv.c
--- mda_unpriv.c        2 Feb 2020 22:13:48 -0000       1.6
+++ mda_unpriv.c        26 Apr 2020 05:27:34 -0000
@@ -69,8 +69,8 @@ mda_unpriv(struct dispatcher *dsp, struc
        xasprintf(&mda_environ[idx++], "RECIPIENT=%s@%s", deliver->dest.user, 
deliver->dest.domain);
        xasprintf(&mda_environ[idx++], "SHELL=/bin/sh");
        xasprintf(&mda_environ[idx++], "LOCAL=%s", deliver->rcpt.user);
-       xasprintf(&mda_environ[idx++], "LOGNAME=%s", pw_name);
-       xasprintf(&mda_environ[idx++], "USER=%s", pw_name);
+       xasprintf(&mda_environ[idx++], "LOGNAME=%s", 
deliver->userinfo.username);
+       xasprintf(&mda_environ[idx++], "USER=%s", deliver->userinfo.username);
if (deliver->sender.user[0])
                xasprintf(&mda_environ[idx++], "SENDER=%s@%s",
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/parse.y,v
retrieving revision 1.277
diff -u -p -r1.277 parse.y
--- parse.y     24 Feb 2020 23:54:27 -0000      1.277
+++ parse.y     26 Apr 2020 05:27:35 -0000
@@ -154,6 +154,7 @@ static int  host_v4(struct listen_opts *)
 static int     host_v6(struct listen_opts *);
 static int     host_dns(struct listen_opts *);
 static int     interface(struct listen_opts *);
+static void    need_priv(const char *);
int delaytonum(char *);
 int             is_if_in_group(const char *, const char *);
@@ -186,7 +187,7 @@ typedef struct {
 %token KEY
 %token LIMIT LISTEN LMTP LOCAL
 %token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE 
MAX_DEFERRED MBOX MDA MTA MX
-%token NO_DSN NO_VERIFY NOOP
+%token NO_DSN NO_PRIV NO_VERIFY NOOP
 %token ON
 %token PHASE PKI PORT PROC PROC_EXEC PROXY_V2
 %token QUEUE QUIT
@@ -212,6 +213,7 @@ grammar             : /* empty */
                | grammar ca '\n'
                | grammar mda '\n'
                | grammar mta '\n'
+               | grammar privs '\n'
                | grammar pki '\n'
                | grammar proc '\n'
                | grammar queue '\n'
@@ -379,6 +381,20 @@ MTA MAX_DEFERRED NUMBER  {
 ;
+privs:
+NO_PRIV {
+       if (conf->sc_opts & SMTPD_OPT_NEEDPRIV) {
+               yyerror("Unprivileged operation is not possible.");
+               YYERROR;
+       }
+       else {
+               log_warnx("Unprivileged operation requested.");
+               conf->sc_opts |= SMTPD_OPT_NOPRIV;
+       }
+}
+;
+
+
 pki:
 PKI STRING {
        char buf[HOST_NAME_MAX+1];
@@ -566,6 +582,8 @@ SRS KEY STRING {
dispatcher_local_option:
 USER STRING {
+       need_priv("with user");
+
        if (dispatcher->u.local.is_mbox) {
                yyerror("user may not be specified for this dispatcher");
                YYERROR;
@@ -662,16 +680,20 @@ dispatcher_local_option dispatcher_local
dispatcher_local:
 MBOX {
+       need_priv("mbox");
        dispatcher->u.local.is_mbox = 1;
        asprintf(&dispatcher->u.local.command, "/usr/libexec/mail.local -f 
%%{mbox.from} -- %%{user.username}");
 } dispatcher_local_options
 | MAILDIR {
+       need_priv("maildir");
        asprintf(&dispatcher->u.local.command, "/usr/libexec/mail.maildir");
 } dispatcher_local_options
 | MAILDIR JUNK {
+       need_priv("maildir");
        asprintf(&dispatcher->u.local.command, "/usr/libexec/mail.maildir -j");
 } dispatcher_local_options
 | MAILDIR STRING {
+       need_priv("maildir");
        if (strncmp($2, "~/", 2) == 0)
                asprintf(&dispatcher->u.local.command,
                    "/usr/libexec/mail.maildir \"%%{user.directory}/%s\"", 
$2+2);
@@ -680,6 +702,7 @@ MBOX {
                    "/usr/libexec/mail.maildir \"%s\"", $2);
 } dispatcher_local_options
 | MAILDIR STRING JUNK {
+       need_priv("maildir");
        if (strncmp($2, "~/", 2) == 0)
                asprintf(&dispatcher->u.local.command,
                    "/usr/libexec/mail.maildir -j \"%%{user.directory}/%s\"", 
$2+2);
@@ -690,12 +713,15 @@ MBOX {
 | LMTP STRING {
        asprintf(&dispatcher->u.local.command,
            "/usr/libexec/mail.lmtp -d %s -u", $2);
+       dispatcher->u.local.user = SMTPD_USER;
 } dispatcher_local_options
 | LMTP STRING RCPT_TO {
        asprintf(&dispatcher->u.local.command,
            "/usr/libexec/mail.lmtp -d %s -r", $2);
+       dispatcher->u.local.user = SMTPD_USER;
 } dispatcher_local_options
 | MDA STRING {
+       need_priv("mda");
        asprintf(&dispatcher->u.local.command,
            "/usr/libexec/mail.mda \"%s\"", $2);
 } dispatcher_local_options
@@ -703,6 +729,7 @@ MBOX {
        dispatcher->u.local.forward_only = 1;
 } dispatcher_local_options
 | EXPAND_ONLY {
+       need_priv("expand");
        dispatcher->u.local.expand_only = 1;
 } dispatcher_local_options
@@ -2656,6 +2683,7 @@ lookup(char *s)
                { "mta",              MTA },
                { "mx",                       MX },
                { "no-dsn",           NO_DSN },
+               { "no-priv",          NO_PRIV },
                { "no-verify",                NO_VERIFY },
                { "noop",             NOOP },
                { "on",                       ON },
@@ -3469,6 +3497,18 @@ interface(struct listen_opts *lo)
        freeifaddrs(ifap);
return ret;
+}
+
+static void
+need_priv(const char *method)
+{
+       if (conf->sc_opts & SMTPD_OPT_NOPRIV)
+               errx(1, "Delivery method %s needs privileges.", method);
+       else {
+               log_warnx("Privileges needed for method %s.",
+                   method);
+               conf->sc_opts |= SMTPD_OPT_NEEDPRIV;
+       }
 }
int
Index: smtpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpd.c,v
retrieving revision 1.332
diff -u -p -r1.332 smtpd.c
--- smtpd.c     24 Feb 2020 16:16:08 -0000      1.332
+++ smtpd.c     26 Apr 2020 05:27:35 -0000
@@ -1036,11 +1036,14 @@ imsg_wait(struct imsgbuf *ibuf, struct i
int
 smtpd(void) {
+       struct passwd   *pw;
        struct event     ev_sigint;
        struct event     ev_sigterm;
        struct event     ev_sigchld;
        struct event     ev_sighup;
        struct timeval   tv;
+       uid_t            uid;
+       gid_t            gid;
imsg_callback = parent_imsg; @@ -1085,6 +1088,17 @@ smtpd(void) { purge_task(); + if (env->sc_opts & SMTPD_OPT_NOPRIV) {
+               if ((pw = getpwnam(SMTPD_USER)) == NULL)
+                       fatalx("unknown user " SMTPD_USER);
+               uid = pw->pw_uid;
+               gid = pw->pw_gid;
+               if (setgroups(1, &gid) ||
+                   setresgid(gid, gid, gid) ||
+                   setresuid(uid, uid, uid))
+                       fatal("smtpd: cannot drop privileges");
+       }
+
        if (pledge("stdio rpath wpath cpath fattr tmppath "
            "getpw sendfd proc exec id inet chown unix", NULL) == -1)
                err(1, "pledge");
@@ -1519,10 +1533,12 @@ forkmda(struct mproc *p, uint64_t id, st
if (chdir(pw_dir) == -1 && chdir("/") == -1)
                err(1, "chdir");
-       if (setgroups(1, &pw_gid) ||
-           setresgid(pw_gid, pw_gid, pw_gid) ||
-           setresuid(pw_uid, pw_uid, pw_uid))
-               err(1, "forkmda: cannot drop privileges");
+       if (! (env->sc_opts & SMTPD_OPT_NOPRIV)) {
+               if (setgroups(1, &pw_gid) ||
+                   setresgid(pw_gid, pw_gid, pw_gid) ||
+                   setresuid(pw_uid, pw_uid, pw_uid))
+                       err(1, "forkmda: cannot drop privileges");
+       }
        if (dup2(pipefd[0], STDIN_FILENO) == -1 ||
            dup2(allout, STDOUT_FILENO) == -1 ||
            dup2(allout, STDERR_FILENO) == -1)
Index: smtpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/smtpd.h,v
retrieving revision 1.656
diff -u -p -r1.656 smtpd.h
--- smtpd.h     8 Apr 2020 07:30:44 -0000       1.656
+++ smtpd.h     26 Apr 2020 05:27:35 -0000
@@ -550,6 +550,8 @@ struct smtpd {
#define SMTPD_OPT_VERBOSE 0x00000001
 #define SMTPD_OPT_NOACTION             0x00000002
+#define SMTPD_OPT_NOPRIV               0x00000004
+#define SMTPD_OPT_NEEDPRIV             0x00000008
        uint32_t                        sc_opts;
#define SMTPD_EXITING 0x00000001 /* unused */


--
http://gmerlin.de
OpenPGP: http://gmerlin.de/christopher.pub
CB07 DA40 B0B6 571D 35E2  0DEF 87E2 92A7 13E5 DEE1

Reply via email to