#define _GNU_SOURCE

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <alloca.h>
#include <errno.h>
#include <termios.h>
#include <sys/utsname.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include "lib.h"
#include "opts_defs.h"

/* most of the functions here (like str_copy, fmt_ulong) 
   have manual pages in libowfat;  http://www.fefe.de/
   see also opts_defs.h for o[Oxxx];  man ngetty */

/* see out_put.c;  man buffer */
#define outs(X) out_puts(X)
#define GO_out "go out!"

static struct utsname uts;
static struct stat st;
static char *tty, *o_opt, o_endchar; 
char **Oo = o;	/* see opts_defs.h */

static void tty_flush() { ioctl(0, TCFLSH, TCIFLUSH); }

/* Format of o[Oprint]: ab:cq:09:AZ:...;  man ngetty
   return -1 if 'ch' does not belongs to the set o[Oprint];  0 success */
static int char_allow(unsigned char ch) {
  unsigned char c0, c1, *x = (unsigned char *)o[Oprint];
 again:
  c0 = x[0]; if (!c0) return -1;
  c1 = x[1]; if (!c1) return -1;
  if (c0 <= ch && ch <= c1) return 0;
  if (x[2] == ':') { x += 3; goto again; }
  return -1;
}

/* print maximal 'len' chars from 's';  see strncpy, snprintf, printf */
static void out_max(char *s, unsigned int len) {
  char c = 0;
  if (s == uts.nodename && !o[Olonghostname]) c = '.';
  while (len && *s && *s != c) {
    out_char(*s); ++s; --len;
  }
}

#define Out_MAX(X) x=X; len=sizeof(X); goto system

/* print string with escapes;  man ngetty;  man issue */ 
static void output_special_char(char *s, int len) {
  char *last = s + len;
  while (s < last) {
    char *x, c;
    if (*s != '\\') { out_char(*s); ++s; continue; }
    if (++s == last) return;

    c = *s++;
    switch (c) {
    case 's': Out_MAX(uts.sysname);
    case 'n': Out_MAX(uts.nodename);
    case 'r': Out_MAX(uts.release);
    case 'v': Out_MAX(uts.version);
    case 'm': Out_MAX(uts.machine);
    case 'o': Out_MAX(uts.domainname);
  system:
    out_max(x,len); break;
    case 'l':
      outs(tty); break;
#ifndef DONT_NEED_ESC_TIME
    case 't':
      x = "H:M:S"; goto do_time;
    case 'd':
      x = (o[Odatestring]) ? "a b d Y" : "Y-m-d";
    do_time:
      fmt_time(x); break;
#endif
#ifndef DONT_NEED_ESC_USERS
    case 'u':
    case 'U':
      {
	char ubuf[8 + 3*sizeof(unsigned long)], *x = ubuf;
	unsigned long usrs = utmp_users();
	x += fmt_ulong(x, usrs);
	if (c=='U') {
	  x += str_copynz(x, " users", 10);
	  if (usrs==1) --x;
	}
	*x = 0;
	outs(ubuf);
      }
      break;
#endif
    default:
      if (s+1 < last) {
	unsigned char uch = (unsigned char)c-'0';
	if (uch < 4) {	/* XXX \0xy is not tested */
	  c = (uch<<6) | ((s[0]-'0')<<3) | (s[1]-'0');
	  s += 2;
	}
      }
      out_char(c);
    }
  }
}

/* print string 's' with escapes;  man ngetty */
static void Out_sp_ch(char *s) { output_special_char(s, str_len(s)); }

/* exception:  print string 's' and exit with status 'i' */
void error(char *s,int i) {
  struct timespec ts = { 2, 500111222 };
  Out_sp_ch("\nngetty-helper: \\d \\t \\l: ");
  outs(s); out_char('\n'); out_flush();
  nanosleep(&ts, 0);
  tty_flush();
  _exit(i);
}

/* print /etc/issue and login prompt;  man issue */
static void do_prompt() {
  int fd,len;
  char *s;
  Out_sp_ch(o[Onewline]); 
  s = o[Oissuefile];
  if (s[0] && (fd=open(s, O_RDONLY)) >= 0) {
    if (GLOBAL_fstat_READ(fd,st,s, len,64000, 1)) _exit(1);
    close(fd);
    output_special_char(s, len);
  }
  Out_sp_ch(o[Ologinprompt]);
  out_flush();
}

/* get login name in logname.  max length = last-logname 
   man ngetty;  options: login-buffer, timeout */
static void get_logname(char *logname, char *last) {
  char *c;
  alarm(x_atoi(o[Otimeout]));
  for (;;) {
    for (c=logname;; c++) {
      if (c == last)
	error("login name too long",11);
      if (read(0,c,1)<1) {
	if (errno==EINTR || errno==EIO || errno==ENOENT)
	  _exit(0);
	error("received strange error",9);
      }
      if (*c == '\n' || *c == '\r') {
	*c=0;
	break;
      }
      if (char_allow((unsigned char)*c))
	error("bad character in login name",10);
    } /* end read */
    if (*logname == '-')
      error("username may not start with a dash",13);
    if (*logname) break;
    do_prompt();
  }
  alarm(0);
}

