]> git.armaanb.net Git - opendoas.git/blob - doas.c
rearrange variable decls a little
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.8 2015/07/18 06:33:23 nicm 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
33 #include "doas.h"
34
35 static void __dead
36 usage(void)
37 {
38         fprintf(stderr, "usage: doas [-s] [-u user] command [args]\n");
39         exit(1);
40 }
41
42 size_t
43 arraylen(const char **arr)
44 {
45         size_t cnt = 0;
46
47         while (*arr) {
48                 cnt++;
49                 arr++;
50         }
51         return cnt;
52 }
53
54 static int
55 parseuid(const char *s, uid_t *uid)
56 {
57         struct passwd *pw;
58         const char *errstr;
59
60         if ((pw = getpwnam(s)) != NULL) {
61                 *uid = pw->pw_uid;
62                 return 0;
63         }
64         *uid = strtonum(s, 0, UID_MAX, &errstr);
65         if (errstr)
66                 return -1;
67         return 0;
68 }
69
70 static int
71 uidcheck(const char *s, uid_t desired)
72 {
73         uid_t uid;
74
75         if (parseuid(s, &uid) != 0)
76                 return -1;
77         if (uid != desired)
78                 return -1;
79         return 0;
80 }
81
82 static gid_t
83 strtogid(const char *s)
84 {
85         struct group *gr;
86         const char *errstr;
87         gid_t gid;
88
89         if ((gr = getgrnam(s)) != NULL)
90                 return gr->gr_gid;
91         gid = strtonum(s, 0, GID_MAX, &errstr);
92         if (errstr)
93                 return -1;
94         return gid;
95 }
96
97 static int
98 match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
99     struct rule *r)
100 {
101         int i;
102
103         if (r->ident[0] == ':') {
104                 gid_t rgid = strtogid(r->ident + 1);
105                 if (rgid == -1)
106                         return 0;
107                 for (i = 0; i < ngroups; i++) {
108                         if (rgid == groups[i])
109                                 break;
110                 }
111                 if (i == ngroups)
112                         return 0;
113         } else {
114                 if (uidcheck(r->ident, uid) != 0)
115                         return 0;
116         }
117         if (r->target && uidcheck(r->target, target) != 0)
118                 return 0;
119         if (r->cmd && strcmp(r->cmd, cmd) != 0)
120                 return 0;
121         return 1;
122 }
123
124 static int
125 permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
126     uid_t target, const char *cmd)
127 {
128         int i;
129
130         *lastr = NULL;
131         for (i = 0; i < nrules; i++) {
132                 if (match(uid, groups, ngroups, target, cmd, rules[i]))
133                         *lastr = rules[i];
134         }
135         if (!*lastr)
136                 return 0;
137         return (*lastr)->action == PERMIT;
138 }
139
140 static void
141 parseconfig(const char *filename)
142 {
143         extern FILE *yyfp;
144         extern int yyparse(void);
145         struct stat sb;
146
147         yyfp = fopen(filename, "r");
148         if (!yyfp) {
149                 fprintf(stderr, "doas is not enabled.\n");
150                 exit(1);
151         }
152
153         if (fstat(fileno(yyfp), &sb) != 0)
154                 err(1, "fstat(\"%s\")", filename);
155         if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
156                 errx(1, "%s is writable by group or other", filename);
157         if (sb.st_uid != 0)
158                 errx(1, "%s is not owned by root", filename);
159
160         yyparse();
161         fclose(yyfp);
162 }
163
164 static int
165 copyenvhelper(const char **oldenvp, const char **safeset, int nsafe,
166     char **envp, int ei)
167 {
168         int i;
169
170         for (i = 0; i < nsafe; i++) {
171                 const char **oe = oldenvp;
172                 while (*oe) {
173                         size_t len = strlen(safeset[i]);
174                         if (strncmp(*oe, safeset[i], len) == 0 &&
175                             (*oe)[len] == '=') {
176                                 if (!(envp[ei++] = strdup(*oe)))
177                                         err(1, "strdup");
178                                 break;
179                         }
180                         oe++;
181                 }
182         }
183         return ei;
184 }
185
186 static char **
187 copyenv(const char **oldenvp, struct rule *rule)
188 {
189         const char *safeset[] = {
190                 "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
191                 "PATH", "TERM", "USER", "USERNAME",
192                 NULL,
193         };
194         char **envp;
195         const char **extra;
196         int ei;
197         int i, j;
198         int nsafe;
199         int nextras = 0;
200         
201         if ((rule->options & KEEPENV) && !rule->envlist) {
202                 j = arraylen(oldenvp);
203                 envp = reallocarray(NULL, j + 1, sizeof(char *));
204                 if (!envp)
205                         err(1, "reallocarray");
206                 for (i = 0; i < j; i++) {
207                         if (!(envp[i] = strdup(oldenvp[i])))
208                                 err(1, "strdup");
209                 }
210                 envp[i] = NULL;
211                 return envp;
212         }
213
214         nsafe = arraylen(safeset);
215         if ((extra = rule->envlist)) {
216                 nextras = arraylen(extra);
217                 for (i = 0; i < nsafe; i++) {
218                         for (j = 0; j < nextras; j++) {
219                                 if (strcmp(extra[j], safeset[i]) == 0) {
220                                         extra[j--] = extra[nextras--];
221                                         extra[nextras] = NULL;
222                                 }
223                         }
224                 }
225         }
226
227         envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
228         if (!envp)
229                 err(1, "can't allocate new environment");
230
231         ei = 0;
232         ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
233         ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
234         envp[ei] = NULL;
235
236         return envp;
237 }
238
239 static void __dead
240 fail(void)
241 {
242         fprintf(stderr, "Permission denied\n");
243         exit(1);
244 }
245
246 int
247 main(int argc, char **argv, char **envp)
248 {
249         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
250             "/usr/local/bin:/usr/local/sbin";
251         char *shargv[] = { NULL, NULL };
252         char *sh;
253         const char *cmd;
254         char cmdline[LINE_MAX];
255         char myname[_PW_NAME_LEN + 1];
256         struct passwd *pw;
257         struct rule *rule;
258         uid_t uid;
259         uid_t target = 0;
260         gid_t groups[NGROUPS_MAX + 1];
261         int ngroups;
262         int i, ch;
263         int sflag = 0;
264
265         parseconfig("/etc/doas.conf");
266
267         while ((ch = getopt(argc, argv, "su:")) != -1) {
268                 switch (ch) {
269                 case 'u':
270                         if (parseuid(optarg, &target) != 0)
271                                 errx(1, "unknown user");
272                         break;
273                 case 's':
274                         sflag = 1;
275                         break;
276                 default:
277                         usage();
278                         break;
279                 }
280         }
281         argv += optind;
282         argc -= optind;
283
284         if ((!sflag && !argc) || (sflag && argc))
285                 usage();
286
287         uid = getuid();
288         pw = getpwuid(uid);
289         if (!pw)
290                 err(1, "getpwuid failed");
291         if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname))
292                 errx(1, "pw_name too long");
293         ngroups = getgroups(NGROUPS_MAX, groups);
294         if (ngroups == -1)
295                 err(1, "can't get groups");
296         groups[ngroups++] = getgid();
297
298         if (sflag) {
299                 sh = getenv("SHELL");
300                 if (sh == NULL || *sh == '\0')
301                         shargv[0] = pw->pw_shell;
302                 else
303                         shargv[0] = sh;
304                 argv = shargv;
305                 argc = 1;
306         }
307
308         cmd = argv[0];
309         if (strlcpy(cmdline, argv[0], sizeof(cmdline)) >= sizeof(cmdline))
310                 errx(1, "command line too long");
311         for (i = 1; i < argc; i++) {
312                 if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
313                         errx(1, "command line too long");
314                 if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
315                         errx(1, "command line too long");
316         }
317
318         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
319                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
320                     "failed command for %s: %s", myname, cmdline);
321                 fail();
322         }
323
324         if (!(rule->options & NOPASS)) {
325                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
326                         syslog(LOG_AUTHPRIV | LOG_NOTICE,
327                             "failed password for %s", myname);
328                         fail();
329                 }
330         }
331         envp = copyenv((const char **)envp, rule);
332
333         pw = getpwuid(target);
334         if (!pw)
335                 errx(1, "no passwd entry for target");
336         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
337             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
338             LOGIN_SETUSER) != 0)
339                 errx(1, "failed to set user context for target");
340
341         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s",
342             myname, pw->pw_name, cmdline);
343         if (setenv("PATH", safepath, 1) == -1)
344                 err(1, "failed to set PATH '%s'", safepath);
345         execvpe(cmd, argv, envp);
346         err(1, "%s", cmd);
347 }