]> git.armaanb.net Git - opendoas.git/blob - doas.c
wrap long lines
[opendoas.git] / doas.c
1 /* $OpenBSD: doas.c,v 1.3 2015/07/16 21:55:03 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,
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                 for (i = 0; i < j; i++) {
192                         if (!(envp[i] = strdup(oldenvp[i])))
193                                 err(1, "strdup");
194                 }
195                 envp[i] = NULL;
196                 return envp;
197         }
198
199         nsafe = arraylen(safeset);
200         if ((extra = rule->envlist)) {
201                 nextras = arraylen(extra);
202                 for (i = 0; i < nsafe; i++) {
203                         for (j = 0; j < nextras; j++) {
204                                 if (strcmp(extra[j], safeset[i]) == 0) {
205                                         extra[j--] = extra[nextras--];
206                                         extra[nextras] = NULL;
207                                 }
208                         }
209                 }
210         }
211
212         envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
213         if (!envp)
214                 err(1, "can't allocate new environment");
215
216         ei = 0;
217         ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
218         ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
219         envp[ei] = NULL;
220
221         return envp;
222 }
223
224 static void __dead
225 fail(void)
226 {
227         fprintf(stderr, "Permission denied\n");
228         exit(1);
229 }
230
231 int
232 main(int argc, char **argv, char **envp)
233 {
234         char cmdline[1024];
235         char myname[32];
236         uid_t uid, target = 0;
237         gid_t groups[NGROUPS_MAX + 1];
238         int ngroups;
239         struct passwd *pw;
240         struct rule *rule;
241         const char *cmd;
242         int i, ch;
243         const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
244             "/usr/local/bin:/usr/local/sbin";
245
246         parseconfig("/etc/doas.conf");
247
248         while ((ch = getopt(argc, argv, "u:")) != -1) {
249                 switch (ch) {
250                 case 'u':
251                         if (parseuid(optarg, &target) != 0)
252                                 errx(1, "unknown user");
253                         break;
254                 default:
255                         usage();
256                         break;
257                 }
258         }
259         argv += optind;
260         argc -= optind;
261
262         if (!argc)
263                 usage();
264
265         cmd = argv[0];
266         strlcpy(cmdline, argv[0], sizeof(cmdline));
267         for (i = 1; i < argc; i++) {
268                 strlcat(cmdline, " ", sizeof(cmdline));
269                 strlcat(cmdline, argv[i], sizeof(cmdline));
270         }
271
272         uid = getuid();
273         pw = getpwuid(uid);
274         if (!pw)
275                 err(1, "getpwuid failed");
276         strlcpy(myname, pw->pw_name, sizeof(myname));
277         ngroups = getgroups(NGROUPS_MAX, groups);
278         if (ngroups == -1)
279                 err(1, "can't get groups");
280         groups[ngroups++] = getgid();
281
282         if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
283                 syslog(LOG_AUTHPRIV | LOG_NOTICE,
284                     "failed command for %s: %s", myname, cmdline);
285                 fail();
286         }
287
288         if (!(rule->options & NOPASS)) {
289                 if (!auth_userokay(myname, NULL, NULL, NULL)) {
290                         syslog(LOG_AUTHPRIV | LOG_NOTICE,
291                             "failed password for %s", myname);
292                         fail();
293                 }
294         }
295         envp = copyenv((const char **)envp, rule);
296
297         pw = getpwuid(target);
298         if (!pw)
299                 errx(1, "no passwd entry for target");
300         if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
301             LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
302             LOGIN_SETUSER) != 0)
303                 errx(1, "failed to set user context for target");
304
305         syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s",
306             myname, pw->pw_name, cmdline);
307         setenv("PATH", safepath, 1);
308         execvpe(cmd, argv, envp);
309         err(1, "%s", cmd);
310 }