1 /* $OpenBSD: doas.c,v 1.1 2015/07/16 20:44:21 tedu Exp $ */
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]))];
241 fprintf(stderr, "%s\n", m);
246 main(int argc, char **argv, char **envp)
250 uid_t uid, target = 0;
251 gid_t groups[NGROUPS_MAX + 1];
257 const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
259 parseconfig("/etc/doas.conf");
261 while ((ch = getopt(argc, argv, "u:")) != -1) {
264 if (parseuid(optarg, &target) != 0)
265 errx(1, "unknown user");
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));
288 err(1, "getpwuid failed");
289 strlcpy(myname, pw->pw_name, sizeof(myname));
290 ngroups = getgroups(NGROUPS_MAX, groups);
292 err(1, "can't get groups");
293 groups[ngroups++] = getgid();
295 if (!permit(uid, groups, ngroups, &rule, target, cmd)) {
296 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed command for %s: %s", myname, cmdline);
300 if (!(rule->options & NOPASS)) {
301 if (!auth_userokay(myname, NULL, NULL, NULL)) {
302 syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed password for %s", myname);
306 envp = copyenv((const char **)envp, rule);
308 pw = getpwuid(target);
310 errx(1, "no passwd entry for target");
311 if (setusercontext(NULL, pw, target, LOGIN_SETGROUP |
312 LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
314 errx(1, "failed to set user context for target");
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);