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