0015-doas-Port-to-linux-musl.patch (14339B)
1 From 8fa1e97f6927bf1afddb5923fff3d29c3389817d Mon Sep 17 00:00:00 2001 2 From: Michael Forney <mforney@mforney.org> 3 Date: Sun, 26 Feb 2017 16:50:55 -0800 4 Subject: [PATCH] doas: Port to linux/musl 5 6 Remove -a login style option and BSD authentication. Instead, compare 7 against shadow file. 8 9 Use timestamp files in /run/doas instead of TIOC*VERAUTH to implement 10 persist. 11 12 Use initgroups/setgid/setuid instead of setusercontext. 13 14 Provide UID_MAX and GID_MAX defaults. 15 16 Use LOGIN_NAME_MAX instead of _PW_NAME_LEN. 17 18 Remove call to closefrom. 19 20 Replace calls to errc with err after setting errno. 21 22 Call openlog at start to set syslog identity. 23 24 Remove unveil/pledge since they aren't supported on Linux. 25 26 Simplify handling of PATH in the environment since we don't have 27 login.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 38 diff --git a/usr.bin/doas/doas.1 b/usr.bin/doas/doas.1 39 index 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 , 65 diff --git a/usr.bin/doas/doas.c b/usr.bin/doas/doas.c 66 index 8b684d6006c..27d7b01014e 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 @@ -200,16 +207,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 @@ -222,14 +241,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 @@ -238,79 +255,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 @@ -323,28 +297,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 @@ -414,50 +380,30 @@ main(int argc, char **argv) 277 cmd = argv[0]; 278 if (!permit(uid, groups, ngroups, &rule, target, cmd, 279 (const char **)argv + 1)) { 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 @@ -471,23 +417,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); 362 diff --git a/usr.bin/doas/doas.h b/usr.bin/doas/doas.h 363 index b98fe353b18..6567625c471 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 385 diff --git a/usr.bin/doas/env.c b/usr.bin/doas/env.c 386 index 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) { 428 diff --git a/usr.bin/doas/parse.y b/usr.bin/doas/parse.y 429 index 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> 440 diff --git a/usr.bin/doas/persist.c b/usr.bin/doas/persist.c 441 new file mode 100644 442 index 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 -- 580 2.37.3 581