blob: 1d3e70c9a9c2b5f71e86534c9700566698e97382 [file] [log] [blame]
/*
* 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);
}