Imported Upstream version 1.8.2
[debian/sudo] / plugins / sudoers / sudoers.c
index f533e591bd6b0483e79942197fd4cb1fc105392a..a3816e2cb8785ec0a9527d6c87870f7e321ec505 100644 (file)
 #ifdef HAVE_SELINUX
 # include <selinux/selinux.h>
 #endif
-#ifdef HAVE_MBR_CHECK_MEMBERSHIP
-# include <membership.h>
-#endif
 #include <ctype.h>
 #include <setjmp.h>
 
 #include "sudoers.h"
-#include "lbuf.h"
 #include "interfaces.h"
 #include "sudoers_version.h"
 #include "auth/sudo_auth.h"
  * Prototypes
  */
 static void init_vars(char * const *);
-static int set_cmnd(int);
+static int set_cmnd(void);
 static void set_loginclass(struct passwd *);
-static void set_runasgr(char *);
-static void set_runaspw(char *);
+static void set_runaspw(const char *);
+static void set_runasgr(const char *);
+static int cb_runas_default(const char *);
 static int sudoers_policy_version(int verbose);
 static int deserialize_info(char * const settings[], char * const user_info[]);
 static char *find_editor(int nfiles, char **files, char ***argv_out);
 static void create_admin_success_flag(void);
 
-/* XXX */
-extern int runas_ngroups;
-extern GETGROUPS_T *runas_groups;
-
 /*
  * Globals
  */
@@ -163,6 +156,8 @@ sudoers_policy_open(unsigned int version, sudo_conv_t conversation,
        return -1;
     }
 
+    bindtextdomain("sudoers", LOCALEDIR);
+
     /*
      * Signal setup:
      * Ignore keyboard-generated signals so the user cannot interrupt
@@ -201,11 +196,11 @@ sudoers_policy_open(unsigned int version, sudo_conv_t conversation,
        if (nss->open(nss) == 0 && nss->parse(nss) == 0) {
            sources++;
            if (nss->setdefs(nss) != 0)
-               log_error(NO_STDERR|NO_EXIT, "problem with defaults entries");
+               log_error(NO_STDERR|NO_EXIT, _("problem with defaults entries"));
        }
     }
     if (sources == 0) {
-       warningx("no valid sudoers sources found, quitting");
+       warningx(_("no valid sudoers sources found, quitting"));
        return -1;
     }
 
@@ -236,7 +231,7 @@ sudoers_policy_open(unsigned int version, sudo_conv_t conversation,
        set_runaspw(runas_user ? runas_user : def_runas_default);
 
     if (!update_defaults(SETDEF_RUNAS))
-       log_error(NO_STDERR|NO_EXIT, "problem with defaults entries");
+       log_error(NO_STDERR|NO_EXIT, _("problem with defaults entries"));
 
     if (def_fqdn)
        set_fqdn();     /* deferred until after sudoers is parsed */
@@ -259,7 +254,7 @@ sudoers_policy_close(int exit_status, int error_code)
 
     /* We do not currently log the exit status. */
     if (error_code)
-       warningx("unable to execute %s: %s", safe_cmnd, strerror(error_code));
+       warningx(_("unable to execute %s: %s"), safe_cmnd, strerror(error_code));
 
     /* Close the session we opened in sudoers_policy_init_session(). */
     if (ISSET(sudo_mode, MODE_RUN|MODE_EDIT))
@@ -270,6 +265,8 @@ sudoers_policy_close(int exit_status, int error_code)
     pw_delref(runas_pw);
     if (runas_gr != NULL)
        gr_delref(runas_gr);
