/* 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include /* FIXME: */ #include #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); } }