#include "bbs.h"

volatile SIG_ATOMIC_T signumber = -1;
volatile SIG_ATOMIC_T canjump_sigalrm = (int)FALSE; 
volatile SIG_ATOMIC_T canjump_sigenv = (int)FALSE;
volatile SIG_ATOMIC_T canjump_talkaccept = (int)FALSE;
volatile SIG_ATOMIC_T canjump_sigusr1 = (int)FALSE; 
volatile SIG_ATOMIC_T canjump_sigusr2 = (int)FALSE;
SIGSET_T old_blocksigset;
sigmaskstatusenum blocksigsetstatus = SIGMASK_UNSET;
sigjmp_buf sigenv, sigalrmenv, talkacceptenv, sigusr1env, sigusr2env;
const char *protokollnamen[] = {"Kermit","Z-Modem"};
const char *sprachennamen[] = {"Deutsch","English"};
const char *sprachensuffix[] = {"ger","eng"};
boolean bbsdaemon = FALSE;


int main(int argc, char *argv[], char *environ[])
  /*
  Die Clients (bbs)
  */
{
  PID_T pid;
  int cmdnr, seclevel;
  char cmd[PATH_MAX+1], params[PATH_MAX+1], cmdline[PATH_MAX+1],
       chrootcwd[PATH_MAX+1], conffile[PATH_MAX+1], c;
  confrecordtyp confrecord;
  SIGSET_T sigset;
  boolean argserror=FALSE;
  extern int optind, opterr, optopt;
  extern char *optarg;
  menuetyp startmenue[] = {
    {HELP_KEY,{"Kurzhilfe anzeigen","show Help"},"help"},
    {DOC_KEY,{"ausfuehrliche Anleitung anzeigen","show Manual"},"anl"},
    {SYS_KEY,{"Systemparameter aendern","system configuration"},"sys"},
    {' ',{"",""},""},
    {DIR_KEY,{"aktuelles Directory auflisten","list directory"},"dir"},
    {CD_KEY,{"aktuelles Directory wechseln","change directory"},"cd"},
    {PWD_KEY,{"Namen vom aktuellen Directory anzeigen","directoryname"},"pwd"},
    {SHOW_KEY,{"Dateiinhalt anzeigen","show file"},"show"},
    {' ',{"",""},""},
    {DOWNLOAD_KEY,{"Dateien downladen","download files"},"get"},
    {UPLOAD_KEY,{"Dateien upladen","upload files"},"put"},
    {' ',{"",""},""},
    {BBS_KEY,{"BBS-System","BBS-System"},"bbssys"},
    {' ',{"",""},""},
    {QUIT_KEY,{"Programm beenden und Verbindung abbauen","exit program"},
        "quit"},
    {'\0',{"",""},""}
  };
  menuetyp sysmenue[] = {
    {ENV_KEY,{"momentane Parameter abfragen","show current settings"},"env"},
    {LANG_KEY,{"Sprachwahl","Language"},"lang"},
    {PROT_KEY,{"Datenuebertragungsprotokoll","change transmittion protocol"},
        "pr"},
#ifndef NO_TV_USEC
    {AUTOZ_KEY,{"Z-Modem Uploadautomatik","automatic Z-Modem uploads"},"azm"},
#endif
    {FULLIST_KEY,{"Dateien bei Directorywechsel listen",
                  "list files on change directory"},"fullist"},
    {TERM_KEY,{"Terminaltyp einstellen","change terminaltype"},"te"},
    {PASSWD_KEY,{"neues Password","new password"},"npass"},
    {' ',{"",""},""},
    {QUIT_KEY,{"Zurueck zum Hauptmenue","back to Mainmenue"},""},
    {'\0',{"",""},""}
  };
  menuetyp bbsmenue[] = {
    {TALK_KEY,{"Dialog mit anderem User","talking to another user"},"talk"},
    {' ',{"",""},""},
    {QUIT_KEY,{"Zurueck zum Hauptmenue","back to Mainmenue"},""},
    {'\0',{"",""},""}
  };

  /* Environment loeschen und einige Variable vorbesetzen */
  environ[0] = (char *)NULL;
  mputenv("EDITOR=/bin/false");
  openlog("bbs",LOG_PID,LOG_BBS);
  seclevel = -1;
  confrecord.curses_on = FALSE;
  strcpy(conffile,CONFFILE);
  confrecord.userrecord.seclevel = -1;
  confrecord.userrecord.name[0] = '\0';
  /* Optionen verarbeiten */
  opterr = FALSE;
  while ((c = getopt(argc, argv, "c:l:s:")) != EOF) {
    switch (c) {
    case 's':
      seclevel = atoi(optarg);
      break;
    case 'l':
      strmaxcpy(confrecord.userrecord.name,optarg,S_STRLEN);
      break;
    case 'c':
      strmaxcpy(conffile,optarg,PATH_MAX);
      break;
    case '?':
      fprintf(stderr,"bbs: unrecognized option: -%c\n", optopt);
      argserror = TRUE;
    }
  }
  if (argserror || (optind < argc)) {
    fprintf(stderr,"usage: bbs [-s seclevel] [-l username] [-c conffile]\n");
    exit(-1);
  }
  
  /* sigusr1 und sigusr2 blockieren */
  sigemptyset(&sigset);
  sigaddset(&sigset,SIGUSR1);
  sigaddset(&sigset,SIGUSR2);
  sigprocmask(SIG_SETMASK,&sigset,NULL);
  
  /* Einsprungpunkt fuer einige Signale (ausser sigalrm) */
  signumber = -1;
  if (sigsetjmp(sigenv,(int)FALSE)==0) {
    canjump_sigenv = TRUE;
    setsighandler(SIGINT, SIG_IGN);
    setsighandler(SIGTERM, sighandler);
    setsighandler(SIGHUP, sighandler);
  }
  else {
    switch(signumber) {
    case -1:
      errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
               msg("bbs",0,confrecord.lang));
      break;
    case SIGTERM:
      errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
               msg("bbs",1,confrecord.lang));
      break;
    case SIGHUP:
      errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
               msg("bbs",2,confrecord.lang));
      break;
    default:
      errormsg(E_LOGFILE|E_USER, &confrecord,"bbs","bbs",
               msg("bbs",3,confrecord.lang));
    }
    bbsexit(0,&confrecord);
  }

  /* Sitzung initialisieren */
  if ((pid=bbsinit(chrootcwd, &confrecord, conffile)) < 0) {
    if (pid < -1)  removesession(-pid,&confrecord);
    exit(-1);
  }
  /* ab hier laeuft der Daemon bereits */

  /* Menue (curses) initialisieren */
  if (window_on(&confrecord) < 0)  exit(-1);

  if (seclevel >= 0)  confrecord.userrecord.seclevel = seclevel;
