/*
   $Id: main.c,v 1.35 2004/02/03 12:26:32 thivillon Exp $
  
   © 2003 Alain Thivillon et Hervé Schauer Consultants 

*/

#ifdef __linux__
#define _GNU_SOURCE
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.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 <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <syslog.h>

#include "config.h"

#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

#ifdef HAVE_STDARG_H
#include <stdarg.h>
#else
#ifdef HAVE_VARARGS_H
#include <varargs.h>
#endif
#endif

#ifndef INADDR_NONE
#define       INADDR_NONE             0xffffffff
#endif

#include "ssltun.h"
#include "base64.h"

#ifdef WITH_AUTH_NTLM
#include "ntlmauth.h"
#endif


int init_socket( );
void sigchld (int status);
void sigint (int status);
SSL *ssl_ppp_fork ( int sock );
int do_client_loop ( SSL *ssl );
int berr_exit(char *string);
SSL_CTX *initialize_ctx(char *calist, char *certfile, char *keyfile);
void destroy_ctx(SSL_CTX *ctx);
key_value_t *file_parse ( char *line , bool *err);
int readconfigfile( char * filename);
void dumpconfigfile ();
void sockerror (char *string);
tunnel *read_cmd_line(int argc, char **argv);
void usage( char *progname);
void do_log( int prio, char *format , ... );
static int ssl_connect_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);
int write_timeout(int s,  char *buffer, int size, int tmo);
int read_timeout(int s, char *buffer, int size, int tmo);
static int wrap_basic_auth(tunnel *cfgtunnel, struct sockaddr_in sin, char *buffer);

#ifdef WITH_AUTH_NTLM
static int wrap_ntlm_auth(tunnel *cfgtunnel, struct sockaddr_in sin, char *response);
#endif


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

int stopped=0;
int killed=0;
int nbchild=0;
int verbose=1;
int inbytes=0;
int outbytes=0;
SSL_CTX *ctx;
tunnel *cfgtunnel ;
char *localpppargs[256];
BIO *bio_err=0;
int pppid=-1;
int restart_session = 0;

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

  int sock;
  SSL *ssl;
  char configfilename[CF_SIZE];
  int  argnumber = 0;
  time_t lasttry=0;
  time_t now=0;
  int dev_null;
  tunnel *cfgline = NULL;
  int logfile=2;

  /* Read options given on command line */
  cfgline = read_cmd_line(argc, argv);
  argc -= optind;
  argv += optind;

  if ( argc >=1) { 
     strncpy(configfilename, argv[argc-1], CF_SIZE);
     argc --;
     argv[argc] = NULL;
  }
  else { 
    strncpy(configfilename, getenv("HOME"), CF_SIZE);
    strncat(configfilename, "/.ssltunnelrc", CF_SIZE);
  }

  fprintf(stderr,"pppclient version %s using %s\n",VERSION,
           OPENSSL_VERSION_TEXT);
  fprintf(stderr,"Using configuration file : %s\n",configfilename);

  if ( ! readconfigfile( configfilename ) ) { 
    exit ( 1 );
  }

  if ( cfgline->remotehost != NULL) cfgtunnel->remotehost =  cfgline->remotehost;
  if ( cfgline->port !=0 ) cfgtunnel->port =  cfgline->port;
  if ( cfgline->proxyname != NULL) cfgtunnel->proxyname = cfgline->proxyname;
  if ( cfgline->useproxy ) cfgtunnel->useproxy = cfgline->useproxy;
  if ( cfgline->proxyport ) cfgtunnel->proxyport = cfgline->proxyport;
  if ( cfgline->proxyusername != NULL) cfgtunnel->proxyusername = cfgline->proxyusername;
  if ( cfgline->proxypass != NULL) cfgtunnel->proxypass = cfgline->proxypass;
  if ( cfgline->autoreconnect ) cfgtunnel->autoreconnect = cfgline->autoreconnect;
  if ( cfgline->daemon ) cfgtunnel->daemon = cfgline->daemon;
  if ( cfgline->logfile ) cfgtunnel->logfile = cfgline->logfile;

  if (cfgtunnel->verbose) dumpconfigfile();

  if (cfgtunnel->bsdppp) {
     argnumber=0;
    localpppargs[argnumber++] = "ppp";
    localpppargs[argnumber++] = "-direct";
    if (cfgtunnel->peer == NULL) {
      fprintf(stderr,"Using *BSD userland ppp needs a peer name (\"peer\") param in config\n");
      exit ( 2 );
    }
    localpppargs[argnumber++] = cfgtunnel->peer;
  }
  else {
    argnumber=0;
    localpppargs[argnumber++] = "pppd";
    localpppargs[argnumber++] = "nodetach";
    localpppargs[argnumber++] = "local";

    if (cfgtunnel->localechoint) {
      localpppargs[argnumber++] = "lcp-echo-interval";
      localpppargs[argnumber] = malloc(10);
      snprintf(localpppargs[argnumber],10,"%d",cfgtunnel->localechoint);
      argnumber++;
    }

    if (cfgtunnel->localechfail) {
      localpppargs[argnumber++] = "lcp-echo-failure";
      localpppargs[argnumber] = malloc(10);
      snprintf(localpppargs[argnumber],10,"%d",cfgtunnel->localechfail);
      argnumber++;
    }

    if (cfgtunnel->ipparam) {
      localpppargs[argnumber++] = "ipparam";
      localpppargs[argnumber++] = cfgtunnel->ipparam;
    }
    localpppargs[argnumber++] = "lcp-max-configure";
    localpppargs[argnumber++] = "40";
    localpppargs[argnumber++] = "9600";
    if (cfgtunnel->localdebug) localpppargs[argnumber++] = "debug";

    if (cfgtunnel->peer != NULL) {
       localpppargs[argnumber++] = "call";
       localpppargs[argnumber++] = cfgtunnel->peer;
    }
  }

  localpppargs[argnumber++] = NULL;

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

  if (ctx == NULL) exit(2);

  if (cfgtunnel->daemon) {
     if (cfgtunnel->logfile != NULL) {
        logfile = open(cfgtunnel->logfile,O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR);
        if (logfile < 0 ) {
           fprintf(stderr,"Can not open %s : %s\n", cfgtunnel->logfile, strerror(errno));
        }
     }
     else {
       openlog ( "pppclient" , LOG_PID, SYSLOG_FAC);
     }

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

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

        (void)chdir("/");

        if ((dev_null = open("/dev/null", O_RDWR, 0)) != -1) {
            dup2(dev_null, 2);
            if (dev_null > 2) close (dev_null);
        }
#endif

     if (logfile >= 0) dup2(logfile, 2);

  }

  signal ( SIGCHLD, &sigchld ) ;
  signal ( SIGINT, &sigint ) ;
  signal ( SIGTERM, &sigint ) ;

  do {

    stopped = 0;
    inbytes=0;
    outbytes=0;
    lasttry = time (NULL);

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


    sock = init_socket ();
    if (sock >= 0) {
      if ( (ssl = ssl_ppp_fork ( sock )) != NULL) {
        do_client_loop ( ssl );
      }
    }

    now = time(NULL);
    restart_session = cfgtunnel->autoreconnect && !killed;

    if (restart_session ) {
      do_log(LOG_INFO, "Sleeping %d seconds", MIN_DELAY);
      sleep ( MIN_DELAY );
      do_log(LOG_INFO, "Restarting");
    } 

  } while (restart_session);
  
  return 0;

}

