/* * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2005 Marcus Glocker * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* solaris quirks */ #ifdef __sun #define INADDR_NONE ((in_addr_t) - 1) #endif /* * prototypes */ int open_proxy(void); int sync_write(int, const char *, int); void logx(const char *, ...); void handle_client(int); void read_config(FILE *f); void parse_host(char *, char **, unsigned *); char *base64e(char *); /* * global variables */ int parentpid; FILE *logfile = NULL; FILE *pidfile = NULL; static const char *version = "1.3"; static const char *cfgfile = "gotthard.conf"; volatile sig_atomic_t quit = 0; struct config { char *pid; char *log; char *path; char *addr_local; char *addr_proxy; char *addr_ext; char *auth; unsigned port_local; unsigned port_proxy; unsigned port_ext; } config; static const unsigned char encode[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* * signal handler */ void handle_signal(int sigraised) { switch (sigraised) { case SIGINT: case SIGTERM: quit = 1; break; case SIGCHLD: { pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) if (WIFEXITED(status)) logx("child %i exited with status %i", pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) logx("child %i exited due to signal %i", pid, WTERMSIG(status)); else logx("child %i stopped", pid); break; } case SIGHUP: /* ignore */ break; case SIGQUIT: /* ignore */ break; case SIGPIPE: /* ignore */ break; case SIGALRM: /* ignore */ break; default: /* ignore */ break; } } /* * usage */ void usage(int mode) { const static char *progname = "gotthard"; if (mode) { fprintf(stderr, "%s %s\n", progname, version); exit(1); } fprintf(stderr, "usage: %s ", progname); fprintf(stderr, "[-hv] [-c configfile]\n\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " -h\t\t: This help.\n"); fprintf(stderr, " -v\t\t: Shows version.\n"); fprintf(stderr, " -c configfile\t: Use an alternate configfile.\n"); exit(1); } /* * ssh through https proxy tunnel */ int main(int argc, char *argv[]) { int r, ch, val, cfgflag, listen_fd; char tmp[1024]; pid_t pid; socklen_t len; struct sockaddr_in sa; FILE *configfile; cfgflag = 0; listen_fd = -1; configfile = NULL; memset(&config, 0, sizeof(config)); /* * get command line options */ while ((ch = getopt(argc, argv, "hvc:")) != -1) { switch (ch) { case 'c': cfgfile = optarg; cfgflag = 1; break; case 'v': usage(1); break; case 'h': /* FALLTHROUGH */ default: usage(0); /* NOTREACHED */ } } /* * read config file */ configfile = fopen(cfgfile, "rb"); if (!configfile) { fprintf(stderr, "%s: %s\n", cfgfile, strerror(errno)); exit(1); } read_config(configfile); fclose(configfile); configfile = NULL; if (!config.addr_local || !config.addr_proxy || !config.addr_ext || !config.port_local || !config.port_proxy || !config.port_ext) { fprintf(stderr, "config file incomplete\n"); exit(1); } /* * prepare pid file and log file in executers home directory */ snprintf(tmp, sizeof(tmp), "%s/.gotthard", getenv("HOME")); config.path = strdup(tmp); snprintf(tmp, sizeof(tmp), "%s/gotthard.log", config.path); config.log = strdup(tmp); snprintf(tmp, sizeof(tmp), "%s/gotthard.pid", config.path); config.pid = strdup(tmp); mkdir(config.path, 0755); /* * daemonize */ if ((pid = fork()) < 0) { perror("fork"); goto error; } else if (pid > 0) exit(0); if ((pid = setsid()) == -1) { perror("setsid"); goto error; } if ((pid = fork()) < 0) { perror("fork"); goto error; } else if (pid > 0) exit(0); parentpid = getpid(); if (chdir("/")) { perror("chdir"); goto error; } umask(022); /* * install signal handler */ signal(SIGHUP, handle_signal); signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); signal(SIGCHLD, handle_signal); signal(SIGQUIT, handle_signal); signal(SIGPIPE, handle_signal); signal(SIGALRM, handle_signal); if (config.pid) { char s[128]; if ((pidfile = fopen(config.pid, "rb"))) { pid_t pid; fread(s, 1, 128, pidfile); fclose(pidfile); pidfile = NULL; pid = atol(s); if (kill(pid, SIGHUP) && errno == ESRCH) fprintf(stderr, "overwriting stale pid file\n"); else { #ifdef __sun fprintf(stdout, "%s already runs (pid %li)\n", #else fprintf(stdout, "%s already runs (pid %d)\n", #endif argv[0], pid); goto error; } } pidfile = fopen(config.pid, "wb"); if (!pidfile) { perror("fopen"); goto error; } else { snprintf(s, 128, "%i\n", parentpid); fwrite(s, 1, strlen(s), pidfile); fflush(pidfile); } } if (config.log) { if (!(logfile = fopen(config.log, "ab"))) { perror("fopen"); goto error; } } close(0); close(1); close(2); open("/dev/null", O_RDWR); dup(logfile ? fileno(logfile) : 0); dup(logfile ? fileno(logfile) : 0); if (cfgflag) logx("STARTED as %s %s %s", argv[0], argv[1], argv[2]); else logx("STARTED as %s", argv[0]); logx(" pid %s [%i]", config.pid, parentpid); logx(" log %s", config.log); logx(" listen %s:%u", config.addr_local, config.port_local); logx(" proxy %s:%u", config.addr_proxy, config.port_proxy); logx(" external %s:%u", config.addr_ext, config.port_ext); if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); goto error; } if (fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | O_NONBLOCK)) { perror("fcntl"); goto error; } val = 1; if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&val, sizeof(val))) { perror("setsockopt"); goto error; } memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(config.addr_local); sa.sin_port = htons(config.port_local); if (bind(listen_fd, (const struct sockaddr *)&sa, sizeof(sa))) { perror("bind"); goto error; } if (listen(listen_fd, 1)) { perror("listen"); goto error; } /* * handle incoming client connections */ while (!quit) { fd_set readfds; struct timeval tv; FD_ZERO(&readfds); FD_SET(listen_fd, &readfds); tv.tv_sec = 10; tv.tv_usec = 0; r = select(listen_fd+1, &readfds, NULL, NULL, &tv); if (r < 0) { if (errno != EINTR) { perror("select"); break; } } else if (r > 0 && FD_ISSET(listen_fd, &readfds)) { int client_fd; memset(&sa, 0, sizeof(sa)); len = sizeof(sa); client_fd = accept(listen_fd, (struct sockaddr *)&sa, &len); if (client_fd < 0 || len != sizeof(sa)) { perror("accept"); break; } pid = fork(); if (pid < 0) { perror("fork"); break; } if (pid) close(client_fd); else { close(listen_fd); logx("connection from %s:%i", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); handle_client(client_fd); close(client_fd); return 0; } } } error: if (listen_fd) close(listen_fd); if (configfile) { fclose(configfile); fclose(pidfile); unlink(config.pid); } if (logfile) { fflush(logfile); fclose(logfile); } if (quit) logx("SIGINT/SIGTERM, terminating gracefully"); return 0; } void handle_client(int client_fd) { int r, max, len, proxy_fd; char buf[65535]; struct timeval tv; fd_set readfds; time_t t; unsigned long bytes_in, bytes_out; proxy_fd = open_proxy(); if (!proxy_fd) { logx("handle_client() can't open proxy connection"); return; } if (fcntl(proxy_fd, F_SETFL, fcntl(proxy_fd, F_GETFL) | O_NONBLOCK)) perror("fcntl"); max = client_fd > proxy_fd ? client_fd : proxy_fd; t = time(0); bytes_in = bytes_out = 0; for (;;) { FD_ZERO(&readfds); FD_SET(proxy_fd, &readfds); FD_SET(client_fd, &readfds); tv.tv_sec = 10; tv.tv_usec = 0; r = select(max + 1, &readfds, NULL, NULL, &tv); if (r < 0) { if (errno != EINTR) { perror("select"); break; } } else if (r > 0) { if (FD_ISSET(proxy_fd, &readfds)) { len = read(proxy_fd, buf, 65535); if (len) { if (sync_write(client_fd, buf, len)) { logx("incomplete write"); break; } bytes_in += len; } else { logx("connection closed by proxy"); break; } } if (FD_ISSET(client_fd, &readfds)) { len = read(client_fd, buf, 65535); if (len) { if (sync_write(proxy_fd, buf, len)) { logx("incomplete write"); break; } bytes_out += len; } else { logx("connection closed by client"); break; } } } } logx("%lu bytes in, %lu bytes out, %u seconds", bytes_in, bytes_out, time(0) - t); } int open_proxy(void) { int i, fd, len; struct sockaddr_in sa; char buf[1024]; if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 0; } memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(config.addr_proxy); if (sa.sin_addr.s_addr == INADDR_NONE) { struct hostent *h = gethostbyname(config.addr_proxy); if (h == NULL) { logx("invalid host name %s", config.addr_proxy); return 0; } memcpy(&sa.sin_addr.s_addr, h->h_addr, sizeof(in_addr_t)); } sa.sin_port = htons(config.port_proxy); if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) { perror("connect"); close(fd); return 0; } if (config.auth) { snprintf(buf, sizeof(buf), "CONNECT %s:%u HTTP/1.0\r\nHost: %s\r\n" "Proxy-Authorization: Basic %s\r\n\r\n", config.addr_ext, config.port_ext, config.addr_ext, config.auth); } else { snprintf(buf, sizeof(buf), "CONNECT %s:%u HTTP/1.0\r\nHost: %s\r\n\r\n", config.addr_ext, config.port_ext, config.addr_ext); } write(fd, buf, strlen(buf)); i = 0; while ((len = read(fd, buf + i, 1)) > 0) { if (buf[i] == '\n' || i == 1023) { if (i && buf[i - 1] == '\r') buf[i - 1] = 0; else buf[i] = 0; i = 0; if (!buf[0]) { logx("connection through proxy %s:%u " "established", config.addr_proxy, config.port_proxy); return fd; } } else i++; } if (len < 0) perror("read"); logx("didn't detect proxy response"); return 0; } int sync_write(int fd, const char *buf, int len) { int off, r; fd_set writefds; struct timeval tv; off = 0; while (len > off) { FD_ZERO(&writefds); FD_SET(fd, &writefds); tv.tv_sec = 10; tv.tv_usec = 0; r = select(fd + 1, NULL, &writefds, NULL, &tv); if (r < 0) { if (errno != EINTR) { perror("select"); return 1; } } else if (r > 0 && FD_ISSET(fd, &writefds)) { r = write(fd, buf + off, len-off); if (r < 0) { perror("write"); return 1; } off += r; } } return 0; } void logx(const char *format, ...) { time_t t; struct tm *tm; pid_t pid; va_list ap; t = time(0); tm = localtime(&t); if (tm) fprintf(stderr, "%4.4i.%2.2i.%2.2i %2.2i:%2.2i:%2.2i ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); pid = getpid(); if (pid == parentpid) fprintf(stderr, "*** "); else #ifdef __sun fprintf(stderr, "%li ", pid); #else fprintf(stderr, "%d ", pid); #endif va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\n"); fflush(stderr); } void read_config(FILE *f) { int i; char b[1024]; i = 0; while (fread(b + i, 1, 1, f)) { if (b[i] == '\n' || i == 1023) { b[i] = 0; if (i > 0 && b[0] != '#') { int j = 0, k; while (b[j] && isspace((int)b[j])) j++; k = j; while (b[k] && !isspace((int)b[k])) k++; if (b[k]) b[k++] = 0; while (b[k] && isspace((int)b[k])) k++; if (!strcmp(b + j, "auth")) config.auth = strdup(base64e(b + k)); else if (!strcmp(b + j, "listen")) parse_host(b + k, &config.addr_local, &config.port_local); else if (!strcmp(b + j, "proxy")) parse_host(b + k, &config.addr_proxy, &config.port_proxy); else if (!strcmp(b+j, "external")) parse_host(b + k, &config.addr_ext, &config.port_ext); else fprintf(stderr, "unknown option %s\n", b + j); } i = 0; } else i++; } } void parse_host(char *s, char **addr, unsigned *port) { int i; i = 0; while (s[i] && s[i] != ':') i++; if (s[i]) s[i++] = 0; *addr = strdup(s); *port = atoi(s + i); } char * base64e(char *string) { int i, j, k; unsigned char in_a[3], out[4]; static char buf[128]; k = 0; memset(buf, 0, sizeof(buf)); for (;;) { in_a[0] = in_a[1] = in_a[2] = 0; for (i = 0; i < 3; i++) { if (*string == '\0') break; in_a[i] = *string; string++; } /* split the 24-bit field in 4 6-bit */ out[0] = encode[(in_a[0] >> 2)]; out[1] = encode[((in_a[0] & 3) << 4) | (in_a[1] >> 4)]; out[2] = i < 2 ? '=' : encode[((in_a[1] & 15) << 2) | (in_a[2] >> 6)]; out[3] = i < 3 ? '=' : encode[(in_a[2] & 63)]; /* fill up buffer */ for (j = 0; j < 4; j++) { buf[k] = out[j]; k++; } /* finished */ if (i != 3) break; } return buf; }