/* $Id: main.c,v 1.20 2004/03/14 13:15:31 thivillon Exp $ */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include <errno.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <zlib.h>
#include <utmp.h>
#include <time.h>

#include "../config.h"

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif 

#ifdef HAVE_PTY_H
#include <pty.h>
#endif

#ifdef HAVE_LIBUTIL_H
#include <libutil.h>
#endif

#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif

#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif

#ifdef HAVE_GRP_H
#include <grp.h>
#endif

#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#ifdef HAVE_UTIL_H
#include <util.h>
#endif

#ifndef INADDR_NONE
#define       INADDR_NONE             0xffffffff
#endif

#ifndef HAVE_OPENPTY
extern int openpty(int *, int *, char *, struct termios *, struct winsize *);
#endif

#ifdef WITH_LIBWRAP
#include <tcpd.h>
#define TCPD_DAEMON_NAME "ssltunnel"
#endif

#include "ssltun.h"
#include "session.h"

extern void initsetproctitle(int argc, char **argv, char **envp);

#ifndef HAVE_SETPROCTITLE
extern void setproctitle(char *fmt, ...);
#endif

extern DH *ssl_callback_TmpDH(SSL *s, int is_export, int keylength);

int init_socket( int port );
void sig_chld_master (int status);
void sig_chld_slave (int status);
void sig_alrm_slave (int status);
void sig_int_slave (int status);
void sig_int_master (int status);

SSL *tcp_accept_fork ( );
int do_server_loop ( SSL *ssl );
int berr_exit(char *string);
SSL_CTX *initialize_ctx(char *calist, char *certfile, char *keyfile);
void destroy_ctx(SSL_CTX *ctx);
RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength);
void sockerror(char *string) ;
char *clientname;
ClientData *getclientdata(char *subject);
key_value_t *file_parse ( char *line , bool *err);
int read_config_file (char *filename);
static void x509_fingerprint (char *s, int l, X509 * cert);
void  kill_then_wait_childs();
static int ssl_accept_timeout(SSL *ssl, int tmo);
static int ssl_write_timeout(SSL *ssl, char *buffer, int size, int tmo);
static int ssl_read_timeout(SSL *ssl, char *buffer, int size, int tmo);

#ifndef HAVE_OPENPTY
int openpty(int *amaster, int *aslave, char *name, 
       struct termios *termp, struct winsize *winp);
#endif


int socklisten[16];

int nbchild=0;
pid_t *child_ids;
int verbose=1;
unsigned long inbytes=0;
unsigned long outbytes=0;
BIO *bio_err=0;
SSL_CTX *ctx;
pid_t pppid = -1;
extern int wtmpfd;
int stopped = 0;

#ifdef WITH_LIBWRAP
/* We need these definitions, since libwrap declares these as extern */
int     allow_severity = LOG_DAEMON; 
int     deny_severity = LOG_WARNING; 
#endif

tunnel *cfgtunnel;

int main(int argc, char **argv, char **envp) {

  SSL *ssl;
  char configfilename[256];
  char pidstring[32];
  int dev_null;
  int index;
  int fpid;
  struct stat lockdirs;

  if (argc > 2) {
    fprintf(stderr,"%s [configfile]\n",argv[0]);
    exit(2);
  }

  cfgtunnel = malloc (sizeof (tunnel));
  cfgtunnel->keyfile = KEYFILE;
  cfgtunnel->certfile = CERTFILE;
  cfgtunnel->cacertfile = CAFILE;
  cfgtunnel->userfile = USERFILE;
  cfgtunnel->maxclients = MAXCHILD;
  cfgtunnel->network_timeout = DEFAULT_NETWORK_TIMEOUT;
  cfgtunnel->listenaddr = NULL;
  cfgtunnel->wtmp = WTMPFILE ;
  cfgtunnel->pidfile = DEFAULT_PIDFILE;
  cfgtunnel->port = DEFAULT_PORT;
  cfgtunnel->lockdir = DEFAULT_LOCKDIR;
  
  if (argc == 2 ) {
    strncpy(configfilename , argv[1], 255);
  }
  else {
    strncpy(configfilename , DEFAULT_CONFIG_FILENAME, 255);
  }

  if ( read_config_file( configfilename ) ) {
    exit ( 1 );
  } 

  openlog ( "pppserver" , LOG_PID, SYSLOG_FAC);

  syslog (LOG_NOTICE, "pppserver version %s using %s",
             VERSION, OPENSSL_VERSION_TEXT);

#ifdef HAVE_DAEMON
  if (daemon(0,1) < 0) {
    perror("daemon");
    exit(1);
  }
#else
  switch (fork()) {
  case -1:
          return (-1);
  case 0:
          break;
  default:
          _exit(0);
  }

  if (setsid() == -1)
          return (-1);

  (void)chdir("/");
#endif


  initsetproctitle(argc, argv, envp);

  setproctitle("initialising");

  close (0);
  close (1);
  close (2);
  dev_null = open("/dev/null", O_RDWR);
  dup2 ( dev_null, 0);
  dup2 ( dev_null, 1);
  dup2 ( dev_null, 2);
  if (dev_null > 2) close (dev_null);

  ctx = initialize_ctx(cfgtunnel->cacertfile, cfgtunnel->certfile, 
    cfgtunnel->keyfile);

  child_ids = malloc ( cfgtunnel->maxclients * sizeof (pid_t));
  for ( index = 0; index < cfgtunnel->maxclients; index++) 
    *( child_ids + index) = -1;

  signal ( SIGCHLD, &sig_chld_master ) ;
  signal ( SIGINT, &sig_int_master ) ;
  signal ( SIGTERM, &sig_int_master ) ;

  init_socket (cfgtunnel->port);

  /* Create pid file */
  unlink(cfgtunnel->pidfile);
  if ((fpid = open(cfgtunnel->pidfile, O_CREAT|O_WRONLY|O_EXCL, 
          S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH )) < 0) {
    syslog(LOG_ERR, "Unable to open %s: %s",
        cfgtunnel->pidfile, strerror(errno));
    exit (1);
  }
  snprintf(pidstring, 31 , "%d\n", (int) getpid());
  write(fpid, pidstring, strlen(pidstring));
  close(fpid);

  /* Create/check lockdir */
  mkdir(cfgtunnel->lockdir, S_IRWXU);
  chmod(cfgtunnel->lockdir, S_IRWXU);
  if ( lstat(cfgtunnel->lockdir, &lockdirs) ||
      (lockdirs.st_mode & S_IFMT) != S_IFDIR ||
      (lockdirs.st_uid != getuid()) ||
      (lockdirs.st_mode & S_IRWXG) != 0 ||
      (lockdirs.st_mode & S_IRWXO) != 0) {

    syslog(LOG_ERR, "Unable to create %s securely",
        cfgtunnel->lockdir);
  }

  /* Clean sessions */
  clean_sessions(cfgtunnel->wtmp);

  while (!stopped) {
    if ( (ssl = tcp_accept_fork ( )) != NULL) {
      do_server_loop ( ssl );
      update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
      exit(0);
    }
  }
  
  /* Kill and wait child */
  kill_then_wait_childs();
  syslog ( LOG_NOTICE, "pppserver exiting");
  unlink(cfgtunnel->pidfile);

  return 0;

}