int init_socket( ) {
 
  int sock;
  struct sockaddr_in sin;
  struct hostent *hostent;
  char response[2048];
  char bufcmd[2048];
  int lg;
  in_addr_t addr;
  int one = 1;
  int do_auth = 0;
  char *ptr,*ptr_data;
  int crlf=0;

  if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) )  < 0 ) {
     do_log ( LOG_WARNING, "socket : %s", strerror(errno)); 
     return (-2) ;
  }
  
  memset( &sin, sizeof(struct sockaddr_in), 0 );
  sin.sin_family = AF_INET;

  if (cfgtunnel->useproxy) {

    sin.sin_port = ntohs ( cfgtunnel->proxyport );
    if ( inet_addr (cfgtunnel->proxyname) == INADDR_NONE )   /* hostname */
    {  
	hostent = gethostbyname ( cfgtunnel->proxyname ) ;
	if (hostent==NULL) {
          do_log ( LOG_WARNING, "gethostbyname %s", cfgtunnel->proxyname);
          return -2;
	};
        memcpy(&addr, *(hostent->h_addr_list) , sizeof (&addr));
    }
    else   /* IP address */
    {  
	addr = inet_addr ( cfgtunnel->proxyname ) ;
    }

  }
  else {

    sin.sin_port = ntohs ( cfgtunnel->port  );
    if ( inet_addr ( cfgtunnel->remotehost ) == INADDR_NONE )   /* hostname */
    {  
	hostent = gethostbyname ( cfgtunnel->remotehost ) ;
	if (hostent==NULL) {
          do_log ( LOG_WARNING, "gethostbyname %s", cfgtunnel->remotehost);
          return -2;
	};
        memcpy(&addr, *(hostent->h_addr_list) , sizeof (&addr));
    }
    else   /* IP address */
    {  
	addr = inet_addr ( cfgtunnel->remotehost ) ;
    }
  }

  sin.sin_addr.s_addr = addr;

  setsockopt ( sock, 6, TCP_NODELAY , &one, sizeof (int) );

  do_log(LOG_INFO, "Connecting to %s", inet_ntoa( (struct in_addr) sin.sin_addr));

  if ( connect ( sock , 
                 ( struct sockaddr * ) &sin ,
                 sizeof ( sin ) ) == -1 )
  {  
     do_log (LOG_WARNING, "connect: %s", strerror(errno)) ;
     close (sock );
     return -1;
  }

  do_log(LOG_INFO, "Connected");

  if(ioctl(sock, FIONBIO, &one)<0)
        if (verbose) do_log(LOG_ERR,"ioctl (ssl)");

  if (cfgtunnel->useproxy) {

    snprintf(bufcmd, 2048,
    "CONNECT %s:%d HTTP/1.0\r\nUser-Agent: %s\r\n\r\n", 
          cfgtunnel->remotehost, cfgtunnel->port, cfgtunnel->useragent);

    do_log(LOG_INFO,"Sent to proxy:\n%s",bufcmd);

    if (write_timeout(sock,bufcmd,strlen(bufcmd), cfgtunnel->network_timeout) <= 0) {
      do_log(LOG_INFO,"Error writing to proxy");
      goto catch; 
    }

   do_log(LOG_INFO,"Waiting for proxy response ...");


    ptr = response;
    *ptr = '\0';
    crlf = 0;
    while (!crlf && ptr < (response+1023) )  {
      lg = read_timeout(sock,ptr,1023 - (ptr-response), cfgtunnel->network_timeout); 
      if (lg > 0) {
        *(ptr+lg) = 0; 
        if ((ptr_data=strstr(response,"\r\n\r\n"))!=NULL) {
           crlf=1;
           *ptr_data = 0;
        }
      }
      else break;
      ptr += lg;
    }

    do_log(LOG_DEBUG,"Proxy response : \n%s\n", response);
    
    if (!crlf) {
       do_log(LOG_INFO,"Bad proxy answer, aborting");
       goto catch;
    }

    if (strlen( cfgtunnel->proxyusername )
     && strlen ( cfgtunnel->proxypass)) do_auth = 1;

#ifdef WITH_AUTH_NTLM
    if ((!strncmp(response,"HTTP/1.0 407",12) ||
         !strncmp(response,"HTTP/1.1 407",12))
         && ( strcasestr(response,"Proxy-Authenticate: NTLM") !=NULL ) && do_auth) {
         close ( sock );
         sock = wrap_ntlm_auth(cfgtunnel, sin, response);
         if (sock < 0) goto catch;
    }
    else 
#endif
    if ((!strncmp(response,"HTTP/1.0 407",12) ||
         !strncmp(response,"HTTP/1.1 407",12))
         && ( strcasestr(response,"Proxy-Authenticate: Basic") !=NULL ) && do_auth) {
         close ( sock );
         sock = wrap_basic_auth(cfgtunnel, sin, response);
         if (sock < 0) goto catch;
    }

    if (strncmp(response,"HTTP/1.0 200",12) && strncmp(response,"HTTP/1.1 200",12)) {
	if (cfgtunnel->verbose) do_log(LOG_INFO,"Bad proxy answer, aborting");
        goto catch;
    }
  }

  return sock;

