Annotation of src/usr.bin/doas/doas.c, Revision 1.44
1.44 ! tedu 1: /* $OpenBSD: doas.c,v 1.43 2015/10/22 04:57:20 deraadt Exp $ */
1.1 tedu 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: */
1.6 nicm 17:
1.1 tedu 18: #include <sys/types.h>
1.6 nicm 19: #include <sys/stat.h>
1.1 tedu 20:
21: #include <limits.h>
22: #include <login_cap.h>
23: #include <bsd_auth.h>
24: #include <string.h>
25: #include <stdio.h>
26: #include <stdlib.h>
27: #include <err.h>
28: #include <unistd.h>
29: #include <pwd.h>
30: #include <grp.h>
31: #include <syslog.h>
1.10 tedu 32: #include <errno.h>
1.1 tedu 33:
34: #include "doas.h"
35:
36: static void __dead
37: usage(void)
38: {
1.28 espie 39: fprintf(stderr, "usage: doas [-ns] [-C config] [-u user] command [args]\n");
1.1 tedu 40: exit(1);
41: }
42:
43: size_t
44: arraylen(const char **arr)
45: {
46: size_t cnt = 0;
1.9 tedu 47:
1.1 tedu 48: while (*arr) {
49: cnt++;
50: arr++;
51: }
52: return cnt;
53: }
54:
55: static int
56: parseuid(const char *s, uid_t *uid)
57: {
58: struct passwd *pw;
59: const char *errstr;
60:
61: if ((pw = getpwnam(s)) != NULL) {
62: *uid = pw->pw_uid;
63: return 0;
64: }
65: *uid = strtonum(s, 0, UID_MAX, &errstr);
66: if (errstr)
67: return -1;
68: return 0;
69: }
70:
71: static int
72: uidcheck(const char *s, uid_t desired)
73: {
74: uid_t uid;
75:
76: if (parseuid(s, &uid) != 0)
77: return -1;
78: if (uid != desired)
79: return -1;
80: return 0;
81: }
82:
1.33 tedu 83: static int
84: parsegid(const char *s, gid_t *gid)
1.1 tedu 85: {
86: struct group *gr;
87: const char *errstr;
88:
1.33 tedu 89: if ((gr = getgrnam(s)) != NULL) {
90: *gid = gr->gr_gid;
91: return 0;
92: }
93: *gid = strtonum(s, 0, GID_MAX, &errstr);
1.1 tedu 94: if (errstr)
95: return -1;
1.33 tedu 96: return 0;
1.1 tedu 97: }
98:
99: static int
100: match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
1.15 zhuk 101: const char **cmdargs, struct rule *r)
1.1 tedu 102: {
103: int i;
104:
105: if (r->ident[0] == ':') {
1.33 tedu 106: gid_t rgid;
107: if (parsegid(r->ident + 1, &rgid) == -1)
1.1 tedu 108: return 0;
109: for (i = 0; i < ngroups; i++) {
110: if (rgid == groups[i])
111: break;
112: }
113: if (i == ngroups)
114: return 0;
115: } else {
116: if (uidcheck(r->ident, uid) != 0)
117: return 0;
118: }
119: if (r->target && uidcheck(r->target, target) != 0)
120: return 0;
1.15 zhuk 121: if (r->cmd) {
122: if (strcmp(r->cmd, cmd))
123: return 0;
124: if (r->cmdargs) {
125: /* if arguments were given, they should match explicitly */
126: for (i = 0; r->cmdargs[i]; i++) {
127: if (!cmdargs[i])
128: return 0;
129: if (strcmp(r->cmdargs[i], cmdargs[i]))
130: return 0;
131: }
132: if (cmdargs[i])
133: return 0;
134: }
135: }
1.1 tedu 136: return 1;
137: }
138:
139: static int
140: permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
1.15 zhuk 141: uid_t target, const char *cmd, const char **cmdargs)
1.1 tedu 142: {
143: int i;
144:
145: *lastr = NULL;
146: for (i = 0; i < nrules; i++) {
1.31 deraadt 147: if (match(uid, groups, ngroups, target, cmd,
148: cmdargs, rules[i]))
1.1 tedu 149: *lastr = rules[i];
150: }
151: if (!*lastr)
152: return 0;
153: return (*lastr)->action == PERMIT;
154: }
155:
156: static void
1.22 zhuk 157: parseconfig(const char *filename, int checkperms)
1.1 tedu 158: {
159: extern FILE *yyfp;
160: extern int yyparse(void);
1.6 nicm 161: struct stat sb;
1.1 tedu 162:
163: yyfp = fopen(filename, "r");
1.36 espie 164: if (!yyfp)
165: err(1, checkperms ? "doas is not enabled, %s" :
166: "could not open config file %s", filename);
1.6 nicm 167:
1.22 zhuk 168: if (checkperms) {
169: if (fstat(fileno(yyfp), &sb) != 0)
170: err(1, "fstat(\"%s\")", filename);
171: if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
172: errx(1, "%s is writable by group or other", filename);
173: if (sb.st_uid != 0)
174: errx(1, "%s is not owned by root", filename);
175: }
1.6 nicm 176:
1.1 tedu 177: yyparse();
178: fclose(yyfp);
1.21 zhuk 179: if (parse_errors)
180: exit(1);
1.1 tedu 181: }
182:
1.30 zhuk 183: /*
1.32 tedu 184: * Copy the environment variables in safeset from oldenvp to envp.
1.30 zhuk 185: */
1.1 tedu 186: static int
1.4 deraadt 187: copyenvhelper(const char **oldenvp, const char **safeset, int nsafe,
188: char **envp, int ei)
1.1 tedu 189: {
190: int i;
1.9 tedu 191:
1.1 tedu 192: for (i = 0; i < nsafe; i++) {
193: const char **oe = oldenvp;
194: while (*oe) {
195: size_t len = strlen(safeset[i]);
196: if (strncmp(*oe, safeset[i], len) == 0 &&
197: (*oe)[len] == '=') {
198: if (!(envp[ei++] = strdup(*oe)))
199: err(1, "strdup");
200: break;
201: }
202: oe++;
203: }
204: }
205: return ei;
206: }
207:
208: static char **
209: copyenv(const char **oldenvp, struct rule *rule)
210: {
211: const char *safeset[] = {
1.14 tedu 212: "DISPLAY", "HOME", "LOGNAME", "MAIL",
1.1 tedu 213: "PATH", "TERM", "USER", "USERNAME",
1.11 tedu 214: NULL
215: };
216: const char *badset[] = {
217: "ENV",
218: NULL
1.1 tedu 219: };
220: char **envp;
221: const char **extra;
222: int ei;
1.11 tedu 223: int nsafe, nbad;
1.9 tedu 224: int nextras = 0;
1.20 zhuk 225:
1.30 zhuk 226: /* if there was no envvar whitelist, pass all except badset ones */
1.11 tedu 227: nbad = arraylen(badset);
1.1 tedu 228: if ((rule->options & KEEPENV) && !rule->envlist) {
1.30 zhuk 229: size_t iold, inew;
1.12 tedu 230: size_t oldlen = arraylen(oldenvp);
231: envp = reallocarray(NULL, oldlen + 1, sizeof(char *));
1.5 nicm 232: if (!envp)
233: err(1, "reallocarray");
1.30 zhuk 234: for (inew = iold = 0; iold < oldlen; iold++) {
235: size_t ibad;
236: for (ibad = 0; ibad < nbad; ibad++) {
237: size_t len = strlen(badset[ibad]);
238: if (strncmp(oldenvp[iold], badset[ibad], len) == 0 &&
239: oldenvp[iold][len] == '=') {
1.11 tedu 240: break;
241: }
242: }
1.30 zhuk 243: if (ibad == nbad) {
244: if (!(envp[inew] = strdup(oldenvp[iold])))
1.11 tedu 245: err(1, "strdup");
1.30 zhuk 246: inew++;
1.11 tedu 247: }
1.1 tedu 248: }
1.30 zhuk 249: envp[inew] = NULL;
1.1 tedu 250: return envp;
251: }
252:
253: nsafe = arraylen(safeset);
254: if ((extra = rule->envlist)) {
1.30 zhuk 255: size_t isafe;
1.1 tedu 256: nextras = arraylen(extra);
1.30 zhuk 257: for (isafe = 0; isafe < nsafe; isafe++) {
258: size_t iextras;
259: for (iextras = 0; iextras < nextras; iextras++) {
260: if (strcmp(extra[iextras], safeset[isafe]) == 0) {
1.29 zhuk 261: nextras--;
1.30 zhuk 262: extra[iextras] = extra[nextras];
1.1 tedu 263: extra[nextras] = NULL;
1.30 zhuk 264: iextras--;
1.1 tedu 265: }
266: }
267: }
268: }
269:
270: envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
271: if (!envp)
272: err(1, "can't allocate new environment");
273:
274: ei = 0;
275: ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
276: ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
277: envp[ei] = NULL;
278:
279: return envp;
280: }
281:
282: static void __dead
1.22 zhuk 283: checkconfig(const char *confpath, int argc, char **argv,
1.24 tedu 284: uid_t uid, gid_t *groups, int ngroups, uid_t target)
285: {
1.22 zhuk 286: struct rule *rule;
287:
288: setresuid(uid, uid, uid);
289: parseconfig(confpath, 0);
290: if (!argc)
291: exit(0);
292:
293: if (permit(uid, groups, ngroups, &rule, target, argv[0],
294: (const char **)argv + 1)) {
295: printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
1.24 tedu 296: exit(0);
1.22 zhuk 297: } else {
298: printf("deny\n");
1.24 tedu 299: exit(1);
1.22 zhuk 300: }
301: }
302:
1.1 tedu 303: int
304: main(int argc, char **argv, char **envp)
305: {
1.9 tedu 306: const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
307: "/usr/local/bin:/usr/local/sbin";
1.22 zhuk 308: const char *confpath = NULL;
1.9 tedu 309: char *shargv[] = { NULL, NULL };
310: char *sh;
311: const char *cmd;
1.7 doug 312: char cmdline[LINE_MAX];
313: char myname[_PW_NAME_LEN + 1];
1.9 tedu 314: struct passwd *pw;
315: struct rule *rule;
316: uid_t uid;
317: uid_t target = 0;
1.1 tedu 318: gid_t groups[NGROUPS_MAX + 1];
319: int ngroups;
320: int i, ch;
1.8 nicm 321: int sflag = 0;
1.26 espie 322: int nflag = 0;
1.38 doug 323: char cwdpath[PATH_MAX];
324: const char *cwd;
1.42 tedu 325:
1.43 deraadt 326: if (pledge("stdio rpath getpw proc exec id", NULL) == -1)
327: err(1, "pledge");
328:
1.42 tedu 329: closefrom(STDERR_FILENO + 1);
1.1 tedu 330:
1.26 espie 331: uid = getuid();
1.34 tedu 332:
1.26 espie 333: while ((ch = getopt(argc, argv, "C:nsu:")) != -1) {
1.1 tedu 334: switch (ch) {
1.16 tedu 335: case 'C':
1.22 zhuk 336: confpath = optarg;
337: break;
1.1 tedu 338: case 'u':
339: if (parseuid(optarg, &target) != 0)
340: errx(1, "unknown user");
341: break;
1.26 espie 342: case 'n':
343: nflag = 1;
344: break;
1.8 nicm 345: case 's':
346: sflag = 1;
347: break;
1.1 tedu 348: default:
349: usage();
350: break;
351: }
352: }
353: argv += optind;
354: argc -= optind;
355:
1.22 zhuk 356: if (confpath) {
357: if (sflag)
358: usage();
359: } else if ((!sflag && !argc) || (sflag && argc))
1.1 tedu 360: usage();
1.16 tedu 361:
1.1 tedu 362: pw = getpwuid(uid);
363: if (!pw)
364: err(1, "getpwuid failed");
1.7 doug 365: if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
366: errx(1, "pw_name too long");
1.1 tedu 367: ngroups = getgroups(NGROUPS_MAX, groups);
368: if (ngroups == -1)
369: err(1, "can't get groups");
370: groups[ngroups++] = getgid();
1.8 nicm 371:
372: if (sflag) {
373: sh = getenv("SHELL");
374: if (sh == NULL || *sh == '\0')
375: shargv[0] = pw->pw_shell;
376: else
377: shargv[0] = sh;
378: argv = shargv;
379: argc = 1;
380: }
1.22 zhuk 381:
1.24 tedu 382: if (confpath) {
383: checkconfig(confpath, argc, argv, uid, groups, ngroups,
384: target);
385: exit(1); /* fail safe */
386: }
387:
1.22 zhuk 388: parseconfig("/etc/doas.conf", 1);
1.8 nicm 389:
1.23 zhuk 390: /* cmdline is used only for logging, no need to abort on truncate */
1.25 zhuk 391: (void) strlcpy(cmdline, argv[0], sizeof(cmdline));
1.8 nicm 392: for (i = 1; i < argc; i++) {
393: if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
1.23 zhuk 394: break;
1.8 nicm 395: if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
1.23 zhuk 396: break;
1.8 nicm 397: }
1.1 tedu 398:
1.23 zhuk 399: cmd = argv[0];
1.15 zhuk 400: if (!permit(uid, groups, ngroups, &rule, target, cmd,
401: (const char**)argv + 1)) {
1.4 deraadt 402: syslog(LOG_AUTHPRIV | LOG_NOTICE,
403: "failed command for %s: %s", myname, cmdline);
1.41 tedu 404: errc(1, EPERM, NULL);
1.1 tedu 405: }
406:
407: if (!(rule->options & NOPASS)) {
1.26 espie 408: if (nflag)
409: errx(1, "Authorization required");
1.39 tedu 410: if (!auth_userokay(myname, NULL, "auth-doas", NULL)) {
1.4 deraadt 411: syslog(LOG_AUTHPRIV | LOG_NOTICE,
412: "failed password for %s", myname);
1.41 tedu 413: errc(1, EPERM, NULL);
1.1 tedu 414: }
415: }
1.43 deraadt 416:
417: if (pledge("stdio rpath getpw exec id", NULL) == -1)
418: err(1, "pledge");
419:
1.1 tedu 420: pw = getpwuid(target);
421: if (!pw)
422: errx(1, "no passwd entry for target");
1.43 deraadt 423:
424: if (pledge("stdio rpath id exec", NULL) == -1)
425: err(1, "pledge");
426:
1.1 tedu 427: if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
428: LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
429: LOGIN_SETUSER) != 0)
430: errx(1, "failed to set user context for target");
431:
1.43 deraadt 432: if (pledge("stdio rpath exec", NULL) == -1)
433: err(1, "pledge");
434:
1.38 doug 435: if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
436: cwd = "(failed)";
437: else
438: cwd = cwdpath;
1.43 deraadt 439:
440: if (pledge("stdio exec", NULL) == -1)
441: err(1, "pledge");
1.38 doug 442:
443: syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command %s as %s from %s",
444: myname, cmdline, pw->pw_name, cwd);
1.44 ! tedu 445:
! 446: envp = copyenv((const char **)envp, rule);
1.38 doug 447:
1.40 tedu 448: if (rule->cmd) {
449: if (setenv("PATH", safepath, 1) == -1)
450: err(1, "failed to set PATH '%s'", safepath);
451: }
1.1 tedu 452: execvpe(cmd, argv, envp);
1.10 tedu 453: if (errno == ENOENT)
454: errx(1, "%s: command not found", cmd);
1.1 tedu 455: err(1, "%s", cmd);
456: }