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