/* parse string;  store it in o[XXX];  see opts_defs.h;  /etc/ngetty/Conf */
static char *opt_get(char *s, int len) {
  if (*o_opt == *s) {
    char *x = o_opt;
    if (!str_diffn(x,s,len) && x[len] == o_endchar) {
      if (o[Odebug] && s != tty) { 
	outs(x - 1);
	out_char('\n');
      }
      return x+len+1;
    }
  }
  return 0;
}

/* return 1 if user is in s, otherwise 0;
   Format of s: ,ab,uvw,MlqR, ... ,last */
static char in_list(char *s, const char *user) {
  char ch;
  int len;
  if (s==0 || *s==0 || user==0) return 0;
  len = str_len(user);
  ch = *s++;
 again:
  if (!str_diffn(s,user,len))
    if (s[len]==ch || s[len]==0) return 1;
  while (*s && *s != ch) s++;
  if (*s++) goto again;
  return 0;
}

/* set controlling tty */
static void set_ctty() {
  if (setsid() == -1 || ioctl(0, TIOCSCTTY, (void *)1))
    error(GO_out, 111);
}

int main(int argc,char *argv[]) {
  int fd,len;
  char *s;
  struct termios term;
  if (argc < 2) _exit(100);

  o[Oautologinfirst] = o[Onoclearfirst] = o[Oautologinname] = "";
  o[Oissuefile] = "/etc/issue";
  o[Ologinprog] = LOGIN;
  o[Oprint] = "az:AZ:09:,.:__";
  o[Otimeout] = "180";
  o[Ologinprompt] = "\\n login: ";
  o[Onewline] = "\n";
  o[Oclear] = "\033c";
#ifndef DONT_NEED_ESC_TIME
  o[Omonths] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  o[Odays] = "SunMonTueWedThuFriSat";
  o[Otz] = "/etc/localtime";
#endif

  tty = argv[1];
  uname(&uts);
  if (dup(0) !=1 || dup(0) !=2 || ioctl(0,TCGETS,&term)) error(GO_out, 100);
  if (!str_diffn(tty, "/dev/", 5)) tty += 5;

  if ((fd = open(NGETTY_HOME "/Conf", O_RDONLY)) >=0) {
    char **aa=0, *q;
    int tty_len = str_len(tty);
    /* 40 bytes for Months or Days */
    if (GLOBAL_fstat_READ(fd,st,s, len,64000, 40)) _exit(1);
    close(fd);
    if (st.st_uid || st.st_gid || (st.st_mode & 07177)) len = 0;
    s[len]=0;

    GLOBAL_split(aa,s,'\n', len);

    for (; *aa; aa++) {
      s=aa[0];
      if (*s==0 || *s=='#' || *s=='\t' || *s==' ') continue;

      o_endchar = '=';
      if (*s=='=') o_opt = s+1;
      else {
	o_opt = s;
	if (!(o_opt = opt_get(tty, tty_len))) continue;
      }

      for (len=0, q=(char *)P_opt; len < MAX_options; len++) {
	int k = P_len[len];
	if (len >= MAX_equal) o_endchar = 0;
	if ((s = opt_get(q, k))) { o[len] = s; break; }
	q += k;
      }
    }
  }

  if ((s=o[Onice])) nice(x_atoi(s));

  if ((s=o[Oenviron])) {
    char **ee=0, ch=*s++;
    if (ch) GLOBAL_split(ee,s,ch, len);
    environ=ee;
  }

  if (argc>2 && argv[2][0] == 'l') { /* get logname and execve() login */
    char *loginargv[]={ o[Ologinprog], "--", 0, 0, 0 };
    fork_and_exec(o[OshB]);

    s = o[Oautologinfirst];
    if (*s && !check_first(s,&st)) o[Oautologinname] = "";

    s = o[Oautologinname];
    if (*s) {
      loginargv[1] = "-f";
      loginargv[2] = s;
    } else {
      o[Oissuefile] = "";
      if (!o[Onousername]) {
	char *last, *buf = o[Ologinbuffer];
	if (buf) last = buf + str_len(buf);
	else { buf = alloca(41); last = buf + 40; }
	loginargv[2] = buf;
	get_logname(buf, last);
      }
    }

    if (o[Oechooff]) {
      term.c_lflag &= ~(ECHO|ISIG);
      ioctl(0, TCSETS, &term);
    }

    if ((s=o[Ologinargv])) {
      loginargv[1] = s;	/* change '--' or '-f' with login-argv */
      loginargv[3] = tty;
    }

    s = loginargv[2];
    if (in_list(o[Odeny],s) ||
	(o[Oallow] && in_list(o[Oallow],s)==0))
      error("login is disabled",14);

    out_flush();
    tty_flush();
    utmp_do(1,tty);

    if ((s=o[Ochroot])) chroot(s);
    if ((s=o[Ochdir]))  chdir(s);
    set_ctty();
    execve(*loginargv, loginargv, environ);
  } else {	/* print /etc/issue and exit */
    char *clear = o[Oclear];
    tty_flush();

    fork_and_exec(o[OshA]);
    if ((s=o[Odelay])) {
      struct timespec ts = { x_atoi(s), 100200300 };
      nanosleep(&ts, 0);
    }

    s = o[Onoclearfirst];
    if (*s && check_first(s,&st)) clear = "";

    Out_sp_ch(clear);
    utmp_do(0,tty);
    do_prompt();

#ifdef HAVE_SYSTEM_VHANGUP
    if (!o[Onohangup]) { set_ctty(); vhangup(); }
#endif
  }
  _exit(0);
}
