/* jumpgate -- jumpgate.c
 * Patroklos G. Argyroudis <argp@cs.tcd.ie>
 *
 * $Id: jumpgate.c,v 1.26 2002/10/17 08:59:47 argp Exp $
 */
 
#include "jumpgate.h"

int
main(int argc, char *argv[])
{
 int lfd, lport, c;
 const int val = 1;			/* flag for REUSEADDR */
 pid_t chldpid;
 socklen_t clen;
 in_addr_t bind_addr;
 struct hostent *lhost;
 struct hostent *h_ent;
 struct servent *s_ent;
 struct jump rec;
 struct sockaddr_in laddr, caddr;
 int syslog_flag = 0;			/* default is not to use syslog */
 int interactive = 0;			/* default is non-interactive mode */
 int backlog = BACKLOG;			/* maximum backlog queue */
 
 rec.latency_flag = rec.log_flag = rec.rport = lport = 0;
 rec.hostname = NULL;
 bind_addr = htonl(INADDR_ANY);
 opterr = 0;
 
 while((c = getopt(argc, argv, "hsvil:r:a:b:f:c:L:")) != -1)
 {
  switch(c)
  {
   case 'h':
   	help(argv[0]);
   	exit(EXIT_SUCCESS);
   case 's':
   	syslog_flag = 1;
   	break;
   case 'v':
   	fprintf(stderr, "jumpgate v%s by Patroklos G. Argyroudis <argp@cs.tcd.ie>\n", VERSION);
   	exit(EXIT_SUCCESS);
   case 'i':
   	interactive = 1;
   	break;
   case 'l':
   	if((s_ent = getservbyname(optarg, "tcp")))
   	{
   	 lport = ntohs(s_ent->s_port);
   	}
   	else
   	{
   	 char *end_ptr;
   	 
   	 lport = strtol(optarg, &end_ptr, 10);
   	 if(!*optarg || *end_ptr || lport < 1 || lport > 65535)
   	 {
   	  error_exit(0, "error: unknown or invalid local port");
   	 }
   	}
   	
   	if(lport <= 1024 && geteuid() != 0)
   	{
   	 error_exit(0, "error: you're trying to bind a well-known port without having root privileges");
   	}
   	
   	break;
   case 'r':
   	if((s_ent = getservbyname(optarg, "tcp")))
   	{
   	 rec.rport = ntohs(s_ent->s_port);
   	}
   	else
   	{
   	 char *end_ptr;
   	 
   	 rec.rport = strtol(optarg, &end_ptr, 10);
   	 if(!*optarg || *end_ptr || rec.rport < 1 || rec.rport > 65535)
   	 {
   	  error_exit(0, "error: unknown or invalid remote port");
   	 }
   	}
   	
   	break;
   case 'a':
   	rec.hostname = strdup(optarg);
   	if((rec.hostaddr = inet_addr(optarg)) == INADDR_NONE)
   	{
   	 if((h_ent = gethostbyname(optarg)) != NULL)
   	 {
   	  memcpy(&rec.hostaddr, h_ent->h_addr, sizeof(in_addr_t));
   	 }
   	 else
   	 {
   	  error_exit(0, "error: error resolving remote hostname");
   	 }
   	}
   	
   	break;
   case 'b':
   	if((bind_addr = inet_addr(optarg)) == INADDR_NONE)
   	{
   	 if((h_ent = gethostbyname(optarg)) != NULL)
   	 {
   	  memcpy(&bind_addr, h_ent->h_addr, sizeof(in_addr_t));
   	 }
   	 else
   	 {
   	  error_exit(0, "error: error resolving hostname for local binding");
   	 }
   	}
   	
   	break; 
   case 'f':
   	rec.log_flag = 1;
   	rec.logfile = optarg;
   	break;
   case 'c':
   	backlog = atoi(optarg);
   	break;
   case 'L':
   	rec.latency = (useconds_t)atoi(optarg);
   	
   	if(rec.latency < 0 || rec.latency > 999999)
   	{
   	 error_exit(0, "error: invalid latency");
   	}
   	
   	rec.latency_flag = 1;
   	break;
   default:
   	usage(argv[0]);
   	exit(EXIT_SUCCESS);
  }
 }
 
 /* check the users input */
 if(lport == 0 && rec.rport == 0 && rec.hostname == NULL && interactive == 0)
 {
  usage(argv[0]);
  exit(EXIT_SUCCESS);
 }
 
 if(lport == 0)
 {
  error_exit(0, "error: you specified no local port");
 }

 if(rec.rport == 0 && interactive == 0)
 {
  error_exit(0, "error: you specified no remote port");
 }
  
 if(rec.hostname == NULL && interactive == 0)
 {
  error_exit(0, "error: you specified no remote host");
 }
   
 if(daemonize(".") == -1)
 {
  error_exit(1, "fork error");
 }
  
 openlog("jumpgate", LOG_PID, LOG_USER);
 
 if((lfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 {
  error_exit(1, "socket error");
 }
 
 bzero(&laddr, sizeof(laddr));
 laddr.sin_family = AF_INET;
 laddr.sin_addr.s_addr = bind_addr;
 laddr.sin_port = htons(lport);
 
 /* if you use linux beware of the SO_REUSEADDR vulnerability,
  * although I think new linux versions fix this problem
  */
 if(setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
 {
  error_exit(1, "setsockopt error");
 }
 
 if(bind(lfd, (struct sockaddr *)&laddr, sizeof(laddr)) < 0)
 {
  error_exit(1, "bind error");
 }

 if(listen(lfd, backlog) < 0)
 {
  error_exit(1, "listen error");
 }

#ifdef SOLARIS
 if(sigset(SIGCHLD, chld_reap) == SIG_ERR)
#else 
 if(signal(SIGCHLD, chld_reap) == SIG_ERR)
#endif /* SOLARIS */
 {
  error_exit(1, "signal error");
 }
 
 for(; ;)
 {
  clen = sizeof(caddr);
  
  if((rec.cfd = accept(lfd, (struct sockaddr *)&caddr, &clen)) < 0)
  {
   if(errno == EINTR || errno == ECONNABORTED)
   {
    continue;
   }
   else
   {
    error_log(1, 1, LOG_ERR, "accept error");
   }
  }
  
  if((chldpid = fork()) == -1)
  {
   error_log(1, 1, LOG_ERR, "fork error");
  }
  
  if(chldpid == 0)
  {
   if((close(lfd)) == -1)
   {
    error_log(1, 1, LOG_ERR, "close error");
   }
   
   if(interactive == 1)
   {
    query(&rec);
   }
    
   tcp_jumpgate(&rec);
   
   if(syslog_flag == 1)
   {
    syslog(LOG_NOTICE, "%s jumped to %s:%d\n", inet_ntoa(caddr.sin_addr), rec.hostname, rec.rport);
   }

   exit(EXIT_SUCCESS);
  }
  
  if((close(rec.cfd)) == -1)
  {
   error_log(1, 1, LOG_ERR, "close error");
  }
 }
 
 return(EXIT_SUCCESS);
}

void
usage(char *name)
{
 fprintf(stderr, "usage: %s [-hsvi] [-b <local host or IP address>] [-l <local port #>]\n", name);
 fprintf(stderr, "	[-r <remote port #>] [-a <remote host or IP address>] [-f <filename>]\n");
 fprintf(stderr, "	[-c <number of connections>] [-L <microseconds>]\n");
}

void
help(char *name)
{
 fprintf(stderr, "\njumpgate v%s by Patroklos G. Argyroudis <argp@cs.tcd.ie>\n\n", VERSION);
 fprintf(stderr, "usage: %s [-hsvi] [-b <local host or IP address>] [-l <local port #>]\n", name);
 fprintf(stderr, "	[-r <remote port #>] [-a <remote host or IP address>] [-f <filename>]\n");
 fprintf(stderr, "	[-c <number of connections>] [-L <microseconds>]\n\n");
 fprintf(stderr, "-h		this help message\n");
 fprintf(stderr, "-s		log jumpgate use via syslog(3)\n");
 fprintf(stderr, "-v		display version number and exit\n");
 fprintf(stderr, "-i		interactively ask the user where to forward\n");
 fprintf(stderr, "-b		specify the local IP to bind to\n");
 fprintf(stderr, "-l		specify the local listening port\n");
 fprintf(stderr, "-r		specify the remote port to forward the connection\n");
 fprintf(stderr, "-a		specify the host to forward the connection\n"); 
 fprintf(stderr, "-f		specify the filename to log the connection\n");
 fprintf(stderr, "-c		specify the maximum number of the connections backlog\n");
 fprintf(stderr, "-L		specify the latency in microseconds\n\n");
}

/* taken from "UNIX Network Programming" by W. Richard Stevens */ 
void
error_exit(int errno_flag, char *msg)
{
 int errno_save;
 char buffer[MAXLINE];
   
 errno_save = errno;
 snprintf(buffer, sizeof(buffer), "%s", msg);
 
 if(errno_flag)
 {
  snprintf(buffer+strlen(buffer), sizeof(buffer)-strlen(buffer), ": %s", strerror(errno_save));
 }
  
 strncat(buffer, "\n", 2);
 fflush(stdout);
 fputs(buffer, stderr);
 fflush(NULL);
 exit(EXIT_FAILURE);
}

void
chld_reap(int signo)
{
 pid_t pid;
 int stat;
  
 while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
 {
  /* do nothing */;
 }
       
 return;
}

int
daemonize(const char *path)
{
 pid_t pid;
 
 if((signal(SIGTTOU, SIG_IGN)) == SIG_ERR)
 {
  error_exit(1, "signal error");
 }
        
 if((signal(SIGTTIN, SIG_IGN)) == SIG_ERR)
 {
  error_exit(1, "signal error");
 }
               
 if((signal(SIGTSTP, SIG_IGN)) == SIG_ERR)
 {
  error_exit(1, "signal error");
 }
                      
 if((signal(SIGHUP, SIG_IGN)) == SIG_ERR)
 {
  error_exit(1, "signal error");
 }
                             
 if((pid = fork()) < 0)
 {
  return(-1);
 }
 else if(pid != 0)
 {
  exit(EXIT_SUCCESS);
 }
                                   
 if(setsid() == -1)
 {
  error_exit(1, "setsid error");
 }
                                          
 if(chdir(path) == -1)
 {
  error_exit(1, "chdir error");
 }
                                                 
 umask(0);
 return(0);
}

void
query(struct jump *jmp)
{
 u_char p[MAXLINE];
 u_char hname[MAXLINE];
 u_char msg[MAXLINE];
 u_char reply[MAXLINE];
 struct hostent *h_ent;
 struct servent *s_ent;
 
 snprintf(msg, sizeof(msg), "<hostname or IP address> <port> (e.g.: bsd.gr 80): ");

 if((write(jmp->cfd, msg, strlen(msg))) == -1)
 {
  error_log(1, 1, LOG_ERR, "write error");
 }
 
 if((read(jmp->cfd, reply, (sizeof(reply) - 1))) == -1)
 {
  error_log(1, 1, LOG_ERR, "read error");
 }
  
 sscanf(reply, "%s %s", hname, p);
 
 if((s_ent = getservbyname(p, "tcp")))
 {
  jmp->rport = ntohs(s_ent->s_port);
 }
 else
 {
  char *end_ptr;
  
  jmp->rport = strtol(p, &end_ptr, 10);
  if(!*optarg || *end_ptr || jmp->rport < 1 || jmp->rport > 65535)
  {
   error_exit(0, "error: unknown or invalid port");
  }
 }
 
 jmp->hostname = strdup(hname);
 
 if((jmp->hostaddr = inet_addr(hname)) == INADDR_NONE)
 {
  if((h_ent = gethostbyname(hname)) != NULL)
  {
   memcpy(&jmp->hostaddr, h_ent->h_addr, sizeof(in_addr_t));
  }
  else
  {
   snprintf(msg, sizeof(msg), "error resolving %s\n", hname);
   if((write(jmp->cfd, msg, strlen(msg))) == -1)
   {
    error_log(1, 1, LOG_ERR, "write error");
   }
   
   exit(EXIT_FAILURE);
  }
 }
 
 return;
} 

void
tcp_jumpgate(struct jump *jmp)
{
 FILE *fp = NULL;
 int n, rfd;
 char buf[BUFSIZE];
 fd_set fdsr, fdse;
 struct sockaddr_in raddr;
 
 if(jmp->log_flag == 1)
 {
  if((fp = fopen(jmp->logfile, "a+")) == NULL)
  {
   error_log(1, 1, LOG_ERR, "fopen error");
  }
 }
 
 if((rfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 {
  error_log(1, 1, LOG_ERR, "socket error");
 }
 
 raddr.sin_family = AF_INET;
 raddr.sin_port = htons(jmp->rport);
 memcpy(&raddr.sin_addr, &jmp->hostaddr, sizeof(in_addr_t));
 
 if((connect(rfd, (struct sockaddr *)&raddr, sizeof(raddr))) < 0)
 {
  error_log(1, 1, LOG_ERR, "connect error");
 }
 
 if(jmp->log_flag == 1)
 {
  fprintf(fp, "--[ host: %s port: %d ]--\n", jmp->hostname, jmp->rport);
 }
 
 for(; ;)
 {
  FD_ZERO(&fdsr);
  FD_ZERO(&fdse);
  FD_SET(jmp->cfd, &fdsr);
  FD_SET(jmp->cfd, &fdse);
  FD_SET(rfd, &fdsr);
  FD_SET(rfd, &fdse);
  
  if(select(MYMAXFDS, &fdsr, NULL, &fdse, NULL) == -1)
  {
   if((shutdown(rfd, 2)) == -1)
   {
    error_log(1, 1, LOG_ERR, "shutdown error");
   }
   
   if((close(rfd)) == -1)
   {
    error_log(1, 1, LOG_ERR, "close error");
   }
   
   if(jmp->log_flag == 1)
   {
    fixfile(jmp->logfile, fp);
   }
    
   return;
  }
  
  if(FD_ISSET(jmp->cfd, &fdsr) || FD_ISSET(jmp->cfd, &fdse))
  {
   if((n = read(jmp->cfd, buf, BUFSIZE)) <= 0)
   {
    if((shutdown(rfd, 2)) == -1)
    {
     error_log(1, 1, LOG_ERR, "shutdown error");
    }
    
    if((close(rfd)) == -1)
    {
     error_log(1, 1, LOG_ERR, "close error");
    }
    
    if(jmp->log_flag == 1)
    {
     fixfile(jmp->logfile, fp);
    }
     
    return;
   }
   else if(jmp->log_flag == 1)
   {
    fprintf(fp, "%s\n", buf);
   }
   
   if((write(rfd, buf, n)) <= 0)
   {
    if((shutdown(rfd, 2)) == -1)
    {
     error_log(1, 1, LOG_ERR, "shutdown error");
    }
    
    if((close(rfd)) == -1)
    {
     error_log(1, 1, LOG_ERR, "close error");
    }
    
    if(jmp->log_flag == 1)
    {
     fixfile(jmp->logfile, fp);
    }
     
    return;
   }
   else if(jmp->latency_flag == 1)
   {
#ifdef LINUX
    usleep(jmp->latency);
#else /* {Free,Net,Open}BSD and Solaris */
    if((usleep(jmp->latency)) == -1)
    {
     error_log(1, 1, LOG_ERR, "usleep error");
    }
#endif /* LINUX */
   }
  } 
  else if(FD_ISSET(rfd, &fdsr) || FD_ISSET(rfd, &fdse))
  {
   if((n = read(rfd, buf, BUFSIZE)) < 0)
   {
    if((shutdown(rfd, 2)) == -1)
    {
     error_log(1, 1, LOG_ERR, "shutdown error");
    }
    
    if((close(rfd)) == -1)
    {
     error_log(1, 1, LOG_ERR, "close error");
    }
    
    if(jmp->log_flag == 1)
    {
     fixfile(jmp->logfile, fp);
    }
     
    return;
   }
   
   if((write(jmp->cfd, buf, n)) <= 0)
   {
    if((shutdown(rfd, 2)) == -1)
    {
     error_log(1, 1, LOG_ERR, "shutdown error");
    }
    
    if((close(rfd)) == -1)
    {
     error_log(1, 1, LOG_ERR, "close error");
    }
    
    if(jmp->log_flag == 1)
    {
     fixfile(jmp->logfile, fp);
    }
     
    return;
   }
   else if(jmp->latency_flag == 1)
   {
#ifdef LINUX
    usleep(jmp->latency);
#else /* {Free,Net,Open}BSD and Solaris */
    if((usleep(jmp->latency)) == -1)
    {
     error_log(1, 1, LOG_ERR, "usleep error");
    }
#endif /* LINUX */
   }
  }
 }
 
 return;
}

void
fixfile(char *file, FILE *pf)
{
 if((chmod(file, (S_IRUSR | S_IWUSR))) == -1)
 {
  error_log(1, 1, LOG_ERR, "chmod error");
 }
  
 if((fclose(pf)) == EOF)
 {
  error_log(1, 1, LOG_ERR, "fclose error");
 }
}

/* taken from "UNIX Network Programming" by W. Richard Stevens */
void
error_log(int errno_flag, int exit_flag, int priority, char *msg)
{
 int errno_save;
 char buffer[MAXLINE];
   
 errno_save = errno;
 snprintf(buffer, sizeof(buffer), "%s", msg);
      
 if(errno_flag)
 {
  snprintf(buffer+strlen(buffer), sizeof(buffer)-strlen(buffer), ": %s", strerror(errno_save));
 }
                   
 strncat(buffer, "\n", 2);
 syslog(priority, "%s", buffer);
                                                   
 if(exit_flag)
 {
  exit(EXIT_FAILURE);
 }
 else
 {
  return;
 }
}

/* EOF */


syntax highlighted by Code2HTML, v. 0.9.1