39d980fa8022870d3142f06079c7dc3477ce73ee
[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 // login defines
39 #define TIMEOUT       60
40 #define EMPTY_USERNAME_COUNT    10
41 #define 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                 if (!( pw = getpwnam ( username ))) {
171                         pw_copy.pw_name   = "UNKNOWN";
172                         pw_copy.pw_passwd = "!";
173                         opt_fflag = 0;
174                         failed = 1;
175                 } else
176                         pw_copy = *pw;
177
178                 pw = &pw_copy;
179
180                 if (( pw-> pw_passwd [0] == '!' ) || ( pw-> pw_passwd[0] == '*' ))
181                         failed = 1;
182
183                 if ( opt_fflag ) {
184                         opt_fflag = 0;
185                         goto auth_ok;
186                 }
187
188                 if (!failed && ( pw-> pw_uid == 0 ) && ( !check_tty ( tty )))
189                         failed = 1;
190
191                 /* Don't check the password if password entry is empty (!) */
192                 if ( !pw-> pw_passwd[0] )
193                         goto auth_ok;
194
195                 /* authorization takes place here */
196                 if ( correct_password ( pw ))
197                         goto auth_ok;
198
199                 failed = 1;
200
201 auth_ok:
202                 if ( !failed)
203                         break;
204
205                 bb_do_delay(FAIL_DELAY);
206                 puts("Login incorrect");
207                 username[0] = 0;
208                 if ( ++count == 3 ) {
209                         syslog ( LOG_WARNING, "invalid password for `%s'%s\n", pw->pw_name, fromhost);
210                         return EXIT_FAILURE;
211         }
212         }
213
214         alarm ( 0 );
215         if ( check_nologin ( pw-> pw_uid == 0 ))
216                 return EXIT_FAILURE;
217
218 #ifdef CONFIG_FEATURE_UTMP
219         setutmp ( username, tty );
220 #endif
221
222         if ( *tty != '/' )
223                 snprintf ( full_tty, sizeof( full_tty ) - 1, "/dev/%s", tty);
224         else
225                 safe_strncpy ( full_tty, tty, sizeof( full_tty ) - 1 );
226
227 #ifdef CONFIG_SELINUX
228         if (is_selinux_enabled())
229         {
230                 security_context_t old_tty_sid, new_tty_sid;
231
232                 if (get_default_context(username, NULL, &user_sid))
233                 {
234                         fprintf(stderr, "Unable to get SID for %s\n", username);
235                         exit(1);
236                 }
237                 if (getfilecon(full_tty, &old_tty_sid) < 0)
238                 {
239                         fprintf(stderr, "getfilecon(%.100s) failed: %.100s\n", full_tty, strerror(errno));
240                         return EXIT_FAILURE;
241                 }
242                 if (security_compute_relabel(user_sid, old_tty_sid, SECCLASS_CHR_FILE, &new_tty_sid) != 0)
243                 {
244                         fprintf(stderr, "security_change_sid(%.100s) failed: %.100s\n", full_tty, strerror(errno));
245                         return EXIT_FAILURE;
246                 }
247                 if(setfilecon(full_tty, new_tty_sid) != 0)
248                 {
249                         fprintf(stderr, "chsid(%.100s, %s) failed: %.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
306 static int login_prompt ( char *buf_name )
307 {
308         char buf [1024];
309         char *sp, *ep;
310         int i;
311
312         for(i=0; i<EMPTY_USERNAME_COUNT; i++) {
313                 print_login_prompt();
314
315                 if ( !fgets ( buf, sizeof( buf ) - 1, stdin ))
316                         return 0;
317
318                 if ( !strchr ( buf, '\n' ))
319                         return 0;
320
321                 for ( sp = buf; isspace ( *sp ); sp++ ) { }
322                 for ( ep = sp; isgraph ( *ep ); ep++ ) { }
323
324                 *ep = 0;
325                 safe_strncpy(buf_name, sp, USERNAME_SIZE);
326                 if(buf_name[0])
327                         return 1;
328         }
329         return 0;
330 }
331
332
333 static int check_nologin ( int amroot )
334 {
335         if ( access ( bb_path_nologin_file, F_OK ) == 0 ) {
336                 FILE *fp;
337                 int c;
338
339                 if (( fp = fopen ( bb_path_nologin_file, "r" ))) {
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         if (( fp = fopen ( bb_path_securetty_file, "r" ))) {
365                 while ( fgets ( buf, sizeof( buf ) - 1, fp )) {
366                         for ( i = strlen( buf ) - 1; i >= 0; --i ) {
367                                 if ( !isspace ( buf[i] ))
368                                         break;
369                         }
370                         buf[++i] = '\0';
371                         if (( buf [0] == '\0' ) || ( buf [0] == '#' ))
372                                 continue;
373
374                         if ( strcmp ( buf, tty ) == 0 ) {
375                                 fclose ( fp );
376                                 return 1;
377                         }
378                 }
379                 fclose(fp);
380                 return 0;
381         }
382         /* A missing securetty file is not an error. */
383         return 1;
384 }
385
386 #endif
387
388 /* returns 1 if true */
389 static int is_my_tty ( const char *tty )
390 {
391         struct stat by_name, by_fd;
392
393         if ( stat ( tty, &by_name ) || fstat ( 0, &by_fd ))
394                 return 0;
395
396         if ( by_name. st_rdev != by_fd. st_rdev )
397                 return 0;
398         else
399                 return 1;
400 }
401
402
403 static void motd (void)
404 {
405         FILE *fp;
406         int c;
407
408         if (( fp = fopen ( bb_path_motd_file, "r" ))) {
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 */