+    if (user_group_list != NULL)
+       grlist_delref(user_group_list);
 }
 
 /*
@@ -306,14 +303,14 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
 
     /* Is root even allowed to run sudo? */
     if (user_uid == 0 && !def_root_sudo) {
-        warningx("sudoers specifies that root is not allowed to sudo");
+        warningx(_("sudoers specifies that root is not allowed to sudo"));
         goto bad;
     }    
 
     /* Check for -C overriding def_closefrom. */
     if (user_closefrom >= 0 && user_closefrom != def_closefrom) {
        if (!def_closefrom_override) {
-           warningx("you are not permitted to use the -C option");
+           warningx(_("you are not permitted to use the -C option"));
            goto bad;
        }
        def_closefrom = user_closefrom;
@@ -335,16 +332,21 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
        NewArgv[0] = user_cmnd;
        NewArgv[1] = NULL;
     } else {
+       /* Must leave an extra slot before NewArgv for bash's --login */
        NewArgc = argc;
-       NewArgv = emalloc2(NewArgc + 1, sizeof(char *));
-       memcpy(NewArgv, argv, argc * sizeof(char *));
+       NewArgv = emalloc2(NewArgc + 2, sizeof(char *));
+       memcpy(++NewArgv, argv, argc * sizeof(char *));
        NewArgv[NewArgc] = NULL;
        if (ISSET(sudo_mode, MODE_LOGIN_SHELL))
            NewArgv[0] = estrdup(runas_pw->pw_shell);
     }
 
+    /* If given the -P option, set the "preserve_groups" flag. */
+    if (ISSET(sudo_mode, MODE_PRESERVE_GROUPS))
+       def_preserve_groups = TRUE;
+
     /* Find command in path */
-    cmnd_status = set_cmnd(sudo_mode);
+    cmnd_status = set_cmnd();
     if (cmnd_status == -1) {
        rval = -1;
        goto done;
@@ -352,7 +354,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
 
 #ifdef HAVE_SETLOCALE
     if (!setlocale(LC_ALL, def_sudoers_locale)) {
-       warningx("unable to set locale to \"%s\", using \"C\"",
+       warningx(_("unable to set locale to \"%s\", using \"C\""),
            def_sudoers_locale);
        setlocale(LC_ALL, "C");
     }
@@ -398,16 +400,12 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
        else
            pw = sudo_getpwnam(def_timestampowner);
        if (!pw)
-           log_error(0, "timestamp owner (%s): No such user",
+           log_error(0, _("timestamp owner (%s): No such user"),
                def_timestampowner);
        timestamp_uid = pw->pw_uid;
        pw_delref(pw);
     }
 
-    /* If given the -P option, set the "preserve_groups" flag. */
-    if (ISSET(sudo_mode, MODE_PRESERVE_GROUPS))
-       def_preserve_groups = TRUE;
-
     /* If no command line args and "shell_noargs" is not set, error out. */
     if (ISSET(sudo_mode, MODE_IMPLIED_SHELL) && !def_shell_noargs) {
        rval = -2; /* usage error */
@@ -418,8 +416,8 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
     if (def_requiretty) {
        int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY);
        if (fd == -1) {
-           audit_failure(NewArgv, "no tty");
-           warningx("sorry, you must have a tty to run sudo");
+           audit_failure(NewArgv, _("no tty"));
+           warningx(_("sorry, you must have a tty to run sudo"));
            goto bad;
        } else
            (void) close(fd);
@@ -455,9 +453,6 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
                    if (sudo_user.pw != NULL)
                        pw_delref(sudo_user.pw);
                    sudo_user.pw = pw;
-#ifdef HAVE_MBR_CHECK_MEMBERSHIP
-                   mbr_uid_to_uuid(user_uid, user_uuid);
-#endif
            }
        }
     }
@@ -465,7 +460,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
     /* If the user was not allowed to run the command we are done. */
     if (!ISSET(validated, VALIDATE_OK)) {
        if (ISSET(validated, FLAG_NO_USER | FLAG_NO_HOST)) {
-           audit_failure(NewArgv, "No user or host");
+           audit_failure(NewArgv, _("No user or host"));
            log_denial(validated, 1);
        } else {
            if (def_path_info) {
@@ -479,14 +474,14 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
                log_denial(validated,
                    !(cmnd_status == NOT_FOUND_DOT || cmnd_status == NOT_FOUND));
                if (cmnd_status == NOT_FOUND)
-                   warningx("%s: command not found", user_cmnd);
+                   warningx(_("%s: command not found"), user_cmnd);
                else if (cmnd_status == NOT_FOUND_DOT)
-                   warningx("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run.", user_cmnd, user_cmnd, user_cmnd);
+                   warningx(_("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run."), user_cmnd, user_cmnd, user_cmnd);
            } else {
                /* Just tell the user they are not allowed to run foo. */
                log_denial(validated, 1);
            }
-           audit_failure(NewArgv, "validation failure");
+           audit_failure(NewArgv, _("validation failure"));
        }
        goto bad;
     }
