/* This file is part of Mailfromd. Copyright (C) 2007-2020 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> #include <mailutils/alloc.h> #include <sysexits.h> #include "libmf.h" #define NGIDSINBLOCK 16 struct mf_gid_block { struct mf_gid_block *next; size_t count; size_t size; gid_t gid[1]; }; struct mf_gid_list { struct mf_gid_block *head, *tail; int sorted; }; struct mf_gid_list * mf_gid_list_alloc() { struct mf_gid_list *p = mu_alloc(sizeof(*p)); p->head = p->tail = NULL; p->sorted = 0; return p; } void mf_gid_list_free(struct mf_gid_list *gl) { struct mf_gid_block *p; if (!gl) return; for (p = gl->head; p; ) { struct mf_gid_block *next = p->next; free(p); p = next; } } static void mf_gid_list_add_block(struct mf_gid_list *gl) { struct mf_gid_block *p = mu_alloc(sizeof(*p) + NGIDSINBLOCK); p->next = NULL; p->size = NGIDSINBLOCK; p->count = 0; if (gl->tail) gl->tail->next = p; else gl->head = p; gl->tail = p; } void mf_gid_list_add(struct mf_gid_list *gl, gid_t gid) { if (!gl->tail || gl->tail->count == gl->tail->size) mf_gid_list_add_block(gl); gl->tail->gid[gl->tail->count++] = gid; gl->sorted = 0; } struct mf_gid_list * mf_gid_list_dup(struct mf_gid_list *src) { struct mf_gid_list *dst = mf_gid_list_alloc(); struct mf_gid_block *p; if (src) { for (p = src->head; p; p = p->next) { size_t i; for (i = 0; i < p->count; i++) mf_gid_list_add(dst, p->gid[i]); } } return dst; } static int gid_cmp(const void *a, const void *b) { gid_t const *ga = a; gid_t const *gb = b; if (*ga < *gb) return -1; if (*ga > *gb) return 1; return 0; } void mf_gid_list_array(struct mf_gid_list *gl, size_t *gc, gid_t **gv) { struct mf_gid_block *p; size_t i, j; if (!gl || gl->head == NULL) { *gc = 0; *gv = NULL; return; } p = gl->head; if (p->next) { size_t n; struct mf_gid_block *q; for (n = 0, p = gl->head; p; p = p->next) n += p->count; p = mu_realloc(gl->head, sizeof(*p) + n); gl->head = p; n = p->count; for (q = p->next; q; ) { struct mf_gid_block *next = q->next; size_t i; for (i = 0; i < q->count; i++) p->gid[n++] = q->gid[i]; free(q); q = next; } p->count = n; p->next = NULL; } if (!gl->sorted) { qsort(p->gid, p->count, sizeof(p->gid[0]), gid_cmp); for (i = j = 1; i < p->count; j++) if (p->gid[j-1] != p->gid[j]) p->gid[i++] = p->gid[j]; p->count = i; gl->sorted = 1; } *gc = p->count; *gv = p->gid; } void get_user_groups(struct mf_gid_list *gl, const char *user) { struct group *gr; setgrent(); while ((gr = getgrent())) { char **p; for (p = gr->gr_mem; *p; p++) if (strcmp(*p, user) == 0) { mf_gid_list_add(gl, gr->gr_gid); } } endgrent(); } /* Switch to the given UID/GID */ int switch_to_privs(uid_t uid, gid_t gid, struct mf_gid_list *retain_groups) { int rc = 0; struct mf_gid_list *gl; size_t gc; gid_t *gv; if (uid == 0) { mu_error(_("refusing to run as root")); return 1; } /* Create a list of supplementary groups */ gl = mf_gid_list_dup(retain_groups); mf_gid_list_add(gl, gid ? gid : getegid()); mf_gid_list_array(gl, &gc, &gv); /* Reset group permissions */ if (geteuid() == 0 && setgroups(gc, gv)) { mu_error(_("setgroups failed: %s"), mu_strerror(errno)); rc = 1; } mf_gid_list_free(gl); /* 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) { struct mf_gid_list *dst = data; struct group *group = getgrnam(item); if (!group) { mu_error(_("unknown group: %s"), (char*) item); return 1; } mf_gid_list_add(dst, group->gr_gid); return 0; } static struct mf_gid_list * grouplist_translate(mu_list_t src) { struct mf_gid_list *dst = mf_gid_list_alloc(); mu_list_foreach(src, translate_item, dst); return dst; } void mf_priv_setup(struct mf_privs *privs) { struct passwd *pw; struct mf_gid_list *grp; if (!privs || !privs->user) return; pw = getpwnam(privs->user); if (!pw) { mu_error(_("no such user: %s"), privs->user); exit(EX_CONFIG); } grp = grouplist_translate(privs->groups); if (privs->allgroups) get_user_groups(grp, privs->user); if (switch_to_privs(pw->pw_uid, pw->pw_gid, grp)) exit(EX_SOFTWARE); mf_gid_list_free(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); } }