]> git.armaanb.net Git - opendoas.git/blob - doas.c
combine fprintfs and use a constant format string. hint from reyk
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.1 2015/07/16 20:44:21 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 #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, "%s\n", m);
242         exit(1);
243 }
244
245 int
246 main(int argc, char **argv, char **envp)
247 {
248         char cmdline[1024];
249         char myname[32];
250         uid_t uid, target = 0;
251         gid_t groups[NGROUPS_MAX + 1];
252         int ngroups;
253         struct passwd *pw;
254         struct rule *rule;
255         const char *cmd;
256         int i, ch;
257         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/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         strlcpy(cmdline, argv[0], sizeof(cmdline));
280         for (i = 1; i < argc; i++) {
281                 strlcat(cmdline, " ", sizeof(cmdline));
282                 strlcat(cmdline, argv[i], sizeof(cmdline));
283         }
284
285         uid = getuid();
286         pw = getpwuid(uid);
287         if (!pw)
288                 err(1, "getpwuid failed");
289         strlcpy(myname, pw->pw_name, sizeof(myname));
290         ngroups = getgroups(NGROUPS_MAX, groups);
291         if (ngroups == -1)
292                 err(1, "can't get groups");
293         groups[ngroups++] = getgid();
294
295         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
296                 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
297                 fail();
298         }
299
300         if (!(rule->options & NOPASS)) {
301                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
302                         syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
303                         fail();
304                 }
305         }
306         envp = copyenv((const char **)envp, rule);
307
308         pw = getpwuid(target);
309         if (!pw)
310                 errx(1, "no passwd entry for target");
311         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
312             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
313             LOGIN_SETUSER) != 0)
314                 errx(1, "failed to set user context for target");
315
316         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s", myname, pw->pw_name, cmdline);
317         setenv("PATH", safepath, 1);
318         execvpe(cmd, argv, envp);
319         err(1, "%s", cmd);
320 }