Annotation of src/usr.bin/doas/doas.c, Revision 1.4
1.4 ! deraadt 1: /* $OpenBSD: doas.c,v 1.3 2015/07/16 21:55:03 tedu 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: */
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
1.4 ! deraadt 153: copyenvhelper(const char **oldenvp, const char **safeset, int nsafe,
! 154: char **envp, int ei)
1.1 tedu 155: {
156: int i;
157: for (i = 0; i < nsafe; i++) {
158: const char **oe = oldenvp;
159: while (*oe) {
160: size_t len = strlen(safeset[i]);
161: if (strncmp(*oe, safeset[i], len) == 0 &&
162: (*oe)[len] == '=') {
163: if (!(envp[ei++] = strdup(*oe)))
164: err(1, "strdup");
165: break;
166: }
167: oe++;
168: }
169: }
170: return ei;
171: }
172:
173: static char **
174: copyenv(const char **oldenvp, struct rule *rule)
175: {
176: const char *safeset[] = {
177: "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
178: "PATH", "TERM", "USER", "USERNAME",
179: NULL,
180: };
181: int nsafe;
182: int nextras = 0;
183: char **envp;
184: const char **extra;
185: int ei;
186: int i, j;
187:
188: if ((rule->options & KEEPENV) && !rule->envlist) {
189: j = arraylen(oldenvp);
190: envp = reallocarray(NULL, j + 1, sizeof(char *));
191: for (i = 0; i < j; i++) {
192: if (!(envp[i] = strdup(oldenvp[i])))
193: err(1, "strdup");
194: }
195: envp[i] = NULL;
196: return envp;
197: }
198:
199: nsafe = arraylen(safeset);
200: if ((extra = rule->envlist)) {
201: nextras = arraylen(extra);
202: for (i = 0; i < nsafe; i++) {
203: for (j = 0; j < nextras; j++) {
204: if (strcmp(extra[j], safeset[i]) == 0) {
205: extra[j--] = extra[nextras--];
206: extra[nextras] = NULL;
207: }
208: }
209: }
210: }
211:
212: envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
213: if (!envp)
214: err(1, "can't allocate new environment");
215:
216: ei = 0;
217: ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
218: ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
219: envp[ei] = NULL;
220:
221: return envp;
222: }
223:
224: static void __dead
225: fail(void)
226: {
1.3 tedu 227: fprintf(stderr, "Permission denied\n");
1.1 tedu 228: exit(1);
229: }
230:
231: int
232: main(int argc, char **argv, char **envp)
233: {
234: char cmdline[1024];
235: char myname[32];
236: uid_t uid, target = 0;
237: gid_t groups[NGROUPS_MAX + 1];
238: int ngroups;
239: struct passwd *pw;
240: struct rule *rule;
241: const char *cmd;
242: int i, ch;
1.4 ! deraadt 243: const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
! 244: "/usr/local/bin:/usr/local/sbin";
1.1 tedu 245:
246: parseconfig("/etc/doas.conf");
247:
248: while ((ch = getopt(argc, argv, "u:")) != -1) {
249: switch (ch) {
250: case 'u':
251: if (parseuid(optarg, &target) != 0)
252: errx(1, "unknown user");
253: break;
254: default:
255: usage();
256: break;
257: }
258: }
259: argv += optind;
260: argc -= optind;
261:
262: if (!argc)
263: usage();
264:
265: cmd = argv[0];
266: strlcpy(cmdline, argv[0], sizeof(cmdline));
267: for (i = 1; i < argc; i++) {
268: strlcat(cmdline, " ", sizeof(cmdline));
269: strlcat(cmdline, argv[i], sizeof(cmdline));
270: }
271:
272: uid = getuid();
273: pw = getpwuid(uid);
274: if (!pw)
275: err(1, "getpwuid failed");
276: strlcpy(myname, pw->pw_name, sizeof(myname));
277: ngroups = getgroups(NGROUPS_MAX, groups);
278: if (ngroups == -1)
279: err(1, "can't get groups");
280: groups[ngroups++] = getgid();
281:
282: if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
1.4 ! deraadt 283: syslog(LOG_AUTHPRIV | LOG_NOTICE,
! 284: "failed command for %s: %s", myname, cmdline);
1.1 tedu 285: fail();
286: }
287:
288: if (!(rule->options & NOPASS)) {
289: if (!auth_userokay(myname, NULL, NULL, NULL)) {
1.4 ! deraadt 290: syslog(LOG_AUTHPRIV | LOG_NOTICE,
! 291: "failed password for %s", myname);
1.1 tedu 292: fail();
293: }
294: }
295: envp = copyenv((const char **)envp, rule);
296:
297: pw = getpwuid(target);
298: if (!pw)
299: errx(1, "no passwd entry for target");
300: if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
301: LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
302: LOGIN_SETUSER) != 0)
303: errx(1, "failed to set user context for target");
304:
1.4 ! deraadt 305: syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s",
! 306: myname, pw->pw_name, cmdline);
1.1 tedu 307: setenv("PATH", safepath, 1);
308: execvpe(cmd, argv, envp);
309: err(1, "%s", cmd);
310: }