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