catch:

  close (sock);
  return -1;

}

void sigchld (int status) {

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

}

SSL *ssl_ppp_fork ( int sock ) {

  SSL *ssl;
  int r,num;
  int  masterfd,slavefd;
  struct termios blah;
  char *ptr;
  char prot[PROT_SIZE];
  char server_version[PROT_SIZE];
  char buffer[SIZE];


  ssl=SSL_new(ctx);
  if (ssl == NULL) {
     close(sock);
     berr_exit("SSL_new");
     return NULL;
  }
  SSL_set_fd(ssl,sock);
  SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
  SSL_set_connect_state(ssl);


  r = ssl_connect_timeout(ssl, cfgtunnel->network_timeout);
  if (r <= 0) {
     goto catch;
  }

  do_log(LOG_INFO,"SSL connect sucessful");


  /* Receive then Send Banner */
  memset(buffer,0,SIZE);
  num=ssl_read_timeout(ssl, buffer , SIZE-1, cfgtunnel->network_timeout);
  if (num <= 0) {
    sockerror("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) {
		 server_version[num]= *ptr;
		 num++; ptr ++;
       }
       server_version[num] = 0;
       do_log(LOG_INFO,"Server version : %s", server_version);
  }
  else {
     sockerror("No valid banner");
     goto catch;
  }

  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;
      do_log(LOG_INFO,"Server Protocol version : %s", prot);
  }
  else {
     sockerror("No valid banner");
     goto catch;
  }

  memset(buffer,0,SIZE);
  sprintf(buffer, CLIENT_BANNER, VERSION);

  num=ssl_write_timeout(ssl, buffer, strlen(buffer), cfgtunnel->network_timeout);
  if (num <= 0) {
    sockerror("Error writing banner");
    goto catch;
  }

  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)) {
    do_log(LOG_WARNING,"openpty : %s", strerror(errno));
    goto catch;
  };

  do_log(LOG_INFO,"forking ppp");

  if ((pppid=fork()) > 0) {

    dup2(masterfd,0);
    dup2(masterfd,1);

    if (slavefd > 2) close (slavefd);
    if (masterfd > 2) close (masterfd);
    return ssl;

  }
  else {

    close(sock);

    dup2(slavefd,1);
    dup2(slavefd,0);

    if (slavefd > 2) close (slavefd);
    if (masterfd > 2) close (masterfd);

    if (execv ( cfgtunnel->localppp , localpppargs )) {
       do_log(LOG_ERR,"error executing %s: %s", 
                  cfgtunnel->localppp, strerror(errno));
       exit(1);
    }
  }

