Logo Search packages:      
Sourcecode: 6tunnel version File versions  Download package

6tunnel.c

/*
 *  6tunnel v0.11
 *  (C) Copyright 2000-2005 by Wojtek Kaniewski <wojtekka@toxygen.net>
 *  
 *  Contributions by:
 *  - Dariusz Jackowski <ascent@linux.pl>
 *  - Ramunas Lukosevicius <lukoramu@parok.lt>
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License Version 2 as
 *  published by the Free Software Foundation.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <pwd.h>
#include <time.h>

#define debug(x...) do { \
      if (verbose) \
            printf(x); \
} while(0)

int verbose = 0, conn_count = 0;
int remote_port, verbose, hint = AF_INET6, hexdump = 0;
char *remote_host, *ircpass = NULL;
char *ircsendpass = NULL, remote[128];
char *pid_file = NULL;
const char *source_host;

00052 typedef struct map {
      char *ipv4;
      char *ipv6;
      struct map *next;
} map_t;

map_t *map_list = NULL;
char *map_file = NULL;

char *xmalloc(int size)
{
      char *tmp;

      if (!(tmp = malloc(size))) {
            perror("malloc");
            exit(1);
      }

      return tmp;
}

char *xrealloc(char *ptr, int size)
{
      char *tmp;

      if (!(tmp = realloc(ptr, size))) {
            perror("realloc");
            exit(1);
      }

      return tmp;
}

char *xstrdup(const char *str)
{
      char *tmp;

      if (!(tmp = strdup(str))) {
            perror("strdup");
            exit(1);
      }

      return tmp;
}

struct sockaddr *resolve_host(const char *name, int hint)
{
      struct addrinfo *ai = NULL, hints;

      memset(&hints, 0, sizeof(hints));
      hints.ai_family = hint;

      if (!getaddrinfo(name, NULL, &hints, &ai) && ai) {
            char *tmp;

            tmp = xmalloc(ai->ai_addrlen);
            memcpy(tmp, ai->ai_addr, ai->ai_addrlen);

            freeaddrinfo(ai);

            return (struct sockaddr*) tmp;
      }

      return NULL;
}

void print_hexdump(const char *buf, int len)
{
      int i, j;
  
      for (i = 0; i < ((len / 16) + ((len % 16) ? 1 : 0)); i++) {
            printf("%.4x: ", i * 16);

            for (j = 0; j < 16; j++) {
                  if (i * 16 + j < len)
                        printf("%.2x ", buf[i*16+j]);
                  else
                        printf("   ");
                  if (j == 7)
                        printf(" ");
            }
            
            printf(" ");
            
            for (j = 0; j < 16; j++) {
                  if (i * 16 + j < len) {
                        char ch = buf[i * 16 + j];

                        printf("%c", (isprint(ch)) ? ch : '.');
                  }
            }

            printf("\n");
      }
}

const char *map_find(const char *ipv4)
{
      map_t *m;

      for (m = map_list; m; m = m->next) {
            if (!strcmp(m->ipv4, ipv4))
                  return m->ipv6;
      }

      for (m = map_list; m; m = m->next) {
            if (!strcmp(m->ipv4, "0.0.0.0") || !strcmp(m->ipv4, "default"))
                  return m->ipv6;
      }

      return source_host;
}

void make_tunnel(int rsock, const char *remote)
{
      char buf[4096], *outbuf = NULL, *inbuf = NULL;
      int sock = -1, outlen = 0, inlen = 0;
      struct sockaddr *sa = NULL;
      const char *source;

      if (map_list) {
            if (!(source = map_find(remote))) {
                  debug("<%d> connection from unmapped address (%s), disconnecting\n", rsock, remote);
                  goto cleanup;
            }

            debug("<%d> mapped to %s\n", rsock, source);
      } else
            source = source_host;

      if (ircpass) {
            int i, ret;

            for (i = 0; i < sizeof(buf) - 1; i++) {
                  if ((ret = read(rsock, buf + i, 1)) < 1)
                        goto cleanup;
                  if (buf[i] == '\n')
                        break;
            }

            buf[i] = 0;
            
            if (i > 0 && buf[i - 1] == '\r')
                  buf[i - 1] = 0;

            if (i == 4095 || strncasecmp(buf, "PASS ", 5)) {
                  char *tmp;

                  debug("<%d> irc proxy auth failed - junk\n", rsock);

                  tmp = "ERROR :Closing link: Make your client send password first\r\n";
                  write(rsock, tmp, strlen(tmp));
                  
                  goto cleanup;
            }

            if (strcmp(buf + 5, ircpass)) {
                  char *tmp;

                  debug("<%d> irc proxy auth failed - password incorrect\n", rsock);
                  tmp = ":6tunnel 464 * :Password incorrect\r\nERROR :Closing link: Password incorrect\r\n";
                  write(rsock, tmp, strlen(tmp));
                  
                  goto cleanup;
            }
            
            debug("<%d> irc proxy auth succeded\n", rsock);
      }
  
      if (!(sa = resolve_host(remote_host, hint))) {
            debug("<%d> unable to resolve %s\n", rsock, remote_host);
            goto cleanup;
      }

      if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) {
            debug("<%d> unable to create socket (%s)\n", rsock, strerror(errno));
            goto cleanup;
      }

      free(sa);
      sa = NULL;

      if (source) {
            if (!(sa = resolve_host(source, hint))) {
                  debug("<%d> unable to resolve source host (%s)\n", rsock, source);
                  goto cleanup;
            }

            if (bind(sock, sa, (hint == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) {
                  debug("<%d> unable to bind to source host (%s)\n", rsock, source);
                  goto cleanup;
            }

            free(sa);
            sa = NULL;
      }

      sa = resolve_host(remote_host, hint);

      ((struct sockaddr_in*) sa)->sin_port = htons(remote_port);

      if (connect(sock, sa, (sa->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))) {
            debug("<%d> connection refused (%s,%d)\n", rsock, remote_host, remote_port);
            goto cleanup;
      }

      free(sa);
      sa = NULL;

      debug("<%d> connected to %s,%d\n", rsock, remote_host, remote_port);

      if (ircsendpass) {
            snprintf(buf, 4096, "PASS %s\r\n", ircsendpass);
            write(sock, buf, strlen(buf));
      }

      for (;;) {
            fd_set rds, wds;
            int ret, sent;

            FD_ZERO(&rds);
            FD_SET(sock, &rds);
            FD_SET(rsock, &rds);

            FD_ZERO(&wds);
            if (outbuf && outlen)
                  FD_SET(rsock, &wds);
            if (inbuf && inlen)
                  FD_SET(sock, &wds);
    
            ret = select((sock > rsock) ? (sock + 1) : (rsock + 1), &rds, &wds, NULL, NULL);

            if (FD_ISSET(rsock, &wds)) {
                  sent = write(rsock, outbuf, outlen);

                  if (sent < 1)
                        goto cleanup;

                  if (sent == outlen) {
                        free(outbuf);
                        outbuf = NULL;
                        outlen = 0;
                  } else {
                        memmove(outbuf, outbuf + sent, outlen - sent);
                        outlen -= sent;
                  }
            }

            if (FD_ISSET(sock, &wds)) {
                  sent = write(sock, inbuf, inlen);

                  if (sent < 1)
                        goto cleanup;

                  if (sent == inlen) {
                        free(inbuf);
                        inbuf = NULL;
                        inlen = 0;
                  } else {
                        memmove(inbuf, inbuf + sent, inlen - sent);
                        inlen -= sent;
                  }
            }

            if (FD_ISSET(sock, &rds)) {
                  if ((ret = read(sock, buf, 4096)) < 1)
                        goto cleanup;

                  if (hexdump) {
                        printf("<%d> recvfrom %s,%d\n", rsock, remote_host, remote_port);
                        print_hexdump(buf, ret);
                  }
                  
                  sent = write(rsock, buf, ret);

                  if (sent < 1)
                        goto cleanup;
                  
                  if (sent < ret) {
                        outbuf = xrealloc(outbuf, outlen + ret - sent);
                        memcpy(outbuf + outlen, buf + sent, ret - sent);
                        outlen = ret - sent;
                  }
            }

            if (FD_ISSET(rsock, &rds)) {
                  if ((ret = read(rsock, buf, 4096)) < 1)
                        goto cleanup;

                  if (hexdump) {
                        printf("<%d> sendto %s,%d\n", rsock, remote_host, remote_port);
                        print_hexdump(buf, ret);
                  }

                  sent = write(sock, buf, ret);

                  if (sent < 1)
                        goto cleanup;

                  if (sent < ret) {
                        inbuf = xrealloc(inbuf, inlen + ret - sent);
                        memcpy(inbuf + inlen, buf + sent, ret - sent);
                        inlen = ret - sent;
                  }
            }
      }


cleanup:
      if (sa)
            free(sa);

      close(rsock);

      if (sock != -1)
            close(sock);
}

void usage(const char *arg0)
{
      fprintf(stderr,
                  
"usage: %s [-146dvh] [-s sourcehost] [-l localhost] [-i pass]\n"
"           [-I pass] [-L limit] [-A filename] [-p pidfile]\n"
"           [-m mapfile] localport remotehost [remoteport]\n"
"\n"     
"  -1  allow only one connection and quit\n"
"  -4  preffer IPv4 endpoints\n"
"  -6  bind to IPv6 address\n"
"  -d  don't detach\n"
"  -f  force tunneling (even if remotehost isn't resolvable)\n"
"  -h  print hex dump of packets\n"
"  -i  act like irc proxy and ask for password\n"
"  -I  send specified password to the irc server\n"
"  -l  bind to specified address\n"
"  -L  limit simultanous connections\n"
"  -p  write down pid to specified file\n"
"  -s  connect using specified address\n"
"  -m  read specified IPv4-to-IPv6 map file\n"
"  -v  be verbose\n"
"\n", arg0);
}

void clear_argv(char *argv)
{
      int x;
  
      for (x = 0; x < strlen(argv); x++)
            argv[x] = 'x';

      return;
}

void map_destroy(void)
{
      map_t *m;
      
      debug("map_destroy()\n");
      
      for (m = map_list; m; ) {
            map_t *n;
            
            free(m->ipv4);
            free(m->ipv6);
            n = m;
            m = m->next;
            free(n);
      }

      map_list = NULL;
}

void map_read(void)
{
      char buf[256];
      FILE *f;

      if (!map_file)
            return;

      debug("reading map from %s\n", map_file);

      if (!(f = fopen(map_file, "r"))) {
            debug("unable to read map file, ignoring\n");
            return;
      }
      
      while (fgets(buf, sizeof(buf), f)) {
            char *p, *ipv4, *ipv6;
            map_t *m;

            for (p = buf; *p == ' ' || *p == '\t'; p++);

            if (!*p)
                  continue;

            ipv4 = p;

            for (; *p && *p != ' ' && *p != '\t'; p++);

            if (!*p)
                  continue;

            *p = 0;
            p++;

            for (; *p == ' ' || *p == '\t'; p++);

            if (!*p)
                  continue;

            ipv6 = p;

            for (; *p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n'; p++);

            *p = 0;

            debug("[%s] mapped to [%s]\n", ipv4, ipv6);
            
            m = (map_t*) xmalloc(sizeof(map_t));
            m->ipv4 = xstrdup(ipv4);
            m->ipv6 = xstrdup(ipv6);
            m->next = map_list;
            map_list = m;
      }
      
      fclose(f);
}

void sighup()
{
      map_destroy();
      map_read();

      signal(SIGHUP, sighup);
}

void sigchld()
{
      while (waitpid(-1, NULL, WNOHANG) > 0) {
            debug("child process exited\n");
            conn_count--;
      }

      signal(SIGCHLD, sigchld);
}

void sigterm()
{
      if (pid_file)
            unlink(pid_file);

      exit(0);
}

int main(int argc, char **argv)
{
      int force = 0, lsock, csock, one = 0, jeden = 1, local_port;
      int detach = 1, listen6 = 0, sa_len, conn_limit = 0;
      char optc, *username = NULL, *bind_host = NULL;
      struct sockaddr *sa;
      struct sockaddr_in laddr, caddr;
      struct sockaddr_in6 laddr6;
      int caddrlen = sizeof(caddr);
      struct passwd *pw = NULL;
      
      while ((optc = getopt(argc, argv, "1dv46fs:l:I:i:hu:m:L:A:p:")) != -1) {
            switch (optc) {
                  case '1':
                        one = 1;
                        break;
                  case 'd':
                        detach = 0;
                        break;
                  case 'v':
                        verbose = 1;
                        break;
                  case '4':
                        hint = AF_INET; 
                        break;
                  case '6':
                        listen6 = 1;
                        break;
                  case 's':
                        source_host = xstrdup(optarg);
                        break;
                  case 'l':
                        bind_host = xstrdup(optarg);
                        break;
                  case 'r':
                        force = 1;
                        break;
                  case 'i':
                        ircpass = xstrdup(optarg);
                        clear_argv(argv[optind - 1]);
                        break;
                  case 'I':
                        ircsendpass = xstrdup(optarg);
                        clear_argv(argv[optind - 1]);
                        break;
                  case 'h':
                        hexdump = 1;
                        break;
                  case 'u':
                        username = xstrdup(optarg);
                        break;
                  case 'm':
                        map_file = xstrdup(optarg);
                        break;
                  case 'L':
                        conn_limit = atoi(optarg);
                        break;
                  case 'p':
                        pid_file = xstrdup(optarg);
                        break;
                  default:
                        return 1;
            }
      }

      if (hexdump)
            verbose = 1;

      if (verbose)
            detach = 0;
      
      if (detach)
            verbose = 0;

      if (argc - optind < 2) {
            usage(argv[0]);
            exit(1);
      }

      if (username && !(pw = getpwnam(username))) {
            fprintf(stderr, "%s: unknown user %s\n", argv[0], username);
            exit(1);
      }
  
      if (map_file)
            map_read();
  
      local_port = atoi(argv[optind++]);
      remote_host = argv[optind++];
      remote_port = (argc == optind) ? local_port : atoi(argv[optind]);

      debug("resolving %s\n", remote_host);

      if (!(sa = resolve_host(remote_host, hint)) && !force) {
            fprintf(stderr, "%s: unable to resolve host %s\n", argv[0], remote_host);
            exit(1);
      }

      free(sa);
      sa = NULL;

      if (bind_host) {
            debug("resolving %s\n", bind_host);

            if (!(sa = resolve_host(bind_host, (listen6) ? AF_INET6 : AF_INET))) {
                  fprintf(stderr, "%s: unable to resolve host %s\n", argv[0], remote_host);
                  exit(1);
            }
      }
 
      debug("local: %s,%d; ", (bind_host) ? bind_host : "default", local_port);
      debug("remote: %s,%d; ", remote_host, remote_port);

      if (map_file)
            debug("source: mapped\n");
      else
            debug("source: %s\n", (source_host) ? source_host : "default");

      if (!listen6) {
            lsock = socket(PF_INET, SOCK_STREAM, 0);

            memset(&laddr, 0, (sa_len = sizeof(laddr)));
            laddr.sin_family = AF_INET;
            laddr.sin_port = htons(local_port);
            
            if (sa) {
                  memcpy(&laddr.sin_addr, &((struct sockaddr_in*) sa)->sin_addr, sizeof(struct in_addr));
                  free(sa);
            }
            
            sa = (struct sockaddr*) &laddr;
      } else {
            lsock = socket(PF_INET6, SOCK_STREAM, 0);
            
            memset(&laddr6, 0, (sa_len = sizeof(laddr6)));
            laddr6.sin6_family = AF_INET6;
            laddr6.sin6_port = htons(local_port);
            
            if (sa) {
                  memcpy(&laddr6.sin6_addr, &((struct sockaddr_in6*) sa)->sin6_addr, sizeof(struct in6_addr));
                  free(sa);
            }

            sa = (struct sockaddr*) &laddr6;
      }

      if (setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &jeden, sizeof(jeden)) == -1) {
            perror("setsockopt");
            exit(1);
      }
  
      if (bind(lsock, sa, sa_len)) {
            perror("bind");
            exit(1);
      }    
  
      if (listen(lsock, 100)) {
            perror("listen");
            exit(1);
      }

      if (detach) {
            int i, ret;

            signal(SIGHUP, sighup);
            
            for (i = 0; i < 3; i++)
                  close(i);

            ret = fork();
            
            if (ret == -1) {
                  perror("fork");
                  exit(1);
            }

            if (ret)
                  exit(0);
      }

      if (pid_file) {
            FILE *f = fopen(pid_file, "w");

            if (!f)
                  debug("warning: cannot write to pidfile (%s)\n", strerror(errno));
            else {
                  fprintf(f, "%d", getpid());
                  fclose(f);
            }
      }

      if (username && ((setgid(pw->pw_gid) == -1) || (setuid(pw->pw_uid) == -1))) {
            perror("setuid/setgid");
            exit(1);
      }

      setsid();
      signal(SIGCHLD, sigchld);
      signal(SIGTERM, sigterm);
      signal(SIGINT, sigterm);
      signal(SIGHUP, sighup);
    
      for (;;) {  
            int ret;
            fd_set rds;

            FD_ZERO(&rds);
            FD_SET(lsock, &rds);

            if (select(lsock + 1, &rds, NULL, NULL, NULL) == -1) {
                  if (errno == EINTR)
                        continue;

                  perror("select");
                  break;
            }

            if ((csock = accept(lsock, (struct sockaddr*) &caddr, &caddrlen)) == -1) {
                  perror("accept");
                  break;
            }

            inet_ntop(caddr.sin_family, (caddr.sin_family == AF_INET) ?
                  &caddr.sin_addr :
                  (void*) &(((struct sockaddr_in6*)&caddr)->sin6_addr),
                  remote, sizeof(remote));

            debug("<%d> connection from %s,%d", csock, remote, ntohs(caddr.sin_port));

            if (conn_limit && (conn_count >= conn_limit)) {
                  debug(" -- rejected due to limit.\n");
                  shutdown(csock, 2);
                  close(csock);
                  continue;
            }
            
            if (conn_limit) {
                  conn_count++;
                  debug(" (no. %d)", conn_count);
            }
            
            fflush(stdout);
    
            if ((ret = fork()) == -1) {
                  debug(" -- fork() failed.\n");
                  shutdown(csock, 2);
                  close(csock);
                  continue;
            }
    
            if (!ret) {
                  signal(SIGHUP, SIG_IGN);
                  close(lsock);
                  debug("\n");
                  make_tunnel(csock, remote);
                  debug("<%d> connection closed\n", csock);
                  exit(0);
            } 

            close(csock);
    
            if (one) {
                  shutdown(lsock, 2);
                  close(lsock);
                  exit(0);
            }

      }

      close(lsock);
  
      exit(1);
}



Generated by  Doxygen 1.6.0   Back to index