destroy potential overflow for x86_64. Added ATTRIBUTE_UNUSED
[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 ATTRIBUTE_UNUSED)
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                 bb_do_delay(FAIL_DELAY);
202                 puts("Login incorrect");
203                 username[0] = 0;
204                 if ( ++count == 3 ) {
205                         syslog ( LOG_WARNING, "invalid password for `%s'%s\n", pw->pw_name, fromhost);
206                         return EXIT_FAILURE;
207         }
208         }
209
210         alarm ( 0 );
211         if ( check_nologin ( pw-> pw_uid == 0 ))
212                 return EXIT_FAILURE;
213
214 #ifdef CONFIG_FEATURE_UTMP
215         setutmp ( username, tty );
216 #endif
217
218         if ( *tty != '/' )
219                 snprintf ( full_tty, sizeof( full_tty ) - 1, "/dev/%s", tty);
220         else
221                 safe_strncpy ( full_tty, tty, sizeof( full_tty ) - 1 );
222
223 #ifdef CONFIG_SELINUX
224         if (is_selinux_enabled())
225         {
226                 struct stat st;
227                 int rc;
228
229                 if (get_default_context(username, NULL, &sid))
230                 {
231                         fprintf(stderr, "Unable to get SID for %s\n", username);
232                         exit(1);
233                 }
234                 rc = getfilecon(full_tty,&stat_sid);
235                 freecon(stat_sid);
236                 if ((rc<0) || (stat(full_tty, &st)<0))
237                 {
238                         fprintf(stderr, "stat_secure(%.100s) failed: %.100s\n", full_tty, strerror(errno));
239                         return EXIT_FAILURE;
240                 }
241                 if (security_compute_relabel (sid, old_tty_sid, SECCLASS_CHR_FILE, &new_tty_sid) != 0)
242                 {
243                         fprintf(stderr, "security_change_sid(%.100s) failed: %.100s\n", full_tty, strerror(errno));
244                         return EXIT_FAILURE;
245                 }
246                 if(setfilecon(full_tty, new_tty_sid) != 0)
247                 {
248                         fprintf(stderr, "chsid(%.100s, %s) failed: %.100s\n", full_tty, new_tty_sid, strerror(errno));
249                         return EXIT_FAILURE;
250                 }
251                 freecon(sid);
252                 freecon(old_tty_sid);
253                 freecon(new_tty_sid);
254         }
255 #endif
256         if ( !is_my_tty ( full_tty ))
257                 syslog ( LOG_ERR, "unable to determine TTY name, got %s\n", full_tty );
258
259         /* Try these, but don't complain if they fail
260          * (for example when the root fs is read only) */
261         chown ( full_tty, pw-> pw_uid, pw-> pw_gid );
262         chmod ( full_tty, 0600 );
263
264         change_identity ( pw );
265         tmp = pw-> pw_shell;
266         if(!tmp || !*tmp)
267                 tmp = DEFAULT_SHELL;
268         setup_environment ( tmp, 1, !opt_preserve, pw );
269
270         motd ( );
271         signal ( SIGALRM, SIG_DFL );    /* default alarm signal */
272
273         if ( pw-> pw_uid == 0 )
274                 syslog ( LOG_INFO, "root login %s\n", fromhost );
275 #ifdef CONFIG_SELINUX
276         set_current_security_context(sid);
277 #endif
278         run_shell ( tmp, 1, 0, 0);      /* exec the shell finally. */
279
280         return EXIT_FAILURE;
281 }
282
283
284
285 static int login_prompt ( char *buf_name )
286 {
287         char buf [1024];
288         char *sp, *ep;
289         int i;
290
291         for(i=0; i<EMPTY_USERNAME_COUNT; i++) {
292                 print_login_prompt();
293
294                 if ( !fgets ( buf, sizeof( buf ) - 1, stdin ))
295                         return 0;
296
297                 if ( !strchr ( buf, '\n' ))
298                         return 0;
299
300                 for ( sp = buf; isspace ( *sp ); sp++ ) { }
301                 for ( ep = sp; isgraph ( *ep ); ep++ ) { }
302
303                 *ep = 0;
304                 safe_strncpy(buf_name, sp, USERNAME_SIZE);
305                 if(buf_name[0])
306                         return 1;
307         }
308         return 0;
309 }
310
311
312 static int check_nologin ( int amroot )
313 {
314         if ( access ( bb_path_nologin_file, F_OK ) == 0 ) {
315                 FILE *fp;
316                 int c;
317
318                 if (( fp = fopen ( bb_path_nologin_file, "r" ))) {
319                         while (( c = getc ( fp )) != EOF )
320                                 putchar (( c == '\n' ) ? '\r' : c );
321
322                         fflush ( stdout );
323                         fclose ( fp );
324                 } else {
325                         puts ( "\r\nSystem closed for routine maintenance.\r" );
326                 }
327                 if ( !amroot )
328                         return 1;
329
330                 puts ( "\r\n[Disconnect bypassed -- root login allowed.]\r" );
331         }
332         return 0;
333 }
334
335 #ifdef CONFIG_FEATURE_SECURETTY
336
337 static int check_tty ( const char *tty )
338 {
339         FILE *fp;
340         int i;
341         char buf[BUFSIZ];
342
343         if (( fp = fopen ( bb_path_securetty_file, "r" ))) {
344                 while ( fgets ( buf, sizeof( buf ) - 1, fp )) {
345                         for ( i = bb_strlen( buf ) - 1; i >= 0; --i ) {
346                                 if ( !isspace ( buf[i] ))
347                                         break;
348                         }
349                         buf[++i] = '\0';
350                         if (( buf [0] == '\0' ) || ( buf [0] == '#' ))
351                                 continue;
352
353                         if ( strcmp ( buf, tty ) == 0 ) {
354                                 fclose ( fp );
355                                 return 1;
356                         }
357                 }
358                 fclose(fp);
359                 return 0;
360         }
361         /* A missing securetty file is not an error. */
362         return 1;
363 }
364
365 #endif
366
367 /* returns 1 if true */
368 static int is_my_tty ( const char *tty )
369 {
370         struct stat by_name, by_fd;
371
372         if ( stat ( tty, &by_name ) || fstat ( 0, &by_fd ))
373                 return 0;
374
375         if ( by_name. st_rdev != by_fd. st_rdev )
376                 return 0;
377         else
378                 return 1;
379 }
380
381
382 static void motd (void)
383 {
384         FILE *fp;
385         register int c;
386
387         if (( fp = fopen ( bb_path_motd_file, "r" ))) {
388                 while (( c = getc ( fp )) != EOF )
389                         putchar ( c );
390                 fclose ( fp );
391         }
392 }
393
394
395 #ifdef CONFIG_FEATURE_UTMP
396 // vv  Taken from tinylogin utmp.c  vv
397
398 #define NO_UTENT \
399         "No utmp entry.  You must exec \"login\" from the lowest level \"sh\""
400 #define NO_TTY \
401         "Unable to determine your tty name."
402
403 /*
404  * checkutmp - see if utmp file is correct for this process
405  *
406  *      System V is very picky about the contents of the utmp file
407  *      and requires that a slot for the current process exist.
408  *      The utmp file is scanned for an entry with the same process
409  *      ID.  If no entry exists the process exits with a message.
410  *
411  *      The "picky" flag is for network and other logins that may
412  *      use special flags.  It allows the pid checks to be overridden.
413  *      This means that getty should never invoke login with any
414  *      command line flags.
415  */
416
417 static void checkutmp(int picky)
418 {
419         char *line;
420         struct utmp *ut;
421         pid_t pid = getpid();
422
423         setutent();
424
425         /* First, try to find a valid utmp entry for this process.  */
426         while ((ut = getutent()))
427                 if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
428                         (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
429                         break;
430
431         /* If there is one, just use it, otherwise create a new one.  */
432         if (ut) {
433                 utent = *ut;
434         } else {
435                 time_t t_tmp;
436                 
437                 if (picky) {
438                         puts(NO_UTENT);
439                         exit(1);
440                 }
441                 line = ttyname(0);
442                 if (!line) {
443                         puts(NO_TTY);
444                         exit(1);
445                 }
446                 if (strncmp(line, "/dev/", 5) == 0)
447                         line += 5;
448                 memset((void *) &utent, 0, sizeof utent);
449                 utent.ut_type = LOGIN_PROCESS;
450                 utent.ut_pid = pid;
451                 strncpy(utent.ut_line, line, sizeof utent.ut_line);
452                 /* XXX - assumes /dev/tty?? */
453                 strncpy(utent.ut_id, utent.ut_line + 3, sizeof utent.ut_id);
454                 strncpy(utent.ut_user, "LOGIN", sizeof utent.ut_user);
455                 t_tmp = (time_t)utent.ut_time;
456                 time(&t_tmp);
457         }
458 }
459
460 /*
461  * setutmp - put a USER_PROCESS entry in the utmp file
462  *
463  *      setutmp changes the type of the current utmp entry to
464  *      USER_PROCESS.  the wtmp file will be updated as well.
465  */
466
467 static void setutmp(const char *name, const char *line ATTRIBUTE_UNUSED)
468 {
469         utent.ut_type = USER_PROCESS;
470         strncpy(utent.ut_user, name, sizeof utent.ut_user);
471         time((time_t*)&utent.ut_time);
472         /* other fields already filled in by checkutmp above */
473         setutent();
474         pututline(&utent);
475         endutent();
476 #ifdef CONFIG_FEATURE_WTMP
477         if (access(_PATH_WTMP, R_OK|W_OK) == -1) {
478                 close(creat(_PATH_WTMP, 0664));
479         }
480         updwtmp(_PATH_WTMP, &utent);
481 #endif
482 }
483 #endif /* CONFIG_FEATURE_UTMP */