redsocks-0.4+dfsg/0000755000175000017500000000000011757642456014026 5ustar apoikosapoikosredsocks-0.4+dfsg/Makefile0000644000175000017500000000434711733675122015464 0ustar apoikosapoikosOBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend OUT := redsocks VERSION := 0.4 LIBS := -levent CFLAGS += -g -O2 override CFLAGS += -std=gnu99 -Wall all: $(OUT) .PHONY: all clean distclean tags: *.c *.h ctags -R $(CONF): @case `uname` in \ Linux*) \ echo "#define USE_IPTABLES" >$(CONF) \ ;; \ OpenBSD) \ echo "#define USE_PF" >$(CONF) \ ;; \ *) \ echo "Unknown system, only generic firewall code is compiled" 1>&2; \ echo "/* Unknown system, only generic firewall code is compiled */" >$(CONF) \ ;; \ esac # Dependency on .git is useful to rebuild `version.c' after commit, but it breaks non-git builds. gen/version.c: *.c *.h gen/.build rm -f $@.tmp echo '/* this file is auto-generated during build */' > $@.tmp echo '#include "../version.h"' >> $@.tmp echo 'const char* redsocks_version = ' >> $@.tmp if [ -d .git ]; then \ echo '"redsocks.git/'`git describe --tags`'"'; \ if [ `git status --porcelain | grep -v -c '^??'` != 0 ]; then \ echo '"-unclean"'; \ fi \ else \ echo '"redsocks/$(VERSION)"'; \ fi >> $@.tmp echo ';' >> $@.tmp mv -f $@.tmp $@ gen/.build: mkdir -p gen touch $@ base.c: $(CONF) $(DEPS): $(SRCS) gcc -MM $(SRCS) 2>/dev/null >$(DEPS) || \ ( \ for I in $(wildcard *.h); do \ export $${I//[-.]/_}_DEPS="`sed '/^\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$I`"; \ done; \ echo -n >$(DEPS); \ for SRC in $(SRCS); do \ echo -n "$${SRC%.c}.o: " >>$(DEPS); \ export SRC_DEPS="`sed '/\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$SRC | sort`"; \ while true; do \ export SRC_DEPS_OLD="$$SRC_DEPS"; \ export SRC_DEEP_DEPS=""; \ for HDR in $$SRC_DEPS; do \ eval export SRC_DEEP_DEPS="\"$$SRC_DEEP_DEPS \$$$${HDR//[-.]/_}_DEPS\""; \ done; \ export SRC_DEPS="`echo $$SRC_DEPS $$SRC_DEEP_DEPS | sed 's/ */\n/g' | sort -u`"; \ test "$$SRC_DEPS" = "$$SRC_DEPS_OLD" && break; \ done; \ echo $$SRC $$SRC_DEPS >>$(DEPS); \ done; \ ) -include $(DEPS) $(OUT): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) clean: $(RM) $(OUT) $(CONF) $(OBJS) distclean: clean $(RM) tags $(DEPS) $(RM) -r gen redsocks-0.4+dfsg/md5.c0000644000175000017500000002737711733675122014665 0ustar apoikosapoikos/* Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.c is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order either statically or dynamically; added missing #include in library. 2002-03-11 lpd Corrected argument list for main(), and added int return type, in test program and T value program. 2002-02-21 lpd Added missing #include in test program. 2000-07-03 lpd Patched to eliminate warnings about "constant is unsigned in ANSI C, signed in traditional"; made test program self-checking. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 1999-05-03 lpd Original version. */ #include "md5.h" #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else # define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t)~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) #define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) #define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) #define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) #define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) #define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) #define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) #define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) #define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) #define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) #define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) #define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) #define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) #define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) #define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) #define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) #define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) #define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) #define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) #define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) #define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; const md5_word_t *X; #endif { #if BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static const int w = 1; if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ #endif #if BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (const md5_byte_t *)0) & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { /* not aligned */ memcpy(xbuf, data, 64); X = xbuf; } } #endif #if BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; # if BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ # else # define xbuf X /* (static only) */ # endif for (i = 0; i < 16; ++i, xp += 4) xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } #endif } #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + F(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, T1); SET(d, a, b, c, 1, 12, T2); SET(c, d, a, b, 2, 17, T3); SET(b, c, d, a, 3, 22, T4); SET(a, b, c, d, 4, 7, T5); SET(d, a, b, c, 5, 12, T6); SET(c, d, a, b, 6, 17, T7); SET(b, c, d, a, 7, 22, T8); SET(a, b, c, d, 8, 7, T9); SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + G(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, T17); SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); SET(b, c, d, a, 0, 20, T20); SET(a, b, c, d, 5, 5, T21); SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); SET(b, c, d, a, 4, 20, T24); SET(a, b, c, d, 9, 5, T25); SET(d, a, b, c, 14, 9, T26); SET(c, d, a, b, 3, 14, T27); SET(b, c, d, a, 8, 20, T28); SET(a, b, c, d, 13, 5, T29); SET(d, a, b, c, 2, 9, T30); SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti)\ t = a + H(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, T33); SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); SET(a, b, c, d, 1, 4, T37); SET(d, a, b, c, 4, 11, T38); SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); SET(a, b, c, d, 13, 4, T41); SET(d, a, b, c, 0, 11, T42); SET(c, d, a, b, 3, 16, T43); SET(b, c, d, a, 6, 23, T44); SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); SET(b, c, d, a, 2, 23, T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + I(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, T49); SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); SET(b, c, d, a, 5, 21, T52); SET(a, b, c, d, 12, 6, T53); SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); SET(b, c, d, a, 1, 21, T56); SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); SET(c, d, a, b, 2, 15, T63); SET(b, c, d, a, 9, 21, T64); #undef SET /* Then perform the following additions. (That is increment each of the four registers by the value it had before this block was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) { const md5_byte_t *p = data; int left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) pms->count[1]++; /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) md5_process(pms, p); /* Process a final partial block. */ if (left) memcpy(pms->buf, p, left); } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } redsocks-0.4+dfsg/md5.h0000644000175000017500000000650011733675122014653 0ustar apoikosapoikos/* Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.h is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed references to Ghostscript; clarified derivation from RFC 1321; now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); added conditionalization for C++ compilation from Martin Purschke . 1999-05-03 lpd Original version. */ #ifndef md5_INCLUDED # define md5_INCLUDED /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is * defined as non-zero, the code will be compiled to run only on big-endian * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to * run on either big- or little-endian CPUs, but will run slightly less * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. */ typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; #ifdef __cplusplus extern "C" { #endif /* Initialize the algorithm. */ void md5_init(md5_state_t *pms); /* Append a string to the message. */ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); #ifdef __cplusplus } /* end extern "C" */ #endif #endif /* md5_INCLUDED */ redsocks-0.4+dfsg/log.c0000644000175000017500000001135611733675122014747 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include "utils.h" #include "log.h" static const char *lowmem = ""; typedef void (*log_func)(const char *file, int line, const char *func, int priority, const char *message, const char *appendix); static void fprint_timestamp( FILE* fd, const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { struct timeval tv = { }; gettimeofday(&tv, 0); /* XXX: there is no error-checking, IMHO it's better to lose messages * then to die and stop service */ if (appendix) fprintf(fd, "%lu.%6.6lu %s:%u %s(...) %s: %s\n", tv.tv_sec, tv.tv_usec, file, line, func, message, appendix); else fprintf(fd, "%lu.%6.6lu %s:%u %s(...) %s\n", tv.tv_sec, tv.tv_usec, file, line, func, message); } static void stderr_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { fprint_timestamp(stderr, file, line, func, priority, message, appendix); } static FILE *logfile = NULL; static void logfile_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { fprint_timestamp(logfile, file, line, func, priority, message, appendix); fflush(logfile); } static void syslog_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { if (appendix) syslog(priority, "%s: %s\n", message, appendix); else syslog(priority, "%s\n", message); } static log_func log_msg = stderr_msg; static log_func log_msg_next = NULL; int log_preopen(const char *dst, bool log_debug, bool log_info) { const char *syslog_prefix = "syslog:"; const char *file_prefix = "file:"; if (strcmp(dst, "stderr") == 0) { log_msg_next = stderr_msg; } else if (strncmp(dst, syslog_prefix, strlen(syslog_prefix)) == 0) { const char *facility_name = dst + strlen(syslog_prefix); int facility = -1; int logmask; struct { char *name; int value; } *ptpl, tpl[] = { { "daemon", LOG_DAEMON }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, }; FOREACH(ptpl, tpl) if (strcmp(facility_name, ptpl->name) == 0) { facility = ptpl->value; break; } if (facility == -1) { log_error(LOG_ERR, "log_preopen(%s, ...): unknown syslog facility", dst); return -1; } openlog("redsocks", LOG_NDELAY | LOG_PID, facility); logmask = setlogmask(0); if (!log_debug) logmask &= ~(LOG_MASK(LOG_DEBUG)); if (!log_info) logmask &= ~(LOG_MASK(LOG_INFO)); setlogmask(logmask); log_msg_next = syslog_msg; } else if (strncmp(dst, file_prefix, strlen(file_prefix)) == 0) { const char *filename = dst + strlen(file_prefix); if ((logfile = fopen(filename, "a")) == NULL) { log_error(LOG_ERR, "log_preopen(%s, ...): %s", dst, strerror(errno)); return -1; } log_msg_next = logfile_msg; /* TODO: add log rotation */ } else { log_error(LOG_ERR, "log_preopen(%s, ...): unknown destination", dst); return -1; } return 0; } void log_open() { log_msg = log_msg_next; log_msg_next = NULL; } void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap) { int saved_errno = errno; struct evbuffer *buff = evbuffer_new(); const char *message; if (buff) { evbuffer_add_vprintf(buff, fmt, ap); message = (const char*)EVBUFFER_DATA(buff); } else message = lowmem; log_msg(file, line, func, priority, message, do_errno ? strerror(saved_errno) : NULL); if (buff) evbuffer_free(buff); } void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) { va_list ap; va_start(ap, fmt); _log_vwrite(file, line, func, do_errno, priority, fmt, ap); va_end(ap); } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/log.h0000644000175000017500000000155011733675122014747 0ustar apoikosapoikos#ifndef LOG_H_WED_JAN_24_18_21_27_2007 #define LOG_H_WED_JAN_24_18_21_27_2007 #include #include #include #define log_errno(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 1, prio, ## msg) #define log_error(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 0, prio, ## msg) int log_preopen(const char *dst, bool log_debug, bool log_info); void log_open(); void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap); void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 6, 7) )) #endif ; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* LOG_H_WED_JAN_24_18_21_27_2007 */ redsocks-0.4+dfsg/README0000644000175000017500000001531211733675122014676 0ustar apoikosapoikosThis tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection is system-wide. Why is that useful? I can suggest following reasons: * you use tor[1] and don't want any TCP connection to leak. * you use DVB ISP and this ISP provides internet connectivity with some special daemon that may be also called "Internet accelerator" and this accelerator acts as proxy. Globax[2] is example of such an accelerator. Linux/iptables, OpenBSD/pf and FreeBSD/ipfw are supported. Linux/iptables is well-tested, other implementations may have bugs, your bugreports are welcome. Transocks[3] is alike project but it has noticable performance penality. Transsocks_ev[4] is alike project too, but it has no HTTPS-proxy support and does not support authentication. Several Andoird apps also use redsocks under-the-hood: ProxyDroid[5][6] and sshtunnel[7][8]. And that's over 100'000 downloads! Wow! [1] http://www.torproject.org [2] http://www.globax.biz [3] http://transocks.sourceforge.net/ [4] http://oss.tiggerswelt.net/transocks_ev/ [5] http://code.google.com/p/proxydroid/ [6] https://market.android.com/details?id=org.proxydroid [7] http://code.google.com/p/sshtunnel/ [8] https://market.android.com/details?id=org.sshtunnel Another related issue is DNS over TCP. Redsocks includes `dnstc' that is fake and really dumb DNS server that returns "truncated answer" to every query via UDP. RFC-compliant resolver should repeat same query via TCP in this case - so the request can be redirected using usual redsocks facilities. Known compliant resolvers are: * bind9 (server) * dig, nslookup (tools based on bind9 code) Known non-compliant resolvers are: * eglibc resolver fails without any attempt to send request via TCP * powerdns-recursor can't properly startup without UDP connectivity as it can't load root hints On the other hand, DNS via TCP using bind9 may be painfully slow. If your bind9 setup is really slow, you have at least two options: pdnsd[9] caching server can run in TCP-only mode, ttdnsd[10][11] has no cache but can be useful for same purpose. [9] http://www.phys.uu.nl/~rombouts/pdnsd.html [10] http://www.mulliner.org/collin/ttdnsd.php [11] https://gitweb.torproject.org/ioerror/ttdnsd.git Features ======== Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) proxy server. Login/password authentication is supported for SOCKS5/HTTPS connections. SOCKS4 supports only username, password is ignored. for HTTPS, currently only Basic and Digest scheme is supported. Redirect UDP packets via SOCKS5 proxy server. NB: UDP still goes via UDP, so you can't relay UDP via OpenSSH. Sends "truncated reply" as an answer to UDP DNS queries. Redirect any HTTP connection to proxy that does not support transparent proxying (e.g. old SQUID had broken `acl myport' for such connections). License ======= All source code is licensed under Apache 2.0 license. You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html Compilation =========== libevent[5] is required. gcc and clang are supported right now, other compilers can be used but may require some code changes. Compilation is as easy as running `make', there is no `./configure' magic. GNU Make works, other implementations of make were not tested. [5] http://libevent.org/ || http://www.monkey.org/~provos/libevent/ Running ======= Program has following command-line options: -c sets proper path to config file ("./redsocks.conf" is default one) -t tests config file syntax -p set a file to write the getpid() into Following signals are understood: SIGUSR1 dumps list of connected clients to log SIGTERM and SIGINT terminates daemon, all active connections are closed You can see configuration file example in redsocks.conf.example iptables example ================ You have to build iptables with connection tracking and REDIRECT target. # Create new chain root# iptables -t nat -N REDSOCKS # Ignore LANs and some other reserved addresses. # See http://en.wikipedia.org/wiki/Reserved_IP_addresses#Reserved_IPv4_addresses # and http://tools.ietf.org/html/rfc5735 for full list of reserved networks. root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN # Anything else should be redirected to port 12345 root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345 # Any tcp connection made by `luser' should be redirected. root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS # You can also control that in more precise way using `gid-owner` from # iptables. root# groupadd socksified root# usermod --append --groups socksified luser root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS # Now you can launch your specific application with GID `socksified` and it # will be... socksified. See following commands (numbers may vary). # Note: you may have to relogin to apply `usermod` changes. luser$ id uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified) luser$ sg socksified -c id uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified) luser$ sg socksified -c "firefox" # If you want to configure socksifying router, you should look at # doc/iptables-packet-flow.png and doc/iptables-packet-flow-ng.png # Note, you should have proper `local_ip' value to get external packets with # redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding # REDIRECT target for details. # Depending on your network configuration iptables conf. may be as easy as: root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS Note about GID-based redirection ======== Keep in mind, that changed GID affects filesystem permissions, so if your application creates some files, the files will be created with luser:socksified owner/group. So, if you're not the only user in the group `socksified` and your umask allows to create group-readable files and your directory permissions, and so on, blah-blah, etc. THEN you may expose your files to another user. Ok, you have been warned. Homepage ======== http://darkk.net.ru/redsocks/ Mailing list: redsocks@librelist.com Mailing list also has archives[1]. [1] http://librelist.com/browser/redsocks/ TODO ==== Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those firewall types. Author ====== This program was written by Leonid Evdokimov redsocks-0.4+dfsg/dnstc.c0000644000175000017500000001463011733675122015277 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include "list.h" #include "log.h" #include "parser.h" #include "main.h" #include "redsocks.h" #include "dnstc.h" #include "utils.h" #define dnstc_log_error(prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.bindaddr, prio, ## msg) #define dnstc_log_errno(prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.bindaddr, prio, ## msg) static void dnstc_fini_instance(dnstc_instance *instance); static int dnstc_fini(); typedef struct dns_header_t { uint16_t id; uint8_t qr_opcode_aa_tc_rd; uint8_t ra_z_rcode; uint16_t qdcount; uint16_t ancount; uint16_t nscount; uint16_t arcount; } PACKED dns_header; #define DNS_QR 0x80 #define DNS_TC 0x02 #define DNS_Z 0x70 /*********************************************************************** * Logic */ static void dnstc_pkt_from_client(int fd, short what, void *_arg) { dnstc_instance *self = _arg; struct sockaddr_in clientaddr; union { char raw[0xFFFF]; // UDP packet can't be larger then that dns_header h; } buf; ssize_t pktlen, outgoing; assert(fd == EVENT_FD(&self->listener)); pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr); if (pktlen == -1) return; if (pktlen <= sizeof(dns_header)) { dnstc_log_error(LOG_INFO, "incomplete DNS request"); return; } if (1 && (buf.h.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ && (buf.h.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ && buf.h.qdcount /* some questions */ && !buf.h.ancount && !buf.h.nscount && !buf.h.arcount /* no answers */ ) { buf.h.qr_opcode_aa_tc_rd |= DNS_QR; buf.h.qr_opcode_aa_tc_rd |= DNS_TC; outgoing = sendto(fd, buf.raw, pktlen, 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr)); if (outgoing != pktlen) dnstc_log_errno(LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", pktlen, outgoing); else dnstc_log_error(LOG_INFO, "sent truncated DNS reply"); } else { dnstc_log_error(LOG_INFO, "malformed DNS request"); } } /*********************************************************************** * Init / shutdown */ static parser_entry dnstc_entries[] = { { .key = "local_ip", .type = pt_in_addr }, { .key = "local_port", .type = pt_uint16 }, { } }; static list_head instances = LIST_HEAD_INIT(instances); static int dnstc_onenter(parser_section *section) { dnstc_instance *instance = calloc(1, sizeof(*instance)); if (!instance) { parser_error(section->context, "Not enough memory"); return -1; } INIT_LIST_HEAD(&instance->list); instance->config.bindaddr.sin_family = AF_INET; instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : NULL; section->data = instance; return 0; } static int dnstc_onexit(parser_section *section) { dnstc_instance *instance = section->data; section->data = NULL; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = NULL; instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); list_add(&instance->list, &instances); return 0; } static int dnstc_init_instance(dnstc_instance *instance) { /* FIXME: dnstc_fini_instance is called in case of failure, this * function will remove instance from instances list - result * looks ugly. */ int error; int fd = -1; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } event_set(&instance->listener, fd, EV_READ | EV_PERSIST, dnstc_pkt_from_client, instance); error = event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } return 0; fail: dnstc_fini_instance(instance); if (fd != -1) { if (close(fd) != 0) log_errno(LOG_WARNING, "close"); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void dnstc_fini_instance(dnstc_instance *instance) { if (event_initialized(&instance->listener)) { if (event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); if (close(EVENT_FD(&instance->listener)) != 0) log_errno(LOG_WARNING, "close"); memset(&instance->listener, 0, sizeof(instance->listener)); } list_del(&instance->list); memset(instance, 0, sizeof(*instance)); free(instance); } static int dnstc_init() { dnstc_instance *tmp, *instance = NULL; // TODO: init debug_dumper list_for_each_entry_safe(instance, tmp, &instances, list) { if (dnstc_init_instance(instance) != 0) goto fail; } return 0; fail: dnstc_fini(); return -1; } static int dnstc_fini() { dnstc_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) dnstc_fini_instance(instance); return 0; } static parser_section dnstc_conf_section = { .name = "dnstc", .entries = dnstc_entries, .onenter = dnstc_onenter, .onexit = dnstc_onexit }; app_subsys dnstc_subsys = { .init = dnstc_init, .fini = dnstc_fini, .conf_section = &dnstc_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/dnstc.h0000644000175000017500000000056711733675122015310 0ustar apoikosapoikos#ifndef DNSTC_H #define DNSTC_H typedef struct dnstc_config_t { struct sockaddr_in bindaddr; } dnstc_config; typedef struct dnstc_instance_t { list_head list; dnstc_config config; struct event listener; } dnstc_instance; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDUDP_H */ redsocks-0.4+dfsg/parser.c0000644000175000017500000003360411733675122015462 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "parser.h" #include "log.h" #define FREE(ptr) do { free(ptr); ptr = NULL; } while (0) typedef int (*value_parser)(parser_context *context, void *addr, const char *token); struct parser_context_t { FILE *fd; parser_section *sections; int line; int error; parser_errhandler errhandler; struct { size_t size; size_t filled; char *data; } buffer; }; void parser_error(parser_context *context, const char *msg) { context->error = 1; if (context->errhandler) context->errhandler(msg, context->line); else fprintf(stderr, "file parsing error at line %u: %s\n", context->line, msg); } parser_context* parser_start(FILE *fd, parser_errhandler errhandler) { parser_context *ret = calloc(1, sizeof(parser_context)); if (!ret) return NULL; ret->fd = fd; ret->errhandler = errhandler; ret->buffer.size = 128; // should be big enough to fetch whole ``line`` ret->buffer.data = malloc(ret->buffer.size); if (!ret->buffer.data) { free(ret); return NULL; } return ret; } void parser_add_section(parser_context *context, parser_section *section) { section->next = context->sections; context->sections = section; section->context = context; } void parser_stop(parser_context *context) { free(context->buffer.data); free(context); } static char unescape(int c) { switch (c) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; case '\\': return '\\'; case '\'': return '\''; case '\"': return '\"'; default: return 0; } } /** returns NULL on invalid OR incomplete token */ static char *gettoken(parser_context *context, char **iter) { char *ret = NULL; size_t len = 0; enum { gt_cstr, gt_plainstr } copytype; // skip spaces while (**iter && isspace(**iter)) (*iter)++; if ( !**iter ) return NULL; // count strlen() of output buffer if ( **iter == '\"' ) { // string with escapes and spaces char *p = *iter + 1; copytype = gt_cstr; while ( 1 ) { if (*p == '\0') return NULL; if (*p == '\"') break; if (*p == '\\') { if ( p[1] != '\0') { if ( unescape(p[1]) ) p++; else { parser_error(context, "unknown escaped char after \\"); return NULL; } } else { return NULL; } } len++; p++; } } else if ( isdigit(**iter) ) { // integer OR IP/NETMASK char *p = *iter; copytype = gt_plainstr; while ( 1 ) { if ( *p == '\0' ) return NULL; else if ( isdigit(*p) || *p == '.' ) p++; else if ( *p == '/' ) { if (isdigit(p[1])) p++; else if (p[1] == '/' || p[1] == '*') // comment token is coming! break; else return NULL; } else break; } len = p - *iter; } else if ( isalpha(**iter) ) { // simple-string char *p = *iter; copytype = gt_plainstr; while ( 1 ) { if ( *p == '\0' ) return NULL; else if (isalnum(*p) || *p == '_' || *p == '.' || *p == '-') // for domain-names p++; else break; } len = p - *iter; } else if ( **iter == '{' || **iter == '}' || **iter == '=' || **iter == ';' ) { // config punctuation copytype = gt_plainstr; len = 1; } else if ( **iter == '/' && ( (*iter)[1] == '/' || (*iter)[1] == '*' ) ) { // comment-start copytype = gt_plainstr; len = 2; } else { parser_error(context, "unexpected char"); return NULL; } ret = malloc(len + 1); if (!ret) { parser_error(context, "malloc failed"); return NULL; } if (copytype == gt_cstr) { char *p = ret; (*iter)++; while ( 1 ) { if (**iter == '\"') { (*iter)++; break; } if (**iter == '\\') { *p = unescape(*(*iter + 1)); (*iter)++; } else { *p = **iter; } (*iter)++; p++; } *p = 0; } else if (copytype == gt_plainstr) { memcpy(ret, *iter, len); *iter += len; ret[len] = 0; } return ret; } static void context_filled_add(parser_context *context, int shift) { context->buffer.filled += shift; context->buffer.data[context->buffer.filled] = 0; } static void context_filled_set(parser_context *context, size_t val) { context->buffer.filled = val; context->buffer.data[context->buffer.filled] = 0; } static int vp_pbool(parser_context *context, void *addr, const char *token) { char *strtrue[] = { "ok", "on", "yes", "true" }; char *strfalse[] = { "off", "no", "false" }; char **tpl; FOREACH(tpl, strtrue) if (strcmp(token, *tpl) == 0) { *(bool*)addr = true; return 0; } FOREACH(tpl, strfalse) if (strcmp(token, *tpl) == 0) { *(bool*)addr = false; return 0; } parser_error(context, "boolean is not parsed"); return -1; } static int vp_pchar(parser_context *context, void *addr, const char *token) { char *p = strdup(token); if (!p) { parser_error(context, "strdup failed"); return -1; } *(char**)addr = p; return 0; } static int vp_uint16(parser_context *context, void *addr, const char *token) { char *end; unsigned long int uli = strtoul(token, &end, 0); if (uli > 0xFFFF) { parser_error(context, "integer out of 16bit range"); return -1; } if (*end != '\0') { parser_error(context, "integer is not parsed"); return -1; } *(uint16_t*)addr = (uint16_t)uli; return 0; } static int vp_in_addr(parser_context *context, void *addr, const char *token) { struct in_addr ia; if (inet_aton(token, &ia)) { memcpy(addr, &ia, sizeof(ia)); } else { struct addrinfo *addr, hints; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; /* IPv4-only */ hints.ai_socktype = SOCK_STREAM; /* I want to have one address once and ONLY once, that's why I specify socktype and protocol */ hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_ADDRCONFIG; /* I don't need IPv4 addrs without IPv4 connectivity */ err = getaddrinfo(token, NULL, &hints, &addr); if (err == 0) { int count, taken; struct addrinfo *iter; struct sockaddr_in *resolved_addr; for (iter = addr, count = 0; iter; iter = iter->ai_next, ++count) ; taken = rand() % count; for (iter = addr; taken > 0; iter = iter->ai_next, --taken) ; resolved_addr = (struct sockaddr_in*)iter->ai_addr; assert(resolved_addr->sin_family == iter->ai_family && iter->ai_family == AF_INET); if (count != 1) log_error(LOG_WARNING, "%s resolves to %d addresses, using %s", token, count, inet_ntoa(resolved_addr->sin_addr)); memcpy(addr, &resolved_addr->sin_addr, sizeof(ia)); freeaddrinfo(addr); } else { if (err == EAI_SYSTEM) parser_error(context, strerror(errno)); else parser_error(context, gai_strerror(err)); return -1; } } return 0; } static int vp_in_addr2(parser_context *context, void *addr, const char *token) { char *host = NULL, *mask = NULL; struct in_addr ia; int retval = 0; host = strdup(token); if (!host) { parser_error(context, "strdup failed"); return -1; } mask = strchr(host, '/'); if (mask) { *mask = '\0'; mask++; } if (inet_aton(host, &ia)) { memcpy(addr, &ia, sizeof(ia)); } else { parser_error(context, "invalid IP address"); retval = -1; } if (mask) { struct in_addr *pinmask = ((struct in_addr*)addr) + 1; char *end; unsigned long int uli = strtoul(mask, &end, 0);; if (*end == '.') { if (inet_aton(mask, &ia)) { memcpy(pinmask , &ia, sizeof(ia)); } else { parser_error(context, "invalid IP address"); retval = -1; } } else if (0 < uli && uli < 32) { pinmask->s_addr = htonl((INADDR_BROADCAST << (32 - uli))); } else { parser_error(context, "number of netmask bits out of range"); retval = -1; } } free(host); return retval; } static value_parser value_parser_by_type[] = { [pt_bool] = vp_pbool, [pt_pchar] = vp_pchar, [pt_uint16] = vp_uint16, [pt_in_addr] = vp_in_addr, [pt_in_addr2] = vp_in_addr2, }; int parser_run(parser_context *context) { char *section_token = NULL, *key_token = NULL, *value_token = NULL; parser_section *section = NULL; bool in_comment = false; bool need_more_space = false; bool need_more_data = true; while ( !context->error && !feof(context->fd) ) { assert(context->buffer.filled < context->buffer.size); // ``<`` and not ``<=`` if (need_more_space) { char *new = realloc(context->buffer.data, context->buffer.size * 2); if (!new) { parser_error(context, "realloc failure"); return -1; } context->buffer.data = new; context->buffer.size *= 2; need_more_space = false; } if (need_more_data) { // read one line per call char *sbegin = context->buffer.data + context->buffer.filled; int len; if (fgets(sbegin, context->buffer.size - context->buffer.filled, context->fd) == NULL) { if (ferror(context->fd)) { parser_error(context, "file read failure"); return -1; } else continue; } len = strlen(sbegin); context_filled_add(context, +len); if (len > 0 && sbegin[len - 1] == '\n') { context->line++; need_more_data = false; } else { need_more_space = true; continue; } } if ( in_comment ) { char *endc = strstr(context->buffer.data, "*/"); if (endc) { endc += 2; int comment_len = endc - context->buffer.data; memmove(context->buffer.data, endc, context->buffer.filled - comment_len); context_filled_add(context, -comment_len); in_comment = false; } else { context_filled_set(context, 0); need_more_data = true; continue; } } char *token = NULL, *iter = context->buffer.data; while ( !context->error && !in_comment && (token = gettoken(context, &iter)) ) { if (strcmp(token, "//") == 0) { char *endc = strchr(iter, '\n'); iter -= 2; endc += 1; // |*data |*iter |*endc -->|<--.filled int moved_len = context->buffer.filled - (endc - context->buffer.data); memmove(iter, endc, moved_len); context_filled_add(context, -(endc - iter)); } else if (strcmp(token, "/*") == 0) { int moved_len = iter - context->buffer.data; memmove(context->buffer.data, iter, context->buffer.filled - moved_len); context_filled_add(context, -moved_len); iter = context->buffer.data; in_comment = true; } else if (strcmp(token, "{") == 0) { // } - I love folding if (section) { parser_error(context, "section-in-section is invalid"); } else if (!section_token) { parser_error(context, "expected token before ``{''"); // } - I love folding } else { for (parser_section *p = context->sections; p; p = p->next) { if (strcmp(p->name, section_token) == 0) { section = p; break; } } if (section) { if (section->onenter) if ( section->onenter(section) == -1 ) parser_error(context, "section->onenter failed"); } else { parser_error(context, "unknown section"); } FREE(section_token); } } else if (strcmp(token, "}") == 0) { // { - I love folding if (section) { if (section->onexit) if ( section->onexit(section) == -1 ) parser_error(context, "section->onexit failed"); section = NULL; } else { parser_error(context, "can't close non-opened section"); } } else if (strcmp(token, "=") == 0) { if (!section) { parser_error(context, "assignment used outside of any section"); } else if (!key_token) { parser_error(context, "assignment used without key"); } else { ; // What can I do? :) } } else if (strcmp(token, ";") == 0) { if (!section) { parser_error(context, "assignment termination outside of any section"); } else if (key_token && !value_token) { parser_error(context, "assignment has only key but no value"); } else if (key_token && value_token) { parser_entry *e; for (e = section->entries; e->key; e++) if (strcmp(e->key, key_token) == 0) break; if (e) { if ( (value_parser_by_type[e->type])(context, e->addr, value_token) == -1 ) parser_error(context, "value can't be parsed"); } else { parser_error(context, "assignment with unknown key"); } } else { assert(false); } FREE(key_token); FREE(value_token); } else { if (!section && !section_token) { section_token = token; token = 0; } else if (section && !key_token) { key_token = token; token = 0; } else if (section && key_token && !value_token) { value_token = token; token = 0; } else { parser_error(context, "invalid token order"); } } free(token); } int moved_len = iter - context->buffer.data; memmove(context->buffer.data, iter, context->buffer.filled - moved_len); context_filled_add(context, -moved_len); if (!token) need_more_data = true; } // file-error goes is thrown here if (section) parser_error(context, "unclosed section"); if (section_token) { parser_error(context, "stale section_token"); free(section_token); } if (key_token) { parser_error(context, "stale key_token"); free(key_token); } if (value_token) { parser_error(context, "stale value_token"); free(value_token); } return context->error ? -1 : 0; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/parser.h0000644000175000017500000000274211733675122015466 0ustar apoikosapoikos#ifndef PARSER_H_THU_JAN_11_04_49_38_2007 #define PARSER_H_THU_JAN_11_04_49_38_2007 #include #include typedef enum { pt_bool, // "bool" from stdbool.h, not "_Bool" or anything else pt_pchar, pt_uint16, pt_in_addr, pt_in_addr2, // inaddr[0] = net, inaddr[1] = netmask } parser_type; typedef struct parser_entry_t { const char *key; parser_type type; void *addr; } parser_entry; typedef struct parser_context_t parser_context; typedef struct parser_section_t parser_section; typedef int (*parser_section_onenter)(parser_section *section); typedef int (*parser_section_onexit)(parser_section *section); struct parser_section_t { parser_section *next; parser_context *context; const char *name; parser_section_onenter onenter; // is called on entry to section parser_section_onexit onexit; // is called on exit from section parser_entry *entries; void *data; }; typedef void (*parser_errhandler)(const char *errmsg, int line); parser_context* parser_start(FILE *fd, parser_errhandler errhandler); void parser_add_section(parser_context *context, parser_section *section); int parser_run(parser_context *context); void parser_error(parser_context *context, const char *msg); void parser_stop(parser_context *context); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* PARSER_H_THU_JAN_11_04_49_38_2007 */ redsocks-0.4+dfsg/http-auth.c0000644000175000017500000001621011733675122016076 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * * http-auth library, provide basic and digest scheme * see RFC 2617 for details */ #include #include #include #include #include "md5.h" #include "base64.h" #include "http-auth.h" char* basic_authentication_encode(const char *user, const char *passwd) { /* prepare the user:pass key pair */ int pair_len = strlen(user) + 1 + strlen(passwd); char *pair_ptr = calloc(pair_len + 1, 1); sprintf(pair_ptr, "%s:%s", user, passwd); /* calculate the final string length */ int basic_len = BASE64_SIZE(pair_len); char *basic_ptr = calloc(basic_len + 1, 1); if (!base64_encode(basic_ptr, basic_len, (const uint8_t*)pair_ptr, pair_len)) return NULL; return basic_ptr; } #define MD5_HASHLEN (16) static void dump_hash(char *buf, const unsigned char *hash) { int i; for (i = 0; i < MD5_HASHLEN; i++) { buf += sprintf(buf, "%02x", hash[i]); } *buf = 0; } typedef struct { const char *b, *e; } param_token; /* Extract a parameter from the string (typically an HTTP header) at **SOURCE and advance SOURCE to the next parameter. Return false when there are no more parameters to extract. The name of the parameter is returned in NAME, and the value in VALUE. If the parameter has no value, the token's b==e.*/ static int extract_param(const char **source, param_token *name, param_token *value, char separator) { const char *p = *source; while (isspace (*p)) ++p; if (!*p) { *source = p; return 0; /* no error; nothing more to extract */ } /* Extract name. */ name->b = p; while (*p && !isspace (*p) && *p != '=' && *p != separator) ++p; name->e = p; if (name->b == name->e) return 0; /* empty name: error */ while (isspace (*p)) ++p; if (*p == separator || !*p) /* no value */ { value->b = value->e = ""; if (*p == separator) ++p; *source = p; return 1; } if (*p != '=') return 0; /* error */ /* *p is '=', extract value */ ++p; while (isspace (*p)) ++p; if (*p == '"') /* quoted */ { value->b = ++p; while (*p && *p != '"') ++p; if (!*p) return 0; value->e = p++; /* Currently at closing quote; find the end of param. */ while (isspace (*p)) ++p; while (*p && *p != separator) ++p; if (*p == separator) ++p; else if (*p) /* garbage after closed quote, e.g. foo="bar"baz */ return 0; } else /* unquoted */ { value->b = p; while (*p && *p != separator) ++p; value->e = p; while (value->e != value->b && isspace (value->e[-1])) --value->e; if (*p == separator) ++p; } *source = p; return 1; } char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce) { char *realm = NULL, *opaque = NULL, *nonce = NULL, *qop = NULL; char nc[9]; sprintf(nc, "%08x", count); const char *ptr = line; param_token name, value; while (extract_param(&ptr, &name, &value, ',')) { int namelen = name.e - name.b; int valuelen = value.e - value.b; if (strncasecmp(name.b, "realm" , namelen) == 0) { strncpy(realm = calloc(valuelen + 1, 1), value.b, valuelen); realm[valuelen] = '\0'; } else if (strncasecmp(name.b, "opaque", namelen) == 0) { strncpy(opaque = calloc(valuelen + 1, 1), value.b, valuelen); opaque[valuelen] = '\0'; } else if (strncasecmp(name.b, "nonce" , namelen) == 0) { strncpy(nonce = calloc(valuelen + 1, 1), value.b, valuelen); nonce[valuelen] = '\0'; } else if (strncasecmp(name.b, "qop" , namelen) == 0) { strncpy(qop = calloc(valuelen + 1, 1), value.b, valuelen); qop[valuelen] = '\0'; } } if (!realm || !nonce || !user || !passwd || !path || !method) { free(realm); free(opaque); free(nonce); free(qop); return NULL; } if (qop && strncasecmp(qop, "auth", 5) != 0) { /* FIXME: currently don't support auth-int, only "auth" is supported */ free(realm); free(opaque); free(nonce); free(qop); return NULL; } /* calculate the digest value */ md5_state_t ctx; md5_byte_t hash[MD5_HASHLEN]; char a1buf[MD5_HASHLEN * 2 + 1], a2buf[MD5_HASHLEN * 2 + 1]; char response[MD5_HASHLEN * 2 + 1]; /* A1 = username-value ":" realm-value ":" passwd */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)user, strlen(user)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)realm, strlen(realm)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)passwd, strlen(passwd)); md5_finish(&ctx, hash); dump_hash(a1buf, hash); /* A2 = Method ":" digest-uri-value */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)method, strlen(method)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)path, strlen(path)); md5_finish(&ctx, hash); dump_hash(a2buf, hash); /* qop set: request-digest = H(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" H(A2) */ /* not set: request-digest = H(A1) ":" nonce-value ":" H(A2) */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)a1buf, strlen(a1buf)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)nonce, strlen(nonce)); md5_append(&ctx, (md5_byte_t*)":", 1); if (qop) { md5_append(&ctx, (md5_byte_t*)nc, strlen(nc)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)cnonce, strlen(cnonce)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)qop, strlen(qop)); md5_append(&ctx, (md5_byte_t*)":", 1); } md5_append(&ctx, (md5_byte_t*)a2buf, strlen(a2buf)); md5_finish(&ctx, hash); dump_hash(response, hash); /* prepare the final string */ int len = 256; len += strlen(user); len += strlen(realm); len += strlen(nonce); len += strlen(path); len += strlen(response); if (qop) { len += strlen(qop); len += strlen(nc); len += strlen(cnonce); } if (opaque) { len += strlen(opaque); } char *res = (char*)malloc(len); if (!qop) { sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", user, realm, nonce, path, response); } else { sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=%s, cnonce=\"%s\"", user, realm, nonce, path, response, qop, nc, cnonce); } if (opaque) { char *p = res + strlen(res); strcat (p, ", opaque=\""); strcat (p, opaque); strcat (p, "\""); } free(realm); free(opaque); free(nonce); free(qop); return res; } const char *auth_request_header = "Proxy-Authenticate:"; const char *auth_response_header = "Proxy-Authorization:"; redsocks-0.4+dfsg/http-auth.h0000644000175000017500000000132711733675122016106 0ustar apoikosapoikos#ifndef HTTP_AUTH_H #define HTTP_AUTH_H typedef struct http_auth_t { char *last_auth_query; int last_auth_count; } http_auth; /* * Create the authentication header contents for the `Basic' scheme. * This is done by encoding the string "USER:PASS" to base64 and * prepending the string "Basic " in front of it. * */ char* basic_authentication_encode(const char *user, const char *passwd); /* * Create the authentication header contents for the 'Digest' scheme. * only md5 method is available, see RFC 2617 for detail. * */ char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce); #endif /* HTTP_AUTH_H */ redsocks-0.4+dfsg/base.c0000644000175000017500000002604111733675122015075 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #if defined USE_IPTABLES # include # include #endif #if defined USE_PF # include # include # include # include #endif #include "log.h" #include "main.h" #include "parser.h" #include "redsocks.h" typedef struct redirector_subsys_t { int (*init)(); void (*fini)(); int (*getdestaddr)(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); const char *name; // some subsystems may store data here: int private; } redirector_subsys; typedef struct base_instance_t { int configured; char *chroot; char *user; char *group; char *redirector_name; redirector_subsys *redirector; char *log_name; bool log_debug; bool log_info; bool daemon; } base_instance; static base_instance instance = { .configured = 0, .log_debug = false, .log_info = false, }; #if defined __FreeBSD__ || defined USE_PF static int redir_open_private(const char *fname, int flags) { int fd = open(fname, flags); if (fd < 0) { log_errno(LOG_ERR, "open(%s)", fname); return -1; } instance.redirector->private = fd; return 0; } static void redir_close_private() { close(instance.redirector->private); instance.redirector->private = -1; } #endif #ifdef __FreeBSD__ static int redir_init_ipf() { #ifdef IPNAT_NAME const char *fname = IPNAT_NAME; #else const char *fname = IPL_NAME; #endif return redir_open_private(fname, O_RDONLY); } static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { int natfd = instance.redirector->private; struct natlookup natLookup; int x; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) struct ipfobj obj; #else static int siocgnatl_cmd = SIOCGNATL & 0xff; #endif #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_size = sizeof(natLookup); obj.ipfo_ptr = &natLookup; obj.ipfo_type = IPFOBJ_NATLOOKUP; obj.ipfo_offset = 0; #endif natLookup.nl_inport = bindaddr->sin_port; natLookup.nl_outport = client->sin_port; natLookup.nl_inip = bindaddr->sin_addr; natLookup.nl_outip = client->sin_addr; natLookup.nl_flags = IPN_TCP; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) x = ioctl(natfd, SIOCGNATL, &obj); #else /* * IP-Filter changed the type for SIOCGNATL between * 3.3 and 3.4. It also changed the cmd value for * SIOCGNATL, so at least we can detect it. We could * put something in configure and use ifdefs here, but * this seems simpler. */ if (63 == siocgnatl_cmd) { struct natlookup *nlp = &natLookup; x = ioctl(natfd, SIOCGNATL, &nlp); } else { x = ioctl(natfd, SIOCGNATL, &natLookup); } #endif if (x < 0) { if (errno != ESRCH) log_errno(LOG_WARNING, "ioctl(SIOCGNATL)\n"); return -1; } else { destaddr->sin_family = AF_INET; destaddr->sin_port = natLookup.nl_realport; destaddr->sin_addr = natLookup.nl_realip; return 0; } } #endif #ifdef USE_PF static int redir_init_pf() { return redir_open_private("/dev/pf", O_RDWR); } static int getdestaddr_pf( int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { int pffd = instance.redirector->private; struct pfioc_natlook nl; int saved_errno; char clientaddr_str[INET6_ADDRSTRLEN], bindaddr_str[INET6_ADDRSTRLEN]; memset(&nl, 0, sizeof(struct pfioc_natlook)); nl.saddr.v4.s_addr = client->sin_addr.s_addr; nl.sport = client->sin_port; nl.daddr.v4.s_addr = bindaddr->sin_addr.s_addr; nl.dport = bindaddr->sin_port; nl.af = AF_INET; nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; if (ioctl(pffd, DIOCNATLOOK, &nl) != 0) { if (errno == ENOENT) { nl.direction = PF_IN; // required to redirect local packets if (ioctl(pffd, DIOCNATLOOK, &nl) != 0) { goto fail; } } else { goto fail; } } destaddr->sin_family = AF_INET; destaddr->sin_port = nl.rdport; destaddr->sin_addr = nl.rdaddr.v4; return 0; fail: saved_errno = errno; if (!inet_ntop(client->sin_family, &client->sin_addr, clientaddr_str, sizeof(clientaddr_str))) strncpy(clientaddr_str, "???", sizeof(clientaddr_str)); if (!inet_ntop(bindaddr->sin_family, &bindaddr->sin_addr, bindaddr_str, sizeof(bindaddr_str))) strncpy(bindaddr_str, "???", sizeof(bindaddr_str)); errno = saved_errno; log_errno(LOG_WARNING, "ioctl(DIOCNATLOOK {src=%s:%d, dst=%s:%d})", clientaddr_str, ntohs(nl.sport), bindaddr_str, ntohs(nl.dport)); return -1; } #endif #ifdef USE_IPTABLES static int getdestaddr_iptables(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; error = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, destaddr, &socklen); if (error) { log_errno(LOG_WARNING, "getsockopt"); return -1; } return 0; } #endif static int getdestaddr_generic(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; error = getsockname(fd, (struct sockaddr*)destaddr, &socklen); if (error) { log_errno(LOG_WARNING, "getsockopt"); return -1; } return 0; } int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { return instance.redirector->getdestaddr(fd, client, bindaddr, destaddr); } static redirector_subsys redirector_subsystems[] = { #ifdef __FreeBSD__ { .name = "ipf", .init = redir_init_ipf, .fini = redir_close_private, .getdestaddr = getdestaddr_ipf }, #endif #ifdef USE_PF { .name = "pf", .init = redir_init_pf, .fini = redir_close_private, .getdestaddr = getdestaddr_pf }, #endif #ifdef USE_IPTABLES { .name = "iptables", .getdestaddr = getdestaddr_iptables }, #endif { .name = "generic", .getdestaddr = getdestaddr_generic }, }; /*********************************************************************** * `base` config parsing */ static parser_entry base_entries[] = { { .key = "chroot", .type = pt_pchar, .addr = &instance.chroot }, { .key = "user", .type = pt_pchar, .addr = &instance.user }, { .key = "group", .type = pt_pchar, .addr = &instance.group }, { .key = "redirector", .type = pt_pchar, .addr = &instance.redirector_name }, { .key = "log", .type = pt_pchar, .addr = &instance.log_name }, { .key = "log_debug", .type = pt_bool, .addr = &instance.log_debug }, { .key = "log_info", .type = pt_bool, .addr = &instance.log_info }, { .key = "daemon", .type = pt_bool, .addr = &instance.daemon }, { } }; static int base_onenter(parser_section *section) { if (instance.configured) { parser_error(section->context, "only one instance of base is valid"); return -1; } memset(&instance, 0, sizeof(instance)); return 0; } static int base_onexit(parser_section *section) { const char *err = NULL; if (instance.redirector_name) { redirector_subsys *ss; FOREACH(ss, redirector_subsystems) { if (!strcmp(ss->name, instance.redirector_name)) { instance.redirector = ss; instance.redirector->private = -1; break; } } if (!instance.redirector) err = "invalid `redirector` set"; } else { err = "no `redirector` set"; } if (err) parser_error(section->context, err); if (!err) instance.configured = 1; return err ? -1 : 0; } static parser_section base_conf_section = { .name = "base", .entries = base_entries, .onenter = base_onenter, .onexit = base_onexit }; /*********************************************************************** * `base` initialization */ static int base_fini(); static int base_init() { uid_t uid = -1; gid_t gid = -1; int devnull = -1; if (!instance.configured) { log_error(LOG_ERR, "there is no configured instance of `base`, check config file"); return -1; } if (instance.redirector->init && instance.redirector->init() < 0) return -1; if (instance.user) { struct passwd *pw = getpwnam(instance.user); if (pw == NULL) { log_errno(LOG_ERR, "getpwnam(%s)", instance.user); goto fail; } uid = pw->pw_uid; } if (instance.group) { struct group *gr = getgrnam(instance.group); if (gr == NULL) { log_errno(LOG_ERR, "getgrnam(%s)", instance.group); goto fail; } gid = gr->gr_gid; } if (log_preopen( instance.log_name ? instance.log_name : instance.daemon ? "syslog:daemon" : "stderr", instance.log_debug, instance.log_info ) < 0 ) { goto fail; } if (instance.daemon) { devnull = open("/dev/null", O_RDWR); if (devnull == -1) { log_errno(LOG_ERR, "open(\"/dev/null\", O_RDWR"); goto fail; } } if (instance.chroot) { if (chroot(instance.chroot) < 0) { log_errno(LOG_ERR, "chroot(%s)", instance.chroot); goto fail; } } if (instance.daemon || instance.chroot) { if (chdir("/") < 0) { log_errno(LOG_ERR, "chdir(\"/\")"); goto fail; } } if (instance.group) { if (setgid(gid) < 0) { log_errno(LOG_ERR, "setgid(%i)", gid); goto fail; } } if (instance.user) { if (setuid(uid) < 0) { log_errno(LOG_ERR, "setuid(%i)", uid); goto fail; } } if (instance.daemon) { switch (fork()) { case -1: // error log_errno(LOG_ERR, "fork()"); goto fail; case 0: // child break; default: // parent, pid is returned exit(EXIT_SUCCESS); } } log_open(); // child has nothing to do with TTY if (instance.daemon) { if (setsid() < 0) { log_errno(LOG_ERR, "setsid()"); goto fail; } int fds[] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO }; int *pfd; FOREACH(pfd, fds) if (dup2(devnull, *pfd) < 0) { log_errno(LOG_ERR, "dup2(devnull, %i)", *pfd); goto fail; } close(devnull); } return 0; fail: if (devnull != -1) close(devnull); base_fini(); return -1; } static int base_fini() { if (instance.redirector->fini) instance.redirector->fini(); free(instance.chroot); free(instance.user); free(instance.group); free(instance.redirector_name); free(instance.log_name); memset(&instance, 0, sizeof(instance)); return 0; } app_subsys base_subsys = { .init = base_init, .fini = base_fini, .conf_section = &base_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/base.h0000644000175000017500000000055511733675122015104 0ustar apoikosapoikos#ifndef BASE_H_SUN_JUN__3_20_15_57_2007 #define BASE_H_SUN_JUN__3_20_15_57_2007 int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* BASE_H_SUN_JUN__3_20_15_57_2007 */ redsocks-0.4+dfsg/.gitignore0000644000175000017500000000004311733675122016001 0ustar apoikosapoikos*.o config.h tags redsocks .depend redsocks-0.4+dfsg/redsocks.c0000644000175000017500000006460711733675122016012 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "parser.h" #include "log.h" #include "main.h" #include "base.h" #include "redsocks.h" #include "utils.h" #define REDSOCKS_RELAY_HALFBUFF 4096 static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); extern relay_subsys http_connect_subsys; extern relay_subsys http_relay_subsys; extern relay_subsys socks4_subsys; extern relay_subsys socks5_subsys; static relay_subsys *relay_subsystems[] = { &http_connect_subsys, &http_relay_subsys, &socks4_subsys, &socks5_subsys, }; static list_head instances = LIST_HEAD_INIT(instances); static parser_entry redsocks_entries[] = { { .key = "local_ip", .type = pt_in_addr }, { .key = "local_port", .type = pt_uint16 }, { .key = "ip", .type = pt_in_addr }, { .key = "port", .type = pt_uint16 }, { .key = "type", .type = pt_pchar }, { .key = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "listenq", .type = pt_uint16 }, { .key = "min_accept_backoff", .type = pt_uint16 }, { .key = "max_accept_backoff", .type = pt_uint16 }, { } }; /* There is no way to get `EVLIST_INSERTED` event flag outside of libevent, so * here are tracking functions. */ static void tracked_event_set( struct tracked_event *tev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg) { event_set(&tev->ev, fd, events, callback, arg); timerclear(&tev->inserted); } static int tracked_event_add(struct tracked_event *tev, const struct timeval *tv) { int ret = event_add(&tev->ev, tv); if (ret == 0) gettimeofday(&tev->inserted, NULL); return ret; } static int tracked_event_del(struct tracked_event *tev) { int ret = event_del(&tev->ev); if (ret == 0) timerclear(&tev->inserted); return ret; } static int redsocks_onenter(parser_section *section) { // FIXME: find proper way to calulate instance_payload_len int instance_payload_len = 0; relay_subsys **ss; FOREACH(ss, relay_subsystems) if (instance_payload_len < (*ss)->instance_payload_len) instance_payload_len = (*ss)->instance_payload_len; redsocks_instance *instance = calloc(1, sizeof(*instance) + instance_payload_len); if (!instance) { parser_error(section->context, "Not enough memory"); return -1; } INIT_LIST_HEAD(&instance->list); INIT_LIST_HEAD(&instance->clients); instance->config.bindaddr.sin_family = AF_INET; instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); instance->config.relayaddr.sin_family = AF_INET; instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* Default value can be checked in run-time, but I doubt anyone needs that. * Linux: sysctl net.core.somaxconn * FreeBSD: sysctl kern.ipc.somaxconn */ instance->config.listenq = SOMAXCONN; instance->config.min_backoff_ms = 100; instance->config.max_backoff_ms = 60000; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : (strcmp(entry->key, "ip") == 0) ? (void*)&instance->config.relayaddr.sin_addr : (strcmp(entry->key, "port") == 0) ? (void*)&instance->config.relayaddr.sin_port : (strcmp(entry->key, "type") == 0) ? (void*)&instance->config.type : (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : (strcmp(entry->key, "min_accept_backoff") == 0) ? (void*)&instance->config.min_backoff_ms : (strcmp(entry->key, "max_accept_backoff") == 0) ? (void*)&instance->config.max_backoff_ms : NULL; section->data = instance; return 0; } static int redsocks_onexit(parser_section *section) { /* FIXME: Rewrite in bullet-proof style. There are memory leaks if config * file is not correct, so correct on-the-fly config reloading is * currently impossible. */ const char *err = NULL; redsocks_instance *instance = section->data; section->data = NULL; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = NULL; instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); instance->config.relayaddr.sin_port = htons(instance->config.relayaddr.sin_port); if (instance->config.type) { relay_subsys **ss; FOREACH(ss, relay_subsystems) { if (!strcmp((*ss)->name, instance->config.type)) { instance->relay_ss = *ss; list_add(&instance->list, &instances); break; } } if (!instance->relay_ss) err = "invalid `type` for redsocks"; } else { err = "no `type` for redsocks"; } if (!err && !instance->config.min_backoff_ms) { err = "`min_accept_backoff` must be positive, 0 ms is too low"; } if (!err && !instance->config.max_backoff_ms) { err = "`max_accept_backoff` must be positive, 0 ms is too low"; } if (!err && !(instance->config.min_backoff_ms < instance->config.max_backoff_ms)) { err = "`min_accept_backoff` must be less than `max_accept_backoff`"; } if (err) parser_error(section->context, err); return err ? -1 : 0; } static parser_section redsocks_conf_section = { .name = "redsocks", .entries = redsocks_entries, .onenter = redsocks_onenter, .onexit = redsocks_onexit }; void redsocks_log_write_plain( const char *file, int line, const char *func, int do_errno, const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, int priority, const char *orig_fmt, ... ) { int saved_errno = errno; struct evbuffer *fmt = evbuffer_new(); va_list ap; char clientaddr_str[INET6_ADDRSTRLEN], destaddr_str[INET6_ADDRSTRLEN]; if (!fmt) { log_errno(LOG_ERR, "evbuffer_new()"); // no return, as I have to call va_start/va_end } if (!inet_ntop(clientaddr->sin_family, &clientaddr->sin_addr, clientaddr_str, sizeof(clientaddr_str))) strncpy(clientaddr_str, "???", sizeof(clientaddr_str)); if (!inet_ntop(destaddr->sin_family, &destaddr->sin_addr, destaddr_str, sizeof(destaddr_str))) strncpy(destaddr_str, "???", sizeof(destaddr_str)); if (fmt) { evbuffer_add_printf(fmt, "[%s:%i->%s:%i]: %s", clientaddr_str, ntohs(clientaddr->sin_port), destaddr_str, ntohs(destaddr->sin_port), orig_fmt); } va_start(ap, orig_fmt); if (fmt) { errno = saved_errno; _log_vwrite(file, line, func, do_errno, priority, (const char*)EVBUFFER_DATA(fmt), ap); evbuffer_free(fmt); } va_end(ap); } void redsocks_touch_client(redsocks_client *client) { redsocks_time(&client->last_event); } static void redsocks_relay_readcb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); } else { if (bufferevent_disable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); } } static void redsocks_relay_writecb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { assert(from == client->client || from == client->relay); char from_eof = (from == client->client ? client->client_evshut : client->relay_evshut) & EV_READ; if (EVBUFFER_LENGTH(from->input) == 0 && from_eof) { redsocks_shutdown(client, to, SHUT_WR); } else if (EVBUFFER_LENGTH(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } } static void redsocks_relay_relayreadcb(struct bufferevent *from, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_readcb(client, client->relay, client->client); } static void redsocks_relay_relaywritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_writecb(client, client->client, client->relay); } static void redsocks_relay_clientreadcb(struct bufferevent *from, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_readcb(client, client->client, client->relay); } static void redsocks_relay_clientwritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_writecb(client, client->relay, client->client); } void redsocks_start_relay(redsocks_client *client) { int error; if (client->instance->relay_ss->fini) client->instance->relay_ss->fini(client); client->relay->wm_read.low = 0; client->relay->wm_write.low = 0; client->client->wm_read.low = 0; client->client->wm_write.low = 0; client->relay->wm_read.high = REDSOCKS_RELAY_HALFBUFF; client->relay->wm_write.high = REDSOCKS_RELAY_HALFBUFF; client->client->wm_read.high = REDSOCKS_RELAY_HALFBUFF; client->client->wm_write.high = REDSOCKS_RELAY_HALFBUFF; client->client->readcb = redsocks_relay_clientreadcb; client->client->writecb = redsocks_relay_clientwritecb; client->relay->readcb = redsocks_relay_relayreadcb; client->relay->writecb = redsocks_relay_relaywritecb; error = bufferevent_enable(client->client, EV_READ | EV_WRITE); if (!error) error = bufferevent_enable(client->relay, EV_READ | EV_WRITE); if (!error) { redsocks_log_error(client, LOG_DEBUG, "data relaying started"); } else { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); redsocks_drop_client(client); } } void redsocks_drop_client(redsocks_client *client) { redsocks_log_error(client, LOG_INFO, "dropping client"); if (client->instance->relay_ss->fini) client->instance->relay_ss->fini(client); if (client->client) { redsocks_close(EVENT_FD(&client->client->ev_write)); bufferevent_free(client->client); } if (client->relay) { redsocks_close(EVENT_FD(&client->relay->ev_write)); bufferevent_free(client->relay); } list_del(&client->list); free(client); } static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how) { short evhow = 0; char *strev, *strhow = NULL, *strevhow = NULL; unsigned short *pevshut; assert(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR); assert(buffev == client->client || buffev == client->relay); assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); if (how == SHUT_RD) { strhow = "SHUT_RD"; evhow = EV_READ; strevhow = "EV_READ"; } else if (how == SHUT_WR) { strhow = "SHUT_WR"; evhow = EV_WRITE; strevhow = "EV_WRITE"; } else if (how == SHUT_RDWR) { strhow = "SHUT_RDWR"; evhow = EV_READ|EV_WRITE; strevhow = "EV_READ|EV_WRITE"; } assert(strhow && strevhow); strev = buffev == client->client ? "client" : "relay"; pevshut = buffev == client->client ? &client->client_evshut : &client->relay_evshut; // if EV_WRITE is already shut and we're going to shutdown read then // we're either going to abort data flow (bad behaviour) or confirm EOF // and in this case socket is already SHUT_RD'ed if ( !(how == SHUT_RD && (*pevshut & EV_WRITE)) ) if (shutdown(EVENT_FD(&buffev->ev_read), how) != 0) redsocks_log_errno(client, LOG_ERR, "shutdown(%s, %s)", strev, strhow); if (bufferevent_disable(buffev, evhow) != 0) redsocks_log_errno(client, LOG_ERR, "bufferevent_disable(%s, %s)", strev, strevhow); *pevshut |= evhow; if (client->relay_evshut == (EV_READ|EV_WRITE) && client->client_evshut == (EV_READ|EV_WRITE)) { redsocks_log_error(client, LOG_DEBUG, "both client and server disconnected"); redsocks_drop_client(client); } } // I assume that -1 is invalid errno value static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev) { int pseudo_errno = red_socket_geterrno(buffev); if (pseudo_errno == -1) { redsocks_log_errno(client, LOG_ERR, "red_socket_geterrno"); return -1; } return pseudo_errno; } static void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg) { redsocks_client *client = _arg; assert(buffev == client->relay || buffev == client->client); redsocks_touch_client(client); if (what == (EVBUFFER_READ|EVBUFFER_EOF)) { struct bufferevent *antiev; if (buffev == client->relay) antiev = client->client; else antiev = client->relay; redsocks_shutdown(client, buffev, SHUT_RD); if (antiev != NULL && EVBUFFER_LENGTH(antiev->output) == 0) redsocks_shutdown(client, antiev, SHUT_WR); } else { errno = redsocks_socket_geterrno(client, buffev); redsocks_log_errno(client, LOG_NOTICE, "%s error, code " event_fmt_str, buffev == client->relay ? "relay" : "client", event_fmt(what)); redsocks_drop_client(client); } } int sizes_equal(size_t a, size_t b) { return a == b; } int sizes_greater_equal(size_t a, size_t b) { return a >= b; } int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected) { size_t len = EVBUFFER_LENGTH(input); if (comparator(len, expected)) { int read = evbuffer_remove(input, data, expected); UNUSED(read); assert(read == expected); return 0; } else { redsocks_log_error(client, LOG_NOTICE, "Can't get expected amount of data"); redsocks_drop_client(client); return -1; } } struct evbuffer *mkevbuffer(void *data, size_t len) { struct evbuffer *buff = NULL, *retval = NULL; buff = evbuffer_new(); if (!buff) { log_errno(LOG_ERR, "evbuffer_new"); goto fail; } if (evbuffer_add(buff, data, len) < 0) { log_errno(LOG_ERR, "evbuffer_add"); goto fail; } retval = buff; buff = NULL; fail: if (buff) evbuffer_free(buff); return retval; } int redsocks_write_helper_ex( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high) { assert(client); return redsocks_write_helper_ex_plain(buffev, client, (redsocks_message_maker_plain)mkmessage, client, state, wm_low, wm_high); } int redsocks_write_helper_ex_plain( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high) { int len; struct evbuffer *buff = NULL; int drop = 1; if (mkmessage) { buff = mkmessage(p); if (!buff) goto fail; assert(!client || buffev == client->relay); len = bufferevent_write_buffer(buffev, buff); if (len < 0) { if (client) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); else log_errno(LOG_ERR, "bufferevent_write_buffer"); goto fail; } } if (client) client->state = state; buffev->wm_read.low = wm_low; buffev->wm_read.high = wm_high; bufferevent_enable(buffev, EV_READ); drop = 0; fail: if (buff) evbuffer_free(buff); if (drop && client) redsocks_drop_client(client); return drop ? -1 : 0; } int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only) { assert(client); return redsocks_write_helper_ex(buffev, client, mkmessage, state, wm_only, wm_only); } static void redsocks_relay_connected(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(buffev == client->relay); redsocks_touch_client(client); if (!red_is_socket_connected_ok(buffev)) { redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); goto fail; } client->relay->readcb = client->instance->relay_ss->readcb; client->relay->writecb = client->instance->relay_ss->writecb; client->relay->writecb(buffev, _arg); return; fail: redsocks_drop_client(client); } void redsocks_connect_relay(redsocks_client *client) { client->relay = red_connect_relay(&client->instance->config.relayaddr, redsocks_relay_connected, redsocks_event_error, client); if (!client->relay) { redsocks_log_errno(client, LOG_ERR, "red_connect_relay"); redsocks_drop_client(client); } } static void redsocks_accept_backoff(int fd, short what, void *_arg) { redsocks_instance *self = _arg; /* Isn't it already deleted? EV_PERSIST has nothing common with timeouts in * old libevent... On the other hand libevent does not return any error. */ if (tracked_event_del(&self->accept_backoff) != 0) log_errno(LOG_ERR, "event_del"); if (tracked_event_add(&self->listener, NULL) != 0) log_errno(LOG_ERR, "event_add"); } void redsocks_close_internal(int fd, const char* file, int line, const char *func) { if (close(fd) == 0) { redsocks_instance *instance = NULL; struct timeval now; gettimeofday(&now, NULL); list_for_each_entry(instance, &instances, list) { if (timerisset(&instance->accept_backoff.inserted)) { struct timeval min_accept_backoff = { instance->config.min_backoff_ms / 1000, (instance->config.min_backoff_ms % 1000) * 1000}; struct timeval time_passed; timersub(&now, &instance->accept_backoff.inserted, &time_passed); if (timercmp(&min_accept_backoff, &time_passed, <)) { redsocks_accept_backoff(-1, 0, instance); break; } } } } else { const int do_errno = 1; _log_write(file, line, func, do_errno, LOG_WARNING, "close"); } } static void redsocks_accept_client(int fd, short what, void *_arg) { redsocks_instance *self = _arg; redsocks_client *client = NULL; struct sockaddr_in clientaddr; struct sockaddr_in myaddr; struct sockaddr_in destaddr; socklen_t addrlen = sizeof(clientaddr); int on = 1; int client_fd = -1; int error; // working with client_fd client_fd = accept(fd, (struct sockaddr*)&clientaddr, &addrlen); if (client_fd == -1) { /* Different systems use different `errno` value to signal different * `lack of file descriptors` conditions. Here are most of them. */ if (errno == ENFILE || errno == EMFILE || errno == ENOBUFS || errno == ENOMEM) { self->accept_backoff_ms = (self->accept_backoff_ms << 1) + 1; clamp_value(self->accept_backoff_ms, self->config.min_backoff_ms, self->config.max_backoff_ms); int delay = (random() % self->accept_backoff_ms) + 1; log_errno(LOG_WARNING, "accept: out of file descriptors, backing off for %u ms", delay); struct timeval tvdelay = { delay / 1000, (delay % 1000) * 1000 }; if (tracked_event_del(&self->listener) != 0) log_errno(LOG_ERR, "event_del"); if (tracked_event_add(&self->accept_backoff, &tvdelay) != 0) log_errno(LOG_ERR, "event_add"); } else { log_errno(LOG_WARNING, "accept"); } goto fail; } self->accept_backoff_ms = 0; // socket is really bound now (it could be bound to 0.0.0.0) addrlen = sizeof(myaddr); error = getsockname(client_fd, (struct sockaddr*)&myaddr, &addrlen); if (error) { log_errno(LOG_WARNING, "getsockname"); goto fail; } error = getdestaddr(client_fd, &clientaddr, &myaddr, &destaddr); if (error) { goto fail; } error = setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); if (error) { log_errno(LOG_WARNING, "setsockopt"); goto fail; } // everything seems to be ok, let's allocate some memory client = calloc(1, sizeof(redsocks_client) + self->relay_ss->payload_len); if (!client) { log_errno(LOG_ERR, "calloc"); goto fail; } client->instance = self; memcpy(&client->clientaddr, &clientaddr, sizeof(clientaddr)); memcpy(&client->destaddr, &destaddr, sizeof(destaddr)); INIT_LIST_HEAD(&client->list); self->relay_ss->init(client); if (redsocks_time(&client->first_event) == ((time_t)-1)) goto fail; redsocks_touch_client(client); client->client = bufferevent_new(client_fd, NULL, NULL, redsocks_event_error, client); if (!client->client) { log_errno(LOG_ERR, "bufferevent_new"); goto fail; } client_fd = -1; list_add(&client->list, &self->clients); // enable reading to handle EOF from client if (bufferevent_enable(client->client, EV_READ) != 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); goto fail; } redsocks_log_error(client, LOG_INFO, "accepted"); if (self->relay_ss->connect_relay) self->relay_ss->connect_relay(client); else redsocks_connect_relay(client); return; fail: if (client) { redsocks_drop_client(client); } if (client_fd != -1) redsocks_close(client_fd); } static const char *redsocks_evshut_str(unsigned short evshut) { return evshut == EV_READ ? "SHUT_RD" : evshut == EV_WRITE ? "SHUT_WR" : evshut == (EV_READ|EV_WRITE) ? "SHUT_RDWR" : evshut == 0 ? "" : "???"; } static const char *redsocks_event_str(unsigned short what) { return what == EV_READ ? "R/-" : what == EV_WRITE ? "-/W" : what == (EV_READ|EV_WRITE) ? "R/W" : what == 0 ? "-/-" : "???"; } static void redsocks_debug_dump_instance(redsocks_instance *instance, time_t now) { redsocks_client *client = NULL; log_error(LOG_DEBUG, "Dumping client list for instance %p:", instance); list_for_each_entry(client, &instance->clients, list) { const char *s_client_evshut = redsocks_evshut_str(client->client_evshut); const char *s_relay_evshut = redsocks_evshut_str(client->relay_evshut); redsocks_log_error(client, LOG_DEBUG, "client: %i (%s)%s%s, relay: %i (%s)%s%s, age: %li sec, idle: %li sec.", EVENT_FD(&client->client->ev_write), redsocks_event_str(client->client->enabled), s_client_evshut[0] ? " " : "", s_client_evshut, EVENT_FD(&client->relay->ev_write), redsocks_event_str(client->relay->enabled), s_relay_evshut[0] ? " " : "", s_relay_evshut, now - client->first_event, now - client->last_event); } log_error(LOG_DEBUG, "End of client list."); } static void redsocks_debug_dump(int sig, short what, void *_arg) { redsocks_instance *instance = NULL; time_t now = redsocks_time(NULL); list_for_each_entry(instance, &instances, list) redsocks_debug_dump_instance(instance, now); } static void redsocks_fini_instance(redsocks_instance *instance); static int redsocks_init_instance(redsocks_instance *instance) { /* FIXME: redsocks_fini_instance is called in case of failure, this * function will remove instance from instances list - result * looks ugly. */ int error; int on = 1; int fd = -1; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (error) { log_errno(LOG_ERR, "setsockopt"); goto fail; } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } error = listen(fd, instance->config.listenq); if (error) { log_errno(LOG_ERR, "listen"); goto fail; } tracked_event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redsocks_accept_client, instance); fd = -1; tracked_event_set(&instance->accept_backoff, -1, 0, redsocks_accept_backoff, instance); error = tracked_event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } return 0; fail: redsocks_fini_instance(instance); if (fd != -1) { redsocks_close(fd); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void redsocks_fini_instance(redsocks_instance *instance) { if (!list_empty(&instance->clients)) { redsocks_client *tmp, *client = NULL; log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); list_for_each_entry_safe(client, tmp, &instance->clients, list) { redsocks_drop_client(client); } } if (instance->relay_ss->instance_fini) instance->relay_ss->instance_fini(instance); if (event_initialized(&instance->listener.ev)) { if (timerisset(&instance->listener.inserted)) if (tracked_event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); redsocks_close(EVENT_FD(&instance->listener.ev)); memset(&instance->listener, 0, sizeof(instance->listener)); } if (event_initialized(&instance->accept_backoff.ev)) { if (timerisset(&instance->accept_backoff.inserted)) if (tracked_event_del(&instance->accept_backoff) != 0) log_errno(LOG_WARNING, "event_del"); memset(&instance->accept_backoff, 0, sizeof(instance->accept_backoff)); } list_del(&instance->list); free(instance->config.type); free(instance->config.login); free(instance->config.password); memset(instance, 0, sizeof(*instance)); free(instance); } static int redsocks_fini(); static struct event debug_dumper; static int redsocks_init() { struct sigaction sa = { }, sa_old = { }; redsocks_instance *tmp, *instance = NULL; sa.sa_handler = SIG_IGN; sa.sa_flags = SA_RESTART; if (sigaction(SIGPIPE, &sa, &sa_old) == -1) { log_errno(LOG_ERR, "sigaction"); return -1; } signal_set(&debug_dumper, SIGUSR1, redsocks_debug_dump, NULL); if (signal_add(&debug_dumper, NULL) != 0) { log_errno(LOG_ERR, "signal_add"); goto fail; } list_for_each_entry_safe(instance, tmp, &instances, list) { if (redsocks_init_instance(instance) != 0) goto fail; } return 0; fail: // that was the first resource allocation, it return's on failure, not goto-fail's sigaction(SIGPIPE, &sa_old, NULL); redsocks_fini(); return -1; } static int redsocks_fini() { redsocks_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) redsocks_fini_instance(instance); if (signal_initialized(&debug_dumper)) { if (signal_del(&debug_dumper) != 0) log_errno(LOG_WARNING, "signal_del"); memset(&debug_dumper, 0, sizeof(debug_dumper)); } return 0; } app_subsys redsocks_subsys = { .init = redsocks_init, .fini = redsocks_fini, .conf_section = &redsocks_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/redsocks.h0000644000175000017500000001010411733675122015776 0ustar apoikosapoikos#ifndef REDSOCKS_H_WED_JAN_24_22_17_11_2007 #define REDSOCKS_H_WED_JAN_24_22_17_11_2007 #include #include #include #include #include "list.h" struct redsocks_client_t; struct redsocks_instance_t; typedef struct relay_subsys_t { char *name; size_t payload_len; // size of relay-specific data in client section size_t instance_payload_len; // size of relay-specify data in instance section evbuffercb readcb; evbuffercb writecb; void (*init)(struct redsocks_client_t *client); void (*fini)(struct redsocks_client_t *client); void (*instance_fini)(struct redsocks_instance_t *instance); // connect_relay (if any) is called instead of redsocks_connect_relay after client connection acceptance void (*connect_relay)(struct redsocks_client_t *client); } relay_subsys; typedef struct redsocks_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; char *type; char *login; char *password; uint16_t min_backoff_ms; uint16_t max_backoff_ms; // backoff capped by 65 seconds is enough :) uint16_t listenq; } redsocks_config; struct tracked_event { struct event ev; struct timeval inserted; }; typedef struct redsocks_instance_t { list_head list; redsocks_config config; struct tracked_event listener; struct tracked_event accept_backoff; uint16_t accept_backoff_ms; list_head clients; relay_subsys *relay_ss; } redsocks_instance; typedef struct redsocks_client_t { list_head list; redsocks_instance *instance; struct bufferevent *client; struct bufferevent *relay; struct sockaddr_in clientaddr; struct sockaddr_in destaddr; int state; // it's used by bottom layer unsigned short client_evshut; unsigned short relay_evshut; time_t first_event; time_t last_event; } redsocks_client; void redsocks_drop_client(redsocks_client *client); void redsocks_touch_client(redsocks_client *client); void redsocks_connect_relay(redsocks_client *client); void redsocks_start_relay(redsocks_client *client); typedef int (*size_comparator)(size_t a, size_t b); int sizes_equal(size_t a, size_t b); int sizes_greater_equal(size_t a, size_t b); /** helper for functions when we expect ONLY reply of some size and anything else is error */ int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected); typedef struct evbuffer* (*redsocks_message_maker)(redsocks_client *client); typedef struct evbuffer* (*redsocks_message_maker_plain)(void *p); struct evbuffer *mkevbuffer(void *data, size_t len); /* Yahoo! This code is ex-plain! :-D */ int redsocks_write_helper_ex_plain( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high); int redsocks_write_helper_ex( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high); int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only); #define redsocks_close(fd) redsocks_close_internal((fd), __FILE__, __LINE__, __func__) void redsocks_close_internal(int fd, const char* file, int line, const char *func); #define redsocks_log_error(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) #define redsocks_log_errno(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) void redsocks_log_write_plain( const char *file, int line, const char *func, int do_errno, const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, int priority, const char *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 8, 9) )) #endif ; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDSOCKS_H_WED_JAN_24_22_17_11_2007 */ redsocks-0.4+dfsg/list.h0000644000175000017500000003044511733675122015146 0ustar apoikosapoikos#ifndef _LINUX_LIST_H #define _LINUX_LIST_H /** That's taken from linux-2.6.18.2 source tree with light modifications * I don't know what copyright to place here but that's GPLv2 code so * I assume I'm free to reuse it. */ #include "utils.h" // container_of /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ typedef struct list_head_t { struct list_head_t *next, *prev; } list_head; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head_t name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head_t *list) { list->next = list; list->prev = list; } /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head_t *new, struct list_head_t *prev, struct list_head_t *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head_t *new, struct list_head_t *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head_t *new, struct list_head_t *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head_t * prev, struct list_head_t * next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty on entry does not return true after this, the entry is * in an undefined state. */ static inline void list_del(struct list_head_t *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_replace - replace old entry by new one * @old : the element to be replaced * @new : the new element to insert * Note: if 'old' was empty, it will be overwritten. */ static inline void list_replace(struct list_head_t *old, struct list_head_t *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } static inline void list_replace_init(struct list_head_t *old, struct list_head_t *new) { list_replace(old, new); INIT_LIST_HEAD(old); } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head_t *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head_t *list, struct list_head_t *head) { __list_del(list->prev, list->next); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head_t *list, struct list_head_t *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } /** * list_is_last - tests whether @list is the last entry in list @head * @list: the entry to test * @head: the head of the list */ static inline int list_is_last(const struct list_head_t *list, const struct list_head_t *head) { return list->next == head; } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head_t *head) { return head->next == head; } /** * list_empty_careful - tests whether a list is empty and not being modified * @head: the list to test * * Description: * tests whether a list is empty _and_ checks that no other CPU might be * in the process of modifying either member (next or prev) * * NOTE: using list_empty_careful() without synchronization * can only be safe if the only activity that can happen * to the list entry is list_del_init(). Eg. it cannot be used * if another CPU could re-list_add() it. */ static inline int list_empty_careful(const struct list_head_t *head) { struct list_head_t *next = head->next; return (next == head) && (next == head->prev); } static inline void __list_splice(struct list_head_t *list, struct list_head_t *head) { struct list_head_t *first = list->next; struct list_head_t *last = list->prev; struct list_head_t *at = head->next; first->prev = head; head->next = first; last->next = at; at->prev = last; } /** * list_splice - join two lists * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(struct list_head_t *list, struct list_head_t *head) { if (!list_empty(list)) __list_splice(list, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head_t *list, struct list_head_t *head) { if (!list_empty(list)) { __list_splice(list, head); INIT_LIST_HEAD(list); } } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head_t pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_for_each - iterate over a list * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) /** * __list_for_each - iterate over a list * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. * * This variant differs from list_for_each() in that it's the * simplest possible list iteration code, no prefetching is done. * Use this for code that knows the list to be very short (empty * or 1 entry) most of the time. */ #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); \ pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head_t to use as a loop cursor. * @n: another &struct list_head_t to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, typeof(*pos), member)) /** * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. * * Prepares a pos entry for use as a start point in list_for_each_entry_continue. */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_continue * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_from * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_reverse * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) #endif redsocks-0.4+dfsg/main.c0000644000175000017500000000776311733675122015121 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include "log.h" #include "main.h" #include "utils.h" #include "version.h" extern app_subsys redsocks_subsys; extern app_subsys base_subsys; extern app_subsys redudp_subsys; extern app_subsys dnstc_subsys; app_subsys *subsystems[] = { &redsocks_subsys, &base_subsys, &redudp_subsys, &dnstc_subsys, }; static const char *confname = "redsocks.conf"; static const char *pidfile = NULL; static void terminate(int sig, short what, void *_arg) { if (event_loopbreak() != 0) log_error(LOG_WARNING, "event_loopbreak"); } static void red_srand() { struct timeval tv; gettimeofday(&tv, NULL); // using tv_usec is a bit less predictable than tv_sec srand(tv.tv_sec*1000000+tv.tv_usec); } int main(int argc, char **argv) { int error; app_subsys **ss; int exit_signals[2] = {SIGTERM, SIGINT}; struct event terminators[2]; bool conftest = false; int opt; int i; red_srand(); while ((opt = getopt(argc, argv, "h?vtc:p:")) != -1) { switch (opt) { case 't': conftest = true; break; case 'c': confname = optarg; break; case 'p': pidfile = optarg; break; case 'v': puts(redsocks_version); return EXIT_SUCCESS; default: printf( "Usage: %s [-?hvt] [-c config] [-p pidfile]\n" " -h, -? this message\n" " -v print version\n" " -t test config syntax\n" " -p write pid to pidfile\n", argv[0]); return (opt == '?' || opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; } } FILE *f = fopen(confname, "r"); if (!f) { perror("Unable to open config file"); return EXIT_FAILURE; } parser_context* parser = parser_start(f, NULL); if (!parser) { perror("Not enough memory for parser"); return EXIT_FAILURE; } FOREACH(ss, subsystems) if ((*ss)->conf_section) parser_add_section(parser, (*ss)->conf_section); error = parser_run(parser); parser_stop(parser); fclose(f); if (error) return EXIT_FAILURE; if (conftest) return EXIT_SUCCESS; event_init(); memset(terminators, 0, sizeof(terminators)); FOREACH(ss, subsystems) { if ((*ss)->init) { error = (*ss)->init(); if (error) goto shutdown; } } if (pidfile) { f = fopen(pidfile, "w"); if (!f) { perror("Unable to open pidfile for write"); return EXIT_FAILURE; } fprintf(f, "%d\n", getpid()); fclose(f); } assert(SIZEOF_ARRAY(exit_signals) == SIZEOF_ARRAY(terminators)); for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { signal_set(&terminators[i], exit_signals[i], terminate, NULL); if (signal_add(&terminators[i], NULL) != 0) { log_errno(LOG_ERR, "signal_add"); goto shutdown; } } log_error(LOG_NOTICE, "redsocks started"); event_dispatch(); log_error(LOG_NOTICE, "redsocks goes down"); shutdown: for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { if (signal_initialized(&terminators[i])) { if (signal_del(&terminators[i]) != 0) log_errno(LOG_WARNING, "signal_del"); memset(&terminators[i], 0, sizeof(terminators[i])); } } for (--ss; ss >= subsystems; ss--) if ((*ss)->fini) (*ss)->fini(); event_base_free(NULL); return !error ? EXIT_SUCCESS : EXIT_FAILURE; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/main.h0000644000175000017500000000116111733675122015110 0ustar apoikosapoikos#ifndef MAIN_H_TUE_JAN_23_15_38_25_2007 #define MAIN_H_TUE_JAN_23_15_38_25_2007 #include "parser.h" typedef struct app_subsys_t { int (*init)(); int (*fini)(); parser_section* conf_section; } app_subsys; #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* MAIN_H_TUE_JAN_23_15_38_25_2007 */ redsocks-0.4+dfsg/http-connect.c0000644000175000017500000001666511733675122016604 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * * http-connect upstream module for redsocks */ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "redsocks.h" #include "http-auth.h" typedef enum httpc_state_t { httpc_new, httpc_request_sent, httpc_reply_came, httpc_headers_skipped, httpc_MAX, } httpc_state; #define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. static void httpc_client_init(redsocks_client *client) { client->state = httpc_new; } static void httpc_instance_fini(redsocks_instance *instance) { http_auth *auth = (void*)(instance + 1); free(auth->last_auth_query); auth->last_auth_query = NULL; } static struct evbuffer *httpc_mkconnect(redsocks_client *client); extern const char *auth_request_header; extern const char *auth_response_header; static char *get_auth_request_header(struct evbuffer *buf) { char *line; for (;;) { line = redsocks_evbuffer_readline(buf); if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { free(line); return NULL; } if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0) return line; free(line); } } static void httpc_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; int dropped = 0; assert(client->state >= httpc_request_sent); redsocks_touch_client(client); if (client->state == httpc_request_sent) { size_t len = EVBUFFER_LENGTH(buffev->input); char *line = redsocks_evbuffer_readline(buffev->input); if (line) { unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match if (code == 407) { // auth failed http_auth *auth = (void*)(client->instance + 1); if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { redsocks_log_error(client, LOG_NOTICE, "proxy auth failed"); redsocks_drop_client(client); dropped = 1; } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { redsocks_log_error(client, LOG_NOTICE, "proxy auth required, but no login information provided"); redsocks_drop_client(client); dropped = 1; } else { char *auth_request = get_auth_request_header(buffev->input); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "403 found, but no proxy auth challenge"); redsocks_drop_client(client); dropped = 1; } else { free(line); free(auth->last_auth_query); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; size_t last_auth_query_len = strlen(ptr) + 1; auth->last_auth_query = calloc(last_auth_query_len, 1); memcpy(auth->last_auth_query, ptr, last_auth_query_len); auth->last_auth_count = 0; free(auth_request); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return; } /* close relay tunnel */ redsocks_close(EVENT_FD(&client->relay->ev_write)); bufferevent_free(client->relay); /* set to initial state*/ client->state = httpc_new; /* and reconnect */ redsocks_connect_relay(client); return; } } } else if (200 <= code && code <= 299) { client->state = httpc_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "%s", line); redsocks_drop_client(client); dropped = 1; } } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_drop_client(client); dropped = 1; } } if (dropped) return; while (client->state == httpc_reply_came) { char *line = redsocks_evbuffer_readline(buffev->input); if (line) { if (strlen(line) == 0) { client->state = httpc_headers_skipped; } free(line); } else { break; } } if (client->state == httpc_headers_skipped) { redsocks_start_relay(client); } } static struct evbuffer *httpc_mkconnect(redsocks_client *client) { struct evbuffer *buff = NULL, *retval = NULL; int len; buff = evbuffer_new(); if (!buff) { redsocks_log_errno(client, LOG_ERR, "evbuffer_new"); goto fail; } http_auth *auth = (void*)(client->instance + 1); ++auth->last_auth_count; const char *auth_scheme = NULL; char *auth_string = NULL; if (auth->last_auth_query != NULL) { /* find previous auth challange */ if (strncasecmp(auth->last_auth_query, "Basic", 5) == 0) { auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0) { /* calculate uri */ char uri[128]; snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); /* prepare an random string for cnounce */ char cnounce[17]; snprintf(cnounce, sizeof(cnounce), "%04x%04x%04x%04x", rand() & 0xffff, rand() & 0xffff, rand() & 0xffff, rand() & 0xffff); auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line client->instance->config.login, client->instance->config.password, //user, pass "CONNECT", uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce auth_scheme = "Digest"; } } if (auth_string == NULL) { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n\r\n", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port) ); } else { len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n%s %s %s\r\n\r\n", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port), auth_response_header, auth_scheme, auth_string ); } free(auth_string); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); goto fail; } retval = buff; buff = NULL; fail: if (buff) evbuffer_free(buff); return retval; } static void httpc_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == httpc_new) { redsocks_write_helper_ex( buffev, client, httpc_mkconnect, httpc_request_sent, 1, HTTP_HEAD_WM_HIGH ); } else if (client->state >= httpc_request_sent) { bufferevent_disable(buffev, EV_WRITE); } } relay_subsys http_connect_subsys = { .name = "http-connect", .payload_len = 0, .instance_payload_len = sizeof(http_auth), .readcb = httpc_read_cb, .writecb = httpc_write_cb, .init = httpc_client_init, .instance_fini = httpc_instance_fini, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/redsocks.conf.example0000644000175000017500000000567611733675122020150 0ustar apoikosapoikosbase { // debug: connection progress & client list on SIGUSR1 log_debug = on; // info: start and end of client session log_info = on; /* possible `log' values are: * stderr * "file:/path/to/file" * syslog:FACILITY facility is any of "daemon", "local0"..."local7" */ log = stderr; // log = "file:/path/to/file"; // log = "syslog:local7"; // detach from console daemon = off; /* Change uid, gid and root directory, these options require root * privilegies on startup. * Note, your chroot may requre /etc/localtime if you write log to syslog. * Log is opened before chroot & uid changing. */ // user = nobody; // group = nobody; // chroot = "/var/chroot"; /* possible `redirector' values are: * iptables - for Linux * ipf - for FreeBSD * pf - for OpenBSD * generic - some generic redirector that MAY work */ redirector = iptables; } redsocks { /* `local_ip' defaults to 127.0.0.1 for security reasons, * use 0.0.0.0 if you want to listen on every interface. * `local_*' are used as port to redirect to. */ local_ip = 127.0.0.1; local_port = 12345; // listen() queue length. Default value is SOMAXCONN and it should be // good enough for most of us. // listenq = 128; // SOMAXCONN equals 128 on my Linux box. // `max_accept_backoff` is a delay to retry `accept()` after accept // failure (e.g. due to lack of file descriptors). It's measured in // milliseconds and maximal value is 65535. `min_accept_backoff` is // used as initial backoff value and as a damper for `accept() after // close()` logic. // min_accept_backoff = 100; // max_accept_backoff = 60000; // `ip' and `port' are IP and tcp-port of proxy-server // You can also use hostname instead of IP, only one (random) // address of multihomed host will be used. ip = example.org; port = 1080; // known types: socks4, socks5, http-connect, http-relay type = socks5; // login = "foobar"; // password = "baz"; } redudp { // `local_ip' should not be 0.0.0.0 as it's also used for outgoing // packets that are sent as replies - and it should be fixed // if we want NAT to work properly. local_ip = 127.0.0.1; local_port = 10053; // `ip' and `port' of socks5 proxy server. ip = 10.0.0.1; port = 1080; login = username; password = pazzw0rd; // kernel does not give us this information, so we have to duplicate it // in both iptables rules and configuration file. By the way, you can // set `local_ip' to 127.45.67.89 if you need more than 65535 ports to // forward ;-) // This limitation may be relaxed in future versions using contrack-tools. dest_ip = 8.8.8.8; dest_port = 53; udp_timeout = 30; udp_timeout_stream = 180; } dnstc { // fake and really dumb DNS server that returns "truncated answer" to // every query via UDP, RFC-compliant resolver should repeat same query // via TCP in this case. local_ip = 127.0.0.1; local_port = 5300; } // you can add more `redsocks' and `redudp' sections if you need. redsocks-0.4+dfsg/utils.c0000644000175000017500000001033011733675122015315 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include "log.h" #include "utils.h" #include "redsocks.h" // for redsocks_close int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr) { socklen_t addrlen = sizeof(*inaddr); ssize_t pktlen; pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen); if (pktlen == -1) { log_errno(LOG_WARNING, "recvfrom"); return -1; } if (addrlen != sizeof(*inaddr)) { log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); return -1; } if (pktlen >= buflen) { char buf[INET6_ADDRSTRLEN]; const char *addr = inet_ntop(inaddr->sin_family, &inaddr->sin_addr, buf, sizeof(buf)); log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s:%u! impossible! dropping it...", pktlen, addr ? addr : "?", ntohs(inaddr->sin_port)); return -1; } return pktlen; } time_t redsocks_time(time_t *t) { time_t retval; retval = time(t); if (retval == ((time_t) -1)) log_errno(LOG_WARNING, "time"); return retval; } char *redsocks_evbuffer_readline(struct evbuffer *buf) { #if _EVENT_NUMERIC_VERSION >= 0x02000000 return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); #else return evbuffer_readline(buf); #endif } struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg) { struct bufferevent *retval = NULL; int on = 1; int relay_fd = -1; int error; relay_fd = socket(AF_INET, SOCK_STREAM, 0); if (relay_fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = fcntl_nonblock(relay_fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } error = setsockopt(relay_fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); if (error) { log_errno(LOG_WARNING, "setsockopt"); goto fail; } error = connect(relay_fd, (struct sockaddr*)addr, sizeof(*addr)); if (error && errno != EINPROGRESS) { log_errno(LOG_NOTICE, "connect"); goto fail; } retval = bufferevent_new(relay_fd, NULL, writecb, errorcb, cbarg); if (!retval) { log_errno(LOG_ERR, "bufferevent_new"); goto fail; } error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... if (error) { log_errno(LOG_ERR, "bufferevent_enable"); goto fail; } return retval; fail: if (relay_fd != -1) redsocks_close(relay_fd); if (retval) bufferevent_free(retval); return NULL; } int red_socket_geterrno(struct bufferevent *buffev) { int error; int pseudo_errno; socklen_t optlen = sizeof(pseudo_errno); assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); error = getsockopt(EVENT_FD(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); if (error) { log_errno(LOG_ERR, "getsockopt"); return -1; } return pseudo_errno; } /** simple fcntl(2) wrapper, provides errno and all logging to caller * I have to use it in event-driven code because of accept(2) (see NOTES) * and connect(2) (see ERRORS about EINPROGRESS) */ int fcntl_nonblock(int fd) { int error; int flags; flags = fcntl(fd, F_GETFL); if (flags == -1) return -1; error = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (error) return -1; return 0; } int red_is_socket_connected_ok(struct bufferevent *buffev) { int pseudo_errno = red_socket_geterrno(buffev); if (pseudo_errno == -1) { return 0; } else if (pseudo_errno) { errno = pseudo_errno; log_errno(LOG_NOTICE, "connect"); return 0; } else { return 1; } } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ redsocks-0.4+dfsg/utils.h0000644000175000017500000000424611733675122015333 0ustar apoikosapoikos#ifndef UTILS_H_SAT_FEB__2_02_24_05_2008 #define UTILS_H_SAT_FEB__2_02_24_05_2008 #include #include #include struct sockaddr_in; #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) #define UNUSED(x) ((void)(x)) #if defined __GNUC__ #define PACKED __attribute__((packed)) #else #error Unknown compiler, modify utils.h for it #endif /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define clamp_value(value, min_val, max_val) do { \ if (value < min_val) \ value = min_val; \ if (value > max_val) \ value = max_val; \ } while (0) time_t redsocks_time(time_t *t); char *redsocks_evbuffer_readline(struct evbuffer *buf); struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); int red_socket_geterrno(struct bufferevent *buffev); int red_is_socket_connected_ok(struct bufferevent *buffev); int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr); int fcntl_nonblock(int fd); #define event_fmt_str "%s|%s|%s|%s|%s|0x%x" #define event_fmt(what) \ (what) & EVBUFFER_READ ? "EVBUFFER_READ" : "0", \ (what) & EVBUFFER_WRITE ? "EVBUFFER_WRITE" : "0", \ (what) & EVBUFFER_EOF ? "EVBUFFER_EOF" : "0", \ (what) & EVBUFFER_ERROR ? "EVBUFFER_ERROR" : "0", \ (what) & EVBUFFER_TIMEOUT ? "EVBUFFER_TIMEOUT" : "0", \ (what) & ~(EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF|EVBUFFER_ERROR|EVBUFFER_TIMEOUT) /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */ redsocks-0.4+dfsg/README.html0000644000175000017500000002222411733675122015641 0ustar apoikosapoikos redsocks - transparent socks redirector

