/* 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
#include
#include
#include "libmf.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);
}
}