Annotation of src/usr.bin/doas/doas.c, Revision 1.9
1.9 ! tedu 1: /* $OpenBSD: doas.c,v 1.8 2015/07/18 06:33:23 nicm 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>
32:
33: #include "doas.h"
34:
35: static void __dead
36: usage(void)
37: {
1.8 nicm 38: fprintf(stderr, "usage: doas [-s] [-u user] command [args]\n");
1.1 tedu 39: exit(1);
40: }
41:
42: size_t
43: arraylen(const char **arr)
44: {
45: size_t cnt = 0;
1.9 ! tedu 46:
1.1 tedu 47: while (*arr) {
48: cnt++;
49: arr++;
50: }
51: return cnt;
52: }
53:
54: static int
55: parseuid(const char *s, uid_t *uid)
56: {
57: struct passwd *pw;
58: const char *errstr;
59:
60: if ((pw = getpwnam(s)) != NULL) {
61: *uid = pw->pw_uid;
62: return 0;
63: }
64: *uid = strtonum(s, 0, UID_MAX, &errstr);
65: if (errstr)
66: return -1;
67: return 0;
68: }
69:
70: static int
71: uidcheck(const char *s, uid_t desired)
72: {
73: uid_t uid;
74:
75: if (parseuid(s, &uid) != 0)
76: return -1;
77: if (uid != desired)
78: return -1;
79: return 0;
80: }
81:
82: static gid_t
83: strtogid(const char *s)
84: {
85: struct group *gr;
86: const char *errstr;
87: gid_t gid;
88:
89: if ((gr = getgrnam(s)) != NULL)
90: return gr->gr_gid;
91: gid = strtonum(s, 0, GID_MAX, &errstr);
92: if (errstr)
93: return -1;
94: return gid;
95: }
96:
97: static int
98: match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
99: struct rule *r)
100: {
101: int i;
102:
103: if (r->ident[0] == ':') {
104: gid_t rgid = strtogid(r->ident + 1);
105: if (rgid == -1)
106: return 0;
107: for (i = 0; i < ngroups; i++) {
108: if (rgid == groups[i])
109: break;
110: }
111: if (i == ngroups)
112: return 0;
113: } else {
114: if (uidcheck(r->ident, uid) != 0)
115: return 0;
116: }
117: if (r->target && uidcheck(r->target, target) != 0)
118: return 0;
119: if (r->cmd && strcmp(r->cmd, cmd) != 0)
120: return 0;
121: return 1;
122: }
123:
124: static int
125: permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
126: uid_t target, const char *cmd)
127: {
128: int i;
129:
130: *lastr = NULL;
131: for (i = 0; i < nrules; i++) {
132: if (match(uid, groups, ngroups, target, cmd, rules[i]))
133: *lastr = rules[i];
134: }
135: if (!*lastr)
136: return 0;
137: return (*lastr)->action == PERMIT;
138: }
139:
140: static void
141: parseconfig(const char *filename)
142: {
143: extern FILE *yyfp;
144: extern int yyparse(void);
1.6 nicm 145: struct stat sb;
1.1 tedu 146:
147: yyfp = fopen(filename, "r");
148: if (!yyfp) {
149: fprintf(stderr, "doas is not enabled.\n");
150: exit(1);
151: }
1.6 nicm 152:
153: if (fstat(fileno(yyfp), &sb) != 0)
154: err(1, "fstat(\"%s\")", filename);
155: if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
156: errx(1, "%s is writable by group or other", filename);
157: if (sb.st_uid != 0)
158: errx(1, "%s is not owned by root", filename);
159:
1.1 tedu 160: yyparse();
161: fclose(yyfp);
162: }
163:
164: static int
1.4 deraadt 165: copyenvhelper(const char **oldenvp, const char **safeset, int nsafe,
166: char **envp, int ei)
1.1 tedu 167: {
168: int i;
1.9 ! tedu 169:
1.1 tedu 170: for (i = 0; i < nsafe; i++) {
171: const char **oe = oldenvp;
172: while (*oe) {
173: size_t len = strlen(safeset[i]);
174: if (strncmp(*oe, safeset[i], len) == 0 &&
175: (*oe)[len] == '=') {
176: if (!(envp[ei++] = strdup(*oe)))
177: err(1, "strdup");
178: break;
179: }
180: oe++;
181: }
182: }
183: return ei;
184: }
185:
186: static char **
187: copyenv(const char **oldenvp, struct rule *rule)
188: {
189: const char *safeset[] = {
190: "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
191: "PATH", "TERM", "USER", "USERNAME",
192: NULL,
193: };
194: char **envp;
195: const char **extra;
196: int ei;
197: int i, j;
1.9 ! tedu 198: int nsafe;
! 199: int nextras = 0;
1.1 tedu 200:
201: if ((rule->options & KEEPENV) && !rule->envlist) {
202: j = arraylen(oldenvp);
203: envp = reallocarray(NULL, j + 1, sizeof(char *));
1.5 nicm 204: if (!envp)
205: err(1, "reallocarray");
1.1 tedu 206: for (i = 0; i < j; i++) {
207: if (!(envp[i] = strdup(oldenvp[i])))
208: err(1, "strdup");
209: }
210: envp[i] = NULL;
211: return envp;
212: }
213:
214: nsafe = arraylen(safeset);
215: if ((extra = rule->envlist)) {
216: nextras = arraylen(extra);
217: for (i = 0; i < nsafe; i++) {
218: for (j = 0; j < nextras; j++) {
219: if (strcmp(extra[j], safeset[i]) == 0) {
220: extra[j--] = extra[nextras--];
221: extra[nextras] = NULL;
222: }
223: }
224: }
225: }
226:
227: envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
228: if (!envp)
229: err(1, "can't allocate new environment");
230:
231: ei = 0;
232: ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
233: ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
234: envp[ei] = NULL;
235:
236: return envp;
237: }
238:
239: static void __dead
240: fail(void)
241: {
1.3 tedu 242: fprintf(stderr, "Permission denied\n");
1.1 tedu 243: exit(1);
244: }
245:
246: int
247: main(int argc, char **argv, char **envp)
248: {
1.9 ! tedu 249: const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
! 250: "/usr/local/bin:/usr/local/sbin";
! 251: char *shargv[] = { NULL, NULL };
! 252: char *sh;
! 253: const char *cmd;
1.7 doug 254: char cmdline[LINE_MAX];
255: char myname[_PW_NAME_LEN + 1];
1.9 ! tedu 256: struct passwd *pw;
! 257: struct rule *rule;
! 258: uid_t uid;
! 259: uid_t target = 0;
1.1 tedu 260: gid_t groups[NGROUPS_MAX + 1];
261: int ngroups;
262: int i, ch;
1.8 nicm 263: int sflag = 0;
1.1 tedu 264:
265: parseconfig("/etc/doas.conf");
266:
1.8 nicm 267: while ((ch = getopt(argc, argv, "su:")) != -1) {
1.1 tedu 268: switch (ch) {
269: case 'u':
270: if (parseuid(optarg, &target) != 0)
271: errx(1, "unknown user");
272: break;
1.8 nicm 273: case 's':
274: sflag = 1;
275: break;
1.1 tedu 276: default:
277: usage();
278: break;
279: }
280: }
281: argv += optind;
282: argc -= optind;
283:
1.8 nicm 284: if ((!sflag && !argc) || (sflag && argc))
1.1 tedu 285: usage();
286:
287: uid = getuid();
288: pw = getpwuid(uid);
289: if (!pw)
290: err(1, "getpwuid failed");
1.7 doug 291: if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
292: errx(1, "pw_name too long");
1.1 tedu 293: ngroups = getgroups(NGROUPS_MAX, groups);
294: if (ngroups == -1)
295: err(1, "can't get groups");
296: groups[ngroups++] = getgid();
1.8 nicm 297:
298: if (sflag) {
299: sh = getenv("SHELL");
300: if (sh == NULL || *sh == '\0')
301: shargv[0] = pw->pw_shell;
302: else
303: shargv[0] = sh;
304: argv = shargv;
305: argc = 1;
306: }
307:
308: cmd = argv[0];
309: if (strlcpy(cmdline, argv[0], sizeof(cmdline)) >= sizeof(cmdline))
310: errx(1, "command line too long");
311: for (i = 1; i < argc; i++) {
312: if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
313: errx(1, "command line too long");
314: if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
315: errx(1, "command line too long");
316: }
1.1 tedu 317:
318: if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
1.4 deraadt 319: syslog(LOG_AUTHPRIV | LOG_NOTICE,
320: "failed command for %s: %s", myname, cmdline);
1.1 tedu 321: fail();
322: }
323:
324: if (!(rule->options & NOPASS)) {
325: if (!auth_userokay(myname, NULL, NULL, NULL)) {
1.4 deraadt 326: syslog(LOG_AUTHPRIV | LOG_NOTICE,
327: "failed password for %s", myname);
1.1 tedu 328: fail();
329: }
330: }
331: envp = copyenv((const char **)envp, rule);
332:
333: pw = getpwuid(target);
334: if (!pw)
335: errx(1, "no passwd entry for target");
336: if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
337: LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
338: LOGIN_SETUSER) != 0)
339: errx(1, "failed to set user context for target");
340:
1.4 deraadt 341: syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s",
342: myname, pw->pw_name, cmdline);
1.7 doug 343: if (setenv("PATH", safepath, 1) == -1)
344: err(1, "failed to set PATH '%s'", safepath);
1.1 tedu 345: execvpe(cmd, argv, envp);
346: err(1, "%s", cmd);
347: }