]>
Commit | Line | Data |
---|---|---|
a74aeac6 PR |
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 | ||
35ec6446 | 31 | #include <sys/mman.h> |
a74aeac6 PR |
32 | #include <sys/types.h> |
33 | #include <sys/stat.h> | |
35ec6446 | 34 | #include <sys/wait.h> |
a74aeac6 PR |
35 | #include <stdio.h> |
36 | #include <string.h> | |
37 | #include <stdlib.h> | |
38 | #include <unistd.h> | |
39 | #include <fcntl.h> | |
40 | #include <signal.h> | |
41 | #include <pwd.h> | |
42 | #include <shadow.h> | |
43 | #include <termios.h> | |
c5b4c135 | 44 | #include <sys/ttydefaults.h> |
a74aeac6 PR |
45 | #include <errno.h> |
46 | #include <sys/ioctl.h> | |
35ec6446 DWF |
47 | #ifdef __linux__ |
48 | # include <sys/statfs.h> | |
49 | # include <sys/mount.h> | |
50 | # include <linux/fs.h> | |
51 | # include <linux/magic.h> | |
52 | # include <linux/major.h> | |
53 | # ifndef TMPFS_MAGIC | |
54 | # define TMPFS_MAGIC 0x01021994 | |
55 | # endif | |
56 | # ifndef MNT_DETACH | |
57 | # define MNT_DETACH 2 | |
58 | # endif | |
f12c0737 | 59 | # define dovoid(f) if ((f)){} |
35ec6446 DWF |
60 | #endif |
61 | ||
62 | #define BS CTRL('h') | |
63 | #define NL CTRL('j') | |
64 | #define CR CTRL('m') | |
a74aeac6 PR |
65 | |
66 | #ifdef WITH_SELINUX | |
67 | # include <selinux/selinux.h> | |
68 | # include <selinux/get_context_list.h> | |
69 | #endif | |
70 | ||
35ec6446 DWF |
71 | #include "consoles.h" |
72 | #define CONMAX 16 | |
73 | ||
a74aeac6 PR |
74 | #define CHECK_DES 1 |
75 | #define CHECK_MD5 1 | |
76 | ||
77 | #define F_PASSWD "/etc/passwd" | |
78 | #define F_SHADOW "/etc/shadow" | |
79 | #define BINSH "/bin/sh" | |
80 | #define STATICSH "/bin/sash" | |
81 | ||
82 | char *Version = "@(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl"; | |
83 | ||
c5b4c135 DWF |
84 | static int timeout; |
85 | static int profile; | |
35ec6446 DWF |
86 | static volatile uint32_t openfd; /* Remember higher file descriptors */ |
87 | static volatile uint32_t *usemask; | |
c5b4c135 | 88 | |
35ec6446 DWF |
89 | static sighandler_t saved_sigint = SIG_DFL; |
90 | static sighandler_t saved_sigtstp = SIG_DFL; | |
91 | static sighandler_t saved_sigquit = SIG_DFL; | |
92 | static sighandler_t saved_sighup = SIG_DFL; | |
a74aeac6 | 93 | |
82111852 | 94 | static volatile sig_atomic_t alarm_rised; |
35ec6446 | 95 | static volatile sig_atomic_t sigchild; |
82111852 | 96 | |
a74aeac6 PR |
97 | #ifndef IUCLC |
98 | # define IUCLC 0 | |
99 | #endif | |
100 | ||
a74aeac6 PR |
101 | /* |
102 | * Fix the tty modes and set reasonable defaults. | |
a74aeac6 | 103 | */ |
c5b4c135 | 104 | static |
35ec6446 | 105 | void tcinit(struct console *con) |
a74aeac6 | 106 | { |
35ec6446 DWF |
107 | int serial, flags; |
108 | struct termios *tio = &con->tio; | |
109 | int fd = con->fd; | |
c5b4c135 | 110 | |
c5b4c135 DWF |
111 | /* Expected error */ |
112 | serial = errno = 0; | |
a74aeac6 | 113 | |
35ec6446 DWF |
114 | /* Get line attributes */ |
115 | if (tcgetattr(fd, tio) < 0) { | |
116 | con->flags |= CON_NOTTY; | |
117 | return; | |
118 | } | |
119 | ||
120 | /* Handle serial lines here */ | |
121 | if (ioctl (fd, TIOCMGET, (char*)&serial) == 0) { | |
122 | speed_t ispeed, ospeed; | |
123 | struct winsize ws; | |
124 | ||
125 | /* this is a modem line */ | |
126 | con->flags |= CON_SERIAL; | |
127 | ||
128 | /* Flush input and output queues on modem lines */ | |
129 | (void) tcflush(fd, TCIOFLUSH); | |
130 | ||
131 | ispeed = cfgetispeed(tio); | |
132 | ospeed = cfgetospeed(tio); | |
133 | ||
134 | if (!ispeed) ispeed = TTYDEF_SPEED; | |
135 | if (!ospeed) ospeed = TTYDEF_SPEED; | |
a74aeac6 | 136 | |
35ec6446 DWF |
137 | tio->c_iflag = tio->c_lflag = tio->c_oflag = 0; |
138 | tio->c_cflag = CREAD | CS8 | HUPCL | (tio->c_cflag & CLOCAL); | |
139 | ||
140 | cfsetispeed(tio, ispeed); | |
141 | cfsetospeed(tio, ospeed); | |
142 | ||
143 | tio->c_line = 0; | |
144 | tio->c_cc[VTIME] = 0; | |
145 | tio->c_cc[VMIN] = 1; | |
146 | ||
147 | if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { | |
148 | int set = 0; | |
149 | if (ws.ws_row == 0) { | |
150 | ws.ws_row = 24; | |
151 | set++; | |
152 | } | |
153 | if (ws.ws_col == 0) { | |
154 | ws.ws_col = 80; | |
155 | set++; | |
156 | } | |
157 | (void)ioctl(fd, TIOCSWINSZ, &ws); | |
158 | } | |
159 | ||
160 | goto setattr; | |
161 | } | |
162 | #if defined(SANE_TIO) && (SANE_TIO == 1) | |
163 | /* | |
164 | * Use defaults of <sys/ttydefaults.h> for base settings | |
165 | * of a local terminal line like a virtual console. | |
166 | */ | |
167 | tio->c_iflag |= TTYDEF_IFLAG; | |
168 | tio->c_oflag |= TTYDEF_OFLAG; | |
169 | tio->c_lflag |= TTYDEF_LFLAG; | |
170 | # ifdef CBAUD | |
171 | tio->c_lflag &= ~CBAUD; | |
172 | # endif | |
173 | tio->c_cflag |= (B38400 | TTYDEF_CFLAG); | |
a74aeac6 | 174 | |
c5b4c135 DWF |
175 | /* Sane setting, allow eight bit characters, no carriage return delay |
176 | * the same result as `stty sane cr0 pass8' | |
177 | */ | |
35ec6446 DWF |
178 | tio->c_iflag |= (BRKINT | ICRNL | IMAXBEL); |
179 | tio->c_iflag &= ~(IGNBRK | INLCR | IGNCR | IXOFF | IUCLC | IXANY | INPCK | ISTRIP); | |
180 | tio->c_oflag |= (OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0); | |
181 | tio->c_oflag &= ~(OLCUC | OCRNL | ONOCR | ONLRET | OFILL | OFDEL |\ | |
c5b4c135 | 182 | NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); |
35ec6446 DWF |
183 | tio->c_lflag |= (ISIG | ICANON | IEXTEN | ECHO|ECHOE|ECHOK|ECHOKE); |
184 | tio->c_lflag &= ~(ECHONL|ECHOCTL|ECHOPRT | NOFLSH | XCASE | TOSTOP); | |
185 | tio->c_cflag |= (CREAD | CS8 | HUPCL); | |
186 | tio->c_cflag &= ~(PARODD | PARENB); | |
c5b4c135 | 187 | |
35ec6446 DWF |
188 | /* |
189 | * VTIME and VMIN can overlap with VEOF and VEOL since they are | |
c5b4c135 DWF |
190 | * only used for non-canonical mode. We just set the at the |
191 | * beginning, so nothing bad should happen. | |
a74aeac6 | 192 | */ |
35ec6446 DWF |
193 | tio->c_cc[VTIME] = 0; |
194 | tio->c_cc[VMIN] = CMIN; | |
195 | tio->c_cc[VINTR] = CINTR; | |
196 | tio->c_cc[VQUIT] = CQUIT; | |
197 | tio->c_cc[VERASE] = CERASE; /* ASCII DEL (0177) */ | |
198 | tio->c_cc[VKILL] = CKILL; | |
199 | tio->c_cc[VEOF] = CEOF; | |
200 | # ifdef VSWTC | |
201 | tio->c_cc[VSWTC] = _POSIX_VDISABLE; | |
202 | # else | |
203 | tio->c_cc[VSWTCH] = _POSIX_VDISABLE; | |
204 | # endif | |
205 | tio->c_cc[VSTART] = CSTART; | |
206 | tio->c_cc[VSTOP] = CSTOP; | |
207 | tio->c_cc[VSUSP] = CSUSP; | |
208 | tio->c_cc[VEOL] = _POSIX_VDISABLE; | |
209 | tio->c_cc[VREPRINT] = CREPRINT; | |
210 | tio->c_cc[VDISCARD] = CDISCARD; | |
211 | tio->c_cc[VWERASE] = CWERASE; | |
212 | tio->c_cc[VLNEXT] = CLNEXT; | |
213 | tio->c_cc[VEOL2] = _POSIX_VDISABLE; | |
214 | #endif | |
215 | setattr: | |
216 | /* Set line attributes */ | |
217 | tcsetattr(fd, TCSANOW, tio); | |
218 | ||
219 | /* Enable blocking mode for read and write */ | |
220 | if ((flags = fcntl(fd, F_GETFL, 0)) != -1) | |
221 | (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); | |
a74aeac6 | 222 | } |
35ec6446 DWF |
223 | |
224 | ||
225 | /* | |
226 | * Finalize the tty modes on modem lines. | |
227 | */ | |
228 | static | |
229 | void tcfinal(struct console *con) | |
230 | { | |
35ec6446 DWF |
231 | struct termios *tio = &con->tio; |
232 | int fd = con->fd; | |
233 | ||
234 | /* Expected error */ | |
c3f589ff | 235 | errno = 0; |
35ec6446 DWF |
236 | |
237 | if ((con->flags & CON_SERIAL) == 0) { | |
238 | #ifdef __linux__ | |
239 | setenv("TERM", "linux", 1); | |
240 | #else | |
241 | setenv("TERM", "vt100", 1); | |
a74aeac6 | 242 | #endif |
35ec6446 DWF |
243 | return; |
244 | } | |
245 | if (con->flags & CON_NOTTY) | |
246 | return; | |
247 | setenv("TERM", "vt100", 1); | |
248 | ||
249 | tio->c_iflag |= (IXON | IXOFF); | |
250 | tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE); | |
251 | tio->c_oflag |= OPOST; | |
252 | ||
253 | tio->c_cc[VINTR] = CINTR; | |
254 | tio->c_cc[VQUIT] = CQUIT; | |
255 | tio->c_cc[VERASE] = con->cp.erase; | |
256 | tio->c_cc[VKILL] = con->cp.kill; | |
257 | tio->c_cc[VEOF] = CEOF; | |
258 | #ifdef VSWTC | |
259 | tio->c_cc[VSWTC] = _POSIX_VDISABLE; | |
260 | #else | |
261 | tio->c_cc[VSWTCH] = _POSIX_VDISABLE; | |
262 | #endif | |
263 | tio->c_cc[VSTART] = CSTART; | |
264 | tio->c_cc[VSTOP] = CSTOP; | |
265 | tio->c_cc[VSUSP] = CSUSP; | |
266 | tio->c_cc[VEOL] = _POSIX_VDISABLE; | |
267 | ||
268 | if (con->cp.eol == CR) { | |
269 | tio->c_iflag |= ICRNL; | |
270 | tio->c_iflag &= ~(INLCR|IGNCR); | |
271 | tio->c_oflag |= ONLCR; | |
272 | tio->c_oflag &= ~(OCRNL|ONLRET); | |
273 | } | |
a74aeac6 | 274 | |
35ec6446 DWF |
275 | switch (con->cp.parity) { |
276 | default: | |
277 | case 0: | |
278 | tio->c_cflag &= ~(PARODD | PARENB); | |
279 | tio->c_iflag &= ~(INPCK | ISTRIP); | |
280 | break; | |
281 | case 1: /* odd parity */ | |
282 | tio->c_cflag |= PARODD; | |
283 | /* fall through */ | |
284 | case 2: /* even parity */ | |
285 | tio->c_cflag |= PARENB; | |
286 | tio->c_iflag |= (INPCK | ISTRIP); | |
287 | /* fall through */ | |
288 | case (1 | 2): /* no parity bit */ | |
289 | tio->c_cflag &= ~CSIZE; | |
290 | tio->c_cflag |= CS7; | |
291 | break; | |
292 | } | |
293 | ||
294 | /* Set line attributes */ | |
295 | (void)tcsetattr(fd, TCSANOW, tio); | |
296 | } | |
a74aeac6 PR |
297 | |
298 | /* | |
299 | * Called at timeout. | |
300 | */ | |
c5b4c135 DWF |
301 | static |
302 | # ifdef __GNUC__ | |
35ec6446 | 303 | __attribute__((__noinline__)) |
c5b4c135 DWF |
304 | void alrm_handler(int sig __attribute__((unused))) |
305 | # else | |
306 | void alrm_handler(int sig) | |
307 | # endif | |
a74aeac6 | 308 | { |
82111852 | 309 | alarm_rised++; |
35ec6446 | 310 | } |
82111852 | 311 | |
35ec6446 DWF |
312 | /* |
313 | * Called at timeout. | |
314 | */ | |
315 | static | |
316 | # ifdef __GNUC__ | |
317 | __attribute__((__noinline__)) | |
318 | void chld_handler(int sig __attribute__((unused))) | |
319 | # else | |
320 | void chld_handler(int sig) | |
321 | # endif | |
322 | { | |
323 | sigchild++; | |
a74aeac6 PR |
324 | } |
325 | ||
326 | /* | |
327 | * See if an encrypted password is valid. The encrypted | |
328 | * password is checked for traditional-style DES and | |
329 | * FreeBSD-style MD5 encryption. | |
330 | */ | |
c5b4c135 | 331 | static |
22786b24 | 332 | int valid(const char *pass) |
a74aeac6 | 333 | { |
22786b24 DWF |
334 | const char *s; |
335 | char id[5]; | |
336 | size_t len; | |
337 | off_t off; | |
a74aeac6 PR |
338 | |
339 | if (pass[0] == 0) return 1; | |
340 | #if CHECK_MD5 | |
22786b24 DWF |
341 | if (pass[0] != '$') goto check_des; |
342 | ||
343 | /* | |
344 | * up to 4 bytes for the signature e.g. $1$ | |
345 | */ | |
346 | for(s = pass+1; *s && *s != '$'; s++) | |
347 | ; | |
348 | if (*s++ != '$') return 0; | |
349 | if ((off = (off_t)(s-pass)) > 4 || off < 3) return 0; | |
350 | ||
351 | memset(id, '\0', sizeof(id)); | |
352 | strncpy(id, pass, off); | |
353 | ||
354 | /* | |
355 | * up to 16 bytes for the salt | |
356 | */ | |
357 | for(; *s && *s != '$'; s++) | |
358 | ; | |
359 | if (*s++ != '$') return 0; | |
360 | if ((off_t)(s-pass) > 16) return 0; | |
361 | len = strlen(s); | |
362 | ||
a74aeac6 | 363 | /* |
a74aeac6 PR |
364 | * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes |
365 | */ | |
22786b24 DWF |
366 | if ((strcmp(id, "$1$") == 0) && (len < 22 || len > 24)) return 0; |
367 | ||
368 | /* | |
369 | * the SHA-256 hash 43 bytes | |
370 | */ | |
371 | if ((strcmp(id, "$5$") == 0) && (len < 42 || len > 44)) return 0; | |
372 | ||
373 | /* | |
374 | * the SHA-512 hash 86 bytes | |
375 | */ | |
376 | if ((strcmp(id, "$6$") == 0) && (len < 85 || len > 87)) return 0; | |
377 | ||
378 | /* | |
379 | * e.g. Blowfish hash | |
380 | */ | |
381 | return 1; | |
382 | check_des: | |
a74aeac6 PR |
383 | #endif |
384 | #if CHECK_DES | |
385 | if (strlen(pass) != 13) return 0; | |
386 | for (s = pass; *s; s++) { | |
387 | if ((*s < '0' || *s > '9') && | |
388 | (*s < 'a' || *s > 'z') && | |
389 | (*s < 'A' || *s > 'Z') && | |
390 | *s != '.' && *s != '/') return 0; | |
391 | } | |
392 | #endif | |
393 | return 1; | |
394 | } | |
395 | ||
396 | /* | |
397 | * Set a variable if the value is not NULL. | |
398 | */ | |
c5b4c135 | 399 | static |
a74aeac6 PR |
400 | void set(char **var, char *val) |
401 | { | |
402 | if (val) *var = val; | |
403 | } | |
404 | ||
405 | /* | |
406 | * Get the root password entry. | |
407 | */ | |
c5b4c135 | 408 | static |
a74aeac6 PR |
409 | struct passwd *getrootpwent(int try_manually) |
410 | { | |
411 | static struct passwd pwd; | |
412 | struct passwd *pw; | |
413 | struct spwd *spw; | |
414 | FILE *fp; | |
415 | static char line[256]; | |
416 | static char sline[256]; | |
417 | char *p; | |
418 | ||
419 | /* | |
420 | * First, we try to get the password the standard | |
421 | * way using normal library calls. | |
422 | */ | |
423 | if ((pw = getpwnam("root")) && | |
424 | !strcmp(pw->pw_passwd, "x") && | |
425 | (spw = getspnam("root"))) | |
426 | pw->pw_passwd = spw->sp_pwdp; | |
427 | if (pw || !try_manually) return pw; | |
428 | ||
429 | /* | |
430 | * If we come here, we could not retrieve the root | |
431 | * password through library calls and we try to | |
432 | * read the password and shadow files manually. | |
433 | */ | |
434 | pwd.pw_name = "root"; | |
435 | pwd.pw_passwd = ""; | |
436 | pwd.pw_gecos = "Super User"; | |
437 | pwd.pw_dir = "/"; | |
438 | pwd.pw_shell = ""; | |
439 | pwd.pw_uid = 0; | |
440 | pwd.pw_gid = 0; | |
441 | ||
442 | if ((fp = fopen(F_PASSWD, "r")) == NULL) { | |
443 | perror(F_PASSWD); | |
444 | return &pwd; | |
445 | } | |
446 | ||
447 | /* | |
448 | * Find root in the password file. | |
449 | */ | |
450 | while((p = fgets(line, 256, fp)) != NULL) { | |
451 | if (strncmp(line, "root:", 5) != 0) | |
452 | continue; | |
453 | p += 5; | |
454 | set(&pwd.pw_passwd, strsep(&p, ":")); | |
455 | (void)strsep(&p, ":"); | |
456 | (void)strsep(&p, ":"); | |
457 | set(&pwd.pw_gecos, strsep(&p, ":")); | |
458 | set(&pwd.pw_dir, strsep(&p, ":")); | |
459 | set(&pwd.pw_shell, strsep(&p, "\n")); | |
460 | p = line; | |
461 | break; | |
462 | } | |
463 | fclose(fp); | |
464 | ||
465 | /* | |
466 | * If the encrypted password is valid | |
467 | * or not found, return. | |
468 | */ | |
469 | if (p == NULL) { | |
35ec6446 | 470 | fprintf(stderr, "sulogin: %s: no entry for root\n\r", F_PASSWD); |
a74aeac6 PR |
471 | return &pwd; |
472 | } | |
473 | if (valid(pwd.pw_passwd)) return &pwd; | |
474 | ||
475 | /* | |
476 | * The password is invalid. If there is a | |
477 | * shadow password, try it. | |
478 | */ | |
479 | strcpy(pwd.pw_passwd, ""); | |
480 | if ((fp = fopen(F_SHADOW, "r")) == NULL) { | |
35ec6446 | 481 | fprintf(stderr, "sulogin: %s: root password garbled\n\r", F_PASSWD); |
a74aeac6 PR |
482 | return &pwd; |
483 | } | |
484 | while((p = fgets(sline, 256, fp)) != NULL) { | |
485 | if (strncmp(sline, "root:", 5) != 0) | |
486 | continue; | |
487 | p += 5; | |
488 | set(&pwd.pw_passwd, strsep(&p, ":")); | |
489 | break; | |
490 | } | |
491 | fclose(fp); | |
492 | ||
493 | /* | |
494 | * If the password is still invalid, | |
495 | * NULL it, and return. | |
496 | */ | |
497 | if (p == NULL) { | |
35ec6446 | 498 | fprintf(stderr, "sulogin: %s: no entry for root\n\r", F_SHADOW); |
a74aeac6 PR |
499 | strcpy(pwd.pw_passwd, ""); |
500 | } | |
501 | if (!valid(pwd.pw_passwd)) { | |
35ec6446 | 502 | fprintf(stderr, "sulogin: %s: root password garbled\n\r", F_SHADOW); |
a74aeac6 PR |
503 | strcpy(pwd.pw_passwd, ""); } |
504 | return &pwd; | |
505 | } | |
506 | ||
507 | /* | |
35ec6446 | 508 | * Ask by prompt for the password. |
a74aeac6 | 509 | */ |
c5b4c135 | 510 | static |
35ec6446 | 511 | void doprompt(const char *crypted, struct console *con) |
a74aeac6 | 512 | { |
35ec6446 DWF |
513 | struct termios tty; |
514 | ||
515 | if (con->flags & CON_SERIAL) { | |
516 | tty = con->tio; | |
517 | /* | |
518 | * For prompting: map NL in output to CR-NL | |
519 | * otherwise we may see stairs in the output. | |
520 | */ | |
521 | tty.c_oflag |= (ONLCR | OPOST); | |
522 | (void) tcsetattr(con->fd, TCSADRAIN, &tty); | |
523 | } | |
524 | if (con->file == (FILE*)0) { | |
525 | if ((con->file = fdopen(con->fd, "r+")) == (FILE*)0) | |
526 | goto err; | |
527 | } | |
ad6831b9 DWF |
528 | #if defined(USE_ONELINE) |
529 | if (crypted[0]) | |
35ec6446 | 530 | fprintf(con->file, "Give root password for login: "); |
ad6831b9 | 531 | else |
35ec6446 | 532 | fprintf(con->file, "Press enter for login: "); |
ad6831b9 DWF |
533 | #else |
534 | if (crypted[0]) | |
35ec6446 | 535 | fprintf(con->file, "Give root password for maintenance\n\r"); |
ad6831b9 | 536 | else |
35ec6446 DWF |
537 | fprintf(con->file, "Press enter for maintenance"); |
538 | fprintf(con->file, "(or type Control-D to continue): "); | |
ad6831b9 | 539 | #endif |
35ec6446 DWF |
540 | fflush(con->file); |
541 | err: | |
542 | if (con->flags & CON_SERIAL) | |
543 | (void) tcsetattr(con->fd, TCSADRAIN, &con->tio); | |
544 | } | |
a74aeac6 | 545 | |
35ec6446 DWF |
546 | /* |
547 | * Make sure to have an own session and controlling terminal | |
548 | */ | |
549 | static | |
550 | void setup(struct console *con) | |
551 | { | |
552 | pid_t pid, pgrp, ppgrp, ttypgrp; | |
553 | int fd; | |
a74aeac6 | 554 | |
35ec6446 DWF |
555 | if (con->flags & CON_NOTTY) |
556 | return; | |
557 | fd = con->fd; | |
558 | ||
559 | /* | |
560 | * Only go through this trouble if the new | |
561 | * tty doesn't fall in this process group. | |
562 | */ | |
563 | pid = getpid(); | |
564 | pgrp = getpgid(0); | |
565 | ppgrp = getpgid(getppid()); | |
566 | ttypgrp = tcgetpgrp(fd); | |
567 | ||
568 | if (pgrp != ttypgrp && ppgrp != ttypgrp) { | |
569 | if (pid != getsid(0)) { | |
570 | if (pid == getpgid(0)) | |
571 | setpgid(0, getpgid(getppid())); | |
572 | setsid(); | |
573 | } | |
574 | ||
575 | signal(SIGHUP, SIG_IGN); | |
576 | if (ttypgrp > 0) | |
577 | ioctl(0, TIOCNOTTY, (char *)1); | |
578 | signal(SIGHUP, saved_sighup); | |
579 | if (fd > 0) close(0); | |
580 | if (fd > 1) close(1); | |
581 | if (fd > 2) close(2); | |
582 | ||
583 | ioctl(fd, TIOCSCTTY, (char *)1); | |
584 | tcsetpgrp(fd, ppgrp); | |
585 | } | |
586 | dup2(fd, 0); | |
587 | dup2(fd, 1); | |
588 | dup2(fd, 2); | |
589 | con->fd = 0; | |
590 | ||
591 | for (fd = 3; fd < 32; fd++) { | |
592 | if (openfd & (1<<fd)) { | |
593 | close(fd); | |
594 | openfd &= ~(1<<fd); | |
595 | } | |
596 | } | |
597 | } | |
598 | ||
599 | /* | |
600 | * Fetch the password. Note that there is no | |
601 | * default timeout as we normally skip this during boot. | |
602 | */ | |
603 | static | |
604 | char *getpasswd(struct console *con) | |
605 | { | |
606 | static char pass[128], *ptr; | |
607 | struct sigaction sa; | |
608 | struct chardata *cp; | |
609 | struct termios tty; | |
610 | char *ret = pass; | |
611 | unsigned char tc; | |
d32c9251 | 612 | char c, ascval; |
35ec6446 | 613 | int eightbit; |
d32c9251 | 614 | int fd; |
35ec6446 DWF |
615 | |
616 | if (con->flags & CON_NOTTY) | |
617 | goto out; | |
618 | fd = con->fd; | |
619 | cp = &con->cp; | |
620 | ||
621 | tty = con->tio; | |
622 | tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); | |
623 | tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG); | |
624 | tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0); | |
a74aeac6 PR |
625 | |
626 | sa.sa_handler = alrm_handler; | |
627 | sa.sa_flags = 0; | |
628 | sigaction(SIGALRM, &sa, NULL); | |
629 | if (timeout) alarm(timeout); | |
630 | ||
35ec6446 DWF |
631 | ptr = &pass[0]; |
632 | cp->eol = *ptr = '\0'; | |
633 | ||
634 | eightbit = ((con->flags & CON_SERIAL) == 0 || (tty.c_cflag & (PARODD|PARENB)) == 0); | |
e114010a | 635 | while (cp->eol == '\0') { |
35ec6446 DWF |
636 | if (read(fd, &c, 1) < 1) { |
637 | if (errno == EINTR || errno == EAGAIN) { | |
638 | usleep(1000); | |
639 | continue; | |
640 | } | |
641 | ret = (char*)0; | |
642 | switch (errno) { | |
643 | case 0: | |
644 | case EIO: | |
645 | case ESRCH: | |
646 | case EINVAL: | |
647 | case ENOENT: | |
648 | break; | |
649 | default: | |
650 | fprintf(stderr, "sulogin: read(%s): %m\n\r", con->tty); | |
a74aeac6 PR |
651 | break; |
652 | } | |
35ec6446 DWF |
653 | goto quit; |
654 | } | |
655 | ||
656 | if (eightbit) | |
657 | ascval = c; | |
658 | else if (c != (ascval = (c & 0177))) { | |
659 | uint32_t bits, mask; | |
660 | for (bits = 1, mask = 1; mask & 0177; mask <<= 1) { | |
661 | if (mask & ascval) | |
662 | bits++; | |
663 | } | |
664 | cp->parity |= ((bits & 1) ? 1 : 2); | |
665 | } | |
666 | ||
667 | switch (ascval) { | |
e114010a DWF |
668 | case 0: |
669 | *ptr = '\0'; | |
670 | goto quit; | |
35ec6446 DWF |
671 | case CR: |
672 | case NL: | |
673 | *ptr = '\0'; | |
674 | cp->eol = ascval; | |
675 | break; | |
676 | case BS: | |
677 | case CERASE: | |
678 | cp->erase = ascval; | |
679 | if (ptr > &pass[0]) | |
680 | ptr--; | |
681 | break; | |
682 | case CKILL: | |
683 | cp->kill = ascval; | |
684 | while (ptr > &pass[0]) | |
685 | ptr--; | |
686 | break; | |
687 | case CEOF: | |
688 | goto quit; | |
689 | default: | |
690 | if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) { | |
691 | fprintf(stderr, "sulogin: input overrun at %s\n\r", con->tty); | |
692 | ret = (char*)0; | |
693 | goto quit; | |
694 | } | |
695 | *ptr++ = ascval; | |
696 | break; | |
697 | } | |
a74aeac6 | 698 | } |
35ec6446 | 699 | quit: |
a74aeac6 | 700 | alarm(0); |
35ec6446 DWF |
701 | if (tc) |
702 | (void)tcsetattr(fd, TCSAFLUSH, &con->tio); | |
703 | if (ret && *ret != '\0') | |
704 | tcfinal(con); | |
705 | printf("\r\n"); | |
706 | out: | |
a74aeac6 PR |
707 | return ret; |
708 | } | |
709 | ||
710 | /* | |
711 | * Password was OK, execute a shell. | |
712 | */ | |
c5b4c135 | 713 | static |
a74aeac6 PR |
714 | void sushell(struct passwd *pwd) |
715 | { | |
716 | char shell[128]; | |
717 | char home[128]; | |
718 | char *p; | |
719 | char *sushell; | |
720 | ||
721 | /* | |
722 | * Set directory and shell. | |
723 | */ | |
35ec6446 DWF |
724 | if (chdir(pwd->pw_dir) < 0) { |
725 | if (chdir("/") < 0) | |
726 | fprintf(stderr, "sulogin: change of working directory failed: %m\n\r"); | |
727 | } | |
a74aeac6 PR |
728 | if ((p = getenv("SUSHELL")) != NULL) |
729 | sushell = p; | |
730 | else if ((p = getenv("sushell")) != NULL) | |
731 | sushell = p; | |
732 | else { | |
733 | if (pwd->pw_shell[0]) | |
734 | sushell = pwd->pw_shell; | |
735 | else | |
736 | sushell = BINSH; | |
737 | } | |
738 | if ((p = strrchr(sushell, '/')) == NULL) | |
739 | p = sushell; | |
740 | else | |
741 | p++; | |
742 | snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p); | |
743 | ||
744 | /* | |
745 | * Set some important environment variables. | |
746 | */ | |
35ec6446 DWF |
747 | if (getcwd(home, sizeof(home)) == (char*)0) |
748 | strcpy(home, "/"); | |
a74aeac6 PR |
749 | setenv("HOME", home, 1); |
750 | setenv("LOGNAME", "root", 1); | |
751 | setenv("USER", "root", 1); | |
752 | if (!profile) | |
753 | setenv("SHLVL","0",1); | |
754 | ||
755 | /* | |
756 | * Try to execute a shell. | |
757 | */ | |
758 | setenv("SHELL", sushell, 1); | |
c5b4c135 DWF |
759 | signal(SIGINT, saved_sigint); |
760 | signal(SIGTSTP, saved_sigtstp); | |
761 | signal(SIGQUIT, saved_sigquit); | |
35ec6446 | 762 | signal(SIGHUP, SIG_DFL); |
a74aeac6 | 763 | #ifdef WITH_SELINUX |
9af2d863 | 764 | if (is_selinux_enabled() > 0) { |
35ec6446 DWF |
765 | security_context_t scon=NULL; |
766 | char *seuser=NULL; | |
767 | char *level=NULL; | |
768 | if (getseuserbyname("root", &seuser, &level) == 0) | |
769 | if (get_default_context_with_level(seuser, level, 0, &scon) == 0) { | |
770 | if (setexeccon(scon) != 0) | |
771 | fprintf(stderr, "sulogin: setexeccon failed\n\r"); | |
772 | freecon(scon); | |
773 | } | |
a74aeac6 PR |
774 | free(seuser); |
775 | free(level); | |
776 | } | |
777 | #endif | |
778 | execl(sushell, shell, NULL); | |
779 | perror(sushell); | |
780 | ||
781 | setenv("SHELL", BINSH, 1); | |
782 | execl(BINSH, profile ? "-sh" : "sh", NULL); | |
783 | perror(BINSH); | |
784 | ||
785 | /* Fall back to staticly linked shell if both the users shell | |
786 | and /bin/sh failed to execute. */ | |
787 | setenv("SHELL", STATICSH, 1); | |
788 | execl(STATICSH, STATICSH, NULL); | |
789 | perror(STATICSH); | |
790 | } | |
791 | ||
35ec6446 DWF |
792 | #ifdef __linux__ |
793 | /* | |
794 | * Make C library standard calls like ttyname(3) work. | |
795 | */ | |
796 | static uint32_t mounts; | |
797 | #define MNT_PROCFS 0x0001 | |
798 | #define MNT_DEVTMPFS 0x0002 | |
799 | ||
800 | static __attribute__((__noinline__)) | |
801 | void putmounts(void) | |
802 | { | |
803 | if (mounts & MNT_DEVTMPFS) | |
804 | umount2("/dev", MNT_DETACH); | |
805 | if (mounts & MNT_PROCFS) | |
806 | umount2("/proc", MNT_DETACH); | |
807 | } | |
808 | ||
809 | static __attribute__((__constructor__)) | |
810 | void getmounts(void) | |
811 | { | |
812 | struct statfs st; | |
813 | if (statfs("/proc", &st) == 0 && st.f_type != PROC_SUPER_MAGIC) { | |
814 | if (mount("proc", "/proc", "proc", MS_RELATIME, NULL) == 0) | |
815 | mounts |= MNT_PROCFS; | |
816 | } | |
817 | if (statfs("/dev", &st) == 0 && st.f_type != TMPFS_MAGIC) { | |
818 | if (mount("devtmpfs", "/dev", "devtmpfs", MS_RELATIME, "mode=0755,nr_inodes=0") == 0) { | |
819 | mounts |= MNT_DEVTMPFS; | |
820 | (void)mknod("/dev/console", S_IFCHR|S_IRUSR|S_IWUSR, makedev(TTYAUX_MAJOR, 1)); | |
821 | if (symlink("/proc/self/fd", "/dev/fd") == 0) { | |
822 | dovoid(symlink("fd/0", "/dev/stdin")); | |
823 | dovoid(symlink("fd/1", "/dev/stdout")); | |
824 | dovoid(symlink("fd/2", "/dev/stderr")); | |
825 | } | |
826 | } | |
827 | } | |
828 | if (mounts) atexit(putmounts); | |
829 | } | |
830 | #endif | |
831 | ||
c5b4c135 | 832 | static |
a74aeac6 PR |
833 | void usage(void) |
834 | { | |
35ec6446 | 835 | fprintf(stderr, "Usage: sulogin [-e] [-p] [-t timeout] [tty device]\n\r"); |
a74aeac6 PR |
836 | } |
837 | ||
838 | int main(int argc, char **argv) | |
839 | { | |
840 | char *tty = NULL; | |
a74aeac6 | 841 | struct passwd *pwd; |
35ec6446 | 842 | int c, status = 0; |
05f2c1ad | 843 | int reconnect = 0; |
a74aeac6 | 844 | int opt_e = 0; |
35ec6446 DWF |
845 | struct console *con; |
846 | pid_t pid; | |
847 | ||
848 | /* | |
849 | * We are init. We hence need to set uo a session. | |
850 | */ | |
851 | if ((pid = getpid()) == 1) { | |
852 | setsid(); | |
853 | (void)ioctl(0, TIOCSCTTY, (char *)1); | |
854 | } | |
a74aeac6 PR |
855 | |
856 | /* | |
05f2c1ad | 857 | * See if we have a timeout flag. |
a74aeac6 PR |
858 | */ |
859 | opterr = 0; | |
860 | while((c = getopt(argc, argv, "ept:")) != EOF) switch(c) { | |
861 | case 't': | |
862 | timeout = atoi(optarg); | |
863 | break; | |
864 | case 'p': | |
865 | profile = 1; | |
866 | break; | |
867 | case 'e': | |
868 | opt_e = 1; | |
869 | break; | |
870 | default: | |
871 | usage(); | |
872 | /* Do not exit! */ | |
873 | break; | |
874 | } | |
875 | ||
876 | if (geteuid() != 0) { | |
35ec6446 | 877 | fprintf(stderr, "sulogin: only root can run sulogin.\n\r"); |
a74aeac6 PR |
878 | exit(1); |
879 | } | |
880 | ||
c5b4c135 | 881 | saved_sigint = signal(SIGINT, SIG_IGN); |
82111852 DWF |
882 | saved_sigquit = signal(SIGQUIT, SIG_IGN); |
883 | saved_sigtstp = signal(SIGTSTP, SIG_IGN); | |
35ec6446 | 884 | saved_sighup = signal(SIGHUP, SIG_IGN); |
c5b4c135 | 885 | |
35ec6446 | 886 | /* |
05f2c1ad | 887 | * See if we need to open an other tty device. |
35ec6446 DWF |
888 | */ |
889 | if (optind < argc) | |
890 | tty = argv[optind]; | |
891 | if (!tty || *tty == '\0') | |
892 | tty = getenv("CONSOLE"); | |
a74aeac6 | 893 | |
35ec6446 | 894 | /* |
05f2c1ad DWF |
895 | * Detect possible consoles, use stdin as fallback. |
896 | * If an optional tty is given, reconnect it to stdin. | |
35ec6446 | 897 | */ |
05f2c1ad | 898 | reconnect = detect_consoles(tty, 0); |
a74aeac6 | 899 | |
35ec6446 DWF |
900 | /* |
901 | * Should not happen | |
902 | */ | |
903 | if (!consoles) { | |
904 | if (!errno) | |
905 | errno = ENOMEM; | |
906 | fprintf(stderr, "sulogin: cannot open console: %m\n\r"); | |
907 | exit(1); | |
a74aeac6 PR |
908 | } |
909 | ||
05f2c1ad DWF |
910 | /* |
911 | * If previous stdin was not the speified tty and therefore reconnected | |
912 | * to the specified tty also reconnect stdout and stderr. | |
913 | */ | |
914 | if (reconnect) { | |
915 | if (isatty(1) == 0) | |
916 | dup2(0, 1); | |
917 | if (isatty(2) == 0) | |
918 | dup2(0, 2); | |
919 | } | |
920 | ||
a74aeac6 PR |
921 | /* |
922 | * Get the root password. | |
923 | */ | |
924 | if ((pwd = getrootpwent(opt_e)) == NULL) { | |
35ec6446 | 925 | fprintf(stderr, "sulogin: cannot open password database!\n\r"); |
a74aeac6 PR |
926 | sleep(2); |
927 | } | |
928 | ||
929 | /* | |
35ec6446 | 930 | * Prompt for input on the consoles |
a74aeac6 | 931 | */ |
35ec6446 DWF |
932 | for (con = consoles; con && con->id < CONMAX; con = con->next) { |
933 | if (con->fd >= 0) { | |
934 | openfd |= (1<<con->fd); | |
935 | tcinit(con); | |
936 | continue; | |
82111852 | 937 | } |
35ec6446 DWF |
938 | if ((con->fd = open(con->tty, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) |
939 | continue; | |
940 | openfd |= (1<<con->fd); | |
941 | tcinit(con); | |
a74aeac6 | 942 | } |
35ec6446 DWF |
943 | con = consoles; |
944 | usemask = (uint32_t*)mmap(NULL, sizeof(uint32_t), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); | |
945 | ||
946 | if (con->next == (struct console*)0) | |
947 | goto nofork; | |
948 | ||
949 | signal(SIGCHLD, chld_handler); | |
950 | do { | |
951 | switch ((con->pid = fork())) { | |
952 | case 0: | |
953 | signal(SIGCHLD, SIG_DFL); | |
954 | /* fall through */ | |
955 | nofork: | |
956 | setup(con); | |
957 | while (1) { | |
958 | char *passwd = pwd->pw_passwd; | |
959 | char *answer; | |
9e94f6aa | 960 | int failed = 0, doshell = 0; |
35ec6446 DWF |
961 | |
962 | doprompt(passwd, con); | |
963 | if ((answer = getpasswd(con)) == NULL) | |
964 | break; | |
965 | ||
9e94f6aa DWF |
966 | if (passwd[0] == '\0') |
967 | doshell++; | |
968 | else { | |
969 | char *cryptbuf; | |
970 | cryptbuf = crypt(answer, passwd); | |
971 | if (cryptbuf == NULL) | |
972 | fprintf(stderr, "sulogin: crypt failed: %m\n\r"); | |
973 | else if (strcmp(cryptbuf, pwd->pw_passwd) == 0) | |
974 | doshell++; | |
975 | } | |
976 | ||
977 | if (doshell) { | |
35ec6446 DWF |
978 | *usemask |= (1<<con->id); |
979 | sushell(pwd); | |
980 | *usemask &= ~(1<<con->id); | |
981 | failed++; | |
982 | } | |
9e94f6aa | 983 | |
35ec6446 DWF |
984 | signal(SIGQUIT, SIG_IGN); |
985 | signal(SIGTSTP, SIG_IGN); | |
986 | signal(SIGINT, SIG_IGN); | |
a74aeac6 | 987 | |
35ec6446 DWF |
988 | if (failed) { |
989 | fprintf(stderr, "sulogin: can not execute su shell.\n\r"); | |
990 | break; | |
991 | } | |
992 | fprintf(stderr, "Login incorrect.\n\r"); | |
05f2c1ad | 993 | sleep(3); |
35ec6446 DWF |
994 | } |
995 | if (alarm_rised) { | |
996 | tcfinal(con); | |
9e94f6aa | 997 | fprintf(stderr, "Timed out.\n\r"); |
35ec6446 DWF |
998 | } |
999 | /* | |
1000 | * User may pressed Control-D. | |
1001 | */ | |
1002 | exit(0); | |
1003 | case -1: | |
1004 | fprintf(stderr, "sulogin: can not fork: %m\n\r"); | |
1005 | /* fall through */ | |
1006 | default: | |
1007 | break; | |
1008 | } | |
1009 | } while ((con = con->next) && (con->id < CONMAX)); | |
1010 | ||
1011 | while ((pid = wait(&status))) { | |
1012 | if (errno == ECHILD) | |
1013 | break; | |
1014 | if (pid < 0) | |
1015 | continue; | |
1016 | for (con = consoles; con && con->id < CONMAX; con = con->next) { | |
1017 | if (con->pid == pid) { | |
1018 | *usemask &= ~(1<<con->id); | |
1019 | continue; | |
1020 | } | |
1021 | if (kill(con->pid, 0) < 0) { | |
1022 | *usemask &= ~(1<<con->id); | |
1023 | continue; | |
1024 | } | |
1025 | if (*usemask & (1<<con->id)) | |
1026 | continue; | |
1027 | kill(con->pid, SIGHUP); | |
1028 | usleep(5000); | |
1029 | kill(con->pid, SIGKILL); | |
1030 | } | |
1031 | } | |
1032 | signal(SIGCHLD, SIG_DFL); | |
82111852 | 1033 | |
a74aeac6 PR |
1034 | return 0; |
1035 | } |