@@ -496,19 +491,19 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
 
     /* Finally tell the user if the command did not exist. */
     if (cmnd_status == NOT_FOUND_DOT) {
-       audit_failure(NewArgv, "command in current directory");
-       warningx("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run.", user_cmnd, user_cmnd, user_cmnd);
+       audit_failure(NewArgv, _("command in current directory"));
+       warningx(_("ignoring `%s' found in '.'\nUse `sudo ./%s' if this is the `%s' you wish to run."), user_cmnd, user_cmnd, user_cmnd);
        goto bad;
     } else if (cmnd_status == NOT_FOUND) {
-       audit_failure(NewArgv, "%s: command not found", user_cmnd);
-       warningx("%s: command not found", user_cmnd);
+       audit_failure(NewArgv, _("%s: command not found"), user_cmnd);
+       warningx(_("%s: command not found"), user_cmnd);
        goto bad;
     }
 
     /* If user specified env vars make sure sudoers allows it. */
     if (ISSET(sudo_mode, MODE_RUN) && !def_setenv) {
        if (ISSET(sudo_mode, MODE_PRESERVE_ENV)) {
-           warningx("sorry, you are not allowed to preserve the environment");
+           warningx(_("sorry, you are not allowed to preserve the environment"));
            goto bad;
        } else
            validate_env_vars(sudo_user.env_vars);
@@ -578,6 +573,21 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
        /* Set cwd to run user's homedir. */
        command_info[info_len++] = fmt_string("cwd", runas_pw->pw_dir);
 
+       /*
+        * Newer versions of bash require the --login option to be used
+        * in conjunction with the -c option even if the shell name starts
+        * with a '-'.  Unfortunately, bash 1.x uses -login, not --login
+        * so this will cause an error for that.
+        */
+       if (NewArgc > 1 && strcmp(NewArgv[0], "-bash") == 0 &&
+           strcmp(NewArgv[1], "-c") == 0) {
+           /* Use the extra slot before NewArgv so we can store --login. */
+           NewArgv--;
+           NewArgc++;
+           NewArgv[0] = NewArgv[1];
+           NewArgv[1] = "--login";
+       }
+
 #if defined(__linux__) || defined(_AIX)
        /* Insert system-wide environment variables. */
        read_env_file(_PATH_ENVIRONMENT, TRUE);
@@ -624,22 +634,24 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
     }
     if (def_preserve_groups) {
        command_info[info_len++] = "preserve_groups=true";
-    } else if (runas_ngroups != -1) {
+    } else {
        int i, len;
        size_t glsize;
        char *cp, *gid_list;
+       struct group_list *grlist = get_group_list(runas_pw);
 
-       glsize = sizeof("runas_groups=") - 1 + (runas_ngroups * (MAX_UID_T_LEN + 1));
+       glsize = sizeof("runas_groups=") - 1 + (grlist->ngids * (MAX_UID_T_LEN + 1));
        gid_list = emalloc(glsize);
        memcpy(gid_list, "runas_groups=", sizeof("runas_groups=") - 1);
        cp = gid_list + sizeof("runas_groups=") - 1;
-       for (i = 0; i < runas_ngroups; i++) {
+       for (i = 0; i < grlist->ngids; i++) {
            /* XXX - check rval */
            len = snprintf(cp, glsize - (cp - gid_list), "%s%u",
-                i ? "," : "", (unsigned int) runas_groups[i]);
+                i ? "," : "", (unsigned int) grlist->gids[i]);
            cp += len;
        }
        command_info[info_len++] = gid_list;
+       grlist_delref(grlist);
     }
     if (def_closefrom >= 0)
        easprintf(&command_info[info_len++], "closefrom=%d", def_closefrom);
