view toys/other/login.c @ 944:b4faf2ae39e8

This inlines CRC64, and nothing more. The functions involved were called only once.
author Isaac Dunham <idunham@lavabit.com>
date Sat, 06 Jul 2013 11:26:15 -0500
parents 0ed3351d91eb
children faf7117c4489
line wrap: on
line source

/* login.c - Start a session on the system.
 *
 * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com>
 *
 * No support for PAM/securetty/selinux/login script/issue/utmp
 * Relies on libcrypt for hash calculation.

USE_LOGIN(NEWTOY(login, ">1fph:", TOYFLAG_BIN))

config LOGIN
  bool "login"
  default y
  help
    usage: login [-p] [-h host] [[-f] username]

    Establish a new session with the system.
    -p	Preserve environment
    -h	The name of the remote host for this login
    -f	Do not perform authentication
*/

#define FOR_login
#include "toys.h"

#define LOGIN_TIMEOUT 60
#define LOGIN_FAIL_TIMEOUT 3
#define USER_NAME_MAX_SIZE 32
#define HOSTNAME_SIZE 32

GLOBALS(
  char *hostname;
)

static void login_timeout_handler(int sig __attribute__((unused)))
{
  printf("\nLogin timed out after %d seconds.\n", LOGIN_TIMEOUT);
  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[0] = c;
  } while (isblank(buff[0]));

  if (buff[0] != '\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(EXIT_FAILURE);
}

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);
}

int change_identity(const struct passwd *pwd)
{
  if (initgroups(pwd->pw_name,pwd->pw_gid)) return 1;
  if (setgid(pwd->pw_uid)) return 1;
  if (setuid(pwd->pw_uid)) return 1;

  return 0;
}

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[USER_NAME_MAX_SIZE+1], *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");

  if (geteuid()) error_exit("not root");

  if (!isatty(0) || !isatty(1) || !isatty(2)) error_exit("no tty");

  openlog("login", LOG_PID | LOG_CONS, LOG_AUTH);
  signal(SIGALRM, login_timeout_handler);
  alarm(LOGIN_TIMEOUT);

  for (ss = forbid; *ss; ss++) unsetenv(*ss);

  while (1) {
    tcflush(0, TCIFLUSH);

    username[USER_NAME_MAX_SIZE] = 0;
    if (toys.optargs[0]) strncpy(username, toys.optargs[0], USER_NAME_MAX_SIZE);
    else {
      read_user(username, USER_NAME_MAX_SIZE+1);
      if (username[0] == 0) continue;
    }

    pwd = getpwnam(username);
    if (!pwd) goto query_pass; // Non-existing user

    if (pwd->pw_passwd[0] == '!' || pwd->pw_passwd[0] == '*')
      goto query_pass;  // Locked account

    if (f_flag) break; // Pre-authenticated

    if (!pwd->pw_passwd[0]) break; // Password-less account

    pass = pwd->pw_passwd;
    if (pwd->pw_passwd[0] == 'x') {
      spwd = getspnam (username);
      if (spwd) pass = spwd->sp_pwdp;
    }

query_pass:
    if (!verify_password(pass)) 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:"");

    sleep(LOGIN_FAIL_TIMEOUT);
    puts("Login incorrect");

    if (++auth_fail_cnt == 3)
      error_exit("Maximum number of tries exceeded (%d)\n", auth_fail_cnt);

    username[0] = 0;
    pwd = NULL;
    spwd = NULL;
  }

  alarm(0);

  if (pwd->pw_uid) handle_nologin();

  if (change_identity(pwd)) error_exit("Failed to change identity");

  setup_environment(pwd, !(toys.optflags & FLAG_p));

  handle_motd();

  syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name,
    ttyname(0), h_flag?"from":"", h_flag?TT.hostname:"");

  spawn_shell(pwd->pw_shell);
}