int init_socket( int port ) {
 
  int sock= -1 ;
  struct sockaddr_in sin;
  int one = 1;
  char *addrs;
  char *uniqueaddr;
  int index;

 
  index = 0;
  if (cfgtunnel->listenaddr != NULL) {

    /* Bind all addresses given */
    addrs = strdup ( cfgtunnel->listenaddr );
    for ( uniqueaddr = strtok (addrs, " ,;");
          uniqueaddr && index < 15;
          uniqueaddr = strtok ( NULL, " ,;")) { 

      if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) )  < 0 ) {
        syslog(LOG_ERR, "socket: %s", strerror(errno));
        exit (-1) ;
      }

      setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, 
                  &one, sizeof(int));

      syslog ( LOG_INFO, "binding to %s", uniqueaddr );
      memset( &sin, sizeof(struct sockaddr_in), 0 );
      sin.sin_family = AF_INET;
      sin.sin_port = ntohs ( port );
      sin.sin_addr.s_addr = inet_addr ( uniqueaddr );
      if ( ( bind ( sock, (struct sockaddr *)  &sin, 
                    sizeof(struct sockaddr_in) ))) {
        syslog(LOG_ERR, "bind: %s", strerror(errno));
        exit (-1);
      }
      if (listen (sock, 6)) {
        syslog(LOG_ERR, "listen: %s", strerror(errno));
        exit (-1);
      }
      if(ioctl(sock, FIONBIO, &one)<0) {
        syslog(LOG_ERR, "ioctl: %s", strerror(errno));
        exit (-1);
      }

      socklisten[index++] = sock;

    }
    free ( addrs );
  }
  else {

    if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) )  < 0 ) {
       syslog(LOG_ERR, "socket: %s", strerror(errno));
       exit (-1) ;
    }
  
    /* Bind INET_ANY */
    memset( &sin, sizeof(struct sockaddr_in), 0 );
    sin.sin_family = AF_INET;
    sin.sin_port = ntohs ( port );
    sin.sin_addr.s_addr = INADDR_ANY;

    setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, 
                  &one, sizeof(int));

    if ( ( bind ( sock, (struct sockaddr *)  &sin, sizeof(struct sockaddr_in) ))) {
      syslog(LOG_ERR, "bind: %s", strerror(errno));
      exit (-1);
    }

    if (listen (sock, 6)) {
      syslog(LOG_ERR, "listen: %s", strerror(errno));
      exit (-1);
    }
    if(ioctl(sock, FIONBIO, &one)<0) {
      syslog(LOG_ERR, "ioctl: %s", strerror(errno));
      exit (-1);
    }

    socklisten[index++] = sock;

  }

  socklisten[index]=-1;

  return sock;

}

void sig_chld_master (int status) {
  
 pid_t child_pid;
 int index;

 while ((child_pid=waitpid(-1, NULL, WNOHANG)) > 0) {
   nbchild--;
   for (index = 0; index < cfgtunnel->maxclients; index ++) {
     if (child_pid == *(child_ids + index)) {
       *(child_ids + index) = -1;
       break;
     }
   }
 }
 signal ( SIGCHLD, &sig_chld_master ) ;
   
}

void sig_chld_slave (int status) {
  
 pid_t child_pid;

 while ((child_pid = waitpid(-1, NULL, WNOHANG)) > 0) {
   nbchild --;
   if (child_pid == pppid) { 
     pppid = -1;
     stopped = 1;
   }
 }
 signal ( SIGCHLD, &sig_chld_slave ) ;

}


