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, ×[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