3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
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.
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.
17 #include <sys/types.h>
20 #include <login_cap.h>
36 fprintf(stderr, "usage: doas [-u user] command [args]\n");
41 arraylen(const char **arr)
52 parseuid(const char *s, uid_t *uid)
57 if ((pw = getpwnam(s)) != NULL) {
61 *uid = strtonum(s, 0, UID_MAX, &errstr);
68 uidcheck(const char *s, uid_t desired)
72 if (parseuid(s, &uid) != 0)
80 strtogid(const char *s)
86 if ((gr = getgrnam(s)) != NULL)
88 gid = strtonum(s, 0, GID_MAX, &errstr);
95 match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
100 if (r->ident[0] == ':') {
101 gid_t rgid = strtogid(r->ident + 1);
104 for (i = 0; i < ngroups; i++) {
105 if (rgid == groups[i])
111 if (uidcheck(r->ident, uid) != 0)
114 if (r->target && uidcheck(r->target, target) != 0)
116 if (r->cmd && strcmp(r->cmd, cmd) != 0)
122 permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr,
123 uid_t target, const char *cmd)
128 for (i = 0; i < nrules; i++) {
129 if (match(uid, groups, ngroups, target, cmd, rules[i]))
134 return (*lastr)->action == PERMIT;
138 parseconfig(const char *filename)
141 extern int yyparse(void);
143 yyfp = fopen(filename, "r");
145 fprintf(stderr, "doas is not enabled.\n");
153 copyenvhelper(const char **oldenvp, const char **safeset, int nsafe, char **envp, int ei)
156 for (i = 0; i < nsafe; i++) {
157 const char **oe = oldenvp;
159 size_t len = strlen(safeset[i]);
160 if (strncmp(*oe, safeset[i], len) == 0 &&
162 if (!(envp[ei++] = strdup(*oe)))
173 copyenv(const char **oldenvp, struct rule *rule)
175 const char *safeset[] = {
176 "DISPLAY", "HOME", "LOGNAME", "MAIL", "SHELL",
177 "PATH", "TERM", "USER", "USERNAME",
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])))
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;
211 envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *));
213 err(1, "can't allocate new environment");
216 ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei);
217 ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei);
226 const char *msgs[] = {
228 "Better luck next time.",
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.",
240 m = msgs[arc4random_uniform(sizeof(msgs) / sizeof(msgs[0]))];
242 fprintf(stderr, "\n");
247 main(int argc, char **argv, char **envp)
251 uid_t uid, target = 0;
252 gid_t groups[NGROUPS_MAX + 1];
258 const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
260 parseconfig("/etc/doas.conf");
262 while ((ch = getopt(argc, argv, "u:")) != -1) {
265 if (parseuid(optarg, &target) != 0)
266 errx(1, "unknown user");
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));
289 err(1, "getpwuid failed");
290 strlcpy(myname, pw->pw_name, sizeof(myname));
291 ngroups = getgroups(NGROUPS_MAX, groups);
293 err(1, "can't get groups");
294 groups[ngroups++] = getgid();
296 if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
297 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
301 if (!(rule->options & NOPASS)) {
302 if (!auth_userokay(myname, NULL, NULL, NULL)) {
303 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
307 envp = copyenv((const char **)envp, rule);
309 pw = getpwuid(target);
311 errx(1, "no passwd entry for target");
312 if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
313 LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
315 errx(1, "failed to set user context for target");
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);