/* * sulogin * * This program gives Linux machines a reasonable secure way to boot single * user. It forces the user to supply the root password before a shell is * started. If there is a shadow password file and the encrypted root password * is "x" the shadow password will be used. * * Copyright (C) 1998-2003 Miquel van Smoorenburg. * Copyright (C) 2012 Karel Zak * Copyright (C) 2012 Werner Fink * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CRYPT_H # include #endif #ifdef HAVE_LIBSELINUX # include # include #endif #ifdef __linux__ # include # include #endif #include "c.h" #include "closestream.h" #include "env.h" #include "nls.h" #include "pathnames.h" #ifdef USE_PLYMOUTH_SUPPORT # include "plymouth-ctrl.h" #endif #include "strutils.h" #include "ttyutils.h" #include "sulogin-consoles.h" #define CONMAX 16 static unsigned int timeout; static int profile; static volatile uint32_t openfd; /* Remember higher file descriptors */ static struct sigaction saved_sigint; static struct sigaction saved_sigtstp; static struct sigaction saved_sigquit; static struct sigaction saved_sighup; static struct sigaction saved_sigchld; static volatile sig_atomic_t alarm_rised; static volatile sig_atomic_t sigchild; #ifndef IUCLC # define IUCLC 0 #endif #ifndef WEXITED # warning "WEXITED is missing, sulogin may not work as expected" # define WEXITED 0 #endif static int locked_account_password(const char * const passwd) { if (passwd && (*passwd == '*' || *passwd == '!')) return 1; return 0; } /* * Fix the tty modes and set reasonable defaults. */ static void tcinit(struct console *con) { int flags = 0, mode = 0; struct termios *tio = &con->tio; const int fd = con->fd; #ifdef USE_PLYMOUTH_SUPPORT struct termios lock; int i = (plymouth_command(MAGIC_PING)) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0; if (i) plymouth_command(MAGIC_QUIT); while (i-- > 0) { /* * With plymouth the termios flags become changed after this * function had changed the termios. */ memset(&lock, 0, sizeof(struct termios)); if (ioctl(fd, TIOCGLCKTRMIOS, &lock) < 0) break; if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag) break; sleep(1); } memset(&lock, 0, sizeof(struct termios)); ioctl(fd, TIOCSLCKTRMIOS, &lock); #endif errno = 0; if (tcgetattr(fd, tio) < 0) { warn(_("tcgetattr failed")); con->flags |= CON_NOTTY; return; } /* Handle lines other than virtual consoles here */ #if defined(KDGKBMODE) if (ioctl(fd, KDGKBMODE, &mode) < 0) #endif { speed_t ispeed, ospeed; struct winsize ws; errno = 0; /* this is a modem line */ con->flags |= CON_SERIAL; /* Flush input and output queues on modem lines */ tcflush(fd, TCIOFLUSH); ispeed = cfgetispeed(tio); ospeed = cfgetospeed(tio); if (!ispeed) ispeed = TTYDEF_SPEED; if (!ospeed) ospeed = TTYDEF_SPEED; tio->c_cflag = CREAD | CS8 | HUPCL | (tio->c_cflag & CLOCAL); tio->c_iflag = 0; tio->c_lflag = 0; tio->c_oflag &= OPOST | ONLCR; cfsetispeed(tio, ispeed); cfsetospeed(tio, ospeed); #ifdef HAVE_STRUCT_TERMIOS_C_LINE tio->c_line = 0; #endif tio->c_cc[VTIME] = 0; tio->c_cc[VMIN] = 1; if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { int update = 0; if (ws.ws_row == 0) { ws.ws_row = 24; update++; } if (ws.ws_col == 0) { ws.ws_col = 80; update++; } if (update) ignore_result( ioctl(fd, TIOCSWINSZ, &ws) ); } setlocale(LC_CTYPE, "POSIX"); goto setattr; } #if defined(IUTF8) && defined(KDGKBMODE) /* Handle mode of current keyboard setup, e.g. for UTF-8 */ switch(mode) { case K_UNICODE: setlocale(LC_CTYPE, "C.UTF-8"); flags |= UL_TTY_UTF8; break; case K_RAW: case K_MEDIUMRAW: case K_XLATE: default: setlocale(LC_CTYPE, "POSIX"); break; } #else setlocale(LC_CTYPE, "POSIX"); #endif reset_virtual_console(tio, flags); setattr: if (tcsetattr(fd, TCSANOW, tio)) warn(_("tcsetattr failed")); /* Enable blocking mode for read and write */ if ((flags = fcntl(fd, F_GETFL, 0)) != -1) ignore_result( fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) ); } /* * Finalize the tty modes on modem lines. */ static void tcfinal(struct console *con) { struct termios *tio = &con->tio; const int fd = con->fd; if ((con->flags & CON_SERIAL) == 0) { xsetenv("TERM", "linux", 1); return; } if (con->flags & CON_NOTTY) { xsetenv("TERM", "dumb", 1); return; } #if defined (__s390__) || defined (__s390x__) xsetenv("TERM", "dumb", 1); #else xsetenv("TERM", "vt102", 1); #endif tio->c_iflag |= (IXON | IXOFF); tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE); tio->c_oflag |= OPOST; tio->c_cc[VINTR] = CINTR; tio->c_cc[VQUIT] = CQUIT; tio->c_cc[VERASE] = con->cp.erase; tio->c_cc[VKILL] = con->cp.kill; tio->c_cc[VEOF] = CEOF; #ifdef VSWTC tio->c_cc[VSWTC] = _POSIX_VDISABLE; #elif defined(VSWTCH) tio->c_cc[VSWTCH] = _POSIX_VDISABLE; #endif tio->c_cc[VSTART] = CSTART; tio->c_cc[VSTOP] = CSTOP; tio->c_cc[VSUSP] = CSUSP; tio->c_cc[VEOL] = _POSIX_VDISABLE; if (con->cp.eol == CR) { tio->c_iflag |= ICRNL; tio->c_iflag &= ~(INLCR|IGNCR); tio->c_oflag |= ONLCR; tio->c_oflag &= ~(OCRNL|ONLRET); } switch (con->cp.parity) { default: case 0: tio->c_cflag &= ~(PARODD | PARENB); tio->c_iflag &= ~(INPCK | ISTRIP); break; case 1: /* odd parity */ tio->c_cflag |= PARODD; /* fallthrough */ case 2: /* even parity */ tio->c_cflag |= PARENB; tio->c_iflag |= (INPCK | ISTRIP); /* fallthrough */ case (1 | 2): /* no parity bit */ tio->c_cflag &= ~CSIZE; tio->c_cflag |= CS7; break; } /* Set line attributes */ tcsetattr(fd, TCSANOW, tio); } /* * Called at timeout. */ static void alrm_handler(int sig __attribute__((unused))) { /* Timeout expired */ alarm_rised++; } static void chld_handler(int sig __attribute__((unused))) { sigchild++; } static void mask_signal(int signal, void (*handler)(int), struct sigaction *origaction) { struct sigaction newaction; newaction.sa_handler = handler; sigemptyset(&newaction.sa_mask); newaction.sa_flags = 0; sigaction(signal, &newaction, origaction); } static void unmask_signal(int signal, struct sigaction *sa) { sigaction(signal, sa, NULL); } /* * See if an encrypted password is valid. The encrypted password is checked for * traditional-style DES and FreeBSD-style MD5 encryption. */ static int valid(const char *pass) { const char *s; char id[5]; size_t len; off_t off; if (pass[0] == 0) return 1; if (pass[0] != '$') goto check_des; /* * up to 4 bytes for the signature e.g. $1$ */ for (s = pass+1; *s && *s != '$'; s++); if (*s++ != '$') return 0; if ((off = (off_t)(s-pass)) > 4 || off < 3) return 0; memset(id, '\0', sizeof(id)); strncpy(id, pass, off); /* * up to 16 bytes for the salt */ for (; *s && *s != '$'; s++); if (*s++ != '$') return 0; if ((off_t)(s-pass) > 16) return 0; len = strlen(s); /* * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes */ if ((strcmp(id, "$1$") == 0) && (len < 22 || len > 24)) return 0; /* * the SHA-256 hash 43 bytes */ if ((strcmp(id, "$5$") == 0) && (len < 42 || len > 44)) return 0; /* * the SHA-512 hash 86 bytes */ if ((strcmp(id, "$6$") == 0) && (len < 85 || len > 87)) return 0; /* * e.g. Blowfish hash */ return 1; check_des: if (strlen(pass) != 13) return 0; for (s = pass; *s; s++) { if ((*s < '0' || *s > '9') && (*s < 'a' || *s > 'z') && (*s < 'A' || *s > 'Z') && *s != '.' && *s != '/') return 0; } return 1; } /* * Set a variable if the value is not NULL. */ static inline void set(char **var, char *val) { if (val) *var = val; } /* * Get the root password entry. */ static struct passwd *getrootpwent(int try_manually) { static struct passwd pwd; struct passwd *pw; struct spwd *spw; FILE *fp; static char line[2 * BUFSIZ]; static char sline[2 * BUFSIZ]; char *p; /* * First, we try to get the password the standard way using normal * library calls. */ if ((pw = getpwnam("root")) && !strcmp(pw->pw_passwd, "x") && (spw = getspnam("root"))) pw->pw_passwd = spw->sp_pwdp; if (pw || !try_manually) return pw; /* * If we come here, we could not retrieve the root password through * library calls and we try to read the password and shadow files * manually. */ pwd.pw_name = "root"; pwd.pw_passwd = ""; pwd.pw_gecos = "Super User"; pwd.pw_dir = "/"; pwd.pw_shell = ""; pwd.pw_uid = 0; pwd.pw_gid = 0; if ((fp = fopen(_PATH_PASSWD, "r")) == NULL) { warn(_("cannot open %s"), _PATH_PASSWD); return &pwd; } /* * Find root in the password file. */ while ((p = fgets(line, sizeof(line), fp)) != NULL) { if (strncmp(line, "root:", 5) != 0) continue; p += 5; set(&pwd.pw_passwd, strsep(&p, ":")); strsep(&p, ":"); strsep(&p, ":"); set(&pwd.pw_gecos, strsep(&p, ":")); set(&pwd.pw_dir, strsep(&p, ":")); set(&pwd.pw_shell, strsep(&p, "\n")); p = line; break; } fclose(fp); /* * If the encrypted password is valid or not found, return. */ if (p == NULL) { warnx(_("%s: no entry for root\n"), _PATH_PASSWD); return &pwd; } if (valid(pwd.pw_passwd)) return &pwd; /* * The password is invalid. If there is a shadow password, try it. */ *pwd.pw_passwd = '\0'; if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) { warn(_("cannot open %s"), _PATH_PASSWD); return &pwd; } while ((p = fgets(sline, sizeof(sline), fp)) != NULL) { if (strncmp(sline, "root:", 5) != 0) continue; p += 5; set(&pwd.pw_passwd, strsep(&p, ":")); break; } fclose(fp); /* * If the password is still invalid, NULL it, and return. */ if (p == NULL) { warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD); *pwd.pw_passwd = '\0'; } /* locked account passwords are valid too */ if (!locked_account_password(pwd.pw_passwd) && !valid(pwd.pw_passwd)) { warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD); *pwd.pw_passwd = '\0'; } return &pwd; } /* * Ask by prompt for the password. */ static void doprompt(const char *crypted, struct console *con, int deny) { struct termios tty; if (con->flags & CON_SERIAL) { tty = con->tio; /* * For prompting: map NL in output to CR-NL * otherwise we may see stairs in the output. */ tty.c_oflag |= (ONLCR | OPOST); tcsetattr(con->fd, TCSADRAIN, &tty); } if (!con->file) { con->file = fdopen(con->fd, "r+"); if (!con->file) goto err; } if (deny) fprintf(con->file, _("\nCannot open access to console, the root account is locked.\n" "See sulogin(8) man page for more details.\n\n" "Press Enter to continue.\n")); else { #if defined(USE_ONELINE) if (crypted[0] && !locked_account_password(crypted)) fprintf(con->file, _("Give root password for login: ")); else fprintf(con->file, _("Press Enter for login: ")); #else if (crypted[0] && !locked_account_password(crypted)) fprintf(con->file, _("Give root password for maintenance\n")); else fprintf(con->file, _("Press Enter for maintenance\n")); fprintf(con->file, _("(or press Control-D to continue): ")); #endif } fflush(con->file); err: if (con->flags & CON_SERIAL) tcsetattr(con->fd, TCSADRAIN, &con->tio); } /* * Make sure to have an own session and controlling terminal */ static void setup(struct console *con) { int fd = con->fd; const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp = getpgid(getppid()), ttypgrp = tcgetpgrp(fd); if (con->flags & CON_NOTTY) return; /* * Only go through this trouble if the new * tty doesn't fall in this process group. */ if (pgrp != ttypgrp && ppgrp != ttypgrp) { if (pid != getsid(0)) { if (pid == getpgid(0)) setpgid(0, getpgid(getppid())); setsid(); } mask_signal(SIGHUP, SIG_IGN, &saved_sighup); if (ttypgrp > 0) ioctl(STDIN_FILENO, TIOCNOTTY, (char *)1); unmask_signal(SIGHUP, &saved_sighup); if (fd > STDIN_FILENO) close(STDIN_FILENO); if (fd > STDOUT_FILENO) close(STDOUT_FILENO); if (fd > STDERR_FILENO) close(STDERR_FILENO); ioctl(fd, TIOCSCTTY, (char *)1); tcsetpgrp(fd, ppgrp); } dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); con->fd = STDIN_FILENO; for (fd = STDERR_FILENO+1; fd < 32; fd++) { if (openfd & (1<fd; if (con->flags & CON_NOTTY) goto out; cp = &con->cp; tty = con->tio; tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG); tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0); sigemptyset(&sa.sa_mask); sa.sa_handler = alrm_handler; sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); if (timeout) alarm(timeout); ptr = &pass[0]; cp->eol = *ptr = '\0'; eightbit = ((con->flags & CON_SERIAL) == 0 || (tty.c_cflag & (PARODD|PARENB)) == 0); while (cp->eol == '\0') { if (read(fd, &c, 1) < 1) { if (errno == EINTR || errno == EAGAIN) { if (alarm_rised) { ret = NULL; goto quit; } xusleep(250000); continue; } ret = NULL; switch (errno) { case 0: case EIO: case ESRCH: case EINVAL: case ENOENT: break; default: warn(_("cannot read %s"), con->tty); break; } goto quit; } if (eightbit) ascval = c; else if (c != (ascval = (c & 0177))) { uint32_t bits, mask; for (bits = 1, mask = 1; mask & 0177; mask <<= 1) { if (mask & ascval) bits++; } cp->parity |= ((bits & 1) ? 1 : 2); } switch (ascval) { case 0: *ptr = '\0'; goto quit; case CR: case NL: *ptr = '\0'; cp->eol = ascval; break; case BS: case CERASE: cp->erase = ascval; if (ptr > &pass[0]) ptr--; break; case CKILL: cp->kill = ascval; while (ptr > &pass[0]) ptr--; break; case CEOF: ret = NULL; goto quit; default: if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) { fprintf(stderr, "sulogin: input overrun at %s\n\r", con->tty); ret = NULL; goto quit; } *ptr++ = ascval; break; } } quit: alarm(0); if (tc) tcsetattr(fd, TCSAFLUSH, &con->tio); tcfinal(con); printf("\r\n"); out: return ret; } /* * Password was OK, execute a shell. */ static void sushell(struct passwd *pwd) { char shell[PATH_MAX]; char home[PATH_MAX]; char const *p; char const *su_shell; /* * Set directory and shell. */ if (chdir(pwd->pw_dir) != 0) { warn(_("%s: change directory failed"), pwd->pw_dir); printf(_("Logging in with home = \"/\".\n")); if (chdir("/") != 0) warn(_("change directory to system root failed")); } if ((p = getenv("SUSHELL")) != NULL) su_shell = p; else if ((p = getenv("sushell")) != NULL) su_shell = p; else { if (pwd->pw_shell[0]) su_shell = pwd->pw_shell; else su_shell = "/bin/sh"; } if ((p = strrchr(su_shell, '/')) == NULL) p = su_shell; else p++; snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p); /* * Set some important environment variables. */ if (getcwd(home, sizeof(home)) == NULL) strcpy(home, "/"); xsetenv("HOME", home, 1); xsetenv("LOGNAME", "root", 1); xsetenv("USER", "root", 1); if (!profile) xsetenv("SHLVL","0",1); /* * Try to execute a shell. */ xsetenv("SHELL", su_shell, 1); unmask_signal(SIGINT, &saved_sigint); unmask_signal(SIGTSTP, &saved_sigtstp); unmask_signal(SIGQUIT, &saved_sigquit); mask_signal(SIGHUP, SIG_DFL, NULL); #ifdef HAVE_LIBSELINUX if (is_selinux_enabled() > 0) { security_context_t scon=NULL; char *seuser=NULL; char *level=NULL; if (getseuserbyname("root", &seuser, &level) == 0) { if (get_default_context_with_level(seuser, level, 0, &scon) == 0) { if (setexeccon(scon) != 0) warnx(_("setexeccon failed")); freecon(scon); } } free(seuser); free(level); } #endif execl(su_shell, shell, (char *)NULL); warn(_("failed to execute %s"), su_shell); xsetenv("SHELL", "/bin/sh", 1); execl("/bin/sh", profile ? "-sh" : "sh", (char *)NULL); warn(_("failed to execute %s"), "/bin/sh"); } static void usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _( " %s [options] [tty device]\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Single-user login.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -p, --login-shell start a login shell\n" " -t, --timeout max time to wait for a password (default: no limit)\n" " -e, --force examine password files directly if getpwnam(3) fails\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(26)); printf(USAGE_MAN_TAIL("sulogin(8)")); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { struct list_head *ptr, consoles; struct console *con; char *tty = NULL; struct passwd *pwd; const struct timespec sigwait = { .tv_sec = 0, .tv_nsec = 50000000 }; siginfo_t status = { 0 }; sigset_t set; int c, reconnect = 0; int opt_e = 0; int wait = 0; pid_t pid; static const struct option longopts[] = { { "login-shell", no_argument, NULL, 'p' }, { "timeout", required_argument, NULL, 't' }, { "force", no_argument, NULL, 'e' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } }; INIT_LIST_HEAD(&consoles); /* * If we are init we need to set up a own session. */ if ((pid = getpid()) == 1) { setsid(); ignore_result( ioctl(STDIN_FILENO, TIOCSCTTY, (char *) 1) ); } setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); /* * See if we have a timeout flag. */ while ((c = getopt_long(argc, argv, "ehpt:V", longopts, NULL)) != -1) { switch(c) { case 't': timeout = strtou32_or_err(optarg, _("invalid timeout argument")); break; case 'p': profile = 1; break; case 'e': opt_e = 1; break; case 'V': print_version(EXIT_SUCCESS); case 'h': usage(); default: /* Do not exit! getopt prints a warning. */ break; } } if (geteuid() != 0) errx(EXIT_FAILURE, _("only superuser can run this program")); mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); mask_signal(SIGINT, SIG_IGN, &saved_sigint); mask_signal(SIGHUP, SIG_IGN, &saved_sighup); emergency_do_mounts(); atexit( emergency_do_umounts ); /* * See if we need to open an other tty device. */ if (optind < argc) tty = argv[optind]; if (!tty || *tty == '\0') tty = getenv("CONSOLE"); /* * Detect possible consoles, use stdin as fallback. * If an optional tty is given, reconnect it to stdin. */ reconnect = detect_consoles(tty, STDIN_FILENO, &consoles); /* * If previous stdin was not the specified tty and therefore reconnected * to the specified tty also reconnect stdout and stderr. */ if (reconnect) { if (isatty(STDOUT_FILENO) == 0) dup2(STDOUT_FILENO, STDIN_FILENO); if (isatty(STDERR_FILENO) == 0) dup2(STDOUT_FILENO, STDERR_FILENO); } /* * Should not happen */ if (list_empty(&consoles)) { if (!errno) errno = ENOENT; err(EXIT_FAILURE, _("cannot open console")); } /* * Get the root password. */ if ((pwd = getrootpwent(opt_e)) == NULL) { warnx(_("cannot open password database")); sleep(2); return EXIT_FAILURE; } /* * Ask for the password on the consoles. */ list_for_each(ptr, &consoles) { con = list_entry(ptr, struct console, entry); if (con->id >= CONMAX) break; if (con->fd >= 0) { openfd |= (1 << con->fd); tcinit(con); continue; } if ((con->fd = open(con->tty, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) continue; openfd |= (1 << con->fd); tcinit(con); } ptr = (&consoles)->next; if (ptr->next == &consoles) { con = list_entry(ptr, struct console, entry); goto nofork; } mask_signal(SIGCHLD, chld_handler, &saved_sigchld); do { con = list_entry(ptr, struct console, entry); if (con->id >= CONMAX) break; switch ((con->pid = fork())) { case 0: mask_signal(SIGCHLD, SIG_DFL, NULL); nofork: setup(con); while (1) { const char *passwd = pwd->pw_passwd; const char *answer; int doshell = 0; int deny = !opt_e && locked_account_password(pwd->pw_passwd); doprompt(passwd, con, deny); if ((answer = getpasswd(con)) == NULL) break; if (deny) exit(EXIT_FAILURE); /* no password or locked account */ if (!passwd[0] || locked_account_password(passwd)) doshell++; else { const char *cryptbuf; cryptbuf = crypt(answer, passwd); if (cryptbuf == NULL) warn(_("crypt failed")); else if (strcmp(cryptbuf, pwd->pw_passwd) == 0) doshell++; } if (doshell) { /* sushell() unmask signals */ sushell(pwd); mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit); mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp); mask_signal(SIGINT, SIG_IGN, &saved_sigint); fprintf(stderr, _("cannot execute su shell\n\n")); break; } fprintf(stderr, _("Login incorrect\n\n")); } if (alarm_rised) { tcfinal(con); warnx(_("Timed out\n\n")); } /* * User pressed Control-D. */ exit(0); case -1: warn(_("fork failed")); /* fallthrough */ default: break; } ptr = ptr->next; } while (ptr != &consoles); do { int ret; status.si_pid = 0; ret = waitid(P_ALL, 0, &status, WEXITED); if (ret == 0) break; if (ret < 0) { if (errno == ECHILD) break; if (errno == EINTR) continue; } errx(EXIT_FAILURE, _("cannot wait on su shell\n\n")); } while (1); list_for_each(ptr, &consoles) { con = list_entry(ptr, struct console, entry); if (con->fd < 0) continue; if (con->pid < 0) continue; if (con->pid == status.si_pid) con->pid = -1; else { kill(con->pid, SIGTERM); wait++; } } sigemptyset(&set); sigaddset(&set, SIGCHLD); do { int signum, ret; if (!wait) break; status.si_pid = 0; ret = waitid(P_ALL, 0, &status, WEXITED|WNOHANG); if (ret < 0) { if (errno == ECHILD) break; if (errno == EINTR) continue; } if (!ret && status.si_pid > 0) { list_for_each(ptr, &consoles) { con = list_entry(ptr, struct console, entry); if (con->fd < 0) continue; if (con->pid < 0) continue; if (con->pid == status.si_pid) { con->pid = -1; wait--; } } continue; } signum = sigtimedwait(&set, NULL, &sigwait); if (signum != SIGCHLD && signum < 0 && errno == EAGAIN) break; } while (1); mask_signal(SIGCHLD, SIG_DFL, NULL); return EXIT_SUCCESS; }