]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/sulogin.c
(minor) update sulogin.c
[thirdparty/util-linux.git] / login-utils / sulogin.c
index 45f6cfa4c58d05a6eaca9d0a16544f5c086b6e2c..501961f5fb249a73b5710129f924ba8870c38ae6 100644 (file)
@@ -52,6 +52,7 @@
 #ifdef __linux__
 # include <sys/kd.h>
 # include <sys/param.h>
+# include <linux/serial.h>
 #endif
 
 #include "c.h"
@@ -80,6 +81,8 @@ static struct sigaction saved_sigchld;
 static volatile sig_atomic_t alarm_rised;
 static volatile sig_atomic_t sigchild;
 
+#define SULOGIN_PASSWORD_BUFSIZ        128
+
 #ifndef IUCLC
 # define IUCLC         0
 #endif
@@ -96,6 +99,81 @@ static int locked_account_password(const char * const passwd)
        return 0;
 }
 
+#ifdef HAVE_LIBSELINUX
+/*
+ * Cached check whether SELinux is enabled.
+ */
+static int is_selinux_enabled_cached(void)
+{
+       static int cache = -1;
+
+       if (cache == -1)
+               cache = is_selinux_enabled();
+
+       return cache;
+}
+
+/* Computed SELinux login context. */
+static char *login_context;
+
+/*
+ * Compute SELinux login context.
+ */
+static void compute_login_context(void)
+{
+       char *seuser = NULL;
+       char *level = NULL;
+
+       if (is_selinux_enabled_cached() == 0)
+               goto cleanup;
+
+       if (getseuserbyname("root", &seuser, &level) == -1) {
+               warnx(_("failed to compute seuser"));
+               goto cleanup;
+       }
+
+       if (get_default_context_with_level(seuser, level, NULL, &login_context) == -1) {
+               warnx(_("failed to compute default context"));
+               goto cleanup;
+       }
+
+cleanup:
+       free(seuser);
+       free(level);
+}
+
+/*
+ * Compute SELinux terminal context.
+ */
+static void tcinit_selinux(struct console *con)
+{
+       security_class_t tclass;
+
+       if (!login_context)
+               return;
+
+       if (fgetfilecon(con->fd, &con->reset_tty_context) == -1) {
+               warn(_("failed to get context of terminal %s"), con->tty);
+               return;
+       }
+
+       tclass = string_to_security_class("chr_file");
+       if (tclass == 0) {
+               warnx(_("security class chr_file not available"));
+               freecon(con->reset_tty_context);
+               con->reset_tty_context = NULL;
+               return;
+       }
+
+       if (security_compute_relabel(login_context, con->reset_tty_context, tclass, &con->user_tty_context) == -1) {
+               warnx(_("failed to compute relabel context of terminal"));
+               freecon(con->reset_tty_context);
+               con->reset_tty_context = NULL;
+               return;
+       }
+}
+#endif
+
 /*
  * Fix the tty modes and set reasonable defaults.
  */
@@ -104,6 +182,9 @@ static void tcinit(struct console *con)
        int flags = 0, mode = 0;
        struct termios *tio = &con->tio;
        const int fd = con->fd;
+#if defined(TIOCGSERIAL)
+       struct serial_struct serinfo = { .flags = 0 };
+#endif
 #ifdef USE_PLYMOUTH_SUPPORT
        struct termios lock;
        int i = (plymouth_command(MAGIC_PING)) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0;
@@ -123,27 +204,76 @@ static void tcinit(struct console *con)
        }
        memset(&lock, 0, sizeof(struct termios));
        ioctl(fd, TIOCSLCKTRMIOS, &lock);
+       errno = 0;
+#endif
+
+#ifdef HAVE_LIBSELINUX
+       tcinit_selinux(con);
 #endif
+
+#ifdef TIOCGSERIAL
+       if (ioctl(fd, TIOCGSERIAL,  &serinfo) >= 0)
+               con->flags |= CON_SERIAL;
        errno = 0;
