diff --git a/common/sysutils.c b/common/sysutils.c index df8ce9d1b..5f541938b 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include #include #ifdef HAVE_STAT @@ -54,6 +56,10 @@ # include # include #endif +#ifdef HAVE_PWD_H +# include +# include +#endif /*HAVE_PWD_H*/ #ifdef HAVE_W32_SYSTEM # if WINVER < 0x0500 # define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */ @@ -1054,6 +1060,98 @@ gnupg_getcwd (void) } +/* Try to set an envvar. Print only a notice on error. */ +static void +try_set_envvar (const char *name, const char *value, int silent) +{ + if (gnupg_setenv (name, value, 1)) + if (!silent) + log_info ("error setting envvar %s to '%s': %s\n", name, value, + gpg_strerror (my_error_from_syserror ())); +} + + +/* Switch to USER which is either a name or an UID. This is a nop + * under Windows. Note that in general it is only possible to switch + * to another user id if the process is running under root. if silent + * is set no diagnostics are printed. */ +gpg_error_t +gnupg_chuid (const char *user, int silent) +{ +#ifdef HAVE_W32_SYSTEM + (void)user; /* Not implemented for Windows - ignore. */ + (void)silent; + return 0; + +#elif HAVE_PWD_H /* A proper Unix */ + unsigned long ul; + struct passwd *pw; + struct stat st; + char *endp; + gpg_error_t err; + + gpg_err_set_errno (0); + ul = strtoul (user, &endp, 10); + if (errno || endp == user || *endp) + pw = getpwnam (user); /* Not a number; assume USER is a name. */ + else + pw = getpwuid ((uid_t)ul); + + if (!pw) + { + if (!silent) + log_error ("user '%s' not found\n", user); + return my_error (GPG_ERR_NOT_FOUND); + } + + /* Try to set some envvars even if we are already that user. */ + if (!stat (pw->pw_dir, &st)) + try_set_envvar ("HOME", pw->pw_dir, silent); + + try_set_envvar ("USER", pw->pw_name, silent); + try_set_envvar ("LOGNAME", pw->pw_name, silent); +#ifdef _AIX + try_set_envvar ("LOGIN", pw->pw_name, silent); +#endif + + if (getuid () == pw->pw_uid) + return 0; /* We are already this user. */ + + /* If we need to switch set PATH to a standard value and make sure + * GNUPGHOME is not set. */ + try_set_envvar ("PATH", "/usr/local/bin:/usr/bin:/bin", silent); + if (gnupg_unsetenv ("GNUPGHOME")) + if (!silent) + log_info ("error unsetting envvar %s: %s\n", "GNUPGHOME", + gpg_strerror (gpg_error_from_syserror ())); + + if (initgroups (pw->pw_name, pw->pw_gid)) + { + err = my_error_from_syserror (); + if (!silent) + log_error ("error setting supplementary groups for '%s': %s\n", + pw->pw_name, gpg_strerror (err)); + return err; + } + + if (setuid (pw->pw_uid)) + { + err = my_error_from_syserror (); + log_error ("error switching to user '%s': %s\n", + pw->pw_name, gpg_strerror (err)); + return err; + } + + return 0; + +#else /*!HAVE_PWD_H */ + if (!silent) + log_info ("system is missing passwd querying functions\n"); + return my_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + #ifdef HAVE_W32CE_SYSTEM /* There is a isatty function declaration in cegcc but it does not diff --git a/common/sysutils.h b/common/sysutils.h index c51bf7a9f..daded986f 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -73,6 +73,7 @@ char *gnupg_mkdtemp (char *template); int gnupg_setenv (const char *name, const char *value, int overwrite); int gnupg_unsetenv (const char *name); char *gnupg_getcwd (void); +gpg_error_t gnupg_chuid (const char *user, int silent); char *gnupg_get_socket_name (int fd); int gnupg_fd_valid (int fd);