]> git.armaanb.net Git - opendoas.git/blob - doas.c
import doas. still subject to changes, large and small.
[opendoas.git] / doas.c
1 /* $OpenBSD$ */
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, char **envp, int ei)
154 {
155         int i;
156         for (i = 0; i < nsafe; i++) {
157                 const char **oe = oldenvp;
158                 while (*oe) {
159                         size_t len = strlen(safeset[i]);
160                         if (strncmp(*oe, safeset[i], len) == 0 &&
161                             (*oe)[len] == '=') {
162                                 if (!(envp[ei++] = strdup(*oe)))
163                                         err(1, "strdup");
164                                 break;
165                         }
166                         oe++;
167                 }
168         }
169         return ei;
170 }
171
172 static char **
173 copyenv(const char **oldenvp, struct rule *rule)
174 {
175         const char *safeset[] = {
176                 "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
177                 "PATH", "TERM", "USER", "USERNAME",
178                 NULL,
179         };
180         int nsafe;
181         int nextras = 0;
182         char **envp;
183         const char **extra;
184         int ei;
185         int i, j;
186         
187         if ((rule->options & KEEPENV) && !rule->envlist) {
188                 j = arraylen(oldenvp);
189                 envp = reallocarray(NULL, j + 1, sizeof(char *));
190                 for (i = 0; i < j; i++) {
191                         if (!(envp[i] = strdup(oldenvp[i])))
192                                 err(1, "strdup");
193                 }
194                 envp[i] = NULL;
195                 return envp;
196         }
197
198         nsafe = arraylen(safeset);
199         if ((extra = rule->envlist)) {
200                 nextras = arraylen(extra);
201                 for (i = 0; i < nsafe; i++) {
202                         for (j = 0; j < nextras; j++) {
203                                 if (strcmp(extra[j], safeset[i]) == 0) {
204                                         extra[j--] = extra[nextras--];
205                                         extra[nextras] = NULL;
206                                 }
207                         }
208                 }
209         }
210
211         envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
212         if (!envp)
213                 err(1, "can't allocate new environment");
214
215         ei = 0;
216         ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
217         ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
218         envp[ei] = NULL;
219
220         return envp;
221 }
222
223 static void __dead
224 fail(void)
225 {
226         const char *msgs[] = {
227                 "No lollygagging!",
228                 "Better luck next time.",
229                 "PEBKAC detected.",
230                 "That's what happens when you're lazy.",
231                 "It is clear that this has not been thought through.",
232                 "That's the most ridiculous thing I've heard in the last two or three minutes!",
233                 "No sane people allowed here.  Go home.",
234                 "I would explain, but I am too drunk.",
235                 "You're not allowed to have an opinion.",
236                 "Complaint forms are handled in another department.",
237         };
238         const char *m;
239
240         m = msgs[arc4random_uniform(sizeof(msgs) / sizeof(msgs[0]))];
241         fprintf(stderr, m);
242         fprintf(stderr, "\n");
243         exit(1);
244 }
245
246 int
247 main(int argc, char **argv, char **envp)
248 {
249         char cmdline[1024];
250         char myname[32];
251         uid_t uid, target = 0;
252         gid_t groups[NGROUPS_MAX + 1];
253         int ngroups;
254         struct passwd *pw;
255         struct rule *rule;
256         const char *cmd;
257         int i, ch;
258         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
259
260         parseconfig("/etc/doas.conf");
261
262         while ((ch = getopt(argc, argv, "u:")) != -1) {
263                 switch (ch) {
264                 case 'u':
265                         if (parseuid(optarg, &target) != 0)
266                                 errx(1, "unknown user");
267                         break;
268                 default:
269                         usage();
270                         break;
271                 }
272         }
273         argv += optind;
274         argc -= optind;
275
276         if (!argc)
277                 usage();
278
279         cmd = argv[0];
280         strlcpy(cmdline, argv[0], sizeof(cmdline));
281         for (i = 1; i < argc; i++) {
282                 strlcat(cmdline, " ", sizeof(cmdline));
283                 strlcat(cmdline, argv[i], sizeof(cmdline));
284         }
285
286         uid = getuid();
287         pw = getpwuid(uid);
288         if (!pw)
289                 err(1, "getpwuid failed");
290         strlcpy(myname, pw->pw_name, sizeof(myname));
291         ngroups = getgroups(NGROUPS_MAX, groups);
292         if (ngroups == -1)
293                 err(1, "can't get groups");
294         groups[ngroups++] = getgid();
295
296         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
297                 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
298                 fail();
299         }
300
301         if (!(rule->options & NOPASS)) {
302                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
303                         syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
304                         fail();
305                 }
306         }
307         envp = copyenv((const char **)envp, rule);
308
309         pw = getpwuid(target);
310         if (!pw)
311                 errx(1, "no passwd entry for target");
312         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
313             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
314             LOGIN_SETUSER) != 0)
315                 errx(1, "failed to set user context for target");
316
317         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s", myname, pw->pw_name, cmdline);
318         setenv("PATH", safepath, 1);
319         execvpe(cmd, argv, envp);
320         err(1, "%s", cmd);
321 }