#ifdef NO_TV_USEC
  confrecord.userrecord.autozmodem = FALSE;
#endif

#ifdef AUTOLISTFILE
  if (checkfileperms(AUTOLISTFILE,geteuid(),getegid()) == 0) {
    do_showfile(AUTOLISTFILE,chrootcwd,&confrecord);
  }
#endif

  /* Befehle vom User einlesen und abarbeiten */
  cmdline[0] = '\0';
  cmdnr = 1;
  do {
    if (sigsetjmp(talkacceptenv,(int)FALSE)==0) {
      canjump_talkaccept = TRUE;
    }
    else {
      alarm(0);
      remconnecttouser(&confrecord);
    }
#ifndef DEBUG
    /* Timer fuer Befehlseingabe-Timeout */
    setsighandler(SIGALRM, sighandler);
    alarm(confrecord.idletimeout);
#endif
    if (sigsetjmp(sigalrmenv,(int)FALSE)==0) {
      canjump_sigalrm = TRUE;
      /* Menue ausgeben und Kommando einlesen */
      if (getcmdline_c(cmdline,startmenue,sysmenue,bbsmenue,chrootcwd,
		       &confrecord) < 0) {
	cmdline[0] = '\0';
      }
      /* Befehlseingabe-Timeout abstellen */
      alarm(0);
      setsighandler(SIGALRM, SIG_IGN);
      canjump_sigalrm = FALSE;
      /* Eingabe loggen */
      if (cmd[0] != '\0' || confrecord.userrecord.loglevel > 1) {
        bbslog(LOG_FILE,&confrecord,cmdline);
      }
    }
    else {
      /* bei Befehlseingabe-Timeout: User benachrichtigen und rausschmeissen */
      errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
               msg("bbs",6,confrecord.lang));
      bbsexit(0,&confrecord);
    }
    cmdnr++;
    /* Kommando in Kleischreibung */
    str2cmdparams(cmdline,cmd,params);
    lowercases(cmd);
    sprintf(cmdline,"%s %s",cmd,params);
