1 /* $OpenBSD: su.c,v 1.82 2020/08/17 18:12:12 semarie 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 if (unveil(_PATH_NOLOGIN, "r") == -1) 174 err(1, "unveil"); 175 176 for (;;) { 177 char *pw_class = class; 178 179 /* get target user, default to root unless in -L mode */ 180 if (*argv) { 181 user = *argv; 182 } else if (emlogin) { 183 if ((user = getloginname()) == NULL) { 184 auth_close(as); 185 exit(1); 186 } 187 } else { 188 user = "root"; 189 } 190 /* style may be specified as part of the username */ 191 if ((p = strchr(user, ':')) != NULL) { 192 *p++ = '\0'; 193 style = p; /* XXX overrides -a flag */ 194 } 195 196 /* 197 * Clean and setup our current authentication session. 198 * Note that options *are* not cleared. 199 */ 200 auth_clean(as); 201 if (auth_setitem(as, AUTHV_INTERACTIVE, "True") != 0 || 202 auth_setitem(as, AUTHV_NAME, user) != 0) 203 auth_err(as, 1, NULL); 204 if ((user = auth_getitem(as, AUTHV_NAME)) == NULL) 205 auth_errx(as, 1, "internal error"); 206 if (auth_setpwd(as, NULL) || (pwd = auth_getpwd(as)) == NULL) { 207 if (emlogin) 208 pwd = NULL; 209 else 210 auth_errx(as, 1, "unknown login %s", user); 211 } 212 213 /* If the user specified a login class, use it */ 214 if (pw_class == NULL && pwd != NULL) 215 pw_class = pwd->pw_class; 216 if ((lc = login_getclass(pw_class)) == NULL) 217 auth_errx(as, 1, "no such login class: %s", 218 pw_class ? pw_class : LOGIN_DEFCLASS); 219 220 if ((ruid == 0 && !emlogin) || 221 verify_user(username, pwd, style, lc, as) == 0) 222 break; 223 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", 224 username, user, ontty()); 225 if (!emlogin) { 226 fprintf(stderr, "Sorry\n"); 227 auth_close(as); 228 exit(1); 229 } 230 fprintf(stderr, "Login incorrect\n"); 231 } 232 if (pwd == NULL) 233 auth_errx(as, 1, "internal error"); 234 235 if (pledge("stdio unveil rpath getpw exec id", NULL) == -1) 236 err(1, "pledge"); 237 238 if (!altshell) { 239 if (asme) { 240 /* must be root to override non-std target shell */ 241 if (ruid && !chshell(pwd->pw_shell)) 242 auth_errx(as, 1, "permission denied (shell)."); 243 } else if (pwd->pw_shell && *pwd->pw_shell) { 244 if ((shell = strdup(pwd->pw_shell)) == NULL) 245 auth_err(as, 1, NULL); 246 iscsh = UNSET; 247 } else { 248 shell = _PATH_BSHELL; 249 iscsh = NO; 250 } 251 } 252 253 if (unveil(shell, "x") == -1) 254 err(1, "unveil"); 255 if (unveil(pwd->pw_dir, "r") == -1) 256 err(1, "unveil"); 257 258 if ((p = strrchr(shell, '/'))) 259 avshell = p+1; 260 else 261 avshell = shell; 262 263 /* if we're forking a csh, we want to slightly muck the args */ 264 if (iscsh == UNSET) 265 iscsh = strcmp(avshell, "csh") ? NO : YES; 266 267 if (!asme) { 268 if (asthem) { 269 p = getenv("TERM"); 270 if ((environ = calloc(1, sizeof (char *))) == NULL) 271 auth_errx(as, 1, "calloc"); 272 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH)) 273 auth_err(as, 1, "unable to set user context"); 274 if (p && setenv("TERM", p, 1) == -1) 275 auth_err(as, 1, "unable to set environment"); 276 277 setegid(pwd->pw_gid); 278 seteuid(pwd->pw_uid); 279 280 homeless = chdir(pwd->pw_dir); 281 if (homeless == -1) { 282 if (login_getcapbool(lc, "requirehome", 0)) { 283 auth_err(as, 1, "%s", pwd->pw_dir); 284 } else { 285 if (unveil("/", "r") == -1) 286 err(1, "unveil"); 287 printf("No home directory %s!\n", pwd->pw_dir); 288 printf("Logging in with home = \"/\".\n"); 289 if (chdir("/") == -1) 290 auth_err(as, 1, "/"); 291 } 292 } 293 setegid(0); /* XXX use a saved gid instead? */ 294 seteuid(0); 295 } else if (pwd->pw_uid == 0) { 296 if (setusercontext(lc, 297 pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK)) 298 auth_err(as, 1, "unable to set user context"); 299 } 300 if (asthem || pwd->pw_uid) { 301 if (setenv("LOGNAME", pwd->pw_name, 1) == -1 || 302 setenv("USER", pwd->pw_name, 1) == -1) 303 auth_err(as, 1, "unable to set environment"); 304 } 305 if (setenv("HOME", homeless ? "/" : pwd->pw_dir, 1) == -1 || 306 setenv("SHELL", shell, 1) == -1) 307 auth_err(as, 1, "unable to set environment"); 308 } else if (altshell) { 309 if (setenv("SHELL", shell, 1) == -1) 310 auth_err(as, 1, "unable to set environment"); 311 } 312 if (pledge("stdio rpath getpw exec id", NULL) == -1) 313 err(1, "pledge"); 314 315 np = *argv ? argv : argv - 1; 316 317 if (iscsh == YES) { 318 if (fastlogin) 319 *np-- = "-f"; 320 if (asme) 321 *np-- = "-m"; 322 323 if (asthem) 324 avshellbuf[0] = '-'; 325 else { 326 /* csh strips the first character... */ 327 avshellbuf[0] = '_'; 328 } 329 strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 330 avshell = avshellbuf; 331 } else if (asthem && !fastlogin) { 332 avshellbuf[0] = '-'; 333 strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1); 334 avshell = avshellbuf; 335 } 336 337 *np = avshell; 338 339 if (ruid != 0) 340 syslog(LOG_NOTICE|LOG_AUTH, "%s to %s%s", 341 username, user, ontty()); 342 343 setpriority(PRIO_PROCESS, 0, prio); 344 if (emlogin) { 345 flags = LOGIN_SETALL & ~LOGIN_SETPATH; 346 /* 347 * Only call setlogin() if this process is a session leader. 348 * In practice, this means the login name will be set only if 349 * we are exec'd by a shell. This is important because 350 * otherwise the parent shell's login name would change too. 351 */ 352 if (getsid(0) != getpid()) 353 flags &= ~LOGIN_SETLOGIN; 354 } else { 355 flags = LOGIN_SETRESOURCES|LOGIN_SETGROUP|LOGIN_SETUSER; 356 if (asthem) 357 flags |= LOGIN_SETENV|LOGIN_SETPRIORITY|LOGIN_SETUMASK; 358 } 359 if (setusercontext(lc, pwd, pwd->pw_uid, flags) != 0) 360 auth_err(as, 1, "unable to set user context"); 361 362 if (pledge("stdio rpath exec", NULL) == -1) 363 err(1, "pledge"); 364 365 if (pwd->pw_uid && auth_approval(as, lc, pwd->pw_name, "su") == 0) 366 auth_err(as, 1, "approval failure"); 367 auth_close(as); 368 369 execv(shell, np); 370 err(1, "%s", shell); 371 } 372 373 int 374 verify_user(char *from, struct passwd *pwd, char *style, 375 login_cap_t *lc, auth_session_t *as) 376 { 377 struct group *gr; 378 char **g, *cp; 379 int authok; 380 381 /* 382 * If we are trying to become root and the default style 383 * is being used, don't bother to look it up (we might be 384 * be su'ing up to fix /etc/login.conf) 385 */ 386 if ((pwd == NULL || pwd->pw_uid != 0 || style == NULL || 387 strcmp(style, LOGIN_DEFSTYLE) != 0) && 388 (style = login_getstyle(lc, style, "auth-su")) == NULL) 389 auth_errx(as, 1, "invalid authentication type"); 390 391 /* 392 * Let the authentication program know whether they are 393 * in group wheel or not (if trying to become super user) 394 */ 395 if (pwd != NULL && pwd->pw_uid == 0 && (gr = getgrgid(0)) != NULL && 396 gr->gr_mem != NULL && *(gr->gr_mem) != NULL) { 397 for (g = gr->gr_mem; *g; ++g) { 398 if (strcmp(from, *g) == 0) { 399 auth_setoption(as, "wheel", "yes"); 400 break; 401 } 402 } 403 if (!*g) 404 auth_setoption(as, "wheel", "no"); 405 } 406 407 auth_verify(as, style, NULL, lc->lc_class, (char *)NULL); 408 authok = auth_getstate(as); 409 if ((authok & AUTH_ALLOW) == 0) { 410 if ((cp = auth_getvalue(as, "errormsg")) != NULL) 411 fprintf(stderr, "%s\n", cp); 412 return(1); 413 } 414 return(0); 415 } 416 417 int 418 chshell(const char *sh) 419 { 420 char *cp; 421 int found = 0; 422 423 setusershell(); 424 while ((cp = getusershell()) != NULL) { 425 if (strcmp(cp, sh) == 0) { 426 found = 1; 427 break; 428 } 429 } 430 endusershell(); 431 return (found); 432 } 433 434 char * 435 ontty(void) 436 { 437 static char buf[PATH_MAX + 4]; 438 char *p; 439 440 buf[0] = 0; 441 if ((p = ttyname(STDERR_FILENO))) 442 snprintf(buf, sizeof(buf), " on %s", p); 443 return (buf); 444 } 445 446 /* 447 * Allow for a '.' and 16 characters for any instance as well as 448 * space for a ':' and 16 characters defining the authentication type. 449 */ 450 #define NBUFSIZ (UT_NAMESIZE + 1 + 16 + 1 + 16) 451 452 char * 453 getloginname(void) 454 { 455 static char nbuf[NBUFSIZ], *p; 456 int ch; 457 458 for (;;) { 459 printf("login: "); 460 for (p = nbuf; (ch = getchar()) != '\n'; ) { 461 if (ch == EOF) 462 return (NULL); 463 if (p < nbuf + (NBUFSIZ - 1)) 464 *p++ = ch; 465 } 466 if (p > nbuf) { 467 if (nbuf[0] == '-') { 468 fprintf(stderr, 469 "login names may not start with '-'.\n"); 470 } else { 471 *p = '\0'; 472 break; 473 } 474 } 475 } 476 return (nbuf); 477 } 478 479 void 480 usage(void) 481 { 482 extern char *__progname; 483 484 fprintf(stderr, "usage: %s [-fKLlm] [-a auth-type] [-c login-class] " 485 "[-s login-shell]\n" 486 "%-*s[login [shell arguments]]\n", __progname, 487 (int)strlen(__progname) + 8, ""); 488 exit(1); 489 } 490 491 void 492 auth_err(auth_session_t *as, int eval, const char *fmt, ...) 493 { 494 va_list ap; 495 496 va_start(ap, fmt); 497 vwarn(fmt, ap); 498 va_end(ap); 499 auth_close(as); 500 exit(eval); 501 } 502 503 void 504 auth_errx(auth_session_t *as, int eval, const char *fmt, ...) 505 { 506 va_list ap; 507 508 va_start(ap, fmt); 509 vwarnx(fmt, ap); 510 va_end(ap); 511 auth_close(as); 512 exit(eval); 513 } 514