/* * Copyright (c) 2002-2006 Hajimu UMEMOTO * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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 PROJECT 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 PROJECT 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. * * $Mahoroba: src/dtcpclient/dtcpclient.c,v 1.25 2006/05/25 16:36:21 ume Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MD5 #include #else #include #include #define MD5Init MD5_Init #define MD5Update MD5_Update #define MD5Final MD5_Final #endif #define SERVICE_DTCP "20200" #define KEEPALIVE_INTERVAL 60 #define TIMEOUT 60 #define RECONNECT_WAIT 10 #define MAXLINELEN 1024 #define DELIM " \t" #define UDP_TUNNEL_PORT 4028 #ifndef _PATH_VARRUN #define _PATH_VARRUN "/var/run" #endif #ifndef PREFIX #define PREFIX "/usr/local" #endif #define PIDFILE _PATH_VARRUN "/dtcpclient.pid" #define PASSWDFILE PREFIX "/etc/dtcpclient.auth" #define SCRIPTFLIE PREFIX "/etc/dtcpclient.script" typedef enum { FALSE = 0, TRUE = 1 } boolean; typedef enum { TUNNEL_ONLY, TUNNEL_HOST, TUNNEL_NETWORK } tuntype; static char *tuntype_table[] = { "tunnelonly", "host", "network" }; struct tunnel_info { char *nickname; char *server; char *myaddr; tuntype tuntype; char *tuninfo[6]; }; static struct tunnel_info *tunnel = NULL; static int dtcp_sock = -1; static boolean dtcp_error = FALSE; static boolean dtcp_connected = FALSE; static char *pidfile = PIDFILE; static char *script = SCRIPTFLIE; static boolean behind_nat = FALSE; static boolean debug = FALSE; static boolean daemonize = FALSE; static int mtu = 0; static boolean verbose = FALSE; static boolean syslog_opened = FALSE; static boolean reconnect_request = FALSE; static int signalpipe[2]; static void bye(int); static void usage(char *progname) { fprintf(stderr, "usage: %s [-dDhlnU] [-b udpport] [-B naptport] [-e nickname] [-f pidfile] [-m mtu] [-p port] [-s script] [-t tuntype] [-u username] server\n", progname); exit(EX_USAGE); } static void logmsg(int priority, const char *fmt, ...) { static char buf[MAXLINELEN]; va_list ap; buf[0] = '\0'; va_start(ap, fmt); vsnprintf(buf, sizeof(buf) - 3, fmt, ap); va_end(ap); if (syslog_opened) syslog(priority, "%s", buf); else fprintf(stderr, "%s\n", buf); } static void debugmsg(const char *fmt, ...) { static char buf[MAXLINELEN]; va_list ap; if (!debug) return; buf[0] = '\0'; va_start(ap, fmt); vsnprintf(buf, sizeof(buf) - 3, fmt, ap); va_end(ap); if (syslog_opened) syslog(LOG_DEBUG, "%s", buf); else fprintf(stderr, "%s\n", buf); } static char * authenticate(char *user, char *challenge, char *pass) { static const char hex[] = "0123456789ABCDEF"; static char ascii_digest[33]; MD5_CTX ctx; unsigned char digest[16]; int i; MD5Init(&ctx); MD5Update(&ctx, user, strlen(user)); MD5Update(&ctx, challenge, strlen(challenge)); MD5Update(&ctx, pass, strlen(pass)); MD5Final(digest, &ctx); for (i = 0; i < 16; i++) { ascii_digest[i + i] = hex[digest[i] >> 4]; ascii_digest[i + i + 1] = hex[digest[i] & 0x0f]; } ascii_digest[i + i] = '\0'; return ascii_digest; } static boolean pidfile_create() { FILE *fp; if ((fp = fopen(pidfile, "w")) == NULL) return FALSE; fprintf(fp, "%d\n", getpid()); fclose(fp); return TRUE; } static void pidfile_delete() { FILE *fp; char buf[16]; if ((fp = fopen(pidfile, "r")) == NULL) return; if (fgets(buf, sizeof(buf), fp) == NULL) return; fclose(fp); if (atoi(buf) == getpid()) unlink(pidfile); } static char * getpassword_file(char *dst, char *user) { FILE *fp; char buf[MAXLINELEN]; char *sep = ":"; char *p, *pw_dst, *pw_user, *pw_passwd; if ((fp = fopen(PASSWDFILE, "r")) == NULL) { debugmsg("no authinfo file found"); return NULL; } while (fgets(buf, sizeof(buf), fp) != NULL) { if ((p = strrchr(buf, '\n')) != NULL) *p = '\0'; if ((pw_dst = strtok(buf, sep)) == NULL) continue; if ((pw_user = strtok(NULL, sep)) == NULL) continue; if (strcmp(pw_dst, dst) != 0 || strcmp(pw_user, user) != 0) continue; if ((pw_passwd = strtok(NULL, sep)) == NULL) continue; fclose(fp); return strdup(pw_passwd); } fclose(fp); debugmsg("no relevant authinfo item found"); return NULL; } static char * getpassword_tty(char *user) { char prompt[MAXLINELEN]; char *password; snprintf(prompt, sizeof(prompt), "password for %s: ", user); if ((password = getpass(prompt)) == NULL) return NULL; password = strdup(password); return password; } char * getpassword(char *dst, char *user) { char *password; if ((password = getpassword_file(dst, user)) == NULL) password = getpassword_tty(user); return password; } static int connect_to(char *host, char *port, sa_family_t family) { struct addrinfo hints, *res, *res0; int err, sock; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; if ((err = getaddrinfo(host, port, &hints, &res0)) != 0) { logmsg(LOG_ERR, "getaddrinfo failed (dst=%s port=%s): %s", host, port, gai_strerror(err)); return -1; } for (sock = -1, res = res0; res; res = res->ai_next) { if ((sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) continue; if (connect(sock, res->ai_addr, res->ai_addrlen) != -1) break; close(sock); sock = -1; } freeaddrinfo(res0); if (sock == -1) { logmsg(LOG_ERR, "could not connect to %s port %s", host, port); return -1; } return sock; } static ssize_t putbuf(char *str, size_t nbytes, int fd) { char *cp; ssize_t nleft, nwritten; cp = str; nleft = nbytes; while (nleft > 0) { if ((nwritten = write(fd, cp, nleft)) < 0) { if (errno != EINTR) dtcp_error = TRUE; if (verbose) logmsg(LOG_ERR, "write failed: %s", strerror(errno)); return -1; } nleft -= nwritten; cp += nwritten; } return nbytes - nleft; } static int dtcp_send(int fd, const char *fmt, ...) { char buf[MAXLINELEN]; va_list ap; buf[0] = '\0'; va_start(ap, fmt); vsnprintf(buf, sizeof(buf) - 3, fmt, ap); va_end(ap); debugmsg("<<< %s", buf); strlcat(buf, "\r\n", sizeof(buf)); if (putbuf(buf, strlen(buf), fd) < 0) return -1; return 0; } static ssize_t dtcp_recv(int sock, char *buf, size_t len) { fd_set readfds; int maxsock, nsig, err; struct timeval tv; ssize_t n, nread; maxsock = (sock >= signalpipe[0]) ? sock : signalpipe[0]; nread = 0; while (len > nread) { FD_ZERO(&readfds); FD_SET(sock, &readfds); FD_SET(signalpipe[0], &readfds); tv.tv_sec = TIMEOUT; tv.tv_usec = 0; err = select(maxsock + 1, &readfds, NULL, NULL, &tv); if (err <= 0) { if (err == 0) { if (verbose) logmsg(LOG_ERR, "select timeout"); } else if (errno == EINTR) continue; if (verbose) logmsg(LOG_ERR, "select failed: %s", strerror(errno)); dtcp_error = TRUE; return -1; } if (FD_ISSET(signalpipe[0], &readfds)) { if (ioctl(signalpipe[0], FIONREAD, &nsig) != 0) { logmsg(LOG_ERR, "ioctl failed: %s", strerror(errno)); bye(EX_OSERR); /* NOTREACHED */ } while (--nsig >= 0) { char c; if (read(signalpipe[0], &c, 1) != 1) { if (verbose) logmsg(LOG_ERR, "read failed: %s", strerror(errno)); bye(EX_OSERR); /* NOTREACHED */ } debugmsg("handling signal flag %c", c); switch(c) { case 'H': /* sighup */ reconnect_request = TRUE; return -1; } } } if (FD_ISSET(sock, &readfds)) { if ((n = read(sock, buf + nread, len - nread)) < 0) { if (errno != EINTR) dtcp_error = TRUE; if (verbose) logmsg(LOG_ERR, "read failed: %s", strerror(errno)); return -1; } if (n == 0) continue; nread += n; if (nread < 2 || buf[nread - 1] != '\n' || buf[nread - 2] != '\r') continue; nread -= 2; buf[nread] = '\0'; debugmsg(">>> %s", buf); return nread; } } if (verbose) logmsg(LOG_ERR, "response too long"); return -1; } static void dtcp_close() { if (dtcp_sock < 0) return; if (!dtcp_error) dtcp_send(dtcp_sock, "quit"); shutdown(dtcp_sock, 1); close(dtcp_sock); dtcp_sock = -1; } static boolean tunnel_delete(struct tunnel_info *tunnel) { int i; if (tunnel != NULL) return FALSE; if (tunnel->nickname) { free(tunnel->nickname); tunnel->nickname = NULL; } if (tunnel->server) { free(tunnel->server); tunnel->server = NULL; } if (tunnel->myaddr) { free(tunnel->myaddr); tunnel->myaddr = NULL; } for (i = 0; tunnel->tuninfo[i] != NULL; ++i) { free(tunnel->tuninfo[i]); tunnel->tuninfo[i] = NULL; } free(tunnel); return TRUE; } static struct tunnel_info * tunnel_new(const char *nickname, const char *server, const char *myaddr, const tuntype tuntype, const char *tuninfo[]) { struct tunnel_info *tunnel; int i; tunnel = (struct tunnel_info *)malloc(sizeof(struct tunnel_info)); if (tunnel == NULL) return NULL; memset(tunnel, 0, sizeof(*tunnel)); if (nickname && (tunnel->nickname = strdup(nickname)) == NULL) { tunnel_delete(tunnel); return NULL; } if ((tunnel->server = strdup(server)) == NULL) { tunnel_delete(tunnel); return NULL; } if ((tunnel->myaddr = strdup(myaddr)) == NULL) { tunnel_delete(tunnel); return NULL; } tunnel->tuntype = tuntype; for (i = 0; i < 5 && tuninfo[i] != NULL; ++i) if ((tunnel->tuninfo[i] = strdup(tuninfo[i])) == NULL) { tunnel_delete(tunnel); return NULL; } tunnel->tuninfo[i] = NULL; return tunnel; } static void invoke_script(const char *state, const char *server, const char*myaddr, const tuntype tuntype, const char *tuninfo[]) { pid_t pid; char buf[MAXLINELEN]; char *argv[11]; int argc = 0, i; int status; argv[argc++] = script; argv[argc++] = (char *)state; argv[argc++] = (char *)server; argv[argc++] = (char *)myaddr; argv[argc++] = tuntype_table[tuntype]; for (i = 0; i < 5; ++i) argv[argc++] = (char *)tuninfo[i]; argv[argc] = NULL; if (verbose) { strncpy(buf, "calling:", sizeof(buf)); for (argc = 0; argv[argc] != NULL; ++argc) { strncat(buf, " ", sizeof(buf)); strncat(buf, argv[argc], sizeof(buf)); } logmsg(LOG_NOTICE, "%s", buf); } switch (pid = vfork()) { case -1: logmsg(LOG_ERR, "cannot fork: %s", strerror(errno)); break; case 0: (void)execv(script, argv); _exit(EX_UNAVAILABLE); default: (void)waitpid(pid, &status, 0); } } static boolean node_up(const char *nickname, const char *server, const char *myaddr, const tuntype tuntype, const char *tuninfo[]) { const char *name; tunnel = tunnel_new(nickname, server, myaddr, tuntype, tuninfo); if (tunnel == NULL) return FALSE; name = nickname ? nickname : server; invoke_script("up", name, myaddr, tuntype, tuninfo); return TRUE; } static boolean node_down() { const char *name; if (tunnel == NULL) return FALSE; name = tunnel->nickname ? tunnel->nickname : tunnel->server; invoke_script("down", name, tunnel->myaddr, tunnel->tuntype, (const char **)tunnel->tuninfo); tunnel_delete(tunnel); tunnel = NULL; return TRUE; } static void bye(int status) { dtcp_close(); node_down(); if (daemonize) pidfile_delete(); exit(status); } static boolean getmyaddr(int sock, char *hbuf, int len) { struct sockaddr_storage ss; socklen_t salen; int niflags, err; salen = sizeof(ss); if (getsockname(sock, (struct sockaddr *)&ss, &salen) != 0) { logmsg(LOG_ERR, "getsockname failed: %s", strerror(errno)); return FALSE; } niflags = NI_NUMERICHOST; #ifdef NI_WITHSCOPEID if (((struct sockaddr *)&ss)->sa_family == AF_INET6) niflags |= NI_WITHSCOPEID; #endif err = getnameinfo((struct sockaddr *)&ss, salen, hbuf, len, NULL, 0, niflags); if (err) { logmsg(LOG_ERR, "getnameinfo failed: %s", gai_strerror(err)); return FALSE; } return TRUE; } static void keep_alive(int sock) { char buf[MAXLINELEN]; char *p; while (1) { debugmsg("sleep(60)"); sleep(KEEPALIVE_INTERVAL); if (dtcp_send(sock, "ping") < 0) break; if (dtcp_recv(sock, buf, sizeof(buf)) < 0) break; if ((p = strtok(buf, DELIM)) == NULL || strcmp(p, "+OK") != 0) { if (verbose) logmsg(LOG_ERR, "invalid response"); break; } } } static char * getchallenge(char *buf) { char *challenge, *p; for (challenge = buf; *challenge == ' '; challenge++) ; if (*challenge == '\0') return NULL; for (p = challenge; *p != '\0' && *p != ' '; p++) ; *p = '\0'; return challenge; } static char * getresponse(char *buf) { char *response, *p; for (response = buf; *response == ' '; response++) ; if (*response == '\0') return NULL; for (p = response; *p != '\0' && *p != '\r' && *p != '\n'; p++) ; *p = '\0'; return response; } static boolean check_tuntype(tuntype tuntype, int nargs) { boolean valid = FALSE; switch (nargs) { case 2: if (tuntype == TUNNEL_ONLY) valid = TRUE; break; case 4: if (tuntype == TUNNEL_HOST) valid = TRUE; break; case 3: case 5: if (tuntype == TUNNEL_NETWORK) valid = TRUE; break; } return valid; } static boolean dtcpc(char *nickname, char *dst, char *port, char *username, char *password, tuntype tuntype, int udp_tunnel, int udp_tunnel_port, int udp_napt_port) { char myaddr[NI_MAXHOST], buf[MAXLINELEN], buf2[MAXLINELEN]; char *challenge, *response, *p; char *tuninfo[6]; int i, r; dtcp_error = FALSE; dtcp_connected = FALSE; if ((dtcp_sock = connect_to(dst, port, AF_INET)) < 0) return FALSE; if (!getmyaddr(dtcp_sock, myaddr, sizeof(myaddr))) { dtcp_close(); return FALSE; } dtcp_connected = TRUE; logmsg(LOG_NOTICE, "logging in to %s port %s", dst, port); /* get greeting */ if (dtcp_recv(dtcp_sock, buf, sizeof(buf)) < 0) { dtcp_close(); return FALSE; } if (strncmp(buf, "+OK ", 4) != 0 || (challenge = getchallenge(buf + 4)) == NULL) { if (verbose) logmsg(LOG_ERR, "invalid response"); dtcp_close(); return FALSE; } response = authenticate(username, challenge, password); snprintf(buf, sizeof(buf), "tunnel %s %s %s", username, response, tuntype_table[tuntype]); if (mtu > 0) { snprintf(buf2, sizeof(buf2), " %d", mtu); strlcat(buf, buf2, sizeof(buf)); } if (udp_tunnel) { strlcat(buf, " proto=udp", sizeof(buf)); if (!behind_nat || udp_napt_port > 0) { snprintf(buf2, sizeof(buf2), " port=%d", (udp_napt_port > 0) ? udp_napt_port : udp_tunnel_port); strlcat(buf, buf2, sizeof(buf)); } } r = dtcp_send(dtcp_sock, buf); if (r < 0) { dtcp_close(); return FALSE; } if (dtcp_recv(dtcp_sock, buf, sizeof(buf)) < 0) { dtcp_close(); return FALSE; } if (strncmp(buf, "+OK ", 4) != 0) { if (strncmp(buf, "-ERR ", 5) != 0) { if (verbose) logmsg(LOG_ERR, "invalid response"); dtcp_close(); return FALSE; } if ((response = getresponse(buf + 4)) != NULL) logmsg(LOG_ERR, "failed, reason: %s", response); dtcp_close(); #if 0 if (response != NULL && strncmp(response, "authentication", 14) == 0) { bye(EX_DATAERR); /* NOTREACHED */ } #endif return FALSE; } if ((p = strtok(buf, DELIM)) == NULL || strcmp(p, "+OK") != 0) { if (verbose) logmsg(LOG_ERR, "invalid response"); dtcp_close(); return FALSE; } /* tunnelonly 1: me, 2: her */ /* host 1: me, 2: her 3: me6 4: her6 */ /* network 1: me, 2: her 3: prefix (4: me6, 5: her6) */ i = 0; while ((p = strtok(NULL, DELIM)) != NULL) { if (i > 4) break; tuninfo[i++] = p; } tuninfo[i] = NULL; if (!check_tuntype(tuntype, i)) { if (verbose) logmsg(LOG_ERR, "invalid response"); dtcp_close(); return FALSE; } if (strcmp(myaddr, tuninfo[0]) != 0 && !behind_nat) { logmsg(LOG_ERR, "failed, you are behind a NAT box (%s != %s)", myaddr, tuninfo[0]); dtcp_close(); bye(EX_DATAERR); /* NOTREACHED */ } if (udp_tunnel) { snprintf(buf2, sizeof(buf2), ";%d", udp_tunnel_port); strlcat(myaddr, buf2, sizeof(myaddr)); } node_up(nickname, dst, myaddr, tuntype, (const char **)tuninfo); keep_alive(dtcp_sock); dtcp_close(); node_down(); return TRUE; } static void flag_signal(int c) { char ch = c; if (write(signalpipe[1], &ch, 1) != 1) { logmsg(LOG_ERR, "write failed: %s", strerror(errno)); bye(EX_OSERR); /* NOTREACHED */ } } static void reconnect(int signo __attribute__((__unused__))) { logmsg(LOG_NOTICE, "reconnect by SIGHUP"); if (dtcp_sock >= 0) flag_signal('H'); } static void terminate(int signo) { if (daemonize) { switch (signo) { case SIGTERM: logmsg(LOG_NOTICE, "exit with SIGTERM"); break; case SIGINT: logmsg(LOG_NOTICE, "exit with SIGINT"); break; default: logmsg(LOG_NOTICE, "exit with signal %d", signo); break; } } bye(EX_OK); /* NOTREACHED */ } int main(int argc, char *argv[]) { int c; boolean loop = FALSE; char *progname; char *nickname = NULL; char *dst; char *port = SERVICE_DTCP; tuntype tuntype = TUNNEL_ONLY; char *username = NULL; char *password = NULL; struct passwd *pwd; char dtcp_mtu[10]; int udp_tunnel = FALSE; int udp_tunnel_port = UDP_TUNNEL_PORT, udp_napt_port = 0; progname = basename(argv[0]); if ((pwd = getpwuid(getuid())) != NULL) username = pwd->pw_name; while ((c = getopt(argc, argv, "b:B:dDe:f:hlm:np:s:t:u:U")) != -1) switch (c) { case 'b': udp_tunnel_port = atoi(optarg); break; case 'B': udp_napt_port = atoi(optarg); break; case 'd': debug = TRUE; break; case 'D': daemonize = TRUE; break; case 'e': nickname = optarg; break; case 'f': pidfile = optarg; break; case 'h': usage(progname); /* NOTREACHED */ case 'l': loop = TRUE; break; case 'm': mtu = atoi(optarg); break; case 'n': behind_nat = TRUE; break; case 'p': port = optarg; break; case 's': script = optarg; break; case 't': if (strcmp(optarg, "tunnelonly") == 0) tuntype = TUNNEL_ONLY; else if (strcmp(optarg, "host") == 0) tuntype = TUNNEL_HOST; else if (strcmp(optarg, "network") == 0) tuntype = TUNNEL_NETWORK; else { usage(progname); /* NOTREACHED */ } break; case 'u': username = optarg; break; case 'U': udp_tunnel = TRUE; break; default: usage(progname); /* NOTREACHED */ } argv += optind; argc -= optind; if (argc != 1) { usage(progname); /* NOTREACHED */ } dst = argv[0]; if (daemonize) { daemon(0, 0); openlog((const char *)progname, LOG_PID, LOG_DAEMON); syslog_opened = TRUE; if (verbose) logmsg(LOG_NOTICE, "start"); } password = getpassword(dst, username); if (pipe(signalpipe) != 0) { logmsg(LOG_ERR, "pipe failed: %s", strerror(errno)); exit(EX_OSERR); } if (daemonize) { if (!pidfile_create()) exit(EX_DATAERR); } signal(SIGPIPE, SIG_IGN); signal(SIGTERM, terminate); signal(SIGINT, terminate); signal(SIGHUP, reconnect); if (mtu > 0) { snprintf(dtcp_mtu, sizeof(dtcp_mtu), "%d", mtu); setenv("DTCP_MTU", dtcp_mtu, 1); } while (1) { dtcpc(nickname, dst, port, username, password, tuntype, udp_tunnel, udp_tunnel_port, udp_napt_port); if (reconnect_request) { reconnect_request = FALSE; continue; } if (dtcp_connected) logmsg(LOG_NOTICE, "connection was lost."); if (!loop) break; sleep(RECONNECT_WAIT); } free(password); if (daemonize) pidfile_delete(); exit(EX_OK); }