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