]> git.armaanb.net Git - opendoas.git/blob - doas.c
doas grows up. no insults.
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.2 2015/07/16 21:00:59 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         fprintf(stderr, "Permission denied\n");
227         exit(1);
228 }
229
230 int
231 main(int argc, char **argv, char **envp)
232 {
233         char cmdline[1024];
234         char myname[32];
235         uid_t uid, target = 0;
236         gid_t groups[NGROUPS_MAX + 1];
237         int ngroups;
238         struct passwd *pw;
239         struct rule *rule;
240         const char *cmd;
241         int i, ch;
242         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
243
244         parseconfig("/etc/doas.conf");
245
246         while ((ch = getopt(argc, argv, "u:")) != -1) {
247                 switch (ch) {
248                 case 'u':
249                         if (parseuid(optarg, &target) != 0)
250                                 errx(1, "unknown user");
251                         break;
252                 default:
253                         usage();
254                         break;
255                 }
256         }
257         argv += optind;
258         argc -= optind;
259
260         if (!argc)
261                 usage();
262
263         cmd = argv[0];
264         strlcpy(cmdline, argv[0], sizeof(cmdline));
265         for (i = 1; i < argc; i++) {
266                 strlcat(cmdline, " ", sizeof(cmdline));
267                 strlcat(cmdline, argv[i], sizeof(cmdline));
268         }
269
270         uid = getuid();
271         pw = getpwuid(uid);
272         if (!pw)
273                 err(1, "getpwuid failed");
274         strlcpy(myname, pw->pw_name, sizeof(myname));
275         ngroups = getgroups(NGROUPS_MAX, groups);
276         if (ngroups == -1)
277                 err(1, "can't get groups");
278         groups[ngroups++] = getgid();
279
280         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
281                 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
282                 fail();
283         }
284
285         if (!(rule->options & NOPASS)) {
286                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
287                         syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
288                         fail();
289                 }
290         }
291         envp = copyenv((const char **)envp, rule);
292
293         pw = getpwuid(target);
294         if (!pw)
295                 errx(1, "no passwd entry for target");
296         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
297             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
298             LOGIN_SETUSER) != 0)
299                 errx(1, "failed to set user context for target");
300
301         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s", myname, pw->pw_name, cmdline);
302         setenv("PATH", safepath, 1);
303         execvpe(cmd, argv, envp);
304         err(1, "%s", cmd);
305 }