SSL *tcp_accept_fork (  ) {

  int sock=-1;
  int newsock;
  struct sockaddr_in peeraddr;
  unsigned int len;
  int pid;
  X509 *xs;
  char *cp;
  SSL *ssl;
  int r,num;
  ClientData *myClient=NULL;
  int valid_cert;
  int tuyauIn[2];
  int tuyauOut[2];
  int  masterfd,slavefd;
  struct termios blah;
  char buffer[SIZE];
  char fingerprint[256];
  fd_set rset;
  int index;
  int maxsocket;
  int nd;
  int one=1;
  char *ptr;
  char prot[PROT_SIZE];
  char client_version[PROT_SIZE];
  char fingerlockfile[1024];
  struct flock locks;
  int fdlock;
  char *ptrsrc,*ptrdest;
#ifdef WITH_LIBWRAP
  struct request_info request;
#endif


  FD_ZERO(&rset);
  maxsocket = 0;
 
  for ( index=0;index < 15 && socklisten[index] >=0; index++ ) {
    FD_SET(socklisten[index], &rset);
    if ( socklisten[index] > maxsocket ) maxsocket = socklisten[index];
  }

  setproctitle("accepting connections");

  while ( (nd = select ( maxsocket + 1, &rset, NULL, NULL, NULL )) < 0 && 
          errno == EINTR && !stopped) ;

  if (nd <=0 ) {
    syslog(LOG_INFO, "select: %s", strerror(errno));
    return NULL;
  }

  for ( index=0;index < 15 && socklisten[index] >=0 ; index++ ) {
    if (FD_ISSET( socklisten[index], &rset )) {
      sock = socklisten[index];
      break;
    }
  }

  len = sizeof (struct sockaddr_in);
  newsock = accept( sock , (struct sockaddr *) &peeraddr, 
             &len);

  if (newsock < 0 ) {
    syslog(LOG_ERR, "accept: %s", strerror(errno));
    return 0;
  }

  if (nbchild >= cfgtunnel->maxclients ) {
     syslog(LOG_ERR, "too many childs !");
     close( newsock );
     return NULL;
  }

#ifdef WITH_LIBWRAP
  request_init(&request, RQ_DAEMON, TCPD_DAEMON_NAME , RQ_FILE, newsock , NULL);
  fromhost(&request); 
  if (!hosts_access(&request)) {
    syslog(LOG_WARNING,"Refused connection from %s\n", eval_client(&request));
    close(newsock);
    return NULL;
  }
#endif 

  if (newsock > 0) {
    pid = fork();
    if (pid > 0) {
      close(newsock);
      for (index = 0; index < cfgtunnel->maxclients; index ++) {
        if (*(child_ids + index) == - 1) {
           *(child_ids + index) = pid;
           break;
        }
      }
      nbchild++;
      return NULL;
    } 
    else if (pid == 0 ) {

      for ( index=0;index < 15 && socklisten[index] >=0 ; index++ ) {
          close(socklisten[index]);
      }

      stopped = 0;
      signal ( SIGCHLD, &sig_chld_slave ) ;

      signal(SIGINT, &sig_int_slave);
      signal(SIGALRM, &sig_alrm_slave);
      signal(SIGTERM, &sig_int_slave);

      setproctitle("accepting connection from %s", 
         inet_ntoa(peeraddr.sin_addr));

      syslog(LOG_NOTICE, "Accepting connection from %s",
         inet_ntoa(peeraddr.sin_addr));
      ssl=SSL_new(ctx);
      if (ssl == NULL) {
        close(newsock);
        berr_exit("SSL_new");
      }

      /* Set non block */
      if(ioctl(newsock, FIONBIO, &one)<0) {
        syslog(LOG_ERR, "ioctl: %s", strerror(errno));
        exit (-1);
      }
      setsockopt( newsock, 6, TCP_NODELAY, 
                  &one, sizeof(int));

 
      SSL_set_fd(ssl,newsock);
      SSL_set_accept_state(ssl);

      setproctitle("SSL starting with %s", 
         inet_ntoa(peeraddr.sin_addr));

      r= ssl_accept_timeout(ssl, cfgtunnel->network_timeout);
  
      if (r <= 0) {
         close(newsock);
         exit(0);
      }

      /* Check Cert */
      valid_cert = 0;
      xs = NULL;
      if ((xs = SSL_get_peer_certificate(ssl)) == NULL) {
        syslog(LOG_NOTICE, "No valid cert !");
        goto catch;
      }

      /* Check subject, look into database */
      cp = X509_NAME_oneline(X509_get_subject_name(xs), NULL, 0);
      if (cp!=NULL) {
        syslog(LOG_NOTICE, "Client certificate : %s\n", cp);
        myClient = getclientdata ( cp );
        valid_cert = ( myClient != NULL  && myClient->exec != NULL) ;
        free(cp);
      }

      if (!valid_cert) {
        syslog(LOG_NOTICE, "No valid data for this user");
        if (xs!=NULL) X509_free(xs);
        goto catch;
      }

      /* Check fingerprint */
      memset(fingerprint, 0, 256);
      x509_fingerprint (fingerprint, 255, xs);
      syslog(LOG_INFO, "Certificate fingerprint: %s", fingerprint);
      if (myClient && myClient->fingerprint && 
          strcmp(fingerprint,myClient->fingerprint)) {
        syslog(LOG_NOTICE, "Invalid fingerprint");
        X509_free(xs);
        goto catch;
      }

      /* Check issuer */
      cp = X509_NAME_oneline(X509_get_issuer_name(xs), NULL, 0);
      if (cp!=NULL) {
        syslog(LOG_INFO, "Issuer : %s", cp);
        if (( myClient->issuer != NULL) &&
            strcmp(cp, myClient->issuer)) {
           syslog(LOG_NOTICE, "Invalid issuer");
           free(cp);
           X509_free(xs);
           goto catch;
        }
        free(cp);
      }
      else {
        syslog(LOG_NOTICE, "No Issuer !");
        X509_free(xs);
        goto catch;
      }

      X509_free(xs);

      /* Create lockfile with cert fingerprint */
      memset(&locks,0,sizeof(struct flock));
      locks.l_type = F_WRLCK;
      locks.l_whence = SEEK_SET;
      strcpy(fingerlockfile,  cfgtunnel->lockdir);
      strcat(fingerlockfile,"/");
      ptrdest = fingerlockfile + strlen(fingerlockfile);
      ptrsrc = fingerprint;
      while (*ptrsrc && ptrdest < (fingerlockfile + 1022)) {
        if (*ptrsrc != ':' && *ptrsrc != '/') {
          *ptrdest = *ptrsrc;
          ptrdest++;
        }
        ptrsrc++;
      }
      *ptrdest = '\0';
      
      if (((fdlock = open(fingerlockfile, O_WRONLY|O_CREAT|O_TRUNC,
               S_IRUSR|S_IWUSR)) < 0 ) ||
          (fcntl(fdlock, F_SETLK, &locks) == -1) ) {
        syslog(LOG_NOTICE, "Unable to lock %s", fingerlockfile);
        goto catch;
      }

      update_wtmp(cfgtunnel->wtmp, myClient->subject, getpid(), 
                     inet_ntoa(peeraddr.sin_addr) );

      if (myClient->gid) {
        syslog(LOG_INFO, "setgid  to %d", myClient->gid);
        if (setgid(myClient->gid)) {
          syslog(LOG_ERR, "Unable to setgid %d : %s", myClient->gid,
               strerror(errno));
          goto catch;
        }
        if (setgroups( 1, &myClient->gid)) {
          syslog(LOG_ERR, "Unable to setgroups %d : %s", myClient->gid,
               strerror(errno));
          goto catch;
        }
      }

      if (myClient->uid) {
         syslog(LOG_INFO, "setuid  to %d", myClient->uid);
        if (setuid(myClient->uid)) {
          syslog(LOG_ERR, "Unable to setuid %d : %s", myClient->uid,
               strerror(errno));
          goto catch;
        }
      }

      SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);

      /* Send and Receive Banner */
      sprintf(buffer, SERVER_BANNER, VERSION);
      num=ssl_write_timeout(ssl, buffer, strlen(buffer), cfgtunnel->network_timeout );
      if (num != strlen(buffer)) {
         syslog(LOG_INFO, "Error writing banner");
         goto catch;
      }

      memset(buffer,0,SIZE);
      num=ssl_read_timeout(ssl, buffer, SIZE-1, cfgtunnel->network_timeout );
      if (num <= 0) {
         syslog(LOG_INFO, "Error reading banner");
         goto catch;
      }
      if ((ptr=strstr(buffer,"SSL-TUNNEL/")) != NULL) {
         num = 0;
         ptr += 11;
         while (*ptr && *ptr != ' ' &&  *ptr != '\r' && 
                       *ptr != '\n' && num < PROT_SIZE -1) {
                   client_version[num]= *ptr;
                   num++; ptr ++;
         }
         client_version[num] = 0;
         syslog(LOG_INFO, "Client version : %s", client_version);
      }
   
      if ((ptr=strstr(buffer,"PROT/"))!=NULL) {
         num = 0;
         ptr += 5;
         while (*ptr && *ptr != '\r' && *ptr != '\n' && num < PROT_SIZE -1) {
                   prot[num]= *ptr;
                   num++; ptr ++;
         }
         prot[num] = 0;
         syslog(LOG_INFO, "Client Protocol version : %s", prot);
      }
      else {
        syslog(LOG_ERR, "No valid banner");
        goto catch;
      }

      if (myClient->pty) {

        memset(&blah,0,sizeof(blah));
        blah.c_cc[VMIN]=1;
        blah.c_cc[VTIME]=0;
        blah.c_cflag |= B9600;

        if (openpty(&masterfd,&slavefd,NULL,&blah,NULL)) {
          syslog(LOG_ERR, "openpty: %s", strerror(errno));
          goto catch;
        };

        tuyauIn[0]= slavefd;
        tuyauOut[0]= masterfd;
        tuyauIn[1]= masterfd;
        tuyauOut[1]= slavefd;

      }
      else {

        if (pipe((int *) &tuyauOut) != 0) goto catch;
        if (pipe((int *) &tuyauIn) != 0) goto catch;

      }

      nbchild = 0;
      if ((pppid = fork()) !=0 ) {

         nbchild++;
         close(0);
         close(1);
         dup2(tuyauOut[0],0);
         dup2(tuyauIn[1],1);
         return ssl;

      }
      else if (pppid == 0) {

         if (wtmpfd != -1) close(wtmpfd);
         close(fdlock);
         close(0);
         close(1);
         close(newsock);
         dup2(tuyauIn[0],0);
         dup2(tuyauOut[1],1);

         syslog (LOG_INFO, "Exec: %s", myClient->exec );

         if (execv ( myClient->exec, myClient->args )) {
           syslog(LOG_ERR, "execv: %s", strerror(errno));
           exit(1);
         }

      }
      else {
         syslog (LOG_ERR, "fork: %s", strerror(errno));
      }

catch:
        update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
        SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
        SSL_free(ssl);
        ERR_remove_state(0);
        close(newsock);
        exit ( 0 );
    }
    else {
      syslog(LOG_ERR, "fork: %s", strerror(errno));
      close(newsock);
      return NULL;
    }
  }

  return NULL;

}

