diff options
Diffstat (limited to 'src/runas.c')
-rw-r--r-- | src/runas.c | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/runas.c b/src/runas.c new file mode 100644 index 0000000..8d86468 --- /dev/null +++ b/src/runas.c @@ -0,0 +1,179 @@ +/* This file is part of NSsync + Copyright (C) 2017 Sergey Poznyakoff + + NSsync 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. + + NSsync 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 NSsync. If not, see <http://www.gnu.org/licenses/>. */ + +#include "nssync.h" +#include <pwd.h> +#include <grp.h> + +char *runas_user; +char *runas_group; + +#ifndef SIZE_T_MAX +# define SIZE_T_MAX ((size_t)-1) +#endif + +static void +addgid(gid_t **pgv, size_t *pgc, size_t *pgi, gid_t gid) +{ + gid_t *gv = *pgv; + size_t gc = *pgc; + size_t gi = *pgi; + + if (gi == gc) { + if (gc == 0) { + gc = 16; + gv = grecs_calloc(gc, sizeof(*gv)); + } else if (gc <= SIZE_T_MAX / 2 / sizeof(*gv)) { + gc *= 2; + gv = grecs_realloc(gv, gc * sizeof(*gv)); + } + } + gv[gi++] = gid; + *pgv = gv; + *pgc = gc; + *pgi = gi; +} + +static int +member(gid_t *gv, size_t gc, gid_t gid) +{ + size_t i; + + for (i = 0; i < gc; i++) + if (gv[i] == gid) + return 1; + return 0; +} + +static size_t +get_user_groups(const char *user, gid_t **pgv, size_t *pgc) +{ + struct group *gr; + size_t gi = 0; + + setgrent(); + while ((gr = getgrent())) { + char **p; + for (p = gr->gr_mem; *p; p++) + if (strcmp(*p, user) == 0) + addgid(pgv, pgc, &gi, gr->gr_gid); + } + endgrent(); + return gi; +} + +void +runas(void) +{ + struct passwd *pw; + struct group *gr; + uid_t uid; + gid_t gid; + char const *user_name; + gid_t *gv; + size_t gc, gn; + + if (!(runas_user || runas_group)) + return; + if (getuid() != 0) { + error("not root: can't switch to user privileges"); + exit(EX_USAGE); + } + + user_name = runas_user; + if (!user_name) { + pw = getpwuid(0); + user_name = "root"; + } else if (user_name[0] == '+') { + char *end; + unsigned long n; + + errno = 0; + n = strtoul(user_name + 1, &end, 10); + if (errno || *end) { + error("invalid user name %s", user_name); + exit(EX_USAGE); + } + + pw = getpwuid(n); + } else + pw = getpwnam(user_name); + + if (!pw) { + error("%s: no such user", runas_user); + exit(EX_USAGE); + } + user_name = pw->pw_name; + + uid = pw->pw_uid; + gid = pw->pw_gid; + + if (runas_group) { + if (runas_group[0] == '+') { + char *end; + unsigned long n; + + errno = 0; + n = strtoul(runas_group + 1, &end, 10); + if (errno || *end) { + error("invalid group name %s", runas_group); + exit(EX_USAGE); + } + + gr = getgrgid(n); + } else + gr = getgrnam(runas_group); + + if (!gr) { + error("%s: no such group", runas_user); + exit(EX_USAGE); + } + + gid = gr->gr_gid; + } + + gv = NULL; + gc = 0; + gn = get_user_groups(user_name, &gv, &gc); + if (!member(gv, gn, gid)) + addgid(&gv, &gc, &gn, gid); + + /* Reset group permissions */ + if (setgroups(gc, gv)) { + error("setgroups failed: %s", strerror(errno)); + exit(EX_UNAVAILABLE); + } + free(gv); + + + if (gid) { + /* Switch to the user's gid. */ + if (setgid(gid)) { + error("setgid(%lu) failed: %s", + (unsigned long) gid, strerror(errno)); + exit(EX_UNAVAILABLE); + } + } + + /* Now reset uid */ + if (uid) { + if (setuid(uid)) { + error("setuid(%lu) failed: %s", + (unsigned long) uid, strerror(errno)); + exit(EX_UNAVAILABLE); + } + } +} |