diff options
Diffstat (limited to 'lib/userprivs.c')
-rw-r--r-- | lib/userprivs.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/lib/userprivs.c b/lib/userprivs.c new file mode 100644 index 0000000..8d13c38 --- /dev/null +++ b/lib/userprivs.c @@ -0,0 +1,291 @@ +/* This file is part of Mailfromd. + Copyright (C) 2007, 2008 Sergey Poznyakoff + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <unistd.h> +#include <mailutils/assoc.h> +#include <mailutils/errno.h> +#include <mailutils/error.h> +#include <mailutils/errno.h> +#include <mailutils/nls.h> +#include <mailutils/list.h> +#include <mailutils/iterator.h> +/* FIXME: */ +#include <sysexits.h> +#include "libpies.h" + +int +get_user_groups (mu_list_t *pgrouplist, const char *user) +{ + int rc; + struct group *gr; + mu_list_t list; + + if (!*pgrouplist) + { + rc = mu_list_create (pgrouplist); + if (rc) + { + mu_error (_("%s: cannot create list: %s"), + "get_user_groups", mu_strerror (rc)); + return rc; + } + } + list = *pgrouplist; + setgrent (); + for (rc = 0; rc == 0 && (gr = getgrent ());) + { + char **p; + for (p = gr->gr_mem; *p; p++) + if (strcmp (*p, user) == 0) + { + /* FIXME: Avoid duplicating gids */ + rc = mu_list_append (list, (void *) gr->gr_gid); + if (rc) + mu_error (_("%s: cannot append to list: %s"), + "get_user_groups", mu_strerror (rc)); + break; + } + } + endgrent (); + return rc; +} + +/* Switch to the given UID/GID */ +int +switch_to_privs (uid_t uid, gid_t gid, mu_list_t retain_groups) +{ + int rc = 0; + gid_t *emptygidset; + size_t size = 1, j = 1; + mu_iterator_t itr; + + if (uid == 0) + { + mu_error (_("Refusing to run as root")); + return 1; + } + + /* Create a list of supplementary groups */ + mu_list_count (retain_groups, &size); + size++; + emptygidset = xmalloc (size * sizeof emptygidset[0]); + emptygidset[0] = gid ? gid : getegid (); + + if (mu_list_get_iterator (retain_groups, &itr) == 0) + { + for (mu_iterator_first (itr); + !mu_iterator_is_done (itr); mu_iterator_next (itr)) + mu_iterator_current (itr, (void **) (emptygidset + j++)); + mu_iterator_destroy (&itr); + } + + /* Reset group permissions */ + if (geteuid () == 0 && setgroups (j, emptygidset)) + { + mu_error (_("setgroups(1, %lu) failed: %s"), + (unsigned long) emptygidset[0], mu_strerror (errno)); + rc = 1; + } + free (emptygidset); + + /* Switch to the user's gid. On some OSes the effective gid must + be reset first */ + +#if defined(HAVE_SETEGID) + if ((rc = setegid (gid)) < 0) + mu_error (_("setegid(%lu) failed: %s"), + (unsigned long) gid, mu_strerror (errno)); +#elif defined(HAVE_SETREGID) + if ((rc = setregid (gid, gid)) < 0) + mu_error (_("setregid(%lu,%lu) failed: %s"), + (unsigned long) gid, (unsigned long) gid, mu_strerror (errno)); +#elif defined(HAVE_SETRESGID) + if ((rc = setresgid (gid, gid, gid)) < 0) + mu_error (_("setresgid(%lu,%lu,%lu) failed: %s"), + (unsigned long) gid, + (unsigned long) gid, (unsigned long) gid, mu_strerror (errno)); +#endif + + if (rc == 0 && gid != 0) + { + if ((rc = setgid (gid)) < 0 && getegid () != gid) + mu_error (_("setgid(%lu) failed: %s"), + (unsigned long) gid, mu_strerror (errno)); + if (rc == 0 && getegid () != gid) + { + mu_error (_("Cannot set effective gid to %lu"), + (unsigned long) gid); + rc = 1; + } + } + + /* Now reset uid */ + if (rc == 0 && uid != 0) + { + uid_t euid; + + if (setuid (uid) + || geteuid () != uid + || (getuid () != uid && (geteuid () == 0 || getuid () == 0))) + { + +#if defined(HAVE_SETREUID) + if (geteuid () != uid) + { + if (setreuid (uid, -1) < 0) + { + mu_error (_("setreuid(%lu,-1) failed: %s"), + (unsigned long) uid, mu_strerror (errno)); + rc = 1; + } + if (setuid (uid) < 0) + { + mu_error (_("second setuid(%lu) failed: %s"), + (unsigned long) uid, mu_strerror (errno)); + rc = 1; + } + } + else +#endif + { + mu_error (_("setuid(%lu) failed: %s"), + (unsigned long) uid, mu_strerror (errno)); + rc = 1; + } + } + + euid = geteuid (); + if (uid != 0 && setuid (0) == 0) + { + mu_error (_("seteuid(0) succeeded when it should not")); + rc = 1; + } + else if (uid != euid && setuid (euid) == 0) + { + mu_error (_("Cannot drop non-root setuid privileges")); + rc = 1; + } + + } + + return rc; +} + + +static int +translate_item (void *item, void *data) +{ + mu_list_t dst = data; + struct group *group = getgrnam (item); + if (!group) + { + mu_error (_("Unknown group: %s"), (char *) item); + return 1; + } + return mu_list_append (dst, (void *) group->gr_gid); +} + +static int +grouplist_translate (mu_list_t * pdst, mu_list_t src) +{ + mu_list_t dst; + int rc; + + if (!src) + return 0; + rc = mu_list_create (&dst); + if (rc) + { + mu_error (_("%s: cannot create list: %s"), + "grouplist_translate", mu_strerror (rc)); + return rc; + } + *pdst = dst; + return mu_list_do (src, translate_item, dst); +} + +void +mf_priv_setup (struct mf_privs *privs) +{ + struct passwd *pw; + mu_list_t grp = NULL; + + if (!privs || !privs->user) + return; + + pw = getpwnam (privs->user); + if (!pw) + { + mu_error (_("No such user: %s"), privs->user); + exit (EX_CONFIG); + } + + grouplist_translate (&grp, privs->groups); + if (privs->allgroups && get_user_groups (&grp, privs->user)) + exit (EX_CONFIG); + if (switch_to_privs (pw->pw_uid, pw->pw_gid, grp)) + exit (EX_SOFTWARE); + mu_list_destroy (&grp); +} + + +void +mf_epriv_setup (struct mf_privs *privs) +{ + uid_t uid; + gid_t gid; + + if (privs) + { + struct passwd *pw; + if (!privs->user) + return; + + pw = getpwnam (privs->user); + if (!pw) + { + mu_error (_("No such user: %s"), privs->user); + exit (EX_CONFIG); + } + uid = pw->pw_uid; + gid = pw->pw_gid; + } + else + { + uid = 0; + gid = 0; + } + + if (setegid (gid)) + { + mu_error (_("Cannot switch to EGID %lu: %s"), + (unsigned long) gid, mu_strerror (errno)); + exit (EX_USAGE); + } + if (seteuid (uid)) + { + mu_error (_("Cannot switch to EUID %lu: %s"), + (unsigned long) uid, mu_strerror (errno)); + exit (EX_USAGE); + } +} |