+#endif
 
+#ifdef KDGKBMODE
+       if (!(con->flags & CON_SERIAL)
+           && ioctl(fd, KDGKBMODE, &mode) < 0)
+               con->flags |= CON_SERIAL;
+       errno = 0;
+#endif
        if (tcgetattr(fd, tio) < 0) {
-               warn(_("tcgetattr failed"));
-               con->flags |= CON_NOTTY;
-               return;
+               int saveno = errno;
+#if defined(KDGKBMODE) || defined(TIOCGSERIAL)
+               if (con->flags & CON_SERIAL) {                  /* Try to recover this */
+
+# if defined(TIOCGSERIAL)
+                       serinfo.flags |= ASYNC_SKIP_TEST;       /* Skip test of UART */
+
+                       if (ioctl(fd, TIOCSSERIAL, &serinfo) < 0)
+                               goto tcgeterr;
+                       if (ioctl(fd, TIOCSERCONFIG) < 0)       /* Try to autoconfigure */
+                               goto tcgeterr;
+                       if (ioctl(fd, TIOCGSERIAL,  &serinfo) < 0)
+                               goto tcgeterr;                  /* Ouch */
+# endif
+                       if (tcgetattr(fd, tio) < 0)             /* Retry to get tty attributes */
+                               saveno = errno;
+               }
+# if defined(TIOCGSERIAL)
+       tcgeterr:
+# endif
+               if (saveno)
+#endif
+               {
+                       FILE *fcerr = fdopen(fd, "w");
+                       if (fcerr) {
+                               fprintf(fcerr, _("tcgetattr failed"));
+                               fclose(fcerr);
+                       }
+                       warn(_("tcgetattr failed"));
+
+                       con->flags &= ~CON_SERIAL;
+                       if (saveno != EIO)
+                               con->flags |= CON_NOTTY;
+                       else
+                               con->flags |= CON_EIO;
+
+                       errno = 0;
+                       return;
+               }
        }
 
        /* Handle lines other than virtual consoles here */
-#if defined(KDGKBMODE)
-       if (ioctl(fd, KDGKBMODE, &mode) < 0)
+#if defined(KDGKBMODE) || defined(TIOCGSERIAL)
+       if (con->flags & CON_SERIAL)
 #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);
 
@@ -168,16 +298,17 @@ static void tcinit(struct console *con)
                tio->c_cc[VMIN]     = 1;
 
                if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
-                       int set = 0;
+                       int update = 0;
+
                        if (ws.ws_row == 0) {
                                ws.ws_row = 24;
-                               set++;
+                               update++;
                        }
                        if (ws.ws_col == 0) {
                                ws.ws_col = 80;
-                               set++;
+                               update++;
                        }
-                       if (set)
+                       if (update)
                                ignore_result( ioctl(fd, TIOCSWINSZ, &ws) );
                }
 
