1 /* $OpenBSD: su.c,v 1.81 2020/07/08 15:36:35 jca Exp $ */ 2 3 /* 4 * Copyright (c) 1988 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/time.h> 33 #include <sys/resource.h> 34 35 #include <err.h> 36 #include <errno.h> 37 #include <grp.h> 38 #include <login_cap.h> 39 #include <paths.h> 40 #include <pwd.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <syslog.h> 45 #include <unistd.h> 46 #include <limits.h> 47 #include <utmp.h> 48 #include <stdarg.h> 49 #include <bsd_auth.h> 50 51 char *getloginname(void); 52 char *ontty(void); 53 int chshell(const char *); 54 int verify_user(char *, struct passwd *, char *, login_cap_t *, 55 auth_session_t *); 56 void usage(void); 57 void auth_err(auth_session_t *, int, const char *, ...); 58 void auth_errx(auth_session_t *, int, const char *, ...); 59 60 int 61 main(int argc, char **argv) 62 { 63 int asme = 0, asthem = 0, ch, fastlogin = 0, emlogin = 0, prio; 64 int altshell = 0, homeless = 0; 65 char *user, *shell = NULL, *avshell, *username, **np; 66 char *class = NULL, *style = NULL, *p; 67 enum { UNSET, YES, NO } iscsh = UNSET; 68 char avshellbuf[PATH_MAX]; 69 extern char **environ; 70 auth_session_t *as; 71 struct passwd *pwd; 72 login_cap_t *lc; 73 uid_t ruid; 74 u_int flags; 75 76 if (pledge("stdio unveil rpath getpw proc exec id", NULL) == -1) 77 err(1, "pledge"); 78 79 while ((ch = getopt(argc, argv, "a:c:fKLlms:-")) != -1) 80 switch (ch) { 81 case 'a': 82 if (style) 83 usage(); 84 style = optarg; 85 break; 86 case 'c': 87 if (class) 88 usage(); 89 class = optarg; 90 break; 91 case 'f': 92 fastlogin = 1; 93 break; 94 case 'K': 95 if (style) 96 usage(); 97 style = "passwd"; 98 break; 99 case 'L': 100 emlogin = 1; 101 break; 102 case 'l': 103 case '-': 104 asme = 0; 105 asthem = 1; 106 break; 107 case 'm': 108 asme = 1; 109 asthem = 0; 110 break; 111 case 's': 112 altshell = 1; 113 shell = optarg; 114 break; 115 default: 116 usage(); 117 } 118 argv += optind; 119 120 errno = 0; 121 prio = getpriority(PRIO_PROCESS, 0); 122 if (errno) 123 prio = 0; 124 setpriority(PRIO_PROCESS, 0, -2); 125 openlog("su", LOG_CONS, 0); 126 127 if ((as = auth_open()) == NULL) { 128 syslog(LOG_ERR, "auth_open: %m"); 129 err(1, "unable to initialize BSD authentication"); 130 } 131 auth_setoption(as, "login", "yes"); 132 133 /* get current login name and shell */ 134 ruid = getuid(); 135 username = getlogin(); 136 137 if (ruid && class) 138 auth_errx(as, 1, "only the superuser may specify a login class"); 139 140 if (ruid && altshell) 141 auth_errx(as, 1, "only the superuser may specify a login shell"); 142 143 if (username != NULL) 144 auth_setoption(as, "invokinguser", username); 145 146 if (username == NULL || (pwd = getpwnam(username)) == NULL || 147 pwd->pw_uid != ruid) 148 pwd = getpwuid(ruid); 149 if (pwd == NULL) 150 auth_errx(as, 1, "who are you?"); 151 if ((username = strdup(pwd->pw_name)) == NULL) 152 auth_err(as, 1, NULL); 153 if (asme && !altshell) { 154 if (pwd->pw_shell && *pwd->pw_shell) { 155 if ((shell = strdup(pwd->pw_shell)) == NULL) 156 auth_err(as, 1, NULL); 157 } else { 158 shell = _PATH_BSHELL; 159 iscsh = NO; 160 } 161 } 162 163 if (unveil(_PATH_LOGIN_CONF, "r") == -1) 164 err(1, "unveil"); 165 if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1) 166 err(1, "unveil"); 167 if (unveil(_PATH_AUTHPROGDIR, "x") == -1) 168 err(1, "unveil"); 169 if (unveil(_PATH_SHELLS, "r") == -1) 170 err(1, "unveil"); 171 if (unveil(_PATH_DEVDB, "r") == -1) 172 err(1, "unveil"); 173 174 for (;;) { 175 char *pw_class = class; 176 177 /* get target user, default to root unless in -L mode */ 178 if (*argv) { 179 user = *argv; 180 } else if (emlogin) { 181 if ((user = getloginname()) == NULL) { 182 auth_close(as); 183 exit(1); 184 } 185 } else { 186 user = "root"; 187 } 188 /* style may be specified as part of the username */ 189 if ((p = strchr(user, ':')) != NULL) { 190 *p++ = '\0'; 191 style = p; /* XXX overrides -a flag */ 192 } 193 194 /* 195 * Clean and setup our current authentication session. 196 * Note that options *are* not cleared. 197 */ 198 auth_clean(as); 199 if (auth_setitem(as, AUTHV_INTERACTIVE, "True") != 0 || 200 auth_setitem(as, AUTHV_NAME, user) != 0) 201 auth_err(as, 1, NULL); 202 if ((user = auth_getitem(as, AUTHV_NAME)) == NULL) 203 auth_errx(as, 1, "internal error"); 204 if (auth_setpwd(as, NULL) || (pwd = auth_getpwd(as)) == NULL) { 205 if (emlogin) 206 pwd = NULL; 207 else 208 auth_errx(as, 1, "unknown login %s", user); 209 } 210 211 /* If the user specified a login class, use it */ 212 if (pw_class == NULL && pwd != NULL) 213 pw_class = pwd->pw_class; 214 if ((lc = login_getclass(pw_class)) == NULL) 215 auth_errx(as, 1, "no such login class: %s", 216 pw_class ? pw_class : LOGIN_DEFCLASS); 217 218 if ((ruid == 0 && !emlogin) || 219 verify_user(username, pwd, style, lc, as) == 0) 220 break; 221 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", 222 username, user, ontty()); 223 if (!emlogin) { 224 fprintf(stderr, "Sorry\n"); 225 auth_close(as); 226 exit(1); 227 } 228 fprintf(stderr, "Login incorrect\n"); 229 } 230 if (pwd == NULL) 231 auth_errx(as, 1, "internal error"); 232 233 if (pledge("stdio unveil rpath getpw exec id", NULL) == -1) 234 err(1, "pledge"); 235 236 if (!altshell) { 237 if (asme) { 238 /* must be root to override non-std target shell */ 239 if (ruid && !chshell(pwd->pw_shell)) 240 auth_errx(as, 1, "permission denied (shell)."); 241 } else if (pwd->pw_shell && *pwd->pw_shell) { 242 if ((shell = strdup(pwd->pw_shell)) == NULL) 243 auth_err(as, 1, NULL); 244 iscsh = UNSET; 245 } else { 246 shell = _PATH_BSHELL; 247 iscsh = NO; 248 } 249 } 250 251 if (unveil(shell, "x") == -1) 252 err(1, "unveil"); 253 if (unveil(pwd->pw_dir, "r") == -1) 254 err(1, "unveil"); 255 256 if ((p = strrchr(shell, '/'))) 257 avshell = p+1; 258 else 259 avshell = shell; 260 261 /* if we're forking a csh, we want to slightly muck the args */ 262 if (iscsh == UNSET) 263 iscsh = strcmp(avshell, "csh") ? NO : YES; 264 265 if (!asme) { 266 if (asthem) { 267 p = getenv("TERM"); 268 if ((environ = calloc(1, sizeof (char *))) == NULL) 269 auth_errx(as, 1, "calloc"); 270 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH)) 271 auth_err(as, 1, "unable to set user context"); 272 if (p && setenv("TERM", p, 1) == -1) 273 auth_err(as, 1, "unable to set environment"); 274 275 setegid(pwd->pw_gid); 276 seteuid(pwd->pw_uid); 277 278 homeless = chdir(pwd->pw_dir); 279 if (homeless == -1) { 280 if (login_getcapbool(lc, "requirehome", 0)) { 281 auth_err(as, 1, "%s", pwd->pw_dir); 282 } else { 283 if (unveil("/", "r") == -1) 284 err(1, "unveil"); 285 printf("No home directory %s!\n", pwd->pw_dir); 286 printf("Logging in with home = \"/\".\n"); 287 if (chdir("/") == -1) 288 auth_err(as, 1, "/"); 289 } 290 } 291 setegid(0); /* XXX use a saved gid instead? */ 292 seteuid(0); 293 } else if (pwd->pw_uid == 0) { 294 if (setusercontext(lc, 295 pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK)) 296 auth_err(as, 1, "unable to set user context"); 297 } 298 if (asthem || pwd->pw_uid) { 299 if (setenv("LOGNAME", pwd->pw_name, 1) == -1 || 300 setenv("USER", pwd->pw_name, 1) == -1) 301 auth_err(as, 1, "unable to set environment"); 302 } 303 if (setenv("HOME", homeless ? "/" : pwd->pw_dir, 1) == -1 || 304 setenv("SHELL", shell, 1) == -1) 305 auth_err(as, 1, "unable to set environment"); 306 } else if (altshell) { 307 if (setenv("SHELL", shell, 1) == -1) 308 auth_err(as, 1, "unable to set environment"); 309 } 310 if (pledge("stdio rpath getpw exec id", NULL) == -1) 311 err(1, "pledge"); 312 313 np = *argv ? argv : argv - 1; 314 315 if (iscsh == YES) { 316 if (fastlogin) 317 *np-- = "-f"; 318 if (asme) 319 *np-- = "-m"; 320 321 if (asthem) 322 avshellbuf[0] = '-'; 323 else { 324 /* csh strips the first character... */ 325 avshellbuf[0] = '_'; 326 } 327 strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 328 avshell = avshellbuf; 329 } else if (asthem && !fastlogin) { 330 avshellbuf[0] = '-'; 331 strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 332 avshell = avshellbuf; 333 } 334 335 *np = avshell; 336 337 if (ruid != 0) 338 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s", 339 username, user, ontty()); 340 341 setpriority(PRIO_PROCESS, 0, prio); 342 if (emlogin) { 343 flags = LOGIN_SETALL & ~LOGIN_SETPATH; 344 /* 345 * Only call setlogin() if this process is a session leader. 346 * In practice, this means the login name will be set only if 347 * we are exec'd by a shell. This is important because 348 * otherwise the parent shell's login name would change too. 349 */ 350 if (getsid(0) != getpid()) 351 flags &= ~LOGIN_SETLOGIN; 352 } else { 353 flags = LOGIN_SETRESOURCES|LOGIN_SETGROUP|LOGIN_SETUSER; 354 if (asthem) 355 flags |= LOGIN_SETENV|LOGIN_SETPRIORITY|LOGIN_SETUMASK; 356 } 357 if (setusercontext(lc, pwd, pwd->pw_uid, flags) != 0) 358 auth_err(as, 1, "unable to set user context"); 359 360 if (pledge("stdio rpath exec", NULL) == -1) 361 err(1, "pledge"); 362 363 if (pwd->pw_uid && auth_approval(as, lc, pwd->pw_name, "su") == 0) 364 auth_err(as, 1, "approval failure"); 365 auth_close(as); 366 367 execv(shell, np); 368 err(1, "%s", shell); 369 } 370 371 int 372 verify_user(char *from, struct passwd *pwd, char *style, 373 login_cap_t *lc, auth_session_t *as) 374 { 375 struct group *gr; 376 char **g, *cp; 377 int authok; 378 379 /* 380 * If we are trying to become root and the default style 381 * is being used, don't bother to look it up (we might be 382 * be su'ing up to fix /etc/login.conf) 383 */ 384 if ((pwd == NULL || pwd->pw_uid != 0 || style == NULL || 385 strcmp(style, LOGIN_DEFSTYLE) != 0) && 386 (style = login_getstyle(lc, style, "auth-su")) == NULL) 387 auth_errx(as, 1, "invalid authentication type"); 388 389 /* 390 * Let the authentication program know whether they are 391 * in group wheel or not (if trying to become super user) 392 */ 393 if (pwd != NULL && pwd->pw_uid == 0 && (gr = getgrgid(0)) != NULL && 394 gr->gr_mem != NULL && *(gr->gr_mem) != NULL) { 395 for (g = gr->gr_mem; *g; ++g) { 396 if (strcmp(from, *g) == 0) { 397 auth_setoption(as, "wheel", "yes"); 398 break; 399 } 400 } 401 if (!*g) 402 auth_setoption(as, "wheel", "no"); 403 } 404 405 auth_verify(as, style, NULL, lc->lc_class, (char *)NULL); 406 authok = auth_getstate(as); 407 if ((authok & AUTH_ALLOW) == 0) { 408 if ((cp = auth_getvalue(as, "errormsg")) != NULL) 409 fprintf(stderr, "%s\n", cp); 410 return(1); 411 } 412 return(0); 413 } 414 415 int 416 chshell(const char *sh) 417 { 418 char *cp; 419 int found = 0; 420 421 setusershell(); 422 while ((cp = getusershell()) != NULL) { 423 if (strcmp(cp, sh) == 0) { 424 found = 1; 425 break; 426 } 427 } 428 endusershell(); 429 return (found); 430 } 431 432 char * 433 ontty(void) 434 { 435 static char buf[PATH_MAX + 4]; 436 char *p; 437 438 buf[0] = 0; 439 if ((p = ttyname(STDERR_FILENO))) 440 snprintf(buf, sizeof(buf), " on %s", p); 441 return (buf); 442 } 443 444 /* 445 * Allow for a '.' and 16 characters for any instance as well as 446 * space for a ':' and 16 characters defining the authentication type. 447 */ 448 #define NBUFSIZ (UT_NAMESIZE + 1 + 16 + 1 + 16) 449 450 char * 451 getloginname(void) 452 { 453 static char nbuf[NBUFSIZ], *p; 454 int ch; 455 456 for (;;) { 457 printf("login: "); 458 for (p = nbuf; (ch = getchar()) != '\n'; ) { 459 if (ch == EOF) 460 return (NULL); 461 if (p < nbuf + (NBUFSIZ - 1)) 462 *p++ = ch; 463 } 464 if (p > nbuf) { 465 if (nbuf[0] == '-') { 466 fprintf(stderr, 467 "login names may not start with '-'.\n"); 468 } else { 469 *p = '\0'; 470 break; 471 } 472 } 473 } 474 return (nbuf); 475 } 476 477 void 478 usage(void) 479 { 480 extern char *__progname; 481 482 fprintf(stderr, "usage: %s [-fKLlm] [-a auth-type] [-c login-class] " 483 "[-s login-shell]\n" 484 "%-*s[login [shell arguments]]\n", __progname, 485 (int)strlen(__progname) + 8, ""); 486 exit(1); 487 } 488 489 void 490 auth_err(auth_session_t *as, int eval, const char *fmt, ...) 491 { 492 va_list ap; 493 494 va_start(ap, fmt); 495 vwarn(fmt, ap); 496 va_end(ap); 497 auth_close(as); 498 exit(eval); 499 } 500 501 void 502 auth_errx(auth_session_t *as, int eval, const char *fmt, ...) 503 { 504 va_list ap; 505 506 va_start(ap, fmt); 507 vwarnx(fmt, ap); 508 va_end(ap); 509 auth_close(as); 510 exit(eval); 511 } 512