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