@@ -218,21 +349,21 @@ static void tcfinal(struct console *con)
 {
        struct termios *tio = &con->tio;
        const int fd = con->fd;
+       char *term, *ttyname = NULL;
 
-       if ((con->flags & CON_SERIAL) == 0) {
-               xsetenv("TERM", "linux", 1);
-               return;
+       if (con->tty)
+               ttyname = strncmp(con->tty, "/dev/", 5) == 0 ?
+                                       con->tty + 5 : con->tty;
+
+       term = get_terminal_default_type(ttyname, con->flags & CON_SERIAL);
+       if (term) {
+               xsetenv("TERM", term, 0);
+               free(term);
        }
-       if (con->flags & CON_NOTTY) {
-               xsetenv("TERM", "dumb", 1);
+
+       if (!(con->flags & CON_SERIAL) || (con->flags & CON_NOTTY))
                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;
@@ -288,12 +419,12 @@ static void tcfinal(struct console *con)
 static void alrm_handler(int sig __attribute__((unused)))
 {
        /* Timeout expired */
-       alarm_rised++;
+       alarm_rised = 1;
 }
 
 static void chld_handler(int sig __attribute__((unused)))
 {
-       sigchild++;
+       sigchild = 1;
 }
 
 static void mask_signal(int signal, void (*handler)(int),
@@ -520,8 +651,9 @@ static void doprompt(const char *crypted, struct console *con, int deny)
                tty.c_oflag |= (ONLCR | OPOST);
                tcsetattr(con->fd, TCSADRAIN, &tty);
        }
-       if (con->file == (FILE*)0) {
-               if  ((con->file = fdopen(con->fd, "r+")) == (FILE*)0)
+       if (!con->file) {
+               con->file = fdopen(con->fd, "r+");
+               if (!con->file)
                        goto err;
        }
 
@@ -532,14 +664,14 @@ static void doprompt(const char *crypted, struct console *con, int deny)
        else {
 #if defined(USE_ONELINE)
                if (crypted[0] && !locked_account_password(crypted))
-                       fprintf(con->file, _("Give root password for login: "));
+                       fprintf(con->file, _("Enter 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"));
+                       fprintf(con->file, _("Enter root password for system maintenance\n"));
                else
-                       fprintf(con->file, _("Press Enter for maintenance\n"));
+                       fprintf(con->file, _("Press Enter for system maintenance\n"));
                fprintf(con->file, _("(or press Control-D to continue): "));
 #endif
        }
@@ -555,12 +687,16 @@ err:
 static void setup(struct console *con)
 {
        int fd = con->fd;
-       const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp =
-           getpgid(getppid()), ttypgrp = tcgetpgrp(fd);
+       const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp = getpgid(getppid());
+       pid_t ttypgrp;
 
        if (con->flags & CON_NOTTY)
+               goto notty;
+       if (con->flags & CON_EIO)
                return;
 
+       ttypgrp = tcgetpgrp(fd);
+
        /*
         * Only go through this trouble if the new
         * tty doesn't fall in this process group.
@@ -583,6 +719,7 @@ static void setup(struct console *con)
                ioctl(fd, TIOCSCTTY, (char *)1);
                tcsetpgrp(fd, ppgrp);
        }
+notty:
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
@@ -600,26 +737,31 @@ static void setup(struct console *con)
  * Ask for the password. Note that there is no default timeout as we normally
  * skip this during boot.
  */
-static const char *getpasswd(struct console *con)
+static char *getpasswd(struct console *con)
 {
        struct sigaction sa;
        struct termios tty;
-       static char pass[128], *ptr;
+       static char pass[SULOGIN_PASSWORD_BUFSIZ], *ptr;
        struct chardata *cp;
-       const char *ret = pass;
+       char *ret = NULL;
        unsigned char tc;
        char c, ascval;
        int eightbit;
        const int fd = con->fd;
 
-       if (con->flags & CON_NOTTY)
+       if (con->flags & CON_EIO)
                goto out;
+
        cp = &con->cp;
        tty = con->tio;
+       tc = 0;
+       ret = pass;
 
        tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
        tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG);
-       tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0);
+
+       if ((con->flags & CON_NOTTY) == 0)
+               tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0);
 
        sigemptyset(&sa.sa_mask);
        sa.sa_handler = alrm_handler;
@@ -633,7 +775,10 @@ static const char *getpasswd(struct console *con)
        cp->eol = *ptr = '\0';
 
        eightbit = ((con->flags & CON_SERIAL) == 0 || (tty.c_cflag & (PARODD|PARENB)) == 0);
+
        while (cp->eol == '\0') {
+               errno = 0;
+
                if (read(fd, &c, 1) < 1) {
                        if (errno == EINTR || errno == EAGAIN) {
                                if (alarm_rised) {
@@ -643,17 +788,17 @@ static const char *getpasswd(struct console *con)
                                xusleep(250000);
                                continue;
                        }
-                       ret = (char*)0;
+                       ret = NULL;
+
                        switch (errno) {
-                       case 0:
                        case EIO:
-                       case ESRCH:
-                       case EINVAL:
-                       case ENOENT:
-                               break;
+                               con->flags |= CON_EIO;
+                               /* fallthrough */
                        default:
                                warn(_("cannot read %s"), con->tty);
                                break;
+                       case 0:
+                               break;
                        }
                        goto quit;
                }
@@ -695,7 +840,7 @@ static const char *getpasswd(struct console *con)
                default:
                        if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) {
                                 fprintf(stderr, "sulogin: input overrun at %s\n\r", con->tty);
-                                ret = (char*)0;
+                                ret = NULL;
                                 goto quit;
                        }
                        *ptr++ = ascval;
@@ -709,13 +854,17 @@ quit:
        tcfinal(con);
        printf("\r\n");
 out:
+#ifdef HAVE_EXPLICIT_BZERO
+       if (ret == NULL)
+               explicit_bzero(pass, sizeof(pass));
+#endif
        return ret;
 }
 
 /*
  * Password was OK, execute a shell.
  */
-static void sushell(struct passwd *pwd)
+static void sushell(struct passwd *pwd, struct console *con)
 {
        char shell[PATH_MAX];
        char home[PATH_MAX];
@@ -772,29 +921,53 @@ static void sushell(struct passwd *pwd)
        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);
-                       }
+       if (is_selinux_enabled_cached() == 1) {
+               if (con->user_tty_context) {
+                       if (fsetfilecon(con->fd, con->user_tty_context) == -1)
+                               warn(_("failed to set context to %s for terminal %s"), con->user_tty_context, con->tty);
+               }
+
+               if (login_context) {
+                       if (setexeccon(login_context) == -1)
+                               warn(_("failed to set exec context to %s"), login_context);
                }
-               free(seuser);
-               free(level);
        }
+#else
+       (void)con;
 #endif
-       execl(su_shell, shell, NULL);
+
+       execl(su_shell, shell, (char *)NULL);
        warn(_("failed to execute %s"), su_shell);
 
        xsetenv("SHELL", "/bin/sh", 1);
-       execl("/bin/sh", profile ? "-sh" : "sh", NULL);
+       execl("/bin/sh", profile ? "-sh" : "sh", (char *)NULL);
        warn(_("failed to execute %s"), "/bin/sh");
 }
 
+#ifdef HAVE_LIBSELINUX
+static void tcreset_selinux(struct list_head *consoles) {
+       struct list_head *ptr;
+       struct console *con;
+
+       if (is_selinux_enabled_cached() == 0)
+               return;
+
+       list_for_each(ptr, consoles) {
+               con = list_entry(ptr, struct console, entry);
+
+               if (con->fd < 0)
+                       continue;
+               if (!con->reset_tty_context)
+                       continue;
+               if (fsetfilecon(con->fd, con->reset_tty_context) == -1)
+                       warn(_("failed to reset context to %s for terminal %s"), con->reset_tty_context, con->tty);
+
+               freecon(con->reset_tty_context);
+               con->reset_tty_context = NULL;
+       }
+}
+#endif
+
 static void usage(void)
 {
        FILE *out = stdout;
@@ -812,8 +985,10 @@ static void usage(void)
                out);
 
        fputs(USAGE_SEPARATOR, out);
-       print_usage_help_options(26);
+       fprintf(out, USAGE_HELP_OPTIONS(26));
        fprintf(out, USAGE_MAN_TAIL("sulogin(8)"));
+
+       exit(EXIT_SUCCESS);
 }
 
 int main(int argc, char **argv)