/*    fprintf(stderr,"%s:%s\n",cmd,params); */
    /* Kommandos ausfuehren */
    if (cmd[0] == '\0') {
      /* */
    }
    else if (strncmp(cmd,"help",4)==0 || strncmp(cmd,"?",1)==0) {
      if (buildhelppath(params,"bbshelp",sprachensuffix[confrecord.userrecord.lang],&confrecord) != NULL) {
	do_showfile(params,NULL,&confrecord);
      }
      else {
	errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
		 "path to bbshelp too long");
      }
    }
    else if (strncmp(cmd,"anl",3)==0) {
      if (buildhelppath(params,"manual",sprachensuffix[confrecord.userrecord.lang],&confrecord) != NULL) {
	do_showfile(params,NULL,&confrecord);
      }
      else {
	errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
		 "path to manual too long");
      }
    }
    else if (strncmp(cmd,"less",4)==0 || strncmp(cmd,"more",4)==0 ||
        strncmp(cmd,"show",4)==0) {
      do_showfile(params,chrootcwd,&confrecord);
    }
    else if (strncmp(cmd,"dir",3)==0) {
      do_ls(params,chrootcwd,&confrecord);
    }
    else if (strncmp(cmd,"cd",2)==0) {
      do_cd(params,chrootcwd,&confrecord);
    }
    else if (strncmp(cmd,"pwd",3)==0) {
      do_pwd(chrootcwd,&confrecord);
    }
    else if (strncmp(cmd,"env",3)==0) {
      do_showenv(&confrecord.userrecord,&confrecord);
    }
    else if (strncmp(cmd,"pr",2)==0) {
      do_protokoll(params,protokollnamen,&confrecord.userrecord.protokoll,
                   &confrecord);
    }
    else if (strncmp(cmd,"ho",2)==0) {
      do_home(params,chrootcwd,&confrecord.userrecord,&confrecord);
    }
    else if (strncmp(cmd,"te",2)==0) {
      do_term(params,&confrecord.userrecord,&confrecord);
    }
    else if (strncmp(cmd,"get",3)==0) {
      do_get(params,chrootcwd,protokollnamen,confrecord.userrecord.protokoll,
             &confrecord);
    }
    else if (strncmp(cmd,"put",3)==0) {
      do_put(params,chrootcwd,confrecord.userrecord.protokoll,&confrecord);
    }
    else if (strncmp(cmd,"npass",4)==0) {
      do_newpasswd(params,&confrecord.userrecord,&confrecord);
    }
#ifndef NO_TV_USEC
    else if (strncmp(cmd,"azm",4)==0) {
      do_autozmodem(params,&confrecord);
    }