int berr_exit(char *string) {

    syslog(LOG_ERR, "berr_exit: %s", string);
    exit(0);

}

SSL_CTX *initialize_ctx(char *calist, char *certfile, 
                        char *keyfile)  {
  SSL_METHOD *meth;
  SSL_CTX *ctx;
    
  if(!bio_err){
    /* Global system initialization*/
    SSL_library_init();
    SSL_load_error_strings();
      
    /* An error write context */
    bio_err=BIO_new_fp(stderr,BIO_NOCLOSE);
  }

  /* Create our context*/
  meth=TLSv1_method();
  SSL_COMP_add_compression_method(0x42, COMP_rle());


  ctx=SSL_CTX_new(meth);

  /* Load our keys and certificates*/
  if(!(SSL_CTX_use_certificate_chain_file(ctx, certfile)))
      berr_exit("Can't read certificate file");

  if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)))
     berr_exit("Can't read key file");

  /* Load the CAs we trust*/
  if(!(SSL_CTX_load_verify_locations(ctx, calist ,0)))
      berr_exit("Can't read CA list");

  SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER |
          SSL_VERIFY_FAIL_IF_NO_PEER_CERT,0);

  SSL_CTX_set_tmp_rsa_callback(ctx, &tmp_rsa_callback);

  /* Prepare for PFS */
  SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
  SSL_CTX_set_tmp_dh_callback(ctx,  ssl_callback_TmpDH);

  return ctx;

}
     