redsocks - transparent socks redirector


This tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection is system-wide.

Why is that useful? I can suggest following reasons:

  • you use tor and don't want any TCP connection to leak
  • you use DVB ISP and this ISP provides internet connectivity with some special daemon that may be also called "Internet accelerator" and this accelerator acts as proxy. Globax is example of such an accelerator

Linux/iptables, OpenBSD/pf and FreeBSD/ipfw are supported. Linux/iptables is well-tested, other implementations may have bugs, your bugreports are welcome.

Transocks is alike project but it has noticable performance penality.

Transsocks_ev is alike project too, but it has no HTTPS-proxy support and does not support authentication.

Several Andoird apps also use redsocks under-the-hood: ProxyDroid (@AndroidMarket) and sshtunnel (@AndroidMarket). And that's over 100'000 downloads! Wow!

Another related issue is DNS over TCP. Redsocks includes `dnstc' that is fake and really dumb DNS server that returns "truncated answer" to every query via UDP. RFC-compliant resolver should repeat same query via TCP in this case - so the request can be redirected using usual redsocks facilities.

Known compliant resolvers are:

  • bind9 (server)
  • dig, nslookup (tools based on bind9 code)

Known non-compliant resolvers are:

  • eglibc resolver fails without any attempt to send request via TCP
  • powerdns-recursor can't properly startup without UDP connectivity as it can't load root hints

On the other hand, DNS via TCP using bind9 may be painfully slow. If your bind9 setup is really slow, you have at least two options: pdnsd caching server can run in TCP-only mode, ttdnsd (git repo) has no cache but can be useful for same purpose.

Features

Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) proxy server.

Login/password authentication is supported for SOCKS5/HTTPS connections. SOCKS4 supports only username, password is ignored. for HTTPS, currently only Basic and Digest scheme is supported.

Redirect UDP packets via SOCKS5 proxy server. NB: UDP still goes via UDP, so you can't relay UDP via OpenSSH.

Sends "truncated reply" as an answer to UDP DNS queries.

Redirect any HTTP connection to proxy that does not support transparent proxying (e.g. old SQUID had broken `acl myport' for such connections).