#endif
    else if (strncmp(cmd,"fullist",7)==0) {
      do_fullist(params,&confrecord);
    }
    else if (strncmp(cmd,"lang",4)==0) {
      do_sprache(params,sprachennamen,&confrecord);
    }
    else if (strncmp(cmd,"talk",4)==0) {
      do_talk(params,&confrecord);
    }
    else if (strcmp(cmd,"quit")==0 || strcmp(cmd,"exit")==0 ||
             strcmp(cmd,"ende")==0 || strcmp(cmd,"bye")==0) {
    }
    else {
      errormsg(E_LOGFILE|E_USER,&confrecord,"bbs","bbs",
               msg("bbs",7,confrecord.lang),cmd,params);
    }
  } while (strcmp(cmd,"quit")!=0 && strcmp(cmd,"exit")!=0 &&
           strcmp(cmd,"ende")!=0 && strcmp(cmd,"bye")!=0);

  /* Userenvironment speichern */
  if (confrecord.userrecord.seclevel != HIGHSECURITY) {
    saveuserrecord(&confrecord.userrecord,&confrecord);
  }
  /* User verabschieden und Sitzung beenden */
  printf("Bye\n");
  bbsexit(0,&confrecord);
  return(0);
}


PID_T bbsinit(char chrootcwd[], confrecordtyp *confrecord,
              const char *conffile)
  /*
  Initialisiert die Sitzung: Daemon eventuell starten, User einloggen
  und sein Environment laden
  Rueckgabewerte: ok:  pid vom Programm, wenn es beim Daemon angemeldet wurde
                       und ein User sich erfolgreich einlogte
              Fehler: -pid vom Programm, wenn es beim Daemon angemeldet wurde
	              -1, sonst
  */
{
  PID_T dpid;
  char *ptr, str[PATH_MAX+1];
  boolean isuser;
  int status;
  sessionrecordtyp sr;

  /* Konfigurationsfile einlesen */
  if (readconffile(confrecord,conffile) < 0) {
    errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","readconffile failed");
    return(-1);
  }

  /* Daemon starten (es darf nur einen geben) und anmelden */
  if ((dpid=testdaemon(confrecord->bbsdpidpath,confrecord)) < 0) {
    unlink(confrecord->sockpath);
  }
  /* dpid enthaelt die dpid des Daemons, wenn > 0 */
  if (dpid <= 0) {
    if ((dpid=fork())<0) {
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","fork: %m");
      return(-1);
    }
    if (dpid==0) {
      execl(confrecord->bbsdpath,"bbsd","-d","-c",conffile,
            "-p",confrecord->bbsdpath,NULL);
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","execl %s: %m",
               confrecord->bbsdpath);
      exit(-1);
    }
    while (wait(&status)!=dpid);
    if (!WIFEXITED(status)) {
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","starting bbsd failed");
      return(-1);
    }
    if (WIFEXITED(status) && WEXITSTATUS(status)!=0) {
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","bbsd exited with %i",
      WEXITSTATUS(status));
      return(-1);
    }
  }
  /* Homedir von bbs aufsuchen */
#ifdef PGHOME
  mdefenv("HOME",PGHOME);
#else
  mdefenv("HOME",confrecord->rootdir);