void destroy_ctx(SSL_CTX *ctx)
{
  SSL_CTX_free(ctx);
}

RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength)
{
   RSA *rsa_tmp=NULL;

   rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL);
   return(rsa_tmp);
}

int do_server_loop ( SSL *ssl ) {

    fd_set rd_set, wr_set;
    int num, fdno, ssl_fd, ssl_bytes, sock_bytes, retval;
    char sock_buff[2 * SIZE], ssl_buff[2 * SIZE];
    int sock_ptr, ssl_ptr, sock_open, ssl_open;
    unsigned long l;
    int try_kill;
    struct timeval tmo;

    ssl_fd=SSL_get_fd(ssl);
    fdno=ssl_fd+1;
    sock_ptr=0;
    ssl_ptr=0;
    sock_open=1;
    ssl_open=1;
    sock_bytes=0;
    ssl_bytes=0;

    l=1; /* ON */
    if(ioctl(0, FIONBIO, &l)<0)
        sockerror("ioctlsocket (sock)");
    if(ioctl(1, FIONBIO, &l)<0)
        sockerror("ioctlsocket (sock)");
    if(ioctl(ssl_fd, FIONBIO, &l)<0)
        sockerror("ioctlsocket (ssl)");

    while((sock_open||sock_ptr) && (ssl_open||ssl_ptr) && !stopped) {

        FD_ZERO(&rd_set);

        if(sock_open && sock_ptr<SIZE) /* can read from socket */
            FD_SET(0, &rd_set);

        if(ssl_open && ssl_ptr<SIZE)   /* can read from SSL */
            FD_SET(ssl_fd, &rd_set);

        FD_ZERO(&wr_set);
        if(sock_open && ssl_ptr)       /* can write to socket */
            FD_SET(1, &wr_set);

        if(ssl_open && sock_ptr)       /* can write to SSL */
            FD_SET(ssl_fd, &wr_set);

        if(select(fdno, &rd_set, &wr_set, NULL, NULL)<0 && errno!=EINTR) {
            sockerror("select");
            goto error;
        }

        if(sock_open && FD_ISSET(0, &rd_set)) {
            num=read(0, sock_buff+sock_ptr, SIZE-sock_ptr);
            if (num < 0 && errno == EAGAIN) {
              /* Interrupt */
            }
            else if (num < 0) {
                sockerror("read");
                sock_open = 0;
            } else if (num > 0) {
                sock_ptr += num;
                outbytes+=num;
            } else { /* close */
                syslog(LOG_INFO, "stdin closed on read");
                sock_open = 0;
            }
        }

        if(ssl_open && FD_ISSET(ssl_fd, &rd_set)) {
            num=SSL_read(ssl, ssl_buff+ssl_ptr, SIZE-ssl_ptr);
            switch(SSL_get_error(ssl, num)) {
            case SSL_ERROR_NONE:
                ssl_ptr+=num;
                break;
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_X509_LOOKUP:
                break;
            case SSL_ERROR_SYSCALL:
                if(num && errno != EINTR) { /* not EOF */
                    sockerror("SSL_read (socket)");
                    goto error;
                }
            case SSL_ERROR_ZERO_RETURN:
                syslog(LOG_INFO, "ssl closed on read");
                ssl_open=0;
                break;
            case SSL_ERROR_SSL:
                syslog(LOG_INFO, "ssl error on read");
                goto error;
            }
        }

        if(sock_open && FD_ISSET(1, &wr_set)) {
            num=write(1, ssl_buff, ssl_ptr);
            if(num<0) {
                sockerror("write");
                goto error;
            }
            if(num) {
                memcpy(ssl_buff, ssl_buff+num, ssl_ptr-num);
                ssl_ptr-=num;
                sock_bytes+=num;
                inbytes+=num;
            } else { /* close */
                syslog(LOG_INFO, "socket closed on write");
                sock_open=0;
            }
        }

        if(ssl_open && FD_ISSET(ssl_fd, &wr_set)) {
            num=SSL_write(ssl, sock_buff, sock_ptr);
            switch(SSL_get_error(ssl, num)) {
            case SSL_ERROR_NONE:
                memcpy(sock_buff, sock_buff+num, sock_ptr-num);
                sock_ptr-=num;
                ssl_bytes+=num;
                break;
            case SSL_ERROR_WANT_WRITE:
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_X509_LOOKUP:
                break;
            case SSL_ERROR_SYSCALL:
                if(num) { /* not EOF */
                    sockerror("SSL_write (socket)");
                    goto error;
                }
            case SSL_ERROR_ZERO_RETURN:
                syslog(LOG_INFO, "SSL closed on write");
                ssl_open=0;
                break;
            case SSL_ERROR_SSL:
                syslog(LOG_INFO, "SSL error on write");
                goto error;
            }
        }
    }
    retval=0;

    syslog(LOG_NOTICE,"terminating normally");
    retval = 0;
    goto done;

error:
    syslog(LOG_NOTICE,"terminating abnormally");
    retval=-1;

done:
    SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
    SSL_free(ssl);
    ERR_remove_state(0);
    close(ssl_fd);
    close(0);
    close(1);

    syslog(LOG_NOTICE,"In Bytes: %ld, Out Bytes: %ld", inbytes, outbytes);
    setproctitle("terminating");
    
    try_kill = 0;
    while (pppid != -1) {
      syslog(LOG_NOTICE,"killing child");
      kill ( pppid, try_kill > 3 ? SIGKILL : SIGTERM );
      try_kill ++;
      tmo.tv_sec = 10;
      tmo.tv_usec = 0;
      select (0, NULL, NULL, NULL, &tmo);
    }
    syslog(LOG_NOTICE,"Child has exited");
      
    return retval;
}

