doas

https://man.openbsd.org/doas.1
git clone anongit@rnpnr.xyz:doas.git
Log | Files | Refs | Feed

doas.c (9355B)


      1 /* $OpenBSD: doas.c,v 1.99 2024/02/15 18:57:58 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 
     18 #include <sys/types.h>
     19 #include <sys/stat.h>
     20 #include <sys/ioctl.h>
     21 
     22 #include <limits.h>
     23 #include <readpassphrase.h>
     24 #include <string.h>
     25 #include <stdio.h>
     26 #include <stdlib.h>
     27 #include <err.h>
     28 #include <unistd.h>
     29 #include <pwd.h>
     30 #include <grp.h>
     31 #include <syslog.h>
     32 #include <errno.h>
     33 #include <fcntl.h>
     34 #include <shadow.h>
     35 
     36 #include "doas.h"
     37 
     38 #ifndef UID_MAX
     39 #define UID_MAX 65535
     40 #endif
     41 
     42 #ifndef GID_MAX
     43 #define GID_MAX 65535
     44 #endif
     45 
     46 static void __dead
     47 usage(void)
     48 {
     49 	fprintf(stderr, "usage: doas [-Lns] [-C config] [-u user]"
     50 	    " command [arg ...]\n");
     51 	exit(1);
     52 }
     53 
     54 static int
     55 parseuid(const char *s, uid_t *uid)
     56 {
     57 	struct passwd *pw;
     58 	const char *errstr;
     59 
     60 	if ((pw = getpwnam(s)) != NULL) {
     61 		*uid = pw->pw_uid;
     62 		if (*uid == UID_MAX)
     63 			return -1;
     64 		return 0;
     65 	}
     66 	*uid = strtonum(s, 0, UID_MAX - 1, &errstr);
     67 	if (errstr)
     68 		return -1;
     69 	return 0;
     70 }
     71 
     72 static int
     73 uidcheck(const char *s, uid_t desired)
     74 {
     75 	uid_t uid;
     76 
     77 	if (parseuid(s, &uid) != 0)
     78 		return -1;
     79 	if (uid != desired)
     80 		return -1;
     81 	return 0;
     82 }
     83 
     84 static int
     85 parsegid(const char *s, gid_t *gid)
     86 {
     87 	struct group *gr;
     88 	const char *errstr;
     89 
     90 	if ((gr = getgrnam(s)) != NULL) {
     91 		*gid = gr->gr_gid;
     92 		if (*gid == GID_MAX)
     93 			return -1;
     94 		return 0;
     95 	}
     96 	*gid = strtonum(s, 0, GID_MAX - 1, &errstr);
     97 	if (errstr)
     98 		return -1;
     99 	return 0;
    100 }
    101 
    102 static int
    103 match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
    104     const char **cmdargs, struct rule *r)
    105 {
    106 	int i;
    107 
    108 	if (r->ident[0] == ':') {
    109 		gid_t rgid;
    110 		if (parsegid(r->ident + 1, &rgid) == -1)
    111 			return 0;
    112 		for (i = 0; i < ngroups; i++) {
    113 			if (rgid == groups[i])
    114 				break;
    115 		}
    116 		if (i == ngroups)
    117 			return 0;
    118 	} else {
    119 		if (uidcheck(r->ident, uid) != 0)
    120 			return 0;
    121 	}
    122 	if (r->target && uidcheck(r->target, target) != 0)
    123 		return 0;
    124 	if (r->cmd) {
    125 		if (strcmp(r->cmd, cmd))
    126 			return 0;
    127 		if (r->cmdargs) {
    128 			/* if arguments were given, they should match explicitly */
    129 			for (i = 0; r->cmdargs[i]; i++) {
    130 				if (!cmdargs[i])
    131 					return 0;
    132 				if (strcmp(r->cmdargs[i], cmdargs[i]))
    133 					return 0;
    134 			}
    135 			if (cmdargs[i])
    136 				return 0;
    137 		}
    138 	}
    139 	return 1;
    140 }
    141 
    142 static int
    143 permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr,
    144     uid_t target, const char *cmd, const char **cmdargs)
    145 {
    146 	size_t i;
    147 
    148 	*lastr = NULL;
    149 	for (i = 0; i < nrules; i++) {
    150 		if (match(uid, groups, ngroups, target, cmd,
    151 		    cmdargs, rules[i]))
    152 			*lastr = rules[i];
    153 	}
    154 	if (!*lastr)
    155 		return -1;
    156 	if ((*lastr)->action == PERMIT)
    157 		return 0;
    158 	return -1;
    159 }
    160 
    161 static void
    162 parseconfig(const char *filename, int checkperms)
    163 {
    164 	extern FILE *yyfp;
    165 	extern int yyparse(void);
    166 	struct stat sb;
    167 
    168 	yyfp = fopen(filename, "r");
    169 	if (!yyfp)
    170 		err(1, checkperms ? "doas is not enabled, %s" :
    171 		    "could not open config file %s", filename);
    172 
    173 	if (checkperms) {
    174 		if (fstat(fileno(yyfp), &sb) != 0)
    175 			err(1, "fstat(\"%s\")", filename);
    176 		if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
    177 			errx(1, "%s is writable by group or other", filename);
    178 		if (sb.st_uid != 0)
    179 			errx(1, "%s is not owned by root", filename);
    180 	}
    181 
    182 	yyparse();
    183 	fclose(yyfp);
    184 	if (parse_error)
    185 		exit(1);
    186 }
    187 
    188 static void __dead
    189 checkconfig(const char *confpath, int argc, char **argv,
    190     uid_t uid, gid_t *groups, int ngroups, uid_t target)
    191 {
    192 	const struct rule *rule;
    193 	int rv;
    194 
    195 	setresuid(uid, uid, uid);
    196 	if (pledge("stdio rpath getpw", NULL) == -1)
    197 		err(1, "pledge");
    198 	parseconfig(confpath, 0);
    199 	if (!argc)
    200 		exit(0);
    201 	rv = permit(uid, groups, ngroups, &rule, target, argv[0], (const char **)argv + 1);
    202 	if (rv == 0) {
    203 		printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
    204 		exit(0);
    205 	} else {
    206 		printf("deny\n");
    207 		exit(1);
    208 	}
    209 }
    210 
    211 static int
    212 verifypasswd(const char *user, const char *pass)
    213 {
    214 	struct spwd *sp;
    215 	char *p1, *p2;
    216 
    217 	sp = getspnam(user);
    218 	if (!sp)
    219 		return 0;
    220 	p1 = sp->sp_pwdp;
    221 	if (p1[0] == '!' || p1[0] == '*')
    222 		return 0;
    223 	p2 = crypt(pass, p1);
    224 	if (!p2)
    225 		return 0;
    226 	return strcmp(p1, p2) == 0;
    227 }
    228 
    229 static int
    230 authuser_checkpass(char *myname)
    231 {
    232 	char *challenge = NULL, *response, rbuf[1024], cbuf[128];
    233 
    234 	if (!challenge) {
    235 		char host[HOST_NAME_MAX + 1];
    236 
    237 		if (gethostname(host, sizeof(host)))
    238 			snprintf(host, sizeof(host), "?");
    239 		snprintf(cbuf, sizeof(cbuf),
    240 		    "\rdoas (%.32s@%.32s) password: ", myname, host);
    241 		challenge = cbuf;
    242 	}
    243 	response = readpassphrase(challenge, rbuf, sizeof(rbuf),
    244 	    RPP_REQUIRE_TTY);
    245 	if (response == NULL && errno == ENOTTY) {
    246 		syslog(LOG_NOTICE, "tty required for %s", myname);
    247 		errx(1, "a tty is required");
    248 	}
    249 	if (!verifypasswd(myname, response)) {
    250 		explicit_bzero(rbuf, sizeof(rbuf));
    251 		syslog(LOG_NOTICE, "failed auth for %s", myname);
    252 		warnx("Authentication failed");
    253 		return AUTH_FAILED;
    254 	}
    255 	explicit_bzero(rbuf, sizeof(rbuf));
    256 	return AUTH_OK;
    257 }
    258 
    259 static void
    260 authuser(char *myname, int persist)
    261 {
    262 	int i, fd = -1, valid = 0;
    263 
    264 	if (persist) {
    265 		fd = openpersist(&valid);
    266 		if (valid)
    267 			goto good;
    268 	}
    269 	for (i = 0; i < AUTH_RETRIES; i++) {
    270 		if (authuser_checkpass(myname) == AUTH_OK)
    271 			goto good;
    272 	}
    273 	exit(1);
    274 good:
    275 	if (fd != -1) {
    276 		setpersist(fd);
    277 		close(fd);
    278 	}
    279 }
    280 
    281 int
    282 main(int argc, char **argv)
    283 {
    284 	const char *confpath = NULL;
    285 	char *shargv[] = { NULL, NULL };
    286 	char *sh;
    287 	const char *cmd;
    288 	char cmdline[LINE_MAX];
    289 	char mypwbuf[1024], targpwbuf[1024];
    290 	struct passwd mypwstore, targpwstore;
    291 	struct passwd *mypw, *targpw;
    292 	const struct rule *rule;
    293 	uid_t uid;
    294 	uid_t target = 0;
    295 	gid_t groups[NGROUPS_MAX + 1];
    296 	int ngroups;
    297 	int i, ch, rv;
    298 	int sflag = 0;
    299 	int nflag = 0;
    300 	char cwdpath[PATH_MAX];
    301 	const char *cwd;
    302 	char **envp;
    303 
    304 	setprogname("doas");
    305 	openlog("doas", 0, LOG_AUTHPRIV);
    306 
    307 	uid = getuid();
    308 
    309 	while ((ch = getopt(argc, argv, "C:Lnsu:")) != -1) {
    310 		switch (ch) {
    311 		case 'C':
    312 			confpath = optarg;
    313 			break;
    314 		case 'L':
    315 			exit(clearpersist() != 0);
    316 		case 'u':
    317 			if (parseuid(optarg, &target) != 0)
    318 				errx(1, "unknown user");
    319 			break;
    320 		case 'n':
    321 			nflag = 1;
    322 			break;
    323 		case 's':
    324 			sflag = 1;
    325 			break;
    326 		default:
    327 			usage();
    328 			break;
    329 		}
    330 	}
    331 	argv += optind;
    332 	argc -= optind;
    333 
    334 	if (confpath) {
    335 		if (sflag)
    336 			usage();
    337 	} else if ((!sflag && !argc) || (sflag && argc))
    338 		usage();
    339 
    340 	rv = getpwuid_r(uid, &mypwstore, mypwbuf, sizeof(mypwbuf), &mypw);
    341 	if (rv != 0)
    342 		err(1, "getpwuid_r failed");
    343 	if (mypw == NULL)
    344 		errx(1, "no passwd entry for self");
    345 	ngroups = getgroups(NGROUPS_MAX, groups);
    346 	if (ngroups == -1)
    347 		err(1, "can't get groups");
    348 	groups[ngroups++] = getgid();
    349 
    350 	if (sflag) {
    351 		sh = getenv("SHELL");
    352 		if (sh == NULL || *sh == '\0') {
    353 			shargv[0] = mypw->pw_shell;
    354 		} else
    355 			shargv[0] = sh;
    356 		argv = shargv;
    357 		argc = 1;
    358 	}
    359 
    360 	if (confpath) {
    361 		if (pledge("stdio rpath getpw id", NULL) == -1)
    362 			err(1, "pledge");
    363 		checkconfig(confpath, argc, argv, uid, groups, ngroups,
    364 		    target);
    365 		exit(1);	/* fail safe */
    366 	}
    367 
    368 	if (geteuid())
    369 		errx(1, "not installed setuid");
    370 
    371 	parseconfig("/etc/doas.conf", 1);
    372 
    373 	/* cmdline is used only for logging, no need to abort on truncate */
    374 	(void)strlcpy(cmdline, argv[0], sizeof(cmdline));
    375 	for (i = 1; i < argc; i++) {
    376 		if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
    377 			break;
    378 		if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
    379 			break;
    380 	}
    381 
    382 	cmd = argv[0];
    383 	rv = permit(uid, groups, ngroups, &rule, target, cmd, (const char **)argv + 1);
    384 	if (rv != 0) {
    385 		syslog(LOG_NOTICE, "command not permitted for %s: %s", mypw->pw_name, cmdline);
    386 		errno = EPERM;
    387 		err(1, NULL);
    388 	}
    389 
    390 	if (!(rule->options & NOPASS)) {
    391 		if (nflag)
    392 			errx(1, "Authentication required");
    393 
    394 		authuser(mypw->pw_name, rule->options & PERSIST);
    395 	}
    396 
    397 	rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw);
    398 	if (rv != 0)
    399 		err(1, "getpwuid_r failed");
    400 	if (targpw == NULL)
    401 		errx(1, "no passwd entry for target");
    402 
    403 	if (initgroups(targpw->pw_name, targpw->pw_gid) == -1)
    404 		err(1, "initgroups");
    405 	if (setgid(targpw->pw_gid) == -1)
    406 		err(1, "setgid");
    407 	if (setuid(targpw->pw_uid) == -1)
    408 		err(1, "setuid");
    409 
    410 	if (pledge("stdio rpath exec", NULL) == -1)
    411 		err(1, "pledge");
    412 
    413 	if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
    414 		cwd = "(failed)";
    415 	else
    416 		cwd = cwdpath;
    417 
    418 	if (pledge("stdio exec", NULL) == -1)
    419 		err(1, "pledge");
    420 
    421 	if (!(rule->options & NOLOG)) {
    422 		syslog(LOG_INFO, "%s ran command %s as %s from %s",
    423 		    mypw->pw_name, cmdline, targpw->pw_name, cwd);
    424 	}
    425 
    426 	envp = prepenv(rule, mypw, targpw);
    427 
    428 	if (rule->cmd) {
    429 		if (setenv("PATH", safepath, 1) == -1)
    430 			err(1, "failed to set PATH '%s'", safepath);
    431 	}
    432 	execvpe(cmd, argv, envp);
    433 	if (errno == ENOENT)
    434 		errx(1, "%s: command not found", cmd);
    435 	err(1, "%s", cmd);
    436 }