128019Sjoerg/* $OpenBSD: doas.c,v 1.99 2024/02/15 18:57:58 tedu Exp $ */
228021Sjoerg/*
328021Sjoerg * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
428021Sjoerg *
528021Sjoerg * Permission to use, copy, modify, and distribute this software for any
628021Sjoerg * purpose with or without fee is hereby granted, provided that the above
728021Sjoerg * copyright notice and this permission notice appear in all copies.
828021Sjoerg *
928021Sjoerg * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1028021Sjoerg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1128021Sjoerg * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1228021Sjoerg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1328021Sjoerg * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1428021Sjoerg * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1528021Sjoerg * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1628021Sjoerg */
1728021Sjoerg
1828021Sjoerg#include <sys/types.h>
1928021Sjoerg#include <sys/stat.h>
2028021Sjoerg#include <sys/ioctl.h>
2128021Sjoerg
2228021Sjoerg#include <limits.h>
2328019Sjoerg#include <login_cap.h>
2428019Sjoerg#include <bsd_auth.h>
2528021Sjoerg#include <readpassphrase.h>
2628019Sjoerg#include <string.h>
2728019Sjoerg#include <stdio.h>
2828019Sjoerg#include <stdlib.h>
2928019Sjoerg#include <err.h>
3028019Sjoerg#include <unistd.h>
3128019Sjoerg#include <pwd.h>
3228019Sjoerg#include <grp.h>
3328019Sjoerg#include <syslog.h>
3428019Sjoerg#include <errno.h>
3528019Sjoerg#include <fcntl.h>
3628019Sjoerg
3728019Sjoerg#include "doas.h"
3828019Sjoerg
3928019Sjoergstatic void __dead
4028019Sjoergusage(void)
4128019Sjoerg{
4228019Sjoerg	fprintf(stderr, "usage: doas [-Lns] [-a style] [-C config] [-u user]"
4328019Sjoerg	    " command [arg ...]\n");
4428019Sjoerg	exit(1);
4528019Sjoerg}
4628019Sjoerg
4728019Sjoergstatic int
4828019Sjoergparseuid(const char *s, uid_t *uid)
4928019Sjoerg{
5028019Sjoerg	struct passwd *pw;
5128019Sjoerg	const char *errstr;
5228019Sjoerg
5328019Sjoerg	if ((pw = getpwnam(s)) != NULL) {
54111010Snectar		*uid = pw->pw_uid;
5528019Sjoerg		if (*uid == UID_MAX)
5628021Sjoerg			return -1;
57111010Snectar		return 0;
5828019Sjoerg	}
59111010Snectar	*uid = strtonum(s, 0, UID_MAX - 1, &errstr);
6028021Sjoerg	if (errstr)
6128019Sjoerg		return -1;
6292986Sobrien	return 0;
6328019Sjoerg}
6471579Sdeischen
6528019Sjoergstatic int
6628019Sjoerguidcheck(const char *s, uid_t desired)
67122830Snectar{
6879664Sdd	uid_t uid;
6928019Sjoerg
7048614Sobrien	if (parseuid(s, &uid) != 0)
7171579Sdeischen		return -1;
7271579Sdeischen	if (uid != desired)
7328021Sjoerg		return -1;
7428019Sjoerg	return 0;
75112156Smtm}
7648614Sobrien
7728021Sjoergstatic int
7828019Sjoergparsegid(const char *s, gid_t *gid)
7948614Sobrien{
80112156Smtm	struct group *gr;
8128019Sjoerg	const char *errstr;
8228021Sjoerg
8328021Sjoerg	if ((gr = getgrnam(s)) != NULL) {
8428021Sjoerg		*gid = gr->gr_gid;
8528021Sjoerg		if (*gid == GID_MAX)
8653941Sache			return -1;
8772168Sphantom		return 0;
8828019Sjoerg	}
8928021Sjoerg	*gid = strtonum(s, 0, GID_MAX - 1, &errstr);
9028021Sjoerg	if (errstr)
9128021Sjoerg		return -1;
9228021Sjoerg	return 0;
9328019Sjoerg}
9428021Sjoerg
9528019Sjoergstatic int
9628021Sjoergmatch(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd,
9728164Sache    const char **cmdargs, struct rule *r)
9828164Sache{
9928021Sjoerg	int i;
10028021Sjoerg
10128021Sjoerg	if (r->ident[0] == ':') {
10228021Sjoerg		gid_t rgid;
10328021Sjoerg		if (parsegid(r->ident + 1, &rgid) == -1)
10428019Sjoerg			return 0;
10553941Sache		for (i = 0; i < ngroups; i++) {
10653941Sache			if (rgid == groups[i])
10753941Sache				break;
10828021Sjoerg		}
10928021Sjoerg		if (i == ngroups)
11028021Sjoerg			return 0;
11128021Sjoerg	} else {
11228021Sjoerg		if (uidcheck(r->ident, uid) != 0)
11328021Sjoerg			return 0;
11428021Sjoerg	}
11528019Sjoerg	if (r->target && uidcheck(r->target, target) != 0)
11653941Sache		return 0;
117112156Smtm	if (r->cmd) {
11828021Sjoerg		if (strcmp(r->cmd, cmd))
11928021Sjoerg			return 0;
12028021Sjoerg		if (r->cmdargs) {
12128019Sjoerg			/* if arguments were given, they should match explicitly */
12253941Sache			for (i = 0; r->cmdargs[i]; i++) {
12353941Sache				if (!cmdargs[i])
12453941Sache					return 0;
12553941Sache				if (strcmp(r->cmdargs[i], cmdargs[i]))
12654316Ssheldonh					return 0;
12754316Ssheldonh			}
12854316Ssheldonh			if (cmdargs[i])
12953941Sache				return 0;
13053941Sache		}
13154316Ssheldonh	}
13253941Sache	return 1;
13353941Sache}
13453941Sache
13553941Sachestatic int
13653941Sachepermit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr,
13753941Sache    uid_t target, const char *cmd, const char **cmdargs)
13853941Sache{
13928021Sjoerg	size_t i;
140112156Smtm
14128021Sjoerg	*lastr = NULL;
14228021Sjoerg	for (i = 0; i < nrules; i++) {
14328021Sjoerg		if (match(uid, groups, ngroups, target, cmd,
14428019Sjoerg		    cmdargs, rules[i]))
14528021Sjoerg			*lastr = rules[i];
146112156Smtm	}
14728021Sjoerg	if (!*lastr)
14828021Sjoerg		return -1;
14928021Sjoerg	if ((*lastr)->action == PERMIT)
15028019Sjoerg		return 0;
15153941Sache	return -1;
15253960Sache}
15353960Sache
15453941Sachestatic void
15553941Sacheparseconfig(const char *filename, int checkperms)
15653941Sache{
15753941Sache	extern FILE *yyfp;
15853960Sache	extern int yyparse(void);
15953960Sache	struct stat sb;
16053941Sache
16153941Sache	yyfp = fopen(filename, "r");
16253941Sache	if (!yyfp)
16353960Sache		err(1, checkperms ? "doas is not enabled, %s" :
164112156Smtm		    "could not open config file %s", filename);
16574412Sache
16674412Sache	if (checkperms) {
16774412Sache		if (fstat(fileno(yyfp), &sb) != 0)
16874412Sache			err(1, "fstat(\"%s\")", filename);
16928021Sjoerg		if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)
170112156Smtm			errx(1, "%s is writable by group or other", filename);
17128021Sjoerg		if (sb.st_uid != 0)
17228021Sjoerg			errx(1, "%s is not owned by root", filename);
17328021Sjoerg	}
17428019Sjoerg
17528021Sjoerg	yyparse();
176112156Smtm	fclose(yyfp);
17728021Sjoerg	if (parse_error)
17828021Sjoerg		exit(1);
17928021Sjoerg}
18028019Sjoerg
18128021Sjoergstatic void __dead
182112156Smtmcheckconfig(const char *confpath, int argc, char **argv,
18328021Sjoerg    uid_t uid, gid_t *groups, int ngroups, uid_t target)
18428021Sjoerg{
18528021Sjoerg	const struct rule *rule;
18628019Sjoerg	int rv;
18728021Sjoerg
188112156Smtm	setresuid(uid, uid, uid);
18928021Sjoerg	if (pledge("stdio rpath getpw", NULL) == -1)
19028021Sjoerg		err(1, "pledge");
19128021Sjoerg	parseconfig(confpath, 0);
19228019Sjoerg	if (!argc)
19328021Sjoerg		exit(0);
194112156Smtm	rv = permit(uid, groups, ngroups, &rule, target, argv[0],
19528021Sjoerg 	    (const char **)argv + 1);
19628021Sjoerg	if (rv == 0) {
19728021Sjoerg		printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : "");
19828019Sjoerg		exit(0);
19928021Sjoerg	} else {
20028164Sache		printf("deny\n");
20128021Sjoerg		exit(1);
20228019Sjoerg	}
20354316Ssheldonh}
20454316Ssheldonh
20528021Sjoergstatic int
20628021Sjoergauthuser_checkpass(char *myname, char *login_style)
20754316Ssheldonh{
20828021Sjoerg	char *challenge = NULL, *response, rbuf[1024], cbuf[128];
20953083Ssheldonh	auth_session_t *as;
21028021Sjoerg
21128019Sjoerg	if (!(as = auth_userchallenge(myname, login_style, "auth-doas",
21253083Ssheldonh	    &challenge))) {
21328021Sjoerg		warnx("Authentication failed");
21428019Sjoerg		return AUTH_FAILED;
21528021Sjoerg	}
21628021Sjoerg	if (!challenge) {
21728164Sache		char host[HOST_NAME_MAX + 1];
21828021Sjoerg
21928019Sjoerg		if (gethostname(host, sizeof(host)))
22028164Sache			snprintf(host, sizeof(host), "?");
22128021Sjoerg		snprintf(cbuf, sizeof(cbuf),
22228019Sjoerg		    "\rdoas (%.32s@%.32s) password: ", myname, host);
22354316Ssheldonh		challenge = cbuf;
22454316Ssheldonh	}
22528021Sjoerg	response = readpassphrase(challenge, rbuf, sizeof(rbuf),
22628021Sjoerg	    RPP_REQUIRE_TTY);
22754316Ssheldonh	if (response == NULL && errno == ENOTTY) {
22828021Sjoerg		syslog(LOG_AUTHPRIV | LOG_NOTICE,
22928019Sjoerg		    "tty required for %s", myname);
23053083Ssheldonh		errx(1, "a tty is required");
23153083Ssheldonh	}
23253083Ssheldonh	if (!auth_userresponse(as, response, 0)) {
23328021Sjoerg		explicit_bzero(rbuf, sizeof(rbuf));
23453083Ssheldonh		syslog(LOG_AUTHPRIV | LOG_NOTICE,
23553083Ssheldonh		    "failed auth for %s", myname);
23653083Ssheldonh		warnx("Authentication failed");
23728021Sjoerg		return AUTH_FAILED;
23853083Ssheldonh	}
23928019Sjoerg	explicit_bzero(rbuf, sizeof(rbuf));
24028164Sache	return AUTH_OK;
24128164Sache}
24228021Sjoerg
24328021Sjoergstatic void
24428019Sjoergauthuser(char *myname, char *login_style, int persist)
24528021Sjoerg{
24628021Sjoerg	int i, fd = -1;
24728021Sjoerg
24828021Sjoerg	if (persist)
24954316Ssheldonh		fd = open("/dev/tty", O_RDWR);
25054316Ssheldonh	if (fd != -1) {
25154316Ssheldonh		if (ioctl(fd, TIOCCHKVERAUTH) == 0)
25254316Ssheldonh			goto good;
25354316Ssheldonh	}
25454316Ssheldonh	for (i = 0; i < AUTH_RETRIES; i++) {
25554316Ssheldonh		if (authuser_checkpass(myname, login_style) == AUTH_OK)
25654316Ssheldonh			goto good;
25728164Sache	}
25828021Sjoerg	exit(1);
25928019Sjoerggood:
26054316Ssheldonh	if (fd != -1) {
26154316Ssheldonh		int secs = 5 * 60;
26228021Sjoerg		ioctl(fd, TIOCSETVERAUTH, &secs);
26328021Sjoerg		close(fd);
26454316Ssheldonh	}
26528021Sjoerg}
26628021Sjoerg
26728021Sjoergint
26828021Sjoergunveilcommands(const char *ipath, const char *cmd)
26954301Ssheldonh{
27028021Sjoerg	char *path = NULL, *p;
27128019Sjoerg	int unveils = 0;
27228021Sjoerg
27328019Sjoerg	if (strchr(cmd, '/') != NULL) {
27428164Sache		if (unveil(cmd, "x") != -1)
27528164Sache			unveils++;
27628021Sjoerg		goto done;
27728021Sjoerg	}
27828019Sjoerg
27928021Sjoerg	if (!ipath) {
28054316Ssheldonh		errno = ENOENT;
28154316Ssheldonh		goto done;
28254316Ssheldonh	}
28354316Ssheldonh	path = strdup(ipath);
28472168Sphantom	if (!path) {
28572168Sphantom		errno = ENOENT;
28628021Sjoerg		goto done;
28728021Sjoerg	}
28828021Sjoerg	for (p = path; p && *p; ) {
28928021Sjoerg		char buf[PATH_MAX];
29028021Sjoerg		char *cp = strsep(&p, ":");
29128021Sjoerg
29228021Sjoerg		if (cp) {
29328019Sjoerg			int r = snprintf(buf, sizeof buf, "%s/%s", cp, cmd);
29472168Sphantom			if (r >= 0 && r < sizeof buf) {
29572168Sphantom				if (unveil(buf, "x") != -1)
29628021Sjoerg					unveils++;
29728021Sjoerg			}
29828021Sjoerg		}
29928021Sjoerg	}
30028021Sjoergdone:
30128021Sjoerg	free(path);
30228021Sjoerg	return (unveils);
30328019Sjoerg}
30428021Sjoerg
30528019Sjoergint
30628021Sjoergmain(int argc, char **argv)
30728021Sjoerg{
30872168Sphantom	const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
30974409Sache	    "/usr/local/bin:/usr/local/sbin";
31074409Sache	const char *confpath = NULL;
31174409Sache	char *shargv[] = { NULL, NULL };
31274409Sache	char *sh;
31374409Sache	const char *p;
31474409Sache	const char *cmd;
31574409Sache	char cmdline[LINE_MAX];
31674409Sache	char mypwbuf[_PW_BUF_LEN], targpwbuf[_PW_BUF_LEN];
31728021Sjoerg	struct passwd mypwstore, targpwstore;
31872168Sphantom	struct passwd *mypw, *targpw;
31928021Sjoerg	const struct rule *rule;
32028019Sjoerg	uid_t uid;
32128021Sjoerg	uid_t target = 0;
32228021Sjoerg	gid_t groups[NGROUPS_MAX + 1];
32328021Sjoerg	int ngroups;
32428019Sjoerg	int i, ch, rv;
32553083Ssheldonh	int sflag = 0;
32653083Ssheldonh	int nflag = 0;
32753083Ssheldonh	char cwdpath[PATH_MAX];
32853083Ssheldonh	const char *cwd;
32953083Ssheldonh	char *login_style = NULL;
33053083Ssheldonh	char **envp;
33153083Ssheldonh
33253083Ssheldonh	setprogname("doas");
33353083Ssheldonh
33453083Ssheldonh	closefrom(STDERR_FILENO + 1);
33553083Ssheldonh
33654316Ssheldonh	uid = getuid();
33754316Ssheldonh
33853083Ssheldonh	while ((ch = getopt(argc, argv, "a:C:Lnsu:")) != -1) {
33953083Ssheldonh		switch (ch) {
34054316Ssheldonh		case 'a':
34153083Ssheldonh			login_style = optarg;
34253083Ssheldonh			break;
34353083Ssheldonh		case 'C':
34453083Ssheldonh			confpath = optarg;
34553083Ssheldonh			break;
34653083Ssheldonh		case 'L':
34753083Ssheldonh			i = open("/dev/tty", O_RDWR);
34853083Ssheldonh			if (i != -1)
34953083Ssheldonh				ioctl(i, TIOCCLRVERAUTH);
35053083Ssheldonh			exit(i == -1);
35153083Ssheldonh		case 'u':
35253083Ssheldonh			if (parseuid(optarg, &target) != 0)
35353083Ssheldonh				errx(1, "unknown user");
35454316Ssheldonh			break;
35553083Ssheldonh		case 'n':
35653083Ssheldonh			nflag = 1;
35753083Ssheldonh			break;
35853083Ssheldonh		case 's':
35953083Ssheldonh			sflag = 1;
36053083Ssheldonh			break;
36153083Ssheldonh		default:
36253083Ssheldonh			usage();
36353083Ssheldonh			break;
36453083Ssheldonh		}
36528021Sjoerg	}
36628021Sjoerg	argv += optind;
36754316Ssheldonh	argc -= optind;
36854316Ssheldonh
36954316Ssheldonh	if (confpath) {
37054316Ssheldonh		if (sflag)
37154316Ssheldonh			usage();
37254316Ssheldonh	} else if ((!sflag && !argc) || (sflag && argc))
37354316Ssheldonh		usage();
37454316Ssheldonh
37528164Sache	rv = getpwuid_r(uid, &mypwstore, mypwbuf, sizeof(mypwbuf), &mypw);
37628021Sjoerg	if (rv != 0)
37728019Sjoerg		err(1, "getpwuid_r failed");
37854316Ssheldonh	if (mypw == NULL)
37954316Ssheldonh		errx(1, "no passwd entry for self");
38028021Sjoerg	ngroups = getgroups(NGROUPS_MAX, groups);
38128021Sjoerg	if (ngroups == -1)
38254316Ssheldonh		err(1, "can't get groups");
38328021Sjoerg	groups[ngroups++] = getgid();
38428021Sjoerg
38528021Sjoerg	if (sflag) {
38628019Sjoerg		sh = getenv("SHELL");
38728021Sjoerg		if (sh == NULL || *sh == '\0') {
38828019Sjoerg			shargv[0] = mypw->pw_shell;
38928164Sache		} else
39028164Sache			shargv[0] = sh;
39128021Sjoerg		argv = shargv;
39228021Sjoerg		argc = 1;
39328019Sjoerg	}
39428021Sjoerg
39528021Sjoerg	if (confpath) {
39628021Sjoerg		if (pledge("stdio rpath getpw id", NULL) == -1)
39772168Sphantom			err(1, "pledge");
39853941Sache		checkconfig(confpath, argc, argv, uid, groups, ngroups,
39953941Sache		    target);
40072168Sphantom		exit(1);	/* fail safe */
40153941Sache	}
40272168Sphantom
40353941Sache	if (geteuid())
40453941Sache		errx(1, "not installed setuid");
40553941Sache
40653941Sache	parseconfig("/etc/doas.conf", 1);
40774409Sache
40874409Sache	/* cmdline is used only for logging, no need to abort on truncate */
40974409Sache	(void)strlcpy(cmdline, argv[0], sizeof(cmdline));
41074409Sache	for (i = 1; i < argc; i++) {
41174409Sache		if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline))
41274409Sache			break;
41374409Sache		if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline))
41474409Sache			break;
41553941Sache	}
41628021Sjoerg
41772168Sphantom	cmd = argv[0];
41828021Sjoerg	rv = permit(uid, groups, ngroups, &rule, target, cmd,
41928019Sjoerg	    (const char **)argv + 1);
42028021Sjoerg	if (rv != 0) {
42128021Sjoerg		syslog(LOG_AUTHPRIV | LOG_NOTICE,
42228021Sjoerg		    "command not permitted for %s: %s", mypw->pw_name, cmdline);
42328019Sjoerg		errc(1, EPERM, NULL);
42428021Sjoerg	}
42528164Sache
42628021Sjoerg	if (!(rule->options & NOPASS)) {
42728019Sjoerg		if (nflag)
42854316Ssheldonh			errx(1, "Authentication required");
42954316Ssheldonh
43028021Sjoerg		authuser(mypw->pw_name, login_style, rule->options & PERSIST);
43128021Sjoerg	}
43254316Ssheldonh
43328021Sjoerg	if ((p = getenv("PATH")) != NULL)
43428021Sjoerg		formerpath = strdup(p);
43528021Sjoerg	if (formerpath == NULL)
43628019Sjoerg		formerpath = "";
43728021Sjoerg
43828019Sjoerg	if (unveil(_PATH_LOGIN_CONF, "r") == -1)
43928164Sache		err(1, "unveil %s", _PATH_LOGIN_CONF);
44028164Sache	if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1)
44128021Sjoerg		err(1, "unveil %s.db", _PATH_LOGIN_CONF);
44228021Sjoerg	if (unveil(_PATH_LOGIN_CONF_D, "r") == -1)
44328019Sjoerg		err(1, "unveil %s", _PATH_LOGIN_CONF_D);
44479664Sdd	if (rule->cmd) {
44579664Sdd		if (setenv("PATH", safepath, 1) == -1)
44679664Sdd			err(1, "failed to set PATH '%s'", safepath);
447122830Snectar	}
448122830Snectar	if (unveilcommands(getenv("PATH"), cmd) == 0)
44979664Sdd		goto fail;
45079664Sdd
451122830Snectar	if (pledge("stdio rpath getpw exec id", NULL) == -1)
452122830Snectar		err(1, "pledge");
453122830Snectar
454122830Snectar	rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw);
455122830Snectar	if (rv != 0)
45679664Sdd		err(1, "getpwuid_r failed");
457122830Snectar	if (targpw == NULL)
458122830Snectar		errx(1, "no passwd entry for target");
45979664Sdd
46079664Sdd	if (setusercontext(NULL, targpw, target, LOGIN_SETGROUP |
461112156Smtm	    LOGIN_SETPATH |
46279664Sdd	    LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
46379664Sdd	    LOGIN_SETUSER | LOGIN_SETENV | LOGIN_SETRTABLE) != 0)
46479664Sdd		errx(1, "failed to set user context for target");
46528021Sjoerg
46628021Sjoerg	if (pledge("stdio rpath exec", NULL) == -1)
46728164Sache		err(1, "pledge");
46828021Sjoerg
46928019Sjoerg	if (getcwd(cwdpath, sizeof(cwdpath)) == NULL)
47028164Sache		cwd = "(failed)";
47128021Sjoerg	else
47228019Sjoerg		cwd = cwdpath;
47354316Ssheldonh
47454316Ssheldonh	if (pledge("stdio exec", NULL) == -1)
47528021Sjoerg		err(1, "pledge");
47628021Sjoerg
47754316Ssheldonh	if (!(rule->options & NOLOG)) {
47828021Sjoerg		syslog(LOG_AUTHPRIV | LOG_INFO,
47928021Sjoerg		    "%s ran command %s as %s from %s",
48028021Sjoerg		    mypw->pw_name, cmdline, targpw->pw_name, cwd);
48146051Swes	}
48246042Swes
48328021Sjoerg	envp = prepenv(rule, mypw, targpw);
48428021Sjoerg
48528019Sjoerg	/* setusercontext set path for the next process, so reset it for us */
48628021Sjoerg	if (rule->cmd) {
48728019Sjoerg		if (setenv("PATH", safepath, 1) == -1)
48828164Sache			err(1, "failed to set PATH '%s'", safepath);
48928164Sache	} else {
49028021Sjoerg		if (setenv("PATH", formerpath, 1) == -1)
49128021Sjoerg			err(1, "failed to set PATH '%s'", formerpath);
49248550Sobrien	}
49348550Sobrien	execvpe(cmd, argv, envp);
49448550Sobrienfail:
49548550Sobrien	if (errno == ENOENT)
49648550Sobrien		errx(1, "%s: command not found", cmd);
49748550Sobrien	err(1, "%s", cmd);
49852860Sache}
49948550Sobrien