master xplshn/aruu / cmd / xsi / login.c
  1/* see LICENSE file for copyright and license details */
  2
  3#include "config.h"
  4#include "passwd.h"
  5#include "util.h"
  6#include "wexec.h"
  7
  8#include <grp.h>
  9#include <limits.h>
 10#include <pwd.h>
 11#include <stdio.h>
 12#include <stdlib.h>
 13#include <string.h>
 14#include <termios.h>
 15#include <time.h>
 16#include <unistd.h>
 17#include <utmp.h>
 18
 19static void
 20usage(void)
 21{
 22  eprintf("usage: %s [-p name] [-f name]\n", argv0);
 23}
 24
 25/* reads one line of input, echoing it or not, into a reusable static
 26 * buffer. returns null on eof so a hung-up line doesnt loop forever */
 27static char *
 28readline(const char *prompt, int echo)
 29{
 30  static char    buf[LOGIN_NAME_MAX + 1];
 31  struct termios old, raw;
 32  int            have_old = 0;
 33  size_t         n;
 34
 35  fputs(prompt, stdout);
 36  fflush(stdout);
 37
 38  if (!echo && tcgetattr(STDIN_FILENO, &old) == 0) {
 39    have_old = 1;
 40    raw      = old;
 41    raw.c_lflag &= ~ECHO;
 42    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
 43  }
 44
 45  if (!fgets(buf, sizeof(buf), stdin)) {
 46    if (have_old)
 47      tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
 48    return NULL;
 49  }
 50
 51  if (have_old) {
 52    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
 53    fputc('\n', stdout);
 54  }
 55
 56  n = strlen(buf);
 57  if (n && buf[n - 1] == '\n')
 58    buf[--n] = '\0';
 59
 60  return buf;
 61}
 62
 63/* best effort utmp record for the controlling tty: getty already
 64 * cleared any stale entry for this line before exec-ing us. only
 65 * ut_line, ut_name (musl aliases this to ut_user), and ut_time
 66 * (aliased to ut_tv.tv_sec) exist on both linux and bsd utmp
 67 * layouts; ut_type/ut_pid do not exist at all on bsd, so those two
 68 * fields are the one genuinely platform-specific part of this record */
 69static void
 70write_utmp(const char *user)
 71{
 72  struct utmp usr;
 73  char       *line;
 74  FILE       *fp;
 75
 76  memset(&usr, 0, sizeof(usr));
 77#if defined(__linux__)
 78  usr.ut_type = USER_PROCESS;
 79  usr.ut_pid  = getpid();
 80#endif
 81  line = ttyname(STDIN_FILENO);
 82  if (line) {
 83    if (!strncmp(line, "/dev/", 5))
 84      line += 5;
 85    strlcpy(usr.ut_line, line, sizeof(usr.ut_line));
 86  }
 87  strlcpy(usr.ut_name, user, sizeof(usr.ut_name));
 88  usr.ut_time = time(NULL);
 89
 90  /* utmp: a live table, so this overwrites/appends the one entry for
 91 * this session */
 92  fp = fopen(UTMP_PATH, "r+");
 93  if (fp) {
 94    fseek(fp, 0, SEEK_END);
 95    fwrite(&usr, sizeof(usr), 1, fp);
 96    fclose(fp);
 97  }
 98
 99  /* wtmp: an ever-growing append-only log of the same record, same
100 * portable field subset, just opened differently */
101  fp = fopen(WTMP_PATH, "a");
102  if (fp) {
103    fwrite(&usr, sizeof(usr), 1, fp);
104    fclose(fp);
105  }
106}
107
108// ?man login: authenticate a user and start a session
109// ?man arguments: [-p name] [-f name]
110// ?man prompt for a username (unless given) and password, verify against
111// ?man the system password database, then exec the user's login shell
112// ?man invoked by getty with -p name once a username has already been
113// ?man typed at the "login:" prompt
114int
115main(int argc, char *argv[])
116{
117  struct passwd *pw;
118  char           namebuf[LOGIN_NAME_MAX + 1];
119  char           shellbuf[PATH_MAX];
120  char          *preset = NULL, *trusted = NULL;
121  char          *name, *pass, *line, *base;
122  char          *shargv[2];
123  int            tries;
124
125  ARGBEGIN
126  {
127    // ?man -p:name preset the username; still prompts for a password
128    case 'p':
129      preset = EARGF(usage());
130      break;
131    // ?man -f:name log in as name without a password prompt: the
132    // ?man caller has already authenticated this user out-of-band
133    case 'f':
134      trusted = EARGF(usage());
135      break;
136    default:
137      usage();
138  }
139  ARGEND
140
141  pw_init();
142
143  if (trusted) {
144    pw = getpwnam(trusted);
145    if (!pw)
146      eprintf("login: unknown user: %s\n", trusted);
147  } else {
148    pw = NULL;
149    for (tries = 0; tries < 3 && !pw; tries++) {
150      if (preset && tries == 0) {
151        strlcpy(namebuf, preset, sizeof(namebuf));
152      } else {
153        line = readline("login: ", 1);
154        if (!line)
155          exit(1);
156        strlcpy(namebuf, line, sizeof(namebuf));
157      }
158      name = namebuf;
159
160      pass = readline("Password: ", 0);
161      if (!pass)
162        pass = "";
163
164      pw = getpwnam(name);
165      if (!pw || pw_check(pw, pass) != 1) {
166        fputs("Login incorrect\n", stdout);
167        pw = NULL;
168      }
169    }
170    if (!pw)
171      exit(1);
172  }
173
174  if (initgroups(pw->pw_name, pw->pw_gid) < 0)
175    weprintf("initgroups:");
176  if (setgid(pw->pw_gid) < 0)
177    eprintf("setgid:");
178  if (setuid(pw->pw_uid) < 0)
179    eprintf("setuid:");
180
181  write_utmp(pw->pw_name);
182
183  if (chdir(pw->pw_dir) < 0)
184    weprintf("chdir %s:", pw->pw_dir);
185
186  setenv("HOME", pw->pw_dir, 1);
187  setenv("USER", pw->pw_name, 1);
188  setenv("LOGNAME", pw->pw_name, 1);
189  strlcpy(shellbuf, pw->pw_shell[0] ? pw->pw_shell : "/bin/sh", sizeof(shellbuf));
190  setenv("SHELL", shellbuf, 1);
191
192  base = strrchr(shellbuf, '/');
193  base = base ? base + 1 : shellbuf;
194
195  shargv[0]    = emalloc(strlen(base) + 2);
196  shargv[0][0] = '-';
197  strcpy(shargv[0] + 1, base);
198  shargv[1] = NULL;
199
200  wexecv_self(shellbuf, shargv);
201  return 1;
202}