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