From d6c13b3d391930f40e947cb68b770910c6e127f3 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 12 Jul 2021 23:19:47 +0200 Subject: [PATCH] first working config file --- config.template | 96 ----------------- us.c | 267 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 237 insertions(+), 126 deletions(-) delete mode 100644 config.template diff --git a/config.template b/config.template deleted file mode 100644 index af339c8..0000000 --- a/config.template +++ /dev/null @@ -1,96 +0,0 @@ -SECURITY CONSIDERATIONS -======================= - -1. commands must be given by absolute path, that's because if you do otherwise - nopassword commands could be hijacked: - - in the config: - nopass badguy as root cmd zzz - in the shell: - ~ $ export PATH=/home/badguy/test:$PATH - ~ $ mkdir test - ~ $ printf '#!/bin/sh\nrm -rf --no-preserve-root' > test/zzz - ~ $ chmod +x test/zzz - ~ $ us zzz #this deletes the filesystem without password! - -IDEA 1 -====== - -# this is a comment -# rules are goruped by user/group -# rules are structured somewhat like json, example: - -# Only 'command' is allowed to run without a password, all the rest is blocked -ale { - allow { - command nopass - } - - deny { - /.*/ - } -} - -IDEA 2 - THE DOAS WAY -===================== - -# this is a comment -# every line is a rule -# rules are structured like this: - -permit|deny [options] identity [as target] [cmd command [args ...]] - -# look at doas.conf(5) for more information - -IDEA 2-3 -======== - -# reverse-doas way --> identity permit|deny [command [args ...]] [options] - -# but how would I distinguish between command and options? --> identity [options] permit|deny [command [args ...]] - -# spaces are not a very good separatow when in comes to commands --> identity,[options],permit|deny,[command [args ...]] - -# -# this is kinda similar to a crontab, basically options are required -# - - # config structure: - -> identity options as action [command [args ...]] - ^ ^ ^ ^ - can be * | | permit, deny - can be nil (NULL) | - can be * - - # permit user "ale" to execute command "shutdown" as root without password: - -> ale nopass root permit shutdown - # permit members of the wheel group to execute any comands as any user: - -> :wheel nil * permit - # deny users of the wheel group to execute commands that begin with "sys": - # this could be circumvented by having the command inside a shell script - -> :wheel nil * deny /sys.*/ - # deny all users to execute all comands as any other user - -> * nil * deny -# -# let's scramble things up to make more sense -# - [action] options identity as [command [args ...]] - ^ ^ ^ ^ - | | can both be * (any) - | can be none, comma separated - none: permit - '!': deny (negate rule) - - # allow users of the wheel group to execute any command as root: - -> none :wheel root - # deny all users to execute commands that start with "sys" - -> ! none * * /sys.*/ - -IDEA 3 - THE SUCKLESS WAY -========================= - -configuration should happen inside a source file called config.h, to apply -changes to the configuration the program has to be recompiled diff --git a/us.c b/us.c index 5ae34a6..97dfba2 100644 --- a/us.c +++ b/us.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #if !defined(_XOPEN_CRYPT) || _XOPEN_CRYPT == -1 #include @@ -46,16 +48,37 @@ #endif #define MAX_HASH 1024 +#define CONF_FILE "/etc/us.conf" +#define CONF_LINE_MAX 1024 +#define GROUPS_MAX 256 +#define FLAG_PERSIST 0x1 +#define FLAG_NOPASS 0x2 +#define FLAG_NOLOG 0x4 + +struct env_elem { + char *name; + char *value; +}; + +struct config { + int type; + int flags; + char *who; + char *as; + struct env_elem *env; + int env_n; +}; static void *emalloc(size_t); static char *estrdup(const char *); +void *erealloc(void *, size_t); static void usage(void); static void die(const char *, ...); static int perm_set(struct passwd *, struct group *); static int authenticate(uid_t, char); static struct passwd* user_to_passwd(const char *); static struct group* group_to_grp(const char *); -//static int execvpe(const char *, char *const *, char *const *); +static int get_config(struct config **, int *); extern char **environ; @@ -104,21 +127,91 @@ int main(int argc, char *argv[]) fprintf(stderr, "getpwid: %s\n", strerror(errno)); return errno; } - char *my_name = estrdup(my_pw->pw_name); + char *my_name = my_pw->pw_name; + gid_t my_groups[GROUPS_MAX]; + int n_groups = 0; + if ((n_groups = getgroups(GROUPS_MAX, my_groups)) == -1) + die("getgroups:"); + + /* Get target user and group info */ + t_pw = user_to_passwd(t_usr); + if (!t_pw) + die("user_to_passwd:"); + t_gr = group_to_grp(t_grp); /* From now on most actions require root */ if (setuid(0) == -1) die("setuid:"); - /* Authenticate, we will be root from now on */ - if (authenticate(my_pw->pw_uid, 0)) - exit(EXIT_FAILURE); + /* get info from the config file and check if the action we want to + * do is permitted */ + struct env_elem *env_extra = NULL; + struct config *conf = NULL; + int conf_num, conf_flags = 0, env_extra_n = 0; + if (get_config(&conf, &conf_num) == -1) + die("get_config: invalid arguments"); + int here = 0; + for (int i = 0; i < conf_num; i++) { + struct passwd *who_pw, *as_pw; + struct group *who_gr, *as_gr; + int who_usr = conf[i].who[0] == ':' ? 0 : 1; + int as_usr = conf[i].as[0] == ':' ? 0 : 1; + if (who_usr) { + who_pw = user_to_passwd(conf[i].who); + if (my_pw->pw_uid != who_pw->pw_uid) { + free(who_pw); + continue; + } + } else { + who_gr = group_to_grp(conf[i].who); + gid_t w_gid = who_gr->gr_gid; + int x = 0; + for (; x < n_groups && w_gid != my_groups[x]; x++); + if (w_gid != my_groups[x]) { + free(who_gr); + continue; + } + } - /* Get target user and group info */ - t_pw = user_to_passwd(t_usr); - if (!t_pw) - die("usr_to_passwd:"); - t_gr = group_to_grp(t_grp); + if (as_usr) { + as_pw = user_to_passwd(conf[i].as); + if (!as_pw) + die("%s not a valid user", conf[i].as); + if (t_pw->pw_uid != as_pw->pw_uid) { + free(as_pw); + continue; + } + } else if (t_gr) { + as_gr = group_to_grp(conf[i].as); + if (t_gr->gr_gid != as_gr->gr_gid) { + free(as_gr); + continue; + } + } else { + continue; + } + here = 1; + if (conf[i].type == 0) + die("Permission denied"); + + conf_flags |= conf[i].flags; + if (conf[i].env_n) { + env_extra = erealloc(env_extra, + (env_extra_n+conf[i].env_n+1)*sizeof(struct env_elem)); + for (int j = env_extra_n, x = 0; j < env_extra_n + conf[i].env_n; j++, x++) + env_extra[j] = conf[i].env[x]; + env_extra_n += conf[i].env_n; + env_extra[env_extra_n] = (struct env_elem){NULL,NULL}; + } + + } + if (!here) + die("no rule found for user %s", my_name); + + /* Authenticate, we will be root from now on */ + if (!(conf_flags & FLAG_NOPASS)) + if (authenticate(my_pw->pw_uid, 0)) + exit(EXIT_FAILURE); /* Get target user's shell */ if (!shellflag) @@ -142,11 +235,6 @@ int main(int argc, char *argv[]) } c_argv[c_argc] = NULL; - struct env_elem { - char *name; - char *value; - }; - struct env_elem env_keep[] = { {"PATH", NULL}, {"TERM", NULL}, @@ -179,20 +267,30 @@ int main(int argc, char *argv[]) } } - if (envflag) { - for (int i = 0; env_keep[i].name; i++) { - if (env_keep[i].value) { - err = setenv(env_keep[i].name, env_keep[i].value, 1); - if (err == -1) { - fprintf(stderr, "setenv: %s\n", strerror(errno)); - goto fail_end; - } + for (int i = 0; envflag &&env_keep[i].name; i++) { + if (env_keep[i].value) { + err = setenv(env_keep[i].name, env_keep[i].value, 1); + if (err == -1) { + fprintf(stderr, "setenv: %s\n", strerror(errno)); + goto fail_end; + } + } + } + + for (int i = 0; env_extra && env_extra[i].name; i++) { + if (env_extra[i].value) { + err = setenv(env_extra[i].name, env_extra[i].value, 1); + if (err == -1) { + fprintf(stderr, "setenv: %s\n", strerror(errno)); + goto fail_end; } + } else { + unsetenv(env_extra[i].name); } } + // do not override, we might be under more levels of 'us' err = setenv("US_USER", my_name, 0); - free(my_name); errno = 0; /* Set permissions */ @@ -386,7 +484,7 @@ static struct passwd* user_to_passwd(const char *user) return NULL; } - struct passwd* pw; + struct passwd *pw, *pr; long uid_l; errno = 0; if (user[0] != '#') { @@ -405,8 +503,9 @@ static struct passwd* user_to_passwd(const char *user) errno = EINVAL; return NULL; } - - return pw; + pr = emalloc(sizeof(struct passwd)); + memcpy(pr, pw, sizeof(struct passwd)); + return pr; } static struct group* group_to_grp(const char *group) @@ -416,7 +515,7 @@ static struct group* group_to_grp(const char *group) return NULL; } - struct group* gr; + struct group *gr, *rr; long gid_l; errno = 0; if (group[0] != '#') { @@ -435,7 +534,9 @@ static struct group* group_to_grp(const char *group) errno = EINVAL; return NULL; } - return gr; + rr = emalloc(sizeof(struct group)); + memcpy(rr, gr, sizeof(struct group)); + return rr; } void die(const char *fmt, ...) @@ -460,12 +561,22 @@ void *emalloc(size_t s) { if (!s || s == (size_t)-1) die("bad malloc: invalid size"); - void *p = malloc(s); + void *p = calloc(1, s); if (!p) die("bad malloc:"); return p; } +void *erealloc(void *p, size_t s) +{ + if (!s || s == (size_t)-1) + die("bad realloc: invalid size"); + void *r = realloc(p, s); + if (!r) + die("bad realloc:"); + return r; +} + char *estrdup(const char *s) { if (!s) @@ -475,3 +586,99 @@ char *estrdup(const char *s) die("bad strdup:"); return r; } + +static int get_config(struct config **conf, int *num) +{ + if (!conf || !num) + return -1; + FILE *fp = fopen(CONF_FILE, "r"); + if (!fp) + die("fopen:"); + struct stat st; + if (fstat(fileno(fp), &st) == -1) + die("fstat:"); + if (st.st_uid != 0 || st.st_gid != 0) + die("config file must be owned by root:root"); + if (!S_ISREG(st.st_mode)) + die("config file must be a regular file"); + if (st.st_mode & S_IRWXO || st.st_mode & S_IROTH || + st.st_mode & S_IWOTH || st.st_mode & S_IXOTH) + die("others may not modify, read or execute config file"); + char line[CONF_LINE_MAX]; + *num = 0; + *conf = NULL; + for (int i = 0; fgets(line, CONF_LINE_MAX, fp); i++) { + char *s, *t, *sv; + int n = 0; + for (int ll = strlen(line)-1; isspace(line[ll]); line[ll--] = '\0'); + struct config c = {0}; + for (s = line;; s = NULL, n++) { + int getflags = 0; + if (!(t = strtok_r(s, " ", &sv))) + break; + if (*t == '#') + break; + switch (n) { + case 0: + if (!strcmp(t, "+")) + c.type = 1; + else if (!strcmp(t, "-")) + c.type = 0; + else + die("non valid config line %d", i); + break; + case 1: + c.who = estrdup(t); + break; + case 2: + if (strcmp(t, "as")) + die("non valid config line %d", i); + break; + case 3: + c.as = estrdup(t); + break; + default: + getflags = 1; + break; + } + if (!getflags) + continue; + + if (isupper(*t)) { + char *e, *se, *et; + for (e = t;; e = NULL) { + if (!(et = strtok_r(e, ",", &se))) + break; + char *sep; + int x; + if (!(sep = strchr(et, ','))) + die("invalid env at %d", i); + c.env = erealloc(c.env, (c.env_n+1)*sizeof(struct env_elem)); + x = c.env_n; + *sep = '\0'; + for (char *p = et; *p; p++) + if (!isupper(*p) && *p != '-' + && *p != '_') + die("non valid" + "env at %d", i); + c.env[x].name = estrdup(et); + c.env[x].value = estrdup(sep+1); + c.env_n++; + } + } else if (!strcmp(t, "persist")) { + c.flags |= FLAG_PERSIST; + } else if (!strcmp(t, "nopass")) { + c.flags |= FLAG_NOPASS; + } else if (!strcmp(t, "nolog")) { + c.flags |= FLAG_NOLOG; + } + } + if (n < 3) + die("non valid config line %d", i); + *conf = erealloc(*conf, ((*num)+1)*sizeof(struct config)); + (*conf)[(*num)] = c; + *num += 1; + } + fclose(fp); + return 0; +}