Long-overdue cleanup on login.
authorRob Landley <rob@landley.net>
Sun, 2 Aug 2015 23:04:17 +0000 (18:04 -0500)
committerRob Landley <rob@landley.net>
Sun, 2 Aug 2015 23:04:17 +0000 (18:04 -0500)
Only tested that it compiled so far.

toys/other/login.c

index b728286..c44a887 100644 (file)
@@ -8,30 +8,28 @@
  * TODO: this command predates "pending" but needs cleanup. It #defines
  * random stuff, calls exit() form a signal handler... yeah.
 
-USE_LOGIN(NEWTOY(login, ">1fph:", TOYFLAG_BIN))
+USE_LOGIN(NEWTOY(login, ">1f:ph:", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 
 config LOGIN
   bool "login"
   default y
   depends on TOYBOX_SHADOW
   help
-    usage: login [-p] [-h host] [[-f] username]
+    usage: login [-p] [-h host] [-f USERNAME] [USERNAME]
 
-    Establish a new session with the system.
+    Log in as a user, prompting for username and password if necessary.
 
     -p Preserve environment
     -h The name of the remote host for this login
-    -f Do not perform authentication
+    -f login as USERNAME without authentication
 */
 
 #define FOR_login
 #include "toys.h"
 
-#define USER_NAME_MAX_SIZE 32
-#define HOSTNAME_SIZE 32
-
 GLOBALS(
   char *hostname;
+  char *username;
 
   int login_timeout, login_fail_timeout;
 )
@@ -42,184 +40,128 @@ static void login_timeout_handler(int sig __attribute__((unused)))
   exit(0);
 }
 
-static char *forbid[] = {
-  "BASH_ENV", "ENV", "HOME", "IFS", "LD_LIBRARY_PATH", "LD_PRELOAD",
-  "LD_TRACE_LOADED_OBJECTS", "LD_BIND_NOW", "LD_AOUT_LIBRARY_PATH",
-  "LD_AOUT_PRELOAD", "LD_NOWARN", "LD_KEEPDIR", "SHELL", NULL
-};
-
-int verify_password(char * pwd)
-{
-  char *pass;
-
-  if (read_password(toybuf, sizeof(toybuf), "Password: ")) return 1;
-  if (!pwd) return 1;
-  if (pwd[0] == '!' || pwd[0] == '*') return 1;
-
-  pass = crypt(toybuf, pwd);
-  if (pass && !strcmp(pass, pwd)) return 0;
-
-  return 1;
-}
-
-void read_user(char * buff, int size)
-{
-  char hostname[HOSTNAME_SIZE+1];
-  int i = 0;
-
-  hostname[HOSTNAME_SIZE] = 0;
-  if (!gethostname(hostname, HOSTNAME_SIZE)) fputs(hostname, stdout);
-
-  fputs(" login: ", stdout);
-  fflush(stdout);
-
-  do {
-    int c = getchar();
-    if (c == EOF) exit(EXIT_FAILURE);
-    *buff = c;
-  } while (isblank(*buff));
-
-  if (*buff != '\n') if(!fgets(&buff[1], HOSTNAME_SIZE-1, stdin)) _exit(1);
-
-  while(i<HOSTNAME_SIZE-1 && isgraph(buff[i])) i++;
-  buff[i] = 0;
-}
-
-void handle_nologin(void)
-{
-  int fd = open("/etc/nologin", O_RDONLY);
-  int size;
-
-  if (fd == -1) return;
-
-  size = readall(fd, toybuf,sizeof(toybuf)-1);
-  toybuf[size] = 0;
-  if (!size) puts("System closed for routine maintenance\n");
-  else puts(toybuf);
-
-  close(fd);
-  fflush(stdout);
-  exit(1);
-}
-
-void handle_motd(void)
-{
-  int fd = open("/etc/motd", O_RDONLY);
-  int size;
-  if (fd == -1) return;
-
-  size = readall(fd, toybuf,sizeof(toybuf)-1);
-  toybuf[size] = 0;
-  puts(toybuf);
-
-  close(fd);
-  fflush(stdout);
-}
-
-void spawn_shell(const char *shell)
-{
-  const char * exec_name = strrchr(shell,'/');
-  if (exec_name) exec_name++;
-  else exec_name = shell;
-
-  snprintf(toybuf,sizeof(toybuf)-1, "-%s", shell);
-  execl(shell, toybuf, NULL);
-  error_exit("Failed to spawn shell");
-}
-
-void setup_environment(const struct passwd *pwd, int clear_env)
-{
-  if (chdir(pwd->pw_dir)) printf("bad home dir: %s\n", pwd->pw_dir);
-
-  if (clear_env) {
-    const char *term = getenv("TERM");
-    clearenv();
-    if (term) setenv("TERM", term, 1);
-  }
-
-  setenv("USER", pwd->pw_name, 1);
-  setenv("LOGNAME", pwd->pw_name, 1);
-  setenv("HOME", pwd->pw_dir, 1);
-  setenv("SHELL", pwd->pw_shell, 1);
-}
-
 void login_main(void)
 {
-  int f_flag = toys.optflags & FLAG_f;
-  int h_flag = toys.optflags & FLAG_h;
-  char username[33], *pass = NULL, **ss;
-  struct passwd * pwd = NULL;
-  struct spwd * spwd = NULL;
-  int auth_fail_cnt = 0;
-
-  if (f_flag && toys.optc != 1) error_exit("-f requires username");
+  char *forbid[] = {
+    "BASH_ENV", "ENV", "HOME", "IFS", "LD_LIBRARY_PATH", "LD_PRELOAD",
+    "LD_TRACE_LOADED_OBJECTS", "LD_BIND_NOW", "LD_AOUT_LIBRARY_PATH",
+    "LD_AOUT_PRELOAD", "LD_NOWARN", "LD_KEEPDIR", "SHELL"
+  };
+  int hh = toys.optflags&FLAG_h, count, tty;
+  char uu[33], *username, *pass = 0, *ss;
+  struct passwd *pwd = 0;
 
-  if (geteuid()) error_exit("not root");
+  for (tty=0; tty<3; tty++) if (isatty(tty)) break;
+  if (tty == 3) error_exit("no tty");
 
-  if (!isatty(0) || !isatty(1) || !isatty(2)) error_exit("no tty");
+  for (count = 0; count < ARRAY_LEN(forbid); count++) unsetenv(forbid[count]);
 
   openlog("login", LOG_PID | LOG_CONS, LOG_AUTH);
   xsignal(SIGALRM, login_timeout_handler);
-  alarm(TT.login_timeout = 60);
 
-  for (ss = forbid; *ss; ss++) unsetenv(*ss);
-
-  while (1) {
+  if (TT.username) username = TT.username;
+  else username = *toys.optargs;
+  for (count = 0; count < 3; count++) {
+    alarm(TT.login_timeout = 60);
     tcflush(0, TCIFLUSH);
 
-    username[sizeof(username)-1] = 0;
-    if (*toys.optargs) xstrncpy(username, *toys.optargs, sizeof(username));
-    else {
-      read_user(username, sizeof(username));
-      if (!*username) continue;
+    if (!username) {
+      int i;
+
+      memset(username = uu, 0, sizeof(uu));
+      gethostname(uu, sizeof(uu)-1);
+      printf("%s%slogin: ", *uu ? uu : "", *uu ? " " : "");
+      fflush(stdout);
+
+      if(!fgets(uu, sizeof(uu)-1, stdin)) _exit(1);
+
+      // Remove trailing \n and so on
+      for (i = 0; i<sizeof(uu); i++) if (uu[i]<=' ' || uu[i]==':') uu[i]=0;
+      if (!*uu) {
+        username = 0;
+        continue;
+      }
     }
 
+    // If user exists and isn't locked
     pwd = getpwnam(username);
-    if (!pwd) goto query_pass; // Non-existing user
+    if (pwd && *pwd->pw_passwd != '!' && *pwd->pw_passwd != '*') {
 
-    if (pwd->pw_passwd[0] == '!' || pwd->pw_passwd[0] == '*')
-      goto query_pass;  // Locked account
+      // Pre-authenticated or passwordless
+      if (TT.username || !*pwd->pw_passwd) break;
 
-    if (f_flag) break; // Pre-authenticated
+      // fetch shadow password if necessary
+      if (*(pass = pwd->pw_passwd) == 'x') {
+        struct spwd *spwd = getspnam (username);
 
-    if (!pwd->pw_passwd[0]) break; // Password-less account
+        if (spwd) pass = spwd->sp_pwdp;
+      }
+    } else if (TT.username) error_exit("bad -f '%s'", TT.username);
 
-    pass = pwd->pw_passwd;
-    if (pwd->pw_passwd[0] == 'x') {
-      spwd = getspnam (username);
-      if (spwd) pass = spwd->sp_pwdp;
-    }
+    // Verify password. (Prompt for password _before_ checking disable state.)
+    if (!read_password(toybuf, sizeof(toybuf), "Password: ")) {
+      int x = pass && (ss = crypt(toybuf, pass)) && !strcmp(pass, ss);
 
-query_pass:
-    if (!verify_password(pass)) break;
+      // password go bye-bye now.
+      memset(toybuf, 0, sizeof(toybuf));
+      if (x) break;
+    }
 
-    f_flag = 0;
-    syslog(LOG_WARNING, "invalid password for '%s' on %s %s %s", username,
-      ttyname(0), h_flag?"from":"", h_flag?TT.hostname:"");
+    syslog(LOG_WARNING, "invalid password for '%s' on %s %s%s", pwd->pw_name,
+      ttyname(tty), hh ? "from " : "", hh ? TT.hostname : "");
 
     sleep(3);
     puts("Login incorrect");
 
-    if (++auth_fail_cnt == 3)
-      error_exit("Maximum number of tries exceeded (3)\n");
-
-    *username = 0;
+    username = 0;
     pwd = 0;
-    spwd = 0;
   }
 
   alarm(0);
+  // This had password data in it, and we reuse for motd below
+  memset(toybuf, 0, sizeof(toybuf));
+
+  if (!pwd) error_exit("max retries (3)");
 
-  if (pwd->pw_uid) handle_nologin();
+  // Check twice because "this file exists" is a security test, and in
+  // theory filehandle exhaustion or other error could make open/read fail.
+  if (pwd->pw_uid && !access("/etc/nologin", R_OK)) {
+    ss = readfile("/etc/nologin", toybuf, sizeof(toybuf));
+    puts ((ss && *ss) ? ss : "nologin");
+    free(ss);
+    toys.exitval = 1;
+
+    return;
+  }
 
   xsetuser(pwd);
 
-  setup_environment(pwd, !(toys.optflags & FLAG_p));
+  if (chdir(pwd->pw_dir)) printf("bad $HOME: %s\n", pwd->pw_dir);
+
+  if (!(toys.optflags&FLAG_p)) {
+    char *term = getenv("TERM");
+
+    clearenv();
+    if (term) setenv("TERM", term, 1);
+  }
+
+  setenv("USER", pwd->pw_name, 1);
+  setenv("LOGNAME", pwd->pw_name, 1);
+  setenv("HOME", pwd->pw_dir, 1);
+  setenv("SHELL", pwd->pw_shell, 1);
 
-  handle_motd();
+  // Message of the day
+  if ((ss = readfile("/etc/motd", 0, 0))) {
+    puts(ss);
+    free(ss);
+  }
 
   syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name,
-    ttyname(0), h_flag?"from":"", h_flag?TT.hostname:"");
+    ttyname(tty), hh ? "from" : "", hh ? TT.hostname : "");
 
-  spawn_shell(pwd->pw_shell);
+  // can't xexec here because name doesn't match argv[0]
+  snprintf(toybuf, sizeof(toybuf)-1, "-%s", ss = basename_r(pwd->pw_shell));
+  toy_exec((char *[]){toybuf, 0});
+  execl(ss, toybuf, NULL);
+  error_exit("Failed to spawn shell");
 }