void sockerror(char *string) 
{

  syslog(LOG_WARNING, "sock error %s", string);

}

ClientData *getclientdata (char *subject) {

 FILE *userfile;
 char line[256];
 bool err=false;
 ClientData *userdata=NULL;
 char *ptrblank;
 int index;

 userfile = fopen (cfgtunnel->userfile, "r");
 if (userfile == NULL) {
   syslog(LOG_ERR, "Unable to open user file: %s", strerror(errno));
   return NULL;
 } 

 while (!err && (fgets( line, 255, userfile) != NULL)) {
    key_value_t *key_value ;

    key_value = file_parse ( line , &err) ;
    if (err == true)
    {
       fclose ( userfile ) ;
       syslog(LOG_ERR, "Unable to parse user file");
       return 0 ;
    }
    else if (key_value == NULL) {
        /* white or commenet line : skip it */
        continue;
    }

    if (!strcasecmp ( key_value->key, "user" )) {
       if ( !strcasecmp(key_value->value, subject) ) {
         /* Good user */
         userdata = malloc (sizeof(ClientData));
         userdata->subject = strdup ( subject );
         userdata->exec = NULL;
         userdata->fingerprint = NULL;
         userdata->issuer = NULL;
         userdata->args[0] = NULL;
         userdata->pty = 0;
         userdata->uid = 0;
         userdata->gid = 0;
       }
       else if (userdata != NULL) break;
    }
    
    if (!strcasecmp (  key_value->key, "command") && userdata != NULL) {
       userdata->exec = strdup(key_value->value);    
       userdata->args[0] = strdup(key_value->value);
       userdata->args[1] = NULL;
    }

    if (!strcasecmp (  key_value->key, "pty") && userdata != NULL) {
       userdata->pty = atoi(key_value->value);    
    }

    if (!strcasecmp (  key_value->key, "args") && userdata != NULL) {
       index = 0;
       while (index < MAXARGS -1 && userdata->args[index] != NULL)
         index++;
       for (ptrblank = strtok(key_value->value," \t");
            ptrblank;
            ptrblank = strtok(NULL," \t")) {
         userdata->args[index++] = strdup(ptrblank);
         userdata->args[index] = NULL;
       }
    }
    if ( ! strcasecmp( key_value->key , "uid") && userdata != NULL) {
         userdata->uid = (uid_t) atoi(key_value->value);
    }
    if ( ! strcasecmp( key_value->key , "gid") && userdata != NULL) {
         userdata->gid = (gid_t) atoi(key_value->value);
    }
    if (!strcasecmp (  key_value->key, "fingerprint") && userdata != NULL) {
       userdata->fingerprint = strdup(key_value->value);    
    }
    if (!strcasecmp (  key_value->key, "issuer") && userdata != NULL) {
       userdata->issuer = strdup(key_value->value);    
    }

 }

 fclose ( userfile ) ;
 return userdata;
  
}

