/* $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_PTY_H #include #endif #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_SYS_FILE_H #include #endif #ifdef HAVE_ZLIB_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_SYS_FILIO_H #include #endif #ifdef HAVE_UTIL_H #include #endif #ifdef HAVE_STDARG_H #include #else #ifdef HAVE_VARARGS_H #include #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 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