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