1/* vi: set sw=4 ts=4: */ 2/* 3 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. 4 */ 5#include "libbb.h" 6#include <syslog.h> 7#if ENABLE_FEATURE_UTMP 8# include <utmp.h> /* USER_PROCESS */ 9#endif 10#include <sys/resource.h> 11 12#if ENABLE_SELINUX 13# include <selinux/selinux.h> /* for is_selinux_enabled() */ 14# include <selinux/get_context_list.h> /* for get_default_context() */ 15# include <selinux/flask.h> /* for security class definitions */ 16#endif 17 18#if ENABLE_PAM 19/* PAM may include <locale.h>. We may need to undefine bbox's stub define: */ 20# undef setlocale 21/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx. 22 * Apparently they like to confuse people. */ 23# include <security/pam_appl.h> 24# include <security/pam_misc.h> 25static const struct pam_conv conv = { 26 misc_conv, 27 NULL 28}; 29#endif 30 31enum { 32 TIMEOUT = 60, 33 EMPTY_USERNAME_COUNT = 10, 34 USERNAME_SIZE = 32, 35 TTYNAME_SIZE = 32, 36}; 37 38static char* short_tty; 39 40#if ENABLE_FEATURE_NOLOGIN 41static void die_if_nologin(void) 42{ 43 FILE *fp; 44 int c; 45 int empty = 1; 46 47 fp = fopen_for_read("/etc/nologin"); 48 if (!fp) /* assuming it does not exist */ 49 return; 50 51 while ((c = getc(fp)) != EOF) { 52 if (c == '\n') 53 bb_putchar('\r'); 54 bb_putchar(c); 55 empty = 0; 56 } 57 if (empty) 58 puts("\r\nSystem closed for routine maintenance\r"); 59 60 fclose(fp); 61 fflush_all(); 62 /* Users say that they do need this prior to exit: */ 63 tcdrain(STDOUT_FILENO); 64 exit(EXIT_FAILURE); 65} 66#else 67# define die_if_nologin() ((void)0) 68#endif 69 70#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM 71static int check_securetty(void) 72{ 73 char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */ 74 parser_t *parser = config_open2("/etc/securetty", fopen_for_read); 75 while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) { 76 if (strcmp(buf, short_tty) == 0) 77 break; 78 buf = NULL; 79 } 80 config_close(parser); 81 /* buf != NULL here if config file was not found, empty 82 * or line was found which equals short_tty */ 83 return buf != NULL; 84} 85#else 86static ALWAYS_INLINE int check_securetty(void) { return 1; } 87#endif 88 89#if ENABLE_SELINUX 90static void initselinux(char *username, char *full_tty, 91 security_context_t *user_sid) 92{ 93 security_context_t old_tty_sid, new_tty_sid; 94 95 if (!is_selinux_enabled()) 96 return; 97 98 if (get_default_context(username, NULL, user_sid)) { 99 bb_error_msg_and_die("can't get SID for %s", username); 100 } 101 if (getfilecon(full_tty, &old_tty_sid) < 0) { 102 bb_perror_msg_and_die("getfilecon(%s) failed", full_tty); 103 } 104 if (security_compute_relabel(*user_sid, old_tty_sid, 105 SECCLASS_CHR_FILE, &new_tty_sid) != 0) { 106 bb_perror_msg_and_die("security_change_sid(%s) failed", full_tty); 107 } 108 if (setfilecon(full_tty, new_tty_sid) != 0) { 109 bb_perror_msg_and_die("chsid(%s, %s) failed", full_tty, new_tty_sid); 110 } 111} 112#endif 113 114#if ENABLE_LOGIN_SCRIPTS 115static void run_login_script(struct passwd *pw, char *full_tty) 116{ 117 char *t_argv[2]; 118 119 t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT"); 120 if (t_argv[0]) { 121 t_argv[1] = NULL; 122 xsetenv("LOGIN_TTY", full_tty); 123 xsetenv("LOGIN_USER", pw->pw_name); 124 xsetenv("LOGIN_UID", utoa(pw->pw_uid)); 125 xsetenv("LOGIN_GID", utoa(pw->pw_gid)); 126 xsetenv("LOGIN_SHELL", pw->pw_shell); 127 spawn_and_wait(t_argv); /* NOMMU-friendly */ 128 unsetenv("LOGIN_TTY"); 129 unsetenv("LOGIN_USER"); 130 unsetenv("LOGIN_UID"); 131 unsetenv("LOGIN_GID"); 132 unsetenv("LOGIN_SHELL"); 133 } 134} 135#else 136void run_login_script(struct passwd *pw, char *full_tty); 137#endif 138 139static void get_username_or_die(char *buf, int size_buf) 140{ 141 int c, cntdown; 142 143 cntdown = EMPTY_USERNAME_COUNT; 144 prompt: 145 print_login_prompt(); 146 /* skip whitespace */ 147 do { 148 c = getchar(); 149 if (c == EOF) 150 exit(EXIT_FAILURE); 151 if (c == '\n') { 152 if (!--cntdown) 153 exit(EXIT_FAILURE); 154 goto prompt; 155 } 156 } while (isspace(c)); /* maybe isblank? */ 157 158 *buf++ = c; 159 if (!fgets(buf, size_buf-2, stdin)) 160 exit(EXIT_FAILURE); 161 if (!strchr(buf, '\n')) 162 exit(EXIT_FAILURE); 163 while ((unsigned char)*buf > ' ') 164 buf++; 165 *buf = '\0'; 166} 167 168static void motd(void) 169{ 170 int fd; 171 172 fd = open(bb_path_motd_file, O_RDONLY); 173 if (fd >= 0) { 174 fflush_all(); 175 bb_copyfd_eof(fd, STDOUT_FILENO); 176 close(fd); 177 } 178} 179 180static void alarm_handler(int sig UNUSED_PARAM) 181{ 182 /* This is the escape hatch! Poor serial line users and the like 183 * arrive here when their connection is broken. 184 * We don't want to block here */ 185 ndelay_on(1); 186 printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT); 187 fflush_all(); 188 /* unix API is brain damaged regarding O_NONBLOCK, 189 * we should undo it, or else we can affect other processes */ 190 ndelay_off(1); 191 _exit(EXIT_SUCCESS); 192} 193 194int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 195int login_main(int argc UNUSED_PARAM, char **argv) 196{ 197 enum { 198 LOGIN_OPT_f = (1<<0), 199 LOGIN_OPT_h = (1<<1), 200 LOGIN_OPT_p = (1<<2), 201 }; 202 char *fromhost; 203 char username[USERNAME_SIZE]; 204 const char *shell; 205 int run_by_root; 206 unsigned opt; 207 int count = 0; 208 struct passwd *pw; 209 char *opt_host = NULL; 210 char *opt_user = opt_user; /* for compiler */ 211 char *full_tty; 212 IF_SELINUX(security_context_t user_sid = NULL;) 213#if ENABLE_PAM 214 int pamret; 215 pam_handle_t *pamh; 216 const char *pamuser; 217 const char *failed_msg; 218 struct passwd pwdstruct; 219 char pwdbuf[256]; 220#endif 221 222 username[0] = '\0'; 223 signal(SIGALRM, alarm_handler); 224 alarm(TIMEOUT); 225 226 /* More of suid paranoia if called by non-root: */ 227 /* Clear dangerous stuff, set PATH */ 228 run_by_root = !sanitize_env_if_suid(); 229 230 /* Mandatory paranoia for suid applet: 231 * ensure that fd# 0,1,2 are opened (at least to /dev/null) 232 * and any extra open fd's are closed. 233 * (The name of the function is misleading. Not daemonizing here.) */ 234 bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL); 235 236 opt = getopt32(argv, "f:h:p", &opt_user, &opt_host); 237 if (opt & LOGIN_OPT_f) { 238 if (!run_by_root) 239 bb_error_msg_and_die("-f is for root only"); 240 safe_strncpy(username, opt_user, sizeof(username)); 241 } 242 argv += optind; 243 if (argv[0]) /* user from command line (getty) */ 244 safe_strncpy(username, argv[0], sizeof(username)); 245 246 /* Let's find out and memorize our tty */ 247 if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) 248 return EXIT_FAILURE; /* Must be a terminal */ 249 full_tty = xmalloc_ttyname(STDIN_FILENO); 250 if (!full_tty) 251 full_tty = xstrdup("UNKNOWN"); 252 short_tty = skip_dev_pfx(full_tty); 253 254 if (opt_host) { 255 fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host); 256 } else { 257 fromhost = xasprintf(" on '%s'", short_tty); 258 } 259 260 /* Was breaking "login <username>" from shell command line: */ 261 /*bb_setpgrp();*/ 262 263 openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH); 264 265 while (1) { 266 /* flush away any type-ahead (as getty does) */ 267 ioctl(0, TCFLSH, TCIFLUSH); 268 269 if (!username[0]) 270 get_username_or_die(username, sizeof(username)); 271 272#if ENABLE_PAM 273 pamret = pam_start("login", username, &conv, &pamh); 274 if (pamret != PAM_SUCCESS) { 275 failed_msg = "start"; 276 goto pam_auth_failed; 277 } 278 /* set TTY (so things like securetty work) */ 279 pamret = pam_set_item(pamh, PAM_TTY, short_tty); 280 if (pamret != PAM_SUCCESS) { 281 failed_msg = "set_item(TTY)"; 282 goto pam_auth_failed; 283 } 284 pamret = pam_authenticate(pamh, 0); 285 if (pamret != PAM_SUCCESS) { 286 failed_msg = "authenticate"; 287 goto pam_auth_failed; 288 /* TODO: or just "goto auth_failed" 289 * since user seems to enter wrong password 290 * (in this case pamret == 7) 291 */ 292 } 293 /* check that the account is healthy */ 294 pamret = pam_acct_mgmt(pamh, 0); 295 if (pamret != PAM_SUCCESS) { 296 failed_msg = "acct_mgmt"; 297 goto pam_auth_failed; 298 } 299 /* read user back */ 300 pamuser = NULL; 301 /* gcc: "dereferencing type-punned pointer breaks aliasing rules..." 302 * thus we cast to (void*) */ 303 if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) { 304 failed_msg = "get_item(USER)"; 305 goto pam_auth_failed; 306 } 307 if (!pamuser || !pamuser[0]) 308 goto auth_failed; 309 safe_strncpy(username, pamuser, sizeof(username)); 310 /* Don't use "pw = getpwnam(username);", 311 * PAM is said to be capable of destroying static storage 312 * used by getpwnam(). We are using safe(r) function */ 313 pw = NULL; 314 getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw); 315 if (!pw) 316 goto auth_failed; 317 pamret = pam_open_session(pamh, 0); 318 if (pamret != PAM_SUCCESS) { 319 failed_msg = "open_session"; 320 goto pam_auth_failed; 321 } 322 pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED); 323 if (pamret != PAM_SUCCESS) { 324 failed_msg = "setcred"; 325 goto pam_auth_failed; 326 } 327 break; /* success, continue login process */ 328 329 pam_auth_failed: 330 /* syslog, because we don't want potential attacker 331 * to know _why_ login failed */ 332 syslog(LOG_WARNING, "pam_%s call failed: %s (%d)", failed_msg, 333 pam_strerror(pamh, pamret), pamret); 334 safe_strncpy(username, "UNKNOWN", sizeof(username)); 335#else /* not PAM */ 336 pw = getpwnam(username); 337 if (!pw) { 338 strcpy(username, "UNKNOWN"); 339 goto fake_it; 340 } 341 342 if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*') 343 goto auth_failed; 344 345 if (opt & LOGIN_OPT_f) 346 break; /* -f USER: success without asking passwd */ 347 348 if (pw->pw_uid == 0 && !check_securetty()) 349 goto auth_failed; 350 351 /* Don't check the password if password entry is empty (!) */ 352 if (!pw->pw_passwd[0]) 353 break; 354 fake_it: 355 /* authorization takes place here */ 356 if (correct_password(pw)) 357 break; 358#endif /* ENABLE_PAM */ 359 auth_failed: 360 opt &= ~LOGIN_OPT_f; 361 bb_do_delay(FAIL_DELAY); 362 /* TODO: doesn't sound like correct English phrase to me */ 363 puts("Login incorrect"); 364 if (++count == 3) { 365 syslog(LOG_WARNING, "invalid password for '%s'%s", 366 username, fromhost); 367 return EXIT_FAILURE; 368 } 369 username[0] = '\0'; 370 } /* while (1) */ 371 372 alarm(0); 373 /* We can ignore /etc/nologin if we are logging in as root, 374 * it doesn't matter whether we are run by root or not */ 375 if (pw->pw_uid != 0) 376 die_if_nologin(); 377 378 IF_SELINUX(initselinux(username, full_tty, &user_sid)); 379 380 /* Try these, but don't complain if they fail. 381 * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */ 382 fchown(0, pw->pw_uid, pw->pw_gid); 383 fchmod(0, 0600); 384 385 update_utmp(getpid(), USER_PROCESS, short_tty, username, run_by_root ? opt_host : NULL); 386 387 /* We trust environment only if we run by root */ 388 if (ENABLE_LOGIN_SCRIPTS && run_by_root) 389 run_login_script(pw, full_tty); 390 391 change_identity(pw); 392 shell = pw->pw_shell; 393 if (!shell || !shell[0]) 394 shell = DEFAULT_SHELL; 395 setup_environment(shell, 396 (!(opt & LOGIN_OPT_p) * SETUP_ENV_CLEARENV) + SETUP_ENV_CHANGEENV, 397 pw); 398 399 motd(); 400 401 if (pw->pw_uid == 0) 402 syslog(LOG_INFO, "root login%s", fromhost); 403 404 /* well, a simple setexeccon() here would do the job as well, 405 * but let's play the game for now */ 406 IF_SELINUX(set_current_security_context(user_sid);) 407 408 // util-linux login also does: 409 // /* start new session */ 410 // setsid(); 411 // /* TIOCSCTTY: steal tty from other process group */ 412 // if (ioctl(0, TIOCSCTTY, 1)) error_msg... 413 // BBox login used to do this (see above): 414 // bb_setpgrp(); 415 // If this stuff is really needed, add it and explain why! 416 417 /* Set signals to defaults */ 418 /* Non-ignored signals revert to SIG_DFL on exec anyway */ 419 /*signal(SIGALRM, SIG_DFL);*/ 420 421 /* Is this correct? This way user can ctrl-c out of /etc/profile, 422 * potentially creating security breach (tested with bash 3.0). 423 * But without this, bash 3.0 will not enable ctrl-c either. 424 * Maybe bash is buggy? 425 * Need to find out what standards say about /bin/login - 426 * should we leave SIGINT etc enabled or disabled? */ 427 signal(SIGINT, SIG_DFL); 428 429 /* Exec login shell with no additional parameters */ 430 run_shell(shell, 1, NULL, NULL); 431 432 /* return EXIT_FAILURE; - not reached */ 433} 434