catch:
  SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
  SSL_free(ssl);
  ERR_remove_state(0);
  close(sock);
  return NULL;

}

int berr_exit(char *string) {

    BIO_printf( bio_err,"%s %d\n", string);
    ERR_print_errors(bio_err);
    return 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*/
  SSL_COMP_add_compression_method(0x42, COMP_rle());
  meth=TLSv1_client_method();

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

  if (cfgtunnel->randfile != NULL)
     RAND_load_file(cfgtunnel->randfile,1024);

  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_client_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;
    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)
        if (verbose) do_log(LOG_ERR,"ioctl (sock)");
    if(ioctl(1, FIONBIO, &l)<0)
        if (verbose) do_log(LOG_ERR,"ioctl (sock)");
    if(ioctl(ssl_fd, FIONBIO, &l)<0)
        if (verbose) do_log(LOG_ERR,"ioctl (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) {
            if (verbose) do_log (LOG_WARNING, "select %s", strerror(errno));
            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) {
                if (verbose) do_log (LOG_WARNING, "read 0 : %s", strerror(errno));
                sock_open = 0;
            } else if (num > 0) {
                sock_ptr += num;
            } else { /* close */
                do_log(LOG_WARNING, "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) { /* not EOF */
                    do_log(LOG_WARNING, "SSL_read: %s", strerror(errno) );
                    goto error;
                }
            case SSL_ERROR_ZERO_RETURN:
                do_log(LOG_WARNING,"SSL closed on read\n");
                ssl_open=0;
                break;
            case SSL_ERROR_SSL:
                do_log(LOG_WARNING,"SSL_read :SSL_error: %d",num);
                goto error;
            }
        }

        if(sock_open && FD_ISSET(1, &wr_set)) {
            num=write(1, ssl_buff, ssl_ptr);
            if(num<0) {
                do_log( LOG_WARNING, "write (stdout): %s", strerror(errno));
                goto error;
            }
            if(num) {
                do_log(LOG_DEBUG,"%4d <-----",num);
                memcpy(ssl_buff, ssl_buff+num, ssl_ptr-num);
                ssl_ptr-=num;
                sock_bytes+=num;
            } else { /* close */
                do_log(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;
                do_log(LOG_DEBUG,"             -----> %4d",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 */
                    do_log(LOG_INFO,"SSL_write (socket)");
                    goto error;
                }
            case SSL_ERROR_ZERO_RETURN:
                do_log(LOG_INFO,"SSL closed on write");
                ssl_open=0;
                break;
            case SSL_ERROR_SSL:
                do_log(LOG_INFO,"SSL_write");
                goto error;
            }
        }
    }
    retval=0;
    goto clean;

error:
    retval=-1;

