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