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