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