License

All source code is licensed under Apache 2.0 license.

You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html

Compilation

libevent is required.

gcc is only supported compiler right now, other compilers can be used but may require some code changes.

Compilation is as easy as running `make', there is no `./configure' magic.

GNU Make works, other implementations of make were not tested.

Running

Program has following command-line options:
-c sets proper path to config file ("./redsocks.conf" is default one)
-t tests config file syntax
-p set a file to write the getpid() into

Following signals are understood:
SIGUSR1 dumps list of connected clients to log
SIGTERM and SIGINT terminates daemon, all active connections are closed

You can see configuration file example in redsocks.conf.example

iptables example

You have to build iptables with connection tracking and REDIRECT target.

# Create new chain
root# iptables -t nat -N REDSOCKS

# Ignore LANs and some other reserved addresses.
# See Wikipedia and RFC5735 for full list of reserved networks.
root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN
root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN
root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN
root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN
root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN

# Anything else should be redirected to port 12345
root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345

# Any tcp connection made by `luser' should be redirected.
root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS

# You can also control that in more precise way using `gid-owner` from
# iptables.
root# groupadd socksified
root# usermod --append --groups socksified luser
root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS

# Now you can launch your specific application with GID `socksified` and it
# will be... socksified. See following commands (numbers may vary).
# Note: you may have to relogin to apply `usermod` changes.
luser$ id
uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified)
luser$ sg socksified -c id
uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified)
luser$ sg socksified -c "firefox"

