714829db1871aecc02eafec171f1318e245f4548
[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
20
21 #ifdef CONFIG_FEATURE_U_W_TMP
22 // import from utmp.c
23 static void checkutmp(int picky);
24 static void setutmp(const char *name, const char *line);
25 #endif
26
27 // import from encrypt.c
28 extern char *pw_encrypt(const char *clear, const char *salt);
29
30
31 // login defines
32 #define TIMEOUT       60
33 #define EMPTY_USERNAME_COUNT    10
34 #define USERNAME_SIZE 32
35
36 /* Stuff global to this file */
37 struct utmp utent;
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         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
82         username[0]=0;
83         amroot = ( getuid ( ) == 0 );
84         signal ( SIGALRM, alarm_handler );
85         
86         if (( argc > 1 ) && ( TIMEOUT > 0 )) {
87                 alarm ( TIMEOUT );
88                 alarmstarted = 1;
89         }
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 seperate token
99                          * (-f root, *NOT* -froot). --marekm
100                          */
101                         if ( optarg != argv[optind-1] )
102                                 show_usage ( );
103
104                         if ( !amroot )          /* Auth bypass only if real UID is zero */
105                                 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                         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_U_W_TMP
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
132                 safe_strncpy ( tty, "UNKNOWN", sizeof( tty ));
133
134         if ( amroot )
135                 memset ( utent.ut_host, 0, sizeof utent.ut_host );
136         
137         if ( opt_host ) {
138                 safe_strncpy ( utent.ut_host, opt_host, sizeof( utent. ut_host ));
139                 
140                 snprintf ( fromhost, sizeof( fromhost ) - 1, " on `%.100s' from `%.200s'", tty, opt_host );
141         }
142         else
143                 snprintf ( fromhost, sizeof( fromhost ) - 1, " on `%.100s'", tty );
144         
145         setpgrp();
146
147         openlog ( "login", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH );
148
149         while ( 1 ) {
150                 failed = 0;
151
152                 if ( !username[0] )
153                         if(!login_prompt ( username ))
154                                 return EXIT_FAILURE;
155
156                 if ( !alarmstarted && ( TIMEOUT > 0 )) {
157                         alarm ( TIMEOUT );
158                         alarmstarted = 1;
159                 }
160
161                 if (!( pw = getpwnam ( username ))) {
162                         pw_copy.pw_name   = "UNKNOWN";
163                         pw_copy.pw_passwd = "!";
164                         opt_fflag = 0;
165                         failed = 1;
166                 } else 
167                         pw_copy = *pw;
168
169                 pw = &pw_copy;
170
171                 if (( pw-> pw_passwd [0] == '!' ) || ( pw-> pw_passwd[0] == '*' ))
172                         failed = 1;
173                 
174                 if ( opt_fflag ) {
175                         opt_fflag = 0;
176                         goto auth_ok;
177                 }
178
179                 if (!failed && ( pw-> pw_uid == 0 ) && ( !check_tty ( tty )))
180                         failed = 1;
181
182                 /* Don't check the password if password entry is empty (!) */
183                 if ( !pw-> pw_passwd[0] )
184                         goto auth_ok;
185
186                 /* authorization takes place here */
187                 if ( correct_password ( pw ))
188                         goto auth_ok;
189
190                 failed = 1;
191                 
192 auth_ok:
193                 if ( !failed) 
194                         break;
195
196                 { // delay next try
197                         time_t start, now;
198                         
199                         time ( &start );
200                         now = start;
201                         while ( difftime ( now, start ) < FAIL_DELAY) {
202                                 sleep ( FAIL_DELAY );
203                                 time ( &now );
204                         }
205                 }
206
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_U_W_TMP
220         setutmp ( username, tty );
221 #endif
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         if ( !is_my_tty ( full_tty ))  
228                 syslog ( LOG_ERR, "unable to determine TTY name, got %s\n", full_tty );
229                 
230         /* Try these, but don't complain if they fail 
231          * (for example when the root fs is read only) */
232         chown ( full_tty, pw-> pw_uid, pw-> pw_gid );
233         chmod ( full_tty, 0600 );
234
235         change_identity ( pw );
236         setup_environment ( pw-> pw_shell, 1, !opt_preserve, pw );
237
238         motd ( );
239         signal ( SIGALRM, SIG_DFL );    /* default alarm signal */
240
241         if ( pw-> pw_uid == 0 ) 
242                 syslog ( LOG_INFO, "root login %s\n", fromhost );
243         
244         run_shell ( pw-> pw_shell, 1, 0, 0 );   /* exec the shell finally. */
245         
246         return EXIT_FAILURE;
247 }
248
249
250
251 static int login_prompt ( char *buf_name )
252 {
253         char buf [1024];
254         char *sp, *ep;
255         int i;
256
257         for(i=0; i<EMPTY_USERNAME_COUNT; i++) {
258         gethostname ( buf, sizeof( buf ));
259         printf ( "\nBusyBox on %s login: ", buf );
260         fflush ( stdout );
261
262         if ( !fgets ( buf, sizeof( buf ) - 1, stdin ))
263                 return 0;
264                 
265                 if ( !strchr ( buf, '\n' ))
266                 return 0;
267         
268         for ( sp = buf; isspace ( *sp ); sp++ ) { }
269         for ( ep = sp; isgraph ( *ep ); ep++ ) { }
270
271         *ep = 0;                
272                 safe_strncpy(buf_name, sp, USERNAME_SIZE);
273                 if(buf_name[0])
274                         return 1;
275         }
276         return 0;
277 }
278
279
280 static int check_nologin ( int amroot )
281 {
282         if ( access ( nologin_file, F_OK ) == 0 ) {
283                 FILE *fp;
284                 int c;
285
286                 if (( fp = fopen ( nologin_file, "r" ))) {
287                         while (( c = getc ( fp )) != EOF )
288                                 putchar (( c == '\n' ) ? '\r' : c );
289
290                         fflush ( stdout );
291                         fclose ( fp );
292                 } else {
293                         puts ( "\r\nSystem closed for routine maintenance.\r" );
294                 }
295                 if ( !amroot )
296                         return 1;
297                         
298                 puts ( "\r\n[Disconnect bypassed -- root login allowed.]\r" );
299         }
300         return 0;
301 }
302
303 #ifdef CONFIG_FEATURE_SECURETTY
304
305 static int check_tty ( const char *tty )
306 {
307         FILE *fp;
308         int i;
309         char buf[BUFSIZ];
310
311         if (( fp = fopen ( securetty_file, "r" ))) {
312                 while ( fgets ( buf, sizeof( buf ) - 1, fp )) {
313                         for ( i = xstrlen( buf ) - 1; i >= 0; --i ) {
314                                 if ( !isspace ( buf[i] ))
315                                         break;
316                         }
317                         buf[++i] = '\0';
318                         if (( buf [0] == '\0' ) || ( buf [0] == '#' ))
319                                 continue;
320
321                         if ( strcmp ( buf, tty ) == 0 ) {
322                                 fclose ( fp );
323                                 return 1;
324                         }
325                 }
326                 fclose(fp);
327                 return 0;
328         }
329         else {
330                 syslog ( LOG_WARNING, "cannot open securetty file.\n" );
331                 return 1;
332         }
333 }
334
335 #endif
336
337 /* returns 1 if true */
338 static int is_my_tty ( const char *tty )
339 {
340         struct stat by_name, by_fd;
341
342         if ( stat ( tty, &by_name ) || fstat ( 0, &by_fd ))
343                 return 0;
344                 
345         if ( by_name. st_rdev != by_fd. st_rdev )
346                 return 0;
347         else
348                 return 1;
349 }
350
351
352 static void motd ( )
353 {
354         FILE *fp;
355         register int c;
356
357         if (( fp = fopen ( motd_file, "r" ))) {
358                 while (( c = getc ( fp )) != EOF ) 
359                         putchar ( c );          
360                 fclose ( fp );
361         }
362 }
363
364
365 #ifdef CONFIG_FEATURE_U_W_TMP
366 // vv  Taken from tinylogin utmp.c  vv
367
368 #define _WTMP_FILE "/var/log/wtmp"
369
370 #define NO_UTENT \
371         "No utmp entry.  You must exec \"login\" from the lowest level \"sh\""
372 #define NO_TTY \
373         "Unable to determine your tty name."
374
375 /*
376  * checkutmp - see if utmp file is correct for this process
377  *
378  *      System V is very picky about the contents of the utmp file
379  *      and requires that a slot for the current process exist.
380  *      The utmp file is scanned for an entry with the same process
381  *      ID.  If no entry exists the process exits with a message.
382  *
383  *      The "picky" flag is for network and other logins that may
384  *      use special flags.  It allows the pid checks to be overridden.
385  *      This means that getty should never invoke login with any
386  *      command line flags.
387  */
388
389 static void checkutmp(int picky)
390 {
391         char *line;
392         struct utmp *ut;
393         pid_t pid = getpid();
394
395         setutent();
396
397         /* First, try to find a valid utmp entry for this process.  */
398         while ((ut = getutent()))
399                 if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
400                         (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
401                         break;
402
403         /* If there is one, just use it, otherwise create a new one.  */
404         if (ut) {
405                 utent = *ut;
406         } else {
407                 if (picky) {
408                         puts(NO_UTENT);
409                         exit(1);
410                 }
411                 line = ttyname(0);
412                 if (!line) {
413                         puts(NO_TTY);
414                         exit(1);
415                 }
416                 if (strncmp(line, "/dev/", 5) == 0)
417                         line += 5;
418                 memset((void *) &utent, 0, sizeof utent);
419                 utent.ut_type = LOGIN_PROCESS;
420                 utent.ut_pid = pid;
421                 strncpy(utent.ut_line, line, sizeof utent.ut_line);
422                 /* XXX - assumes /dev/tty?? */
423                 strncpy(utent.ut_id, utent.ut_line + 3, sizeof utent.ut_id);
424                 strncpy(utent.ut_user, "LOGIN", sizeof utent.ut_user);
425                 time(&utent.ut_time);
426         }
427 }
428
429 /*
430  * setutmp - put a USER_PROCESS entry in the utmp file
431  *
432  *      setutmp changes the type of the current utmp entry to
433  *      USER_PROCESS.  the wtmp file will be updated as well.
434  */
435
436 static void setutmp(const char *name, const char *line)
437 {
438         utent.ut_type = USER_PROCESS;
439         strncpy(utent.ut_user, name, sizeof utent.ut_user);
440         time(&utent.ut_time);
441         /* other fields already filled in by checkutmp above */
442         setutent();
443         pututline(&utent);
444         endutent();
445         updwtmp(_WTMP_FILE, &utent);
446 }
447 #endif /* CONFIG_FEATURE_U_W_TMP */