82be87eb366f45369d7e1a260dd4a60b06ad527b
[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, "\nLogin timed out after %d seconds.\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 `%.200s'", tty, opt_host);
150         }
151         else
152                 snprintf(fromhost, sizeof(fromhost)-1, " on `%.100s'", tty);
153
154         bb_setpgrp;
155
156         openlog("login", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
157
158         while (1) {
159                 failed = 0;
160
161                 if (!username[0])
162                         if (!login_prompt(username))
163                                 return EXIT_FAILURE;
164
165                 if (!alarmstarted && (TIMEOUT > 0)) {
166                         alarm(TIMEOUT);
167                         alarmstarted = 1;
168                 }
169
170                 pw = getpwnam(username);
171                 if (!pw) {
172                         pw_copy.pw_name   = "UNKNOWN";
173                         pw_copy.pw_passwd = "!";
174                         opt_fflag = 0;
175                         failed = 1;
176                 } else
177                         pw_copy = *pw;
178
179                 pw = &pw_copy;
180
181                 if ((pw->pw_passwd[0] == '!') || (pw->pw_passwd[0] == '*'))
182                         failed = 1;
183
184                 if (opt_fflag) {
185                         opt_fflag = 0;
186                         goto auth_ok;
187                 }
188
189                 if (!failed && (pw->pw_uid == 0) && (!check_tty(tty)))
190                         failed = 1;
191
192                 /* Don't check the password if password entry is empty (!) */
193                 if (!pw->pw_passwd[0])
194                         goto auth_ok;
195
196                 /* authorization takes place here */
197                 if (correct_password(pw))
198                         goto auth_ok;
199
200                 failed = 1;
201
202 auth_ok:
203                 if (!failed)
204                         break;
205
206                 bb_do_delay(FAIL_DELAY);
207                 puts("Login incorrect");
208                 username[0] = 0;
209                 if (++count == 3) {
210                         syslog(LOG_WARNING, "invalid password for `%s'%s'\n", pw->pw_name, fromhost);
211                         return EXIT_FAILURE;
212                 }
213         }
214
215         alarm(0);
216         if (check_nologin(pw->pw_uid == 0))
217                 return EXIT_FAILURE;
218
219 #ifdef CONFIG_FEATURE_UTMP
220         setutmp(username, tty);
221 #endif
222
223         if (*tty != '/')
224                 snprintf(full_tty, sizeof(full_tty)-1, "/dev/%s", tty);
225         else
226                 safe_strncpy(full_tty, tty, sizeof(full_tty)-1);
227
228 #ifdef CONFIG_SELINUX
229         if (is_selinux_enabled()) {
230                 security_context_t old_tty_sid, new_tty_sid;
231
232                 if (get_default_context(username, NULL, &user_sid)) {
233                         fprintf(stderr, "Unable to get SID for %s\n", username);
234                         exit(1);
235                 }
236                 if (getfilecon(full_tty, &old_tty_sid) < 0) {
237                         fprintf(stderr, "getfilecon(%.100s) failed: "
238                                         "%.100s\n", full_tty, strerror(errno));
239                         return EXIT_FAILURE;
240                 }
241                 if (security_compute_relabel(user_sid, old_tty_sid, SECCLASS_CHR_FILE, 
242                                                         &new_tty_sid) != 0) {
243                         fprintf(stderr, "security_change_sid(%.100s) failed: "
244                                         "%.100s\n", full_tty, strerror(errno));
245                         return EXIT_FAILURE;
246                 }
247                 if (setfilecon(full_tty, new_tty_sid) != 0) {
248                         fprintf(stderr, "chsid(%.100s, %s) failed: "
249                                 "%.100s\n", full_tty, new_tty_sid, strerror(errno));
250                         return EXIT_FAILURE;
251                 }
252         }
253 #endif
254         if (!is_my_tty(full_tty))
255                 syslog(LOG_ERR, "unable to determine TTY name, got %s\n", full_tty);
256
257         /* Try these, but don't complain if they fail
258          * (for example when the root fs is read only) */
259         chown(full_tty, pw->pw_uid, pw->pw_gid);
260         chmod(full_tty, 0600);
261
262         if (ENABLE_LOGIN_SCRIPTS) {
263                 char *script = getenv("LOGIN_PRE_SUID_SCRIPT");
264                 if (script) {
265                         char *t_argv[2] = { script, NULL };
266                         switch (fork()) {
267                         case -1: break;
268                         case 0: /* child */
269                                 xchdir("/");
270                                 setenv("LOGIN_TTY", full_tty, 1);
271                                 setenv("LOGIN_USER", pw->pw_name, 1);
272                                 setenv("LOGIN_UID", utoa(pw->pw_uid), 1);
273                                 setenv("LOGIN_GID", utoa(pw->pw_gid), 1);
274                                 setenv("LOGIN_SHELL", pw->pw_shell, 1);
275                                 execvp(script, t_argv);
276                                 exit(1);
277                         default: /* parent */
278                                 wait(NULL);
279                         }
280                 }
281         }
282
283         change_identity(pw);
284         tmp = pw->pw_shell;
285         if (!tmp || !*tmp)
286                 tmp = DEFAULT_SHELL;
287         setup_environment(tmp, 1, !opt_preserve, pw);
288
289         motd();
290         signal(SIGALRM, SIG_DFL);       /* default alarm signal */
291
292         if (pw->pw_uid == 0)
293                 syslog(LOG_INFO, "root login %s\n", fromhost);
294 #ifdef CONFIG_SELINUX
295         /* well, a simple setexeccon() here would do the job as well,
296          * but let's play the game for now */
297         set_current_security_context(user_sid);
298 #endif
299         run_shell(tmp, 1, 0, 0);        /* exec the shell finally. */
300
301         return EXIT_FAILURE;
302 }
303
304
305 static int login_prompt(char *buf_name)
306 {
307         char buf[1024];
308         char *sp, *ep;
309         int i;
310
311         for (i=0; i<EMPTY_USERNAME_COUNT; i++) {
312                 print_login_prompt();
313
314                 if (!fgets(buf, sizeof(buf)-1, stdin))
315                         return 0;
316
317                 if (!strchr(buf, '\n'))
318                         return 0;
319
320                 for (sp = buf; isspace(*sp); sp++) { }
321                 for (ep = sp; isgraph(*ep); ep++) { }
322
323                 *ep = '\0';
324                 safe_strncpy(buf_name, sp, USERNAME_SIZE);
325                 if (buf_name[0])
326                         return 1;
327         }
328         return 0;
329 }
330
331
332 static int check_nologin(int amroot)
333 {
334         if (access(bb_path_nologin_file, F_OK) == 0) {
335                 FILE *fp;
336                 int c;
337
338                 fp = fopen(bb_path_nologin_file, "r");
339                 if (fp) {
340                         while ((c = getc(fp)) != EOF)
341                                 putchar((c=='\n') ? '\r' : c);
342
343                         fflush(stdout);
344                         fclose(fp);
345                 } else {
346                         puts("\r\nSystem closed for routine maintenance.\r");
347                 }
348                 if (!amroot)
349                         return 1;
350
351                 puts("\r\n[Disconnect bypassed -- root login allowed.]\r");
352         }
353         return 0;
354 }
355
356 #ifdef CONFIG_FEATURE_SECURETTY
357
358 static int check_tty(const char *tty)
359 {
360         FILE *fp;
361         int i;
362         char buf[BUFSIZ];
363
364         fp = fopen(bb_path_securetty_file, "r");
365         if (fp) {
366                 while (fgets(buf, sizeof(buf)-1, fp)) {
367                         for(i = strlen(buf)-1; i>=0; --i) {
368                                 if (!isspace(buf[i]))
369                                         break;
370                         }
371                         buf[++i] = '\0';
372                         if ((buf[0]=='\0') || (buf[0]=='#'))
373                                 continue;
374
375                         if (strcmp(buf, tty)== 0) {
376                                 fclose(fp);
377                                 return 1;
378                         }
379                 }
380                 fclose(fp);
381                 return 0;
382         }
383         /* A missing securetty file is not an error. */
384         return 1;
385 }
386
387 #endif
388
389 /* returns 1 if true */
390 static int is_my_tty(const char *tty)
391 {
392         struct stat by_name, by_fd;
393
394         if (stat(tty, &by_name) || fstat(0, &by_fd))
395                 return 0;
396
397         if (by_name.st_rdev != by_fd.st_rdev)
398                 return 0;
399         else
400                 return 1;
401 }
402
403
404 static void motd(void)
405 {
406         FILE *fp;
407         int c;
408
409         fp = fopen(bb_path_motd_file, "r");
410         if (fp) {
411                 while ((c = getc(fp)) != EOF)
412                         putchar(c);
413                 fclose(fp);
414         }
415 }
416
417
418 #ifdef CONFIG_FEATURE_UTMP
419 // vv  Taken from tinylogin utmp.c  vv
420
421 #define NO_UTENT \
422         "No utmp entry.  You must exec \"login\" from the lowest level \"sh\""
423 #define NO_TTY \
424         "Unable to determine your tty name."
425
426 /*
427  * checkutmp - see if utmp file is correct for this process
428  *
429  *      System V is very picky about the contents of the utmp file
430  *      and requires that a slot for the current process exist.
431  *      The utmp file is scanned for an entry with the same process
432  *      ID.  If no entry exists the process exits with a message.
433  *
434  *      The "picky" flag is for network and other logins that may
435  *      use special flags.  It allows the pid checks to be overridden.
436  *      This means that getty should never invoke login with any
437  *      command line flags.
438  */
439
440 static void checkutmp(int picky)
441 {
442         char *line;
443         struct utmp *ut;
444         pid_t pid = getpid();
445
446         setutent();
447
448         /* First, try to find a valid utmp entry for this process.  */
449         while ((ut = getutent()))
450                 if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
451                         (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
452                         break;
453
454         /* If there is one, just use it, otherwise create a new one.  */
455         if (ut) {
456                 utent = *ut;
457         } else {
458                 time_t t_tmp;
459                 
460                 if (picky) {
461                         puts(NO_UTENT);
462                         exit(1);
463                 }
464                 line = ttyname(0);
465                 if (!line) {
466                         puts(NO_TTY);
467                         exit(1);
468                 }
469                 if (strncmp(line, "/dev/", 5) == 0)
470                         line += 5;
471                 memset(&utent, 0, sizeof(utent));
472                 utent.ut_type = LOGIN_PROCESS;
473                 utent.ut_pid = pid;
474                 strncpy(utent.ut_line, line, sizeof(utent.ut_line));
475                 /* XXX - assumes /dev/tty?? */
476                 strncpy(utent.ut_id, utent.ut_line + 3, sizeof(utent.ut_id));
477                 strncpy(utent.ut_user, "LOGIN", sizeof(utent.ut_user));
478                 t_tmp = (time_t)utent.ut_time;
479                 time(&t_tmp);
480         }
481 }
482
483 /*
484  * setutmp - put a USER_PROCESS entry in the utmp file
485  *
486  *      setutmp changes the type of the current utmp entry to
487  *      USER_PROCESS.  the wtmp file will be updated as well.
488  */
489
490 static void setutmp(const char *name, const char *line ATTRIBUTE_UNUSED)
491 {
492         time_t t_tmp = (time_t)utent.ut_time;
493
494         utent.ut_type = USER_PROCESS;
495         strncpy(utent.ut_user, name, sizeof(utent.ut_user));
496         time(&t_tmp);
497         /* other fields already filled in by checkutmp above */
498         setutent();
499         pututline(&utent);
500         endutent();
501 #ifdef CONFIG_FEATURE_WTMP
502         if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
503                 close(creat(bb_path_wtmp_file, 0664));
504         }
505         updwtmp(bb_path_wtmp_file, &utent);
506 #endif
507 }
508 #endif /* CONFIG_FEATURE_UTMP */