/*
$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