]> git.armaanb.net Git - opendoas.git/blob - doas.c
doas: remove v flag, not neccessary, upstream doesn't have it and __DATE__ is bad...
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.52 2016/04/28 04:48:56 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 #include <sys/ioctl.h>
21
22 #include <limits.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <err.h>
27 #include <unistd.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <syslog.h>
31 #include <errno.h>
32 #if HAVE_SHADOW_H
33 #include <shadow.h>
34 #endif
35
36 #include "includes.h"
37
38 #include "doas.h"
39
40 static void __dead
41 usage(void)
42 {
43         fprintf(stderr, "usage: doas [-Lns] "
44 #ifdef HAVE_BSD_AUTH_H
45             "[-a style] "
46 #endif
47             "[-C config] [-u user] command [args]\n");
48         exit(1);
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 int
80 parsegid(const char *s, gid_t *gid)
81 {
82         struct group *gr;
83         const char *errstr;
84
85         if ((gr = getgrnam(s)) != NULL) {
86                 *gid = gr->gr_gid;
87                 return 0;
88         }
89         *gid = strtonum(s, 0, GID_MAX, &errstr);
90         if (errstr)
91                 return -1;
92         return 0;
93 }
94
95 static int
96 match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
97     const char **cmdargs, struct rule *r)
98 {
99         int i;
100
101         if (r->ident[0] == ':') {
102                 gid_t rgid;
103                 if (parsegid(r->ident + 1, &rgid) == -1)
104                         return 0;
105                 for (i = 0; i < ngroups; i++) {
106                         if (rgid == groups[i])
107                                 break;
108                 }
109                 if (i == ngroups)
110                         return 0;
111         } else {
112                 if (uidcheck(r->ident, uid) != 0)
113                         return 0;
114         }
115         if (r->target && uidcheck(r->target, target) != 0)
116                 return 0;
117         if (r->cmd) {
118                 if (strcmp(r->cmd, cmd))
119                         return 0;
120                 if (r->cmdargs) {
121                         /* if arguments were given, they should match explicitly */
122                         for (i = 0; r->cmdargs[i]; i++) {
123                                 if (!cmdargs[i])
124                                         return 0;
125                                 if (strcmp(r->cmdargs[i], cmdargs[i]))
126                                         return 0;
127                         }
128                         if (cmdargs[i])
129                                 return 0;
130                 }
131         }
132         return 1;
133 }
134
135 static int
136 permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr,
137     uid_t target, const char *cmd, const char **cmdargs)
138 {
139         int i;
140
141         *lastr = NULL;
142         for (i = 0; i < nrules; i++) {
143                 if (match(uid, groups, ngroups, target, cmd,
144                     cmdargs, rules[i]))
145                         *lastr = rules[i];
146         }
147         if (!*lastr)
148                 return 0;
149         return (*lastr)->action == PERMIT;
150 }
151
152 static void
153 parseconfig(const char *filename, int checkperms)
154 {
155         extern FILE *yyfp;
156         extern int yyparse(void);
157         struct stat sb;
158
159         yyfp = fopen(filename, "r");
160         if (!yyfp)
161                 err(1, checkperms ? "doas is not enabled, %s" :
162                     "could not open config file %s", filename);
163
164         if (checkperms) {
165                 if (fstat(fileno(yyfp), &sb) != 0)
166                         err(1, "fstat(\"%s\")", filename);
167                 if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
168                         errx(1, "%s is writable by group or other", filename);
169                 if (sb.st_uid != 0)
170                         errx(1, "%s is not owned by root", filename);
171         }
172
173         yyparse();
174         fclose(yyfp);
175         if (parse_errors)
176                 exit(1);
177 }
178
179 static void __dead
180 checkconfig(const char *confpath, int argc, char **argv,
181     uid_t uid, gid_t *groups, int ngroups, uid_t target)
182 {
183         const struct rule *rule;
184
185         if (setresuid(uid, uid, uid) != 0)
186                 err(1, "setresuid");
187
188         parseconfig(confpath, 0);
189         if (!argc)
190                 exit(0);
191
192         if (permit(uid, groups, ngroups, &rule, target, argv[0],
193             (const char **)argv + 1)) {
194                 printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
195                 exit(0);
196         } else {
197                 printf("deny\n");
198                 exit(1);
199         }
200 }
201
202 #ifdef HAVE_BSD_AUTH_H
203 static void
204 authuser(char *myname, char *login_style, int persist)
205 {
206         char *challenge = NULL, *response, rbuf[1024], cbuf[128];
207         auth_session_t *as;
208         int fd = -1;
209
210         if (persist)
211                 fd = open("/dev/tty", O_RDWR);
212         if (fd != -1) {
213                 if (ioctl(fd, TIOCCHKVERAUTH) == 0)
214                         goto good;
215         }
216
217         if (!(as = auth_userchallenge(myname, login_style, "auth-doas",
218             &challenge)))
219                 errx(1, "Authorization failed");
220         if (!challenge) {
221                 char host[HOST_NAME_MAX + 1];
222                 if (gethostname(host, sizeof(host)))
223                         snprintf(host, sizeof(host), "?");
224                 snprintf(cbuf, sizeof(cbuf),
225                     "\rdoas (%.32s@%.32s) password: ", myname, host);
226                 challenge = cbuf;
227         }
228         response = readpassphrase(challenge, rbuf, sizeof(rbuf),
229             RPP_REQUIRE_TTY);
230         if (response == NULL && errno == ENOTTY) {
231                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
232                     "tty required for %s", myname);
233                 errx(1, "a tty is required");
234         }
235         if (!auth_userresponse(as, response, 0)) {
236                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
237                     "failed auth for %s", myname);
238                 errx(1, "Authorization failed");
239         }
240         explicit_bzero(rbuf, sizeof(rbuf));
241 good:
242         if (fd != -1) {
243                 int secs = 5 * 60;
244                 ioctl(fd, TIOCSETVERAUTH, &secs);
245                 close(fd);
246         }
247 }
248 #elif HAVE_SHADOW_H
249 static void
250 authuser(const char *myname, const char *login_style, int persist)
251 {
252         const char *hash;
253         char *encrypted;
254         struct passwd *pw;
255
256         (void)login_style;
257
258 #ifdef PERSIST_TIMESTAMP
259         int fd = -1;
260         int valid;
261         if (persist)
262                 fd = persist_open(&valid, 5 * 60);
263         if (fd != -1 && valid)
264                 goto good;
265 #else
266         (void)persist;
267 #endif
268
269         if (!(pw = getpwnam(myname)))
270                 err(1, "getpwnam");
271
272         hash = pw->pw_passwd;
273         if (hash[0] == 'x' && hash[1] == '\0') {
274                 struct spwd *sp;
275                 if (!(sp = getspnam(myname)))
276                         errx(1, "Authorization failed");
277                 hash = sp->sp_pwdp;
278         } else if (hash[0] != '*') {
279                 errx(1, "Authorization failed");
280         }
281
282         char *challenge, *response, rbuf[1024], cbuf[128], host[HOST_NAME_MAX + 1];
283         if (gethostname(host, sizeof(host)))
284                 snprintf(host, sizeof(host), "?");
285         snprintf(cbuf, sizeof(cbuf),
286                         "\rdoas (%.32s@%.32s) password: ", myname, host);
287         challenge = cbuf;
288
289         response = readpassphrase(challenge, rbuf, sizeof(rbuf), RPP_REQUIRE_TTY);
290         if (response == NULL && errno == ENOTTY) {
291                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
292                         "tty required for %s", myname);
293                 errx(1, "a tty is required");
294         }
295         if (!(encrypted = crypt(response, hash)))
296                 errx(1, "crypt");
297         if (strcmp(encrypted, hash) != 0) {
298                 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname);
299                 errx(1, "Authorization failed");
300         }
301         explicit_bzero(rbuf, sizeof(rbuf));
302 #ifdef PERSIST_TIMESTAMP
303 good:
304         if (fd != -1) {
305                 persist_set(fd, 5 * 60);
306                 close(fd);
307         }
308 #endif
309 }
310 #endif /* HAVE_BSD_AUTH_H */
311
312 int
313 main(int argc, char **argv)
314 {
315         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
316             "/usr/local/bin:/usr/local/sbin";
317         const char *confpath = NULL;
318         char *shargv[] = { NULL, NULL };
319         char *sh;
320         const char *cmd;
321         char cmdline[LINE_MAX];
322         char myname[_PW_NAME_LEN + 1];
323         struct passwd *pw;
324         const struct rule *rule;
325         uid_t uid;
326         uid_t target = 0;
327         gid_t groups[NGROUPS_MAX + 1];
328         int ngroups;
329         int i, ch;
330         int sflag = 0;
331         int nflag = 0;
332         char cwdpath[PATH_MAX];
333         const char *cwd;
334         char **envp;
335 #ifdef HAVE_BSD_AUTH_H
336         char *login_style = NULL;
337 #endif
338
339         setprogname("doas");
340
341         closefrom(STDERR_FILENO + 1);
342
343         uid = getuid();
344
345 #ifdef HAVE_BSD_AUTH_H
346 # define OPTSTRING "a:C:Lnsu:v"
347 #else
348 # define OPTSTRING "+C:Lnsu:v"
349 #endif
350
351         while ((ch = getopt(argc, argv, OPTSTRING)) != -1) {
352                 switch (ch) {
353 #ifdef HAVE_BSD_AUTH_H
354                 case 'a':
355                         login_style = optarg;
356                         break;
357 #endif
358                 case 'C':
359                         confpath = optarg;
360                         break;
361                 case 'L':
362 #ifdef TIOCCLRVERAUTH
363                         i = open("/dev/tty", O_RDWR);
364                         if (i != -1)
365                                 ioctl(i, TIOCCLRVERAUTH);
366                         exit(i == -1);
367 #elif PERSIST_TIMESTAMP
368                         exit(persist_clear() != 0);
369 #endif
370                 case 'u':
371                         if (parseuid(optarg, &target) != 0)
372                                 errx(1, "unknown user");
373                         break;
374                 case 'n':
375                         nflag = 1;
376                         break;
377                 case 's':
378                         sflag = 1;
379                         break;
380                 default:
381                         usage();
382                         break;
383                 }
384         }
385         argv += optind;
386         argc -= optind;
387
388         if (confpath) {
389                 if (sflag)
390                         usage();
391         } else if ((!sflag && !argc) || (sflag && argc))
392                 usage();
393
394         pw = getpwuid(uid);
395         if (!pw)
396                 err(1, "getpwuid failed");
397         if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
398                 errx(1, "pw_name too long");
399         ngroups = getgroups(NGROUPS_MAX, groups);
400         if (ngroups == -1)
401                 err(1, "can't get groups");
402         groups[ngroups++] = getgid();
403
404         if (sflag) {
405                 sh = getenv("SHELL");
406                 if (sh == NULL || *sh == '\0') {
407                         shargv[0] = strdup(pw->pw_shell);
408                         if (shargv[0] == NULL)
409                                 err(1, NULL);
410                 } else
411                         shargv[0] = sh;
412                 argv = shargv;
413                 argc = 1;
414         }
415
416         if (confpath) {
417                 checkconfig(confpath, argc, argv, uid, groups, ngroups,
418                     target);
419                 exit(1);        /* fail safe */
420         }
421
422         if (geteuid())
423                 errx(1, "not installed setuid");
424
425         parseconfig("/etc/doas.conf", 1);
426
427         /* cmdline is used only for logging, no need to abort on truncate */
428         (void)strlcpy(cmdline, argv[0], sizeof(cmdline));
429         for (i = 1; i < argc; i++) {
430                 if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
431                         break;
432                 if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
433                         break;
434         }
435
436         cmd = argv[0];
437         if (!permit(uid, groups, ngroups, &rule, target, cmd,
438             (const char **)argv + 1)) {
439                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
440                     "failed command for %s: %s", myname, cmdline);
441                 errc(1, EPERM, NULL);
442         }
443
444 #if defined(HAVE_BSD_AUTH_H) || defined(HAVE_SHADOW_H)
445         if (!(rule->options & NOPASS)) {
446                 if (nflag)
447                         errx(1, "Authorization required");
448
449                 authuser(myname, login_style, rule->options & PERSIST);
450         }
451 #elif HAVE_PAM_APPL_H
452         pw = getpwuid(target);
453         if (!pw)
454                 errx(1, "no passwd entry for target");
455
456         if (!pamauth(pw->pw_name, myname, !nflag, rule->options & NOPASS)) {
457                 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname);
458                 errx(1, "Authorization failed");
459         }
460 #else
461 #error "No authentication method"
462 #endif /* HAVE_BSD_AUTH_H */
463
464         if (pledge("stdio rpath getpw exec id", NULL) == -1)
465                 err(1, "pledge");
466
467         pw = getpwuid(target);
468         if (!pw)
469                 errx(1, "no passwd entry for target");
470
471 #ifdef HAVE_BSD_AUTH_H
472         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
473             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
474             LOGIN_SETUSER) != 0)
475                 errx(1, "failed to set user context for target");
476 #else
477         if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0)
478                 errx(1, "setresgid");
479         if (initgroups(pw->pw_name, pw->pw_gid) != 0)
480                 errx(1, "initgroups");
481         if (setresuid(target, target, target) != 0)
482                 errx(1, "setresuid");
483 #endif
484
485         if (pledge("stdio rpath exec", NULL) == -1)
486                 err(1, "pledge");
487
488         if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
489                 cwd = "(failed)";
490         else
491                 cwd = cwdpath;
492
493         if (pledge("stdio exec", NULL) == -1)
494                 err(1, "pledge");
495
496         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command %s as %s from %s",
497             myname, cmdline, pw->pw_name, cwd);
498
499         envp = prepenv(rule);
500
501         if (rule->cmd) {
502                 if (setenv("PATH", safepath, 1) == -1)
503                         err(1, "failed to set PATH '%s'", safepath);
504         }
505         execvpe(cmd, argv, envp);
506         if (errno == ENOENT)
507                 errx(1, "%s: command not found", cmd);
508         err(1, "%s", cmd);
509 }