* sulogin - add the possibility to reset the terminal io
[sysvinit.git] / src / sulogin.c
1 /*
2 * sulogin This program gives Linux machines a reasonable
3 * secure way to boot single user. It forces the
4 * user to supply the root password before a
5 * shell is started.
6 *
7 * If there is a shadow password file and the
8 * encrypted root password is "x" the shadow
9 * password will be used.
10 *
11 * Version: @(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl
12 *
13 * Copyright (C) 1998-2003 Miquel van Smoorenburg.
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28 *
29 */
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <pwd.h>
40 #include <shadow.h>
41 #include <termios.h>
42 #include <sys/ttydefaults.h>
43 #include <errno.h>
44 #include <sys/ioctl.h>
45 #if defined(__GLIBC__)
46 # include <crypt.h>
47 #endif
48
49 #ifdef WITH_SELINUX
50 # include <selinux/selinux.h>
51 # include <selinux/get_context_list.h>
52 #endif
53
54 #define CHECK_DES 1
55 #define CHECK_MD5 1
56
57 #define F_PASSWD "/etc/passwd"
58 #define F_SHADOW "/etc/shadow"
59 #define BINSH "/bin/sh"
60 #define STATICSH "/bin/sash"
61
62 char *Version = "@(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl";
63
64 static int timeout;
65 static int profile;
66
67 static void (*saved_sigint) = SIG_DFL;
68 static void (*saved_sigtstp) = SIG_DFL;
69 static void (*saved_sigquit) = SIG_DFL;
70
71 #ifndef IUCLC
72 # define IUCLC 0
73 #endif
74
75 #if defined(SANE_TIO) && (SANE_TIO == 1)
76 /*
77 * Fix the tty modes and set reasonable defaults.
78 * (I'm not sure if this is needed under Linux, but..)
79 */
80 static
81 void fixtty(void)
82 {
83 struct termios tty;
84 int serial;
85
86 /* Skip serial console */
87 if (ioctl (0, TIOCMGET, (char*)&serial) == 0)
88 goto out;
89 /* Expected error */
90 serial = errno = 0;
91
92 tcgetattr(0, &tty);
93
94 /* Use defaults of <sys/ttydefaults.h> for base settings */
95 tty.c_iflag |= TTYDEF_IFLAG;
96 tty.c_oflag |= TTYDEF_OFLAG;
97 tty.c_lflag |= TTYDEF_LFLAG;
98 tty.c_cflag |= (TTYDEF_SPEED | TTYDEF_CFLAG);
99
100 /* Sane setting, allow eight bit characters, no carriage return delay
101 * the same result as `stty sane cr0 pass8'
102 */
103 tty.c_iflag |= (BRKINT | ICRNL | IMAXBEL);
104 #ifdef IUTF8 /* Not defined on FreeBSD */
105 tty.c_iflag |= IUTF8;
106 #endif /* IUTF8 */
107 tty.c_iflag &= ~(IGNBRK | INLCR | IGNCR | IXOFF | IUCLC | IXANY | ISTRIP);
108 tty.c_oflag |= (OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0);
109 tty.c_oflag &= ~(OLCUC | OCRNL | ONOCR | ONLRET | OFILL | OFDEL |\
110 NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
111 tty.c_lflag |= (ISIG | ICANON | IEXTEN | ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE);
112 tty.c_lflag &= ~(ECHONL | NOFLSH | XCASE | TOSTOP | ECHOPRT);
113 tty.c_cflag |= (CREAD | CS8 | B9600);
114 tty.c_cflag &= ~(PARENB);
115
116 /* VTIME and VMIN can overlap with VEOF and VEOL since they are
117 * only used for non-canonical mode. We just set the at the
118 * beginning, so nothing bad should happen.
119 */
120 tty.c_cc[VTIME] = 0;
121 tty.c_cc[VMIN] = 1;
122 tty.c_cc[VINTR] = CINTR;
123 tty.c_cc[VQUIT] = CQUIT;
124 tty.c_cc[VERASE] = CERASE; /* ASCII DEL (0177) */
125 tty.c_cc[VKILL] = CKILL;
126 tty.c_cc[VEOF] = CEOF;
127 tty.c_cc[VSWTC] = _POSIX_VDISABLE;
128 tty.c_cc[VSTART] = CSTART;
129 tty.c_cc[VSTOP] = CSTOP;
130 tty.c_cc[VSUSP] = CSUSP;
131 tty.c_cc[VEOL] = _POSIX_VDISABLE;
132 tty.c_cc[VREPRINT] = CREPRINT;
133 tty.c_cc[VDISCARD] = CDISCARD;
134 tty.c_cc[VWERASE] = CWERASE;
135 tty.c_cc[VLNEXT] = CLNEXT;
136 tty.c_cc[VEOL2] = _POSIX_VDISABLE;
137
138 tcsetattr(0, TCSANOW, &tty);
139 out:
140 return;
141 }
142 #endif
143
144
145 /*
146 * Called at timeout.
147 */
148 static
149 # ifdef __GNUC__
150 void alrm_handler(int sig __attribute__((unused)))
151 # else
152 void alrm_handler(int sig)
153 # endif
154 {
155 }
156
157 /*
158 * See if an encrypted password is valid. The encrypted
159 * password is checked for traditional-style DES and
160 * FreeBSD-style MD5 encryption.
161 */
162 static
163 int valid(char *pass)
164 {
165 char *s;
166 int len;
167
168 if (pass[0] == 0) return 1;
169 #if CHECK_MD5
170 /*
171 * 3 bytes for the signature $1$
172 * up to 8 bytes for the salt
173 * $
174 * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes
175 */
176 if (strncmp(pass, "$1$", 3) == 0) {
177 for(s = pass + 3; *s && *s != '$'; s++)
178 ;
179 if (*s++ != '$') return 0;
180 len = strlen(s);
181 if (len < 22 || len > 24) return 0;
182
183 return 1;
184 }
185 #endif
186 #if CHECK_DES
187 if (strlen(pass) != 13) return 0;
188 for (s = pass; *s; s++) {
189 if ((*s < '0' || *s > '9') &&
190 (*s < 'a' || *s > 'z') &&
191 (*s < 'A' || *s > 'Z') &&
192 *s != '.' && *s != '/') return 0;
193 }
194 #endif
195 return 1;
196 }
197
198 /*
199 * Set a variable if the value is not NULL.
200 */
201 static
202 void set(char **var, char *val)
203 {
204 if (val) *var = val;
205 }
206
207 /*
208 * Get the root password entry.
209 */
210 static
211 struct passwd *getrootpwent(int try_manually)
212 {
213 static struct passwd pwd;
214 struct passwd *pw;
215 struct spwd *spw;
216 FILE *fp;
217 static char line[256];
218 static char sline[256];
219 char *p;
220
221 /*
222 * First, we try to get the password the standard
223 * way using normal library calls.
224 */
225 if ((pw = getpwnam("root")) &&
226 !strcmp(pw->pw_passwd, "x") &&
227 (spw = getspnam("root")))
228 pw->pw_passwd = spw->sp_pwdp;
229 if (pw || !try_manually) return pw;
230
231 /*
232 * If we come here, we could not retrieve the root
233 * password through library calls and we try to
234 * read the password and shadow files manually.
235 */
236 pwd.pw_name = "root";
237 pwd.pw_passwd = "";
238 pwd.pw_gecos = "Super User";
239 pwd.pw_dir = "/";
240 pwd.pw_shell = "";
241 pwd.pw_uid = 0;
242 pwd.pw_gid = 0;
243
244 if ((fp = fopen(F_PASSWD, "r")) == NULL) {
245 perror(F_PASSWD);
246 return &pwd;
247 }
248
249 /*
250 * Find root in the password file.
251 */
252 while((p = fgets(line, 256, fp)) != NULL) {
253 if (strncmp(line, "root:", 5) != 0)
254 continue;
255 p += 5;
256 set(&pwd.pw_passwd, strsep(&p, ":"));
257 (void)strsep(&p, ":");
258 (void)strsep(&p, ":");
259 set(&pwd.pw_gecos, strsep(&p, ":"));
260 set(&pwd.pw_dir, strsep(&p, ":"));
261 set(&pwd.pw_shell, strsep(&p, "\n"));
262 p = line;
263 break;
264 }
265 fclose(fp);
266
267 /*
268 * If the encrypted password is valid
269 * or not found, return.
270 */
271 if (p == NULL) {
272 fprintf(stderr, "%s: no entry for root\n", F_PASSWD);
273 return &pwd;
274 }
275 if (valid(pwd.pw_passwd)) return &pwd;
276
277 /*
278 * The password is invalid. If there is a
279 * shadow password, try it.
280 */
281 strcpy(pwd.pw_passwd, "");
282 if ((fp = fopen(F_SHADOW, "r")) == NULL) {
283 fprintf(stderr, "%s: root password garbled\n", F_PASSWD);
284 return &pwd;
285 }
286 while((p = fgets(sline, 256, fp)) != NULL) {
287 if (strncmp(sline, "root:", 5) != 0)
288 continue;
289 p += 5;
290 set(&pwd.pw_passwd, strsep(&p, ":"));
291 break;
292 }
293 fclose(fp);
294
295 /*
296 * If the password is still invalid,
297 * NULL it, and return.
298 */
299 if (p == NULL) {
300 fprintf(stderr, "%s: no entry for root\n", F_SHADOW);
301 strcpy(pwd.pw_passwd, "");
302 }
303 if (!valid(pwd.pw_passwd)) {
304 fprintf(stderr, "%s: root password garbled\n", F_SHADOW);
305 strcpy(pwd.pw_passwd, ""); }
306 return &pwd;
307 }
308
309 /*
310 * Ask for the password. Note that there is no
311 * default timeout as we normally skip this during boot.
312 */
313 static
314 char *getpasswd(char *crypted)
315 {
316 struct sigaction sa;
317 struct termios old, tty;
318 static char pass[128];
319 char *ret = pass;
320 int i;
321
322 if (crypted[0]) {
323 printf("Give root password for login: ");
324 } else
325 printf("Press enter for login: ");
326 fflush(stdout);
327
328 tcgetattr(0, &old);
329 tcgetattr(0, &tty);
330 tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
331 tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
332 tcsetattr(0, TCSANOW, &tty);
333
334 pass[sizeof(pass) - 1] = 0;
335
336 sa.sa_handler = alrm_handler;
337 sa.sa_flags = 0;
338 sigaction(SIGALRM, &sa, NULL);
339 if (timeout) alarm(timeout);
340
341 if (read(0, pass, sizeof(pass) - 1) <= 0)
342 ret = NULL;
343 else {
344 for(i = 0; i < (int)sizeof(pass) && pass[i]; i++)
345 if (pass[i] == '\r' || pass[i] == '\n') {
346 pass[i] = 0;
347 break;
348 }
349 }
350 alarm(0);
351 tcsetattr(0, TCSANOW, &old);
352 printf("\n");
353
354 return ret;
355 }
356
357 /*
358 * Password was OK, execute a shell.
359 */
360 static
361 void sushell(struct passwd *pwd)
362 {
363 char shell[128];
364 char home[128];
365 char *p;
366 char *sushell;
367
368 /*
369 * Set directory and shell.
370 */
371 (void)chdir(pwd->pw_dir);
372 if ((p = getenv("SUSHELL")) != NULL)
373 sushell = p;
374 else if ((p = getenv("sushell")) != NULL)
375 sushell = p;
376 else {
377 if (pwd->pw_shell[0])
378 sushell = pwd->pw_shell;
379 else
380 sushell = BINSH;
381 }
382 if ((p = strrchr(sushell, '/')) == NULL)
383 p = sushell;
384 else
385 p++;
386 snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p);
387
388 /*
389 * Set some important environment variables.
390 */
391 getcwd(home, sizeof(home));
392 setenv("HOME", home, 1);
393 setenv("LOGNAME", "root", 1);
394 setenv("USER", "root", 1);
395 if (!profile)
396 setenv("SHLVL","0",1);
397
398 /*
399 * Try to execute a shell.
400 */
401 setenv("SHELL", sushell, 1);
402 signal(SIGINT, saved_sigint);
403 signal(SIGTSTP, saved_sigtstp);
404 signal(SIGQUIT, saved_sigquit);
405 #ifdef WITH_SELINUX
406 if (is_selinux_enabled > 0) {
407 security_context_t scon=NULL;
408 char *seuser=NULL;
409 char *level=NULL;
410 if (getseuserbyname("root", &seuser, &level) == 0)
411 if (get_default_context_with_level(seuser, level, 0, &scon) > 0) {
412 if (setexeccon(scon) != 0)
413 fprintf(stderr, "setexeccon faile\n");
414 freecon(scon);
415 }
416 free(seuser);
417 free(level);
418 }
419 #endif
420 execl(sushell, shell, NULL);
421 perror(sushell);
422
423 setenv("SHELL", BINSH, 1);
424 execl(BINSH, profile ? "-sh" : "sh", NULL);
425 perror(BINSH);
426
427 /* Fall back to staticly linked shell if both the users shell
428 and /bin/sh failed to execute. */
429 setenv("SHELL", STATICSH, 1);
430 execl(STATICSH, STATICSH, NULL);
431 perror(STATICSH);
432 }
433
434 static
435 void usage(void)
436 {
437 fprintf(stderr, "Usage: sulogin [-e] [-p] [-t timeout] [tty device]\n");
438 }
439
440 int main(int argc, char **argv)
441 {
442 char *tty = NULL;
443 char *p;
444 struct passwd *pwd;
445 int c, fd = -1;
446 int opt_e = 0;
447 pid_t pid, pgrp, ppgrp, ttypgrp;
448
449 /*
450 * See if we have a timeout flag.
451 */
452 opterr = 0;
453 while((c = getopt(argc, argv, "ept:")) != EOF) switch(c) {
454 case 't':
455 timeout = atoi(optarg);
456 break;
457 case 'p':
458 profile = 1;
459 break;
460 case 'e':
461 opt_e = 1;
462 break;
463 default:
464 usage();
465 /* Do not exit! */
466 break;
467 }
468
469 if (geteuid() != 0) {
470 fprintf(stderr, "sulogin: only root can run sulogin.\n");
471 exit(1);
472 }
473
474 /*
475 * See if we need to open an other tty device.
476 */
477 saved_sigint = signal(SIGINT, SIG_IGN);
478 saved_sigtstp = signal(SIGQUIT, SIG_IGN);
479 saved_sigquit = signal(SIGTSTP, SIG_IGN);
480 if (optind < argc) tty = argv[optind];
481
482 if (tty || (tty = getenv("CONSOLE"))) {
483
484 if ((fd = open(tty, O_RDWR)) < 0) {
485 perror(tty);
486 fd = dup(0);
487 }
488
489 if (!isatty(fd)) {
490 fprintf(stderr, "%s: not a tty\n", tty);
491 close(fd);
492 } else {
493
494 /*
495 * Only go through this trouble if the new
496 * tty doesn't fall in this process group.
497 */
498 pid = getpid();
499 pgrp = getpgid(0);
500 ppgrp = getpgid(getppid());
501 ttypgrp = tcgetpgrp(fd);
502
503 if (pgrp != ttypgrp && ppgrp != ttypgrp) {
504 if (pid != getsid(0)) {
505 if (pid == getpgid(0))
506 setpgid(0, getpgid(getppid()));
507 setsid();
508 }
509
510 signal(SIGHUP, SIG_IGN);
511 if (ttypgrp > 0)
512 ioctl(0, TIOCNOTTY, (char *)1);
513 signal(SIGHUP, SIG_DFL);
514 close(0);
515 close(1);
516 close(2);
517 if (fd > 2)
518 close(fd);
519 if ((fd = open(tty, O_RDWR)) < 0) {
520 perror(tty);
521 } else {
522 ioctl(0, TIOCSCTTY, (char *)1);
523 tcsetpgrp(fd, ppgrp);
524 dup2(fd, 0);
525 dup2(fd, 1);
526 dup2(fd, 2);
527 if (fd > 2)
528 close(fd);
529 }
530 } else
531 if (fd > 2)
532 close(fd);
533 }
534 } else if (getpid() == 1) {
535 /* We are init. We hence need to set a session anyway */
536 setsid();
537 if (ioctl(0, TIOCSCTTY, (char *)1))
538 perror("ioctl(TIOCSCTTY)");
539 }
540
541 #if defined(SANE_TIO) && (SANE_TIO == 1)
542 fixtty();
543 #endif
544
545 /*
546 * Get the root password.
547 */
548 if ((pwd = getrootpwent(opt_e)) == NULL) {
549 fprintf(stderr, "sulogin: cannot open password database!\n");
550 sleep(2);
551 }
552
553 /*
554 * Ask for the password.
555 */
556 while(pwd) {
557 if ((p = getpasswd(pwd->pw_passwd)) == NULL) break;
558 if (pwd->pw_passwd[0] == 0 ||
559 strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0)
560 sushell(pwd);
561 saved_sigquit = signal(SIGQUIT, SIG_IGN);
562 saved_sigtstp = signal(SIGTSTP, SIG_IGN);
563 saved_sigint = signal(SIGINT, SIG_IGN);
564 printf("Login incorrect.\n");
565 }
566
567 /*
568 * User pressed Control-D.
569 */
570 return 0;
571 }