clean:
    SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
    SSL_free(ssl);
    ERR_remove_state(0);

    try = 0;
    while (pppid != -1) {
       tmo.tv_sec = 10;
       tmo.tv_usec = 0;
       do_log(LOG_INFO,"Trying to kill ppp");
       kill(pppid, try < 3 ? SIGTERM : SIGKILL);
       try++;
       select ( 0, NULL, NULL, NULL, &tmo);
    }
       
    do_log(LOG_INFO,"PPP terminated");
    do_log(LOG_INFO,"InBytes: %d, OutBytes: %d",sock_bytes,ssl_bytes); 

    close(ssl_fd);
    close(0);
    close(1);

    return retval;
}

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;
      fprintf(stderr,"nsm_parse : malloc");
      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 readconfigfile( char * filename) {

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

  fconfig = fopen( filename ,"r");
  if ( fconfig == NULL)  {
    perror(filename);
    return 0;
  }
  cfgtunnel = (tunnel *) malloc( sizeof ( tunnel ));
  if (cfgtunnel == NULL) {
    perror("malloc in readconfigfile");
    return 0;
  }
  memset( (void * ) cfgtunnel, 0, sizeof(tunnel)) ;
  cfgtunnel->remotehost   = strdup("127.0.0.1") ;
  cfgtunnel->localppp     = strdup(LOCAL_PPP) ;
  cfgtunnel->verbose      = 0 ;
  cfgtunnel->autoreconnect= 0 ;
  cfgtunnel->daemon       = 0 ;
  cfgtunnel->useproxy     = 0 ;
  cfgtunnel->localdebug   = 0 ;
  cfgtunnel->ipparam      = strdup(DEFAULT_IP_PARAM) ;
  cfgtunnel->proxyname    = strdup(DEFAULT_PROXY_NAME) ;
  cfgtunnel->proxyport    = DEFAULT_PROXY_PORT ;
  cfgtunnel->proxyusername= strdup("") ;
  cfgtunnel->proxypass    = strdup("") ;
  cfgtunnel->useragent    = strdup(DEFAULT_USER_AGENT) ;
  cfgtunnel->network_timeout = DEFAULT_NETWORK_TIMEOUT;
  cfgtunnel->localechoint = DEFAULT_LOCAL_ECHOINT ;
  cfgtunnel->localechfail = DEFAULT_LOCAL_ECHOFAIL ;
  cfgtunnel->localproxy   = DEFAULT_LOCAL_PROXYARP ;
  cfgtunnel->keyfile	  = KEYFILE;
  cfgtunnel->certfile	  = CERTFILE;
  cfgtunnel->cacertfile	  = CAFILE;
  cfgtunnel->randfile	  = NULL;
  cfgtunnel->logfile	  = NULL;
  cfgtunnel->peer	  = NULL;

  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 || key_value->key == NULL ||
               key_value->value == NULL) {
	  /* white or commenet line : skip it */
	  continue;
      }
      if ( ! strcmp( key_value->key , "remotehost") ) {
         free ( cfgtunnel->remotehost );
         cfgtunnel->remotehost = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "localppp") ) {
         free ( cfgtunnel->localppp );
         cfgtunnel->localppp = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "verbose") ) {
         cfgtunnel->verbose = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "useproxy") ) {
         cfgtunnel->useproxy = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "ipparam") ) {
         free ( cfgtunnel->ipparam );
         cfgtunnel->ipparam = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "proxy") ) {
         free ( cfgtunnel->proxyname );
         cfgtunnel->proxyname = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "port") ) {
         cfgtunnel->port = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "proxyport") ) {
         cfgtunnel->proxyport = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "proxyuser") ) {
         free ( cfgtunnel->proxyusername );
         cfgtunnel->proxyusername = key_value->value ? key_value->value : "";
      }
      else if ( ! strcmp( key_value->key , "proxypass") ) {
         free ( cfgtunnel->proxypass );
         cfgtunnel->proxypass = key_value->value ? key_value->value : "";
      }
      else if ( ! strcmp( key_value->key , "useragent") ) {
         free ( cfgtunnel->useragent );
         cfgtunnel->useragent = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "localechoint") ) {
         cfgtunnel->localechoint = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "localechofail") ) {
         cfgtunnel->localechfail = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "localproxyarp") ) {
         cfgtunnel->localproxy = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "localdebug") ) {
         cfgtunnel->localdebug = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "timeout") ) {
         cfgtunnel->network_timeout = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "autoreconnect") ) {
         cfgtunnel->autoreconnect = atoi(key_value->value);
      }
      else if ( ! strcmp( key_value->key , "daemon") ) {
         cfgtunnel->daemon = atoi(key_value->value);
      }
      else 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 , "randfile") ) {
         cfgtunnel->randfile = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "logfile") ) {
         cfgtunnel->logfile = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "peer") ) {
         cfgtunnel->peer = key_value->value;
      }
      else if ( ! strcmp( key_value->key , "bsdppp") ) {
         cfgtunnel->bsdppp = atoi(key_value->value);
      }
      else {
         fprintf(stderr,"Bad key/pair value : %s / %s\n",key_value->key,
                 key_value->value);
      }
      free ( key_value->key ) ;
      free ( key_value ) ;
   }
   fclose(fconfig);
   return true;
}

