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