doas

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

doas.c (9304B)


      1 /* $OpenBSD: doas.c,v 1.98 2022/12/22 19:53:22 kn 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 0;
    156 	return (*lastr)->action == PERMIT;
    157 }
    158 
    159 static void
    160 parseconfig(const char *filename, int checkperms)
    161 {
    162 	extern FILE *yyfp;
    163 	extern int yyparse(void);
    164 	struct stat sb;
    165 
    166 	yyfp = fopen(filename, "r");
    167 	if (!yyfp)
    168 		err(1, checkperms ? "doas is not enabled, %s" :
    169 		    "could not open config file %s", filename);
    170 
    171 	if (checkperms) {
    172 		if (fstat(fileno(yyfp), &sb) != 0)
    173 			err(1, "fstat(\"%s\")", filename);
    174 		if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
    175 			errx(1, "%s is writable by group or other", filename);
    176 		if (sb.st_uid != 0)
    177 			errx(1, "%s is not owned by root", filename);
    178 	}
    179 
    180 	yyparse();
    181 	fclose(yyfp);
    182 	if (parse_error)
    183 		exit(1);
    184 }
    185 
    186 static void __dead
    187 checkconfig(const char *confpath, int argc, char **argv,
    188     uid_t uid, gid_t *groups, int ngroups, uid_t target)
    189 {
    190 	const struct rule *rule;
    191 
    192 	setresuid(uid, uid, uid);
    193 	if (pledge("stdio rpath getpw", NULL) == -1)
    194 		err(1, "pledge");
    195 	parseconfig(confpath, 0);
    196 	if (!argc)
    197 		exit(0);
    198 
    199 	if (permit(uid, groups, ngroups, &rule, target, argv[0],
    200 	    (const char **)argv + 1)) {
    201 		printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
    202 		exit(0);
    203 	} else {
    204 		printf("deny\n");
    205 		exit(1);
    206 	}
    207 }
    208 
    209 static int
    210 verifypasswd(const char *user, const char *pass)
    211 {
    212 	struct spwd *sp;
    213 	char *p1, *p2;
    214 
    215 	sp = getspnam(user);
    216 	if (!sp)
    217 		return 0;
    218 	p1 = sp->sp_pwdp;
    219 	if (p1[0] == '!' || p1[0] == '*')
    220 		return 0;
    221 	p2 = crypt(pass, p1);
    222 	if (!p2)
    223 		return 0;
    224 	return strcmp(p1, p2) == 0;
    225 }
    226 
    227 static int
    228 authuser_checkpass(char *myname)
    229 {
    230 	char *challenge = NULL, *response, rbuf[1024], cbuf[128];
    231 
    232 	if (!challenge) {
    233 		char host[HOST_NAME_MAX + 1];
    234 
    235 		if (gethostname(host, sizeof(host)))
    236 			snprintf(host, sizeof(host), "?");
    237 		snprintf(cbuf, sizeof(cbuf),
    238 		    "\rdoas (%.32s@%.32s) password: ", myname, host);
    239 		challenge = cbuf;
    240 	}
    241 	response = readpassphrase(challenge, rbuf, sizeof(rbuf),
    242 	    RPP_REQUIRE_TTY);
    243 	if (response == NULL && errno == ENOTTY) {
    244 		syslog(LOG_NOTICE, "tty required for %s", myname);
    245 		errx(1, "a tty is required");
    246 	}
    247 	if (!verifypasswd(myname, response)) {
    248 		explicit_bzero(rbuf, sizeof(rbuf));
    249 		syslog(LOG_NOTICE, "failed auth for %s", myname);
    250 		warnx("Authentication failed");
    251 		return AUTH_FAILED;
    252 	}
    253 	explicit_bzero(rbuf, sizeof(rbuf));
    254 	return AUTH_OK;
    255 }
    256 
    257 static void
    258 authuser(char *myname, int persist)
    259 {
    260 	int i, fd = -1, valid = 0;
    261 
    262 	if (persist) {
    263 		fd = openpersist(&valid);
    264 		if (valid)
    265 			goto good;
    266 	}
    267 	for (i = 0; i < AUTH_RETRIES; i++) {
    268 		if (authuser_checkpass(myname) == AUTH_OK)
    269 			goto good;
    270 	}
    271 	exit(1);
    272 good:
    273 	if (fd != -1) {
    274 		setpersist(fd);
    275 		close(fd);
    276 	}
    277 }
    278 
    279 int
    280 main(int argc, char **argv)
    281 {
    282 	const char *confpath = NULL;
    283 	char *shargv[] = { NULL, NULL };
    284 	char *sh;
    285 	const char *cmd;
    286 	char cmdline[LINE_MAX];
    287 	char mypwbuf[1024], targpwbuf[1024];
    288 	struct passwd mypwstore, targpwstore;
    289 	struct passwd *mypw, *targpw;
    290 	const struct rule *rule;
    291 	uid_t uid;
    292 	uid_t target = 0;
    293 	gid_t groups[NGROUPS_MAX + 1];
    294 	int ngroups;
    295 	int i, ch, rv;
    296 	int sflag = 0;
    297 	int nflag = 0;
    298 	char cwdpath[PATH_MAX];
    299 	const char *cwd;
    300 	char **envp;
    301 
    302 	setprogname("doas");
    303 	openlog("doas", 0, LOG_AUTHPRIV);
    304 
    305 	uid = getuid();
    306 
    307 	while ((ch = getopt(argc, argv, "C:Lnsu:")) != -1) {
    308 		switch (ch) {
    309 		case 'C':
    310 			confpath = optarg;
    311 			break;
    312 		case 'L':
    313 			exit(clearpersist() != 0);
    314 		case 'u':
    315 			if (parseuid(optarg, &target) != 0)
    316 				errx(1, "unknown user");
    317 			break;
    318 		case 'n':
    319 			nflag = 1;
    320 			break;
    321 		case 's':
    322 			sflag = 1;
    323 			break;
    324 		default:
    325 			usage();
    326 			break;
    327 		}
    328 	}
    329 	argv += optind;
    330 	argc -= optind;
    331 
    332 	if (confpath) {
    333 		if (sflag)
    334 			usage();
    335 	} else if ((!sflag && !argc) || (sflag && argc))
    336 		usage();
    337 
    338 	rv = getpwuid_r(uid, &mypwstore, mypwbuf, sizeof(mypwbuf), &mypw);
    339 	if (rv != 0)
    340 		err(1, "getpwuid_r failed");
    341 	if (mypw == NULL)
    342 		errx(1, "no passwd entry for self");
    343 	ngroups = getgroups(NGROUPS_MAX, groups);
    344 	if (ngroups == -1)
    345 		err(1, "can't get groups");
    346 	groups[ngroups++] = getgid();
    347 
    348 	if (sflag) {
    349 		sh = getenv("SHELL");
    350 		if (sh == NULL || *sh == '\0') {
    351 			shargv[0] = mypw->pw_shell;
    352 		} else
    353 			shargv[0] = sh;
    354 		argv = shargv;
    355 		argc = 1;
    356 	}
    357 
    358 	if (confpath) {
    359 		if (pledge("stdio rpath getpw id", NULL) == -1)
    360 			err(1, "pledge");
    361 		checkconfig(confpath, argc, argv, uid, groups, ngroups,
    362 		    target);
    363 		exit(1);	/* fail safe */
    364 	}
    365 
    366 	if (geteuid())
    367 		errx(1, "not installed setuid");
    368 
    369 	parseconfig("/etc/doas.conf", 1);
    370 
    371 	/* cmdline is used only for logging, no need to abort on truncate */
    372 	(void)strlcpy(cmdline, argv[0], sizeof(cmdline));
    373 	for (i = 1; i < argc; i++) {
    374 		if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
    375 			break;
    376 		if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
    377 			break;
    378 	}
    379 
    380 	cmd = argv[0];
    381 	if (!permit(uid, groups, ngroups, &rule, target, cmd,
    382 	    (const char **)argv + 1)) {
    383 		syslog(LOG_NOTICE, "command not permitted for %s: %s", mypw->pw_name, cmdline);
    384 		errno = EPERM;
    385 		err(1, NULL);
    386 	}
    387 
    388 	if (!(rule->options & NOPASS)) {
    389 		if (nflag)
    390 			errx(1, "Authentication required");
    391 
    392 		authuser(mypw->pw_name, rule->options & PERSIST);
    393 	}
    394 
    395 	rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw);
    396 	if (rv != 0)
    397 		err(1, "getpwuid_r failed");
    398 	if (targpw == NULL)
    399 		errx(1, "no passwd entry for target");
    400 
    401 	if (initgroups(targpw->pw_name, targpw->pw_gid) == -1)
    402 		err(1, "initgroups");
    403 	if (setgid(targpw->pw_gid) == -1)
    404 		err(1, "setgid");
    405 	if (setuid(targpw->pw_uid) == -1)
    406 		err(1, "setuid");
    407 
    408 	if (pledge("stdio rpath exec", NULL) == -1)
    409 		err(1, "pledge");
    410 
    411 	if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
    412 		cwd = "(failed)";
    413 	else
    414 		cwd = cwdpath;
    415 
    416 	if (pledge("stdio exec", NULL) == -1)
    417 		err(1, "pledge");
    418 
    419 	if (!(rule->options & NOLOG)) {
    420 		syslog(LOG_INFO, "%s ran command %s as %s from %s",
    421 		    mypw->pw_name, cmdline, targpw->pw_name, cwd);
    422 	}
    423 
    424 	envp = prepenv(rule, mypw, targpw);
    425 
    426 	if (rule->cmd) {
    427 		if (setenv("PATH", safepath, 1) == -1)
    428 			err(1, "failed to set PATH '%s'", safepath);
    429 	}
    430 	execvpe(cmd, argv, envp);
    431 	if (errno == ENOENT)
    432 		errx(1, "%s: command not found", cmd);
    433 	err(1, "%s", cmd);
    434 }