udhcpc: add comment about server IP
[oweals/busybox.git] / loginutils / login.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
4  */
5
6 //usage:#define login_trivial_usage
7 //usage:       "[-p] [-h HOST] [[-f] USER]"
8 //usage:#define login_full_usage "\n\n"
9 //usage:       "Begin a new session on the system\n"
10 //usage:     "\n        -f      Don't authenticate (user already authenticated)"
11 //usage:     "\n        -h      Name of the remote host"
12 //usage:     "\n        -p      Preserve environment"
13
14 #include "libbb.h"
15 #include <syslog.h>
16 #include <sys/resource.h>
17
18 #if ENABLE_SELINUX
19 # include <selinux/selinux.h>  /* for is_selinux_enabled()  */
20 # include <selinux/get_context_list.h> /* for get_default_context() */
21 # include <selinux/flask.h> /* for security class definitions  */
22 #endif
23
24 #if ENABLE_PAM
25 /* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
26 # undef setlocale
27 /* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
28  * Apparently they like to confuse people. */
29 # include <security/pam_appl.h>
30 # include <security/pam_misc.h>
31 static const struct pam_conv conv = {
32         misc_conv,
33         NULL
34 };
35 #endif
36
37 enum {
38         TIMEOUT = 60,
39         EMPTY_USERNAME_COUNT = 10,
40         USERNAME_SIZE = 32,
41         TTYNAME_SIZE = 32,
42 };
43
44 #if ENABLE_FEATURE_NOLOGIN
45 static void die_if_nologin(void)
46 {
47         FILE *fp;
48         int c;
49         int empty = 1;
50
51         fp = fopen_for_read("/etc/nologin");
52         if (!fp) /* assuming it does not exist */
53                 return;
54
55         while ((c = getc(fp)) != EOF) {
56                 if (c == '\n')
57                         bb_putchar('\r');
58                 bb_putchar(c);
59                 empty = 0;
60         }
61         if (empty)
62                 puts("\r\nSystem closed for routine maintenance\r");
63
64         fclose(fp);
65         fflush_all();
66         /* Users say that they do need this prior to exit: */
67         tcdrain(STDOUT_FILENO);
68         exit(EXIT_FAILURE);
69 }
70 #else
71 # define die_if_nologin() ((void)0)
72 #endif
73
74 #if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
75 static int check_securetty(const char *short_tty)
76 {
77         char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */
78         parser_t *parser = config_open2("/etc/securetty", fopen_for_read);
79         while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) {
80                 if (strcmp(buf, short_tty) == 0)
81                         break;
82                 buf = NULL;
83         }
84         config_close(parser);
85         /* buf != NULL here if config file was not found, empty
86          * or line was found which equals short_tty */
87         return buf != NULL;
88 }
89 #else
90 static ALWAYS_INLINE int check_securetty(const char *short_tty UNUSED_PARAM) { return 1; }
91 #endif
92
93 #if ENABLE_SELINUX
94 static void initselinux(char *username, char *full_tty,
95                                                 security_context_t *user_sid)
96 {
97         security_context_t old_tty_sid, new_tty_sid;
98
99         if (!is_selinux_enabled())
100                 return;
101
102         if (get_default_context(username, NULL, user_sid)) {
103                 bb_error_msg_and_die("can't get SID for %s", username);
104         }
105         if (getfilecon(full_tty, &old_tty_sid) < 0) {
106                 bb_perror_msg_and_die("getfilecon(%s) failed", full_tty);
107         }
108         if (security_compute_relabel(*user_sid, old_tty_sid,
109                                 SECCLASS_CHR_FILE, &new_tty_sid) != 0) {
110                 bb_perror_msg_and_die("security_change_sid(%s) failed", full_tty);
111         }
112         if (setfilecon(full_tty, new_tty_sid) != 0) {
113                 bb_perror_msg_and_die("chsid(%s, %s) failed", full_tty, new_tty_sid);
114         }
115 }
116 #endif
117
118 #if ENABLE_LOGIN_SCRIPTS
119 static void run_login_script(struct passwd *pw, char *full_tty)
120 {
121         char *t_argv[2];
122
123         t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT");
124         if (t_argv[0]) {
125                 t_argv[1] = NULL;
126                 xsetenv("LOGIN_TTY", full_tty);
127                 xsetenv("LOGIN_USER", pw->pw_name);
128                 xsetenv("LOGIN_UID", utoa(pw->pw_uid));
129                 xsetenv("LOGIN_GID", utoa(pw->pw_gid));
130                 xsetenv("LOGIN_SHELL", pw->pw_shell);
131                 spawn_and_wait(t_argv); /* NOMMU-friendly */
132                 unsetenv("LOGIN_TTY");
133                 unsetenv("LOGIN_USER");
134                 unsetenv("LOGIN_UID");
135                 unsetenv("LOGIN_GID");
136                 unsetenv("LOGIN_SHELL");
137         }
138 }
139 #else
140 void run_login_script(struct passwd *pw, char *full_tty);
141 #endif
142
143 #if ENABLE_LOGIN_SESSION_AS_CHILD && ENABLE_PAM
144 static void login_pam_end(pam_handle_t *pamh)
145 {
146         int pamret;
147
148         pamret = pam_setcred(pamh, PAM_DELETE_CRED);
149         if (pamret != PAM_SUCCESS) {
150                 bb_error_msg("pam_%s failed: %s (%d)", "setcred",
151                         pam_strerror(pamh, pamret), pamret);
152         }
153         pamret = pam_close_session(pamh, 0);
154         if (pamret != PAM_SUCCESS) {
155                 bb_error_msg("pam_%s failed: %s (%d)", "close_session",
156                         pam_strerror(pamh, pamret), pamret);
157         }
158         pamret = pam_end(pamh, pamret);
159         if (pamret != PAM_SUCCESS) {
160                 bb_error_msg("pam_%s failed: %s (%d)", "end",
161                         pam_strerror(pamh, pamret), pamret);
162         }
163 }
164 #endif /* ENABLE_PAM */
165
166 static void get_username_or_die(char *buf, int size_buf)
167 {
168         int c, cntdown;
169
170         cntdown = EMPTY_USERNAME_COUNT;
171  prompt:
172         print_login_prompt();
173         /* skip whitespace */
174         do {
175                 c = getchar();
176                 if (c == EOF)
177                         exit(EXIT_FAILURE);
178                 if (c == '\n') {
179                         if (!--cntdown)
180                                 exit(EXIT_FAILURE);
181                         goto prompt;
182                 }
183         } while (isspace(c)); /* maybe isblank? */
184
185         *buf++ = c;
186         if (!fgets(buf, size_buf-2, stdin))
187                 exit(EXIT_FAILURE);
188         if (!strchr(buf, '\n'))
189                 exit(EXIT_FAILURE);
190         while ((unsigned char)*buf > ' ')
191                 buf++;
192         *buf = '\0';
193 }
194
195 static void motd(void)
196 {
197         int fd;
198
199         fd = open(bb_path_motd_file, O_RDONLY);
200         if (fd >= 0) {
201                 fflush_all();
202                 bb_copyfd_eof(fd, STDOUT_FILENO);
203                 close(fd);
204         }
205 }
206
207 static void alarm_handler(int sig UNUSED_PARAM)
208 {
209         /* This is the escape hatch!  Poor serial line users and the like
210          * arrive here when their connection is broken.
211          * We don't want to block here */
212         ndelay_on(1);
213         printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT);
214         fflush_all();
215         /* unix API is brain damaged regarding O_NONBLOCK,
216          * we should undo it, or else we can affect other processes */
217         ndelay_off(1);
218         _exit(EXIT_SUCCESS);
219 }
220
221 int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
222 int login_main(int argc UNUSED_PARAM, char **argv)
223 {
224         enum {
225                 LOGIN_OPT_f = (1<<0),
226                 LOGIN_OPT_h = (1<<1),
227                 LOGIN_OPT_p = (1<<2),
228         };
229         char *fromhost;
230         char username[USERNAME_SIZE];
231         int run_by_root;
232         unsigned opt;
233         int count = 0;
234         struct passwd *pw;
235         char *opt_host = NULL;
236         char *opt_user = opt_user; /* for compiler */
237         char *full_tty;
238         char *short_tty;
239         IF_SELINUX(security_context_t user_sid = NULL;)
240 #if ENABLE_PAM
241         int pamret;
242         pam_handle_t *pamh;
243         const char *pamuser;
244         const char *failed_msg;
245         struct passwd pwdstruct;
246         char pwdbuf[256];
247         char **pamenv;
248 #endif
249 #if ENABLE_LOGIN_SESSION_AS_CHILD
250         pid_t child_pid;
251 #endif
252
253         username[0] = '\0';
254         signal(SIGALRM, alarm_handler);
255         alarm(TIMEOUT);
256
257         /* More of suid paranoia if called by non-root: */
258         /* Clear dangerous stuff, set PATH */
259         run_by_root = !sanitize_env_if_suid();
260
261         /* Mandatory paranoia for suid applet:
262          * ensure that fd# 0,1,2 are opened (at least to /dev/null)
263          * and any extra open fd's are closed.
264          * (The name of the function is misleading. Not daemonizing here.) */
265         bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL);
266
267         opt = getopt32(argv, "f:h:p", &opt_user, &opt_host);
268         if (opt & LOGIN_OPT_f) {
269                 if (!run_by_root)
270                         bb_error_msg_and_die("-f is for root only");
271                 safe_strncpy(username, opt_user, sizeof(username));
272         }
273         argv += optind;
274         if (argv[0]) /* user from command line (getty) */
275                 safe_strncpy(username, argv[0], sizeof(username));
276
277         /* Let's find out and memorize our tty */
278         if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO))
279                 return EXIT_FAILURE;  /* Must be a terminal */
280         full_tty = xmalloc_ttyname(STDIN_FILENO);
281         if (!full_tty)
282                 full_tty = xstrdup("UNKNOWN");
283         short_tty = skip_dev_pfx(full_tty);
284
285         if (opt_host) {
286                 fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host);
287         } else {
288                 fromhost = xasprintf(" on '%s'", short_tty);
289         }
290
291         /* Was breaking "login <username>" from shell command line: */
292         /*bb_setpgrp();*/
293
294         openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH);
295
296         while (1) {
297                 /* flush away any type-ahead (as getty does) */
298                 tcflush(0, TCIFLUSH);
299
300                 if (!username[0])
301                         get_username_or_die(username, sizeof(username));
302
303 #if ENABLE_PAM
304                 pamret = pam_start("login", username, &conv, &pamh);
305                 if (pamret != PAM_SUCCESS) {
306                         failed_msg = "start";
307                         goto pam_auth_failed;
308                 }
309                 /* set TTY (so things like securetty work) */
310                 pamret = pam_set_item(pamh, PAM_TTY, short_tty);
311                 if (pamret != PAM_SUCCESS) {
312                         failed_msg = "set_item(TTY)";
313                         goto pam_auth_failed;
314                 }
315                 /* set RHOST */
316                 if (opt_host) {
317                         pamret = pam_set_item(pamh, PAM_RHOST, opt_host);
318                         if (pamret != PAM_SUCCESS) {
319                                 failed_msg = "set_item(RHOST)";
320                                 goto pam_auth_failed;
321                         }
322                 }
323                 pamret = pam_authenticate(pamh, 0);
324                 if (pamret != PAM_SUCCESS) {
325                         failed_msg = "authenticate";
326                         goto pam_auth_failed;
327                         /* TODO: or just "goto auth_failed"
328                          * since user seems to enter wrong password
329                          * (in this case pamret == 7)
330                          */
331                 }
332                 /* check that the account is healthy */
333                 pamret = pam_acct_mgmt(pamh, 0);
334                 if (pamret != PAM_SUCCESS) {
335                         failed_msg = "acct_mgmt";
336                         goto pam_auth_failed;
337                 }
338                 /* read user back */
339                 pamuser = NULL;
340                 /* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
341                  * thus we cast to (void*) */
342                 if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) {
343                         failed_msg = "get_item(USER)";
344                         goto pam_auth_failed;
345                 }
346                 if (!pamuser || !pamuser[0])
347                         goto auth_failed;
348                 safe_strncpy(username, pamuser, sizeof(username));
349                 /* Don't use "pw = getpwnam(username);",
350                  * PAM is said to be capable of destroying static storage
351                  * used by getpwnam(). We are using safe(r) function */
352                 pw = NULL;
353                 getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw);
354                 if (!pw)
355                         goto auth_failed;
356                 pamret = pam_open_session(pamh, 0);
357                 if (pamret != PAM_SUCCESS) {
358                         failed_msg = "open_session";
359                         goto pam_auth_failed;
360                 }
361                 pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
362                 if (pamret != PAM_SUCCESS) {
363                         failed_msg = "setcred";
364                         goto pam_auth_failed;
365                 }
366                 break; /* success, continue login process */
367
368  pam_auth_failed:
369                 /* syslog, because we don't want potential attacker
370                  * to know _why_ login failed */
371                 syslog(LOG_WARNING, "pam_%s call failed: %s (%d)", failed_msg,
372                                         pam_strerror(pamh, pamret), pamret);
373                 safe_strncpy(username, "UNKNOWN", sizeof(username));
374 #else /* not PAM */
375                 pw = getpwnam(username);
376                 if (!pw) {
377                         strcpy(username, "UNKNOWN");
378                         goto fake_it;
379                 }
380
381                 if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')
382                         goto auth_failed;
383
384                 if (opt & LOGIN_OPT_f)
385                         break; /* -f USER: success without asking passwd */
386
387                 if (pw->pw_uid == 0 && !check_securetty(short_tty))
388                         goto auth_failed;
389
390                 /* Don't check the password if password entry is empty (!) */
391                 if (!pw->pw_passwd[0])
392                         break;
393  fake_it:
394                 /* authorization takes place here */
395                 if (correct_password(pw))
396                         break;
397 #endif /* ENABLE_PAM */
398  auth_failed:
399                 opt &= ~LOGIN_OPT_f;
400                 bb_do_delay(LOGIN_FAIL_DELAY);
401                 /* TODO: doesn't sound like correct English phrase to me */
402                 puts("Login incorrect");
403                 if (++count == 3) {
404                         syslog(LOG_WARNING, "invalid password for '%s'%s",
405                                                 username, fromhost);
406
407                         if (ENABLE_FEATURE_CLEAN_UP)
408                                 free(fromhost);
409
410                         return EXIT_FAILURE;
411                 }
412                 username[0] = '\0';
413         } /* while (1) */
414
415         alarm(0);
416         /* We can ignore /etc/nologin if we are logging in as root,
417          * it doesn't matter whether we are run by root or not */
418         if (pw->pw_uid != 0)
419                 die_if_nologin();
420
421
422 #if ENABLE_LOGIN_SESSION_AS_CHILD
423         child_pid = vfork();
424         if (child_pid != 0) {
425                 if (child_pid < 0)
426                         bb_perror_msg("vfork");
427                 else {
428                         if (safe_waitpid(child_pid, NULL, 0) == -1)
429                                 bb_perror_msg("waitpid");
430                         update_utmp(child_pid, DEAD_PROCESS, NULL, NULL, NULL);
431                 }
432                 IF_PAM(login_pam_end(pamh);)
433                 return 0;
434         }
435 #endif
436
437         IF_SELINUX(initselinux(username, full_tty, &user_sid);)
438
439         /* Try these, but don't complain if they fail.
440          * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */
441         fchown(0, pw->pw_uid, pw->pw_gid);
442         fchmod(0, 0600);
443
444         update_utmp(getpid(), USER_PROCESS, short_tty, username, run_by_root ? opt_host : NULL);
445
446         /* We trust environment only if we run by root */
447         if (ENABLE_LOGIN_SCRIPTS && run_by_root)
448                 run_login_script(pw, full_tty);
449
450         change_identity(pw);
451         setup_environment(pw->pw_shell,
452                         (!(opt & LOGIN_OPT_p) * SETUP_ENV_CLEARENV) + SETUP_ENV_CHANGEENV,
453                         pw);
454
455 #if ENABLE_PAM
456         /* Modules such as pam_env will setup the PAM environment,
457          * which should be copied into the new environment. */
458         pamenv = pam_getenvlist(pamh);
459         if (pamenv) while (*pamenv) {
460                 putenv(*pamenv);
461                 pamenv++;
462         }
463 #endif
464
465         motd();
466
467         if (pw->pw_uid == 0)
468                 syslog(LOG_INFO, "root login%s", fromhost);
469
470         if (ENABLE_FEATURE_CLEAN_UP)
471                 free(fromhost);
472
473         /* well, a simple setexeccon() here would do the job as well,
474          * but let's play the game for now */
475         IF_SELINUX(set_current_security_context(user_sid);)
476
477         // util-linux login also does:
478         // /* start new session */
479         // setsid();
480         // /* TIOCSCTTY: steal tty from other process group */
481         // if (ioctl(0, TIOCSCTTY, 1)) error_msg...
482         // BBox login used to do this (see above):
483         // bb_setpgrp();
484         // If this stuff is really needed, add it and explain why!
485
486         /* Set signals to defaults */
487         /* Non-ignored signals revert to SIG_DFL on exec anyway */
488         /*signal(SIGALRM, SIG_DFL);*/
489
490         /* Is this correct? This way user can ctrl-c out of /etc/profile,
491          * potentially creating security breach (tested with bash 3.0).
492          * But without this, bash 3.0 will not enable ctrl-c either.
493          * Maybe bash is buggy?
494          * Need to find out what standards say about /bin/login -
495          * should we leave SIGINT etc enabled or disabled? */
496         signal(SIGINT, SIG_DFL);
497
498         /* Exec login shell with no additional parameters */
499         run_shell(pw->pw_shell, 1, NULL, NULL);
500
501         /* return EXIT_FAILURE; - not reached */
502 }