void dumpconfigfile () {
  fprintf(stderr,"verbose\t\t\t\t%d\n",cfgtunnel->verbose);

  fprintf(stderr,"remotehost\t\t\t%s\n",cfgtunnel->remotehost);
  fprintf(stderr,"port\t\t\t\t%d\n",cfgtunnel->port);

  fprintf(stderr,"localppp\t\t\t%s\n",cfgtunnel->localppp);
  fprintf(stderr,"ipparam\t\t\t\t%s\n",cfgtunnel->ipparam);
  fprintf(stderr,"localproxyarp\t\t\t%d\n",cfgtunnel->localproxy);
  fprintf(stderr,"localechoint\t\t\t%d\n",cfgtunnel->localechoint);
  fprintf(stderr,"localechofail\t\t\t%d\n",cfgtunnel->localechfail);
  fprintf(stderr,"localdebug\t\t\t%d\n",cfgtunnel->localdebug);
  fprintf(stderr,"timeout\t\t\t%d\n",cfgtunnel->network_timeout);
  
  fprintf(stderr,"useproxy\t\t\t%d\n",cfgtunnel->useproxy);
  fprintf(stderr,"proxy\t\t\t\t%s\n",cfgtunnel->proxyname);
  fprintf(stderr,"proxyport\t\t\t%d\n",cfgtunnel->proxyport);
  fprintf(stderr,"proxyuser\t\t\t%s\n",cfgtunnel->proxyusername);
  fprintf(stderr,"useragent\t\t\t%s\n",cfgtunnel->useragent);

  fprintf(stderr,"keyfile\t\t\t\t%s\n",cfgtunnel->keyfile);
  fprintf(stderr,"certfile\t\t\t%s\n",cfgtunnel->certfile);
  fprintf(stderr,"cacertfile\t\t\t%s\n",cfgtunnel->cacertfile);

  fprintf(stderr,"autoreconnect\t\t%d\n",cfgtunnel->autoreconnect);
  fprintf(stderr,"daemon\t\t\t%d\n",cfgtunnel->daemon);
  if (cfgtunnel->logfile != NULL) 
      fprintf(stderr,"logfile\t\t\t%s\n",cfgtunnel->logfile);
  if (cfgtunnel->peer != NULL) 
      fprintf(stderr,"peer\t\t\t%s\n",cfgtunnel->peer);
  fprintf(stderr,"bsdppp\t\t%d\n",cfgtunnel->bsdppp);
  fflush(stderr);

}

void sockerror (char *string)
{
  fprintf(stderr,"%s\n", string);
}

void sigint (int status) {
   if (pppid != -1) {
     fprintf(stderr, "Kill ppp (pid %d)\n", pppid);
     fflush(stderr);
     kill (pppid, SIGTERM);
     killed = 1;
   }
   else {
     killed = 1;
     exit(2);
   }
  signal ( SIGINT, &sigint);
}

tunnel *read_cmd_line(int argc, char **argv)
{
  int ch;
  extern char *optarg;
  char *ptr;
  char *progname;
  tunnel *cfgline;
  

  
  cfgline = malloc (sizeof (tunnel));
  memset (cfgline, 0, sizeof (tunnel));

  progname = argv[0];

  while ((ch = getopt(argc, argv, "r:u:a:p:h:c:d:l:")) != -1) {
     switch (ch) {
        case 'h':
          /* Hostname */
          cfgline->remotehost = strdup(optarg);
          break;
        case 'p':
          /* Port */
          cfgline->port = atoi(optarg);
          break;
        case 'r':
          /* Proxy */
          cfgline->proxyname = strdup(optarg);
          cfgline->useproxy = 1;
          break;
        case 'u':
          /* Proxy Port */
          cfgline->proxyport = atoi(optarg);
          break;
        case 'l':
          /* Log File */
          cfgline->logfile = strdup(optarg);
          break;
        case 'a':
          /* Autentication */
          cfgline->proxyusername = strtok(optarg,":");
          if ((ptr = strtok(NULL,":")) != NULL) {
            cfgline->proxypass = ptr;
          };
         case 'c':
          /* reconnect */
          cfgline->autoreconnect = atoi(optarg);
          break;
         case 'd':
          /* Become daemon */
          cfgline->daemon = atoi(optarg);
          break;
        default:
          usage( progname );
          break;
     }
  }

  return cfgline;

}

#ifdef WITH_AUTH_NTLM