@@ -842,7 +1017,7 @@ int main(int argc, char **argv)
        INIT_LIST_HEAD(&consoles);
 
        /*
-        * If we are init we need to set up a own session.
+        * If we are init we need to set up an own session.
         */
        if ((pid = getpid()) == 1) {
                setsid();
@@ -852,7 +1027,7 @@ int main(int argc, char **argv)
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout); /* XXX */
+       close_stdout_atexit();
 
        /*
         * See if we have a timeout flag.
@@ -869,11 +1044,17 @@ int main(int argc, char **argv)
                        opt_e = 1;
                        break;
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+               {
+                       static const char *features[] = {
+#ifdef USE_SULOGIN_EMERGENCY_MOUNT
+                               "emergency-mount",
+#endif
+                               NULL
+                       };
+                       print_version_with_features(EXIT_SUCCESS, features);
+               }
                case 'h':
                        usage();
-                       return EXIT_SUCCESS;
                default:
                        /* Do not exit! getopt prints a warning. */
                        break;
@@ -936,6 +1117,10 @@ int main(int argc, char **argv)
                return EXIT_FAILURE;
        }
 
+#ifdef HAVE_LIBSELINUX
+       compute_login_context();
+#endif
+
        /*
         * Ask for the password on the consoles.
         */
@@ -955,9 +1140,18 @@ int main(int argc, char **argv)
        }
        ptr = (&consoles)->next;
 
