On Thu, 18 May 2023 18:13:57 +0200, Omar Polo wrote:

> existing code just used 0 and 1 (e.g. creategid.)  lone zeros as
> arguments are not really readable, but still I can't make myself
> liking stdbool.  not that my tastes matters, but it seems to be used
> very sparsingly in usr.sbin

This was leftover from before I added bit flags to runas().
It is not actually needed now so I have removed it.

> >  #include <stdio.h>
> >  #include <stdlib.h>
> >  #include <string.h>
> > @@ -103,6 +104,10 @@ enum {
> >     F_ACCTUNLOCK    = 0x10000
> >  };
> >  
> > +/* bit flags for runas() */
> > +#define RUNAS_DUP_DEVNULL  0x01
> > +#define RUNAS_IGNORE_EXITVAL       0x02
>
> another nit: the other flags are defined within an enums.

Sure, no reason to be inconsistent.

> > [...]
> > -/* a replacement for system(3) */
> > +/* run the given command with optional arguments as the specified uid */
> >  static int
> > -asystem(const char *fmt, ...)
> > +runas(const char *path, const char *const argv[], uid_t uid, int flags)
> >  {
> > [...]
> > +   default:
> > +           while (waitpid(child, &status, 0) == -1) {
> > +                   if (errno != EINTR)
> > +                           err(EXIT_FAILURE, "waitpid");
> > +           }
> > +           if (WIFSIGNALED(status)) {
> > +                   ret = WTERMSIG(status);
> > +                   warnx("[Warning] `%s' killed by signal %d", buf, ret);
> > +                   ret |= 128;
> > +           } else {
> > +                   if (!(flags & RUNAS_IGNORE_EXITVAL))
> > +                           ret = WEXITSTATUS(status);
> > +                   if (ret != 0)
> > +                           warnx("[Warning] unable to run `%s'", buf);
>
> more than "unable to run" I'd say "failed to run" or "command
> failed" / "exited with nonzero status".

How about "failed with status N"?  It can be useful to have the
exit status since that may indicate the source of the errorĀ (though
perhaps not so much in this case).

Updated diff follows.

 - todd

Index: usr.sbin/user/user.c
===================================================================
RCS file: /cvs/src/usr.sbin/user/user.c,v
retrieving revision 1.130
diff -u -p -u -r1.130 user.c
--- usr.sbin/user/user.c        16 May 2023 21:28:46 -0000      1.130
+++ usr.sbin/user/user.c        18 May 2023 17:24:01 -0000
@@ -31,6 +31,7 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 #include <ctype.h>
 #include <dirent.h>
@@ -42,7 +43,6 @@
 #include <login_cap.h>
 #include <paths.h>
 #include <pwd.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -103,6 +103,12 @@ enum {
        F_ACCTUNLOCK    = 0x10000
 };
 
+/* flags for runas() */
+enum {
+       RUNAS_DUP_DEVNULL       = 0x01,
+       RUNAS_IGNORE_EXITVAL    = 0x02
+};
+
 #define CONFFILE               "/etc/usermgmt.conf"
 #define _PATH_NONEXISTENT      "/nonexistent"
 
@@ -179,8 +185,6 @@ enum {
 
 static int adduser(char *, user_t *);
 static int append_group(char *, int, const char **);
-static int asystem(const char *, ...)
-       __attribute__((__format__(__printf__, 1, 2)));
 static int copydotfiles(char *, char *);
 static int creategid(char *, gid_t, const char *);
 static int getnextgid(uid_t *, uid_t, uid_t);
@@ -214,30 +218,79 @@ strsave(char **cpp, const char *s)
                err(1, NULL);
 }
 
-/* a replacement for system(3) */
+/* run the given command with optional arguments as the specified uid */
 static int
-asystem(const char *fmt, ...)
+runas(const char *path, const char *const argv[], uid_t uid, int flags)
 {
-       va_list vp;
-       char    buf[MaxCommandLen];
-       int     ret;
+       int argc, status, ret = 0;
+       char buf[MaxCommandLen];
+       pid_t child;
 
-       va_start(vp, fmt);
-       (void) vsnprintf(buf, sizeof(buf), fmt, vp);
-       va_end(vp);
-       if (verbose) {
-               printf("Command: %s\n", buf);
+       strlcpy(buf, path, sizeof(buf));
+       for (argc = 1; argv[argc] != NULL; argc++) {
+               strlcat(buf, " ", sizeof(buf));
+               strlcat(buf, argv[argc], sizeof(buf));
        }
-       if ((ret = system(buf)) != 0) {
-               warnx("[Warning] can't system `%s'", buf);
+       if (verbose)
+               printf("Command: %s\n", buf);
+
+       child = fork();
+       switch (child) {
+       case -1:
+               err(EXIT_FAILURE, "fork");
+       case 0:
+               if (flags & RUNAS_DUP_DEVNULL) {
+                       /* Redirect output to /dev/null if possible. */
+                       int dev_null = open(_PATH_DEVNULL, O_RDWR);
+                       if (dev_null != -1) {
+                               dup2(dev_null, STDOUT_FILENO);
+                               dup2(dev_null, STDERR_FILENO);
+                               if (dev_null > STDERR_FILENO)
+                                       close(dev_null);
+                       } else {
+                               warn("%s", _PATH_DEVNULL);
+                       }
+               }
+               if (uid != -1) {
+                       if (setresuid(uid, uid, uid) == -1)
+                               warn("setresuid(%u, %u, %u)", uid, uid, uid);
+               }
+               execv(path, (char **)argv);
+               warn("%s", buf);
+               _exit(EXIT_FAILURE);
+       default:
+               while (waitpid(child, &status, 0) == -1) {
+                       if (errno != EINTR)
+                               err(EXIT_FAILURE, "waitpid");
+               }
+               if (WIFSIGNALED(status)) {
+                       ret = WTERMSIG(status);
+                       warnx("[Warning] `%s' killed by signal %d", buf, ret);
+                       ret |= 128;
+               } else {
+                       if (!(flags & RUNAS_IGNORE_EXITVAL))
+                               ret = WEXITSTATUS(status);
+                       if (ret != 0) {
+                               warnx("[Warning] `%s' failed with status %d",
+                                   buf, ret);
+                       }
+               }
+               return ret;
        }
-       return ret;
+}
+
+/* run the given command with optional arguments */
+static int
+run(const char *path, const char *const argv[])
+{
+       return runas(path, argv, -1, 0);
 }
 
 /* remove a users home directory, returning 1 for success (ie, no problems 
encountered) */
 static int
 removehomedir(const char *user, uid_t uid, const char *dir)
 {
+       const char *rm_argv[] = { "rm", "-rf", dir, NULL };
        struct stat st;
 
        /* userid not root? */
@@ -263,11 +316,9 @@ removehomedir(const char *user, uid_t ui
                return 0;
        }
 
-       (void) seteuid(uid);
-       /* we add the "|| true" to keep asystem() quiet if there is a non-zero 
exit status. */
-       (void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir);
-       (void) seteuid(0);
-       if (rmdir(dir) == -1) {
+       /* run "rm -rf dir 2>&1/dev/null" as user, not root */
+       (void) runas(RM, rm_argv, uid, RUNAS_DUP_DEVNULL|RUNAS_IGNORE_EXITVAL);
+       if (rmdir(dir) == -1 && errno != ENOENT) {
                warnx("Unable to remove all files in `%s'", dir);
                return 0;
        }
@@ -288,11 +339,12 @@ checkeuid(void)
 
 /* copy any dot files into the user's home directory */
 static int
-copydotfiles(char *skeldir, char *dir)
+copydotfiles(char *skeldir, char *dst)
 {
+       char            src[MaxFileNameLen];
        struct dirent   *dp;
        DIR             *dirp;
-       int             n;
+       int             len, n;
 
        if (*skeldir == '\0')
                return 0;
@@ -311,8 +363,16 @@ copydotfiles(char *skeldir, char *dir)
        if (n == 0) {
                warnx("No \"dot\" initialisation files found");
        } else {
-               (void) asystem("%s -a %s %s/. %s",
-                   CP, (verbose) ? "-v" : "", skeldir, dir);
+               len = snprintf(src, sizeof(src), "%s/.", skeldir);
+               if (len < 0 || len >= sizeof(src)) {
+                       warnx("skeleton directory `%s' too long", skeldir);
+                       n = 0;
+               } else {
+                       const char *cp_argv[] = { "cp", "-a", src, dst, NULL };
+                       if (verbose)
+                               cp_argv[1] = "-av";
+                       run(CP, cp_argv);
+               }
        }
        return n;
 }
@@ -1203,7 +1263,15 @@ adduser(char *login_name, user_t *up)
                        errx(EXIT_FAILURE, "home directory `%s' already exists",
                            home);
                } else {
-                       if (asystem("%s -p %s", MKDIR, home) != 0) {
+                       char idstr[64];
+                       const char *mkdir_argv[] =
+                           { "mkdir", "-p", home, NULL };
+                       const char *chown_argv[] =
+                           { "chown", "-RP", idstr, home, NULL };
+                       const char *chmod_argv[] =
+                           { "chmod", "-R", "u+w", home, NULL };
+
+                       if (run(MKDIR, mkdir_argv) != 0) {
                                int saved_errno = errno;
                                close(ptmpfd);
                                pw_abort();
@@ -1211,9 +1279,10 @@ adduser(char *login_name, user_t *up)
                                    "can't mkdir `%s'", home);
                        }
                        (void) copydotfiles(up->u_skeldir, home);
-                       (void) asystem("%s -R -P %u:%u %s", CHOWN, up->u_uid,
-                           gid, home);
-                       (void) asystem("%s -R u+w %s", CHMOD, home);
+                       (void) snprintf(idstr, sizeof(idstr), "%u:%u",
+                           up->u_uid, gid);
+                       (void) run(CHOWN, chown_argv);
+                       (void) run(CHMOD, chmod_argv);
                }
        }
        if (strcmp(up->u_primgrp, "=uid") == 0 && !group_exists(login_name) &&
@@ -1657,8 +1726,9 @@ moduser(char *login_name, char *newlogin
                }
        }
        if (up != NULL) {
+               const char *mv_argv[] = { "mv", homedir, pwp->pw_dir, NULL };
                if ((up->u_flags & F_MKDIR) &&
-                   asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) {
+                   run(MV, mv_argv) != 0) {
                        int saved_errno = errno;
                        close(ptmpfd);
                        pw_abort();

Reply via email to