master hovercats/oakiss / pkg / openbsd / patch / 0014-doas-Port-to-linux-musl.patch
  1From cca6c84b472255c4d015d7d97225791796f61392 Mon Sep 17 00:00:00 2001
  2From: Michael Forney <mforney@mforney.org>
  3Date: Sun, 26 Feb 2017 16:50:55 -0800
  4Subject: [PATCH] doas: Port to linux/musl
  5
  6Remove -a login style option and BSD authentication. Instead, compare
  7against shadow file.
  8
  9Use timestamp files in /run/doas instead of TIOC*VERAUTH to implement
 10persist.
 11
 12Use initgroups/setgid/setuid instead of setusercontext.
 13
 14Provide UID_MAX and GID_MAX defaults.
 15
 16Use LOGIN_NAME_MAX instead of _PW_NAME_LEN.
 17
 18Remove call to closefrom.
 19
 20Replace calls to errc with err after setting errno.
 21
 22Call openlog at start to set syslog identity.
 23
 24Remove unveil/pledge since they aren't supported on Linux.
 25
 26Simplify handling of PATH in the environment since we don't have
 27login.conf with per-user default PATH.
 28---
 29 usr.bin/doas/doas.1    |   9 ---
 30 usr.bin/doas/doas.c    | 168 +++++++++++++----------------------------
 31 usr.bin/doas/doas.h    |   6 +-
 32 usr.bin/doas/env.c     |  17 ++---
 33 usr.bin/doas/parse.y   |   1 +
 34 usr.bin/doas/persist.c | 133 ++++++++++++++++++++++++++++++++
 35 6 files changed, 198 insertions(+), 136 deletions(-)
 36 create mode 100644 usr.bin/doas/persist.c
 37
 38diff --git a/usr.bin/doas/doas.1 b/usr.bin/doas/doas.1
 39index 25827cc7104..3542680faf5 100644
 40--- a/usr.bin/doas/doas.1
 41+++ b/usr.bin/doas/doas.1
 42@@ -22,7 +22,6 @@
 43 .Sh SYNOPSIS
 44 .Nm doas
 45 .Op Fl Lns
 46-.Op Fl a Ar style
 47 .Op Fl C Ar config
 48 .Op Fl u Ar user
 49 .Ar command
 50@@ -67,14 +66,6 @@ The working directory is not changed.
 51 .Pp
 52 The options are as follows:
 53 .Bl -tag -width tenletters
 54-.It Fl a Ar style
 55-Use the specified authentication style when validating the user,
 56-as allowed by
 57-.Pa /etc/login.conf .
 58-A list of doas-specific authentication methods may be configured by adding an
 59-.Sq auth-doas
 60-entry in
 61-.Xr login.conf 5 .
 62 .It Fl C Ar config
 63 Parse and check the configuration file
 64 .Ar config ,
 65diff --git a/usr.bin/doas/doas.c b/usr.bin/doas/doas.c
 66index 3999b2e2f64..32532359267 100644
 67--- a/usr.bin/doas/doas.c
 68+++ b/usr.bin/doas/doas.c
 69@@ -20,8 +20,6 @@
 70 #include <sys/ioctl.h>
 71 
 72 #include <limits.h>
 73-#include <login_cap.h>
 74-#include <bsd_auth.h>
 75 #include <readpassphrase.h>
 76 #include <string.h>
 77 #include <stdio.h>
 78@@ -33,13 +31,22 @@
 79 #include <syslog.h>
 80 #include <errno.h>
 81 #include <fcntl.h>
 82+#include <shadow.h>
 83 
 84 #include "doas.h"
 85 
 86+#ifndef UID_MAX
 87+#define UID_MAX 65535
 88+#endif
 89+
 90+#ifndef GID_MAX
 91+#define GID_MAX 65535
 92+#endif
 93+
 94 static void __dead
 95 usage(void)
 96 {
 97-	fprintf(stderr, "usage: doas [-Lns] [-a style] [-C config] [-u user]"
 98+	fprintf(stderr, "usage: doas [-Lns] [-C config] [-u user]"
 99 	    " command [arg ...]\n");
100 	exit(1);
101 }
102@@ -203,16 +210,28 @@ checkconfig(const char *confpath, int argc, char **argv,
103 }
104 
105 static int
106-authuser_checkpass(char *myname, char *login_style)
107+verifypasswd(const char *user, const char *pass)
108+{
109+	struct spwd *sp;
110+	char *p1, *p2;
111+
112+	sp = getspnam(user);
113+	if (!sp)
114+		return 0;
115+	p1 = sp->sp_pwdp;
116+	if (p1[0] == '!' || p1[0] == '*')
117+		return 0;
118+	p2 = crypt(pass, p1);
119+	if (!p2)
120+		return 0;
121+	return strcmp(p1, p2) == 0;
122+}
123+
124+static int
125+authuser_checkpass(char *myname)
126 {
127 	char *challenge = NULL, *response, rbuf[1024], cbuf[128];
128-	auth_session_t *as;
129 
130-	if (!(as = auth_userchallenge(myname, login_style, "auth-doas",
131-	    &challenge))) {
132-		warnx("Authentication failed");
133-		return AUTH_FAILED;
134-	}
135 	if (!challenge) {
136 		char host[HOST_NAME_MAX + 1];
137 
138@@ -225,14 +244,12 @@ authuser_checkpass(char *myname, char *login_style)
139 	response = readpassphrase(challenge, rbuf, sizeof(rbuf),
140 	    RPP_REQUIRE_TTY);
141 	if (response == NULL && errno == ENOTTY) {
142-		syslog(LOG_AUTHPRIV | LOG_NOTICE,
143-		    "tty required for %s", myname);
144+		syslog(LOG_NOTICE, "tty required for %s", myname);
145 		errx(1, "a tty is required");
146 	}
147-	if (!auth_userresponse(as, response, 0)) {
148+	if (!verifypasswd(myname, response)) {
149 		explicit_bzero(rbuf, sizeof(rbuf));
150-		syslog(LOG_AUTHPRIV | LOG_NOTICE,
151-		    "failed auth for %s", myname);
152+		syslog(LOG_NOTICE, "failed auth for %s", myname);
153 		warnx("Authentication failed");
154 		return AUTH_FAILED;
155 	}
156@@ -241,79 +258,36 @@ authuser_checkpass(char *myname, char *login_style)
157 }
158 
159 static void
160-authuser(char *myname, char *login_style, int persist)
161+authuser(char *myname, int persist)
162 {
163-	int i, fd = -1;
164+	int i, fd = -1, valid = 0;
165 
166-	if (persist)
167-		fd = open("/dev/tty", O_RDWR);
168-	if (fd != -1) {
169-		if (ioctl(fd, TIOCCHKVERAUTH) == 0)
170+	if (persist) {
171+		fd = openpersist(&valid);
172+		if (valid)
173 			goto good;
174 	}
175 	for (i = 0; i < AUTH_RETRIES; i++) {
176-		if (authuser_checkpass(myname, login_style) == AUTH_OK)
177+		if (authuser_checkpass(myname) == AUTH_OK)
178 			goto good;
179 	}
180 	exit(1);
181 good:
182 	if (fd != -1) {
183-		int secs = 5 * 60;
184-		ioctl(fd, TIOCSETVERAUTH, &secs);
185+		setpersist(fd);
186 		close(fd);
187 	}
188 }
189 
190-int
191-unveilcommands(const char *ipath, const char *cmd)
192-{
193-	char *path = NULL, *p;
194-	int unveils = 0;
195-
196-	if (strchr(cmd, '/') != NULL) {
197-		if (unveil(cmd, "x") != -1)
198-			unveils++;
199-		goto done;
200-	}
201-
202-	if (!ipath) {
203-		errno = ENOENT;
204-		goto done;
205-	}
206-	path = strdup(ipath);
207-	if (!path) {
208-		errno = ENOENT;
209-		goto done;
210-	}
211-	for (p = path; p && *p; ) {
212-		char buf[PATH_MAX];
213-		char *cp = strsep(&p, ":");
214-
215-		if (cp) {
216-			int r = snprintf(buf, sizeof buf, "%s/%s", cp, cmd);
217-			if (r >= 0 && r < sizeof buf) {
218-				if (unveil(buf, "x") != -1)
219-					unveils++;
220-			}
221-		}
222-	}
223-done:
224-	free(path);
225-	return (unveils);
226-}
227-
228 int
229 main(int argc, char **argv)
230 {
231-	const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:"
232-	    "/usr/local/bin:/usr/local/sbin";
233 	const char *confpath = NULL;
234 	char *shargv[] = { NULL, NULL };
235 	char *sh;
236-	const char *p;
237 	const char *cmd;
238 	char cmdline[LINE_MAX];
239-	char mypwbuf[_PW_BUF_LEN], targpwbuf[_PW_BUF_LEN];
240+	char mypwbuf[1024], targpwbuf[1024];
241 	struct passwd mypwstore, targpwstore;
242 	struct passwd *mypw, *targpw;
243 	const struct rule *rule;
244@@ -326,28 +300,20 @@ main(int argc, char **argv)
245 	int nflag = 0;
246 	char cwdpath[PATH_MAX];
247 	const char *cwd;
248-	char *login_style = NULL;
249 	char **envp;
250 
251 	setprogname("doas");
252-
253-	closefrom(STDERR_FILENO + 1);
254+	openlog("doas", 0, LOG_AUTHPRIV);
255 
256 	uid = getuid();
257 
258-	while ((ch = getopt(argc, argv, "a:C:Lnsu:")) != -1) {
259+	while ((ch = getopt(argc, argv, "C:Lnsu:")) != -1) {
260 		switch (ch) {
261-		case 'a':
262-			login_style = optarg;
263-			break;
264 		case 'C':
265 			confpath = optarg;
266 			break;
267 		case 'L':
268-			i = open("/dev/tty", O_RDWR);
269-			if (i != -1)
270-				ioctl(i, TIOCCLRVERAUTH);
271-			exit(i == -1);
272+			exit(clearpersist() != 0);
273 		case 'u':
274 			if (parseuid(optarg, &target) != 0)
275 				errx(1, "unknown user");
276@@ -418,50 +384,30 @@ main(int argc, char **argv)
277 	rv = permit(uid, groups, ngroups, &rule, target, cmd,
278 	    (const char **)argv + 1);
279 	if (rv != 0) {
280-		syslog(LOG_AUTHPRIV | LOG_NOTICE,
281-		    "command not permitted for %s: %s", mypw->pw_name, cmdline);
282-		errc(1, EPERM, NULL);
283+		syslog(LOG_NOTICE, "command not permitted for %s: %s", mypw->pw_name, cmdline);
284+		errno = EPERM;
285+		err(1, NULL);
286 	}
287 
288 	if (!(rule->options & NOPASS)) {
289 		if (nflag)
290 			errx(1, "Authentication required");
291 
292-		authuser(mypw->pw_name, login_style, rule->options & PERSIST);
293+		authuser(mypw->pw_name, rule->options & PERSIST);
294 	}
295 
296-	if ((p = getenv("PATH")) != NULL)
297-		formerpath = strdup(p);
298-	if (formerpath == NULL)
299-		formerpath = "";
300-
301-	if (unveil(_PATH_LOGIN_CONF, "r") == -1)
302-		err(1, "unveil %s", _PATH_LOGIN_CONF);
303-	if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1)
304-		err(1, "unveil %s.db", _PATH_LOGIN_CONF);
305-	if (unveil(_PATH_LOGIN_CONF_D, "r") == -1)
306-		err(1, "unveil %s", _PATH_LOGIN_CONF_D);
307-	if (rule->cmd) {
308-		if (setenv("PATH", safepath, 1) == -1)
309-			err(1, "failed to set PATH '%s'", safepath);
310-	}
311-	if (unveilcommands(getenv("PATH"), cmd) == 0)
312-		goto fail;
313-
314-	if (pledge("stdio rpath getpw exec id", NULL) == -1)
315-		err(1, "pledge");
316-
317 	rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw);
318 	if (rv != 0)
319 		err(1, "getpwuid_r failed");
320 	if (targpw == NULL)
321 		errx(1, "no passwd entry for target");
322 
323-	if (setusercontext(NULL, targpw, target, LOGIN_SETGROUP |
324-	    LOGIN_SETPATH |
325-	    LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK |
326-	    LOGIN_SETUSER | LOGIN_SETENV | LOGIN_SETRTABLE) != 0)
327-		errx(1, "failed to set user context for target");
328+	if (initgroups(targpw->pw_name, targpw->pw_gid) == -1)
329+		err(1, "initgroups");
330+	if (setgid(targpw->pw_gid) == -1)
331+		err(1, "setgid");
332+	if (setuid(targpw->pw_uid) == -1)
333+		err(1, "setuid");
334 
335 	if (pledge("stdio rpath exec", NULL) == -1)
336 		err(1, "pledge");
337@@ -475,23 +421,17 @@ main(int argc, char **argv)
338 		err(1, "pledge");
339 
340 	if (!(rule->options & NOLOG)) {
341-		syslog(LOG_AUTHPRIV | LOG_INFO,
342-		    "%s ran command %s as %s from %s",
343+		syslog(LOG_INFO, "%s ran command %s as %s from %s",
344 		    mypw->pw_name, cmdline, targpw->pw_name, cwd);
345 	}
346 
347 	envp = prepenv(rule, mypw, targpw);
348 
349-	/* setusercontext set path for the next process, so reset it for us */
350 	if (rule->cmd) {
351 		if (setenv("PATH", safepath, 1) == -1)
352 			err(1, "failed to set PATH '%s'", safepath);
353-	} else {
354-		if (setenv("PATH", formerpath, 1) == -1)
355-			err(1, "failed to set PATH '%s'", formerpath);
356 	}
357 	execvpe(cmd, argv, envp);
358-fail:
359 	if (errno == ENOENT)
360 		errx(1, "%s: command not found", cmd);
361 	err(1, "%s", cmd);
362diff --git a/usr.bin/doas/doas.h b/usr.bin/doas/doas.h
363index ce6a03618ac..363e2626c23 100644
364--- a/usr.bin/doas/doas.h
365+++ b/usr.bin/doas/doas.h
366@@ -29,13 +29,17 @@ extern struct rule **rules;
367 extern size_t nrules;
368 extern int parse_error;
369 
370-extern const char *formerpath;
371+extern const char *safepath;
372 
373 struct passwd;
374 
375 char **prepenv(const struct rule *, const struct passwd *,
376     const struct passwd *);
377 
378+int openpersist(int *valid);
379+int setpersist(int fd);
380+int clearpersist(void);
381+
382 #define PERMIT	-1
383 #define DENY	2
384 
385diff --git a/usr.bin/doas/env.c b/usr.bin/doas/env.c
386index 2d93a4089b6..dc9be691955 100644
387--- a/usr.bin/doas/env.c
388+++ b/usr.bin/doas/env.c
389@@ -28,7 +28,7 @@
390 
391 #include "doas.h"
392 
393-const char *formerpath;
394+const char *safepath = "/bin";
395 
396 struct envnode {
397 	RB_ENTRY(envnode) node;
398@@ -103,7 +103,7 @@ createenv(const struct rule *rule, const struct passwd *mypw,
399 	addnode(env, "DOAS_USER", mypw->pw_name);
400 	addnode(env, "HOME", targpw->pw_dir);
401 	addnode(env, "LOGNAME", targpw->pw_name);
402-	addnode(env, "PATH", getenv("PATH"));
403+	addnode(env, "PATH", safepath);
404 	addnode(env, "SHELL", targpw->pw_shell);
405 	addnode(env, "USER", targpw->pw_name);
406 
407@@ -200,17 +200,10 @@ fillenv(struct env *env, const char **envlist)
408 		/* assign value or inherit from environ */
409 		if (eq) {
410 			val = eq + 1;
411-			if (*val == '$') {
412-				if (strcmp(val + 1, "PATH") == 0)
413-					val = formerpath;
414-				else
415-					val = getenv(val + 1);
416-			}
417+			if (*val == '$')
418+				val = getenv(val + 1);
419 		} else {
420-			if (strcmp(name, "PATH") == 0)
421-				val = formerpath;
422-			else
423-				val = getenv(name);
424+			val = getenv(name);
425 		}
426 		/* at last, we have something to insert */
427 		if (val) {
428diff --git a/usr.bin/doas/parse.y b/usr.bin/doas/parse.y
429index 604becb5445..e5fc912a9c4 100644
430--- a/usr.bin/doas/parse.y
431+++ b/usr.bin/doas/parse.y
432@@ -20,6 +20,7 @@
433 #include <ctype.h>
434 #include <limits.h>
435 #include <unistd.h>
436+#include <stdlib.h>
437 #include <stdint.h>
438 #include <stdarg.h>
439 #include <stdio.h>
440diff --git a/usr.bin/doas/persist.c b/usr.bin/doas/persist.c
441new file mode 100644
442index 00000000000..4ad1bf1efbf
443--- /dev/null
444+++ b/usr.bin/doas/persist.c
445@@ -0,0 +1,133 @@
446+#include <errno.h>
447+#include <fcntl.h>
448+#include <limits.h>
449+#include <stdio.h>
450+#include <stdlib.h>
451+#include <string.h>
452+#include <sys/stat.h>
453+#include <sys/types.h>
454+#include <time.h>
455+#include <unistd.h>
456+
457+#include "doas.h"
458+
459+#define PERSIST_DIR "/run/doas"
460+#define PERSIST_TIMEOUT 5 * 60
461+
462+static int
463+ttyid(dev_t *tty)
464+{
465+	int fd, i;
466+	char buf[BUFSIZ], *p;
467+	ssize_t n;
468+
469+	fd = open("/proc/self/stat", O_RDONLY);
470+	if (fd == -1)
471+		return -1;
472+	n = read(fd, buf, sizeof(buf) - 1);
473+	if (n >= 0)
474+		buf[n] = '\0';
475+	/* check that we read the whole file */
476+	n = read(fd, buf, 1);
477+	close(fd);
478+	if (n != 0)
479+		return -1;
480+	p = strrchr(buf, ')');
481+	if (!p)
482+		return -1;
483+	++p;
484+	/* ttr_nr is the 5th field after executable name, so skip the next 4 */
485+	for (i = 0; i < 4; ++i) {
486+		p = strchr(++p, ' ');
487+		if (!p)
488+			return -1;
489+	}
490+	*tty = strtol(p, &p, 10);
491+	if (*p != ' ')
492+		return -1;
493+	return 0;
494+}
495+
496+static int
497+persistpath(char *buf, size_t len)
498+{
499+	dev_t tty;
500+	int n;
501+
502+	if (ttyid(&tty) < 0)
503+		return -1;
504+	n = snprintf(buf, len, PERSIST_DIR "/%ju-%ju", (uintmax_t)getuid(), (uintmax_t)tty);
505+	if (n < 0 || n >= (int)len)
506+		return -1;
507+	return 0;
508+}
509+
510+int
511+openpersist(int *valid)
512+{
513+	char path[256];
514+	struct stat st;
515+	struct timespec ts;
516+	int fd;
517+
518+	if (stat(PERSIST_DIR, &st) < 0) {
519+		if (errno != ENOENT)
520+			return -1;
521+		if (mkdir(PERSIST_DIR, 0700) < 0)
522+			return -1;
523+	} else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
524+		return -1;
525+	}
526+	if (persistpath(path, sizeof(path)) < 0)
527+		return -1;
528+	fd = open(path, O_RDONLY);
529+	if (fd == -1) {
530+		char tmp[256];
531+		struct timespec ts[2] = { { .tv_nsec = UTIME_OMIT }, { 0 } };
532+		int n;
533+
534+		n = snprintf(tmp, sizeof(tmp), PERSIST_DIR "/.tmp-%d", getpid());
535+		if (n < 0 || n >= (int)sizeof(tmp))
536+			return -1;
537+		fd = open(tmp, O_RDONLY | O_CREAT | O_EXCL, 0);
538+		if (fd == -1)
539+			return -1;
540+		if (futimens(fd, ts) < 0 || rename(tmp, path) < 0) {
541+			close(fd);
542+			unlink(tmp);
543+			return -1;
544+		}
545+		*valid = 0;
546+	} else {
547+		*valid = clock_gettime(CLOCK_BOOTTIME, &ts) == 0 &&
548+		         fstat(fd, &st) == 0 &&
549+		         (ts.tv_sec < st.st_mtim.tv_sec ||
550+		          (ts.tv_sec == st.st_mtim.tv_sec && ts.tv_nsec < st.st_mtim.tv_nsec)) &&
551+		         st.st_mtime - ts.tv_sec <= PERSIST_TIMEOUT;
552+	}
553+	return fd;
554+}
555+
556+int
557+setpersist(int fd)
558+{
559+	struct timespec times[2];
560+
561+	if (clock_gettime(CLOCK_BOOTTIME, &times[1]) < 0)
562+		return -1;
563+	times[0].tv_nsec = UTIME_OMIT;
564+	times[1].tv_sec += PERSIST_TIMEOUT;
565+	return futimens(fd, times);
566+}
567+
568+int
569+clearpersist(void)
570+{
571+	char path[256];
572+
573+	if (persistpath(path, sizeof(path)) < 0)
574+		return -1;
575+	if (unlink(path) < 0 && errno != ENOENT)
576+		return -1;
577+	return 0;
578+}
579-- 
5802.49.0
581