| /* |
| * 6tunnel v0.11 |
| * (C) Copyright 2000-2005,2013 by Wojtek Kaniewski <wojtekka@toxygen.net> |
| * |
| * Contributions by: |
| * - Dariusz Jackowski <ascent@linux.pl> |
| * - Ramunas Lukosevicius <lukoramu@parok.lt> |
| * - Roland Stigge <stigge@antcom.de> |
| * |
| * 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, hexdump = 0; |
| int remote_hint = AF_INET6; |
| int local_hint = AF_INET; |
| int hint_optional = 0; |
| char *remote_host, *ircpass = NULL; |
| char *ircsendpass = NULL, remote[128]; |
| char *pid_file = NULL; |
| const char *source_host; |
| |
| 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) == 0 && ai != NULL) { |
| char *tmp; |
| |
| tmp = xmalloc(ai->ai_addrlen); |
| memcpy(tmp, ai->ai_addr, ai->ai_addrlen); |
| |
| freeaddrinfo(ai); |
| |
| return (struct sockaddr*) tmp; |
| } |
| |
| /* If hints are optional, try again without them. */ |
| if (hint_optional && hint != 0) { |
| struct sockaddr *res; |
| |
| res = resolve_host(name, 0); |
| |
| if (res != NULL) { |
| debug("warning: %s resolved ignoring hints\n", name); |
| return res; |
| } |
| } |
| |
| 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"; |
| if (write(rsock, tmp, strlen(tmp)) != strlen(tmp)) { |
| // Do nothing. We're failing anyway. |
| } |
| |
| 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"; |
| if (write(rsock, tmp, strlen(tmp)) != strlen(tmp)) { |
| // Do nothing. We're failing anyway. |
| } |
| |
| goto cleanup; |
| } |
| |
| debug("<%d> irc proxy auth succeded\n", rsock); |
| } |
| |
| if (!(sa = resolve_host(remote_host, remote_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, local_hint))) { |
| debug("<%d> unable to resolve source host (%s)\n", rsock, source); |
| goto cleanup; |
| } |
| |
| if (bind(sock, sa, (local_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, remote_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); |
| if (write(sock, buf, strlen(buf)) != strlen(buf)) |
| goto cleanup; |
| } |
| |
| 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 [-146dvhH] [-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 prefer 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" |
| " -H make IPv4/IPv6 resolver hints optional\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, sa_len, conn_limit = 0, optc; |
| char *username = NULL, *bind_host = NULL; |
| struct sockaddr *sa; |
| struct sockaddr_in laddr, caddr; |
| struct sockaddr_in6 laddr6; |
| unsigned int caddrlen = sizeof(caddr); |
| struct passwd *pw = NULL; |
| |
| while ((optc = getopt(argc, argv, "1dv46fHs: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': |
| break; |
| case '6': |
| remote_hint = AF_INET; |
| local_hint = AF_INET6; |
| 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; |
| case 'H': |
| hint_optional = 1; |
| 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, remote_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, local_hint))) { |
| 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 (local_hint == AF_INET) { |
| 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); |
| } |
| |
| // Reuse port wasn't working when only Weave set it. |
| if (setsockopt(lsock, SOL_SOCKET, SO_REUSEPORT, &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); |
| } |
| |
| |