key_value_t *file_parse ( char *line , bool *err)
{
   char *tmp , *key ;
   key_value_t *key_value ;

   *err = false;
   
   key_value = ( key_value_t * ) malloc ( sizeof ( key_value_t ) ) ;
   if ( key_value == NULL )
   {
      *err = true;
      return NULL ;
   }

   /* strip comment or final \n */

   for ( tmp = line ; *tmp != '\0' ; tmp ++ )
   {
      if ( *tmp == '#' || *tmp == '\n' )
      {
         *tmp = '\0' ;
         break ;
      }
   }

   /* strip trailing whitespaces */

   while ( tmp != line )
   {
      tmp -- ;
      if ( *tmp == ' ' || *tmp == '\t' )
      {
         *tmp = '\0' ;
      }
      else
      {
         break ;
      }
   }

   /* find the key */

   tmp = line ;
   while ( *tmp == ' ' || *tmp == '\t' )
   {
      tmp ++ ;
   }
   if ( *tmp == '\0' )   /* premature end of line */
   {
      return NULL ;
   }

   /* strip the key */

   key = tmp ;
   while ( *tmp != ' ' && *tmp != '\t' && *tmp != '\0' )
   {
      tmp ++ ;
   }
   if ( *tmp == '\0' )   /* no value */
   {
      key_value->key = strdup ( key ) ;
      if ( key_value->key == NULL )
      {
         *err = true;
         return NULL ;
      }
      key_value->value = NULL ;
      return key_value ;
   }
   *tmp = '\0' ;

   /* copy the key */

   key_value->key = strdup ( key ) ;
   if ( key_value->key == NULL )
   {
      *err = true;
      return NULL ;
   }

   /* strip the blanks */

   tmp ++ ;
   while ( *tmp == ' ' || *tmp == '\t' )
   {
      tmp ++ ;
   }
   if ( *tmp == '\0' )   /* premature end of line */
   {
      key_value->value = NULL ;
      return key_value ;
   }

   /* copy the value */

   key_value->value = strdup ( tmp ) ;
   if ( key_value->value == NULL )
   {
      *err = true;
      return NULL ;
   }

   return key_value ;
}

int read_config_file (char *filename) {

  FILE *fconfig;
  bool err=0;
  char line[256];

  fconfig = fopen (filename, "r");
  if (fconfig == NULL) {
    perror(filename);
    return -1;
  }

  while (!err && (fgets( line, 255, fconfig) != NULL)) {
      key_value_t *key_value ;
  
      key_value = file_parse ( line , &err) ;
      if (err == true)
      {
         fclose ( fconfig ) ;
         fprintf (stderr, "Unable to parse config file(%s)\n", filename );
         return 0 ;
      }
      else if (key_value == NULL) {
          /* white or commenet line : skip it */
          continue; 
      }

      if ( ! strcmp( key_value->key , "keyfile") ) { 
         cfgtunnel->keyfile = key_value->value;
      }  
      else if ( ! strcmp( key_value->key , "certfile") ) {
         cfgtunnel->certfile = key_value->value;
      }  
      else if ( ! strcmp( key_value->key , "cacertfile") ) {
         cfgtunnel->cacertfile = key_value->value;
      }  
      else if ( ! strcmp( key_value->key , "userfile") ) {
         cfgtunnel->userfile = key_value->value;
      }  
      else if ( ! strcmp( key_value->key , "maxclients") ) {
         cfgtunnel->maxclients = atoi(key_value->value);
      }  
      else if ( ! strcmp( key_value->key , "port") ) {
         cfgtunnel->port = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "timeout") ) {
         cfgtunnel->network_timeout = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "listenaddr") ) {
         cfgtunnel->listenaddr = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "wtmp") ) {
         cfgtunnel->wtmp = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "pidfile") ) {
         cfgtunnel->pidfile = key_value->value;
      }
     else if ( ! strcmp( key_value->key , "lockdir") ) {
         cfgtunnel->lockdir = key_value->value;
      }
}
  fclose (fconfig);
  return err;
}

static void x509_fingerprint (char *s, int l, X509 * cert)
{
  unsigned char md[EVP_MAX_MD_SIZE];
  unsigned int n;
  int j;

  if (!X509_digest (cert, EVP_md5 (), md, &n))
  {
    snprintf (s, l, "[unable to calculate]");
  }
  else
  {
    for (j = 0; j < (int) n; j++)
    {
      char ch[8];
      snprintf (ch, 8, "%02X%c", md[j], (j +1 == (int) n ? '\0': ':' ));
      strncat (s, ch, l);
    }
  }
}

/* Signal handler for forked process */

void sig_int_slave ( int status ) {

  /* Kill ppp */
  if (pppid != -1) kill ( pppid, SIGTERM);
  else {
    update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
    exit(0);
  }
  signal(SIGINT, &sig_int_slave);
  signal(SIGTERM, &sig_int_slave);

}

void sig_alrm_slave (int status ) {

  stopped = 1;
  signal(SIGALRM, &sig_alrm_slave);

}

void sig_int_master ( int status ) {

  stopped = 1;
  signal ( SIGINT, &sig_int_master ) ;
  signal ( SIGTERM, &sig_int_master ) ;

}

void kill_then_wait_childs () {

  int index;

  for (index = 0; index < cfgtunnel->maxclients; index ++) {
    if (*(child_ids + index) != - 1) {
      syslog( LOG_DEBUG, "Killing %d", *(child_ids + index));
      kill ( *(child_ids + index), SIGTERM);
    }
  }
  while (nbchild) {
    select ( 0 , NULL, NULL, NULL, NULL );
  }
}


