c05ec066aa2fc7a445575a1a21ddc11c70147b44
[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 tarball for details.
4  */
5
6 #include <fcntl.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <syslog.h>
12 #include <termios.h>
13 #include <unistd.h>
14 #include <utmp.h>
15 #include <sys/resource.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <ctype.h>
20 #include <time.h>
21
22 #include "busybox.h"
23 #ifdef CONFIG_SELINUX
24 #include <selinux/selinux.h>  /* for is_selinux_enabled()  */
25 #include <selinux/get_context_list.h> /* for get_default_context() */
26 #include <selinux/flask.h> /* for security class definitions  */
27 #include <errno.h>
28 #endif
29
30 #ifdef CONFIG_FEATURE_UTMP
31 // import from utmp.c
32 static void checkutmp(int picky);
33 static void setutmp(const char *name, const char *line);
34 /* Stuff global to this file */
35 static struct utmp utent;
36 #endif
37
38 enum {
39         TIMEOUT = 60,
40         EMPTY_USERNAME_COUNT = 10,
41         USERNAME_SIZE = 32,
42 };
43
44 static int check_nologin(int amroot);
45
46 #if defined CONFIG_FEATURE_SECURETTY
47 static int check_tty(const char *tty);
48
49 #else
50 static inline int check_tty(const char *tty) { return 1; }
51
52 #endif
53
54 static int is_my_tty(const char *tty);
55 static int login_prompt(char *buf_name);
56 static void motd(void);
57
58
59 static void alarm_handler(int sig ATTRIBUTE_UNUSED)
60 {
61         fprintf(stderr, "\r\nLogin timed out after %s seconds\r\n", TIMEOUT);
62         exit(EXIT_SUCCESS);
63 }
64
65
66 int login_main(int argc, char **argv)
67 {
68         char tty[BUFSIZ];
69         char full_tty[200];
70         char fromhost[512];
71         char username[USERNAME_SIZE];
72         const char *tmp;
73         int amroot;
74         int flag;
75         int failed;
76         int count = 0;
77         struct passwd *pw, pw_copy;
78 #ifdef CONFIG_WHEEL_GROUP
79         struct group *grp;
80 #endif
81         int opt_preserve = 0;
82         int opt_fflag = 0;
83         char *opt_host = 0;
84         int alarmstarted = 0;
85 #ifdef CONFIG_SELINUX
86         security_context_t user_sid = NULL;
87 #endif
88
89         username[0] = '\0';
90         amroot = (getuid() == 0);
91         signal(SIGALRM, alarm_handler);
92         alarm(TIMEOUT);
93         alarmstarted = 1;
94
95         while ((flag = getopt(argc, argv, "f:h:p")) != EOF) {
96                 switch (flag) {
97                 case 'p':
98                         opt_preserve = 1;
99                         break;
100                 case 'f':
101                         /*
102                          * username must be a separate token
103                          * (-f root, *NOT* -froot). --marekm
104                          */
105                         if (optarg != argv[optind-1])
106                                 bb_show_usage();
107
108                         if (!amroot)            /* Auth bypass only if real UID is zero */
109                                 bb_error_msg_and_die("-f permission denied");
110
111                         safe_strncpy(username, optarg, USERNAME_SIZE);
112                         opt_fflag = 1;
113                         break;
114                 case 'h':
115                         opt_host = optarg;
116                         break;
117                 default:
118                         bb_show_usage();
119                 }
120         }
121
122         if (optind < argc)             /* user from command line (getty) */
123                 safe_strncpy(username, argv[optind], USERNAME_SIZE);
124
125         if (!isatty(0) || !isatty(1) || !isatty(2))
126                 return EXIT_FAILURE;            /* Must be a terminal */
127
128 #ifdef CONFIG_FEATURE_UTMP
129         checkutmp(!amroot);
130 #endif
131
132         tmp = ttyname(0);
133         if (tmp && (strncmp(tmp, "/dev/", 5) == 0))
134                 safe_strncpy(tty, tmp + 5, sizeof(tty));
135         else if (tmp && *tmp == '/')
136                 safe_strncpy(tty, tmp, sizeof(tty));
137         else
138                 safe_strncpy(tty, "UNKNOWN", sizeof(tty));
139
140 #ifdef CONFIG_FEATURE_UTMP
141         if (amroot)
142                 memset(utent.ut_host, 0, sizeof(utent.ut_host));
143 #endif
144
145         if (opt_host) {
146 #ifdef CONFIG_FEATURE_UTMP
147                 safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host));
148 #endif
149                 snprintf(fromhost, sizeof(fromhost)-1, " on `%.100s' from "
150                                         "`%.200s'", tty, opt_host);
151         }
152         else
153                 snprintf(fromhost, sizeof(fromhost)-1, " on `%.100s'", tty);
154
155         bb_setpgrp;
156
157         openlog(bb_applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
158
159         while (1) {
160                 failed = 0;
161
162                 if (!username[0])
163                         if (!login_prompt(username))
164                                 return EXIT_FAILURE;
165
166                 if (!alarmstarted && (TIMEOUT > 0)) {
167                         alarm(TIMEOUT);
168                         alarmstarted = 1;
169                 }
170
171                 pw = getpwnam(username);
172                 if (!pw) {
173                         pw_copy.pw_name   = "UNKNOWN";
174                         pw_copy.pw_passwd = "!";
175                         opt_fflag = 0;
176                         failed = 1;
177                 } else
178                         pw_copy = *pw;
179
180                 pw = &pw_copy;
181
182                 if ((pw->pw_passwd[0] == '!') || (pw->pw_passwd[0] == '*'))
183                         failed = 1;
184
185                 if (opt_fflag) {
186                         opt_fflag = 0;
187                         goto auth_ok;
188                 }
189
190                 if (!failed && (pw->pw_uid == 0) && (!check_tty(tty)))
191                         failed = 1;
192
193                 /* Don't check the password if password entry is empty (!) */
194                 if (!pw->pw_passwd[0])
195                         goto auth_ok;
196
197                 /* authorization takes place here */
198                 if (correct_password(pw))
199                         goto auth_ok;
200
201                 failed = 1;
202
203 auth_ok:
204                 if (!failed)
205                         break;
206
207                 bb_do_delay(FAIL_DELAY);
208                 puts("Login incorrect");
209                 username[0] = 0;
210                 if (++count == 3) {
211                         syslog(LOG_WARNING, "invalid password for `%s'%s", pw->pw_name, fromhost);
212                         return EXIT_FAILURE;
213                 }
214         }
215
216         alarm(0);
217         if (check_nologin(pw->pw_uid == 0))
218                 return EXIT_FAILURE;
219
220 #ifdef CONFIG_FEATURE_UTMP
221         setutmp(username, tty);
222 #endif
223
224         if (*tty != '/')
225                 snprintf(full_tty, sizeof(full_tty)-1, "/dev/%s", tty);
226         else
227                 safe_strncpy(full_tty, tty, sizeof(full_tty)-1);
228
229 #ifdef CONFIG_SELINUX
230         if (is_selinux_enabled()) {
231                 security_context_t old_tty_sid, new_tty_sid;
232
233                 if (get_default_context(username, NULL, &user_sid)) {
234                         bb_error_msg_and_die("unable to get SID for %s",
235                                         username);
236                 }
237                 if (getfilecon(full_tty, &old_tty_sid) < 0) {
238                         bb_perror_msg_and_die("getfilecon(%.100s) failed",
239                                         full_tty);
240                 }
241                 if (security_compute_relabel(user_sid, old_tty_sid,
242                                         SECCLASS_CHR_FILE, &new_tty_sid) != 0) {
243                         bb_perror_msg_and_die("security_change_sid(%.100s) failed",
244                                         full_tty);
245                 }
246                 if (setfilecon(full_tty, new_tty_sid) != 0) {
247                         bb_perror_msg_and_die("chsid(%.100s, %s) failed",
248                                         full_tty, new_tty_sid);
249                 }
250         }
251 #endif
252         if (!is_my_tty(full_tty))
253                 syslog(LOG_ERR, "unable to determine TTY name, got %s", full_tty);
254
255         /* Try these, but don't complain if they fail
256          * (for example when the root fs is read only) */
257         chown(full_tty, pw->pw_uid, pw->pw_gid);
258         chmod(full_tty, 0600);
259
260         if (ENABLE_LOGIN_SCRIPTS) {
261                 char *script = getenv("LOGIN_PRE_SUID_SCRIPT");
262                 if (script) {
263                         char *t_argv[2] = { script, NULL };
264                         switch (fork()) {
265                         case -1: break;
266                         case 0: /* child */
267                                 xchdir("/");
268                                 setenv("LOGIN_TTY", full_tty, 1);
269                                 setenv("LOGIN_USER", pw->pw_name, 1);
270                                 setenv("LOGIN_UID", utoa(pw->pw_uid), 1);
271                                 setenv("LOGIN_GID", utoa(pw->pw_gid), 1);
272                                 setenv("LOGIN_SHELL", pw->pw_shell, 1);
273                                 execvp(script, t_argv);
274                                 exit(1);
275                         default: /* parent */
276                                 wait(NULL);
277                         }
278                 }
279         }
280
281         change_identity(pw);
282         tmp = pw->pw_shell;
283         if (!tmp || !*tmp)
284                 tmp = DEFAULT_SHELL;
285         setup_environment(tmp, 1, !opt_preserve, pw);
286
287         motd();
288         signal(SIGALRM, SIG_DFL);       /* default alarm signal */
289
290         if (pw->pw_uid == 0)
291                 syslog(LOG_INFO, "root login %s", fromhost);
292 #ifdef CONFIG_SELINUX
293         /* well, a simple setexeccon() here would do the job as well,
294          * but let's play the game for now */
295         set_current_security_context(user_sid);
296 #endif
297         run_shell(tmp, 1, 0, 0);        /* exec the shell finally. */
298
299         return EXIT_FAILURE;
300 }
301
302
303 static int login_prompt(char *buf_name)
304 {
305         char buf[1024];
306         char *sp, *ep;
307         int i;
308
309         for (i=0; i<EMPTY_USERNAME_COUNT; i++) {
310                 print_login_prompt();
311
312                 if (!fgets(buf, sizeof(buf)-1, stdin))
313                         return 0;
314
315                 if (!strchr(buf, '\n'))
316                         return 0;
317
318                 for (sp = buf; isspace(*sp); sp++) { }
319                 for (ep = sp; isgraph(*ep); ep++) { }
320
321                 *ep = '\0';
322                 safe_strncpy(buf_name, sp, USERNAME_SIZE);
323                 if (buf_name[0])
324                         return 1;
325         }
326         return 0;
327 }
328
329
330 static int check_nologin(int amroot)
331 {
332         if (access(bb_path_nologin_file, F_OK) == 0) {
333                 FILE *fp;
334                 int c;
335
336                 fp = fopen(bb_path_nologin_file, "r");
337                 if (fp) {
338                         while ((c = getc(fp)) != EOF)
339                                 putchar((c=='\n') ? '\r' : c);
340
341                         fflush(stdout);
342                         fclose(fp);
343                 } else {
344                         puts("\r\nSystem closed for routine maintenance.\r");
345                 }
346                 if (!amroot)
347                         return 1;
348
349                 puts("\r\n[Disconnect bypassed -- root login allowed.]\r");
350         }
351         return 0;
352 }
353
354 #ifdef CONFIG_FEATURE_SECURETTY
355
356 static int check_tty(const char *tty)
357 {
358         FILE *fp;
359         int i;
360         char buf[BUFSIZ];
361
362         fp = fopen(bb_path_securetty_file, "r");
363         if (fp) {
364                 while (fgets(buf, sizeof(buf)-1, fp)) {
365                         for(i = strlen(buf)-1; i>=0; --i) {
366                                 if (!isspace(buf[i]))
367                                         break;
368                         }
369                         buf[++i] = '\0';
370                         if ((buf[0]=='\0') || (buf[0]=='#'))
371                                 continue;
372
373                         if (strcmp(buf, tty)== 0) {
374                                 fclose(fp);
375                                 return 1;
376                         }
377                 }
378                 fclose(fp);
379                 return 0;
380         }
381         /* A missing securetty file is not an error. */
382         return 1;
383 }
384
385 #endif
386
387 /* returns 1 if true */
388 static int is_my_tty(const char *tty)
389 {
390         struct stat by_name, by_fd;
391
392         if (stat(tty, &by_name) || fstat(0, &by_fd))
393                 return 0;
394
395         if (by_name.st_rdev != by_fd.st_rdev)
396                 return 0;
397         else
398                 return 1;
399 }
400
401
402 static void motd(void)
403 {
404         FILE *fp;
405         int c;
406
407         fp = fopen(bb_path_motd_file, "r");
408         if (fp) {
409                 while ((c = getc(fp)) != EOF)
410                         putchar(c);
411                 fclose(fp);
412         }
413 }
414
415
416 #ifdef CONFIG_FEATURE_UTMP
417 // vv  Taken from tinylogin utmp.c  vv
418
419 #define NO_UTENT \
420         "No utmp entry.  You must exec \"login\" from the lowest level \"sh\""
421 #define NO_TTY \
422         "Unable to determine your tty name."
423
424 /*
425  * checkutmp - see if utmp file is correct for this process
426  *
427  *      System V is very picky about the contents of the utmp file
428  *      and requires that a slot for the current process exist.
429  *      The utmp file is scanned for an entry with the same process
430  *      ID.  If no entry exists the process exits with a message.
431  *
432  *      The "picky" flag is for network and other logins that may
433  *      use special flags.  It allows the pid checks to be overridden.
434  *      This means that getty should never invoke login with any
435  *      command line flags.
436  */
437
438 static void checkutmp(int picky)
439 {
440         char *line;
441         struct utmp *ut;
442         pid_t pid = getpid();
443
444         setutent();
445
446         /* First, try to find a valid utmp entry for this process.  */
447         while ((ut = getutent()))
448                 if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
449                         (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
450                         break;
451
452         /* If there is one, just use it, otherwise create a new one.  */
453         if (ut) {
454                 utent = *ut;
455         } else {
456                 time_t t_tmp;
457                 
458                 if (picky) {
459                         puts(NO_UTENT);
460                         exit(1);
461                 }
462                 line = ttyname(0);
463                 if (!line) {
464                         puts(NO_TTY);
465                         exit(1);
466                 }
467                 if (strncmp(line, "/dev/", 5) == 0)
468                         line += 5;
469                 memset(&utent, 0, sizeof(utent));
470                 utent.ut_type = LOGIN_PROCESS;
471                 utent.ut_pid = pid;
472                 strncpy(utent.ut_line, line, sizeof(utent.ut_line));
473                 /* XXX - assumes /dev/tty?? */
474                 strncpy(utent.ut_id, utent.ut_line + 3, sizeof(utent.ut_id));
475                 strncpy(utent.ut_user, "LOGIN", sizeof(utent.ut_user));
476                 t_tmp = (time_t)utent.ut_time;
477                 time(&t_tmp);
478         }
479 }
480
481 /*
482  * setutmp - put a USER_PROCESS entry in the utmp file
483  *
484  *      setutmp changes the type of the current utmp entry to
485  *      USER_PROCESS.  the wtmp file will be updated as well.
486  */
487
488 static void setutmp(const char *name, const char *line ATTRIBUTE_UNUSED)
489 {
490         time_t t_tmp = (time_t)utent.ut_time;
491
492         utent.ut_type = USER_PROCESS;
493         strncpy(utent.ut_user, name, sizeof(utent.ut_user));
494         time(&t_tmp);
495         /* other fields already filled in by checkutmp above */
496         setutent();
497         pututline(&utent);
498         endutent();
499 #ifdef CONFIG_FEATURE_WTMP
500         if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
501                 close(creat(bb_path_wtmp_file, 0664));
502         }
503         updwtmp(bb_path_wtmp_file, &utent);
504 #endif
505 }
506 #endif /* CONFIG_FEATURE_UTMP */