#ifdef __linux__
# include <sys/kd.h>
# include <sys/param.h>
+# include <linux/serial.h>
#endif
#include "c.h"
static int profile;
static volatile uint32_t openfd; /* Remember higher file descriptors */
-struct sigaction saved_sigint;
-struct sigaction saved_sigtstp;
-struct sigaction saved_sigquit;
-struct sigaction saved_sighup;
-struct sigaction saved_sigchld;
+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;
+#define SULOGIN_PASSWORD_BUFSIZ 128
+
#ifndef IUCLC
# define IUCLC 0
#endif
# define WEXITED 0
#endif
-static int locked_account_password(const char *passwd)
+static int locked_account_password(const char * const passwd)
{
if (passwd && (*passwd == '*' || *passwd == '!'))
return 1;
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.
*/
static void tcinit(struct console *con)
{
- int mode = 0, flags = 0;
+ int flags = 0, mode = 0;
struct termios *tio = &con->tio;
- int fd = con->fd;
+ 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;
}
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);
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) );
}
*/
static void tcfinal(struct console *con)
{
- struct termios *tio;
- int fd;
+ 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->flags & CON_NOTTY) {
- xsetenv("TERM", "dumb", 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 defined (__s390__) || defined (__s390x__)
- xsetenv("TERM", "dumb", 1);
-#else
- xsetenv("TERM", "vt102", 1);
-#endif
- tio = &con->tio;
- fd = con->fd;
+ if (!(con->flags & CON_SERIAL) || (con->flags & CON_NOTTY))
+ return;
tio->c_iflag |= (IXON | IXOFF);
tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE);
break;
case 1: /* odd parity */
tio->c_cflag |= PARODD;
- /* fall through */
+ /* fallthrough */
case 2: /* even parity */
tio->c_cflag |= PARENB;
tio->c_iflag |= (INPCK | ISTRIP);
- /* fall through */
+ /* fallthrough */
case (1 | 2): /* no parity bit */
tio->c_cflag &= ~CSIZE;
tio->c_cflag |= CS7;
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),
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;
}
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
}
*/
static void setup(struct console *con)
{
- pid_t pid, pgrp, ppgrp, ttypgrp;
- int fd;
+ int fd = con->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;
- fd = con->fd;
+
+ ttypgrp = tcgetpgrp(fd);
/*
* Only go through this trouble if the new
* tty doesn't fall in this process group.
*/
- pid = getpid();
- pgrp = getpgid(0);
- ppgrp = getpgid(getppid());
- ttypgrp = tcgetpgrp(fd);
-
if (pgrp != ttypgrp && ppgrp != ttypgrp) {
if (pid != getsid(0)) {
if (pid == getpgid(0))
ioctl(fd, TIOCSCTTY, (char *)1);
tcsetpgrp(fd, ppgrp);
}
+notty:
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
{
struct sigaction sa;
struct termios tty;
- static char pass[128], *ptr;
+ static char pass[SULOGIN_PASSWORD_BUFSIZ], *ptr;
struct chardata *cp;
- char *ret = pass;
+ char *ret = NULL;
unsigned char tc;
char c, ascval;
int eightbit;
- int fd;
+ const int fd = con->fd;
- if (con->flags & CON_NOTTY)
+ if (con->flags & CON_EIO)
goto out;
- fd = con->fd;
+
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;
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) {
+ ret = NULL;
+ goto quit;
+ }
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;
}
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;
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];
- char *p;
- char *su_shell;
+ char const *p;
+ char const *su_shell;
/*
* Set directory and shell.
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");
}
-static void usage(FILE *out)
+#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;
fputs(USAGE_HEADER, out);
fprintf(out, _(
" %s [options] [tty device]\n"), program_invocation_short_name);
out);
fputs(USAGE_SEPARATOR, out);
- fputs(USAGE_HELP, out);
- fputs(USAGE_VERSION, out);
+ fprintf(out, USAGE_HELP_OPTIONS(26));
fprintf(out, USAGE_MAN_TAIL("sulogin(8)"));
+
+ exit(EXIT_SUCCESS);
}
int main(int argc, char **argv)
{
- LIST_HEAD(consoles);
- struct list_head *ptr;
+ struct list_head *ptr, consoles;
struct console *con;
char *tty = NULL;
struct passwd *pwd;
- struct timespec sigwait = { .tv_sec = 0, .tv_nsec = 50000000 };
- siginfo_t status = {};
+ 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;
pid_t pid;
static const struct option longopts[] = {
- { "login-shell", 0, 0, 'p' },
- { "timeout", 1, 0, 't' },
- { "force", 0, 0, 'e' },
- { "help", 0, 0, 'h' },
- { "version", 0, 0, 'V' },
- { NULL, 0, 0, 0 }
+ { "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 we are init we need to set up an own session.
*/
if ((pid = getpid()) == 1) {
setsid();
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
- atexit(close_stdout); /* XXX */
+ close_stdout_atexit();
/*
* See if we have a timeout flag.
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(stdout);
- return EXIT_SUCCESS;
+ usage();
default:
- usage(stderr);
- /* Do not exit! */
+ /* Do not exit! getopt prints a warning. */
break;
}
}
return EXIT_FAILURE;
}
+#ifdef HAVE_LIBSELINUX
+ compute_login_context();
+#endif
+
/*
* Ask for the password on the consoles.
*/
}
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;
+ }
}
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);
- /* fall through */
+ 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))
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;
}
exit(0);
case -1:
warn(_("fork failed"));
- /* fall through */
+ /* fallthrough */
default:
break;
}
-
+ next:
ptr = ptr->next;
} while (ptr != &consoles);
} while (1);
mask_signal(SIGCHLD, SIG_DFL, NULL);
+
+#ifdef HAVE_LIBSELINUX
+ tcreset_selinux(&consoles);
+#endif
+
return EXIT_SUCCESS;
}