@@ -649,6 +661,8 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
        command_info[info_len++] = fmt_string("noexec_file", def_noexec_file);
     if (def_set_utmp)
        command_info[info_len++] = estrdup("set_utmp=true");
+    if (def_use_pty)
+       command_info[info_len++] = estrdup("use_pty=true");
     if (def_utmp_runas)
        command_info[info_len++] = fmt_string("utmp_user", runas_pw->pw_name);
 #ifdef HAVE_LOGIN_CAP_H
@@ -731,7 +745,7 @@ sudoers_policy_list(int argc, char * const argv[], int verbose,
     if (list_user) {
        list_pw = sudo_getpwnam(list_user);
        if (list_pw == NULL) {
-           warningx("unknown user: %s", list_user);
+           warningx(_("unknown user: %s"), list_user);
            return -1;
        }
     }
@@ -797,13 +811,19 @@ init_vars(char * const envp[])
         * YP/NIS/NIS+/LDAP/etc daemon has died.
         */
        if (sudo_mode == MODE_KILL || sudo_mode == MODE_INVALIDATE)
-           errorx(1, "unknown user: %s", user_name);
-       log_error(0, "unknown user: %s", user_name);
+           errorx(1, _("unknown user: %s"), user_name);
+       log_error(0, _("unknown user: %s"), user_name);
        /* NOTREACHED */
     }
-#ifdef HAVE_MBR_CHECK_MEMBERSHIP
-    mbr_uid_to_uuid(user_uid, user_uuid);
-#endif
+
+    /*
+     * Get group list.
+     */
+    if (user_group_list == NULL)
+       user_group_list = get_group_list(sudo_user.pw);
+
+    /* Set runas callback. */
+    sudo_defs_table[I_RUNAS_DEFAULT].callback = cb_runas_default;
 
     /* It is now safe to use log_error() and set_perms() */
 }
@@ -813,7 +833,7 @@ init_vars(char * const envp[])
  * and apply any command-specific defaults entries.
  */
 static int
-set_cmnd(int sudo_mode)
+set_cmnd(void)
 {
     int rval;
     char *path = user_path;
@@ -845,25 +865,42 @@ set_cmnd(int sudo_mode)
 
        /* set user_args */
        if (NewArgc > 1) {
-           char *to, **from;
+           char *to, *from, **av;
            size_t size, n;
 
            /* Alloc and build up user_args. */
-           for (size = 0, from = NewArgv + 1; *from; from++)
-               size += strlen(*from) + 1;
+           for (size = 0, av = NewArgv + 1; *av; av++)
+               size += strlen(*av) + 1;
            user_args = emalloc(size);
-           for (to = user_args, from = NewArgv + 1; *from; from++) {
-               n = strlcpy(to, *from, size - (to - user_args));
-               if (n >= size - (to - user_args))
-                   errorx(1, "internal error, set_cmnd() overflow");
-               to += n;
-               *to++ = ' ';
+           if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
+               /*
+                * When running a command via a shell, the sudo front-end
+                * escapes potential meta chars.  We unescape non-spaces
+                * for sudoers matching and logging purposes.
+                */
+               for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
+                   while (*from) {
+                       if (from[0] == '\\' && !isspace((unsigned char)from[1]))
+                           from++;
+                       *to++ = *from++;
+                   }
+                   *to++ = ' ';
+               }
+               *--to = '\0';
+           } else {
+               for (to = user_args, av = NewArgv + 1; *av; av++) {
+                   n = strlcpy(to, *av, size - (to - user_args));
+                   if (n >= size - (to - user_args))
+                       errorx(1, _("internal error, set_cmnd() overflow"));
+                   to += n;
+                   *to++ = ' ';
+               }
+               *--to = '\0';
            }
-           *--to = '\0';
        }
     }
     if (strlen(user_cmnd) >= PATH_MAX)
-       errorx(1, "%s: file name too long", user_cmnd);
+       errorx(1, _("%s: %s"), user_cmnd, strerror(ENAMETOOLONG));
 
     if ((user_base = strrchr(user_cmnd, '/')) != NULL)
        user_base++;
@@ -871,10 +908,7 @@ set_cmnd(int sudo_mode)
        user_base = user_cmnd;
 
     if (!update_defaults(SETDEF_CMND))