-       if (ptr->next == &consoles) {
-               con = list_entry(ptr, struct console, entry);
-               goto nofork;
+#ifdef HAVE_LIBSELINUX
+       /*
+        * Always fork with SELinux enabled, so the parent can restore the
+        * terminal context afterwards.
+        */
+       if (is_selinux_enabled_cached() == 0)
+#endif
+       {
+               if (ptr->next == &consoles) {
+                       con = list_entry(ptr, struct console, entry);
+                       goto nofork;
+               }
        }
 
 
@@ -966,24 +1160,31 @@ int main(int argc, char **argv)
                con = list_entry(ptr, struct console, entry);
                if (con->id >= CONMAX)
                        break;
+               if (con->flags & CON_EIO)
+                       goto next;
 
                switch ((con->pid = fork())) {
                case 0:
                        mask_signal(SIGCHLD, SIG_DFL, NULL);
+                       dup2(con->fd, STDERR_FILENO);
                nofork:
                        setup(con);
                        while (1) {
                                const char *passwd = pwd->pw_passwd;
-                               const char *answer;
-                               int failed = 0, doshell = 0;
+                               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)
+                               if (deny) {
+#ifdef HAVE_EXPLICIT_BZERO
+                                       explicit_bzero(answer, SULOGIN_PASSWORD_BUFSIZ);
+#endif
                                        exit(EXIT_FAILURE);
+                               }
 
                                /* no password or locked account */
                                if (!passwd[0] || locked_account_password(passwd))
@@ -996,17 +1197,17 @@ int main(int argc, char **argv)
                                        else if (strcmp(cryptbuf, pwd->pw_passwd) == 0)
                                                doshell++;
                                }
-
+#ifdef HAVE_EXPLICIT_BZERO
+                               explicit_bzero(answer, SULOGIN_PASSWORD_BUFSIZ);
+#endif
                                if (doshell) {
-                                       sushell(pwd);
-                                       failed++;
-                               }
+                                       /* sushell() unmask signals */
+                                       sushell(pwd, con);
 
-                               mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
-                               mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
-                               mask_signal(SIGINT,  SIG_IGN, &saved_sigint);
+                                       mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
+                                       mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
+                                       mask_signal(SIGINT,  SIG_IGN, &saved_sigint);
 
-                               if (failed) {
                                        fprintf(stderr, _("cannot execute su shell\n\n"));
                                        break;
                                }
@@ -1026,7 +1227,7 @@ int main(int argc, char **argv)
                default:
                        break;
                }
-
+       next:
                ptr = ptr->next;
 
        } while (ptr != &consoles);
@@ -1107,5 +1308,10 @@ int main(int argc, char **argv)
        } while (1);
 
        mask_signal(SIGCHLD, SIG_DFL, NULL);
+
+#ifdef HAVE_LIBSELINUX
+       tcreset_selinux(&consoles);
+#endif
+
        return EXIT_SUCCESS;
 }