#ifdef __linux__
# include <sys/kd.h>
# include <sys/param.h>
+# include <linux/serial.h>
#endif
#include "c.h"
static volatile sig_atomic_t alarm_rised;
static volatile sig_atomic_t sigchild;
+#define SULOGIN_PASSWORD_BUFSIZ 128
+
#ifndef IUCLC
# define IUCLC 0
#endif
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.
*/
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;
}
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) );
}
{
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;
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)
{
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.
ioctl(fd, TIOCSCTTY, (char *)1);
tcsetpgrp(fd, ppgrp);
}
+notty:
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
* 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;
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) {
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];
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;
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)
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();
- return EXIT_SUCCESS;
default:
/* 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);
+ 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;
}
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;
}