Annotation of src/usr.bin/doas/doas.c, Revision 1.1
1.1 ! tedu 1: /* $OpenBSD$ */
! 2: /*
! 3: * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
! 4: *
! 5: * Permission to use, copy, modify, and distribute this software for any
! 6: * purpose with or without fee is hereby granted, provided that the above
! 7: * copyright notice and this permission notice appear in all copies.
! 8: *
! 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 16: */
! 17: #include <sys/types.h>
! 18:
! 19: #include <limits.h>
! 20: #include <login_cap.h>
! 21: #include <bsd_auth.h>
! 22: #include <string.h>
! 23: #include <stdio.h>
! 24: #include <stdlib.h>
! 25: #include <err.h>
! 26: #include <unistd.h>
! 27: #include <pwd.h>
! 28: #include <grp.h>
! 29: #include <syslog.h>
! 30:
! 31: #include "doas.h"
! 32:
! 33: static void __dead
! 34: usage(void)
! 35: {
! 36: fprintf(stderr, "usage: doas [-u user] command [args]\n");
! 37: exit(1);
! 38: }
! 39:
! 40: size_t
! 41: arraylen(const char **arr)
! 42: {
! 43: size_t cnt = 0;
! 44: while (*arr) {
! 45: cnt++;
! 46: arr++;
! 47: }
! 48: return cnt;
! 49: }
! 50:
! 51: static int
! 52: parseuid(const char *s, uid_t *uid)
! 53: {
! 54: struct passwd *pw;
! 55: const char *errstr;
! 56:
! 57: if ((pw = getpwnam(s)) != NULL) {
! 58: *uid = pw->pw_uid;
! 59: return 0;
! 60: }
! 61: *uid = strtonum(s, 0, UID_MAX, &errstr);
! 62: if (errstr)
! 63: return -1;
! 64: return 0;
! 65: }
! 66:
! 67: static int
! 68: uidcheck(const char *s, uid_t desired)
! 69: {
! 70: uid_t uid;
! 71:
! 72: if (parseuid(s, &uid) != 0)
! 73: return -1;
! 74: if (uid != desired)
! 75: return -1;
! 76: return 0;
! 77: }
! 78:
! 79: static gid_t
! 80: strtogid(const char *s)
! 81: {
! 82: struct group *gr;
! 83: const char *errstr;
! 84: gid_t gid;
! 85:
! 86: if ((gr = getgrnam(s)) != NULL)
! 87: return gr->gr_gid;
! 88: gid = strtonum(s, 0, GID_MAX, &errstr);
! 89: if (errstr)
! 90: return -1;
! 91: return gid;
! 92: }
! 93:
! 94: static int
! 95: match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
! 96: struct rule *r)
! 97: {
! 98: int i;
! 99:
! 100: if (r->ident[0] == ':') {
! 101: gid_t rgid = strtogid(r->ident + 1);
! 102: if (rgid == -1)
! 103: return 0;
! 104: for (i = 0; i < ngroups; i++) {
! 105: if (rgid == groups[i])
! 106: break;
! 107: }
! 108: if (i == ngroups)
! 109: return 0;
! 110: } else {
! 111: if (uidcheck(r->ident, uid) != 0)
! 112: return 0;
! 113: }
! 114: if (r->target && uidcheck(r->target, target) != 0)
! 115: return 0;
! 116: if (r->cmd && strcmp(r->cmd, cmd) != 0)
! 117: return 0;
! 118: return 1;
! 119: }
! 120:
! 121: static int
! 122: permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
! 123: uid_t target, const char *cmd)
! 124: {
! 125: int i;
! 126:
! 127: *lastr = NULL;
! 128: for (i = 0; i < nrules; i++) {
! 129: if (match(uid, groups, ngroups, target, cmd, rules[i]))
! 130: *lastr = rules[i];
! 131: }
! 132: if (!*lastr)
! 133: return 0;
! 134: return (*lastr)->action == PERMIT;
! 135: }
! 136:
! 137: static void
! 138: parseconfig(const char *filename)
! 139: {
! 140: extern FILE *yyfp;
! 141: extern int yyparse(void);
! 142:
! 143: yyfp = fopen(filename, "r");
! 144: if (!yyfp) {
! 145: fprintf(stderr, "doas is not enabled.\n");
! 146: exit(1);
! 147: }
! 148: yyparse();
! 149: fclose(yyfp);
! 150: }
! 151:
! 152: static int
! 153: copyenvhelper(const char **oldenvp, const char **safeset, int nsafe, char **envp, int ei)
! 154: {
! 155: int i;
! 156: for (i = 0; i < nsafe; i++) {
! 157: const char **oe = oldenvp;
! 158: while (*oe) {
! 159: size_t len = strlen(safeset[i]);
! 160: if (strncmp(*oe, safeset[i], len) == 0 &&
! 161: (*oe)[len] == '=') {
! 162: if (!(envp[ei++] = strdup(*oe)))
! 163: err(1, "strdup");
! 164: break;
! 165: }
! 166: oe++;
! 167: }
! 168: }
! 169: return ei;
! 170: }
! 171:
! 172: static char **
! 173: copyenv(const char **oldenvp, struct rule *rule)
! 174: {
! 175: const char *safeset[] = {
! 176: "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
! 177: "PATH", "TERM", "USER", "USERNAME",
! 178: NULL,
! 179: };
! 180: int nsafe;
! 181: int nextras = 0;
! 182: char **envp;
! 183: const char **extra;
! 184: int ei;
! 185: int i, j;
! 186:
! 187: if ((rule->options & KEEPENV) && !rule->envlist) {
! 188: j = arraylen(oldenvp);
! 189: envp = reallocarray(NULL, j + 1, sizeof(char *));
! 190: for (i = 0; i < j; i++) {
! 191: if (!(envp[i] = strdup(oldenvp[i])))
! 192: err(1, "strdup");
! 193: }
! 194: envp[i] = NULL;
! 195: return envp;
! 196: }
! 197:
! 198: nsafe = arraylen(safeset);
! 199: if ((extra = rule->envlist)) {
! 200: nextras = arraylen(extra);
! 201: for (i = 0; i < nsafe; i++) {
! 202: for (j = 0; j < nextras; j++) {
! 203: if (strcmp(extra[j], safeset[i]) == 0) {
! 204: extra[j--] = extra[nextras--];
! 205: extra[nextras] = NULL;
! 206: }
! 207: }
! 208: }
! 209: }
! 210:
! 211: envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
! 212: if (!envp)
! 213: err(1, "can't allocate new environment");
! 214:
! 215: ei = 0;
! 216: ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
! 217: ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
! 218: envp[ei] = NULL;
! 219:
! 220: return envp;
! 221: }
! 222:
! 223: static void __dead
! 224: fail(void)
! 225: {
! 226: const char *msgs[] = {
! 227: "No lollygagging!",
! 228: "Better luck next time.",
! 229: "PEBKAC detected.",
! 230: "That's what happens when you're lazy.",
! 231: "It is clear that this has not been thought through.",
! 232: "That's the most ridiculous thing I've heard in the last two or three minutes!",
! 233: "No sane people allowed here. Go home.",
! 234: "I would explain, but I am too drunk.",
! 235: "You're not allowed to have an opinion.",
! 236: "Complaint forms are handled in another department.",
! 237: };
! 238: const char *m;
! 239:
! 240: m = msgs[arc4random_uniform(sizeof(msgs) / sizeof(msgs[0]))];
! 241: fprintf(stderr, m);
! 242: fprintf(stderr, "\n");
! 243: exit(1);
! 244: }
! 245:
! 246: int
! 247: main(int argc, char **argv, char **envp)
! 248: {
! 249: char cmdline[1024];
! 250: char myname[32];
! 251: uid_t uid, target = 0;
! 252: gid_t groups[NGROUPS_MAX + 1];
! 253: int ngroups;
! 254: struct passwd *pw;
! 255: struct rule *rule;
! 256: const char *cmd;
! 257: int i, ch;
! 258: const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
! 259:
! 260: parseconfig("/etc/doas.conf");
! 261:
! 262: while ((ch = getopt(argc, argv, "u:")) != -1) {
! 263: switch (ch) {
! 264: case 'u':
! 265: if (parseuid(optarg, &target) != 0)
! 266: errx(1, "unknown user");
! 267: break;
! 268: default:
! 269: usage();
! 270: break;
! 271: }
! 272: }
! 273: argv += optind;
! 274: argc -= optind;
! 275:
! 276: if (!argc)
! 277: usage();
! 278:
! 279: cmd = argv[0];
! 280: strlcpy(cmdline, argv[0], sizeof(cmdline));
! 281: for (i = 1; i < argc; i++) {
! 282: strlcat(cmdline, " ", sizeof(cmdline));
! 283: strlcat(cmdline, argv[i], sizeof(cmdline));
! 284: }
! 285:
! 286: uid = getuid();
! 287: pw = getpwuid(uid);
! 288: if (!pw)
! 289: err(1, "getpwuid failed");
! 290: strlcpy(myname, pw->pw_name, sizeof(myname));
! 291: ngroups = getgroups(NGROUPS_MAX, groups);
! 292: if (ngroups == -1)
! 293: err(1, "can't get groups");
! 294: groups[ngroups++] = getgid();
! 295:
! 296: if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
! 297: syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
! 298: fail();
! 299: }
! 300:
! 301: if (!(rule->options & NOPASS)) {
! 302: if (!auth_userokay(myname, NULL, NULL, NULL)) {
! 303: syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
! 304: fail();
! 305: }
! 306: }
! 307: envp = copyenv((const char **)envp, rule);
! 308:
! 309: pw = getpwuid(target);
! 310: if (!pw)
! 311: errx(1, "no passwd entry for target");
! 312: if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
! 313: LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
! 314: LOGIN_SETUSER) != 0)
! 315: errx(1, "failed to set user context for target");
! 316:
! 317: syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s", myname, pw->pw_name, cmdline);
! 318: setenv("PATH", safepath, 1);
! 319: execvpe(cmd, argv, envp);
! 320: err(1, "%s", cmd);
! 321: }