-       log_error(NO_STDERR|NO_EXIT, "problem with defaults entries");
-
-    if (!runas_user && !runas_group)
-       set_runaspw(def_runas_default); /* may have been updated above */
+       log_error(NO_STDERR|NO_EXIT, _("problem with defaults entries"));
 
     return rval;
 }
@@ -899,17 +933,17 @@ open_sudoers(const char *sudoers, int doedit, int *keepopen)
        (statbuf.st_mode & 0007777) == 0400) {
 
        if (chmod(sudoers, sudoers_mode) == 0) {
-           warningx("fixed mode on %s", sudoers);
+           warningx(_("fixed mode on %s"), sudoers);
            SET(statbuf.st_mode, sudoers_mode);
            if (statbuf.st_gid != sudoers_gid) {
                if (chown(sudoers, (uid_t) -1, sudoers_gid) == 0) {
-                   warningx("set group on %s", sudoers);
+                   warningx(_("set group on %s"), sudoers);
                    statbuf.st_gid = sudoers_gid;
                } else
-                   warning("unable to set group on %s", sudoers);
+                   warning(_("unable to set group on %s"), sudoers);
            }
        } else
-           warning("unable to fix mode on %s", sudoers);
+           warning(_("unable to fix mode on %s"), sudoers);
     }
 
     /*
@@ -920,28 +954,28 @@ open_sudoers(const char *sudoers, int doedit, int *keepopen)
     set_perms(PERM_SUDOERS);
 
     if (rootstat != 0 && stat_sudoers(sudoers, &statbuf) != 0)
-       log_error(USE_ERRNO|NO_EXIT, "can't stat %s", sudoers);
+       log_error(USE_ERRNO|NO_EXIT, _("unable to stat %s"), sudoers);
     else if (!S_ISREG(statbuf.st_mode))
-       log_error(NO_EXIT, "%s is not a regular file", sudoers);
+       log_error(NO_EXIT, _("%s is not a regular file"), sudoers);
     else if ((statbuf.st_mode & 07577) != sudoers_mode)
-       log_error(NO_EXIT, "%s is mode 0%o, should be 0%o", sudoers,
+       log_error(NO_EXIT, _("%s is mode 0%o, should be 0%o"), sudoers,
            (unsigned int) (statbuf.st_mode & 07777),
            (unsigned int) sudoers_mode);
     else if (statbuf.st_uid != sudoers_uid)
-       log_error(NO_EXIT, "%s is owned by uid %u, should be %u", sudoers,
+       log_error(NO_EXIT, _("%s is owned by uid %u, should be %u"), sudoers,
            (unsigned int) statbuf.st_uid, (unsigned int) sudoers_uid);
-    else if (statbuf.st_gid != sudoers_gid)
-       log_error(NO_EXIT, "%s is owned by gid %u, should be %u", sudoers,
+    else if (statbuf.st_gid != sudoers_gid && ISSET(statbuf.st_mode, S_IRGRP|S_IWGRP))
+       log_error(NO_EXIT, _("%s is owned by gid %u, should be %u"), sudoers,
            (unsigned int) statbuf.st_gid, (unsigned int) sudoers_gid);
     else if ((fp = fopen(sudoers, "r")) == NULL)
-       log_error(USE_ERRNO|NO_EXIT, "can't open %s", sudoers);
+       log_error(USE_ERRNO|NO_EXIT, _("unable to open %s"), sudoers);
     else {
        /*
         * Make sure we can actually read sudoers so we can present the
         * user with a reasonable error message (unlike the lexer).
         */
        if (statbuf.st_size != 0 && fgetc(fp) == EOF) {
-           log_error(USE_ERRNO|NO_EXIT, "can't read %s", sudoers);
+           log_error(USE_ERRNO|NO_EXIT, _("unable to read %s"), sudoers);
            fclose(fp);
            fp = NULL;
        }