#endif
  if ((ptr=getenv("HOME"))==NULL) {
    errormsg(E_LOGFILE|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit",
             "cannot find homedir in environment");
    return(-1);
  }
  /* cwd bezueglich chroot auf ROOT setzen */
  if (chdir(ptr)<0) {
    errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","chdir to %s: %m",ptr);
    return(-1);
  }
  if (getchrootcwd(chrootcwd,confrecord->rootdir) == (char *)0) {
    errormsg(E_LOGFILE|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit",
             "cannot set chroot-homedir (chroot: %s, home: %s)",
	     confrecord->rootdir,ptr);
    return(-1);
  }

  isuser = FALSE;
  /* User einloggen */
  if (sigsetjmp(sigalrmenv,(int)FALSE)==0) {
#ifndef DEBUG
    canjump_sigalrm = TRUE;
    setsighandler(SIGALRM, sighandler);
    alarm(confrecord->logintimeout);
#endif
    isuser = userlogin(confrecord,confrecord->userrecord.name);
  }
  else {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","bbsinit","Timeout (login)");
  }
  alarm(0);
  setsighandler(SIGALRM, SIG_IGN);
  canjump_sigalrm = FALSE;
  if (isuser) {
    bbslog(LOG_FILE,confrecord,"Login %s (Level %i) OK",
           confrecord->userrecord.name, confrecord->userrecord.seclevel);
    printf("Login %s (Level %i) OK\n\n", confrecord->userrecord.name,
           confrecord->userrecord.seclevel);
  }
  else {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","bbsinit",
             msg("bbsinit",0,confrecord->lang));
    return(-1);
  }
    /* beim Daemon anmelden */
  sr.pid = getpid();
  if (isatty(0) && (ptr=ttyname(0))!=NULL)  strcpy(sr.tty,ptr);
  strcpy(sr.user,confrecord->userrecord.name);
  sr.talking_to = 0;
  if (addsession(&sr,confrecord)!=0) {
    errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit","cannot send sessionrecord to daemon");
    return(-sr.pid);
  }

  strcpy(str,confrecord->userrecord.home);
  chrootpath(str,chrootcwd,confrecord->rootdir);
  mdefenv("HOME",str);
  if (chdir(str)<0) {
    errormsg(E_LOGFILE|E_USER|E_CONSOLE,confrecord,"bbs","bbsinit",
             "chdir to %s: %m",str);
    return(-sr.pid);
  }
  getchrootcwd(chrootcwd,confrecord->rootdir);
  mdefenv("SHELL",confrecord->userrecord.shell);
  mdefenv("PATH",confrecord->userrecord.path);
  mdefenv("TERM",confrecord->userrecord.term);
  mdefenv("LINES","%i",confrecord->userrecord.lines);
  mdefenv("COLUMNS","%i",confrecord->userrecord.columns);
  
  /* User begruessen (ohne curses, falls Terminaltyp nicht stimmt) */
  if (sigsetjmp(sigalrmenv,(int)FALSE)==0) {
#ifndef DEBUG
    canjump_sigalrm = TRUE;
    setsighandler(SIGALRM, sighandler);
    alarm(confrecord->idletimeout);
#endif
    status = usergreeting(confrecord);
  }
  else {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","bbsinit","Timeout (term)");
    status = -2;
  }
  alarm(0);
  setsighandler(SIGALRM, SIG_IGN);
  canjump_sigalrm = FALSE;
  if (status < 0) return(-sr.pid);
  
  return sr.pid;
}


PID_T testdaemon(const char *bbsdpidpath, confrecordtyp *confrecord)
  /*
  Prueft die Existenz eines Daemons
  Rueckgabewerte: 0, wenn kein Pidfile vom Daemon existiert
                 -1, wenn Pidfile existiert, aber nicht lesbar ist
		 -2, wenn Pidfile existiert, Muell drinsteht
		 -3, wenn Pidfile existiert, aber kill -1 liefert
		  pid des Daemons sonst
  */
{
  PID_T pid;
  int fd;
  SSIZE_T ss;
  char str[S_STRLEN+1];
  
  if (access(bbsdpidpath,F_OK) == 0) {
    if ((fd=open(bbsdpidpath,O_RDONLY)) < 0) {
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","testdaemon",
               "open %s: %m",bbsdpidpath);
      return(-1);
    }
    if ((ss=readn(fd,(void *)str,(SIZE_T)S_STRLEN)) < 0) {
      errormsg(E_SYSLOG|E_USER|E_CONSOLE,confrecord,"bbs","testdaemon",
               "read %s: %m",bbsdpidpath);
      return(-1);
    }
    close(fd);
    if (ss == (SSIZE_T)S_STRLEN) {
      return(-2);
    }
    pid = (PID_T)atoi(str);
    if (kill(pid,0) < 0)  {
      if (errno != EPERM) {
        pid = -3;
      }
    }
  }
  else {
    pid = 0;
  }
  return pid;
}


void bbsexit(const int k, confrecordtyp *confrecord)
{
  removesession(getpid(),confrecord);
  closelog();
  window_off(confrecord);
  exit(k);
}
