/* otptunnel.c - a TCP relay server that XORs communication with a file. Written by Volker Schatz and licensed under CC0: http://creativecommons.org/publicdomain/zero/1.0/legalcode compile with: grep ^gcc otptunnel.c | sh gcc -o otptunnel otptunnel.c */ #define _LARGEFILE_SOURCE #define _FILE_OFFSET_BITS 64 #define _BSD_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFSIZE 0x10000 #define EXITOK 0 #define EXITARGERR 1 #define EXITFILEERR 2 #define EXITSOCKERR 3 #define EXITMEMERR 4 #define EXITHANDLER 5 #define EXITPADEMPTY 6 #define ifsockerr(a) if( (a)==-1 && (errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=ECONNABORTED && errno!=EINTR) ) void usageexit(void); void openpad( char *filename, int upstream ); size_t readpad(char *buf, size_t count, int upstream); void parseconnectaddr(char *arg, struct sockaddr_in *connectaddr); int openlistensock( char *arg ); int waitreadable2(int fd1, int fd2); void ferrexit(FILE *handle, const char *format, ...); void sockerrexit(int status, const char *format, ...); void xor( char *dest, const char *src, unsigned count ); void truncatepads(void); void catchsignal(int sig); int main(int argc, char **argv) { struct sigaction sighandler; struct sockaddr_in conaddr; int listsock, consock, accsock; char *bufup, *bufpadup, *bufdown, *bufpaddown; ssize_t recvcount, sendcount; size_t padcount; int readables; if( argc != 5 ) usageexit(); openpad(argv[1], 1); openpad(argv[2], 0); bufup= malloc( 4*BUFSIZE ); if( !bufup ) { fprintf(stderr, "Could not allocate data buffers.\n"); exit(EXITMEMERR); } bufpadup= bufup + BUFSIZE; bufdown= bufup + 2*BUFSIZE; bufpaddown= bufup + 3*BUFSIZE; sighandler.sa_handler= catchsignal; sigemptyset(&sighandler.sa_mask); sighandler.sa_flags= SA_RESTART; if( atexit(truncatepads) || sigaction(SIGINT, &sighandler, NULL) || sigaction(SIGTERM, &sighandler, NULL) || sigaction(SIGUSR1, &sighandler, NULL) ) { fprintf(stderr, "Could not install handler for printing offsets on termination / signal.\n"); exit(EXITHANDLER); } listsock= openlistensock(argv[3]); consock= socket(AF_INET, SOCK_STREAM, 0); sockerrexit(consock, "Could not create upstream socket.\n"); parseconnectaddr(argv[4], &conaddr); while( 13 ) { accsock= accept(listsock, NULL, NULL); if( accsock < 0 ) { if( errno != EAGAIN && errno != EWOULDBLOCK && errno != ECONNABORTED && errno != EINTR ) continue; perror("Error accepting connection on listening socket"); break; } if( 0 > connect(consock, (struct sockaddr *)&conaddr, sizeof(struct sockaddr_in)) ) { perror("Error connecting"); close(accsock); continue; } while( 13 ) { readables= waitreadable2(consock, accsock); if( readables & 1 ) { // downstream recvcount= recv(consock, bufdown, BUFSIZE, 0); if( recvcount <= 0 ) { sockerrexit( recvcount, "Error receiving data from upstream (the socket we connected to)"); break; } padcount= readpad(bufpaddown, (size_t)recvcount, 0); xor(bufdown, bufpaddown, (unsigned)padcount); sendcount= send(accsock, bufdown, (size_t)recvcount, 0); if( sendcount != padcount ) { sockerrexit(sendcount, "Error transmitting data downstream.\n"); fprintf(stderr, "Downstream message only partially transmitted.\n"); exit(EXITSOCKERR); } if( padcount != (size_t)recvcount ) { fprintf(stderr, "Downstream pad file %s exhausted. Terminating\n", argv[2]); exit(EXITPADEMPTY); } } if( readables & 2 ) { // upstream recvcount= recv(accsock, bufup, BUFSIZE, 0); if( recvcount <= 0 ) { sockerrexit( recvcount, "Error receiving data from downstream (on our listening socket)"); break; } padcount= readpad(bufpadup, (size_t)recvcount, 1); xor(bufup, bufpadup, (unsigned)padcount); sendcount= send(consock, bufup, (size_t)recvcount, 0); if( sendcount != recvcount ) { sockerrexit(sendcount, "Error transmitting data upstream.\n"); fprintf(stderr, "Upstream message only partially transmitted.\n"); exit(EXITSOCKERR); } if( padcount != (size_t)recvcount ) { fprintf(stderr, "Upstream pad file %s exhausted. Terminating\n", argv[1]); exit(EXITPADEMPTY); } } } close(consock); close(accsock); } free(bufup); close(listsock); close(consock); return EXITOK; } void usageexit(void) { fprintf(stderr, "usage: otptunnel [:] :\n\n" "Provides one endpoint of a TCP tunnel that XORs all its communication with\n" "one-time pads. Pad 1 is used for upstream communication (from the listening\n" "socket to the one the tunnel itself connects to), pad 2 for downstream. The\n" "pad files are read backwards and truncated when otptunnel terminates. The\n" "other endpoint has to have the same pads 1 and 2 with the same sizes.\n\n" "The first IP address and port is the one on which the tunnel acts as a server.\n" "It opens a listening socket on the interface with the given IP address and on\n" "the given port. This IP address has to be one of the addresses of the\n" "computer on which it is running. If it is omitted, the server will listen on\n" "all interfaces. The second IP address and port is where the tunnel itself\n" "connects to. Both IP addresses have to be numerical.\n\n" "When tickled with a USR1 signal, the pad file names and current offsets are\n" "printed to stdout. (Be aware that this involves a race condition if\n" "communication is in progress.)\n\n" "The return values of this program are the following: %d orderly shutdown after\n" "disconnect of network connections, %d error in command-line arguments, %d file\n" "access error, %d socket access error, %d out of memory, %d error installing\n" "signal / exit routine handler, %d one-time pad exhausted, 128+signr killed by\n" "signal.\n\n", EXITOK, EXITARGERR, EXITFILEERR, EXITSOCKERR, EXITMEMERR, EXITHANDLER, EXITPADEMPTY); exit(EXITARGERR); } static FILE *uppad= NULL, *downpad= NULL; static char *uppadfile, *downpadfile; void openpad( char *filename, int upstream ) { FILE *pad; pad= fopen(filename, "r+b"); ferrexit(pad, "Could not open %s for reading.\n", filename); fseeko(pad, 0, SEEK_END); ferrexit(pad, "Could not seek to end of pad file %s.\n", filename); if( upstream ) { uppad= pad; uppadfile= filename; } else { downpad= pad; downpadfile= filename; } } size_t readpad(char *buf, size_t count, int upstream) { FILE *pad= upstream? uppad : downpad; off_t pos= ftello(pad); size_t readsize; if( count > pos ) count= (size_t)pos; fseeko(pad, -(off_t)count, SEEK_CUR); readsize= fread(buf, 1, count, pad); if( readsize != count ) ferrexit(pad, "Error reading from pad file %s for downstream communication", upstream? uppadfile : downpadfile); fseeko(pad, -(off_t)count, SEEK_CUR); return count; } void parseconnectaddr(char *arg, struct sockaddr_in *connectaddr) { struct addrinfo hints, *list, *scan; char *colon, *end; unsigned long portnr; int status; colon= strchr(arg, ':'); if( !colon ) { fprintf(stderr, "No port number found in `%s' (has to be separated by colon).\n", arg); usageexit(); } portnr= strtoul(colon+1, &end, 0); if( *end || end == colon+1 ) { fprintf(stderr, "Illegal characters in port number in `%s'.\n", arg); usageexit(); } if( portnr > USHRT_MAX ) { fprintf(stderr, "Port number in `%s' too large.\n", arg); usageexit(); } connectaddr->sin_family= AF_INET; connectaddr->sin_port= htons(portnr); *colon= 0; hints.ai_family= PF_INET; hints.ai_socktype= SOCK_STREAM; hints.ai_protocol= 0; hints.ai_flags= 0; status= getaddrinfo(arg, NULL, &hints, &list); if( !status ) for( scan= list; scan; scan= scan->ai_next ) if( scan->ai_addrlen == sizeof(struct sockaddr_in) ) break; if( status || ! scan ) { fprintf(stderr, "Could not resolve host `%s': "); if( status ) fprintf(stderr, "%s\n", arg, gai_strerror(status)); else fprintf(stderr, "No suitable IPv4 address in list.\n"); exit(EXITSOCKERR); } connectaddr->sin_addr.s_addr= ((struct sockaddr_in *)scan->ai_addr)->sin_addr.s_addr; freeaddrinfo(list); } int openlistensock( char *arg ) { int sock; struct sockaddr_in addr; char *colon, *portstr, *end; unsigned long portnr; colon= strchr(arg, ':'); portstr= colon? colon+1 : arg; portnr= strtoul(portstr, &end, 0); if( end == portstr ) { fprintf(stderr, "No port number found in `%s' (has to be separated by colon).\n", arg); usageexit(); } if( *end ) { fprintf(stderr, "Illegal characters in port number in `%s'.\n", portstr); usageexit(); } if( !portnr || portnr > USHRT_MAX ) { fprintf(stderr, "Port number in `%s' is zero or too large.\n", portstr); usageexit(); } addr.sin_family= AF_INET; addr.sin_port= htons(portnr); if( !colon ) { addr.sin_addr.s_addr= INADDR_ANY; } else { *colon= 0; if( !inet_aton(arg, &addr.sin_addr) ) { fprintf(stderr, "Could not parse IP address `%s'. Only numerical IPs are accepted.\n", arg); exit(EXITARGERR); } *colon= ':'; } sock= socket(AF_INET, SOCK_STREAM, 0); sockerrexit(sock, "Could not create downstreamsocket.\n"); sockerrexit( bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)), "Could not bind listening socket to %s.\n", arg); sockerrexit( listen(sock, 0), "Could not make socket listen on %s.\n", arg); return sock; } int waitreadable2(int fd1, int fd2) { fd_set readset; int selstatus, retval; do { FD_ZERO(&readset); FD_SET(fd1, &readset); FD_SET(fd2, &readset); selstatus= select( (fd1 > fd2 ? fd1 : fd2) + 1, &readset, NULL, NULL, NULL); } while( selstatus == -1 && errno == EINTR ); sockerrexit(selstatus, "Error waiting for network data.\n"); retval= 0; if( FD_ISSET(fd1, &readset) ) retval += 1; if( FD_ISSET(fd2, &readset) ) retval += 2; return retval; } void ferrexit(FILE *handle, const char *format, ...) { va_list ap; if( handle && !ferror(handle) ) return; perror(""); if( format ) { va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } exit(EXITFILEERR); } void sockerrexit(int status, const char *format, ...) { va_list ap; if( status != -1 ) return; perror(""); if( format ) { va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } exit(EXITSOCKERR); } void xor( char *dest, const char *src, unsigned count ) { for( ; count; ++dest, ++src, --count ) *dest= *dest ^ *src; } void truncatepads(void) { if( uppad ) ftruncate(fileno(uppad), ftello(uppad)); if( downpad ) ftruncate(fileno(downpad), ftello(downpad)); } void catchsignal(int sig) { if( sig != SIGUSR1 ) // Do the same as usual for SIGINT and SIGTERM, but allow // truncatepads() to be run via atexit(). exit(128 + sig); printf("%s: 0x%llX %s: 0x%llX\n", uppadfile, (unsigned long long)ftello(uppad), downpadfile, (unsigned long long)ftello(downpad)); }