]> git.armaanb.net Git - opendoas.git/blob - doas.c
Missing reallocarray check in doas.c (ok tedu) and a calloc in parse.y
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.4 2015/07/16 21:57:54 deraadt 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 #include <sys/types.h>
18
19 #include <limits.h>
20 #include <login_cap.h>
21 #include <bsd_auth.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
31 #include "doas.h"
32
33 static void __dead
34 usage(void)
35 {
36         fprintf(stderr, "usage: doas [-u user] command [args]\n");
37         exit(1);
38 }
39
40 size_t
41 arraylen(const char **arr)
42 {
43         size_t cnt = 0;
44         while (*arr) {
45                 cnt++;
46                 arr++;
47         }
48         return cnt;
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 gid_t
80 strtogid(const char *s)
81 {
82         struct group *gr;
83         const char *errstr;
84         gid_t gid;
85
86         if ((gr = getgrnam(s)) != NULL)
87                 return gr->gr_gid;
88         gid = strtonum(s, 0, GID_MAX, &errstr);
89         if (errstr)
90                 return -1;
91         return gid;
92 }
93
94 static int
95 match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
96     struct rule *r)
97 {
98         int i;
99
100         if (r->ident[0] == ':') {
101                 gid_t rgid = strtogid(r->ident + 1);
102                 if (rgid == -1)
103                         return 0;
104                 for (i = 0; i < ngroups; i++) {
105                         if (rgid == groups[i])
106                                 break;
107                 }
108                 if (i == ngroups)
109                         return 0;
110         } else {
111                 if (uidcheck(r->ident, uid) != 0)
112                         return 0;
113         }
114         if (r->target && uidcheck(r->target, target) != 0)
115                 return 0;
116         if (r->cmd && strcmp(r->cmd, cmd) != 0)
117                 return 0;
118         return 1;
119 }
120
121 static int
122 permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
123     uid_t target, const char *cmd)
124 {
125         int i;
126
127         *lastr = NULL;
128         for (i = 0; i < nrules; i++) {
129                 if (match(uid, groups, ngroups, target, cmd, rules[i]))
130                         *lastr = rules[i];
131         }
132         if (!*lastr)
133                 return 0;
134         return (*lastr)->action == PERMIT;
135 }
136
137 static void
138 parseconfig(const char *filename)
139 {
140         extern FILE *yyfp;
141         extern int yyparse(void);
142
143         yyfp = fopen(filename, "r");
144         if (!yyfp) {
145                 fprintf(stderr, "doas is not enabled.\n");
146                 exit(1);
147         }
148         yyparse();
149         fclose(yyfp);
150 }
151
152 static int
153 copyenvhelper(const char **oldenvp, const char **safeset, int nsafe,
154     char **envp, int ei)
155 {
156         int i;
157         for (i = 0; i < nsafe; i++) {
158                 const char **oe = oldenvp;
159                 while (*oe) {
160                         size_t len = strlen(safeset[i]);
161                         if (strncmp(*oe, safeset[i], len) == 0 &&
162                             (*oe)[len] == '=') {
163                                 if (!(envp[ei++] = strdup(*oe)))
164                                         err(1, "strdup");
165                                 break;
166                         }
167                         oe++;
168                 }
169         }
170         return ei;
171 }
172
173 static char **
174 copyenv(const char **oldenvp, struct rule *rule)
175 {
176         const char *safeset[] = {
177                 "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
178                 "PATH", "TERM", "USER", "USERNAME",
179                 NULL,
180         };
181         int nsafe;
182         int nextras = 0;
183         char **envp;
184         const char **extra;
185         int ei;
186         int i, j;
187         
188         if ((rule->options & KEEPENV) && !rule->envlist) {
189                 j = arraylen(oldenvp);
190                 envp = reallocarray(NULL, j + 1, sizeof(char *));
191                 if (!envp)
192                         err(1, "reallocarray");
193                 for (i = 0; i < j; i++) {
194                         if (!(envp[i] = strdup(oldenvp[i])))
195                                 err(1, "strdup");
196                 }
197                 envp[i] = NULL;
198                 return envp;
199         }
200
201         nsafe = arraylen(safeset);
202         if ((extra = rule->envlist)) {
203                 nextras = arraylen(extra);
204                 for (i = 0; i < nsafe; i++) {
205                         for (j = 0; j < nextras; j++) {
206                                 if (strcmp(extra[j], safeset[i]) == 0) {
207                                         extra[j--] = extra[nextras--];
208                                         extra[nextras] = NULL;
209                                 }
210                         }
211                 }
212         }
213
214         envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
215         if (!envp)
216                 err(1, "can't allocate new environment");
217
218         ei = 0;
219         ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
220         ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
221         envp[ei] = NULL;
222
223         return envp;
224 }
225
226 static void __dead
227 fail(void)
228 {
229         fprintf(stderr, "Permission denied\n");
230         exit(1);
231 }
232
233 int
234 main(int argc, char **argv, char **envp)
235 {
236         char cmdline[1024];
237         char myname[32];
238         uid_t uid, target = 0;
239         gid_t groups[NGROUPS_MAX + 1];
240         int ngroups;
241         struct passwd *pw;
242         struct rule *rule;
243         const char *cmd;
244         int i, ch;
245         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
246             "/usr/local/bin:/usr/local/sbin";
247
248         parseconfig("/etc/doas.conf");
249
250         while ((ch = getopt(argc, argv, "u:")) != -1) {
251                 switch (ch) {
252                 case 'u':
253                         if (parseuid(optarg, &target) != 0)
254                                 errx(1, "unknown user");
255                         break;
256                 default:
257                         usage();
258                         break;
259                 }
260         }
261         argv += optind;
262         argc -= optind;
263
264         if (!argc)
265                 usage();
266
267         cmd = argv[0];
268         strlcpy(cmdline, argv[0], sizeof(cmdline));
269         for (i = 1; i < argc; i++) {
270                 strlcat(cmdline, " ", sizeof(cmdline));
271                 strlcat(cmdline, argv[i], sizeof(cmdline));
272         }
273
274         uid = getuid();
275         pw = getpwuid(uid);
276         if (!pw)
277                 err(1, "getpwuid failed");
278         strlcpy(myname, pw->pw_name, sizeof(myname));
279         ngroups = getgroups(NGROUPS_MAX, groups);
280         if (ngroups == -1)
281                 err(1, "can't get groups");
282         groups[ngroups++] = getgid();
283
284         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
285                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
286                     "failed command for %s: %s", myname, cmdline);
287                 fail();
288         }
289
290         if (!(rule->options & NOPASS)) {
291                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
292                         syslog(LOG_AUTHPRIV | LOG_NOTICE,
293                             "failed password for %s", myname);
294                         fail();
295                 }
296         }
297         envp = copyenv((const char **)envp, rule);
298
299         pw = getpwuid(target);
300         if (!pw)
301                 errx(1, "no passwd entry for target");
302         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
303             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
304             LOGIN_SETUSER) != 0)
305                 errx(1, "failed to set user context for target");
306
307         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s",
308             myname, pw->pw_name, cmdline);
309         setenv("PATH", safepath, 1);
310         execvpe(cmd, argv, envp);
311         err(1, "%s", cmd);
312 }