static int wrap_ntlm_auth(tunnel *cfgtunnel, struct sockaddr_in sin, char *buffer) {

  int one = 1;
  int lg,crlf;
  char *ptr;
  int sock;
  char *ptr_data;

  if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) )  < 0 ) {
     fprintf(stderr,"socket : %s\n", strerror(errno)); 
     return (-1) ;
  }
  
  setsockopt ( sock, 6, TCP_NODELAY , &one, sizeof (int) );

  if ( connect ( sock ,
                 ( struct sockaddr * ) &sin ,
                 sizeof ( sin ) ) == -1 )
  {
     fprintf(stderr, "connect: %s", strerror(errno)) ;
     return -1;
  }

  ioctl(sock, FIONBIO, &one);
  snprintf(buffer, 1024, "CONNECT %s:%d HTTP/1.1", 
          cfgtunnel->remotehost, cfgtunnel->port);

  if (do_ntlm_auth (sock, cfgtunnel->proxyusername , cfgtunnel->proxypass , 
                    buffer , cfgtunnel->remotehost, cfgtunnel->useragent, PROXY_AUTH)) goto catch;

  ptr = buffer;
  *ptr = '\0';
  crlf = 0;
  while (!crlf && (ptr < buffer+1023))  {
    lg = read_timeout(sock,ptr,1023 - (ptr-buffer), cfgtunnel->network_timeout); 
    if (lg > 0) {
      *(ptr+lg) = 0; 
      if ((ptr_data=strstr(buffer,"\r\n\r\n")) != NULL) {
        crlf=1;
        *ptr_data = '\0';
      }
      ptr += lg;
    }
    else break;
  }

  do_log(LOG_DEBUG,"Proxy response is : \n%s\n", buffer);

  if (!crlf) {
       do_log(LOG_INFO,"Bad proxy answer, aborting");
       goto catch;
  }

  return sock; 

catch:

  close (sock);
  return -1;


}

#endif /* WITH_AUTH_NTLM */

static int wrap_basic_auth(tunnel *cfgtunnel, struct sockaddr_in sin, char *buffer) {

  int one = 1;
  int lg,crlf;
  char *ptr;
  int sock;
  char auth[255];
  char auth64[512];
  char *ptr_data;

  if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) )  < 0 ) {
     fprintf(stderr,"socket : %s\n", strerror(errno)); 
     return (-1) ;
  }
  
  setsockopt ( sock, 6, TCP_NODELAY , &one, sizeof (int) );

  if ( connect ( sock ,
                 ( struct sockaddr * ) &sin ,
                 sizeof ( sin ) ) == -1 )
  {
     fprintf(stderr, "connect: %s", strerror(errno)) ;
     return -1;
  }

  ioctl(sock, FIONBIO, &one);

  snprintf(auth, 255, "%s:%s", cfgtunnel->proxyusername, cfgtunnel->proxypass );
  base64_encode( auth,auth64,strlen(auth) );

  snprintf(buffer, 1024, 
    "CONNECT %s:%d HTTP/1.0\r\nProxy-Authorization: Basic %s\r\nUser-Agent: %s\r\n\r\n", 
          cfgtunnel->remotehost, cfgtunnel->port, auth64, cfgtunnel->useragent);
  do_log(LOG_INFO,"Sending Proxy Auth to proxy: \n%s",buffer);

  if (write_timeout(sock,buffer,strlen(buffer), cfgtunnel->network_timeout) <= 0) {
       do_log(LOG_INFO,"Error writing to proxy");
       goto catch; 
  }

  ptr = buffer;
  *ptr = '\0';
  crlf = 0;
  while (!crlf  && (ptr < buffer+1023))  {
    lg = read_timeout(sock,ptr,1023 - (ptr-buffer), cfgtunnel->network_timeout); 
    if (lg > 0) {
      *(ptr+lg) = 0; 
      if ((ptr_data=strstr(buffer,"\r\n\r\n")) != NULL) {
        crlf=1;
        *ptr_data = '\0';
      }
      ptr += lg;
    }
    else break;
  }

  do_log(LOG_DEBUG,"Proxy response is : \n%s\n", buffer);

  if (!crlf) {
       do_log(LOG_INFO,"Bad proxy answer, aborting");
       goto catch;
  }

  return sock; 

catch:

  close (sock);
  return -1;


}


void usage( char *progname) {

 fprintf(stderr,"Usage: %s [-h finalhost] [-p finalport] [-r proxyname] [-u proxyport] [-a user-proxy:pass-proxy] [-c 0/1] [-d 0/1] [-l logfile] [configfile]\n", progname);
 exit(2);
  
}

