]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/sulogin.c
build-sys: add --without-* for all libs
[thirdparty/util-linux.git] / login-utils / sulogin.c
CommitLineData
7d8a8d2c 1/*
adfbaecf 2 * sulogin
7d8a8d2c 3 *
adfbaecf
KZ
4 * This program gives Linux machines a reasonable secure way to boot single
5 * user. It forces the user to supply the root password before a shell is
6 * started. If there is a shadow password file and the encrypted root password
7 * is "x" the shadow password will be used.
7d8a8d2c
DR
8 *
9 * Copyright (C) 1998-2003 Miquel van Smoorenburg.
7c6bc22b 10 * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
729bfb80 11 * Copyright (C) 2012 Werner Fink <werner@suse.de>
7d8a8d2c
DR
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
7d8a8d2c 26 */
dfbc7c89 27#include <sys/mman.h>
7d8a8d2c
DR
28#include <sys/types.h>
29#include <sys/stat.h>
dfbc7c89 30#include <sys/wait.h>
7d8a8d2c
DR
31#include <stdio.h>
32#include <string.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <fcntl.h>
36#include <signal.h>
37#include <pwd.h>
38#include <shadow.h>
39#include <termios.h>
7d8a8d2c 40#include <errno.h>
8614335f 41#include <getopt.h>
7d8a8d2c 42#include <sys/ioctl.h>
5fe090d8 43#ifdef HAVE_CRYPT_H
adfbaecf 44# include <crypt.h>
7d8a8d2c
DR
45#endif
46
47#ifdef HAVE_LIBSELINUX
adfbaecf
KZ
48# include <selinux/selinux.h>
49# include <selinux/get_context_list.h>
7d8a8d2c
DR
50#endif
51
b9c73909
WF
52#ifdef __linux__
53# include <sys/kd.h>
54# include <sys/param.h>
55#endif
56
9211be30 57#include "c.h"
439cdf1e 58#include "closestream.h"
9211be30 59#include "nls.h"
97e04909 60#include "pathnames.h"
1d28b828 61#include "strutils.h"
59ef660f 62#include "ttyutils.h"
a73f59fa 63#include "sulogin-consoles.h"
dfbc7c89
WF
64#define CONMAX 16
65
1d28b828 66static unsigned int timeout;
7d8a8d2c 67static int profile;
dfbc7c89
WF
68static volatile uint32_t openfd; /* Remember higher file descriptors */
69static volatile uint32_t *usemask;
7d8a8d2c 70
97795a73
DR
71struct sigaction saved_sigint;
72struct sigaction saved_sigtstp;
73struct sigaction saved_sigquit;
dfbc7c89
WF
74struct sigaction saved_sighup;
75struct sigaction saved_sigchld;
7d8a8d2c 76
729bfb80 77static volatile sig_atomic_t alarm_rised;
dfbc7c89
WF
78static volatile sig_atomic_t sigchild;
79
80#ifndef IUCLC
81# define IUCLC 0
82#endif
83
88e0f3df 84#ifdef TIOCGLCKTRMIOS
bb280f79
WF
85/*
86 * For the case plymouth is found on this system
87 */
88static int plymouth_command(const char* arg)
89{
90 const char *cmd = "/usr/bin/plymouth";
91 static int has_plymouth = 1;
92 pid_t pid;
93
94 if (!has_plymouth)
95 return 127;
96
97 pid = fork();
98 if (!pid) {
99 int fd = open("/dev/null", O_RDWR);
c210324c
KZ
100 if (fd < 0)
101 exit(127);
bb280f79
WF
102 dup2(fd, 0);
103 dup2(fd, 1);
104 dup2(fd, 2);
105 close(fd);
106 execl(cmd, cmd, arg, (char *) NULL);
107 exit(127);
108 } else if (pid > 0) {
109 int status;
110 waitpid(pid, &status, 0);
111 if (status == 127)
112 has_plymouth = 0;
113 return status;
114 }
115 return 1;
116}
88e0f3df 117#endif
bb280f79 118
dfbc7c89
WF
119/*
120 * Fix the tty modes and set reasonable defaults.
121 */
122static void tcinit(struct console *con)
123{
124 int mode = 0, flags = 0;
125 struct termios *tio = &con->tio;
bb280f79 126 struct termios lock;
88e0f3df
ST
127 int fd = con->fd;
128#ifdef TIOCGLCKTRMIOS
129 int i = (plymouth_command("--ping")) ? 20 : 0;
bb280f79
WF
130
131 while (i-- > 0) {
132 /*
133 * With plymouth the termios flags become changed after this
134 * function had changed the termios.
135 */
136 memset(&lock, 0, sizeof(struct termios));
137 if (ioctl(fd, TIOCGLCKTRMIOS, &lock) < 0)
138 break;
139 if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag)
140 break;
141 if (i == 15 && plymouth_command("quit") != 0)
142 break;
143 sleep(1);
144 }
145 memset(&lock, 0, sizeof(struct termios));
146 ioctl(fd, TIOCSLCKTRMIOS, &lock);
88e0f3df 147#endif
dfbc7c89
WF
148
149 errno = 0;
150
151 if (tcgetattr(fd, tio) < 0) {
152 warn(_("tcgetattr failed"));
153 con->flags |= CON_NOTTY;
154 return;
155 }
156
b9c73909
WF
157 /* Handle lines other than virtual consoles here */
158#if defined(KDGKBMODE)
159 if (ioctl(fd, KDGKBMODE, &mode) < 0)
160#endif
161 {
dfbc7c89
WF
162 speed_t ispeed, ospeed;
163 struct winsize ws;
b9c73909 164 errno = 0;
dfbc7c89
WF
165
166 /* this is a modem line */
167 con->flags |= CON_SERIAL;
168
169 /* Flush input and output queues on modem lines */
cba09b5f 170 tcflush(fd, TCIOFLUSH);
dfbc7c89
WF
171
172 ispeed = cfgetispeed(tio);
173 ospeed = cfgetospeed(tio);
174
175 if (!ispeed) ispeed = TTYDEF_SPEED;
176 if (!ospeed) ospeed = TTYDEF_SPEED;
177
dfbc7c89 178 tio->c_cflag = CREAD | CS8 | HUPCL | (tio->c_cflag & CLOCAL);
84ece698
KZ
179 tio->c_iflag = 0;
180 tio->c_lflag = 0;
181 tio->c_oflag &= OPOST | ONLCR;
dfbc7c89
WF
182
183 cfsetispeed(tio, ispeed);
184 cfsetospeed(tio, ospeed);
185
e4544ec1 186#ifdef HAVE_STRUCT_TERMIOS_C_LINE
dfbc7c89 187 tio->c_line = 0;
e4544ec1 188#endif
dfbc7c89
WF
189 tio->c_cc[VTIME] = 0;
190 tio->c_cc[VMIN] = 1;
191
192 if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
193 int set = 0;
194 if (ws.ws_row == 0) {
195 ws.ws_row = 24;
196 set++;
197 }
198 if (ws.ws_col == 0) {
199 ws.ws_col = 80;
200 set++;
201 }
202 if (set)
cba09b5f 203 ignore_result( ioctl(fd, TIOCSWINSZ, &ws) );
dfbc7c89
WF
204 }
205
206 setlocale(LC_CTYPE, "POSIX");
207 goto setattr;
208 }
209#if defined(IUTF8) && defined(KDGKBMODE)
b9c73909 210 /* Handle mode of current keyboard setup, e.g. for UTF-8 */
dfbc7c89
WF
211 switch(mode) {
212 case K_UNICODE:
213 setlocale(LC_CTYPE, "C.UTF-8");
214 flags |= UL_TTY_UTF8;
215 break;
216 case K_RAW:
217 case K_MEDIUMRAW:
218 case K_XLATE:
219 default:
220 setlocale(LC_CTYPE, "POSIX");
221 break;
222 }
223#else
224 setlocale(LC_CTYPE, "POSIX");
225#endif
226 reset_virtual_console(tio, flags);
227setattr:
228 if (tcsetattr(fd, TCSANOW, tio))
229 warn(_("tcsetattr failed"));
230
231 /* Enable blocking mode for read and write */
232 if ((flags = fcntl(fd, F_GETFL, 0)) != -1)
cba09b5f 233 ignore_result( fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) );
dfbc7c89
WF
234}
235
236/*
237 * Finalize the tty modes on modem lines.
238 */
239static void tcfinal(struct console *con)
240{
241 struct termios *tio;
242 int fd;
243
244 if ((con->flags & CON_SERIAL) == 0) {
245 setenv("TERM", "linux", 1);
246 return;
247 }
f2bcda51
WF
248 if (con->flags & CON_NOTTY) {
249 setenv("TERM", "dumb", 1);
dfbc7c89 250 return;
f2bcda51 251 }
dfbc7c89 252
f2bcda51
WF
253#if defined (__s390__) || defined (__s390x__)
254 setenv("TERM", "dumb", 1);
255#else
dfbc7c89 256 setenv("TERM", "vt102", 1);
f2bcda51 257#endif
dfbc7c89
WF
258 tio = &con->tio;
259 fd = con->fd;
260
261 tio->c_iflag |= (IXON | IXOFF);
262 tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE);
263 tio->c_oflag |= OPOST;
264
265 tio->c_cc[VINTR] = CINTR;
266 tio->c_cc[VQUIT] = CQUIT;
267 tio->c_cc[VERASE] = con->cp.erase;
268 tio->c_cc[VKILL] = con->cp.kill;
269 tio->c_cc[VEOF] = CEOF;
270#ifdef VSWTC
271 tio->c_cc[VSWTC] = _POSIX_VDISABLE;
e4544ec1 272#elif defined(VSWTCH)
dfbc7c89
WF
273 tio->c_cc[VSWTCH] = _POSIX_VDISABLE;
274#endif
275 tio->c_cc[VSTART] = CSTART;
276 tio->c_cc[VSTOP] = CSTOP;
277 tio->c_cc[VSUSP] = CSUSP;
278 tio->c_cc[VEOL] = _POSIX_VDISABLE;
279
280 if (con->cp.eol == CR) {
281 tio->c_iflag |= ICRNL;
282 tio->c_iflag &= ~(INLCR|IGNCR);
283 tio->c_oflag |= ONLCR;
284 tio->c_oflag &= ~(OCRNL|ONLRET);
285 }
286
287 switch (con->cp.parity) {
288 default:
289 case 0:
290 tio->c_cflag &= ~(PARODD | PARENB);
291 tio->c_iflag &= ~(INPCK | ISTRIP);
292 break;
293 case 1: /* odd parity */
294 tio->c_cflag |= PARODD;
295 /* fall through */
296 case 2: /* even parity */
297 tio->c_cflag |= PARENB;
298 tio->c_iflag |= (INPCK | ISTRIP);
299 /* fall through */
300 case (1 | 2): /* no parity bit */
301 tio->c_cflag &= ~CSIZE;
302 tio->c_cflag |= CS7;
303 break;
304 }
305
306 /* Set line attributes */
cba09b5f 307 tcsetattr(fd, TCSANOW, tio);
dfbc7c89 308}
729bfb80 309
7d8a8d2c 310/*
adfbaecf 311 * Called at timeout.
7d8a8d2c 312 */
97795a73 313static void alrm_handler(int sig __attribute__((unused)))
7d8a8d2c 314{
729bfb80
WF
315 /* Timeout expired */
316 alarm_rised++;
97795a73
DR
317}
318
dfbc7c89
WF
319static void chld_handler(int sig __attribute__((unused)))
320{
321 sigchild++;
322}
323
97795a73
DR
324static void mask_signal(int signal, void (*handler)(int),
325 struct sigaction *origaction)
326{
327 struct sigaction newaction;
328
329 newaction.sa_handler = handler;
330 sigemptyset(&newaction.sa_mask);
331 newaction.sa_flags = 0;
332
dfbc7c89 333 sigaction(signal, &newaction, origaction);
97795a73
DR
334}
335
336static void unmask_signal(int signal, struct sigaction *sa)
337{
338 sigaction(signal, sa, NULL);
7d8a8d2c
DR
339}
340
341/*
adfbaecf
KZ
342 * See if an encrypted password is valid. The encrypted password is checked for
343 * traditional-style DES and FreeBSD-style MD5 encryption.
7d8a8d2c 344 */
60706626 345static int valid(const char *pass)
7d8a8d2c
DR
346{
347 const char *s;
348 char id[5];
349 size_t len;
350 off_t off;
351
60706626
DR
352 if (pass[0] == 0)
353 return 1;
60706626
DR
354 if (pass[0] != '$')
355 goto check_des;
7d8a8d2c
DR
356
357 /*
adfbaecf 358 * up to 4 bytes for the signature e.g. $1$
7d8a8d2c 359 */
60706626
DR
360 for (s = pass+1; *s && *s != '$'; s++);
361
362 if (*s++ != '$')
363 return 0;
364
365 if ((off = (off_t)(s-pass)) > 4 || off < 3)
366 return 0;
7d8a8d2c
DR
367
368 memset(id, '\0', sizeof(id));
369 strncpy(id, pass, off);
370
371 /*
adfbaecf 372 * up to 16 bytes for the salt
7d8a8d2c 373 */
60706626
DR
374 for (; *s && *s != '$'; s++);
375
376 if (*s++ != '$')
377 return 0;
378
379 if ((off_t)(s-pass) > 16)
380 return 0;
381
7d8a8d2c
DR
382 len = strlen(s);
383
384 /*
adfbaecf 385 * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes
7d8a8d2c 386 */
60706626
DR
387 if ((strcmp(id, "$1$") == 0) && (len < 22 || len > 24))
388 return 0;
7d8a8d2c
DR
389
390 /*
adfbaecf 391 * the SHA-256 hash 43 bytes
7d8a8d2c 392 */
60706626
DR
393 if ((strcmp(id, "$5$") == 0) && (len < 42 || len > 44))
394 return 0;
7d8a8d2c
DR
395
396 /*
adfbaecf 397 * the SHA-512 hash 86 bytes
7d8a8d2c 398 */
60706626
DR
399 if ((strcmp(id, "$6$") == 0) && (len < 85 || len > 87))
400 return 0;
7d8a8d2c
DR
401
402 /*
adfbaecf 403 * e.g. Blowfish hash
7d8a8d2c
DR
404 */
405 return 1;
406check_des:
60706626
DR
407 if (strlen(pass) != 13)
408 return 0;
409
7d8a8d2c
DR
410 for (s = pass; *s; s++) {
411 if ((*s < '0' || *s > '9') &&
412 (*s < 'a' || *s > 'z') &&
413 (*s < 'A' || *s > 'Z') &&
60706626
DR
414 *s != '.' && *s != '/')
415 return 0;
7d8a8d2c 416 }
7d8a8d2c
DR
417 return 1;
418}
419
420/*
adfbaecf 421 * Set a variable if the value is not NULL.
7d8a8d2c 422 */
24ff2f57 423static inline void set(char **var, char *val)
7d8a8d2c 424{
60706626
DR
425 if (val)
426 *var = val;
7d8a8d2c
DR
427}
428
429/*
adfbaecf 430 * Get the root password entry.
7d8a8d2c 431 */
60706626 432static struct passwd *getrootpwent(int try_manually)
7d8a8d2c
DR
433{
434 static struct passwd pwd;
435 struct passwd *pw;
436 struct spwd *spw;
437 FILE *fp;
438 static char line[256];
439 static char sline[256];
440 char *p;
441
442 /*
adfbaecf
KZ
443 * First, we try to get the password the standard way using normal
444 * library calls.
7d8a8d2c
DR
445 */
446 if ((pw = getpwnam("root")) &&
447 !strcmp(pw->pw_passwd, "x") &&
448 (spw = getspnam("root")))
449 pw->pw_passwd = spw->sp_pwdp;
adfbaecf 450
60706626
DR
451 if (pw || !try_manually)
452 return pw;
7d8a8d2c
DR
453
454 /*
adfbaecf
KZ
455 * If we come here, we could not retrieve the root password through
456 * library calls and we try to read the password and shadow files
457 * manually.
7d8a8d2c
DR
458 */
459 pwd.pw_name = "root";
460 pwd.pw_passwd = "";
461 pwd.pw_gecos = "Super User";
462 pwd.pw_dir = "/";
463 pwd.pw_shell = "";
464 pwd.pw_uid = 0;
465 pwd.pw_gid = 0;
466
97e04909 467 if ((fp = fopen(_PATH_PASSWD, "r")) == NULL) {
289dcc90 468 warn(_("cannot open %s"), _PATH_PASSWD);
7d8a8d2c
DR
469 return &pwd;
470 }
471
472 /*
adfbaecf 473 * Find root in the password file.
7d8a8d2c 474 */
60706626 475 while ((p = fgets(line, 256, fp)) != NULL) {
7d8a8d2c
DR
476 if (strncmp(line, "root:", 5) != 0)
477 continue;
478 p += 5;
479 set(&pwd.pw_passwd, strsep(&p, ":"));
60706626
DR
480 strsep(&p, ":");
481 strsep(&p, ":");
7d8a8d2c
DR
482 set(&pwd.pw_gecos, strsep(&p, ":"));
483 set(&pwd.pw_dir, strsep(&p, ":"));
484 set(&pwd.pw_shell, strsep(&p, "\n"));
485 p = line;
486 break;
487 }
adfbaecf 488
7d8a8d2c
DR
489 fclose(fp);
490
491 /*
adfbaecf 492 * If the encrypted password is valid or not found, return.
7d8a8d2c
DR
493 */
494 if (p == NULL) {
b6506dc9 495 warnx(_("%s: no entry for root\n"), _PATH_PASSWD);
7d8a8d2c
DR
496 return &pwd;
497 }
60706626
DR
498 if (valid(pwd.pw_passwd))
499 return &pwd;
7d8a8d2c
DR
500
501 /*
adfbaecf 502 * The password is invalid. If there is a shadow password, try it.
7d8a8d2c
DR
503 */
504 strcpy(pwd.pw_passwd, "");
97e04909 505 if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) {
289dcc90 506 warn(_("cannot open %s"), _PATH_PASSWD);
7d8a8d2c
DR
507 return &pwd;
508 }
60706626 509 while ((p = fgets(sline, 256, fp)) != NULL) {
7d8a8d2c
DR
510 if (strncmp(sline, "root:", 5) != 0)
511 continue;
512 p += 5;
513 set(&pwd.pw_passwd, strsep(&p, ":"));
514 break;
515 }
516 fclose(fp);
517
518 /*
adfbaecf 519 * If the password is still invalid, NULL it, and return.
7d8a8d2c
DR
520 */
521 if (p == NULL) {
d45e5dff 522 warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD);
7d8a8d2c
DR
523 strcpy(pwd.pw_passwd, "");
524 }
525 if (!valid(pwd.pw_passwd)) {
d45e5dff 526 warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD);
60706626
DR
527 strcpy(pwd.pw_passwd, "");
528 }
7d8a8d2c
DR
529 return &pwd;
530}
531
dfbc7c89
WF
532/*
533 * Ask by prompt for the password.
534 */
535static void doprompt(const char *crypted, struct console *con)
536{
537 struct termios tty;
538
539 if (con->flags & CON_SERIAL) {
540 tty = con->tio;
541 /*
542 * For prompting: map NL in output to CR-NL
543 * otherwise we may see stairs in the output.
544 */
545 tty.c_oflag |= (ONLCR | OPOST);
cba09b5f 546 tcsetattr(con->fd, TCSADRAIN, &tty);
dfbc7c89
WF
547 }
548 if (con->file == (FILE*)0) {
549 if ((con->file = fdopen(con->fd, "r+")) == (FILE*)0)
550 goto err;
551 }
552#if defined(USE_ONELINE)
553 if (crypted[0])
554 fprintf(con->file, _("Give root password for login: "));
555 else
97b820bf 556 fprintf(con->file, _("Press Enter for login: "));
dfbc7c89
WF
557#else
558 if (crypted[0])
559 fprintf(con->file, _("Give root password for maintenance\n"));
560 else
97b820bf
BS
561 fprintf(con->file, _("Press Enter for maintenance"));
562 fprintf(con->file, _("(or press Control-D to continue): "));
dfbc7c89
WF
563#endif
564 fflush(con->file);
565err:
566 if (con->flags & CON_SERIAL)
cba09b5f 567 tcsetattr(con->fd, TCSADRAIN, &con->tio);
dfbc7c89
WF
568}
569
570/*
571 * Make sure to have an own session and controlling terminal
572 */
573static void setup(struct console *con)
574{
575 pid_t pid, pgrp, ppgrp, ttypgrp;
576 int fd;
577
578 if (con->flags & CON_NOTTY)
579 return;
580 fd = con->fd;
581
582 /*
583 * Only go through this trouble if the new
584 * tty doesn't fall in this process group.
585 */
586 pid = getpid();
587 pgrp = getpgid(0);
588 ppgrp = getpgid(getppid());
589 ttypgrp = tcgetpgrp(fd);
590
591 if (pgrp != ttypgrp && ppgrp != ttypgrp) {
592 if (pid != getsid(0)) {
593 if (pid == getpgid(0))
594 setpgid(0, getpgid(getppid()));
595 setsid();
596 }
597
598 mask_signal(SIGHUP, SIG_IGN, &saved_sighup);
599 if (ttypgrp > 0)
600 ioctl(STDIN_FILENO, TIOCNOTTY, (char *)1);
601 unmask_signal(SIGHUP, &saved_sighup);
602 if (fd > STDIN_FILENO) close(STDIN_FILENO);
603 if (fd > STDOUT_FILENO) close(STDOUT_FILENO);
604 if (fd > STDERR_FILENO) close(STDERR_FILENO);
605
606 ioctl(fd, TIOCSCTTY, (char *)1);
607 tcsetpgrp(fd, ppgrp);
608 }
609 dup2(fd, STDIN_FILENO);
610 dup2(fd, STDOUT_FILENO);
611 dup2(fd, STDERR_FILENO);
612 con->fd = STDIN_FILENO;
613
614 for (fd = STDERR_FILENO+1; fd < 32; fd++) {
615 if (openfd & (1<<fd)) {
616 close(fd);
617 openfd &= ~(1<<fd);
618 }
619 }
620}
621
7d8a8d2c 622/*
adfbaecf
KZ
623 * Ask for the password. Note that there is no default timeout as we normally
624 * skip this during boot.
7d8a8d2c 625 */
dfbc7c89 626static char *getpasswd(struct console *con)
7d8a8d2c
DR
627{
628 struct sigaction sa;
dfbc7c89
WF
629 struct termios tty;
630 static char pass[128], *ptr;
631 struct chardata *cp;
7d8a8d2c 632 char *ret = pass;
dfbc7c89
WF
633 unsigned char tc;
634 char c, ascval;
635 int eightbit;
636 int fd;
5ec4a799 637
dfbc7c89
WF
638 if (con->flags & CON_NOTTY)
639 goto out;
640 fd = con->fd;
641 cp = &con->cp;
642 tty = con->tio;
7d8a8d2c 643
7d8a8d2c 644 tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
dfbc7c89
WF
645 tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG);
646 tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0);
7d8a8d2c 647
fa936c9f 648 sigemptyset(&sa.sa_mask);
7d8a8d2c
DR
649 sa.sa_handler = alrm_handler;
650 sa.sa_flags = 0;
651 sigaction(SIGALRM, &sa, NULL);
cba09b5f
KZ
652
653 if (timeout)
654 alarm(timeout);
7d8a8d2c 655
dfbc7c89
WF
656 ptr = &pass[0];
657 cp->eol = *ptr = '\0';
658
659 eightbit = ((con->flags & CON_SERIAL) == 0 || (tty.c_cflag & (PARODD|PARENB)) == 0);
660 while (cp->eol == '\0') {
661 if (read(fd, &c, 1) < 1) {
662 if (errno == EINTR || errno == EAGAIN) {
a5bd7939 663 xusleep(250000);
dfbc7c89
WF
664 continue;
665 }
666 ret = (char*)0;
667 switch (errno) {
668 case 0:
669 case EIO:
670 case ESRCH:
671 case EINVAL:
672 case ENOENT:
7d8a8d2c 673 break;
dfbc7c89 674 default:
47481cbd 675 warn(_("cannot read %s"), con->tty);
dfbc7c89
WF
676 break;
677 }
678 goto quit;
679 }
680
681 if (eightbit)
682 ascval = c;
683 else if (c != (ascval = (c & 0177))) {
684 uint32_t bits, mask;
685 for (bits = 1, mask = 1; mask & 0177; mask <<= 1) {
686 if (mask & ascval)
687 bits++;
688 }
689 cp->parity |= ((bits & 1) ? 1 : 2);
690 }
691
692 switch (ascval) {
693 case 0:
694 *ptr = '\0';
695 goto quit;
696 case CR:
697 case NL:
698 *ptr = '\0';
699 cp->eol = ascval;
700 break;
701 case BS:
702 case CERASE:
703 cp->erase = ascval;
704 if (ptr > &pass[0])
705 ptr--;
706 break;
707 case CKILL:
708 cp->kill = ascval;
709 while (ptr > &pass[0])
710 ptr--;
711 break;
712 case CEOF:
713 goto quit;
714 default:
715 if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) {
716 fprintf(stderr, "sulogin: input overrun at %s\n\r", con->tty);
717 ret = (char*)0;
718 goto quit;
7d8a8d2c 719 }
dfbc7c89
WF
720 *ptr++ = ascval;
721 break;
722 }
7d8a8d2c 723 }
dfbc7c89 724quit:
7d8a8d2c 725 alarm(0);
dfbc7c89 726 if (tc)
cba09b5f 727 tcsetattr(fd, TCSAFLUSH, &con->tio);
dfbc7c89
WF
728 if (ret && *ret != '\0')
729 tcfinal(con);
730 printf("\r\n");
731out:
7d8a8d2c
DR
732 return ret;
733}
734
735/*
adfbaecf 736 * Password was OK, execute a shell.
7d8a8d2c 737 */
60706626 738static void sushell(struct passwd *pwd)
7d8a8d2c 739{
adf97f07
KZ
740 char shell[PATH_MAX];
741 char home[PATH_MAX];
7d8a8d2c 742 char *p;
9f94bdce 743 char *su_shell;
7d8a8d2c
DR
744
745 /*
adfbaecf 746 * Set directory and shell.
7d8a8d2c 747 */
adf97f07
KZ
748 if (chdir(pwd->pw_dir) != 0) {
749 warn(_("%s: change directory failed"), pwd->pw_dir);
750 printf(_("Logging in with home = \"/\".\n"));
751
752 if (chdir("/") != 0)
753 warn(_("change directory to system root failed"));
754 }
755
7d8a8d2c 756 if ((p = getenv("SUSHELL")) != NULL)
9f94bdce 757 su_shell = p;
7d8a8d2c 758 else if ((p = getenv("sushell")) != NULL)
9f94bdce 759 su_shell = p;
7d8a8d2c
DR
760 else {
761 if (pwd->pw_shell[0])
9f94bdce 762 su_shell = pwd->pw_shell;
7d8a8d2c 763 else
9f94bdce 764 su_shell = "/bin/sh";
7d8a8d2c 765 }
9f94bdce
SK
766 if ((p = strrchr(su_shell, '/')) == NULL)
767 p = su_shell;
7d8a8d2c
DR
768 else
769 p++;
adfbaecf 770
7d8a8d2c
DR
771 snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p);
772
773 /*
adfbaecf 774 * Set some important environment variables.
7d8a8d2c 775 */
dfbc7c89
WF
776 if (getcwd(home, sizeof(home)) == NULL)
777 strcpy(home, "/");
adf97f07 778
dfbc7c89 779 setenv("HOME", home, 1);
7d8a8d2c
DR
780 setenv("LOGNAME", "root", 1);
781 setenv("USER", "root", 1);
782 if (!profile)
783 setenv("SHLVL","0",1);
784
785 /*
adfbaecf 786 * Try to execute a shell.
7d8a8d2c 787 */
9f94bdce 788 setenv("SHELL", su_shell, 1);
97795a73
DR
789 unmask_signal(SIGINT, &saved_sigint);
790 unmask_signal(SIGTSTP, &saved_sigtstp);
791 unmask_signal(SIGQUIT, &saved_sigquit);
dfbc7c89 792 mask_signal(SIGHUP, SIG_DFL, NULL);
c3e706a9
KZ
793
794#ifdef HAVE_LIBSELINUX
7d8a8d2c 795 if (is_selinux_enabled() > 0) {
60706626
DR
796 security_context_t scon=NULL;
797 char *seuser=NULL;
798 char *level=NULL;
799 if (getseuserbyname("root", &seuser, &level) == 0) {
800 if (get_default_context_with_level(seuser, level, 0, &scon) == 0) {
801 if (setexeccon(scon) != 0)
d45e5dff 802 warnx(_("setexeccon failed"));
60706626
DR
803 freecon(scon);
804 }
805 }
7d8a8d2c
DR
806 free(seuser);
807 free(level);
808 }
809#endif
9f94bdce 810 execl(su_shell, shell, NULL);
07ff972e 811 warn(_("failed to execute %s"), su_shell);
7d8a8d2c 812
97e04909
DR
813 setenv("SHELL", "/bin/sh", 1);
814 execl("/bin/sh", profile ? "-sh" : "sh", NULL);
07ff972e 815 warn(_("failed to execute %s"), "/bin/sh");
7d8a8d2c
DR
816}
817
9211be30 818static void usage(FILE *out)
7d8a8d2c 819{
8614335f 820 fputs(USAGE_HEADER, out);
9211be30 821 fprintf(out, _(
8614335f
KZ
822 " %s [options] [tty device]\n"), program_invocation_short_name);
823
451dbcfa
BS
824 fputs(USAGE_SEPARATOR, out);
825 fputs(_("Single-user login.\n"), out);
826
8614335f
KZ
827 fputs(USAGE_OPTIONS, out);
828 fputs(_(" -p, --login-shell start a login shell\n"
829 " -t, --timeout <seconds> max time to wait for a password (default: no limit)\n"
830 " -e, --force examine password files directly if getpwnam(3) fails\n"),
831 out);
832
833 fputs(USAGE_SEPARATOR, out);
834 fputs(USAGE_HELP, out);
835 fputs(USAGE_VERSION, out);
836 fprintf(out, USAGE_MAN_TAIL("sulogin(8)"));
7d8a8d2c
DR
837}
838
839int main(int argc, char **argv)
840{
cba09b5f
KZ
841 LIST_HEAD(consoles);
842 struct list_head *ptr;
dfbc7c89 843 struct console *con;
7d8a8d2c 844 char *tty = NULL;
7d8a8d2c 845 struct passwd *pwd;
dfbc7c89
WF
846 int c, status = 0;
847 int reconnect = 0;
7d8a8d2c 848 int opt_e = 0;
dfbc7c89 849 pid_t pid;
7d8a8d2c 850
8614335f
KZ
851 static const struct option longopts[] = {
852 { "login-shell", 0, 0, 'p' },
853 { "timeout", 1, 0, 't' },
854 { "force", 0, 0, 'e' },
855 { "help", 0, 0, 'h' },
856 { "version", 0, 0, 'V' },
857 { NULL, 0, 0, 0 }
858 };
859
dfbc7c89
WF
860 /*
861 * If we are init we need to set up a own session.
862 */
863 if ((pid = getpid()) == 1) {
864 setsid();
cba09b5f 865 ignore_result( ioctl(STDIN_FILENO, TIOCSCTTY, (char *) 1) );
dfbc7c89
WF
866 }
867
24ff2f57
KZ
868 setlocale(LC_ALL, "");
869 bindtextdomain(PACKAGE, LOCALEDIR);
870 textdomain(PACKAGE);
dfbc7c89 871 atexit(close_stdout); /* XXX */
24ff2f57 872
7d8a8d2c 873 /*
adfbaecf 874 * See if we have a timeout flag.
7d8a8d2c 875 */
8614335f 876 while ((c = getopt_long(argc, argv, "ehpt:V", longopts, NULL)) != -1) {
60706626 877 switch(c) {
7d8a8d2c 878 case 't':
92bb28a4 879 timeout = strtou32_or_err(optarg, _("invalid timeout argument"));
7d8a8d2c
DR
880 break;
881 case 'p':
882 profile = 1;
883 break;
884 case 'e':
885 opt_e = 1;
886 break;
8614335f
KZ
887 case 'V':
888 printf(UTIL_LINUX_VERSION);
889 return EXIT_SUCCESS;
9211be30
DR
890 case 'h':
891 usage(stdout);
cd8703ad 892 return EXIT_SUCCESS;
7d8a8d2c 893 default:
9211be30 894 usage(stderr);
7d8a8d2c
DR
895 /* Do not exit! */
896 break;
60706626 897 }
7d8a8d2c
DR
898 }
899
d45e5dff 900 if (geteuid() != 0)
1d231190 901 errx(EXIT_FAILURE, _("only superuser can run this program"));
7d8a8d2c 902
97795a73
DR
903 mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
904 mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
905 mask_signal(SIGINT, SIG_IGN, &saved_sigint);
dfbc7c89
WF
906 mask_signal(SIGHUP, SIG_IGN, &saved_sighup);
907
4226f910
KZ
908
909 emergency_do_mounts();
910 atexit( emergency_do_umounts );
911
dfbc7c89
WF
912 /*
913 * See if we need to open an other tty device.
914 */
60706626
DR
915 if (optind < argc)
916 tty = argv[optind];
7d8a8d2c 917
dfbc7c89
WF
918 if (!tty || *tty == '\0')
919 tty = getenv("CONSOLE");
7d8a8d2c 920
dfbc7c89
WF
921 /*
922 * Detect possible consoles, use stdin as fallback.
923 * If an optional tty is given, reconnect it to stdin.
924 */
925 reconnect = detect_consoles(tty, STDIN_FILENO, &consoles);
7d8a8d2c 926
dfbc7c89
WF
927 /*
928 * If previous stdin was not the speified tty and therefore reconnected
929 * to the specified tty also reconnect stdout and stderr.
930 */
931 if (reconnect) {
932 if (isatty(STDOUT_FILENO) == 0)
933 dup2(STDOUT_FILENO, STDIN_FILENO);
934 if (isatty(STDERR_FILENO) == 0)
935 dup2(STDOUT_FILENO, STDERR_FILENO);
7d8a8d2c
DR
936 }
937
dfbc7c89
WF
938 /*
939 * Should not happen
940 */
941 if (list_empty(&consoles)) {
942 if (!errno)
943 errno = ENOENT;
4b3624ca 944 err(EXIT_FAILURE, _("cannot open console"));
dfbc7c89 945 }
59ef660f 946
7d8a8d2c 947 /*
adfbaecf 948 * Get the root password.
7d8a8d2c
DR
949 */
950 if ((pwd = getrootpwent(opt_e)) == NULL) {
1d231190 951 warnx(_("cannot open password database"));
7d8a8d2c 952 sleep(2);
ad85c7ba 953 return EXIT_FAILURE;
7d8a8d2c
DR
954 }
955
956 /*
dfbc7c89 957 * Ask for the password on the consoles.
7d8a8d2c 958 */
dfbc7c89
WF
959 list_for_each(ptr, &consoles) {
960 con = list_entry(ptr, struct console, entry);
961 if (con->id >= CONMAX)
60706626 962 break;
dfbc7c89 963 if (con->fd >= 0) {
cba09b5f 964 openfd |= (1 << con->fd);
dfbc7c89
WF
965 tcinit(con);
966 continue;
729bfb80 967 }
dfbc7c89
WF
968 if ((con->fd = open(con->tty, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0)
969 continue;
cba09b5f 970 openfd |= (1 << con->fd);
dfbc7c89 971 tcinit(con);
7d8a8d2c 972 }
dfbc7c89 973 ptr = (&consoles)->next;
cba09b5f
KZ
974 usemask = (uint32_t*) mmap(NULL, sizeof(uint32_t),
975 PROT_READ|PROT_WRITE,
976 MAP_ANONYMOUS|MAP_SHARED, -1, 0);
7d8a8d2c 977
dfbc7c89
WF
978 if (ptr->next == &consoles) {
979 con = list_entry(ptr, struct console, entry);
980 goto nofork;
981 }
729bfb80 982
dfbc7c89
WF
983 mask_signal(SIGCHLD, chld_handler, &saved_sigchld);
984 do {
985 con = list_entry(ptr, struct console, entry);
986 if (con->id >= CONMAX)
987 break;
988
989 switch ((con->pid = fork())) {
990 case 0:
991 mask_signal(SIGCHLD, SIG_DFL, NULL);
992 /* fall through */
993 nofork:
994 setup(con);
995 while (1) {
996 const char *passwd = pwd->pw_passwd;
997 const char *answer;
998 int failed = 0, doshell = 0;
999
1000 doprompt(passwd, con);
1001 if ((answer = getpasswd(con)) == NULL)
1002 break;
1003
1004 if (passwd[0] == '\0')
1005 doshell++;
1006 else {
1007 const char *cryptbuf;
1008 cryptbuf = crypt(answer, passwd);
1009 if (cryptbuf == NULL)
4b3624ca 1010 warn(_("crypt failed"));
dfbc7c89
WF
1011 else if (strcmp(cryptbuf, pwd->pw_passwd) == 0)
1012 doshell++;
1013 }
1014
1015 if (doshell) {
1016 *usemask |= (1<<con->id);
1017 sushell(pwd);
1018 *usemask &= ~(1<<con->id);
1019 failed++;
1020 }
1021
1022 mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
1023 mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
1024 mask_signal(SIGINT, SIG_IGN, &saved_sigint);
1025
1026 if (failed) {
1027 fprintf(stderr, _("Can not execute su shell\n\n"));
1028 break;
1029 }
1030 fprintf(stderr, _("Login incorrect\n\n"));
1031 }
1032 if (alarm_rised) {
1033 tcfinal(con);
1034 warnx(_("Timed out\n\n"));
1035 }
1036 /*
1037 * User pressed Control-D.
1038 */
1039 exit(0);
1040 case -1:
4b3624ca 1041 warn(_("fork failed"));
dfbc7c89
WF
1042 /* fall through */
1043 default:
1044 break;
1045 }
1046
1047 ptr = ptr->next;
1048
1049 } while (ptr != &consoles);
1050
1051 while ((pid = wait(&status))) {
1052 if (errno == ECHILD)
1053 break;
1054 if (pid < 0)
1055 continue;
1056 list_for_each(ptr, &consoles) {
1057 con = list_entry(ptr, struct console, entry);
1058 if (con->pid == pid) {
1059 *usemask &= ~(1<<con->id);
1060 continue;
1061 }
1062 if (kill(con->pid, 0) < 0) {
1063 *usemask &= ~(1<<con->id);
1064 continue;
1065 }
1066 if (*usemask & (1<<con->id))
1067 continue;
1068 kill(con->pid, SIGHUP);
559a5b6c 1069 xusleep(50000);
dfbc7c89
WF
1070 kill(con->pid, SIGKILL);
1071 }
1072 }
1073
1074 mask_signal(SIGCHLD, SIG_DFL, NULL);
cd8703ad 1075 return EXIT_SUCCESS;
7d8a8d2c 1076}