static int ssl_accept_timeout(SSL *ssl, int tmo)
{

  int r=0;
  int rfd, wfd;
  int n,maxfd;
  fd_set rfds, wfds;
  fd_set *pwfds;
  struct timeval tv;
  long end;
  int t;
  long errcode;

  rfd = SSL_get_fd(ssl);
  wfd = SSL_get_fd(ssl);
  n = rfd + 1;
  maxfd = (rfd > wfd ? rfd : wfd) + 1;

  pwfds = (fd_set *) NULL;
  end = tmo + time( NULL );

  tv.tv_sec = tmo;
  tv.tv_usec = 0;

  FD_ZERO(&rfds);
  FD_SET(rfd,&rfds);

  /* number of descriptors that changes status */
  while (0 < (n = select(n,&rfds,pwfds,(fd_set *) 0,&tv)))
  {
    r = SSL_accept(ssl);
    if (r > 0) {
      return r;
    }

    switch (errcode=SSL_get_error(ssl, r))
    {
    case SSL_ERROR_WANT_READ:
      pwfds = (fd_set *) 0;
      n = rfd + 1;
      break;
    case SSL_ERROR_WANT_WRITE:
      pwfds = &wfds;
      FD_ZERO(&wfds);
      FD_SET(wfd,&wfds);
      n = maxfd;
      break;
    default:
      switch (errcode) {
        case SSL_ERROR_SSL:
        case SSL_ERROR_SYSCALL:
           if ((errcode = ERR_get_error()) > 0) {
             syslog(LOG_NOTICE,"ssl_accept : %s", ERR_error_string(errcode, NULL));
           }
           else {
             syslog(LOG_NOTICE,"ssl_accept : %d", SSL_get_error(ssl, r));
           }
           break;
        default:
           syslog(LOG_NOTICE,"ssl_accept : %d", SSL_get_error(ssl, r));
           break;
      }
      return -2;
    }

    if ((t = end - time( NULL )) < 0) break;

    tv.tv_sec = t;
    tv.tv_usec = 0;

    FD_ZERO(&rfds);
    FD_SET(rfd,&rfds);
  }

  syslog(LOG_NOTICE,"ssl_accept : timeout");
  return -1;

}

static int ssl_write_timeout(SSL *ssl, char *buffer, int len, int tmo)
{

  int r=0;
  int rfd, wfd;
  int n,maxfd;
  fd_set rfds, wfds;
  fd_set *prfds;
  struct timeval tv;
  long end;
  int t;

  rfd = SSL_get_fd(ssl);
  wfd = SSL_get_fd(ssl);
  n = rfd + 1;
  maxfd = (rfd > wfd ? rfd : wfd) + 1;

  prfds = (fd_set *) NULL;
  end = tmo + time( NULL );

  tv.tv_sec = tmo;
  tv.tv_usec = 0;

  FD_ZERO(&wfds);
  FD_SET(wfd,&wfds);

  /* number of descriptors that changes status */
  while (0 < (n = select(n,prfds,&wfds,(fd_set *) 0,&tv)))
  {
    r = SSL_write(ssl, buffer, len);
    if (r > 0) {
      return r;
    }

    switch (SSL_get_error(ssl, r))
    {
    case SSL_ERROR_WANT_READ:
      prfds = (fd_set *) &rfds;
      FD_ZERO(&rfds);
      FD_SET(rfd,&rfds);
      n = maxfd;
      break;
    case SSL_ERROR_WANT_WRITE:
      prfds = (fd_set *) NULL;
      n = wfd + 1;
      break;
    default:
      /* some other error */
      syslog(LOG_NOTICE,"ssl_write : %d", SSL_get_error(ssl, r));
      return -2;
    }

    if ((t = end - time( NULL )) < 0) break;

    tv.tv_sec = t;
    tv.tv_usec = 0;

    FD_ZERO(&wfds);
    FD_SET(wfd,&wfds);
  }

  syslog(LOG_NOTICE,"ssl_write : timeout");
  return -1;

}

static int ssl_read_timeout(SSL *ssl, char *buffer, int len, int tmo)
{

  int r=0;
  int rfd, wfd;
  int n,maxfd;
  fd_set rfds, wfds;
  fd_set *pwfds;
  struct timeval tv;
  long end;
  int t;

  rfd = SSL_get_fd(ssl);
  wfd = SSL_get_fd(ssl);
  n = rfd + 1;
  maxfd = (rfd > wfd ? rfd : wfd) + 1;

  pwfds = (fd_set *) NULL;
  end = tmo + time( NULL );

  tv.tv_sec = tmo;
  tv.tv_usec = 0;

  FD_ZERO(&rfds);
  FD_SET(rfd,&rfds);

  /* number of descriptors that changes status */
  while (0 < (n = select(n,&rfds,pwfds,(fd_set *) 0,&tv)))
  {
    r = SSL_read(ssl, buffer, len);
    if (r > 0) {
      return r;
    }

    switch (SSL_get_error(ssl, r))
    {
    case SSL_ERROR_WANT_READ:
      pwfds = (fd_set *) 0;
      n = rfd + 1;
      break;
    case SSL_ERROR_WANT_WRITE:
      pwfds = &wfds;
      FD_ZERO(&wfds);
      FD_SET(wfd,&wfds);
      n = maxfd;
      break;
    default:
      /* some other error */
      syslog(LOG_NOTICE,"ssl_read : %d", SSL_get_error(ssl, r));
      return -2;
    }

    if ((t = end - time( NULL )) < 0) break;

    tv.tv_sec = t;
    tv.tv_usec = 0;

    FD_ZERO(&rfds);
    FD_SET(rfd,&rfds);
  }

  syslog(LOG_NOTICE,"ssl_read : timeout");
  return -1;

}

/*# vim:set expandtab:set syntax=c:*/



syntax highlighted by Code2HTML, v. 0.9.1