@@ -975,7 +1009,7 @@ set_loginclass(struct passwd *pw)
     if (login_class && strcmp(login_class, "-") != 0) {
        if (user_uid != 0 &&
            strcmp(runas_user ? runas_user : def_runas_default, "root") != 0)
-           errorx(1, "only root can use -c %s", login_class);
+           errorx(1, _("only root can use `-c %s'"), login_class);
     } else {
        login_class = pw->pw_class;
        if (!login_class || !*login_class)
@@ -985,7 +1019,7 @@ set_loginclass(struct passwd *pw)
 
     lc = login_getclass(login_class);
     if (!lc || !lc->lc_class || strcmp(lc->lc_class, login_class) != 0) {
-       log_error(errflags, "unknown login class: %s", login_class);
+       log_error(errflags, _("unknown login class: %s"), login_class);
        if (!lc)
            lc = login_getclass(NULL);  /* needed for login_getstyle() later */
     }
@@ -1019,7 +1053,7 @@ set_fqdn(void)
     if (!(hp = gethostbyname(user_host))) {
 #endif
        log_error(MSG_ONLY|NO_EXIT,
-           "unable to resolve host %s", user_host);
+           _("unable to resolve host %s"), user_host);
     } else {
        if (user_shost != user_host)
            efree(user_shost);
@@ -1041,8 +1075,8 @@ set_fqdn(void)
  * Get passwd entry for the user we are going to run commands as
  * and store it in runas_pw.  By default, commands run as "root".
  */
-static void
-set_runaspw(char *user)
+void
+set_runaspw(const char *user)
 {
     if (runas_pw != NULL)
        pw_delref(runas_pw);
@@ -1050,10 +1084,8 @@ set_runaspw(char *user)
        if ((runas_pw = sudo_getpwuid(atoi(user + 1))) == NULL)
            runas_pw = sudo_fakepwnam(user, runas_gr ? runas_gr->gr_gid : 0);
     } else {
-       if ((runas_pw = sudo_getpwnam(user)) == NULL) {
-           audit_failure(NewArgv, "unknown user: %s", user);
-           log_error(NO_MAIL|MSG_ONLY, "unknown user: %s", user);
-       }
+       if ((runas_pw = sudo_getpwnam(user)) == NULL)
+           log_error(NO_MAIL|MSG_ONLY, _("unknown user: %s"), user);
     }
 }
 
@@ -1062,7 +1094,7 @@ set_runaspw(char *user)
  * and store it in runas_gr.
  */
 static void
-set_runasgr(char *group)
+set_runasgr(const char *group)
 {
     if (runas_gr != NULL)
        gr_delref(runas_gr);
@@ -1071,10 +1103,22 @@ set_runasgr(char *group)
            runas_gr = sudo_fakegrnam(group);
     } else {
        if ((runas_gr = sudo_getgrnam(group)) == NULL)
-           log_error(NO_MAIL|MSG_ONLY, "unknown group: %s", group);
+           log_error(NO_MAIL|MSG_ONLY, _("unknown group: %s"), group);
     }
 }
 
+/*
+ * Callback for runas_default sudoers setting.
+ */
+static int
+cb_runas_default(const char *user)
+{
+    /* Only reset runaspw if user didn't specify one. */
+    if (!runas_user && !runas_group)
+       set_runaspw(user);
+    return TRUE;
+}
+
 /*
  * Cleanup hook for error()/errorx()
  */
@@ -1103,19 +1147,19 @@ sudoers_policy_version(int verbose)
        return -1;
     }
 
-    sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers policy plugin version %s\n",
+    sudo_printf(SUDO_CONV_INFO_MSG, _("Sudoers policy plugin version %s\n"),
        PACKAGE_VERSION);