void do_log( int prio, char *format , ... )
{
  va_list ap;
  char buffer[2048];
  struct tm *local;
  time_t now;
  int len;

  if (cfgtunnel != NULL && !cfgtunnel->verbose && prio == LOG_DEBUG)
    return;
  if ((cfgtunnel->logfile == NULL) && cfgtunnel->daemon) {
    /* Syslog */
    va_start(ap, format);
    vsyslog ( prio, format, ap  );
    va_end(ap);
  }
  else {
     
    time(&now);
    local = localtime( &now );
    len = sprintf(buffer, "%02d:%02d:%02d " , 
            local->tm_hour,
            local->tm_min,
            local->tm_sec);

    va_start(ap, format);
    vsnprintf(buffer+len, 2045-len, format, ap);
    va_end(ap);

    strcat(buffer,"\n");
    fprintf(stderr, buffer);
    fflush(stderr);

  }
  
}


static int ssl_connect_timeout(SSL *ssl, 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;
  int errcode;

  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_connect(ssl);
    SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
    if (r > 0) {
      return r;
    }

    switch (errcode=SSL_get_error(ssl, r))
    {
    case SSL_ERROR_WANT_READ:
      prfds = &rfds;
      FD_ZERO(&rfds);
      FD_SET(rfd,&rfds);
      n = maxfd;
      break;
    case SSL_ERROR_WANT_WRITE:
      prfds = (fd_set *) 0;
      n = wfd + 1;
      break;
    default:
      /* some other error */
      switch (errcode) {
        case SSL_ERROR_SSL:
        case SSL_ERROR_SYSCALL:
           if ((errcode = ERR_get_error()) > 0) {
             do_log(LOG_NOTICE,"ssl_connect : %s", ERR_error_string(errcode, NULL));
           }
           else {
             do_log(LOG_NOTICE,"ssl_connect : %d", SSL_get_error(ssl, r));
           }
           break;
        default:
           do_log(LOG_NOTICE,"ssl_connect : %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_connect : 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;

}

int read_timeout(int s, char *buffer, int len, int tmo)
{

  int r=0;
  int rfd;
  int n;
  fd_set rfds;
  struct timeval tv;

  rfd = s;
  n = rfd + 1;

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

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

  /* number of descriptors that changes status */
  if ((n = select(n,&rfds,NULL, NULL, &tv)) > 0)
  {
    r = read(rfd, buffer, len);
    if (r > 0) {
      return r;
    }
    if (r <= 0 ) {
      do_log(LOG_NOTICE,"read error :%s", strerror(errno));
      return -2;
    }
  }

  do_log(LOG_NOTICE,"read : timeout");
  errno = EAGAIN;
  return -1;

}

int write_timeout(int s, char *buffer, int len, int tmo)
{

  int r=0;
  int wfd;
  int n;
  fd_set wfds;
  struct timeval tv;

  wfd = s;
  n = wfd + 1;

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

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

  if ((n = select(n, NULL ,&wfds, NULL,&tv)) > 0)
  {

    r = write(s, buffer, len);
    if (r > 0) {
      return r;
    }

    if (r <= 0 ) {
      do_log(LOG_NOTICE,"read error :%s", strerror(errno));
      return -2;
    }
  }

  do_log(LOG_NOTICE,"write : timeout");
  errno = EAGAIN;
  return -1;

}



#ifndef HAVE_OPENPTY

#define TTY_LETTERS "pqrstuvwxyzPQRST"

int
openpty(amaster, aslave, name, termp, winp)
	int *amaster, *aslave;
	char *name;
	struct termios *termp;
	struct winsize *winp;
{
	char line[] = "/dev/ptyXX";
	register const char *cp1, *cp2;
	register int master, slave, ttygid;
	struct group *gr;

	if ((gr = getgrnam("tty")) != NULL)
		ttygid = gr->gr_gid;
	else
		ttygid = -1;

	for (cp1 = TTY_LETTERS; *cp1; cp1++) {
		line[8] = *cp1;
		for (cp2 = "0123456789abcdef"; *cp2; cp2++) {
			line[9] = *cp2;
			line[5] = 'p';
			if ((master = open(line, O_RDWR, 0)) == -1) {
				if (errno == ENOENT)
					return (-1);	/* out of ptys */
			} else {
				line[5] = 't';
				(void) chown(line, getuid(), ttygid);
				(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
/* which is BSD only		(void) revoke(line); */
				if ((slave = open(line, O_RDWR, 0)) != -1) {
					*amaster = master;
					*aslave = slave;
					if (name)
						strcpy(name, line);
					if (termp)
						(void) tcsetattr(slave, 
							TCSAFLUSH, termp);
					if (winp)
						(void) ioctl(slave, TIOCSWINSZ, 
							(char *)winp);
					return (0);
				}
				(void) close(master);
			}
		}
	}
	errno = ENOENT;	/* out of ptys */
	return (-1);
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1