1/* login.c - Start a session on the system.
2 *
3 * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com>
4 *
5 * No support for PAM/securetty/selinux/login script/issue/utmp
6 * Relies on libcrypt for hash calculation.
7 *
8 * TODO: this command predates "pending" but needs cleanup. It #defines
9 * random stuff, calls exit() form a signal handler... yeah.
10
11USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
12
13config LOGIN
14  bool "login"
15  default y
16  depends on TOYBOX_SHADOW
17  help
18    usage: login [-p] [-h host] [-f USERNAME] [USERNAME]
19
20    Log in as a user, prompting for username and password if necessary.
21
22    -p	Preserve environment
23    -h	The name of the remote host for this login
24    -f	login as USERNAME without authentication
25*/
26
27#define FOR_login
28#include "toys.h"
29
30GLOBALS(
31  char *hostname;
32  char *username;
33
34  int login_timeout, login_fail_timeout;
35)
36
37static void login_timeout_handler(int sig __attribute__((unused)))
38{
39  printf("\nLogin timed out after %d seconds.\n", TT.login_timeout);
40  exit(0);
41}
42
43void login_main(void)
44{
45  char *forbid[] = {
46    "BASH_ENV", "ENV", "HOME", "IFS", "LD_LIBRARY_PATH", "LD_PRELOAD",
47    "LD_TRACE_LOADED_OBJECTS", "LD_BIND_NOW", "LD_AOUT_LIBRARY_PATH",
48    "LD_AOUT_PRELOAD", "LD_NOWARN", "LD_KEEPDIR", "SHELL"
49  };
50  int hh = toys.optflags&FLAG_h, count, tty;
51  char uu[33], *username, *pass = 0, *ss;
52  struct passwd *pwd = 0;
53
54  for (tty=0; tty<3; tty++) if (isatty(tty)) break;
55  if (tty == 3) error_exit("no tty");
56
57  for (count = 0; count < ARRAY_LEN(forbid); count++) unsetenv(forbid[count]);
58
59  openlog("login", LOG_PID | LOG_CONS, LOG_AUTH);
60  xsignal(SIGALRM, login_timeout_handler);
61
62  if (TT.username) username = TT.username;
63  else username = *toys.optargs;
64  for (count = 0; count < 3; count++) {
65    alarm(TT.login_timeout = 60);
66    tcflush(0, TCIFLUSH);
67
68    if (!username) {
69      int i;
70
71      memset(username = uu, 0, sizeof(uu));
72      gethostname(uu, sizeof(uu)-1);
73      printf("%s%slogin: ", *uu ? uu : "", *uu ? " " : "");
74      fflush(stdout);
75
76      if(!fgets(uu, sizeof(uu)-1, stdin)) _exit(1);
77
78      // Remove trailing \n and so on
79      for (i = 0; i<sizeof(uu); i++) if (uu[i]<=' ' || uu[i]==':') uu[i]=0;
80      if (!*uu) {
81        username = 0;
82        continue;
83      }
84    }
85
86    // If user exists and isn't locked
87    pwd = getpwnam(username);
88    if (pwd && *pwd->pw_passwd != '!' && *pwd->pw_passwd != '*') {
89
90      // Pre-authenticated or passwordless
91      if (TT.username || !*pwd->pw_passwd) break;
92
93      // fetch shadow password if necessary
94      if (*(pass = pwd->pw_passwd) == 'x') {
95        struct spwd *spwd = getspnam (username);
96
97        if (spwd) pass = spwd->sp_pwdp;
98      }
99    } else if (TT.username) error_exit("bad -f '%s'", TT.username);
100
101    // Verify password. (Prompt for password _before_ checking disable state.)
102    if (!read_password(toybuf, sizeof(toybuf), "Password: ")) {
103      int x = pass && (ss = crypt(toybuf, pass)) && !strcmp(pass, ss);
104
105      // password go bye-bye now.
106      memset(toybuf, 0, sizeof(toybuf));
107      if (x) break;
108    }
109
110    syslog(LOG_WARNING, "invalid password for '%s' on %s %s%s", pwd->pw_name,
111      ttyname(tty), hh ? "from " : "", hh ? TT.hostname : "");
112
113    sleep(3);
114    puts("Login incorrect");
115
116    username = 0;
117    pwd = 0;
118  }
119
120  alarm(0);
121  // This had password data in it, and we reuse for motd below
122  memset(toybuf, 0, sizeof(toybuf));
123
124  if (!pwd) error_exit("max retries (3)");
125
126  // Check twice because "this file exists" is a security test, and in
127  // theory filehandle exhaustion or other error could make open/read fail.
128  if (pwd->pw_uid && !access("/etc/nologin", R_OK)) {
129    ss = readfile("/etc/nologin", toybuf, sizeof(toybuf));
130    puts ((ss && *ss) ? ss : "nologin");
131    free(ss);
132    toys.exitval = 1;
133
134    return;
135  }
136
137  xsetuser(pwd);
138
139  if (chdir(pwd->pw_dir)) printf("bad $HOME: %s\n", pwd->pw_dir);
140
141  if (!(toys.optflags&FLAG_p)) {
142    char *term = getenv("TERM");
143
144    clearenv();
145    if (term) setenv("TERM", term, 1);
146  }
147
148  setenv("USER", pwd->pw_name, 1);
149  setenv("LOGNAME", pwd->pw_name, 1);
150  setenv("HOME", pwd->pw_dir, 1);
151  setenv("SHELL", pwd->pw_shell, 1);
152
153  // Message of the day
154  if ((ss = readfile("/etc/motd", 0, 0))) {
155    puts(ss);
156    free(ss);
157  }
158
159  syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name,
160    ttyname(tty), hh ? "from" : "", hh ? TT.hostname : "");
161
162  // not using xexec(), login calls absolute path from filesystem so must exec()
163  execl(pwd->pw_shell, xmprintf("-%s", pwd->pw_shell), (char *)0);
164  perror_exit("exec shell '%s'", pwd->pw_shell);
165}
166