-    sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers file grammar version %d\n",
+    sudo_printf(SUDO_CONV_INFO_MSG, _("Sudoers file grammar version %d\n"),
        SUDOERS_GRAMMAR_VERSION);
 
     if (verbose) {
-       sudo_printf(SUDO_CONV_INFO_MSG, "\nSudoers path: %s\n", sudoers_file);
+       sudo_printf(SUDO_CONV_INFO_MSG, _("\nSudoers path: %s\n"), sudoers_file);
 #ifdef HAVE_LDAP
 # ifdef _PATH_NSSWITCH_CONF
-       sudo_printf(SUDO_CONV_INFO_MSG, "nsswitch path: %s\n", _PATH_NSSWITCH_CONF);
+       sudo_printf(SUDO_CONV_INFO_MSG, _("nsswitch path: %s\n"), _PATH_NSSWITCH_CONF);
 # endif
-       sudo_printf(SUDO_CONV_INFO_MSG, "ldap.conf path: %s\n", _PATH_LDAP_CONF);
-       sudo_printf(SUDO_CONV_INFO_MSG, "ldap.secret path: %s\n", _PATH_LDAP_SECRET);
+       sudo_printf(SUDO_CONV_INFO_MSG, _("ldap.conf path: %s\n"), _PATH_LDAP_CONF);
+       sudo_printf(SUDO_CONV_INFO_MSG, _("ldap.secret path: %s\n"), _PATH_LDAP_SECRET);
 #endif
        dump_auth_methods();
        dump_defaults();
@@ -1130,7 +1174,7 @@ static int
 deserialize_info(char * const settings[], char * const user_info[])
 {
     char * const *cur;
-    const char *p;
+    const char *p, *groups = NULL;
     int flags = 0;
 
 #define MATCHES(s, v) (strncmp(s, v, sizeof(v) - 1) == 0)
@@ -1267,32 +1311,12 @@ deserialize_info(char * const settings[], char * const user_info[])
            continue;
        }
        if (MATCHES(*cur, "gid=")) {
-           user_gid = (gid_t) atoi(*cur + sizeof("gid=") - 1);
+           p = *cur + sizeof("gid=") - 1;
+           user_gid = (gid_t) atoi(p);
            continue;
        }
        if (MATCHES(*cur, "groups=")) {
-           /* Count number of groups */
-           const char *val = *cur + sizeof("groups=") - 1;
-           const char *cp;
-           if (val[0] != '\0') {
-               user_ngroups = 1;
-               for (cp = val; *cp != '\0'; cp++) {
-                   if (*cp == ',')
-                       user_ngroups++;
-               }
-
-               user_groups = emalloc2(user_ngroups, sizeof(GETGROUPS_T));
-               user_ngroups = 0;
-               cp = val;
-               for (;;) {
-                   /* XXX - strtol would be better here */
-                   user_groups[user_ngroups++] = atoi(cp);
-                   cp = strchr(cp, ',');
-                   if (cp == NULL)
-                       break;
-                   cp++; /* skip over comma */
-               }
-           }
+           groups = *cur + sizeof("groups=") - 1;
            continue;
        }
        if (MATCHES(*cur, "cwd=")) {
@@ -1325,6 +1349,36 @@ deserialize_info(char * const settings[], char * const user_info[])
     if (user_tty == NULL)
        user_tty = "unknown"; /* user_ttypath remains NULL */
 
+    if (groups != NULL && groups[0] != '\0') {
+       const char *cp;
+       GETGROUPS_T *gids;
+       int ngids;
+
+       /* Count number of groups, including passwd gid. */
+       ngids = 2;
+       for (cp = groups; *cp != '\0'; cp++) {
+           if (*cp == ',')
+               ngids++;
+       }
+
+       /* The first gid in the list is the passwd group gid. */
+       gids = emalloc2(ngids, sizeof(GETGROUPS_T));
+       gids[0] = user_gid;
+       ngids = 1;
+       cp = groups;
+       for (;;) {
+           gids[ngids] = atoi(cp);
+           if (gids[0] != gids[ngids])
+               ngids++;
+           cp = strchr(cp, ',');
+           if (cp == NULL)
+               break;
+           cp++; /* skip over comma */
+       }
+       set_group_list(user_name, gids, ngids);
+       efree(gids);
+    }
+
 #undef MATCHES
     return flags;
 }
@@ -1408,8 +1462,8 @@ find_editor(int nfiles, char **files, char ***argv_out)
            efree(editor);
     }
     if (!editor_path) {
-       audit_failure(NewArgv, "%s: command not found", editor);
-       warningx("%s: command not found", editor);
+       audit_failure(NewArgv, _("%s: command not found"), editor);
+       warningx(_("%s: command not found"), editor);
     }
     return editor_path;
 }