/* $Id: main.c,v 1.20 2004/03/14 13:15:31 thivillon Exp $ */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <zlib.h>
#include <utmp.h>
#include <time.h>
#include "../config.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_PTY_H
#include <pty.h>
#endif
#ifdef HAVE_LIBUTIL_H
#include <libutil.h>
#endif
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef HAVE_UTIL_H
#include <util.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
#ifndef HAVE_OPENPTY
extern int openpty(int *, int *, char *, struct termios *, struct winsize *);
#endif
#ifdef WITH_LIBWRAP
#include <tcpd.h>
#define TCPD_DAEMON_NAME "ssltunnel"
#endif
#include "ssltun.h"
#include "session.h"
extern void initsetproctitle(int argc, char **argv, char **envp);
#ifndef HAVE_SETPROCTITLE
extern void setproctitle(char *fmt, ...);
#endif
extern DH *ssl_callback_TmpDH(SSL *s, int is_export, int keylength);
int init_socket( int port );
void sig_chld_master (int status);
void sig_chld_slave (int status);
void sig_alrm_slave (int status);
void sig_int_slave (int status);
void sig_int_master (int status);
SSL *tcp_accept_fork ( );
int do_server_loop ( SSL *ssl );
int berr_exit(char *string);
SSL_CTX *initialize_ctx(char *calist, char *certfile, char *keyfile);
void destroy_ctx(SSL_CTX *ctx);
RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength);
void sockerror(char *string) ;
char *clientname;
ClientData *getclientdata(char *subject);
key_value_t *file_parse ( char *line , bool *err);
int read_config_file (char *filename);
static void x509_fingerprint (char *s, int l, X509 * cert);
void kill_then_wait_childs();
static int ssl_accept_timeout(SSL *ssl, int tmo);
static int ssl_write_timeout(SSL *ssl, char *buffer, int size, int tmo);
static int ssl_read_timeout(SSL *ssl, char *buffer, int size, int tmo);
#ifndef HAVE_OPENPTY
int openpty(int *amaster, int *aslave, char *name,
struct termios *termp, struct winsize *winp);
#endif
int socklisten[16];
int nbchild=0;
pid_t *child_ids;
int verbose=1;
unsigned long inbytes=0;
unsigned long outbytes=0;
BIO *bio_err=0;
SSL_CTX *ctx;
pid_t pppid = -1;
extern int wtmpfd;
int stopped = 0;
#ifdef WITH_LIBWRAP
/* We need these definitions, since libwrap declares these as extern */
int allow_severity = LOG_DAEMON;
int deny_severity = LOG_WARNING;
#endif
tunnel *cfgtunnel;
int main(int argc, char **argv, char **envp) {
SSL *ssl;
char configfilename[256];
char pidstring[32];
int dev_null;
int index;
int fpid;
struct stat lockdirs;
if (argc > 2) {
fprintf(stderr,"%s [configfile]\n",argv[0]);
exit(2);
}
cfgtunnel = malloc (sizeof (tunnel));
cfgtunnel->keyfile = KEYFILE;
cfgtunnel->certfile = CERTFILE;
cfgtunnel->cacertfile = CAFILE;
cfgtunnel->userfile = USERFILE;
cfgtunnel->maxclients = MAXCHILD;
cfgtunnel->network_timeout = DEFAULT_NETWORK_TIMEOUT;
cfgtunnel->listenaddr = NULL;
cfgtunnel->wtmp = WTMPFILE ;
cfgtunnel->pidfile = DEFAULT_PIDFILE;
cfgtunnel->port = DEFAULT_PORT;
cfgtunnel->lockdir = DEFAULT_LOCKDIR;
if (argc == 2 ) {
strncpy(configfilename , argv[1], 255);
}
else {
strncpy(configfilename , DEFAULT_CONFIG_FILENAME, 255);
}
if ( read_config_file( configfilename ) ) {
exit ( 1 );
}
openlog ( "pppserver" , LOG_PID, SYSLOG_FAC);
syslog (LOG_NOTICE, "pppserver version %s using %s",
VERSION, OPENSSL_VERSION_TEXT);
#ifdef HAVE_DAEMON
if (daemon(0,1) < 0) {
perror("daemon");
exit(1);
}
#else
switch (fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1)
return (-1);
(void)chdir("/");
#endif
initsetproctitle(argc, argv, envp);
setproctitle("initialising");
close (0);
close (1);
close (2);
dev_null = open("/dev/null", O_RDWR);
dup2 ( dev_null, 0);
dup2 ( dev_null, 1);
dup2 ( dev_null, 2);
if (dev_null > 2) close (dev_null);
ctx = initialize_ctx(cfgtunnel->cacertfile, cfgtunnel->certfile,
cfgtunnel->keyfile);
child_ids = malloc ( cfgtunnel->maxclients * sizeof (pid_t));
for ( index = 0; index < cfgtunnel->maxclients; index++)
*( child_ids + index) = -1;
signal ( SIGCHLD, &sig_chld_master ) ;
signal ( SIGINT, &sig_int_master ) ;
signal ( SIGTERM, &sig_int_master ) ;
init_socket (cfgtunnel->port);
/* Create pid file */
unlink(cfgtunnel->pidfile);
if ((fpid = open(cfgtunnel->pidfile, O_CREAT|O_WRONLY|O_EXCL,
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH )) < 0) {
syslog(LOG_ERR, "Unable to open %s: %s",
cfgtunnel->pidfile, strerror(errno));
exit (1);
}
snprintf(pidstring, 31 , "%d\n", (int) getpid());
write(fpid, pidstring, strlen(pidstring));
close(fpid);
/* Create/check lockdir */
mkdir(cfgtunnel->lockdir, S_IRWXU);
chmod(cfgtunnel->lockdir, S_IRWXU);
if ( lstat(cfgtunnel->lockdir, &lockdirs) ||
(lockdirs.st_mode & S_IFMT) != S_IFDIR ||
(lockdirs.st_uid != getuid()) ||
(lockdirs.st_mode & S_IRWXG) != 0 ||
(lockdirs.st_mode & S_IRWXO) != 0) {
syslog(LOG_ERR, "Unable to create %s securely",
cfgtunnel->lockdir);
}
/* Clean sessions */
clean_sessions(cfgtunnel->wtmp);
while (!stopped) {
if ( (ssl = tcp_accept_fork ( )) != NULL) {
do_server_loop ( ssl );
update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
exit(0);
}
}
/* Kill and wait child */
kill_then_wait_childs();
syslog ( LOG_NOTICE, "pppserver exiting");
unlink(cfgtunnel->pidfile);
return 0;
}
int init_socket( int port ) {
int sock= -1 ;
struct sockaddr_in sin;
int one = 1;
char *addrs;
char *uniqueaddr;
int index;
index = 0;
if (cfgtunnel->listenaddr != NULL) {
/* Bind all addresses given */
addrs = strdup ( cfgtunnel->listenaddr );
for ( uniqueaddr = strtok (addrs, " ,;");
uniqueaddr && index < 15;
uniqueaddr = strtok ( NULL, " ,;")) {
if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) ) < 0 ) {
syslog(LOG_ERR, "socket: %s", strerror(errno));
exit (-1) ;
}
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR,
&one, sizeof(int));
syslog ( LOG_INFO, "binding to %s", uniqueaddr );
memset( &sin, sizeof(struct sockaddr_in), 0 );
sin.sin_family = AF_INET;
sin.sin_port = ntohs ( port );
sin.sin_addr.s_addr = inet_addr ( uniqueaddr );
if ( ( bind ( sock, (struct sockaddr *) &sin,
sizeof(struct sockaddr_in) ))) {
syslog(LOG_ERR, "bind: %s", strerror(errno));
exit (-1);
}
if (listen (sock, 6)) {
syslog(LOG_ERR, "listen: %s", strerror(errno));
exit (-1);
}
if(ioctl(sock, FIONBIO, &one)<0) {
syslog(LOG_ERR, "ioctl: %s", strerror(errno));
exit (-1);
}
socklisten[index++] = sock;
}
free ( addrs );
}
else {
if ( ( sock = socket (AF_INET , SOCK_STREAM, 0 ) ) < 0 ) {
syslog(LOG_ERR, "socket: %s", strerror(errno));
exit (-1) ;
}
/* Bind INET_ANY */
memset( &sin, sizeof(struct sockaddr_in), 0 );
sin.sin_family = AF_INET;
sin.sin_port = ntohs ( port );
sin.sin_addr.s_addr = INADDR_ANY;
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR,
&one, sizeof(int));
if ( ( bind ( sock, (struct sockaddr *) &sin, sizeof(struct sockaddr_in) ))) {
syslog(LOG_ERR, "bind: %s", strerror(errno));
exit (-1);
}
if (listen (sock, 6)) {
syslog(LOG_ERR, "listen: %s", strerror(errno));
exit (-1);
}
if(ioctl(sock, FIONBIO, &one)<0) {
syslog(LOG_ERR, "ioctl: %s", strerror(errno));
exit (-1);
}
socklisten[index++] = sock;
}
socklisten[index]=-1;
return sock;
}
void sig_chld_master (int status) {
pid_t child_pid;
int index;
while ((child_pid=waitpid(-1, NULL, WNOHANG)) > 0) {
nbchild--;
for (index = 0; index < cfgtunnel->maxclients; index ++) {
if (child_pid == *(child_ids + index)) {
*(child_ids + index) = -1;
break;
}
}
}
signal ( SIGCHLD, &sig_chld_master ) ;
}
void sig_chld_slave (int status) {
pid_t child_pid;
while ((child_pid = waitpid(-1, NULL, WNOHANG)) > 0) {
nbchild --;
if (child_pid == pppid) {
pppid = -1;
stopped = 1;
}
}
signal ( SIGCHLD, &sig_chld_slave ) ;
}
SSL *tcp_accept_fork ( ) {
int sock=-1;
int newsock;
struct sockaddr_in peeraddr;
unsigned int len;
int pid;
X509 *xs;
char *cp;
SSL *ssl;
int r,num;
ClientData *myClient=NULL;
int valid_cert;
int tuyauIn[2];
int tuyauOut[2];
int masterfd,slavefd;
struct termios blah;
char buffer[SIZE];
char fingerprint[256];
fd_set rset;
int index;
int maxsocket;
int nd;
int one=1;
char *ptr;
char prot[PROT_SIZE];
char client_version[PROT_SIZE];
char fingerlockfile[1024];
struct flock locks;
int fdlock;
char *ptrsrc,*ptrdest;
#ifdef WITH_LIBWRAP
struct request_info request;
#endif
FD_ZERO(&rset);
maxsocket = 0;
for ( index=0;index < 15 && socklisten[index] >=0; index++ ) {
FD_SET(socklisten[index], &rset);
if ( socklisten[index] > maxsocket ) maxsocket = socklisten[index];
}
setproctitle("accepting connections");
while ( (nd = select ( maxsocket + 1, &rset, NULL, NULL, NULL )) < 0 &&
errno == EINTR && !stopped) ;
if (nd <=0 ) {
syslog(LOG_INFO, "select: %s", strerror(errno));
return NULL;
}
for ( index=0;index < 15 && socklisten[index] >=0 ; index++ ) {
if (FD_ISSET( socklisten[index], &rset )) {
sock = socklisten[index];
break;
}
}
len = sizeof (struct sockaddr_in);
newsock = accept( sock , (struct sockaddr *) &peeraddr,
&len);
if (newsock < 0 ) {
syslog(LOG_ERR, "accept: %s", strerror(errno));
return 0;
}
if (nbchild >= cfgtunnel->maxclients ) {
syslog(LOG_ERR, "too many childs !");
close( newsock );
return NULL;
}
#ifdef WITH_LIBWRAP
request_init(&request, RQ_DAEMON, TCPD_DAEMON_NAME , RQ_FILE, newsock , NULL);
fromhost(&request);
if (!hosts_access(&request)) {
syslog(LOG_WARNING,"Refused connection from %s\n", eval_client(&request));
close(newsock);
return NULL;
}
#endif
if (newsock > 0) {
pid = fork();
if (pid > 0) {
close(newsock);
for (index = 0; index < cfgtunnel->maxclients; index ++) {
if (*(child_ids + index) == - 1) {
*(child_ids + index) = pid;
break;
}
}
nbchild++;
return NULL;
}
else if (pid == 0 ) {
for ( index=0;index < 15 && socklisten[index] >=0 ; index++ ) {
close(socklisten[index]);
}
stopped = 0;
signal ( SIGCHLD, &sig_chld_slave ) ;
signal(SIGINT, &sig_int_slave);
signal(SIGALRM, &sig_alrm_slave);
signal(SIGTERM, &sig_int_slave);
setproctitle("accepting connection from %s",
inet_ntoa(peeraddr.sin_addr));
syslog(LOG_NOTICE, "Accepting connection from %s",
inet_ntoa(peeraddr.sin_addr));
ssl=SSL_new(ctx);
if (ssl == NULL) {
close(newsock);
berr_exit("SSL_new");
}
/* Set non block */
if(ioctl(newsock, FIONBIO, &one)<0) {
syslog(LOG_ERR, "ioctl: %s", strerror(errno));
exit (-1);
}
setsockopt( newsock, 6, TCP_NODELAY,
&one, sizeof(int));
SSL_set_fd(ssl,newsock);
SSL_set_accept_state(ssl);
setproctitle("SSL starting with %s",
inet_ntoa(peeraddr.sin_addr));
r= ssl_accept_timeout(ssl, cfgtunnel->network_timeout);
if (r <= 0) {
close(newsock);
exit(0);
}
/* Check Cert */
valid_cert = 0;
xs = NULL;
if ((xs = SSL_get_peer_certificate(ssl)) == NULL) {
syslog(LOG_NOTICE, "No valid cert !");
goto catch;
}
/* Check subject, look into database */
cp = X509_NAME_oneline(X509_get_subject_name(xs), NULL, 0);
if (cp!=NULL) {
syslog(LOG_NOTICE, "Client certificate : %s\n", cp);
myClient = getclientdata ( cp );
valid_cert = ( myClient != NULL && myClient->exec != NULL) ;
free(cp);
}
if (!valid_cert) {
syslog(LOG_NOTICE, "No valid data for this user");
if (xs!=NULL) X509_free(xs);
goto catch;
}
/* Check fingerprint */
memset(fingerprint, 0, 256);
x509_fingerprint (fingerprint, 255, xs);
syslog(LOG_INFO, "Certificate fingerprint: %s", fingerprint);
if (myClient && myClient->fingerprint &&
strcmp(fingerprint,myClient->fingerprint)) {
syslog(LOG_NOTICE, "Invalid fingerprint");
X509_free(xs);
goto catch;
}
/* Check issuer */
cp = X509_NAME_oneline(X509_get_issuer_name(xs), NULL, 0);
if (cp!=NULL) {
syslog(LOG_INFO, "Issuer : %s", cp);
if (( myClient->issuer != NULL) &&
strcmp(cp, myClient->issuer)) {
syslog(LOG_NOTICE, "Invalid issuer");
free(cp);
X509_free(xs);
goto catch;
}
free(cp);
}
else {
syslog(LOG_NOTICE, "No Issuer !");
X509_free(xs);
goto catch;
}
X509_free(xs);
/* Create lockfile with cert fingerprint */
memset(&locks,0,sizeof(struct flock));
locks.l_type = F_WRLCK;
locks.l_whence = SEEK_SET;
strcpy(fingerlockfile, cfgtunnel->lockdir);
strcat(fingerlockfile,"/");
ptrdest = fingerlockfile + strlen(fingerlockfile);
ptrsrc = fingerprint;
while (*ptrsrc && ptrdest < (fingerlockfile + 1022)) {
if (*ptrsrc != ':' && *ptrsrc != '/') {
*ptrdest = *ptrsrc;
ptrdest++;
}
ptrsrc++;
}
*ptrdest = '\0';
if (((fdlock = open(fingerlockfile, O_WRONLY|O_CREAT|O_TRUNC,
S_IRUSR|S_IWUSR)) < 0 ) ||
(fcntl(fdlock, F_SETLK, &locks) == -1) ) {
syslog(LOG_NOTICE, "Unable to lock %s", fingerlockfile);
goto catch;
}
update_wtmp(cfgtunnel->wtmp, myClient->subject, getpid(),
inet_ntoa(peeraddr.sin_addr) );
if (myClient->gid) {
syslog(LOG_INFO, "setgid to %d", myClient->gid);
if (setgid(myClient->gid)) {
syslog(LOG_ERR, "Unable to setgid %d : %s", myClient->gid,
strerror(errno));
goto catch;
}
if (setgroups( 1, &myClient->gid)) {
syslog(LOG_ERR, "Unable to setgroups %d : %s", myClient->gid,
strerror(errno));
goto catch;
}
}
if (myClient->uid) {
syslog(LOG_INFO, "setuid to %d", myClient->uid);
if (setuid(myClient->uid)) {
syslog(LOG_ERR, "Unable to setuid %d : %s", myClient->uid,
strerror(errno));
goto catch;
}
}
SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
/* Send and Receive Banner */
sprintf(buffer, SERVER_BANNER, VERSION);
num=ssl_write_timeout(ssl, buffer, strlen(buffer), cfgtunnel->network_timeout );
if (num != strlen(buffer)) {
syslog(LOG_INFO, "Error writing banner");
goto catch;
}
memset(buffer,0,SIZE);
num=ssl_read_timeout(ssl, buffer, SIZE-1, cfgtunnel->network_timeout );
if (num <= 0) {
syslog(LOG_INFO, "Error reading banner");
goto catch;
}
if ((ptr=strstr(buffer,"SSL-TUNNEL/")) != NULL) {
num = 0;
ptr += 11;
while (*ptr && *ptr != ' ' && *ptr != '\r' &&
*ptr != '\n' && num < PROT_SIZE -1) {
client_version[num]= *ptr;
num++; ptr ++;
}
client_version[num] = 0;
syslog(LOG_INFO, "Client version : %s", client_version);
}
if ((ptr=strstr(buffer,"PROT/"))!=NULL) {
num = 0;
ptr += 5;
while (*ptr && *ptr != '\r' && *ptr != '\n' && num < PROT_SIZE -1) {
prot[num]= *ptr;
num++; ptr ++;
}
prot[num] = 0;
syslog(LOG_INFO, "Client Protocol version : %s", prot);
}
else {
syslog(LOG_ERR, "No valid banner");
goto catch;
}
if (myClient->pty) {
memset(&blah,0,sizeof(blah));
blah.c_cc[VMIN]=1;
blah.c_cc[VTIME]=0;
blah.c_cflag |= B9600;
if (openpty(&masterfd,&slavefd,NULL,&blah,NULL)) {
syslog(LOG_ERR, "openpty: %s", strerror(errno));
goto catch;
};
tuyauIn[0]= slavefd;
tuyauOut[0]= masterfd;
tuyauIn[1]= masterfd;
tuyauOut[1]= slavefd;
}
else {
if (pipe((int *) &tuyauOut) != 0) goto catch;
if (pipe((int *) &tuyauIn) != 0) goto catch;
}
nbchild = 0;
if ((pppid = fork()) !=0 ) {
nbchild++;
close(0);
close(1);
dup2(tuyauOut[0],0);
dup2(tuyauIn[1],1);
return ssl;
}
else if (pppid == 0) {
if (wtmpfd != -1) close(wtmpfd);
close(fdlock);
close(0);
close(1);
close(newsock);
dup2(tuyauIn[0],0);
dup2(tuyauOut[1],1);
syslog (LOG_INFO, "Exec: %s", myClient->exec );
if (execv ( myClient->exec, myClient->args )) {
syslog(LOG_ERR, "execv: %s", strerror(errno));
exit(1);
}
}
else {
syslog (LOG_ERR, "fork: %s", strerror(errno));
}
catch:
update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
SSL_free(ssl);
ERR_remove_state(0);
close(newsock);
exit ( 0 );
}
else {
syslog(LOG_ERR, "fork: %s", strerror(errno));
close(newsock);
return NULL;
}
}
return NULL;
}
int berr_exit(char *string) {
syslog(LOG_ERR, "berr_exit: %s", string);
exit(0);
}
SSL_CTX *initialize_ctx(char *calist, char *certfile,
char *keyfile) {
SSL_METHOD *meth;
SSL_CTX *ctx;
if(!bio_err){
/* Global system initialization*/
SSL_library_init();
SSL_load_error_strings();
/* An error write context */
bio_err=BIO_new_fp(stderr,BIO_NOCLOSE);
}
/* Create our context*/
meth=TLSv1_method();
SSL_COMP_add_compression_method(0x42, COMP_rle());
ctx=SSL_CTX_new(meth);
/* Load our keys and certificates*/
if(!(SSL_CTX_use_certificate_chain_file(ctx, certfile)))
berr_exit("Can't read certificate file");
if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)))
berr_exit("Can't read key file");
/* Load the CAs we trust*/
if(!(SSL_CTX_load_verify_locations(ctx, calist ,0)))
berr_exit("Can't read CA list");
SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,0);
SSL_CTX_set_tmp_rsa_callback(ctx, &tmp_rsa_callback);
/* Prepare for PFS */
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH);
return ctx;
}
void destroy_ctx(SSL_CTX *ctx)
{
SSL_CTX_free(ctx);
}
RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength)
{
RSA *rsa_tmp=NULL;
rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL);
return(rsa_tmp);
}
int do_server_loop ( SSL *ssl ) {
fd_set rd_set, wr_set;
int num, fdno, ssl_fd, ssl_bytes, sock_bytes, retval;
char sock_buff[2 * SIZE], ssl_buff[2 * SIZE];
int sock_ptr, ssl_ptr, sock_open, ssl_open;
unsigned long l;
int try_kill;
struct timeval tmo;
ssl_fd=SSL_get_fd(ssl);
fdno=ssl_fd+1;
sock_ptr=0;
ssl_ptr=0;
sock_open=1;
ssl_open=1;
sock_bytes=0;
ssl_bytes=0;
l=1; /* ON */
if(ioctl(0, FIONBIO, &l)<0)
sockerror("ioctlsocket (sock)");
if(ioctl(1, FIONBIO, &l)<0)
sockerror("ioctlsocket (sock)");
if(ioctl(ssl_fd, FIONBIO, &l)<0)
sockerror("ioctlsocket (ssl)");
while((sock_open||sock_ptr) && (ssl_open||ssl_ptr) && !stopped) {
FD_ZERO(&rd_set);
if(sock_open && sock_ptr<SIZE) /* can read from socket */
FD_SET(0, &rd_set);
if(ssl_open && ssl_ptr<SIZE) /* can read from SSL */
FD_SET(ssl_fd, &rd_set);
FD_ZERO(&wr_set);
if(sock_open && ssl_ptr) /* can write to socket */
FD_SET(1, &wr_set);
if(ssl_open && sock_ptr) /* can write to SSL */
FD_SET(ssl_fd, &wr_set);
if(select(fdno, &rd_set, &wr_set, NULL, NULL)<0 && errno!=EINTR) {
sockerror("select");
goto error;
}
if(sock_open && FD_ISSET(0, &rd_set)) {
num=read(0, sock_buff+sock_ptr, SIZE-sock_ptr);
if (num < 0 && errno == EAGAIN) {
/* Interrupt */
}
else if (num < 0) {
sockerror("read");
sock_open = 0;
} else if (num > 0) {
sock_ptr += num;
outbytes+=num;
} else { /* close */
syslog(LOG_INFO, "stdin closed on read");
sock_open = 0;
}
}
if(ssl_open && FD_ISSET(ssl_fd, &rd_set)) {
num=SSL_read(ssl, ssl_buff+ssl_ptr, SIZE-ssl_ptr);
switch(SSL_get_error(ssl, num)) {
case SSL_ERROR_NONE:
ssl_ptr+=num;
break;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_X509_LOOKUP:
break;
case SSL_ERROR_SYSCALL:
if(num && errno != EINTR) { /* not EOF */
sockerror("SSL_read (socket)");
goto error;
}
case SSL_ERROR_ZERO_RETURN:
syslog(LOG_INFO, "ssl closed on read");
ssl_open=0;
break;
case SSL_ERROR_SSL:
syslog(LOG_INFO, "ssl error on read");
goto error;
}
}
if(sock_open && FD_ISSET(1, &wr_set)) {
num=write(1, ssl_buff, ssl_ptr);
if(num<0) {
sockerror("write");
goto error;
}
if(num) {
memcpy(ssl_buff, ssl_buff+num, ssl_ptr-num);
ssl_ptr-=num;
sock_bytes+=num;
inbytes+=num;
} else { /* close */
syslog(LOG_INFO, "socket closed on write");
sock_open=0;
}
}
if(ssl_open && FD_ISSET(ssl_fd, &wr_set)) {
num=SSL_write(ssl, sock_buff, sock_ptr);
switch(SSL_get_error(ssl, num)) {
case SSL_ERROR_NONE:
memcpy(sock_buff, sock_buff+num, sock_ptr-num);
sock_ptr-=num;
ssl_bytes+=num;
break;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_X509_LOOKUP:
break;
case SSL_ERROR_SYSCALL:
if(num) { /* not EOF */
sockerror("SSL_write (socket)");
goto error;
}
case SSL_ERROR_ZERO_RETURN:
syslog(LOG_INFO, "SSL closed on write");
ssl_open=0;
break;
case SSL_ERROR_SSL:
syslog(LOG_INFO, "SSL error on write");
goto error;
}
}
}
retval=0;
syslog(LOG_NOTICE,"terminating normally");
retval = 0;
goto done;
error:
syslog(LOG_NOTICE,"terminating abnormally");
retval=-1;
done:
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
SSL_free(ssl);
ERR_remove_state(0);
close(ssl_fd);
close(0);
close(1);
syslog(LOG_NOTICE,"In Bytes: %ld, Out Bytes: %ld", inbytes, outbytes);
setproctitle("terminating");
try_kill = 0;
while (pppid != -1) {
syslog(LOG_NOTICE,"killing child");
kill ( pppid, try_kill > 3 ? SIGKILL : SIGTERM );
try_kill ++;
tmo.tv_sec = 10;
tmo.tv_usec = 0;
select (0, NULL, NULL, NULL, &tmo);
}
syslog(LOG_NOTICE,"Child has exited");
return retval;
}
void sockerror(char *string)
{
syslog(LOG_WARNING, "sock error %s", string);
}
ClientData *getclientdata (char *subject) {
FILE *userfile;
char line[256];
bool err=false;
ClientData *userdata=NULL;
char *ptrblank;
int index;
userfile = fopen (cfgtunnel->userfile, "r");
if (userfile == NULL) {
syslog(LOG_ERR, "Unable to open user file: %s", strerror(errno));
return NULL;
}
while (!err && (fgets( line, 255, userfile) != NULL)) {
key_value_t *key_value ;
key_value = file_parse ( line , &err) ;
if (err == true)
{
fclose ( userfile ) ;
syslog(LOG_ERR, "Unable to parse user file");
return 0 ;
}
else if (key_value == NULL) {
/* white or commenet line : skip it */
continue;
}
if (!strcasecmp ( key_value->key, "user" )) {
if ( !strcasecmp(key_value->value, subject) ) {
/* Good user */
userdata = malloc (sizeof(ClientData));
userdata->subject = strdup ( subject );
userdata->exec = NULL;
userdata->fingerprint = NULL;
userdata->issuer = NULL;
userdata->args[0] = NULL;
userdata->pty = 0;
userdata->uid = 0;
userdata->gid = 0;
}
else if (userdata != NULL) break;
}
if (!strcasecmp ( key_value->key, "command") && userdata != NULL) {
userdata->exec = strdup(key_value->value);
userdata->args[0] = strdup(key_value->value);
userdata->args[1] = NULL;
}
if (!strcasecmp ( key_value->key, "pty") && userdata != NULL) {
userdata->pty = atoi(key_value->value);
}
if (!strcasecmp ( key_value->key, "args") && userdata != NULL) {
index = 0;
while (index < MAXARGS -1 && userdata->args[index] != NULL)
index++;
for (ptrblank = strtok(key_value->value," \t");
ptrblank;
ptrblank = strtok(NULL," \t")) {
userdata->args[index++] = strdup(ptrblank);
userdata->args[index] = NULL;
}
}
if ( ! strcasecmp( key_value->key , "uid") && userdata != NULL) {
userdata->uid = (uid_t) atoi(key_value->value);
}
if ( ! strcasecmp( key_value->key , "gid") && userdata != NULL) {
userdata->gid = (gid_t) atoi(key_value->value);
}
if (!strcasecmp ( key_value->key, "fingerprint") && userdata != NULL) {
userdata->fingerprint = strdup(key_value->value);
}
if (!strcasecmp ( key_value->key, "issuer") && userdata != NULL) {
userdata->issuer = strdup(key_value->value);
}
}
fclose ( userfile ) ;
return userdata;
}
key_value_t *file_parse ( char *line , bool *err)
{
char *tmp , *key ;
key_value_t *key_value ;
*err = false;
key_value = ( key_value_t * ) malloc ( sizeof ( key_value_t ) ) ;
if ( key_value == NULL )
{
*err = true;
return NULL ;
}
/* strip comment or final \n */
for ( tmp = line ; *tmp != '\0' ; tmp ++ )
{
if ( *tmp == '#' || *tmp == '\n' )
{
*tmp = '\0' ;
break ;
}
}
/* strip trailing whitespaces */
while ( tmp != line )
{
tmp -- ;
if ( *tmp == ' ' || *tmp == '\t' )
{
*tmp = '\0' ;
}
else
{
break ;
}
}
/* find the key */
tmp = line ;
while ( *tmp == ' ' || *tmp == '\t' )
{
tmp ++ ;
}
if ( *tmp == '\0' ) /* premature end of line */
{
return NULL ;
}
/* strip the key */
key = tmp ;
while ( *tmp != ' ' && *tmp != '\t' && *tmp != '\0' )
{
tmp ++ ;
}
if ( *tmp == '\0' ) /* no value */
{
key_value->key = strdup ( key ) ;
if ( key_value->key == NULL )
{
*err = true;
return NULL ;
}
key_value->value = NULL ;
return key_value ;
}
*tmp = '\0' ;
/* copy the key */
key_value->key = strdup ( key ) ;
if ( key_value->key == NULL )
{
*err = true;
return NULL ;
}
/* strip the blanks */
tmp ++ ;
while ( *tmp == ' ' || *tmp == '\t' )
{
tmp ++ ;
}
if ( *tmp == '\0' ) /* premature end of line */
{
key_value->value = NULL ;
return key_value ;
}
/* copy the value */
key_value->value = strdup ( tmp ) ;
if ( key_value->value == NULL )
{
*err = true;
return NULL ;
}
return key_value ;
}
int read_config_file (char *filename) {
FILE *fconfig;
bool err=0;
char line[256];
fconfig = fopen (filename, "r");
if (fconfig == NULL) {
perror(filename);
return -1;
}
while (!err && (fgets( line, 255, fconfig) != NULL)) {
key_value_t *key_value ;
key_value = file_parse ( line , &err) ;
if (err == true)
{
fclose ( fconfig ) ;
fprintf (stderr, "Unable to parse config file(%s)\n", filename );
return 0 ;
}
else if (key_value == NULL) {
/* white or commenet line : skip it */
continue;
}
if ( ! strcmp( key_value->key , "keyfile") ) {
cfgtunnel->keyfile = key_value->value;
}
else if ( ! strcmp( key_value->key , "certfile") ) {
cfgtunnel->certfile = key_value->value;
}
else if ( ! strcmp( key_value->key , "cacertfile") ) {
cfgtunnel->cacertfile = key_value->value;
}
else if ( ! strcmp( key_value->key , "userfile") ) {
cfgtunnel->userfile = key_value->value;
}
else if ( ! strcmp( key_value->key , "maxclients") ) {
cfgtunnel->maxclients = atoi(key_value->value);
}
else if ( ! strcmp( key_value->key , "port") ) {
cfgtunnel->port = atoi(key_value->value);
}
else if ( ! strcmp( key_value->key , "timeout") ) {
cfgtunnel->network_timeout = atoi(key_value->value);
}
else if ( ! strcmp( key_value->key , "listenaddr") ) {
cfgtunnel->listenaddr = key_value->value;
}
else if ( ! strcmp( key_value->key , "wtmp") ) {
cfgtunnel->wtmp = key_value->value;
}
else if ( ! strcmp( key_value->key , "pidfile") ) {
cfgtunnel->pidfile = key_value->value;
}
else if ( ! strcmp( key_value->key , "lockdir") ) {
cfgtunnel->lockdir = key_value->value;
}
}
fclose (fconfig);
return err;
}
static void x509_fingerprint (char *s, int l, X509 * cert)
{
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int n;
int j;
if (!X509_digest (cert, EVP_md5 (), md, &n))
{
snprintf (s, l, "[unable to calculate]");
}
else
{
for (j = 0; j < (int) n; j++)
{
char ch[8];
snprintf (ch, 8, "%02X%c", md[j], (j +1 == (int) n ? '\0': ':' ));
strncat (s, ch, l);
}
}
}
/* Signal handler for forked process */
void sig_int_slave ( int status ) {
/* Kill ppp */
if (pppid != -1) kill ( pppid, SIGTERM);
else {
update_wtmp(cfgtunnel->wtmp, "", getpid(), "");
exit(0);
}
signal(SIGINT, &sig_int_slave);
signal(SIGTERM, &sig_int_slave);
}
void sig_alrm_slave (int status ) {
stopped = 1;
signal(SIGALRM, &sig_alrm_slave);
}
void sig_int_master ( int status ) {
stopped = 1;
signal ( SIGINT, &sig_int_master ) ;
signal ( SIGTERM, &sig_int_master ) ;
}
void kill_then_wait_childs () {
int index;
for (index = 0; index < cfgtunnel->maxclients; index ++) {
if (*(child_ids + index) != - 1) {
syslog( LOG_DEBUG, "Killing %d", *(child_ids + index));
kill ( *(child_ids + index), SIGTERM);
}
}
while (nbchild) {
select ( 0 , NULL, NULL, NULL, NULL );
}
}
static int ssl_accept_timeout(SSL *ssl, int tmo)
{
int r=0;
int rfd, wfd;
int n,maxfd;
fd_set rfds, wfds;
fd_set *pwfds;
struct timeval tv;
long end;
int t;
long errcode;
rfd = SSL_get_fd(ssl);
wfd = SSL_get_fd(ssl);
n = rfd + 1;
maxfd = (rfd > wfd ? rfd : wfd) + 1;
pwfds = (fd_set *) NULL;
end = tmo + time( NULL );
tv.tv_sec = tmo;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(rfd,&rfds);
/* number of descriptors that changes status */
while (0 < (n = select(n,&rfds,pwfds,(fd_set *) 0,&tv)))
{
r = SSL_accept(ssl);
if (r > 0) {
return r;
}
switch (errcode=SSL_get_error(ssl, r))
{
case SSL_ERROR_WANT_READ:
pwfds = (fd_set *) 0;
n = rfd + 1;
break;
case SSL_ERROR_WANT_WRITE:
pwfds = &wfds;
FD_ZERO(&wfds);
FD_SET(wfd,&wfds);
n = maxfd;
break;
default:
switch (errcode) {
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
if ((errcode = ERR_get_error()) > 0) {
syslog(LOG_NOTICE,"ssl_accept : %s", ERR_error_string(errcode, NULL));
}
else {
syslog(LOG_NOTICE,"ssl_accept : %d", SSL_get_error(ssl, r));
}
break;
default:
syslog(LOG_NOTICE,"ssl_accept : %d", SSL_get_error(ssl, r));
break;
}
return -2;
}
if ((t = end - time( NULL )) < 0) break;
tv.tv_sec = t;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(rfd,&rfds);
}
syslog(LOG_NOTICE,"ssl_accept : timeout");
return -1;
}
static int ssl_write_timeout(SSL *ssl, char *buffer, int len, int tmo)
{
int r=0;
int rfd, wfd;
int n,maxfd;
fd_set rfds, wfds;
fd_set *prfds;
struct timeval tv;
long end;
int t;
rfd = SSL_get_fd(ssl);
wfd = SSL_get_fd(ssl);
n = rfd + 1;
maxfd = (rfd > wfd ? rfd : wfd) + 1;
prfds = (fd_set *) NULL;
end = tmo + time( NULL );
tv.tv_sec = tmo;
tv.tv_usec = 0;
FD_ZERO(&wfds);
FD_SET(wfd,&wfds);
/* number of descriptors that changes status */
while (0 < (n = select(n,prfds,&wfds,(fd_set *) 0,&tv)))
{
r = SSL_write(ssl, buffer, len);
if (r > 0) {
return r;
}
switch (SSL_get_error(ssl, r))
{
case SSL_ERROR_WANT_READ:
prfds = (fd_set *) &rfds;
FD_ZERO(&rfds);
FD_SET(rfd,&rfds);
n = maxfd;
break;
case SSL_ERROR_WANT_WRITE:
prfds = (fd_set *) NULL;
n = wfd + 1;
break;
default:
/* some other error */
syslog(LOG_NOTICE,"ssl_write : %d", SSL_get_error(ssl, r));
return -2;
}
if ((t = end - time( NULL )) < 0) break;
tv.tv_sec = t;
tv.tv_usec = 0;
FD_ZERO(&wfds);
FD_SET(wfd,&wfds);
}
syslog(LOG_NOTICE,"ssl_write : timeout");
return -1;
}
static int ssl_read_timeout(SSL *ssl, char *buffer, int len, int tmo)
{
int r=0;
int rfd, wfd;
int n,maxfd;
fd_set rfds, wfds;
fd_set *pwfds;
struct timeval tv;
long end;
int t;
rfd = SSL_get_fd(ssl);
wfd = SSL_get_fd(ssl);
n = rfd + 1;
maxfd = (rfd > wfd ? rfd : wfd) + 1;
pwfds = (fd_set *) NULL;
end = tmo + time( NULL );
tv.tv_sec = tmo;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(rfd,&rfds);
/* number of descriptors that changes status */
while (0 < (n = select(n,&rfds,pwfds,(fd_set *) 0,&tv)))
{
r = SSL_read(ssl, buffer, len);
if (r > 0) {
return r;
}
switch (SSL_get_error(ssl, r))
{
case SSL_ERROR_WANT_READ:
pwfds = (fd_set *) 0;
n = rfd + 1;
break;
case SSL_ERROR_WANT_WRITE:
pwfds = &wfds;
FD_ZERO(&wfds);
FD_SET(wfd,&wfds);
n = maxfd;
break;
default:
/* some other error */
syslog(LOG_NOTICE,"ssl_read : %d", SSL_get_error(ssl, r));
return -2;
}
if ((t = end - time( NULL )) < 0) break;
tv.tv_sec = t;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(rfd,&rfds);
}
syslog(LOG_NOTICE,"ssl_read : timeout");
return -1;
}
/*# vim:set expandtab:set syntax=c:*/
syntax highlighted by Code2HTML, v. 0.9.1