# If you want to configure socksifying router, you should look at
# doc/iptables-packet-flow.png and doc/iptables-packet-flow-ng.png
# Note, you should have proper `local_ip' value to get external packets with
# redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding
# REDIRECT target for details.
# Depending on your network configuration iptables conf. may be as easy as:
root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS

Note about GID-based redirection

Keep in mind, that changed GID affects filesystem permissions, so if your application creates some files, the files will be created with luser:socksified owner/group. So, if you're not the only user in the group `socksified` and your umask allows to create group-readable files and your directory permissions, and so on, blah-blah, etc. THEN you may expose your files to another user.

Ok, you have been warned.

Homepage

Homepage: http://darkk.net.ru/redsocks/

Mailing list: redsocks@librelist.com

Mailing list also has archives.

TODO

  • Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those firewall types.

Author

This program was written by Leonid Evdokimov.
 (~~~~~~~~~~\   __
 | jabber:  )  /  \
 | mailto: (  /|oo \
  \_________\(_|  /_)
              _ @/_ \
             |     | \   \\
      leon   | (*) |  \   ))  darkk.net.ru
             |__U__| /  \//
              _//|| _\   /
             (_/(_|(____/
                         Valid CSS!
                         Valid XHTML 1.0 Transitional
redsocks-0.4+dfsg/version.h0000644000175000017500000000025211733675122015651 0ustar apoikosapoikos#ifndef VERSION_H_SUN_NOV_27_03_22_30_2011 #define VERSION_H_SUN_NOV_27_03_22_30_2011 extern const char* redsocks_version; #endif // VERSION_H_SUN_NOV_27_03_22_30_2011 redsocks-0.4+dfsg/base64.c0000644000175000017500000000614211733675122015247 0ustar apoikosapoikos/* * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file * @brief Base64 encode/decode * @author Ryan Martell (with lots of Michael) */ #include #include "utils.h" #include "base64.h" /* ---------------- private code */ static const uint8_t map2[] = { 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 }; int base64_decode(uint8_t *out, const char *in, int out_size) { int i, v; uint8_t *dst = out; v = 0; for (i = 0; in[i] && in[i] != '='; i++) { unsigned int index= in[i]-43; if (index>=SIZEOF_ARRAY(map2) || map2[index] == 0xff) return -1; v = (v << 6) + map2[index]; if (i & 3) { if (dst - out < out_size) { *dst++ = v >> (6 - 2 * (i & 3)); } } } return dst - out; } /***************************************************************************** * b64_encode: Stolen from VLC's http.c. * Simplified by Michael. * Fixed edge cases and made it work from data (vs. strings) by Ryan. *****************************************************************************/ char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *ret, *dst; unsigned i_bits = 0; int i_shift = 0; int bytes_remaining = in_size; if (in_size >= UINT_MAX / 4 || out_size < BASE64_SIZE(in_size)) return NULL; ret = dst = out; while (bytes_remaining) { i_bits = (i_bits << 8) + *in++; bytes_remaining--; i_shift += 8; do { *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; i_shift -= 6; } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); } while ((dst - ret) & 3) *dst++ = '='; *dst = '\0'; return ret; } redsocks-0.4+dfsg/base64.h0000644000175000017500000000345611733675122015261 0ustar apoikosapoikos/* * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef BASE64_H #define BASE64_H #include /** * Decode a base64-encoded string. * * @param out buffer for decoded data * @param in null-terminated input string * @param out_size size in bytes of the out buffer, must be at * least 3/4 of the length of in * @return number of bytes written, or a negative value in case of * invalid input */ int base64_decode(uint8_t *out, const char *in, int out_size); /** * Encode data to base64 and null-terminate. * * @param out buffer for encoded data * @param out_size size in bytes of the output buffer, must be at * least BASE64_SIZE(in_size) * @param in_size size in bytes of the 'in' buffer * @return 'out' or NULL in case of error */ char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size); /** * Calculate the output size needed to base64-encode x bytes. */ #define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) #endif /* BASE64_H */ redsocks-0.4+dfsg/http-relay.c0000644000175000017500000003675711733675122016273 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * * http-relay upstream module for redsocks */ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "redsocks.h" #include "http-auth.h" #include "utils.h" #define HTTP_HEAD_WM_HIGH (4096) typedef enum httpr_state_t { httpr_new, httpr_recv_request_headers, httpr_request_sent, httpr_reply_came, httpr_headers_skipped, httpr_MAX, } httpr_state; typedef struct httpr_buffer_t { char *buff; int len; int max_len; } httpr_buffer; typedef struct httpr_client_t { char *firstline; char *host; int has_host; httpr_buffer client_buffer; httpr_buffer relay_buffer; } httpr_client; extern const char *auth_request_header; extern const char *auth_response_header; static void httpr_connect_relay(redsocks_client *client); static int httpr_buffer_init(httpr_buffer *buff) { buff->max_len = 4096; buff->len = 0; buff->buff = calloc(buff->max_len, 1); if (!buff->buff) return -1; return 0; } static void httpr_buffer_fini(httpr_buffer *buff) { free(buff->buff); buff->buff = NULL; } static int httpr_buffer_append(httpr_buffer *buff, const char *data, int len) { while (buff->len + len + 1 > buff->max_len) { /* double the buffer size */ buff->max_len *= 2; } char *new_buff = calloc(buff->max_len, 1); if (!new_buff) { return -1; } memcpy(new_buff, buff->buff, buff->len); memcpy(new_buff + buff->len, data, len); buff->len += len; new_buff[buff->len] = '\0'; free(buff->buff); buff->buff = new_buff; return 0; } static void httpr_client_init(redsocks_client *client) { httpr_client *httpr = (void*)(client + 1); client->state = httpr_new; memset(httpr, 0, sizeof(*httpr)); httpr_buffer_init(&httpr->client_buffer); httpr_buffer_init(&httpr->relay_buffer); } static void httpr_client_fini(redsocks_client *client) { httpr_client *httpr = (void*)(client + 1); free(httpr->firstline); httpr->firstline = NULL; free(httpr->host); httpr->host = NULL; httpr_buffer_fini(&httpr->client_buffer); httpr_buffer_fini(&httpr->relay_buffer); } static void httpr_instance_fini(redsocks_instance *instance) { http_auth *auth = (void*)(instance + 1); free(auth->last_auth_query); auth->last_auth_query = NULL; } static char *get_auth_request_header(struct evbuffer *buf) { char *line; for (;;) { line = redsocks_evbuffer_readline(buf); if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { free(line); return NULL; } if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0) return line; free(line); } } static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = (void*)(client + 1); int dropped = 0; assert(client->state >= httpr_request_sent); redsocks_touch_client(client); httpr_buffer_fini(&httpr->relay_buffer); httpr_buffer_init(&httpr->relay_buffer); if (client->state == httpr_request_sent) { size_t len = EVBUFFER_LENGTH(buffev->input); char *line = redsocks_evbuffer_readline(buffev->input); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match if (code == 407) { // auth failed http_auth *auth = (void*)(client->instance + 1); if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { redsocks_log_error(client, LOG_NOTICE, "proxy auth failed"); redsocks_drop_client(client); dropped = 1; } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { redsocks_log_error(client, LOG_NOTICE, "proxy auth required, but no login information provided"); redsocks_drop_client(client); dropped = 1; } else { free(line); char *auth_request = get_auth_request_header(buffev->input); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "403 found, but no proxy auth challenge"); redsocks_drop_client(client); dropped = 1; } else { free(auth->last_auth_query); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; auth->last_auth_query = calloc(strlen(ptr) + 1, 1); strcpy(auth->last_auth_query, ptr); auth->last_auth_count = 0; free(auth_request); httpr_buffer_fini(&httpr->relay_buffer); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return; } /* close relay tunnel */ redsocks_close(EVENT_FD(&client->relay->ev_write)); bufferevent_free(client->relay); /* set to initial state*/ client->state = httpr_recv_request_headers; /* and reconnect */ redsocks_connect_relay(client); return; } } } else if (100 <= code && code <= 999) { client->state = httpr_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "%s", line); redsocks_drop_client(client); dropped = 1; } } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_drop_client(client); dropped = 1; } } if (dropped) return; while (client->state == httpr_reply_came) { char *line = redsocks_evbuffer_readline(buffev->input); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); if (strlen(line) == 0) { client->state = httpr_headers_skipped; } free(line); } else { break; } } if (client->state == httpr_headers_skipped) { if (bufferevent_write(client->client, httpr->relay_buffer.buff, httpr->relay_buffer.len) != 0) { redsocks_log_error(client, LOG_NOTICE, "bufferevent_write"); redsocks_drop_client(client); return; } redsocks_start_relay(client); } } static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = (void*)(client + 1); int len = 0; assert(client->state >= httpr_recv_request_headers); redsocks_touch_client(client); if (client->state == httpr_recv_request_headers) { if (httpr->firstline) { len = bufferevent_write(client->relay, httpr->firstline, strlen(httpr->firstline)); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } http_auth *auth = (void*)(client->instance + 1); ++auth->last_auth_count; const char *auth_scheme = NULL; char *auth_string = NULL; if (auth->last_auth_query != NULL) { /* find previous auth challange */ if (strncasecmp(auth->last_auth_query, "Basic", 5) == 0) { auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0 && httpr->firstline) { /* calculate method & uri */ char *ptr = strchr(httpr->firstline, ' '), *ptr2; char *method = calloc(ptr - httpr->firstline + 1, 1); memcpy(method, httpr->firstline, ptr - httpr->firstline); method[ptr - httpr->firstline] = 0; ptr = strchr(httpr->firstline, '/'); if (!ptr || *++ptr != '/') { free(method); redsocks_log_error(client, LOG_NOTICE, "malformed request came"); redsocks_drop_client(client); return; } if (!(ptr = strchr(++ptr, '/')) || !(ptr2 = strchr(ptr, ' '))) { free(method); redsocks_log_error(client, LOG_NOTICE, "malformed request came"); redsocks_drop_client(client); return; } char *uri = calloc(ptr2 - ptr + 1, 1); memcpy(uri, ptr, ptr2 - ptr); uri[ptr2 - ptr] = 0; /* prepare an random string for cnounce */ char cnounce[17]; snprintf(cnounce, sizeof(cnounce), "%04x%04x%04x%04x", rand() & 0xffff, rand() & 0xffff, rand() & 0xffff, rand() & 0xffff); auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line client->instance->config.login, client->instance->config.password, //user, pass method, uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce free(method); free(uri); auth_scheme = "Digest"; } } if (auth_string != NULL) { len = 0; len |= bufferevent_write(client->relay, auth_response_header, strlen(auth_response_header)); len |= bufferevent_write(client->relay, " ", 1); len |= bufferevent_write(client->relay, auth_scheme, strlen(auth_scheme)); len |= bufferevent_write(client->relay, " ", 1); len |= bufferevent_write(client->relay, auth_string, strlen(auth_string)); len |= bufferevent_write(client->relay, "\r\n", 2); if (len) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } free(auth_string); len = bufferevent_write(client->relay, httpr->client_buffer.buff, httpr->client_buffer.len); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } client->state = httpr_request_sent; buffev->wm_read.low = 1; buffev->wm_read.high = HTTP_HEAD_WM_HIGH; bufferevent_enable(buffev, EV_READ); } } // drops client on failure static int httpr_append_header(redsocks_client *client, char *line) { httpr_client *httpr = (void*)(client + 1); if (httpr_buffer_append(&httpr->client_buffer, line, strlen(line)) != 0) return -1; if (httpr_buffer_append(&httpr->client_buffer, "\x0d\x0a", 2) != 0) return -1; return 0; } // This function is not reenterable static char *fmt_http_host(struct sockaddr_in addr) { static char host[] = "123.123.123.123:12345"; if (ntohs(addr.sin_port) == 80) return inet_ntoa(addr.sin_addr); else { snprintf(host, sizeof(host), "%s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) ); return host; } } static int httpr_toss_http_firstline(redsocks_client *client) { httpr_client *httpr = (void*)(client + 1); char *uri = NULL; char *host = httpr->has_host ? httpr->host : fmt_http_host(client->destaddr); assert(httpr->firstline); uri = strchr(httpr->firstline, ' '); if (uri) uri += 1; // one char further else { redsocks_log_error(client, LOG_NOTICE, "malformed request came"); goto fail; } httpr_buffer nbuff; if (httpr_buffer_init(&nbuff) != 0) { redsocks_log_error(client, LOG_ERR, "httpr_buffer_init"); goto fail; } if (httpr_buffer_append(&nbuff, httpr->firstline, uri - httpr->firstline) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, "http://", 7) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, host, strlen(host)) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, uri, strlen(uri)) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, "\x0d\x0a", 2) != 0) goto addition_fail; free(httpr->firstline); httpr->firstline = calloc(nbuff.len + 1, 1); strcpy(httpr->firstline, nbuff.buff); httpr_buffer_fini(&nbuff); return 0; addition_fail: httpr_buffer_fini(&nbuff); fail: redsocks_log_error(client, LOG_ERR, "httpr_toss_http_firstline"); return -1; } static void httpr_client_read_content(struct bufferevent *buffev, redsocks_client *client) { httpr_client *httpr = (void*)(client + 1); static int post_buffer_len = 64 * 1024; char *post_buffer = calloc(post_buffer_len, 1); if (!post_buffer) { redsocks_log_error(client, LOG_ERR, "run out of memory"); redsocks_drop_client(client); return; } int error; while (true) { error = evbuffer_remove(buffev->input, post_buffer, post_buffer_len); if (error < 0) { free(post_buffer); redsocks_log_error(client, LOG_ERR, "evbuffer_remove"); redsocks_drop_client(client); return; } if (error == 0) break; httpr_buffer_append(&httpr->client_buffer, post_buffer, error); if (client->relay && client->state >= httpr_request_sent) { if (bufferevent_write(client->relay, post_buffer, error) != 0) { free(post_buffer); redsocks_log_error(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } } free(post_buffer); } static void httpr_client_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = (void*)(client + 1); redsocks_touch_client(client); if (client->state >= httpr_recv_request_headers) { httpr_client_read_content(buffev, client); return; } char *line = NULL; int connect_relay = 0; while (!connect_relay && (line = redsocks_evbuffer_readline(buffev->input))) { int skip_line = 0; int do_drop = 0; if (strlen(line) > 0) { if (!httpr->firstline) { httpr->firstline = line; line = 0; } else if (strncasecmp(line, "Host", 4) == 0) { httpr->has_host = 1; char *ptr = line + 5; while (*ptr && isspace(*ptr)) ptr ++; httpr->host = calloc(strlen(ptr) + 1, 1); strcpy(httpr->host, ptr); } else if (strncasecmp(line, "Proxy-Connection", 16) == 0) skip_line = 1; else if (strncasecmp(line, "Connection", 10) == 0) skip_line = 1; } else { // last line of request if (!httpr->firstline || httpr_toss_http_firstline(client) < 0) do_drop = 1; if (!httpr->has_host) { char host[32]; // "Host: 123.456.789.012:34567" int written_wo_null = snprintf(host, sizeof(host), "Host: %s", fmt_http_host(client->destaddr)); UNUSED(written_wo_null); assert(0 < written_wo_null && written_wo_null < sizeof(host)); if (httpr_append_header(client, host) < 0) do_drop = 1; } if (httpr_append_header(client, "Proxy-Connection: close") < 0) do_drop = 1; if (httpr_append_header(client, "Connection: close") < 0) do_drop = 1; connect_relay = 1; } if (line && !skip_line) if (httpr_append_header(client, line) < 0) do_drop = 1; free(line); if (do_drop) { redsocks_drop_client(client); return; } } if (connect_relay) { client->state = httpr_recv_request_headers; httpr_client_read_content(buffev, client); redsocks_connect_relay(client); } } static void httpr_connect_relay(redsocks_client *client) { int error; client->client->readcb = httpr_client_read_cb; error = bufferevent_enable(client->client, EV_READ); if (error) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); redsocks_drop_client(client); } } relay_subsys http_relay_subsys = { .name = "http-relay", .payload_len = sizeof(httpr_client), .instance_payload_len = sizeof(http_auth), .init = httpr_client_init, .fini = httpr_client_fini, .connect_relay = httpr_connect_relay, .readcb = httpr_relay_read_cb, .writecb = httpr_relay_write_cb, .instance_fini = httpr_instance_fini, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/reddns.c0000644000175000017500000000507511733675122015446 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include "parser.h" #include "main.h" typedef struct reddns_config_t reddns_config; struct reddns_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; struct in_addr fakenet; struct in_addr fakenetmask; }; int reddns_onenter(parser_section *section) { reddns_config *conf = calloc(1, sizeof(reddns_config)); section->data = conf; section->entries[0].addr = &conf->bindaddr.sin_port; section->entries[1].addr = &conf->fakenet; section->entries[2].addr = &conf->fakenetmask; section->entries[3].addr = &conf->relayaddr.sin_addr; section->entries[4].addr = &conf->relayaddr.sin_port; fprintf(stderr, "%s\n", __FUNCTION__); return 0; } int reddns_onexit(parser_section *section) { reddns_config *conf = section->data; fprintf(stderr, "%s {\n" "local_port = %u;\n" "fakeip_net = %s/%s;\n" "ip = %s;\n" "port = %u;\n" "}\n", __FUNCTION__, conf->bindaddr.sin_port, strdup(inet_ntoa(conf->fakenet)), strdup(inet_ntoa(conf->fakenetmask)), strdup(inet_ntoa(conf->relayaddr.sin_addr)), conf->relayaddr.sin_port ); return 0; } parser_entry reddns_entries[] = { { .key = "local_port", .type = pt_uint16 }, { .key = "fakeip_net", .type = pt_in_addr2 }, { .key = "fakeip_netmask", .type = pt_in_addr }, { .key = "ip", .type = pt_in_addr }, { .key = "port", .type = pt_uint16 }, { } }; parser_section reddns_conf_section = { .name = "reddns", .entries = reddns_entries, .onenter = reddns_onenter, .onexit = reddns_onexit }; int reddns_init() { #error It's non-working stub at the moment. return 0; } int reddns_fini() { return 0; } app_subsys reddns_subsys = { .init = reddns_init, .fini = reddns_fini, .conf_section = &reddns_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/redudp.c0000644000175000017500000005173411733675122015455 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include "list.h" #include "log.h" #include "socks5.h" #include "parser.h" #include "main.h" #include "redsocks.h" #include "redudp.h" #define redudp_log_error(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg) #define redudp_log_errno(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg) static void redudp_pkt_from_socks(int fd, short what, void *_arg); static void redudp_drop_client(redudp_client *client); static void redudp_fini_instance(redudp_instance *instance); static int redudp_fini(); typedef struct redudp_expected_assoc_reply_t { socks5_reply h; socks5_addr_ipv4 ip; } PACKED redudp_expected_assoc_reply; /*********************************************************************** * Helpers */ static void redudp_fill_preamble(socks5_udp_preabmle *preamble, redudp_client *client) { preamble->reserved = 0; preamble->frag_no = 0; /* fragmentation is not supported */ preamble->addrtype = socks5_addrtype_ipv4; preamble->ip.addr = client->instance->config.destaddr.sin_addr.s_addr; preamble->ip.port = client->instance->config.destaddr.sin_port; } static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p) { int *do_password = p; return socks5_mkmethods_plain(*do_password); } static struct evbuffer* socks5_mkpassword_plain_wrapper(void *p) { redudp_instance *self = p; return socks5_mkpassword_plain(self->config.login, self->config.password); } static struct evbuffer* socks5_mkassociate(void *p) { struct sockaddr_in sa; p = p; /* Make compiler happy */ memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; return socks5_mkcommand_plain(socks5_cmd_udp_associate, &sa); } /*********************************************************************** * Logic */ static void redudp_drop_client(redudp_client *client) { int fd; redudp_log_error(client, LOG_INFO, "Dropping..."); enqueued_packet *q, *tmp; if (event_initialized(&client->timeout)) { if (event_del(&client->timeout) == -1) redudp_log_errno(client, LOG_ERR, "event_del"); } if (client->relay) { fd = EVENT_FD(&client->relay->ev_read); bufferevent_free(client->relay); shutdown(fd, SHUT_RDWR); redsocks_close(fd); } if (event_initialized(&client->udprelay)) { fd = EVENT_FD(&client->udprelay); if (event_del(&client->udprelay) == -1) redudp_log_errno(client, LOG_ERR, "event_del"); redsocks_close(fd); } list_for_each_entry_safe(q, tmp, &client->queue, list) { list_del(&q->list); free(q); } list_del(&client->list); free(client); } static void redudp_bump_timeout(redudp_client *client) { struct timeval tv; tv.tv_sec = client->instance->config.udp_timeout; tv.tv_usec = 0; // TODO: implement udp_timeout_stream if (event_add(&client->timeout, &tv) != 0) { redudp_log_error(client, LOG_WARNING, "event_add(&client->timeout, ...)"); redudp_drop_client(client); } } static void redudp_forward_pkt(redudp_client *client, char *buf, size_t pktlen) { socks5_udp_preabmle req; struct msghdr msg; struct iovec io[2]; ssize_t outgoing, fwdlen = pktlen + sizeof(req); redudp_fill_preamble(&req, client); memset(&msg, 0, sizeof(msg)); msg.msg_name = &client->udprelayaddr; msg.msg_namelen = sizeof(client->udprelayaddr); msg.msg_iov = io; msg.msg_iovlen = SIZEOF_ARRAY(io); io[0].iov_base = &req; io[0].iov_len = sizeof(req); io[1].iov_base = buf; io[1].iov_len = pktlen; outgoing = sendmsg(EVENT_FD(&client->udprelay), &msg, 0); if (outgoing == -1) { redudp_log_errno(client, LOG_WARNING, "sendmsg: Can't forward packet, dropping it"); return; } else if (outgoing != fwdlen) { redudp_log_error(client, LOG_WARNING, "sendmsg: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); return; } } static int redudp_enqeue_pkt(redudp_client *client, char *buf, size_t pktlen) { enqueued_packet *q = NULL; redudp_log_error(client, LOG_DEBUG, ""); if (client->queue_len >= client->instance->config.max_pktqueue) { redudp_log_error(client, LOG_WARNING, "There are already %u packets in queue. Dropping.", client->queue_len); return -1; } q = calloc(1, sizeof(enqueued_packet) + pktlen); if (!q) { redudp_log_errno(client, LOG_ERR, "Can't enqueue packet: calloc"); return -1; } q->len = pktlen; memcpy(q->data, buf, pktlen); client->queue_len += 1; list_add_tail(&q->list, &client->queue); return 0; } static void redudp_flush_queue(redudp_client *client) { enqueued_packet *q, *tmp; redudp_log_error(client, LOG_INFO, "Starting UDP relay"); list_for_each_entry_safe(q, tmp, &client->queue, list) { redudp_forward_pkt(client, q->data, q->len); list_del(&q->list); free(q); } client->queue_len = 0; assert(list_empty(&client->queue)); } static void redudp_read_assoc_reply(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; redudp_expected_assoc_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); int fd = -1; int error; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } if (reply.h.ver != socks5_ver) { redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version: %u", reply.h.ver); goto fail; } if (reply.h.status != socks5_status_succeeded) { redudp_log_error(client, LOG_NOTICE, "Socks5 server status: \"%s\" (%i)", socks5_status_to_str(reply.h.status), reply.h.status); goto fail; } if (reply.h.addrtype != socks5_addrtype_ipv4) { redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type for UDP dgram destination: %u", reply.h.addrtype); goto fail; } client->udprelayaddr.sin_family = AF_INET; client->udprelayaddr.sin_port = reply.ip.port; client->udprelayaddr.sin_addr.s_addr = reply.ip.addr; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { redudp_log_errno(client, LOG_ERR, "socket"); goto fail; } error = connect(fd, (struct sockaddr*)&client->udprelayaddr, sizeof(client->udprelayaddr)); if (error) { redudp_log_errno(client, LOG_NOTICE, "connect"); goto fail; } event_set(&client->udprelay, fd, EV_READ | EV_PERSIST, redudp_pkt_from_socks, client); error = event_add(&client->udprelay, NULL); if (error) { redudp_log_errno(client, LOG_ERR, "event_add"); goto fail; } redudp_flush_queue(client); // TODO: bufferevent_disable ? return; fail: if (fd != -1) redsocks_close(fd); redudp_drop_client(client); } static void redudp_read_auth_reply(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; socks5_auth_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); int error; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } if (reply.ver != socks5_password_ver || reply.status != socks5_password_passed) { redudp_log_error(client, LOG_NOTICE, "Socks5 authentication error. Version: %u, error code: %u", reply.ver, reply.status); goto fail; } error = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); if (error) goto fail; client->relay->readcb = redudp_read_assoc_reply; return; fail: redudp_drop_client(client); } static void redudp_read_auth_methods(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); socks5_method_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); const char *error = NULL; int ierror = 0; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } error = socks5_is_known_auth_method(&reply, do_password); if (error) { redudp_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); goto fail; } else if (reply.method == socks5_auth_none) { ierror = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); if (ierror) goto fail; client->relay->readcb = redudp_read_assoc_reply; } else if (reply.method == socks5_auth_password) { ierror = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkpassword_plain_wrapper, client->instance, 0, /* last one is ignored */ sizeof(socks5_auth_reply), sizeof(socks5_auth_reply)); if (ierror) goto fail; client->relay->readcb = redudp_read_auth_reply; } return; fail: redudp_drop_client(client); } static void redudp_relay_connected(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); int error; redudp_log_error(client, LOG_DEBUG, ""); if (!red_is_socket_connected_ok(buffev)) { redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); goto fail; } error = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkmethods_plain_wrapper, &do_password, 0 /* does not matter */, sizeof(socks5_method_reply), sizeof(socks5_method_reply)); if (error) goto fail; client->relay->readcb = redudp_read_auth_methods; client->relay->writecb = 0; //bufferevent_disable(buffev, EV_WRITE); // I don't want to check for writeability. return; fail: redudp_drop_client(client); } static void redudp_relay_error(struct bufferevent *buffev, short what, void *_arg) { redudp_client *client = _arg; // TODO: FIXME: Implement me redudp_log_error(client, LOG_NOTICE, "redudp_relay_error"); redudp_drop_client(client); } static void redudp_timeout(int fd, short what, void *_arg) { redudp_client *client = _arg; redudp_log_error(client, LOG_INFO, "Client timeout. First: %li, last_client: %li, last_relay: %li.", client->first_event, client->last_client_event, client->last_relay_event); redudp_drop_client(client); } static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, char *buf, size_t pktlen) { redudp_client *client = calloc(1, sizeof(*client)); if (!client) { log_errno(LOG_WARNING, "calloc"); return; } INIT_LIST_HEAD(&client->list); INIT_LIST_HEAD(&client->queue); client->instance = self; memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr)); evtimer_set(&client->timeout, redudp_timeout, client); // XXX: self->relay_ss->init(client); client->relay = red_connect_relay(&client->instance->config.relayaddr, redudp_relay_connected, redudp_relay_error, client); if (!client->relay) goto fail; if (redsocks_time(&client->first_event) == (time_t)-1) goto fail; client->last_client_event = client->first_event; redudp_bump_timeout(client); if (redudp_enqeue_pkt(client, buf, pktlen) == -1) goto fail; list_add(&client->list, &self->clients); redudp_log_error(client, LOG_INFO, "got 1st packet from client"); return; fail: redudp_drop_client(client); } static void redudp_pkt_from_socks(int fd, short what, void *_arg) { redudp_client *client = _arg; union { char buf[0xFFFF]; socks5_udp_preabmle header; } pkt; ssize_t pktlen, fwdlen, outgoing; struct sockaddr_in udprelayaddr; assert(fd == EVENT_FD(&client->udprelay)); pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr); if (pktlen == -1) return; if (memcmp(&udprelayaddr, &client->udprelayaddr, sizeof(udprelayaddr)) != 0) { char buf[INET6_ADDRSTRLEN]; const char *addr = inet_ntop(udprelayaddr.sin_family, &udprelayaddr.sin_addr, buf, sizeof(buf)); redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s:%u.", addr ? addr : "?", ntohs(udprelayaddr.sin_port)); return; } if (pkt.header.frag_no != 0) { // FIXME: does anybody need it? redudp_log_error(client, LOG_WARNING, "Got fragment #%u. Packet fragmentation is not supported!", pkt.header.frag_no); return; } if (pkt.header.addrtype != socks5_addrtype_ipv4) { redudp_log_error(client, LOG_NOTICE, "Got address type #%u instead of expected #%u (IPv4).", pkt.header.addrtype, socks5_addrtype_ipv4); return; } if (pkt.header.ip.port != client->instance->config.destaddr.sin_port || pkt.header.ip.addr != client->instance->config.destaddr.sin_addr.s_addr) { char buf[INET6_ADDRSTRLEN]; const char *addr = inet_ntop(AF_INET, &pkt.header.ip.addr, buf, sizeof(buf)); redudp_log_error(client, LOG_NOTICE, "Socks5 server relayed packet from unexpected address %s:%u.", addr ? addr : "?", ntohs(pkt.header.ip.port)); return; } redsocks_time(&client->last_relay_event); redudp_bump_timeout(client); fwdlen = pktlen - sizeof(pkt.header); outgoing = sendto(EVENT_FD(&client->instance->listener), pkt.buf + sizeof(pkt.header), fwdlen, 0, (struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr)); if (outgoing != fwdlen) { redudp_log_error(client, LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); return; } } static void redudp_pkt_from_client(int fd, short what, void *_arg) { redudp_instance *self = _arg; struct sockaddr_in clientaddr; char buf[0xFFFF]; // UDP packet can't be larger then that ssize_t pktlen; redudp_client *tmp, *client = NULL; assert(fd == EVENT_FD(&self->listener)); pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr); if (pktlen == -1) return; // TODO: this lookup may be SLOOOOOW. list_for_each_entry(tmp, &self->clients, list) { if (0 == memcmp(&clientaddr, &tmp->clientaddr, sizeof(clientaddr))) { client = tmp; break; } } if (client) { redsocks_time(&client->last_client_event); redudp_bump_timeout(client); if (event_initialized(&client->udprelay)) { redudp_forward_pkt(client, buf, pktlen); } else { redudp_enqeue_pkt(client, buf, pktlen); } } else { redudp_first_pkt_from_client(self, &clientaddr, buf, pktlen); } } /*********************************************************************** * Init / shutdown */ static parser_entry redudp_entries[] = { { .key = "local_ip", .type = pt_in_addr }, { .key = "local_port", .type = pt_uint16 }, { .key = "ip", .type = pt_in_addr }, { .key = "port", .type = pt_uint16 }, { .key = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "dest_ip", .type = pt_in_addr }, { .key = "dest_port", .type = pt_uint16 }, { .key = "udp_timeout", .type = pt_uint16 }, { .key = "udp_timeout_stream", .type = pt_uint16 }, { } }; static list_head instances = LIST_HEAD_INIT(instances); static int redudp_onenter(parser_section *section) { redudp_instance *instance = calloc(1, sizeof(*instance)); if (!instance) { parser_error(section->context, "Not enough memory"); return -1; } INIT_LIST_HEAD(&instance->list); INIT_LIST_HEAD(&instance->clients); instance->config.bindaddr.sin_family = AF_INET; instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); instance->config.relayaddr.sin_family = AF_INET; instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); instance->config.destaddr.sin_family = AF_INET; instance->config.destaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); instance->config.max_pktqueue = 5; instance->config.udp_timeout = 30; instance->config.udp_timeout_stream = 180; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : (strcmp(entry->key, "ip") == 0) ? (void*)&instance->config.relayaddr.sin_addr : (strcmp(entry->key, "port") == 0) ? (void*)&instance->config.relayaddr.sin_port : (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "dest_ip") == 0) ? (void*)&instance->config.destaddr.sin_addr : (strcmp(entry->key, "dest_port") == 0) ? (void*)&instance->config.destaddr.sin_port : (strcmp(entry->key, "max_pktqueue") == 0) ? (void*)&instance->config.max_pktqueue : (strcmp(entry->key, "udp_timeout") == 0) ? (void*)&instance->config.udp_timeout: (strcmp(entry->key, "udp_timeout_stream") == 0) ? (void*)&instance->config.udp_timeout_stream : NULL; section->data = instance; return 0; } static int redudp_onexit(parser_section *section) { redudp_instance *instance = section->data; section->data = NULL; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = NULL; instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); instance->config.relayaddr.sin_port = htons(instance->config.relayaddr.sin_port); instance->config.destaddr.sin_port = htons(instance->config.destaddr.sin_port); if (instance->config.udp_timeout_stream < instance->config.udp_timeout) { parser_error(section->context, "udp_timeout_stream should be not less then udp_timeout"); return -1; } list_add(&instance->list, &instances); return 0; } static int redudp_init_instance(redudp_instance *instance) { /* FIXME: redudp_fini_instance is called in case of failure, this * function will remove instance from instances list - result * looks ugly. */ int error; int fd = -1; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redudp_pkt_from_client, instance); error = event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } return 0; fail: redudp_fini_instance(instance); if (fd != -1) { redsocks_close(fd); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void redudp_fini_instance(redudp_instance *instance) { if (!list_empty(&instance->clients)) { redudp_client *tmp, *client = NULL; log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); list_for_each_entry_safe(client, tmp, &instance->clients, list) { redudp_drop_client(client); } } if (event_initialized(&instance->listener)) { if (event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); redsocks_close(EVENT_FD(&instance->listener)); memset(&instance->listener, 0, sizeof(instance->listener)); } list_del(&instance->list); free(instance->config.login); free(instance->config.password); memset(instance, 0, sizeof(*instance)); free(instance); } static int redudp_init() { redudp_instance *tmp, *instance = NULL; // TODO: init debug_dumper list_for_each_entry_safe(instance, tmp, &instances, list) { if (redudp_init_instance(instance) != 0) goto fail; } return 0; fail: redudp_fini(); return -1; } static int redudp_fini() { redudp_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) redudp_fini_instance(instance); return 0; } static parser_section redudp_conf_section = { .name = "redudp", .entries = redudp_entries, .onenter = redudp_onenter, .onexit = redudp_onexit }; app_subsys redudp_subsys = { .init = redudp_init, .fini = redudp_fini, .conf_section = &redudp_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/redudp.h0000644000175000017500000000231111733675122015445 0ustar apoikosapoikos#ifndef REDUDP_H #define REDUDP_H typedef struct redudp_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; // TODO: outgoingaddr; struct sockaddr_in destaddr; char *login; char *password; uint16_t max_pktqueue; uint16_t udp_timeout; uint16_t udp_timeout_stream; } redudp_config; typedef struct redudp_instance_t { list_head list; redudp_config config; struct event listener; list_head clients; } redudp_instance; typedef struct redudp_client_t { list_head list; redudp_instance *instance; struct sockaddr_in clientaddr; struct event timeout; struct bufferevent *relay; struct event udprelay; struct sockaddr_in udprelayaddr; int state; // it's used by bottom layer time_t first_event; time_t last_client_event; time_t last_relay_event; unsigned int queue_len; list_head queue; } redudp_client; typedef struct enqueued_packet_t { list_head list; size_t len; char data[1]; } enqueued_packet; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDUDP_H */ redsocks-0.4+dfsg/socks4.c0000644000175000017500000000760011733675122015371 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include "log.h" #include "redsocks.h" typedef enum socks4_state_t { socks4_new, socks4_request_sent, socks4_reply_came, socks4_MAX, } socks4_state; typedef struct socks4_req_t { uint8_t ver; uint8_t cmd; uint16_t port; uint32_t addr; char login[1]; // we need at least zero-byte } PACKED socks4_req; const int socks4_ver = 4; const int socks4_cmd_connect = 1; const int socks4_cmd_bind = 2; typedef struct socks4_reply_t { uint8_t ver; uint8_t status; uint16_t port; uint32_t addr; } PACKED socks4_reply; const int socks4_status_ok = 90; const int socks4_status_fail = 91; const int socks4_status_no_ident = 92; const int socks4_status_fake_ident = 93; void socks4_client_init(redsocks_client *client) { if (client->instance->config.password) redsocks_log_error(client, LOG_WARNING, "password is ignored for socks4 connections"); client->state = socks4_new; } static void socks4_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(client->state >= socks4_request_sent); redsocks_touch_client(client); if (client->state == socks4_request_sent) { socks4_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) return; client->state = socks4_reply_came; if (reply.ver != 0) { redsocks_log_error(client, LOG_NOTICE, "Socks4 server reported unexpected reply version..."); redsocks_drop_client(client); } else if (reply.status == socks4_status_ok) redsocks_start_relay(client); else { redsocks_log_error(client, LOG_NOTICE, "Socks4 server status: %s (%i)", reply.status == socks4_status_fail ? "fail" : reply.status == socks4_status_no_ident ? "no ident" : reply.status == socks4_status_fake_ident ? "fake ident" : "?", reply.status); redsocks_drop_client(client); } } } static struct evbuffer *socks4_mkconnect(redsocks_client *client) { const redsocks_config *config = &client->instance->config; const char *username = config->login ? config->login : ""; // space for \0 comes from socks4_req->login size_t username_len = strlen(username); size_t len = sizeof(socks4_req) + username_len; socks4_req *req = calloc(1, len); req->ver = socks4_ver; req->cmd = socks4_cmd_connect; req->port = client->destaddr.sin_port; req->addr = client->destaddr.sin_addr.s_addr; memcpy(req->login, username, username_len + 1); struct evbuffer *ret = mkevbuffer(req, len); free(req); return ret; } static void socks4_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == socks4_new) { redsocks_write_helper( buffev, client, socks4_mkconnect, socks4_request_sent, sizeof(socks4_reply) ); } else if (client->state >= socks4_request_sent) { bufferevent_disable(buffev, EV_WRITE); } } relay_subsys socks4_subsys = { .name = "socks4", .payload_len = 0, .instance_payload_len = 0, .readcb = socks4_read_cb, .writecb = socks4_write_cb, .init = socks4_client_init, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/socks5.c0000644000175000017500000002217511733675122015376 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include "utils.h" #include "log.h" #include "redsocks.h" #include "socks5.h" typedef enum socks5_state_t { socks5_new, socks5_method_sent, socks5_auth_sent, socks5_request_sent, socks5_skip_domain, socks5_skip_address, socks5_MAX, } socks5_state; typedef struct socks5_client_t { int do_password; // 1 - password authentication is possible int to_skip; // valid while reading last reply (after main request) } socks5_client; const char *socks5_strstatus[] = { "ok", "server failure", "connection not allowed by ruleset", "network unreachable", "host unreachable", "connection refused", "TTL expired", "command not supported", "address type not supported", }; const size_t socks5_strstatus_len = SIZEOF_ARRAY(socks5_strstatus); const char* socks5_status_to_str(int socks5_status) { if (0 <= socks5_status && socks5_status < socks5_strstatus_len) { return socks5_strstatus[socks5_status]; } else { return ""; } } int socks5_is_valid_cred(const char *login, const char *password) { if (!login || !password) return 0; if (strlen(login) > 255) { log_error(LOG_WARNING, "Socks5 login can't be more than 255 chars, <%s> is too long", login); return 0; } if (strlen(password) > 255) { log_error(LOG_WARNING, "Socks5 password can't be more than 255 chars, <%s> is too long", password); return 0; } return 1; } void socks5_client_init(redsocks_client *client) { socks5_client *socks5 = (void*)(client + 1); const redsocks_config *config = &client->instance->config; client->state = socks5_new; socks5->do_password = socks5_is_valid_cred(config->login, config->password); } static struct evbuffer *socks5_mkmethods(redsocks_client *client) { socks5_client *socks5 = (void*)(client + 1); return socks5_mkmethods_plain(socks5->do_password); } struct evbuffer *socks5_mkmethods_plain(int do_password) { assert(do_password == 0 || do_password == 1); int len = sizeof(socks5_method_req) + do_password; socks5_method_req *req = calloc(1, len); req->ver = socks5_ver; req->num_methods = 1 + do_password; req->methods[0] = socks5_auth_none; if (do_password) req->methods[1] = socks5_auth_password; struct evbuffer *ret = mkevbuffer(req, len); free(req); return ret; } static struct evbuffer *socks5_mkpassword(redsocks_client *client) { return socks5_mkpassword_plain(client->instance->config.login, client->instance->config.password); } struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password) { size_t ulen = strlen(login); size_t plen = strlen(password); size_t length = 1 /* version */ + 1 + ulen + 1 + plen; uint8_t req[length]; req[0] = socks5_password_ver; // RFC 1929 says so req[1] = ulen; memcpy(&req[2], login, ulen); req[2+ulen] = plen; memcpy(&req[3+ulen], password, plen); return mkevbuffer(req, length); } struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr) { struct { socks5_req head; socks5_addr_ipv4 ip; } PACKED req; assert(destaddr->sin_family == AF_INET); req.head.ver = socks5_ver; req.head.cmd = socks5_cmd; req.head.reserved = 0; req.head.addrtype = socks5_addrtype_ipv4; req.ip.addr = destaddr->sin_addr.s_addr; req.ip.port = destaddr->sin_port; return mkevbuffer(&req, sizeof(req)); } static struct evbuffer *socks5_mkconnect(redsocks_client *client) { return socks5_mkcommand_plain(socks5_cmd_connect, &client->destaddr); } static void socks5_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == socks5_new) { redsocks_write_helper( buffev, client, socks5_mkmethods, socks5_method_sent, sizeof(socks5_method_reply) ); } } const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password) { if (reply->ver != socks5_ver) return "Socks5 server reported unexpected auth methods reply version..."; else if (reply->method == socks5_auth_invalid) return "Socks5 server refused all our auth methods."; else if (reply->method != socks5_auth_none && !(reply->method == socks5_auth_password && do_password)) return "Socks5 server requested unexpected auth method..."; else return NULL; } static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_method_reply reply; const char *error = NULL; if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) return; error = socks5_is_known_auth_method(&reply, socks5->do_password); if (error) { redsocks_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); redsocks_drop_client(client); } else if (reply.method == socks5_auth_none) { redsocks_write_helper( buffev, client, socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) ); } else if (reply.method == socks5_auth_password) { redsocks_write_helper( buffev, client, socks5_mkpassword, socks5_auth_sent, sizeof(socks5_auth_reply) ); } } static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_auth_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_password_ver) { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected auth reply version..."); redsocks_drop_client(client); } else if (reply.status == socks5_password_passed) redsocks_write_helper( buffev, client, socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) ); else redsocks_drop_client(client); } static void socks5_read_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_ver) { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version..."); redsocks_drop_client(client); } else if (reply.status == socks5_status_succeeded) { socks5_state nextstate; size_t len; if (reply.addrtype == socks5_addrtype_ipv4) { len = socks5->to_skip = sizeof(socks5_addr_ipv4); nextstate = socks5_skip_address; } else if (reply.addrtype == socks5_addrtype_ipv6) { len = socks5->to_skip = sizeof(socks5_addr_ipv6); nextstate = socks5_skip_address; } else if (reply.addrtype == socks5_addrtype_domain) { socks5_addr_domain domain; len = sizeof(domain.size); nextstate = socks5_skip_domain; } else { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type..."); redsocks_drop_client(client); return; } redsocks_write_helper( buffev, client, NULL, nextstate, len ); } else { redsocks_log_error(client, LOG_NOTICE, "Socks5 server status: %s (%i)", /* 0 <= reply.status && */ reply.status < SIZEOF_ARRAY(socks5_strstatus) ? socks5_strstatus[reply.status] : "?", reply.status); redsocks_drop_client(client); } } static void socks5_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; socks5_client *socks5 = (void*)(client + 1); redsocks_touch_client(client); if (client->state == socks5_method_sent) { socks5_read_auth_methods(buffev, client, socks5); } else if (client->state == socks5_auth_sent) { socks5_read_auth_reply(buffev, client, socks5); } else if (client->state == socks5_request_sent) { socks5_read_reply(buffev, client, socks5); } else if (client->state == socks5_skip_domain) { socks5_addr_ipv4 ipv4; // all socks5_addr*.port are equal uint8_t size; if (redsocks_read_expected(client, buffev->input, &size, sizes_greater_equal, sizeof(size)) < 0) return; socks5->to_skip = size + sizeof(ipv4.port); redsocks_write_helper( buffev, client, NULL, socks5_skip_address, socks5->to_skip ); } else if (client->state == socks5_skip_address) { uint8_t data[socks5->to_skip]; if (redsocks_read_expected(client, buffev->input, data, sizes_greater_equal, socks5->to_skip) < 0) return; redsocks_start_relay(client); } else { redsocks_drop_client(client); } } relay_subsys socks5_subsys = { .name = "socks5", .payload_len = sizeof(socks5_client), .instance_payload_len = 0, .readcb = socks5_read_cb, .writecb = socks5_write_cb, .init = socks5_client_init, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-0.4+dfsg/socks5.h0000644000175000017500000000565311733675122015405 0ustar apoikosapoikos#ifndef SOCKS5_H #define SOCKS5_H #include #include "utils.h" typedef struct socks5_method_req_t { uint8_t ver; uint8_t num_methods; uint8_t methods[1]; // at least one } PACKED socks5_method_req; typedef struct socks5_method_reply_t { uint8_t ver; uint8_t method; } PACKED socks5_method_reply; static const int socks5_ver = 5; static const int socks5_auth_none = 0x00; static const int socks5_auth_gssapi = 0x01; static const int socks5_auth_password = 0x02; static const int socks5_auth_invalid = 0xFF; typedef struct socks5_auth_reply_t { uint8_t ver; uint8_t status; } PACKED socks5_auth_reply; static const int socks5_password_ver = 0x01; static const int socks5_password_passed = 0x00; typedef struct socks5_addr_ipv4_t { uint32_t addr; uint16_t port; } PACKED socks5_addr_ipv4; typedef struct socks5_addr_domain_t { uint8_t size; uint8_t more[1]; /* uint16_t port; */ } PACKED socks5_addr_domain; typedef struct socks5_addr_ipv6_t { uint8_t addr[16]; uint16_t port; } PACKED socks5_addr_ipv6; typedef struct socks5_req_t { uint8_t ver; uint8_t cmd; uint8_t reserved; uint8_t addrtype; /* socks5_addr_* */ } PACKED socks5_req; typedef struct socks5_reply_t { uint8_t ver; uint8_t status; uint8_t reserved; uint8_t addrtype; /* socks5_addr_* */ } PACKED socks5_reply; typedef struct socks5_udp_preabmle_t { uint16_t reserved; uint8_t frag_no; uint8_t addrtype; /* 0x01 for IPv4 */ /* socks5_addr_* */ socks5_addr_ipv4 ip; /* I support only IPv4 at the moment */ } PACKED socks5_udp_preabmle; static const int socks5_reply_maxlen = 512; // as domain name can't be longer than 256 bytes static const int socks5_addrtype_ipv4 = 1; static const int socks5_addrtype_domain = 3; static const int socks5_addrtype_ipv6 = 4; static const int socks5_status_succeeded = 0; static const int socks5_status_server_failure = 1; static const int socks5_status_connection_not_allowed_by_ruleset = 2; static const int socks5_status_Network_unreachable = 3; static const int socks5_status_Host_unreachable = 4; static const int socks5_status_Connection_refused = 5; static const int socks5_status_TTL_expired = 6; static const int socks5_status_Command_not_supported = 7; static const int socks5_status_Address_type_not_supported = 8; const char* socks5_status_to_str(int socks5_status); int socks5_is_valid_cred(const char *login, const char *password); struct evbuffer *socks5_mkmethods_plain(int do_password); struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password); const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password); static const int socks5_cmd_connect = 1; static const int socks5_cmd_bind = 2; static const int socks5_cmd_udp_associate = 3; struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* SOCKS5_H */