]>
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 | ||
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 <errno.h> | |
43 | #include <sys/ioctl.h> | |
44 | #if defined(__GLIBC__) | |
45 | # include <crypt.h> | |
46 | #endif | |
47 | ||
48 | #ifdef WITH_SELINUX | |
49 | # include <selinux/selinux.h> | |
50 | # include <selinux/get_context_list.h> | |
51 | #endif | |
52 | ||
53 | #define CHECK_DES 1 | |
54 | #define CHECK_MD5 1 | |
55 | ||
56 | #define F_PASSWD "/etc/passwd" | |
57 | #define F_SHADOW "/etc/shadow" | |
58 | #define BINSH "/bin/sh" | |
59 | #define STATICSH "/bin/sash" | |
60 | ||
61 | char *Version = "@(#)sulogin 2.85-3 23-Apr-2003 miquels@cistron.nl"; | |
62 | ||
63 | int timeout = 0; | |
64 | int profile = 0; | |
65 | ||
66 | #ifndef IUCLC | |
67 | # define IUCLC 0 | |
68 | #endif | |
69 | ||
70 | #if 0 | |
71 | /* | |
72 | * Fix the tty modes and set reasonable defaults. | |
73 | * (I'm not sure if this is needed under Linux, but..) | |
74 | */ | |
75 | void fixtty(void) | |
76 | { | |
77 | struct termios tty; | |
78 | ||
79 | tcgetattr(0, &tty); | |
80 | ||
81 | /* | |
82 | * Set or adjust tty modes. | |
83 | */ | |
84 | tty.c_iflag &= ~(INLCR|IGNCR|IUCLC); | |
85 | tty.c_iflag |= ICRNL; | |
86 | tty.c_oflag &= ~(OCRNL|OLCUC|ONOCR|ONLRET|OFILL); | |
87 | tty.c_oflag |= OPOST|ONLCR; | |
88 | tty.c_cflag |= CLOCAL; | |
89 | tty.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE; | |
90 | ||
91 | /* | |
92 | * Set the most important characters */ | |
93 | */ | |
94 | tty.c_cc[VINTR] = 3; | |
95 | tty.c_cc[VQUIT] = 28; | |
96 | tty.c_cc[VERASE] = 127; | |
97 | tty.c_cc[VKILL] = 24; | |
98 | tty.c_cc[VEOF] = 4; | |
99 | tty.c_cc[VTIME] = 0; | |
100 | tty.c_cc[VMIN] = 1; | |
101 | tty.c_cc[VSTART] = 17; | |
102 | tty.c_cc[VSTOP] = 19; | |
103 | tty.c_cc[VSUSP] = 26; | |
104 | ||
105 | tcsetattr(0, TCSANOW, &tty); | |
106 | } | |
107 | #endif | |
108 | ||
109 | ||
110 | /* | |
111 | * Called at timeout. | |
112 | */ | |
113 | void alrm_handler() | |
114 | { | |
115 | } | |
116 | ||
117 | /* | |
118 | * See if an encrypted password is valid. The encrypted | |
119 | * password is checked for traditional-style DES and | |
120 | * FreeBSD-style MD5 encryption. | |
121 | */ | |
122 | int valid(char *pass) | |
123 | { | |
124 | char *s; | |
125 | int len; | |
126 | ||
127 | if (pass[0] == 0) return 1; | |
128 | #if CHECK_MD5 | |
129 | /* | |
130 | * 3 bytes for the signature $1$ | |
131 | * up to 8 bytes for the salt | |
132 | * $ | |
133 | * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes | |
134 | */ | |
135 | if (strncmp(pass, "$1$", 3) == 0) { | |
136 | for(s = pass + 3; *s && *s != '$'; s++) | |
137 | ; | |
138 | if (*s++ != '$') return 0; | |
139 | len = strlen(s); | |
140 | if (len < 22 || len > 24) return 0; | |
141 | ||
142 | return 1; | |
143 | } | |
144 | #endif | |
145 | #if CHECK_DES | |
146 | if (strlen(pass) != 13) return 0; | |
147 | for (s = pass; *s; s++) { | |
148 | if ((*s < '0' || *s > '9') && | |
149 | (*s < 'a' || *s > 'z') && | |
150 | (*s < 'A' || *s > 'Z') && | |
151 | *s != '.' && *s != '/') return 0; | |
152 | } | |
153 | #endif | |
154 | return 1; | |
155 | } | |
156 | ||
157 | /* | |
158 | * Set a variable if the value is not NULL. | |
159 | */ | |
160 | void set(char **var, char *val) | |
161 | { | |
162 | if (val) *var = val; | |
163 | } | |
164 | ||
165 | /* | |
166 | * Get the root password entry. | |
167 | */ | |
168 | struct passwd *getrootpwent(int try_manually) | |
169 | { | |
170 | static struct passwd pwd; | |
171 | struct passwd *pw; | |
172 | struct spwd *spw; | |
173 | FILE *fp; | |
174 | static char line[256]; | |
175 | static char sline[256]; | |
176 | char *p; | |
177 | ||
178 | /* | |
179 | * First, we try to get the password the standard | |
180 | * way using normal library calls. | |
181 | */ | |
182 | if ((pw = getpwnam("root")) && | |
183 | !strcmp(pw->pw_passwd, "x") && | |
184 | (spw = getspnam("root"))) | |
185 | pw->pw_passwd = spw->sp_pwdp; | |
186 | if (pw || !try_manually) return pw; | |
187 | ||
188 | /* | |
189 | * If we come here, we could not retrieve the root | |
190 | * password through library calls and we try to | |
191 | * read the password and shadow files manually. | |
192 | */ | |
193 | pwd.pw_name = "root"; | |
194 | pwd.pw_passwd = ""; | |
195 | pwd.pw_gecos = "Super User"; | |
196 | pwd.pw_dir = "/"; | |
197 | pwd.pw_shell = ""; | |
198 | pwd.pw_uid = 0; | |
199 | pwd.pw_gid = 0; | |
200 | ||
201 | if ((fp = fopen(F_PASSWD, "r")) == NULL) { | |
202 | perror(F_PASSWD); | |
203 | return &pwd; | |
204 | } | |
205 | ||
206 | /* | |
207 | * Find root in the password file. | |
208 | */ | |
209 | while((p = fgets(line, 256, fp)) != NULL) { | |
210 | if (strncmp(line, "root:", 5) != 0) | |
211 | continue; | |
212 | p += 5; | |
213 | set(&pwd.pw_passwd, strsep(&p, ":")); | |
214 | (void)strsep(&p, ":"); | |
215 | (void)strsep(&p, ":"); | |
216 | set(&pwd.pw_gecos, strsep(&p, ":")); | |
217 | set(&pwd.pw_dir, strsep(&p, ":")); | |
218 | set(&pwd.pw_shell, strsep(&p, "\n")); | |
219 | p = line; | |
220 | break; | |
221 | } | |
222 | fclose(fp); | |
223 | ||
224 | /* | |
225 | * If the encrypted password is valid | |
226 | * or not found, return. | |
227 | */ | |
228 | if (p == NULL) { | |
229 | fprintf(stderr, "%s: no entry for root\n", F_PASSWD); | |
230 | return &pwd; | |
231 | } | |
232 | if (valid(pwd.pw_passwd)) return &pwd; | |
233 | ||
234 | /* | |
235 | * The password is invalid. If there is a | |
236 | * shadow password, try it. | |
237 | */ | |
238 | strcpy(pwd.pw_passwd, ""); | |
239 | if ((fp = fopen(F_SHADOW, "r")) == NULL) { | |
240 | fprintf(stderr, "%s: root password garbled\n", F_PASSWD); | |
241 | return &pwd; | |
242 | } | |
243 | while((p = fgets(sline, 256, fp)) != NULL) { | |
244 | if (strncmp(sline, "root:", 5) != 0) | |
245 | continue; | |
246 | p += 5; | |
247 | set(&pwd.pw_passwd, strsep(&p, ":")); | |
248 | break; | |
249 | } | |
250 | fclose(fp); | |
251 | ||
252 | /* | |
253 | * If the password is still invalid, | |
254 | * NULL it, and return. | |
255 | */ | |
256 | if (p == NULL) { | |
257 | fprintf(stderr, "%s: no entry for root\n", F_SHADOW); | |
258 | strcpy(pwd.pw_passwd, ""); | |
259 | } | |
260 | if (!valid(pwd.pw_passwd)) { | |
261 | fprintf(stderr, "%s: root password garbled\n", F_SHADOW); | |
262 | strcpy(pwd.pw_passwd, ""); } | |
263 | return &pwd; | |
264 | } | |
265 | ||
266 | /* | |
267 | * Ask for the password. Note that there is no | |
268 | * default timeout as we normally skip this during boot. | |
269 | */ | |
270 | char *getpasswd(char *crypted) | |
271 | { | |
272 | struct sigaction sa; | |
273 | struct termios old, tty; | |
274 | static char pass[128]; | |
275 | char *ret = pass; | |
276 | int i; | |
277 | ||
278 | if (crypted[0]) | |
279 | printf("Give root password for maintenance\n"); | |
280 | else | |
281 | printf("Press enter for maintenance\n"); | |
282 | printf("(or type Control-D to continue): "); | |
283 | fflush(stdout); | |
284 | ||
285 | tcgetattr(0, &old); | |
286 | tcgetattr(0, &tty); | |
287 | tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); | |
288 | tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); | |
289 | tcsetattr(0, TCSANOW, &tty); | |
290 | ||
291 | pass[sizeof(pass) - 1] = 0; | |
292 | ||
293 | sa.sa_handler = alrm_handler; | |
294 | sa.sa_flags = 0; | |
295 | sigaction(SIGALRM, &sa, NULL); | |
296 | if (timeout) alarm(timeout); | |
297 | ||
298 | if (read(0, pass, sizeof(pass) - 1) <= 0) | |
299 | ret = NULL; | |
300 | else { | |
192c4567 | 301 | for(i = 0; i < (int)sizeof(pass) && pass[i]; i++) |
a74aeac6 PR |
302 | if (pass[i] == '\r' || pass[i] == '\n') { |
303 | pass[i] = 0; | |
304 | break; | |
305 | } | |
306 | } | |
307 | alarm(0); | |
308 | tcsetattr(0, TCSANOW, &old); | |
309 | printf("\n"); | |
310 | ||
311 | return ret; | |
312 | } | |
313 | ||
314 | /* | |
315 | * Password was OK, execute a shell. | |
316 | */ | |
317 | void sushell(struct passwd *pwd) | |
318 | { | |
319 | char shell[128]; | |
320 | char home[128]; | |
321 | char *p; | |
322 | char *sushell; | |
323 | ||
324 | /* | |
325 | * Set directory and shell. | |
326 | */ | |
327 | (void)chdir(pwd->pw_dir); | |
328 | if ((p = getenv("SUSHELL")) != NULL) | |
329 | sushell = p; | |
330 | else if ((p = getenv("sushell")) != NULL) | |
331 | sushell = p; | |
332 | else { | |
333 | if (pwd->pw_shell[0]) | |
334 | sushell = pwd->pw_shell; | |
335 | else | |
336 | sushell = BINSH; | |
337 | } | |
338 | if ((p = strrchr(sushell, '/')) == NULL) | |
339 | p = sushell; | |
340 | else | |
341 | p++; | |
342 | snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p); | |
343 | ||
344 | /* | |
345 | * Set some important environment variables. | |
346 | */ | |
347 | getcwd(home, sizeof(home)); | |
348 | setenv("HOME", home, 1); | |
349 | setenv("LOGNAME", "root", 1); | |
350 | setenv("USER", "root", 1); | |
351 | if (!profile) | |
352 | setenv("SHLVL","0",1); | |
353 | ||
354 | /* | |
355 | * Try to execute a shell. | |
356 | */ | |
357 | setenv("SHELL", sushell, 1); | |
358 | signal(SIGINT, SIG_DFL); | |
359 | signal(SIGTSTP, SIG_DFL); | |
360 | signal(SIGQUIT, SIG_DFL); | |
361 | #ifdef WITH_SELINUX | |
362 | if (is_selinux_enabled > 0) { | |
363 | security_context_t scon=NULL; | |
364 | char *seuser=NULL; | |
365 | char *level=NULL; | |
366 | if (getseuserbyname("root", &seuser, &level) == 0) | |
367 | if (get_default_context_with_level(seuser, level, 0, &scon) > 0) { | |
368 | if (setexeccon(scon) != 0) | |
369 | fprintf(stderr, "setexeccon faile\n"); | |
370 | freecon(scon); | |
371 | } | |
372 | free(seuser); | |
373 | free(level); | |
374 | } | |
375 | #endif | |
376 | execl(sushell, shell, NULL); | |
377 | perror(sushell); | |
378 | ||
379 | setenv("SHELL", BINSH, 1); | |
380 | execl(BINSH, profile ? "-sh" : "sh", NULL); | |
381 | perror(BINSH); | |
382 | ||
383 | /* Fall back to staticly linked shell if both the users shell | |
384 | and /bin/sh failed to execute. */ | |
385 | setenv("SHELL", STATICSH, 1); | |
386 | execl(STATICSH, STATICSH, NULL); | |
387 | perror(STATICSH); | |
388 | } | |
389 | ||
390 | void usage(void) | |
391 | { | |
392 | fprintf(stderr, "Usage: sulogin [-e] [-p] [-t timeout] [tty device]\n"); | |
393 | } | |
394 | ||
395 | int main(int argc, char **argv) | |
396 | { | |
397 | char *tty = NULL; | |
398 | char *p; | |
399 | struct passwd *pwd; | |
400 | int c, fd = -1; | |
401 | int opt_e = 0; | |
402 | pid_t pid, pgrp, ppgrp, ttypgrp; | |
403 | ||
404 | /* | |
405 | * See if we have a timeout flag. | |
406 | */ | |
407 | opterr = 0; | |
408 | while((c = getopt(argc, argv, "ept:")) != EOF) switch(c) { | |
409 | case 't': | |
410 | timeout = atoi(optarg); | |
411 | break; | |
412 | case 'p': | |
413 | profile = 1; | |
414 | break; | |
415 | case 'e': | |
416 | opt_e = 1; | |
417 | break; | |
418 | default: | |
419 | usage(); | |
420 | /* Do not exit! */ | |
421 | break; | |
422 | } | |
423 | ||
424 | if (geteuid() != 0) { | |
425 | fprintf(stderr, "sulogin: only root can run sulogin.\n"); | |
426 | exit(1); | |
427 | } | |
428 | ||
429 | /* | |
430 | * See if we need to open an other tty device. | |
431 | */ | |
432 | signal(SIGINT, SIG_IGN); | |
433 | signal(SIGQUIT, SIG_IGN); | |
434 | signal(SIGTSTP, SIG_IGN); | |
435 | if (optind < argc) tty = argv[optind]; | |
436 | if (tty) { | |
437 | if ((fd = open(tty, O_RDWR)) < 0) { | |
438 | perror(tty); | |
439 | } else if (!isatty(fd)) { | |
440 | fprintf(stderr, "%s: not a tty\n", tty); | |
441 | close(fd); | |
442 | } else { | |
443 | ||
444 | /* | |
445 | * Only go through this trouble if the new | |
446 | * tty doesn't fall in this process group. | |
447 | */ | |
448 | pid = getpid(); | |
449 | pgrp = getpgid(0); | |
450 | ppgrp = getpgid(getppid()); | |
451 | ioctl(fd, TIOCGPGRP, &ttypgrp); | |
452 | ||
453 | if (pgrp != ttypgrp && ppgrp != ttypgrp) { | |
454 | if (pid != getsid(0)) { | |
455 | if (pid == getpgid(0)) | |
456 | setpgid(0, getpgid(getppid())); | |
457 | setsid(); | |
458 | } | |
459 | ||
460 | signal(SIGHUP, SIG_IGN); | |
461 | ioctl(0, TIOCNOTTY, (char *)1); | |
462 | signal(SIGHUP, SIG_DFL); | |
463 | close(0); | |
464 | close(1); | |
465 | close(2); | |
466 | close(fd); | |
467 | fd = open(tty, O_RDWR); | |
468 | ioctl(0, TIOCSCTTY, (char *)1); | |
469 | dup(fd); | |
470 | dup(fd); | |
471 | } else | |
472 | close(fd); | |
473 | } | |
474 | } else if (getpid() == 1) { | |
475 | /* We are init. We hence need to set a session anyway */ | |
476 | setsid(); | |
477 | if (ioctl(0, TIOCSCTTY, (char *)1)) | |
478 | perror("ioctl(TIOCSCTTY)"); | |
479 | } | |
480 | ||
481 | /* | |
482 | * Get the root password. | |
483 | */ | |
484 | if ((pwd = getrootpwent(opt_e)) == NULL) { | |
485 | fprintf(stderr, "sulogin: cannot open password database!\n"); | |
486 | sleep(2); | |
487 | } | |
488 | ||
489 | /* | |
490 | * Ask for the password. | |
491 | */ | |
492 | while(pwd) { | |
493 | if ((p = getpasswd(pwd->pw_passwd)) == NULL) break; | |
494 | if (pwd->pw_passwd[0] == 0 || | |
495 | strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0) | |
496 | sushell(pwd); | |
497 | printf("Login incorrect.\n"); | |
498 | } | |
499 | ||
500 | /* | |
501 | * User pressed Control-D. | |
502 | */ | |
503 | return 0; | |
504 | } | |
505 |