diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 18 | ||||
-rw-r--r-- | src/challenge.c | 56 | ||||
-rw-r--r-- | src/challenge.h | 23 | ||||
-rw-r--r-- | src/md5.c | 381 | ||||
-rw-r--r-- | src/md5.h | 92 | ||||
-rw-r--r-- | src/options.c | 504 | ||||
-rw-r--r-- | src/options.h | 85 | ||||
-rw-r--r-- | src/pconfig.h | 91 | ||||
-rw-r--r-- | src/pdesc.c | 228 | ||||
-rw-r--r-- | src/pdesc.h | 136 | ||||
-rw-r--r-- | src/pkt.c | 403 | ||||
-rw-r--r-- | src/pkt.h | 96 | ||||
-rw-r--r-- | src/ptunnel.c | 781 | ||||
-rw-r--r-- | src/ptunnel.h | 145 | ||||
-rw-r--r-- | src/utils.c | 73 | ||||
-rw-r--r-- | src/utils.h | 14 |
16 files changed, 3126 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..b51bad2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,18 @@ +bin_PROGRAMS = ptunnel + +ptunnel_CFLAGS = -Wall +if HAVE_PCAP +ptunnel_CFLAGS += -DHAVE_PCAP=1 +endif +if HAVE_SELINUX +ptunnel_CFLAGS += -DHAVE_SELINUX=1 +endif + +ptunnel_SOURCES = \ + md5.c \ + challenge.c \ + options.c \ + utils.c \ + pkt.c \ + pdesc.c \ + ptunnel.c diff --git a/src/challenge.c b/src/challenge.c new file mode 100644 index 0000000..4d8bf65 --- /dev/null +++ b/src/challenge.c @@ -0,0 +1,56 @@ +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#include "challenge.h" +#include "options.h" +#include "md5.h" + +/* generate_challenge: Generates a random challenge, incorporating the current + * local timestamp to avoid replay attacks. + */ +challenge_t* generate_challenge(void) { + struct timeval tt; + challenge_t *c; + int i; + + c = (challenge_t *) calloc(1, sizeof(challenge_t)); + gettimeofday(&tt, 0); + c->sec = tt.tv_sec; + c->usec_rnd = tt.tv_usec + rand(); + for (i=0;i<6;i++) + c->random[i] = rand(); + + return c; +} + +/* generate_response: Generates a response to the given challenge. The response + * is generated by combining the concatenating the challenge data with the + * md5 digest of the password, and then calculating the MD5 digest of the + * entire buffer. The result is stored in the passed-in challenge, overwriting + * the challenge data. + */ +void generate_response(challenge_t *challenge) { + md5_byte_t buf[sizeof(challenge_t)+kMD5_digest_size]; + md5_state_t state; + + memcpy(buf, challenge, sizeof(challenge_t)); + memcpy(&buf[sizeof(challenge_t)], opts.password_digest, kMD5_digest_size); + memset(challenge, 0, sizeof(challenge_t)); + md5_init(&state); + md5_append(&state, buf, sizeof(challenge_t)+kMD5_digest_size); + md5_finish(&state, (md5_byte_t*)challenge); +} + +/* validate_challenge: Checks whether a given response matches the expected + * response, returning 1 if validation succeeded, and 0 otherwise. Note that + * overwriting the local challenge with the challenge result is not a problem, + * as the data will not be used again anyway (authentication either succeeds, + * or the connection is closed down). + */ +int validate_challenge(challenge_t *local, challenge_t *remote) { + generate_response(local); + if (memcmp(local, remote, sizeof(challenge_t)) == 0) + return 1; + return 0; +} diff --git a/src/challenge.h b/src/challenge.h new file mode 100644 index 0000000..4863782 --- /dev/null +++ b/src/challenge.h @@ -0,0 +1,23 @@ +#ifndef CHALLENGE_H +#define CHALLENGE_H 1 + +#include <stdint.h> + +/** challenge_t: This structure contains the pseudo-random challenge used for + * authentication. + */ +typedef struct challenge_t { + /** tv_sec as returned by gettimeofday */ + uint32_t sec; + /** tv_usec as returned by gettimeofday + random value */ + uint32_t usec_rnd; + /** random values */ + uint32_t random[6]; +} __attribute__ ((packed)) challenge_t; + + +challenge_t* generate_challenge(void); +void generate_response(challenge_t *challenge); +int validate_challenge(challenge_t *local, challenge_t *remote); + +#endif diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000..cd87d02 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,381 @@ +/* + 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.1 2005/04/15 07:37:22 daniels 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 + <ghost@aladdin.com>. 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 <string.h> + 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 <stdio.h> 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 <string.h> + +#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)); +} diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 0000000..f9fdeb4 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,92 @@ +/* + 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.1 2005/04/15 07:37:22 daniels 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 + <ghost@aladdin.com>. 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 <purschke@bnl.gov>. + 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 MD5_LEN 16 + +/* 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[MD5_LEN]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..0c9bbe8 --- /dev/null +++ b/src/options.c @@ -0,0 +1,504 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdint.h> +#include <string.h> +#include <getopt.h> +#include <ctype.h> +#include <assert.h> + +#include "options.h" +#include "utils.h" +#include "ptunnel.h" +#include "md5.h" + + +struct options opts; + +enum option_type { + OPT_BOOL, OPT_DEC32, OPT_HEX32, OPT_STR +}; + +struct option_usage { + const char *short_help; + int required; + enum option_type otype; + union { + int32_t num; + uint32_t unum; + const char *str; + }; + const char *long_help; +}; + +static const struct option_usage usage[] = { + /** --magic */ + {"magic", 0, OPT_HEX32, {.unum = 0xdeadc0de}, + "Set ptunnel magic hexadecimal number. (32-bit unsigned)\n" + "It is an identifier for all ICMP/UDP packets\n" + "and can be used to bypass Cisco IPS fingerprint scan.\n" + "This value has to be the same on the server and client!\n" + }, + /** --proxy */ + {"address", 1, OPT_STR, {.str = NULL}, + "Set address of peer running packet forwarder. This causes\n" + "ptunnel to operate in forwarding mode - the absence of this\n" + "option causes ptunnel to operate in proxy mode.\n" + }, + /** --listen */ + {"port", 1, OPT_DEC32, {.unum = 2222}, + "Set TCP listening port (only used when operating in forward mode)\n" + }, + /** --remote-adr */ + {"address", 1, OPT_STR, {.str = "127.0.0.1"}, + "Set remote proxy destination address if client\n" + "Restrict to only this destination address if server\n" + }, + /** --remote-port */ + {"port", 1, OPT_DEC32, {.unum = 22}, + "Set remote proxy destination port if client\n" + "Restrict to only this destination port if server\n" + }, + /** --connections */ + {"connections", 0, OPT_DEC32, {.unum = kMax_tunnels}, + "Set maximum number of concurrent tunnels\n" + }, + /** --verbosity */ + {"level", 0, OPT_DEC32, {.num = kLog_event}, + "Verbosity level (-1 to 4, where -1 is no output, and 4 is all output)\n" + "The special level 5 (or higher) includes xfer logging (lots of output)\n" + }, + /** --libpcap */ + {"interface", 0, OPT_STR, {.str = "eth0"}, + "Enable libpcap on the given device.\n" + }, + /** --logfile */ + {"file", 0, OPT_STR, {.str = "/var/log/ptunnel.log"}, + "Specify a file to log to, rather than printing to standard out.\n" + }, + /** --statistics */ + {NULL, 0, OPT_BOOL, {.num = 0}, + "Client only. Enables continuous output of statistics (packet loss, etc.)\n" + }, + /** --passwd */ + {"password", 0, OPT_STR, {.str = NULL}, + "Set password (must be same on client and proxy)\n" + "If no password is set, you will be asked during runtime.\n" + }, + /** --udp */ + {NULL, 0, OPT_BOOL, {.num = 0}, + "Toggle use of UDP instead of ICMP. Proxy will listen on port 53 (must be root).\n" + }, + /** --unprivileged */ + {NULL, 0, OPT_BOOL, {.num = 0}, + "Run proxy in unprivileged mode. This causes the proxy to forward\n" + "packets using standard echo requests, instead of crafting custom echo replies.\n" + "Unprivileged mode will only work on some systems, and is in general less reliable\n" + "than running in privileged mode.\n" + }, + /** --base64 */ + {NULL, 0, OPT_BOOL, {.num = 0}, + "Base64 encode/decode all outoging/incoming packets."}, +#ifndef WIN32 + /** --daemon */ + {"pidfile", 0, OPT_STR, {.str = "/run/ptunnel.pid"}, + "Run in background, the PID will be written in the file supplied as argument\n" + }, + /** --syslog */ + {NULL, 0, OPT_BOOL, {.num = 0}, + "Output debug to syslog instead of standard out.\n" + }, + /** --user */ + {"user", 0, OPT_STR, {.str = "nobody"}, + "When started in privileged mode, drop down to user's rights as soon as possible\n" + }, + /** --group */ + {"group", 0, OPT_STR, {.str = "nogroup"}, + "When started in privileged mode, drop down to group's rights as soon as possible\n" + }, + /** --chroot */ + {"directory", 0, OPT_STR, {.str = "/var/lib/ptunnel"}, + "When started in privileged mode, restrict file access to the specified directory\n" + }, +#endif + /** --setcon */ + {NULL, 0, OPT_STR, {.num = 0}, + "Set SELinux context when all there is left to do are network I/O operations\n" + "To combine with -chroot you will have to `mount --bind /proc /chrootdir/proc`\n" + }, + /** --help */ + {"help", 0, OPT_STR, {.str = NULL}, "this\n"}, + {NULL,0,OPT_BOOL,{.unum=0},NULL} +}; + +static struct option long_options[] = { + {"magic", required_argument, 0, 'm'}, + {"proxy", required_argument, 0, 'p'}, + {"listen", required_argument, 0, 'l'}, + {"remote-adr", optional_argument, 0, 'r'}, + {"remote-port", optional_argument, 0, 'R'}, + {"connections", required_argument, 0, 'c'}, + {"verbosity", required_argument, 0, 'v'}, + {"libpcap", required_argument, 0, 'a'}, + {"logfile", optional_argument, 0, 'o'}, + {"statistics", no_argument, 0, 's'}, + {"passwd", required_argument, 0, 'x'}, + {"udp", no_argument, &opts.udp, 1 }, + {"unprivileged", no_argument, &opts.unprivileged, 1 }, + {"base64", no_argument, &opts.base64, 1 }, +#ifndef WIN32 + {"daemon", optional_argument, 0, 'd'}, + {"syslog", no_argument, 0, 'S'}, + {"user", optional_argument, 0, 'u'}, + {"group", optional_argument, 0, 'g'}, + {"chroot", optional_argument, 0, 'C'}, +#endif + {"setcon", no_argument, 0, 'e'}, + {"help", no_argument, 0, 'h'}, + {NULL,0,0,0} +}; + + +static const void *get_default_optval(enum option_type opttype, const char *optname) { + for (unsigned i = 0; i < ARRAY_SIZE(long_options); ++i) { + if (strncmp(long_options[i].name, optname, strlen(long_options[i].name)) == 0) { + assert(usage[i].otype == opttype); + return &usage[i].str; + } + } + assert(NULL); + return NULL; +} + +static void set_options_defaults(void) { +#ifndef WIN32 + char *tmp; + struct passwd *pwnam; + struct group *grnam; +#endif + + memset(&opts, 0, sizeof(opts)); + opts.magic = *(uint32_t *) get_default_optval(OPT_HEX32, "magic"); + opts.mode = kMode_proxy; + opts.tcp_listen_port = *(uint32_t *) get_default_optval(OPT_DEC32, "listen"); + opts.given_dst_hostname = strdup(*(char **) get_default_optval(OPT_STR, "remote-adr")); + opts.given_dst_port = *(uint32_t *) get_default_optval(OPT_DEC32, "remote-port"); + opts.max_tunnels = *(uint32_t *) get_default_optval(OPT_DEC32, "connections"); + opts.log_level = *(int *) get_default_optval(OPT_DEC32, "verbosity"); +#ifdef HAVE_PCAP + opts.pcap_device = strdup(*(char **)get_default_optval(OPT_STR, "libpcap")); +#endif + opts.log_path = strdup(*(char **)get_default_optval(OPT_STR, "logfile")); + opts.log_file = stdout; + opts.print_stats = *(int *) get_default_optval(OPT_BOOL, "statistics"); + opts.udp = *(int *) get_default_optval(OPT_BOOL, "udp"); + opts.unprivileged = *(int *) get_default_optval(OPT_BOOL, "unprivileged"); +#ifndef WIN32 + opts.pid_path = strdup(*(char **)get_default_optval(OPT_STR, "daemon")); + + errno = 0; + tmp = *(char **) get_default_optval(OPT_STR, "user"); + if (NULL == (pwnam = getpwnam(tmp))) + pt_log(kLog_error, "%s: %s\n", tmp, errno ? strerror(errno) : "unknown user"); + else { + opts.uid = pwnam->pw_uid; + if (!opts.gid) + opts.gid = pwnam->pw_gid; + } + + errno = 0; + tmp = *(char **) get_default_optval(OPT_STR, "group"); + if (NULL == (grnam = getgrnam(tmp))) + pt_log(kLog_error, "%s: %s\n", tmp, errno ? strerror(errno) : "unknown group"); + else + opts.gid = grnam->gr_gid; + + opts.root_dir = strdup(*(char **)get_default_optval(OPT_STR, "chroot")); +#endif +} + +static void print_multiline(const char *prefix, const char *multiline) { + const char sep[] = "\n"; + const char *start, *end; + + start = multiline; + do { + if (start) { + end = strstr(start, sep); + if (end) { + printf("%s%.*s\n", prefix, (int)(end-start), start); + start = end + strlen(sep); + } + } + } while (start && end); +} + +static void print_long_help(unsigned index, int required_state) { + const char spaces[] = " "; + + if (usage[index].required != required_state) + return; + if (!long_options[index].name) + return; + + if (isalpha(long_options[index].val)) { + printf("%.*s-%c --%s\n", 4, spaces, long_options[index].val, long_options[index].name); + } else { + printf("%.*s--%s\n", 4, spaces, long_options[index].name); + } + + if (usage[index].long_help) { + print_multiline(&spaces[4], usage[index].long_help); + } + + switch (usage[index].otype) { + case OPT_BOOL: + break; + case OPT_DEC32: + printf("%s(default: %d)\n", spaces, usage[index].num); + break; + case OPT_HEX32: + printf("%s(default: 0x%X)\n", spaces, usage[index].unum); + break; + case OPT_STR: + if (usage[index].str) + printf("%s(default: %s)\n", spaces, usage[index].str); + break; + } +} + +static void print_short_help(unsigned index, int required_state) { + const char *ob = (required_state == 0 ? "[" : ""); + const char *cb = (required_state == 0 ? "]" : ""); + + if (usage[index].required != required_state) + return; + if (!long_options[index].name) + return; + + if (!usage[index].short_help && isalpha(long_options[index].val)) { + printf(" %s-%c%s", ob, long_options[index].val, cb); + } + else if (!usage[index].short_help) { + printf(" %s--%s%s", ob, long_options[index].name, cb); + } + else if (isalpha(long_options[index].val)) { + printf(" %s-%c <%s>%s", ob, long_options[index].val, usage[index].short_help, cb); + } + else { + printf(" %s--%s <%s>%s", ob, long_options[index].name, usage[index].short_help, cb); + } +} + +void print_usage(const char *arg0) { + unsigned i; + + printf("ptunnel-ng v%d.%.2d\n\nUsage: %s", kMajor_version, kMinor_version, arg0); + /* print (short)help argument line */ + for (i = 0; i < ARRAY_SIZE(usage); ++i) { + print_short_help(i, 1); + } + for (i = 0; i < ARRAY_SIZE(usage); ++i) { + print_short_help(i, 0); + } + + printf("%s", "\n\n"); + /* print (long)help lines */ + for (i = 0; i < ARRAY_SIZE(usage); ++i) { + print_long_help(i, 1); + } + for (i = 0; i < ARRAY_SIZE(usage); ++i) { + print_long_help(i, 0); + } +} + +int parse_options(int argc, char **argv) { + int c = 0, optind = -1, has_logfile = 0; + struct hostent *host_ent; + md5_state_t state; +#ifndef WIN32 + struct passwd *pwnam; + struct group *grnam; +#endif + FILE *tmp_log; + + assert( ARRAY_SIZE(long_options) == ARRAY_SIZE(usage) ); + + /* set defaults */ + set_options_defaults(); + + /* parse command line arguments */ + while (1) { + c = getopt_long(argc, argv, "m:p:l:r::R::c:v:a::o::sd::Sx:u::g::C::eh", &long_options[0], &optind); + if (c == -1) break; + + switch (c) { + case 'm': + opts.magic = strtoul(optarg, NULL, 16); + break; + case 'p': + opts.mode = kMode_forward; + if (opts.given_proxy_hostname) + free(opts.given_proxy_hostname); + opts.given_proxy_hostname = strdup(optarg); + break; + case 'l': + if (optarg) + opts.tcp_listen_port = strtoul(optarg, NULL, 10); + break; + case 'r': + if (!optarg) + break; + if (opts.given_dst_hostname) + free(opts.given_dst_hostname); + opts.given_dst_hostname = strdup(optarg); + break; + case 'R': + if (optarg) + opts.given_dst_port = strtoul(optarg, NULL, 10); + break; + case 'c': + opts.max_tunnels = strtoul(optarg, NULL,10); + if (opts.max_tunnels > kMax_tunnels) + opts.max_tunnels = kMax_tunnels; + break; + case 'v': + opts.log_level = strtol(optarg, NULL, 10); + break; + case 'a': +#ifdef HAVE_PCAP + opts.pcap = 1; + if (!optarg) + break; + if (opts.pcap_device) + free(opts.pcap_device); + opts.pcap_device = strdup(optarg); + break; +#else + pt_log(kLog_error, "-%c: feature not supported\n", c); + exit(1); +#endif + case 'o': + has_logfile = 1; + if (!optarg) + break; + if (opts.log_path) + free(opts.log_path); + opts.log_path = strdup(optarg); + break; + case 's': + opts.print_stats = !opts.print_stats; + break; + case 'x': + if (opts.password) + free(opts.password); + opts.password = strdup(optarg); + pt_log(kLog_debug, "Password set - unauthenicated connections will be refused.\n"); + // Compute the password digest + md5_init(&state); + md5_append(&state, (md5_byte_t*)optarg, strlen(opts.password)); + md5_finish(&state, &opts.password_digest[0]); + // Hide the password in process listing + memset(optarg, '*', strlen(optarg)); + break; +#ifndef WIN32 + case 'd': + opts.daemonize = true; + if (!optarg) + break; + if (opts.pid_path) + free(opts.pid_path); + opts.pid_path = strdup(optarg); + break; + case 'S': + opts.use_syslog = 1; + break; + case 'u': + if (!optarg) + break; + errno = 0; + if (NULL == (pwnam = getpwnam(optarg))) { + pt_log(kLog_error, "%s: %s\n", optarg, errno ? strerror(errno) : "unknown user"); + exit(1); + } + opts.uid = pwnam->pw_uid; + if (!opts.gid) + opts.gid = pwnam->pw_gid; + break; + case 'g': + if (!optarg) + break; + errno = 0; + if (NULL == (grnam = getgrnam(optarg))) { + pt_log(kLog_error, "%s: %s\n", optarg, errno ? strerror(errno) : "unknown group"); + exit(1); + } + opts.gid = grnam->gr_gid; + break; + case 'C': + opts.chroot = 1; + if (!optarg) + break; + if (opts.root_dir) + free(opts.root_dir); + opts.root_dir = strdup(optarg); + break; +#else + case 'd': + case 'S': + case 'u': + case 'g': + case 't': + pt_log(kLog_error, "-%c: feature not supported\n", c); + exit(1); +#endif + case 'e': +#ifdef HAVE_SELINUX + if (opts.selinux_context) + free(opts.selinux_context); + opts.selinux_context = strdup(optarg); + break; +#else + pt_log(kLog_error, "-%c: feature not supported\n", c); + exit(1); +#endif + case 'h': + print_usage(argv[0]); + _exit(EXIT_SUCCESS); + case 0: /* long opt only */ + default: + break; + } + } + + if (opts.given_proxy_hostname) { + if (NULL == (host_ent = gethostbyname(opts.given_proxy_hostname))) { + pt_log(kLog_error, "Failed to look up %s as proxy address\n", opts.given_proxy_hostname); + return 1; + } + opts.given_proxy_ip = *(uint32_t*)host_ent->h_addr_list[0]; + } + + if (NULL == (host_ent = gethostbyname(opts.given_dst_hostname))) { + pt_log(kLog_error, "Failed to look up %s as destination address\n", opts.given_dst_hostname); + return 1; + } + opts.given_dst_ip = *(uint32_t*)host_ent->h_addr_list[0]; + + if (NULL == (opts.pid_file = fopen(opts.pid_path, "w"))) + pt_log(kLog_error, "Failed to open pidfile: \"%s\", Cause: %s\n", opts.pid_path, strerror(errno)); + + if (has_logfile && opts.log_path) { + pt_log(kLog_info, "Open Logfile: \"%s\"\n", opts.log_path); + tmp_log = fopen(opts.log_path, "a"); + if (!tmp_log) { + pt_log(kLog_error, "Failed to open log file: \"%s\", Cause: %s\n", opts.log_path, strerror(errno)); + pt_log(kLog_error, "Reverting log to standard out.\n"); + } else opts.log_file = tmp_log; + } + + if (opts.base64 != 0) { + pt_log(kLog_debug, "Base64 enabled."); + } + + return 0; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..8678d60 --- /dev/null +++ b/src/options.h @@ -0,0 +1,85 @@ +#ifndef OPTIONS_H +#define OPTIONS_H 1 + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <pwd.h> +#include <grp.h> +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif + +#include "md5.h" +#include "pconfig.h" + +struct options { + /** user defined magic value (prevent Cisco WSA/IronPort fingerprint scan) */ + uint32_t magic; + /** proxy or forwarder? */ + int mode; + /** Proxy's internet address */ + char *given_proxy_hostname; + uint32_t given_proxy_ip; + /** Port the client listens on */ + uint32_t tcp_listen_port; + /** Forward/Proxy destination internet address */ + char *given_dst_hostname; + uint32_t given_dst_ip; + /** Forward/Proxy destination port */ + uint32_t given_dst_port; + /** Default maximum number of tunnels to support at once */ + uint32_t max_tunnels; + /** Default log level */ + int log_level; +#ifdef HAVE_PCAP + /** Non zero value if user wants packet capturing */ + int pcap; + /** Device to capture packets from */ + char *pcap_device; +#endif + /** Usually stdout, but can be altered by the user */ + char *log_path; + FILE *log_file; + /** Print more detailed traffic statistics if non zero value */ + int print_stats; + /** Password (must be the same on proxy and client for authentica tion to succeed) */ + char *password; + /** MD5 digest of challenge+password */ + md5_byte_t password_digest[kMD5_digest_size]; + /** use UDP instead of ICMP */ + int udp; + /** unpriviledged mode */ + int unprivileged; + /** use base64 encoded packets */ + int base64; + +#ifndef WIN32 + /** run as daemon if non zero value */ + int daemonize; + /** PIDFILE if running as daemon */ + char *pid_path; + FILE *pid_file; + /** log to syslog if non zero value */ + int use_syslog; + /** UID of the running process */ + uid_t uid; + /** GID of the running process */ + gid_t gid; + /** CHROOT dir */ + int chroot; + char *root_dir; +#endif + +#ifdef HAVE_SELINUX + char *selinux_context; +#endif +}; + +extern struct options opts; + +void print_usage(const char *arg0); + +int parse_options(int argc, char **argv); + +#endif diff --git a/src/pconfig.h b/src/pconfig.h new file mode 100644 index 0000000..4adabc9 --- /dev/null +++ b/src/pconfig.h @@ -0,0 +1,91 @@ +#ifndef PCONFIG_H +#define PCONFIG_H 1 + +enum { + /** Ping tunnel's operating mode (client) */ + kMode_forward = 0, + /** Ping tunnel's operating mode (server) */ + kMode_proxy, + /** Set this constant to the number of + * concurrent connections you wish to handle by default. + */ + kMax_tunnels = 10, + /** Different verbosity levels. */ + kNo_log = -1, + kLog_error = 0, + kLog_info, + kLog_event, + kLog_verbose, + kLog_debug, + kLog_sendrecv, + /** Major (0.xx) and minor (x.70) version */ + kMajor_version = 0, + /** numbers */ + kMinor_version = 72, + kIP_packet_max_size = 576, + /** In bytes, mind you */ + kIP_header_size = 20, + kIP_actual_size = (kIP_packet_max_size - kIP_header_size) - ((kIP_packet_max_size - kIP_header_size) % 8), + /** Also in bytes */ + kICMP_header_size = 8, + /** This constant control the maximum size of + * the payload-portion of the ICMP packets + * we send. Note that this does not include + * the IP or ICMP headers! + */ + kDefault_buf_size = 1024, + /** Type code for echo request and replies */ + kICMP_echo_request = 8, + kICMP_echo_reply = 0, + /** number of packets we can have in our send/receive ring */ + kPing_window_size = 64, + /** Tunnels are automatically closed after one minute of inactivity. Since + * we continously send acknowledgements between the two peers, this mechanism + * won't disconnect "valid" connections. + */ + kAutomatic_close_timeout = 60, // Seconds! + /** size of md5 digest in bytes */ + kMD5_digest_size = 16, + /** These constants are used to indicate the protocol state. The protocol + * works as follows: + * - The identifier is used by both the proxy and the forwarder + * to identify the session (and thus the relevant sockets). + * - The seq-no of the ping packet is used in a sliding-window-esque + * way, and to identify the order of data. + * + * The protocol can be in any of the following states: + * kProxy_start Causes the proxy to open a connection to the given + * host and port, associating the ID with the socket, + * before the data on the socket are transmitted. + * kProxy_data Indicates that the packet contains data from the proxy. + * Data ordering is indicated by the seq-no, which will start + * at 0. (The proxy and forwarder maintain different seq-nos.) + * kUser_data This packet contains user data. + * kConnection_close Indicates that the connection is being closed. + * kProxy_ack and Acknowledges the packet (and all packets before it) with seq_no = ack. + * kUser_ack This is used if there are no implicit acknowledgements due to data + * being sent. + * + * Acknowledgements work by the remote peer acknowledging the last + * continuous seq no it has received. + * + * Note: A proxy receiving a kProxy_data packet, or a user receiving a + * kUser_data packet, should ignore it, as it is the host operating system + * actually returning the ping. This is mostly relevant for users, and for + * proxies running in unprivileged mode. + */ + kProxy_start = 0, + kProto_ack, + kProto_data, + kProto_close, + kProto_authenticate, + kNum_proto_types, + /** set when packet comes from a user */ + kUser_flag = 1 << 30, + /** set when packet comes from the proxy */ + kProxy_flag = 1 << 31, + kFlag_mask = kUser_flag | kProxy_flag, + kDNS_port = 53 +}; + +#endif diff --git a/src/pdesc.c b/src/pdesc.c new file mode 100644 index 0000000..0bc5193 --- /dev/null +++ b/src/pdesc.c @@ -0,0 +1,228 @@ +#include <stdlib.h> + +#include "pdesc.h" +#include "options.h" +#include "utils.h" +#include "ptunnel.h" + + +/* create_and_insert_proxy_desc: Creates a new proxy descriptor, linking it into + * the descriptor chain. If the sock argument is 0, the function will establish + * a TCP connection to the ip and port given by dst_ip, dst_port. + */ +proxy_desc_t* create_and_insert_proxy_desc(uint16_t id_no, uint16_t icmp_id, + int sock, struct sockaddr_in *addr, + uint32_t dst_ip, uint32_t dst_port, + uint32_t init_state, uint32_t type) { + proxy_desc_t *cur; + + pthread_mutex_lock(&chain_lock); + if (num_tunnels >= opts.max_tunnels) { + pt_log(kLog_info, "Discarding incoming connection - too many tunnels! Maximum count is %u (adjust with the -m switch).\n", opts.max_tunnels); + if (sock) + close(sock); + pthread_mutex_unlock(&chain_lock); + return 0; + } + num_tunnels++; + pthread_mutex_unlock(&chain_lock); + + pt_log(kLog_debug, "Adding proxy desc to run loop. Type is %s. Will create socket: %s\n", (type == kUser_flag ? "user" : "proxy"), (sock ? "No" : "Yes")); + cur = (proxy_desc_t *) calloc(1, sizeof(proxy_desc_t)); + cur->id_no = id_no; + cur->dest_addr = *addr; + cur->dst_ip = dst_ip; + cur->dst_port = dst_port; + cur->icmp_id = icmp_id; + if (!sock) { + cur->sock = socket(AF_INET, SOCK_STREAM, 0); + memset(addr, 0, sizeof(struct sockaddr_in)); + addr->sin_port = htons((uint16_t)dst_port); + addr->sin_addr.s_addr = dst_ip; + addr->sin_family = AF_INET; + /* Let's just assume success, shall we? */ + if (connect(cur->sock, (struct sockaddr*)addr, sizeof(struct sockaddr_in)) < 0) { + pt_log(kLog_error, "Connect to %s:%d failed: %s\n", inet_ntoa(*(struct in_addr*)&addr->sin_addr.s_addr) , ntohs(addr->sin_port), strerror(errno)); + } + } + else + cur->sock = sock; + cur->state = init_state; + cur->type_flag = type; + if (cur->type_flag == kUser_flag) + cur->pkt_type = kICMP_echo_request; + else + cur->pkt_type = (opts.unprivileged ? kICMP_echo_request : kICMP_echo_reply); + cur->buf = (char *) malloc(icmp_receive_buf_len); + cur->last_activity = time_as_double(); + cur->authenticated = 0; + + pthread_mutex_lock(&chain_lock); + cur->next = chain; + chain = cur; + pthread_mutex_unlock(&chain_lock); + cur->xfer.bytes_in = 0.0; + cur->xfer.bytes_out = 0.0; + return cur; +} + +/* remove_proxy_desc: Removes the given proxy desc, freeing its resources. + * Assumes that we hold the chain_lock. + */ +void remove_proxy_desc(proxy_desc_t *cur, proxy_desc_t *prev) { + int i; + struct timeval tt; + + pt_log(kLog_debug, "Removing proxy descriptor.\n"); + /* Get a timestamp, for making an entry in the seq_expiry_tbl */ + gettimeofday(&tt, 0); + seq_expiry_tbl[cur->id_no] = tt.tv_sec+(2*kAutomatic_close_timeout); + + /* Free resources associated with connection */ + if (cur->buf) + free(cur->buf); + cur->buf = 0; + for (i=0;i<kPing_window_size;i++) { + if (cur->send_ring[i].pkt) + free(cur->send_ring[i].pkt); + cur->send_ring[i].pkt = 0; + if (cur->recv_ring[i]) + free(cur->recv_ring[i]); + cur->recv_ring[i] = 0; + } + close(cur->sock); + cur->sock = 0; + + /* Keep list up-to-date */ + if (prev) + prev->next = cur->next; + else + chain = cur->next; + if (cur->challenge) + free(cur->challenge); + free(cur); + num_tunnels--; +} + +forward_desc_t* create_fwd_desc(uint16_t seq_no, uint32_t data_len, char *data) { + forward_desc_t *fwd_desc; + fwd_desc = (forward_desc_t *) malloc(sizeof(forward_desc_t)+data_len); + fwd_desc->seq_no = seq_no; + fwd_desc->length = data_len; + fwd_desc->remaining = data_len; + if (data_len > 0) + memcpy(fwd_desc->data, data, data_len); + return fwd_desc; +} + +/* queue_packet: + * Creates an ICMP packet descriptor, and sends it. The packet descriptor is added + * to the given send ring, for potential resends later on. + */ +int queue_packet(int icmp_sock, uint8_t type, char *buf, int num_bytes, + uint16_t id_no, uint16_t icmp_id, uint16_t *seq, icmp_desc_t ring[], + int *insert_idx, int *await_send, uint32_t ip, uint32_t port, + uint32_t state, struct sockaddr_in *dest_addr, uint16_t next_expected_seq, + int *first_ack, uint16_t *ping_seq) +{ + int pkt_len = sizeof(icmp_echo_packet_t) + + sizeof(ping_tunnel_pkt_t) + num_bytes; + int err = 0; + icmp_echo_packet_t *pkt = 0; + ping_tunnel_pkt_t *pt_pkt = 0; + uint16_t ack_val = next_expected_seq - 1; + + if (pkt_len % 2) + pkt_len++; + + pkt = (icmp_echo_packet_t *) calloc(1, pkt_len); + /* ICMP Echo request or reply */ + pkt->type = type; + /* Must be zero (non-zero requires root) */ + pkt->code = 0; + pkt->identifier = htons(icmp_id); + pkt->seq = htons(*ping_seq); + pkt->checksum = 0; + (*ping_seq)++; + /* Add our information */ + pt_pkt = (ping_tunnel_pkt_t*)pkt->data; + pt_pkt->magic = htonl(opts.magic); + pt_pkt->dst_ip = ip; + pt_pkt->dst_port = htonl(port); + pt_pkt->ack = htonl(ack_val); + pt_pkt->data_len = htonl(num_bytes); + pt_pkt->state = htonl(state); + pt_pkt->seq_no = htons(*seq); + pt_pkt->id_no = htons(id_no); + /* Copy user data */ + if (buf && num_bytes > 0) + memcpy(pt_pkt->data, buf, num_bytes); + pkt->checksum = htons(calc_icmp_checksum((uint16_t*)pkt, pkt_len)); + + /* Send it! */ + pt_log(kLog_sendrecv, "Send: %d [%d] bytes [seq = %d] " + "[type = %s] [ack = %d] [icmp = %d] [user = %s]\n", + pkt_len, num_bytes, *seq, state_name[state & (~kFlag_mask)], + ack_val, type, ((state & kUser_flag) == kUser_flag ? "yes" : "no")); + err = sendto(icmp_sock, (const void*)pkt, pkt_len, 0, + (struct sockaddr*)dest_addr, sizeof(struct sockaddr)); + if (err < 0) { + pt_log(kLog_error, "Failed to send ICMP packet: %s\n", strerror(errno)); + return -1; + } + else if (err != pkt_len) + pt_log(kLog_error, "WARNING WARNING, didn't send entire packet\n"); + + /* Update sequence no's and so on */ + ring[*insert_idx].pkt = pkt; + ring[*insert_idx].pkt_len = pkt_len; + ring[*insert_idx].last_resend = time_as_double(); + ring[*insert_idx].seq_no = *seq; + ring[*insert_idx].icmp_id = icmp_id; + (*seq)++; + if (!ring[*first_ack].pkt) + *first_ack = *insert_idx; + (*await_send)++; + (*insert_idx)++; + if (*insert_idx >= kPing_window_size) + *insert_idx = 0; + return 0; +} + +/* send_packets: + * Examines the passed-in ring, and forwards data in it over TCP. + */ +uint32_t send_packets(forward_desc_t *ring[], int *xfer_idx, int *await_send, int *sock) { + forward_desc_t *fwd_desc; + int bytes, total = 0; + + while (*await_send > 0) { + fwd_desc = ring[*xfer_idx]; + if (!fwd_desc)/* We haven't got this packet yet.. */ + break; + if (fwd_desc->length > 0) { + bytes = send(*sock, &fwd_desc->data[fwd_desc->length - fwd_desc->remaining], + fwd_desc->remaining, 0); + if (bytes < 0) { + printf("Weirdness.\n"); + /* TODO: send close stuff */ + close(*sock); + *sock = 0; + break; + } + fwd_desc->remaining -= bytes; + total += bytes; + } + if (!fwd_desc->remaining) { + ring[*xfer_idx] = 0; + free(fwd_desc); + (*xfer_idx)++; + (*await_send)--; + if (*xfer_idx >= kPing_window_size) + *xfer_idx = 0; + } + else + break; + } + return total; +} diff --git a/src/pdesc.h b/src/pdesc.h new file mode 100644 index 0000000..18751b7 --- /dev/null +++ b/src/pdesc.h @@ -0,0 +1,136 @@ +#ifndef PDESC_H +#define PDESC_H 1 + +#include <stdint.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "pkt.h" +#include "challenge.h" +#include "pconfig.h" + +/** forward_desc_t: Describes a piece of that needs to be forwarded. This + * structure is used for receiving data from the network, and for subsequent + * forwarding over TCP: + * + * 1. Client sends data to proxy over ICMP + * 2. Proxy receives the data, and puts it into a forward_desc_t + * 3. The proxy starts send()-ing the data over the TCP socket to the destination, + * decreasing forward_desc_t->remaining with the number of bytes transferred. + * 4. Once remaining reaches 0, the forward_desc_t is removed from the receive + * ring. + * + * The same procedure is followed in proxy-to-client communication. Just replace + * proxy with client and vice versa in the list above. + */ +typedef struct forward_desc_t { + /** ping_tunnel_pkt_t seq_no */ + int seq_no; + /** length of data */ + int length; + /** amount of data not yet transferred */ + int remaining; + char data[0]; +} forward_desc_t; + +/** icmp_desc_t: This structure is used to track the ICMP packets sent by either + * the client or proxy. The last_resend variable is used to prevent resending + * the packet too often. Once the packet is acknowledged by the remote end, + * it will be removed from the send-ring, freeing up space for more outgoing + * ICMP packets. + */ +typedef struct icmp_desc_t { + /** total length of ICMP packet, including ICMP header and ptunnel data. */ + int pkt_len; + double last_resend; + int resend_count; + uint16_t seq_no; + uint16_t icmp_id; + icmp_echo_packet_t *pkt; +} icmp_desc_t; + +/** xfer_stats_t: Various transfer statistics, such as bytes sent and received, + * number of ping packets sent/received, etc. + */ +typedef struct xfer_stats_t { + double bytes_in; + double bytes_out; + uint32_t icmp_in; + uint32_t icmp_out; + uint32_t icmp_resent; + uint32_t icmp_ack_out; +} xfer_stats_t; + +/** proxy_desc_t: This massive structure describes a tunnel instance. + */ +typedef struct proxy_desc_t { + /** ICMP or UDP socket */ + int sock; + /** number of bytes in receive buffer */ + int bytes; + /** set to true once this instance should be removed */ + int should_remove; + /** data buffer, used to receive ping and pong packets */ + char *buf; + uint16_t id_no; + uint16_t my_seq; + uint16_t ping_seq; + uint16_t next_remote_seq; + uint16_t pkt_type; + uint16_t remote_ack_val; + uint16_t icmp_id; + /** first available slot in recv ring */ + int recv_idx; + /** current slot in recv ring being transferred */ + int recv_xfer_idx; + /** first available slot in send ring */ + int send_idx; + /** first packet in send ring not yet acked */ + int send_first_ack; + /** number of items in recv ring awaiting send */ + int recv_wait_send; + /** number of items in send ring awaiting ack */ + int send_wait_ack; + int next_resend_start; + int authenticated; + /** Contains the challenge, if used. */ + challenge_t *challenge; + /** Protocol state */ + uint32_t state; + /** Either kProxy_flag or kUser_flag */ + uint32_t type_flag; + /** IP and port to which data should be forwarded. */ + uint32_t dst_ip; + uint32_t dst_port; + /** Same as above */ + struct sockaddr_in dest_addr; + /** Time when last ack packet was sent. */ + double last_ack; + /** Time when a packet was last received. */ + double last_activity; + icmp_desc_t send_ring[kPing_window_size]; + forward_desc_t *recv_ring[kPing_window_size]; + xfer_stats_t xfer; + struct proxy_desc_t *next; +} proxy_desc_t; + + +proxy_desc_t* create_and_insert_proxy_desc(uint16_t id_no, uint16_t icmp_id, + int sock, struct sockaddr_in *addr, + uint32_t dst_ip, uint32_t dst_port, + uint32_t init_state, uint32_t type); + +void remove_proxy_desc(proxy_desc_t *cur, proxy_desc_t *prev); + +forward_desc_t* create_fwd_desc(uint16_t seq_no, uint32_t data_len, char *data); + +int queue_packet(int icmp_sock, uint8_t type, char *buf, int num_bytes, + uint16_t id_no, uint16_t icmp_id, uint16_t *seq, icmp_desc_t ring[], + int *insert_idx, int *await_send, uint32_t ip, uint32_t port, + uint32_t state, struct sockaddr_in *dest_addr, uint16_t next_expected_seq, + int *first_ack, uint16_t *ping_seq); + +uint32_t send_packets(forward_desc_t *ring[], int *xfer_idx, int *await_send, int *sock); + +#endif diff --git a/src/pkt.c b/src/pkt.c new file mode 100644 index 0000000..9bd8777 --- /dev/null +++ b/src/pkt.c @@ -0,0 +1,403 @@ +#ifndef WIN32 +#include <netinet/in.h> +#include <arpa/inet.h> +#include <pthread.h> +#endif +#include <sys/time.h> + +#include "ptunnel.h" +#include "pkt.h" +#include "pdesc.h" +#include "options.h" +#include "utils.h" + +/* handle_proxy_packet: + * Processes incoming ICMP packets for the proxy. The packet can come either from the + * packet capture lib, or from the actual socket or both. + * Input: A buffer pointing at the start of an IP header, the buffer length and the proxy + * descriptor chain. + */ +void handle_packet(char *buf, unsigned bytes, int is_pcap, struct sockaddr_in *addr, int icmp_sock) { + ip_packet_t *ip_pkt; + icmp_echo_packet_t *pkt; + ping_tunnel_pkt_t *pt_pkt; + proxy_desc_t *cur; + uint32_t type_flag, pkt_flag, init_state, proxy_flag; + challenge_t *challenge; + struct timeval tt; + + proxy_flag = kProxy_flag; + + if (bytes < sizeof(icmp_echo_packet_t)+sizeof(ping_tunnel_pkt_t)) + pt_log(kLog_verbose, "Skipping this packet - too short. " + "Expect: %d+%d = %d ; Got: %d\n", + sizeof(icmp_echo_packet_t), + sizeof(ping_tunnel_pkt_t), + sizeof(icmp_echo_packet_t) + + sizeof(ping_tunnel_pkt_t), bytes); + else { + if (opts.udp) { + ip_pkt = 0; + pkt = (icmp_echo_packet_t*)buf; + pt_pkt = (ping_tunnel_pkt_t*)pkt->data; + } + else { + ip_pkt = (ip_packet_t*)buf; + pkt = (icmp_echo_packet_t*)ip_pkt->data; + pt_pkt = (ping_tunnel_pkt_t*)pkt->data; + } + + if (ntohl(pt_pkt->magic) == opts.magic) { + pt_pkt->state = ntohl(pt_pkt->state); + pkt->identifier = ntohs(pkt->identifier); + pt_pkt->id_no = ntohs(pt_pkt->id_no); + pt_pkt->seq_no = ntohs(pt_pkt->seq_no); + /* Find the relevant connection, if it exists */ + pthread_mutex_lock(&chain_lock); + for (cur=chain;cur;cur=cur->next) { + if (cur->id_no == pt_pkt->id_no) + break; + } + pthread_mutex_unlock(&chain_lock); + + /* Handle the packet if it comes from "the other end." This is a bit tricky + * to get right, since we receive both our own and the other end's packets. + * Basically, a proxy will accept any packet from a user, regardless if it + * has a valid connection or not. A user will only accept the packet if there + * exists a connection to handle it. + */ + if (cur) { + type_flag = cur->type_flag; + if (type_flag == (uint32_t)kProxy_flag) + cur->icmp_id = pkt->identifier; + if (!is_pcap) + cur->xfer.icmp_in++; + } + else + type_flag = kProxy_flag; + + pkt_flag = pt_pkt->state & kFlag_mask; + pt_pkt->state &= ~kFlag_mask; + pt_log(kLog_sendrecv, "Recv: %d [%d] bytes " + "[seq = %d] [type = %s] " + "[ack = %d] [icmp = %d] " + "[user = %s] [pcap = %d]\n", + bytes, ntohl(pt_pkt->data_len), + pt_pkt->seq_no, state_name[pt_pkt->state & (~kFlag_mask)], + ntohl(pt_pkt->ack), pkt->type, + (pkt_flag == kUser_flag ? "yes" : "no"), is_pcap); + + /* This test essentially verifies that the packet comes from someone who isn't us. */ + if ((pkt_flag == kUser_flag && type_flag == proxy_flag) || + (pkt_flag == proxy_flag && type_flag == kUser_flag)) + { + pt_pkt->data_len = ntohl(pt_pkt->data_len); + pt_pkt->ack = ntohl(pt_pkt->ack); + if (pt_pkt->state == kProxy_start) { + if (!cur && type_flag == proxy_flag) { + pt_log(kLog_info, "Incoming tunnel request from %s.\n", + inet_ntoa(*(struct in_addr *)&addr->sin_addr)); + gettimeofday(&tt, 0); + if (tt.tv_sec < seq_expiry_tbl[pt_pkt->id_no]) { + pt_log(kLog_verbose, "Dropping request: ID was recently in use.\n"); + return; + } + pt_log(kLog_info, "Starting new session to %s:%d with ID %d\n", + inet_ntoa(*(struct in_addr *)&pt_pkt->dst_ip), + ntohl(pt_pkt->dst_port), pt_pkt->id_no); + if ((opts.given_dst_ip && opts.given_dst_ip != pt_pkt->dst_ip) || + ((uint32_t)-1 != opts.given_dst_port && opts.given_dst_port != ntohl(pt_pkt->dst_port))) + { + pt_log(kLog_info, "Destination administratively prohibited!\n"); + return; + } + + if (opts.password) + init_state = kProto_authenticate; + else + init_state = kProto_data; + + cur = create_and_insert_proxy_desc(pt_pkt->id_no, pkt->identifier, 0, + addr, pt_pkt->dst_ip, + ntohl(pt_pkt->dst_port), + init_state, kProxy_flag); + if (init_state == kProto_authenticate) { + pt_log(kLog_debug, "Sending authentication challenge..\n"); + /* Send challenge */ + cur->challenge = generate_challenge(); + memcpy(cur->buf, cur->challenge, sizeof(challenge_t)); + queue_packet(icmp_sock, cur->pkt_type, cur->buf, + sizeof(challenge_t), cur->id_no, + cur->icmp_id, &cur->my_seq, cur->send_ring, + &cur->send_idx, &cur->send_wait_ack, 0, 0, + kProto_authenticate | cur->type_flag, + &cur->dest_addr, cur->next_remote_seq, + &cur->send_first_ack, &cur->ping_seq); + } + } + else if (type_flag == kUser_flag) { + pt_log(kLog_error, "Dropping proxy session request - we are not a proxy!\n"); + return; + } + else + pt_log(kLog_error, "Dropping duplicate proxy session request.\n"); + } + else if (cur && pt_pkt->state == kProto_authenticate) { + /* Sanity check packet length, and make sure it matches what we expect */ + if (pt_pkt->data_len != sizeof(challenge_t)) { + pt_log(kLog_error, "Received challenge packet, but data length " + "is not as expected.\n"); + pt_log(kLog_debug, "Data length: %d Expected: %d\n", + pt_pkt->data_len, sizeof (challenge_t)); + cur->should_remove = 1; + return; + } + /* Prevent packet data from being forwarded over TCP! */ + pt_pkt->data_len = 0; + challenge = (challenge_t*)pt_pkt->data; + /* If client: Compute response to challenge */ + if (type_flag == kUser_flag) { + if (!opts.password) { + pt_log(kLog_error, "This proxy requires a password! " + "Please supply one usin g the -x switch.\n"); + send_termination_msg(cur, icmp_sock); + cur->should_remove = 1; + return; + } + pt_log(kLog_debug, "Got authentication challenge - sending response\n"); + generate_response(challenge); + queue_packet(icmp_sock, cur->pkt_type, (char*)challenge, + sizeof(challenge_t), cur->id_no, cur->icmp_id, + &cur->my_seq, cur->send_ring, &cur->send_idx, + &cur->send_wait_ack, 0, 0, + kProto_authenticate | cur->type_flag, &cur->dest_addr, + cur->next_remote_seq, &cur->send_first_ack, &cur-> ping_seq); + /* We have authenticated locally. + * It's up to the proxy now if it accepts our response or not.. + */ + cur->authenticated = 1; + handle_data(pkt, bytes, cur->recv_ring, &cur->recv_wait_send, + &cur->recv_idx, &cur->next_remote_seq); + return; + } + /* If proxy: Handle client's response to challenge */ + else if (type_flag == proxy_flag) { + pt_log(kLog_debug, "Received remote challenge response.\n"); + if (validate_challenge(cur->challenge, challenge) || + cur->authenticated) + { + pt_log(kLog_verbose, "Remote end authenticated successfully.\n"); + /* Authentication has succeeded, so now we can proceed + * to handle incoming TCP data. + */ + cur->authenticated = 1; + cur->state = kProto_data; + /* Insert the packet into the receive ring, to avoid + * confusing the reliab ility mechanism. + */ + handle_data(pkt, bytes, cur->recv_ring, &cur->recv_wait_send, + &cur->recv_idx, &cur->next_remote_seq); + } + else { + pt_log(kLog_info, "Remote end failed authentication.\n"); + send_termination_msg(cur, icmp_sock); + cur->should_remove = 1; + } + return; + } + } + /* Handle close-messages for connections we know about */ + if (cur && pt_pkt->state == kProto_close) { + pt_log(kLog_info, "Received session close from remote peer.\n"); + cur->should_remove = 1; + return; + } + /* The proxy will ignore any other packets from the client + * until it has been authenticated. The packet resend mechanism + * insures that this isn't problematic. + */ + if (type_flag == proxy_flag && opts.password && + cur && !cur->authenticated) + { + pt_log(kLog_debug, "Ignoring packet with seq-no %d " + "- not authenticated yet.\n", pt_pkt->seq_no); + return; + } + + if (cur && cur->sock) { + if (pt_pkt->state == kProto_data || pt_pkt->state == kProxy_start || + pt_pkt->state == kProto_ack) + { + handle_data(pkt, bytes, cur->recv_ring, &cur->recv_wait_send, + &cur->recv_idx, &cur->next_remote_seq); + } + handle_ack((uint16_t)pt_pkt->ack, cur->send_ring, &cur->send_wait_ack, + 0, cur->send_idx, &cur->send_first_ack, &cur->remote_ack_val, + is_pcap); + cur->last_activity = time_as_double(); + } + } + } + else + pt_log(kLog_verbose, "Ignored incoming packet.\n"); + } +} + +/* handle_data: + * Utility function for handling kProto_data packets, and place the data it contains + * onto the passed-in receive ring. + */ +void handle_data(icmp_echo_packet_t *pkt, int total_len, forward_desc_t *ring[], + int *await_send, int *insert_idx, uint16_t *next_expected_seq) +{ + ping_tunnel_pkt_t *pt_pkt = (ping_tunnel_pkt_t*)pkt->data; + int expected_len = sizeof(ip_packet_t) + sizeof(icmp_echo_packet_t) + + sizeof(ping_tunnel_pkt_t); /* 20+8+28 */ + /* Place packet in the receive ring, in its proper place. + * This works as follows: + * -1. Packet == ack packet? Perform ack, and continue. + * 0. seq_no < next_remote_seq, and absolute difference is bigger than w size => discard + * 1. If seq_no == next_remote_seq, we have no problems; just put it in the ring. + * 2. If seq_no > next_remote_seq + remaining window size, discard packet. + * Send resend request for missing packets. + * 3. Else, put packet in the proper place in the ring + * (don't overwrite if one is already there), but don't increment next_remote_seq_no + * 4. If packed was not discarded, process ack info in packet. + */ + expected_len += pt_pkt->data_len; + expected_len += expected_len % 2; + if (opts.udp) + expected_len -= sizeof(ip_packet_t); + if (total_len < expected_len) { + pt_log(kLog_error, "Packet not completely received: %d Should be: %d. " + "For some reason, this error is fatal.\n", total_len, expected_len); + pt_log(kLog_debug, "Data length: %d Total length: %d\n", pt_pkt->data_len, total_len); + /* TODO: This error isn't fatal, so it should definitely be handled in some way. + * We could simply discard it. + */ + exit(0); + } + if (pt_pkt->seq_no == *next_expected_seq) { + /* hmm, what happens if this test is true? */ + if (!ring[*insert_idx]) { /* && pt_pkt->state == kProto_data */ + /* pt_log(kLog_debug, "Queing data packet: %d\n", pt_pkt->seq_no); */ + ring[*insert_idx] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); + (*await_send)++; + (*insert_idx)++; + } + else if (ring[*insert_idx]) + pt_log(kLog_debug, "Dup packet?\n"); + + (*next_expected_seq)++; + if (*insert_idx >= kPing_window_size) + *insert_idx = 0; + /* Check if we have already received some of the next packets */ + while (ring[*insert_idx]) { + if (ring[*insert_idx]->seq_no == *next_expected_seq) { + (*next_expected_seq)++; + (*insert_idx)++; + if (*insert_idx >= kPing_window_size) + *insert_idx = 0; + } + else + break; + } + } + else { + int r, s, d, pos; + pos = -1; /* If pos ends up staying -1, packet is discarded. */ + r = *next_expected_seq; + s = pt_pkt->seq_no; + d = s - r; + if (d < 0) { /* This packet _may_ be old, or seq_no may have wrapped around */ + d = (s+0xFFFF) - r; + if (d < kPing_window_size) { + /* Counter has wrapped, so we should add this packet to the recv ring */ + pos = ((*insert_idx)+d) % kPing_window_size; + } + } + else if (d < kPing_window_size) + pos = ((*insert_idx)+d) % kPing_window_size; + + if (pos != -1) { + if (!ring[pos]) { + pt_log(kLog_verbose, "Out of order. Expected: %d Got: %d Inserted: %d " + "(cur = %d)\n", *next_expected_seq, pt_pkt->seq_no, pos, + (*insert_idx)); + ring[pos] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); + (*await_send)++; + } + } + /* else + * pt_log(kLog_debug, "Packet discarded - outside receive window.\n"); + */ + } +} + +void handle_ack(uint16_t seq_no, icmp_desc_t ring[], int *packets_awaiting_ack, + int one_ack_only, int insert_idx, int *first_ack, + uint16_t *remote_ack, int is_pcap) +{ + int i, j, k; + ping_tunnel_pkt_t *pt_pkt; + + if (*packets_awaiting_ack > 0) { + if (one_ack_only) { + for (i = 0; i < kPing_window_size; i++) { + if (ring[i].pkt && ring[i].seq_no == seq_no && !is_pcap) { + pt_log(kLog_debug, "Received ack for only seq %d\n", seq_no); + pt_pkt = (ping_tunnel_pkt_t*)ring[i].pkt->data; + /* WARNING: We make the dangerous assumption here that packets arrive in order! */ + *remote_ack = (uint16_t)ntohl(pt_pkt->ack); + free(ring[i].pkt); + ring[i].pkt = 0; + (*packets_awaiting_ack)--; + if (i == *first_ack) { + for (j=1;j<kPing_window_size;j++) { + k = (i+j)%kPing_window_size; + if (ring[k].pkt) { + *first_ack = k; + break; + } + /* we have looped through everything */ + if (k == i) + *first_ack = insert_idx; + j++; + } + } + return; + } + } + } + else { + int i, can_ack = 0, count = 0; + i = insert_idx-1; + if (i < 0) + i = kPing_window_size - 1; + + pt_log(kLog_debug, "Received ack-series starting at seq %d\n", seq_no); + while (count < kPing_window_size) { + if (!ring[i].pkt) + break; + + if (ring[i].seq_no == seq_no) + can_ack = 1; + else if (!can_ack) + *first_ack = i; + + if (can_ack) { + free(ring[i].pkt); + ring[i].pkt = 0; + (*packets_awaiting_ack)--; + } + i--; + if (i < 0) + i = kPing_window_size - 1; + count++; + } + } + } +/* else + * pt_log(kLog_verbose, "Dropping superfluous acknowledgement (no outstanding packets needing ack.)\n"); + */ +} diff --git a/src/pkt.h b/src/pkt.h new file mode 100644 index 0000000..22c039d --- /dev/null +++ b/src/pkt.h @@ -0,0 +1,96 @@ +#ifndef PKT_H +#define PKT_H 1 + +#include <stdint.h> + +#ifdef WIN32 +#include <winsock2.h> +typedef int socklen_t; +typedef uint32_t in_addr_t; +#define ETH_ALEN 6 /* Octets in one ethernet addr */ +struct ether_header { + u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */ + u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */ + u_int16_t ether_type; /* packet type ID field */ +}; +#endif /* WIN32 */ + +/** Resend packets after this interval (in seconds) */ +#define kResend_interval 1.5 + +/** ping_tunnel_pkt_t: This data structure represents the header of a ptunnel + * packet, consisting of a magic number, the tunnel's destination IP and port, + * as well as some other fields. Note that the dest IP and port is only valid + * in packets from the client to the proxy. + */ +typedef struct { + /** magic number, used to identify ptunnel packets. */ + uint32_t magic; + /** destination IP and port (used by proxy to figure */ + uint32_t dst_ip; + /** out where to tunnel to) */ + uint32_t dst_port; + /** current connection state; see constants above. */ + uint32_t state; + /** sequence number of last packet received from other end */ + uint32_t ack; + /** length of data buffer */ + uint32_t data_len; + /** sequence number of this packet */ + uint16_t seq_no; + /** id number, used to separate different tunnels from each other */ + uint16_t id_no; + /** optional data buffer */ + char data[0]; +} __attribute__ ((packed)) ping_tunnel_pkt_t; + +/** ip_packet_t: This is basically my own definition of the IP packet, which + * of course complies with the official definition ;) See any good book on IP + * (or even the RFC) for info on the contents of this packet. + */ +typedef struct { + uint8_t vers_ihl; + uint8_t tos; + uint16_t pkt_len; + uint16_t id; + uint16_t flags_frag_offset; + uint8_t ttl; + uint8_t proto; // 1 for ICMP + uint16_t checksum; + uint32_t src_ip; + uint32_t dst_ip; + char data[0]; +} __attribute__ ((packed)) ip_packet_t; + +/** icmp_echo_packet_t: This is the definition of a standard ICMP header. The + * ptunnel packets are constructed as follows: + * [ ip header (20 bytes) ] + * [ icmp header (8 bytes) ] + * [ ptunnel header (28 bytes) ] + * + * We actually only create the ICMP and ptunnel headers, the IP header is + * taken care of by the OS. + */ +typedef struct { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t identifier; + uint16_t seq; + char data[0]; +} __attribute__ ((packed)) icmp_echo_packet_t; + +typedef struct forward_desc_t forward_desc_t; +typedef struct icmp_desc_t icmp_desc_t; + + +void handle_packet(char *buf, unsigned bytes, int is_pcap, struct sockaddr_in *addr, int icmp_sock); + +void handle_data(icmp_echo_packet_t *pkt, int total_len, forward_desc_t **ring, + int *await_send, int *insert_idx, uint16_t *next_expected_seq); + +void handle_ack(uint16_t seq_no, icmp_desc_t *ring, int *packets_awaiting_ack, + int one_ack_only, int insert_idx, int *first_ack, + uint16_t *remote_ack, int is_pcap); + +#endif diff --git a/src/ptunnel.c b/src/ptunnel.c new file mode 100644 index 0000000..0af75f8 --- /dev/null +++ b/src/ptunnel.c @@ -0,0 +1,781 @@ +/* + * ptunnel.c + * ptunnel is licensed under the BSD license: + * + * Copyright (c) 2004-2011, Daniel Stoedle <daniels@cs.uit.no>, + * Yellow Lemon Software. All rights reserved. + * + * Copyright (c) 2017 Toni Uhlig <matzeton@googlemail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Yellow Lemon Software nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contacting the author: + * You can get in touch with me, Daniel Stødle (that's the Norwegian letter oe, + * in case your text editor didn't realize), here: <daniels@cs.uit.no> + * + * The official ptunnel website is here: + * <http://www.cs.uit.no/~daniels/PingTunnel/> + * + * Note that the source code is best viewed with tabs set to 4 spaces. + */ + +#include "ptunnel.h" +#include "options.h" +#include "utils.h" +#include "md5.h" +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif + +#ifdef WIN32 +/** pthread porting to windows */ +typedef CRITICAL_SECTION pthread_mutex_t; +typedef unsigned long pthread_t; +#define pthread_mutex_init InitializeCriticalSectionAndSpinCount +#define pthread_mutex_lock EnterCriticalSection +#define pthread_mutex_unlock LeaveCriticalSection + +#include <winsock2.h> +/* Map errno (which Winsock doesn't use) to GetLastError; include the code in the strerror */ +#ifdef errno +#undef errno +#endif /* errno */ +#define errno GetLastError() +/** Local error string storage */ +static char errorstr[255]; +static char * print_last_windows_error() { + DWORD last_error = GetLastError(); + memset(errorstr, 0, sizeof(errorstr)); + FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, last_error, 0, errorstr, sizeof(errorstr), NULL); + snprintf(errorstr, sizeof(errorstr), "%s (%d)", errorstr, last_error); + return errorstr; +} +#define strerror(x) print_last_windows_error() +#endif /* WIN32 */ + +/* globals */ +/** Lock protecting the chain of connections */ +pthread_mutex_t chain_lock; +/** Lock protecting the num_threads variable */ +pthread_mutex_t num_threads_lock; +/** Current thread count */ +int num_threads = 0; +/** Current tunnel count */ +uint32_t num_tunnels = 0; +/** Table indicating when a connection ID is allowable (used by proxy) */ +uint32_t *seq_expiry_tbl = NULL; + +/* Some buffer constants */ +const int tcp_receive_buf_len = kDefault_buf_size; +const int icmp_receive_buf_len = kDefault_buf_size + kIP_header_size + + kICMP_header_size + sizeof(ping_tunnel_pkt_t); +const int pcap_buf_size = (kDefault_buf_size + kIP_header_size + + kICMP_header_size + sizeof(ping_tunnel_pkt_t)+64)*64; +/** (icmp[icmptype] = icmp-echo || icmp[icmptype] = icmp-echoreply) */ +char pcap_filter_program[] = "icmp"; + +/** The chain of client/proxy connections */ +proxy_desc_t *chain = 0; +const char *state_name[kNum_proto_types] = { "start", "ack", "data", + "close", "authenticate" }; + +/* Let the fun begin! */ +int main(int argc, char *argv[]) { +#ifndef WIN32 + pid_t pid; +#endif +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + return -1; + } + + if (LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 2) + { + WSACleanup(); + return -1; + } +#endif /* WIN32 */ + + /* Seed random generator; it'll be used in combination with a timestamp + * when generating authentication challenges. + */ + srand(time(0)); + memset(opts.password_digest, 0, kMD5_digest_size); + + /* The seq_expiry_tbl is used to prevent the remote ends from prematurely + * re-using a sequence number. + */ + seq_expiry_tbl = (uint32_t *) calloc(65536, sizeof(uint32_t)); + + /* Parse options */ + if (parse_options(argc, argv)) + return -1; + +#ifdef HAVE_PCAP + if (opts.pcap && opts.udp) { + pt_log(kLog_error, "Packet capture is not supported (or needed) when using UDP for transport.\n"); + opts.pcap = 0; + } +#endif + pt_log(kLog_info, "Starting ptunnel v %d.%.2d.\n", kMajor_version, kMinor_version); + pt_log(kLog_info, "(c) 2004-2011 Daniel Stoedle, <daniels@cs.uit.no>\n"); +#ifdef WIN32 + pt_log(kLog_info, "Windows version by Mike Miller, <mike@mikeage.net>\n"); +#else + pt_log(kLog_info, "Security features by Sebastien Raveau, <sebastien.raveau@epita.fr>\n"); +#endif + pt_log(kLog_info, "%s.\n", (opts.mode == kMode_forward ? "Relaying packets from incoming TCP streams" : + "Forwarding incoming ping packets over TCP")); + if (opts.udp) + pt_log(kLog_info, "UDP transport enabled.\n"); + + pt_log(kLog_debug, "Destination at %s:%u\n", opts.given_dst_hostname, opts.given_dst_port); + + if (opts.mode == kMode_forward) + pt_log(kLog_debug, "Listen for incoming connections at 0.0.0.0:%u\n", opts.tcp_listen_port); + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); + if (opts.use_syslog) { + if (opts.log_file != stdout) { + pt_log(kLog_error, "Logging using syslog overrides the use of a specified logfile (using -f).\n"); + fclose(opts.log_file); + opts.log_file = stdout; + } + openlog("ptunnel", LOG_PID, LOG_USER); + } + if (opts.chroot) { + pt_log(kLog_info, "Restricting file access to %s\n", opts.root_dir); + if (-1 == chdir(opts.root_dir) || -1 == chroot(opts.root_dir)) { + pt_log(kLog_error, "%s: %s\n", opts.root_dir, strerror(errno)); + exit(1); + } + } + if (opts.daemonize) { + pt_log(kLog_info, "Going to the background.\n"); + if (0 < (pid = fork())) + exit(0); + if (0 > pid) + pt_log(kLog_error, "fork: %s\n", strerror(errno)); + else + if (-1 == setsid()) + pt_log(kLog_error, "setsid: %s\n", strerror(errno)); + else { + if (0 < (pid = fork())) + exit(0); + if (0 > pid) + pt_log(kLog_error, "fork: %s\n", strerror(errno)); + else { + if (NULL != opts.pid_file) { + fprintf(opts.pid_file, "%d\n", getpid()); + fclose(opts.pid_file); + } + if (! freopen("/dev/null", "r", stdin) || + ! freopen("/dev/null", "w", stdout) || + ! freopen("/dev/null", "w", stderr)) + pt_log(kLog_error, "freopen: %s\n", strerror(errno)); + } + } + } +#endif /* !WIN32 */ + +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + return -1; + } + + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 2 ) { + WSACleanup(); + return -1; + } +#endif /* WIN32 */ + pthread_mutex_init(&chain_lock, 0); + pthread_mutex_init(&num_threads_lock, 0); + + // Check mode, validate arguments and start either client or proxy. + if (opts.mode == kMode_forward) { + if (!opts.given_proxy_ip || !opts.given_dst_ip || !opts.given_dst_port || !opts.tcp_listen_port) { + printf("One of the options are missing or invalid.\n"); + print_usage(argv[0]); + return -1; + } + pt_forwarder(); + } + else + pt_proxy(0); + +#ifdef WIN32 + WSACleanup(); +#else + if (opts.root_dir) + free(opts.root_dir); +#ifdef HAVE_SELINUX + if (NULL != opts.selinux_context) + free(opts.selinux_context); +#endif +#endif /* WIN32 */ + + pt_log(kLog_info, "ptunnel is exiting.\n"); + return 0; +} + +/** pt_forwarder: + * Sets up a listening TCP socket, and forwards incoming connections + * over ping packets. + */ +void pt_forwarder(void) { + int server_sock, new_sock, sock, yes = 1; + fd_set set; + struct timeval time; + struct sockaddr_in addr, dest_addr; + socklen_t addr_len; + pthread_t pid; + uint16_t rand_id; + + pt_log(kLog_debug, "Starting forwarder..\n"); + /** Open our listening socket */ + sock = socket(AF_INET, SOCK_STREAM, 0); + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &yes, sizeof(int)) == -1) { + pt_log(kLog_error, "Failed to set SO_REUSEADDR option on listening socket: %s\n", strerror(errno)); + close(sock); + return; + } + addr.sin_family = AF_INET; + addr.sin_port = htons(opts.tcp_listen_port); + addr.sin_addr.s_addr = INADDR_ANY; + memset(&(addr.sin_zero), 0, 8); + if (bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) == -1) { + pt_log(kLog_error, "Failed to bind listening socket to port %u: %s\n", opts.tcp_listen_port, strerror(errno)); + close(sock); + return; + } + server_sock = sock; + /* Fill out address structure */ + memset(&dest_addr, 0, sizeof(struct sockaddr_in)); + dest_addr.sin_family = AF_INET; + if (opts.udp) + dest_addr.sin_port = htons(kDNS_port /* dns port.. */); + else + dest_addr.sin_port = 0; + dest_addr.sin_addr.s_addr = opts.given_proxy_ip; + pt_log(kLog_verbose, "Proxy IP address: %s\n", inet_ntoa(*((struct in_addr*)&opts.given_proxy_ip))); + + listen(server_sock, 10); + while (1) { + FD_ZERO(&set); + FD_SET(server_sock, &set); + time.tv_sec = 1; + time.tv_usec = 0; + if (select(server_sock+1, &set, 0, 0, &time) > 0) { + pt_log(kLog_info, "Incoming connection.\n"); + addr_len = sizeof(struct sockaddr_in); + new_sock = accept(server_sock, (struct sockaddr*)&addr, &addr_len); + if (new_sock < 0) { + pt_log(kLog_error, "Accepting incoming connection failed.\n"); + continue; + } + pthread_mutex_lock(&num_threads_lock); + if (num_threads <= 0) { + pt_log(kLog_event, "No running proxy thread - starting it.\n"); +#ifndef WIN32 + if (pthread_create(&pid, 0, pt_proxy, 0) != 0) +#else + if (0 == (pid = _beginthreadex(0, 0, (unsigned int (__stdcall *)(void *))pt_proxy, 0, 0, 0))) +#endif + { + pt_log(kLog_error, "Couldn't create thread! Dropping incoming connection.\n"); + close(new_sock); + pthread_mutex_unlock(&num_threads_lock); + continue; + } + } + addr = dest_addr; + rand_id = (uint16_t)rand(); + create_and_insert_proxy_desc(rand_id, rand_id, new_sock, &addr, opts.given_dst_ip, opts.given_dst_port, kProxy_start, kUser_flag); + pthread_mutex_unlock(&num_threads_lock); + } + } +} + + +int pt_create_udp_socket(int port) { + struct sockaddr_in addr; + int sock, yes = 1; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + pt_log(kLog_error, "Failed to set create UDP socket..\n"); + return 0; + } + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void*)&yes, sizeof(int)) < 0) { + pt_log(kLog_error, "Failed to set UDP REUSEADDR socket option. (Not fatal, hopefully.)\n"); + close(sock); + return 0; + } +#ifdef SO_REUSEPORT + yes = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const void*)&yes, sizeof(int)) < 0) + pt_log(kLog_error, "Failed to set UDP REUSEPORT socket option. (Not fatal, hopefully.)\n"); +#endif /* SO_REUSEPORT */ + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + if (bind(sock, (struct sockaddr*) &addr, sizeof(struct sockaddr_in)) < 0) { + pt_log(kLog_error, "Failed to bind UDP socket to port %d (try running as root).\n", port); + close(sock); + return 0; + } + return sock; +} + +/* pt_proxy: This function does all the client and proxy stuff. + */ +void* pt_proxy(void *args) { + fd_set set; + struct timeval timeout; + int bytes; + struct sockaddr_in addr; + socklen_t addr_len; + int fwd_sock = 0, + max_sock = 0, + idx; + char *buf; + double now, last_status_update = 0.0; + proxy_desc_t *cur, *prev, *tmp; +#ifdef HAVE_PCAP + pcap_info_t pc; +#endif + xfer_stats_t xfer; +#ifdef HAVE_PCAP + ip_packet_t *pkt; + uint32_t ip; + in_addr_t *adr; +#endif + + /* Start the thread, initialize protocol and ring states. */ + pt_log(kLog_debug, "Starting ping proxy..\n"); + if (opts.udp) { + pt_log(kLog_debug, "Creating UDP socket..\n"); + if (opts.mode == kMode_proxy) + fwd_sock = pt_create_udp_socket(kDNS_port); + else + fwd_sock = pt_create_udp_socket(0); + if (!fwd_sock) { + pt_log(kLog_error, "Failed to create UDP socket.\n"); + return 0; + } + } + else { + if (opts.unprivileged) { + pt_log(kLog_debug, "Attempting to create unprivileged ICMP datagram socket..\n"); + fwd_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + } + else { + pt_log(kLog_debug, "Attempting to create privileged ICMP raw socket..\n"); + fwd_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + } + if (fwd_sock < 0) { + pt_log(kLog_error, "Couldn't create %s socket: %s\n", + (opts.unprivileged ? "unprivileged datagram" : + "privileged raw"), strerror(errno)); + return 0; + } + } + max_sock = fwd_sock+1; +#ifdef HAVE_PCAP + if (opts.pcap) { + if (opts.udp) { + pt_log(kLog_error, "Packet capture is not useful with UDP [should not get here!]!\n"); + close(fwd_sock); + return 0; + } + if (!opts.unprivileged) { + memset(&pc, 0, sizeof(pc)); + pt_log(kLog_info, "Initializing pcap.\n"); + pc.pcap_err_buf = (char *) malloc(PCAP_ERRBUF_SIZE); + pc.pcap_data_buf = (char *) malloc(pcap_buf_size); + pc.pcap_desc = pcap_open_live(opts.pcap_device, + pcap_buf_size, 0 /* promiscous */, + 50 /* ms */, pc.pcap_err_buf); + if (pc.pcap_desc) { + if (pcap_lookupnet(opts.pcap_device, &pc.netp, + &pc.netmask, pc.pcap_err_buf) == -1) + { + pt_log(kLog_error, "pcap error: %s\n", pc.pcap_err_buf); + opts.pcap = 0; + } + pt_log(kLog_verbose, "Network: %s\n", inet_ntoa(*(struct in_addr*)&pc.netp)); + pt_log(kLog_verbose, "Netmask: %s\n", inet_ntoa(*(struct in_addr*)&pc.netmask)); + if (pcap_compile(pc.pcap_desc, &pc.fp, pcap_filter_program, 0, pc.netp) == -1) { + pt_log(kLog_error, "Failed to compile pcap filter program.\n"); + pcap_close(pc.pcap_desc); + opts.pcap = 0; + } + else if (pcap_setfilter(pc.pcap_desc, &pc.fp) == -1) { + pt_log(kLog_error, "Failed to set pcap filter program.\n"); + pcap_close(pc.pcap_desc); + opts.pcap = 0; + } + } + else { + pt_log(kLog_error, "pcap error: %s\n", pc.pcap_err_buf); + opts.pcap = 0; + } + pc.pkt_q.head = 0; + pc.pkt_q.tail = 0; + pc.pkt_q.elems = 0; + /* Check if we have succeeded, and free stuff if not */ + if (!opts.pcap) { + pt_log(kLog_error, "There were errors enabling pcap - pcap has been disabled.\n"); + free(pc.pcap_err_buf); + free(pc.pcap_data_buf); + return 0; + } + } + else + pt_log(kLog_info, "pcap disabled since we're running in unprivileged mode.\n"); + } +#endif + + pthread_mutex_lock(&num_threads_lock); + num_threads++; + pthread_mutex_unlock(&num_threads_lock); + + /* Allocate icmp receive buffer */ + buf = (char *) malloc(icmp_receive_buf_len); + + /* Start forwarding :) */ + pt_log(kLog_info, "Ping proxy is listening in %s mode.\n", + (opts.unprivileged ? "unprivileged" : "privileged")); + +#ifndef WIN32 +#ifdef HAVE_SELINUX + if (opts.uid || opts.gid || opts.selinux_context) +#else + if (opts.uid || opts.gid) +#endif + pt_log(kLog_info, "Dropping privileges now.\n"); + if (opts.gid && -1 == setgid(opts.gid)) + pt_log(kLog_error, "setgid(%d): %s\n", opts.gid, strerror(errno)); + if (opts.uid && -1 == setuid(opts.uid)) + pt_log(kLog_error, "setuid(%d): %s\n", opts.uid, strerror(errno)); +#ifdef HAVE_SELINUX + if (NULL != opts.selinux_context && -1 == setcon(opts.selinux_context)) + pt_log(kLog_error, "setcon(%s) failed: %s\n", opts.selinux_context, strerror(errno)); +#endif +#endif + + while (1) { + FD_ZERO(&set); + FD_SET(fwd_sock, &set); + max_sock = fwd_sock+1; + pthread_mutex_lock(&chain_lock); + for (cur = chain; cur; cur = cur->next) { + if (cur->sock) { + FD_SET(cur->sock, &set); + if (cur->sock >= max_sock) + max_sock = cur->sock+1; + } + } + pthread_mutex_unlock(&chain_lock); + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + /* Don't care about return val, since we need to check for new states anyway.. */ + select(max_sock, &set, 0, 0, &timeout); + + pthread_mutex_lock(&chain_lock); + for (prev = 0, cur = chain; cur && cur->sock; cur = tmp) { + /* Client: If we're starting up, send a message to the remote end saying so, + * causing him to connect to our desired endpoint. + */ + if (cur->state == kProxy_start) { + pt_log(kLog_verbose, "Sending proxy request.\n"); + cur->last_ack = time_as_double(); + queue_packet(fwd_sock, cur->pkt_type, 0, 0, cur->id_no, cur->id_no, + &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, + cur->dst_ip, cur->dst_port, cur->state | cur->type_flag, + &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); + cur->xfer.icmp_out++; + cur->state = kProto_data; + } + if (cur->should_remove) { + pt_log(kLog_info, "\nSession statistics:\n"); + print_statistics(&cur->xfer, 0); + pt_log(kLog_info, "\n"); + tmp = cur->next; + remove_proxy_desc(cur, prev); + continue; + } + /* Only handle traffic if there is traffic on the socket, we have + * room in our send window AND we either don't use a password, or + * have been authenticated. + */ + if (FD_ISSET(cur->sock, &set) && cur->send_wait_ack < kPing_window_size && + (!opts.password || cur->authenticated)) + { + bytes = recv(cur->sock, cur->buf, tcp_receive_buf_len, 0); + if (bytes <= 0) { + pt_log(kLog_info, "Connection closed or lost.\n"); + tmp = cur->next; + send_termination_msg(cur, fwd_sock); + pt_log(kLog_info, "Session statistics:\n"); + print_statistics(&cur->xfer, 0); + remove_proxy_desc(cur, prev); + /* No need to update prev */ + continue; + } + cur->xfer.bytes_out += bytes; + cur->xfer.icmp_out++; + queue_packet(fwd_sock, cur->pkt_type, cur->buf, bytes, cur->id_no, + cur->icmp_id, &cur->my_seq, cur->send_ring, &cur->send_idx, + &cur->send_wait_ack, 0, 0, cur->state | cur->type_flag, + &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); + } + prev = cur; + tmp = cur->next; + } + pthread_mutex_unlock(&chain_lock); + + if (FD_ISSET(fwd_sock, &set)) { + /* Handle ping traffic */ + addr_len = sizeof(struct sockaddr); + bytes = recvfrom(fwd_sock, buf, icmp_receive_buf_len, 0, (struct sockaddr*)&addr, &addr_len); + if (bytes < 0) { + pt_log(kLog_error, "Error receiving packet on ICMP socket: %s\n", strerror(errno)); + break; + } + handle_packet(buf, bytes, 0, &addr, fwd_sock); + } + + /* Check for packets needing resend, and figure out if any connections + * should be closed down due to inactivity. + */ + pthread_mutex_lock(&chain_lock); + now = time_as_double(); + for (cur = chain; cur; cur = cur->next) { + if (cur->last_activity + kAutomatic_close_timeout < now) { + pt_log(kLog_info, "Dropping tunnel to %s:%d due to inactivity.\n", inet_ntoa(*(struct in_addr*)&cur->dst_ip), cur->dst_port, cur->id_no); + cur->should_remove = 1; + continue; + } + if (cur->recv_wait_send && cur->sock) + cur->xfer.bytes_in += send_packets(cur->recv_ring, &cur->recv_xfer_idx, &cur->recv_wait_send, &cur->sock); + + /* Check for any icmp packets requiring resend, and resend _only_ the first packet. */ + idx = cur->send_first_ack; + if (cur->send_ring[idx].pkt && cur->send_ring[idx].last_resend+kResend_interval < now) { + pt_log(kLog_debug, "Resending packet with seq-no %d.\n", cur->send_ring[idx].seq_no); + cur->send_ring[idx].last_resend = now; + cur->send_ring[idx].pkt->seq = htons(cur->ping_seq); + cur->ping_seq++; + cur->send_ring[idx].pkt->checksum = 0; + cur->send_ring[idx].pkt->checksum = htons(calc_icmp_checksum((uint16_t*)cur->send_ring[idx].pkt, cur->send_ring[idx].pkt_len)); + /* printf("ID: %d\n", htons(cur->send_ring[idx].pkt->identifier)); */ + sendto(fwd_sock, (const void*)cur->send_ring[idx].pkt, cur->send_ring[idx].pkt_len, + 0, (struct sockaddr*)&cur->dest_addr, sizeof(struct sockaddr)); + cur->xfer.icmp_resent++; + } + /* Figure out if it's time to send an explicit acknowledgement */ + if (cur->last_ack+1.0 < now && cur->send_wait_ack < kPing_window_size && + cur->remote_ack_val+1 != cur->next_remote_seq) + { + cur->last_ack = now; + queue_packet(fwd_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, + &cur->my_seq, cur->send_ring, &cur->send_idx, &cur->send_wait_ack, + cur->dst_ip, cur->dst_port, kProto_ack | cur->type_flag, + &cur->dest_addr, cur->next_remote_seq, &cur->send_first_ack, &cur->ping_seq); + cur->xfer.icmp_ack_out++; + } + } + pthread_mutex_unlock(&chain_lock); +#ifdef HAVE_PCAP + if (opts.pcap) { + if (pcap_dispatch(pc.pcap_desc, 32, pcap_packet_handler, (u_char*)&pc.pkt_q) > 0) { + pqueue_elem_t *cur; + /* pt_log(kLog_verbose, "pcap captured %d packets - handling them..\n", pc.pkt_q.elems); */ + while (pc.pkt_q.head) { + cur = pc.pkt_q.head; + memset(&addr, 0, sizeof(struct sockaddr)); + addr.sin_family = AF_INET; + pkt = (ip_packet_t*)&cur->data[0]; + ip = pkt->src_ip; + adr = (in_addr_t*)&ip; + addr.sin_addr.s_addr = *adr; + handle_packet(cur->data, cur->bytes, 1, &addr, fwd_sock); + pc.pkt_q.head = cur->next; + free(cur); + pc.pkt_q.elems--; + } + pc.pkt_q.tail = 0; + pc.pkt_q.head = 0; + } + } +#endif + /* Update running statistics, if requested (only once every second) */ + if (opts.print_stats && opts.mode == kMode_forward && now > last_status_update+1) { + pthread_mutex_lock(&chain_lock); + memset(&xfer, 0, sizeof(xfer_stats_t)); + for (cur = chain; cur; cur = cur->next) { + xfer.bytes_in += cur->xfer.bytes_in; + xfer.bytes_out += cur->xfer.bytes_out; + xfer.icmp_in += cur->xfer.icmp_in; + xfer.icmp_out += cur->xfer.icmp_out; + xfer.icmp_resent += cur->xfer.icmp_resent; + } + pthread_mutex_unlock(&chain_lock); + print_statistics(&xfer, (opts.log_level >= kLog_verbose ? 0 : 1)); + last_status_update = now; + } + } + pt_log(kLog_debug, "Proxy exiting..\n"); + if (fwd_sock) + close(fwd_sock); + /* TODO: Clean up the other descs. Not really a priority since there's no + * real way to quit ptunnel in the first place.. + */ + free(buf); + pt_log(kLog_debug, "Ping proxy done\n"); + return 0; +} + +/* print_statistics: Prints transfer statistics for the given xfer block. The + * is_continuous variable controls the output mode, either printing a new line + * or overwriting the old line. + */ +void print_statistics(xfer_stats_t *xfer, int is_continuous) { + const double mb = 1024.0*1024.0; + double loss = 0.0; + + if (xfer->icmp_out > 0) + loss = (double)xfer->icmp_resent/(double)xfer->icmp_out; + + if (is_continuous) + printf("\r"); + + printf("[inf]: I/O: %6.2f/%6.2f mb ICMP I/O/R: %8d/%8d/%8d Loss: %4.1f%%", + xfer->bytes_in/mb, xfer->bytes_out/mb, xfer->icmp_in, xfer->icmp_out, xfer->icmp_resent, loss); + + if (!is_continuous) + printf("\n"); + else + fflush(stdout); +} + +/* pcap_packet_handler: + * This is our callback function handling captured packets. We already know that the packets + * are ICMP echo or echo-reply messages, so all we need to do is strip off the ethernet header + * and append it to the queue descriptor (the refcon argument). + * + * Ok, the above isn't entirely correct (we can get other ICMP types as well). This function + * also has problems when it captures packets on the loopback interface. The moral of the + * story: Don't do ping forwarding over the loopback interface. + * + * Also, we currently don't support anything else than ethernet when in pcap mode. The reason + * is that I haven't read up on yet on how to remove the frame header from the packet.. + */ +void pcap_packet_handler(u_char *refcon, const struct pcap_pkthdr *hdr, const u_char* pkt) { + pqueue_t *q; + pqueue_elem_t *elem; + ip_packet_t *ip; + + /* pt_log(kLog_verbose, "Packet handler: %d =? %d\n", hdr->caplen, hdr->len); */ + q = (pqueue_t*)refcon; + elem = (pqueue_elem_t *) malloc(sizeof(pqueue_elem_t)+hdr->caplen-sizeof(struct ether_header)); + memcpy(elem->data, pkt+sizeof(struct ether_header), hdr->caplen-sizeof(struct ether_header)); + ip = (ip_packet_t*)elem->data; + /* TODO: Add fragment support */ + elem->bytes = ntohs(ip->pkt_len); + if (elem->bytes > hdr->caplen-sizeof(struct ether_header)) { + pt_log(kLog_error, "Received fragmented packet - unable to reconstruct!\n"); + pt_log(kLog_error, "This error usually occurs because pcap is used on " + "devices that are not wlan or ethernet.\n"); + free(elem); + return; + } + /* elem->bytes = hdr->caplen-sizeof(struct ether_header); */ + elem->next = 0; + if (q->tail) { + q->tail->next = elem; + q->tail = elem; + } + else { + q->head = elem; + q->tail = elem; + } + q->elems++; +} + +uint16_t calc_icmp_checksum(uint16_t *data, int bytes) { + uint32_t sum; + int i; + + sum = 0; + for (i = 0; i < bytes / 2; i++) { + /* WARNING; this might be a bug, but might explain why I occasionally + * see buggy checksums.. (added htons, that might be the correct behaviour) + */ + sum += data[i]; + } + sum = (sum & 0xFFFF) + (sum >> 16); + sum = htons(0xFFFF - sum); + return sum; +} + +/* send_termination_msg: Sends two packets to the remote end, informing it that + * the tunnel is being closed down. + */ +void send_termination_msg(proxy_desc_t *cur, int icmp_sock) { + /* Send packet twice, hoping at least one of them makes it through.. */ + queue_packet(icmp_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, &cur->my_seq, + cur->send_ring, &cur->send_idx, &cur->send_wait_ack, 0, 0, + kProto_close | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, + &cur->send_first_ack, &cur->ping_seq); + queue_packet(icmp_sock, cur->pkt_type, 0, 0, cur->id_no, cur->icmp_id, &cur->my_seq, + cur->send_ring, &cur->send_idx, &cur->send_wait_ack, 0, 0, + kProto_close | cur->type_flag, &cur->dest_addr, cur->next_remote_seq, + &cur->send_first_ack, &cur->ping_seq); + cur->xfer.icmp_out += 2; +} diff --git a/src/ptunnel.h b/src/ptunnel.h new file mode 100644 index 0000000..7ce15e3 --- /dev/null +++ b/src/ptunnel.h @@ -0,0 +1,145 @@ +/* ptunnel.h + ptunnel is licensed under the BSD license: + + Copyright (c) 2004-2011, Daniel Stoedle <daniels@cs.uit.no>, + Yellow Lemon Software. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of the Yellow Lemon Software nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Contacting the author: + You can get in touch with me, Daniel Stødle (that's the Norwegian letter oe, + in case your text editor didn't realize), here: <daniels@cs.uit.no> + + The official ptunnel website is here: + <http://www.cs.uit.no/~daniels/PingTunnel/> + + Note that the source code is best viewed with tabs set to 4 spaces. +*/ + +#ifndef PING_TUNNEL_H +#define PING_TUNNEL_H 1 + +#ifndef WIN32 +#include <sys/unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <pthread.h> +#include <errno.h> +#include <net/ethernet.h> +#include <syslog.h> +#include <pwd.h> +#include <grp.h> +#endif /* !WIN32 */ +#include <stdarg.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <signal.h> +#include <stdint.h> +#include <stdbool.h> +#include <pcap.h> + +#include "pkt.h" +#include "pdesc.h" +#include "challenge.h" + +extern pthread_mutex_t chain_lock; +extern uint32_t num_tunnels; +extern const int icmp_receive_buf_len; +extern proxy_desc_t *chain; +extern uint32_t *seq_expiry_tbl; +extern const char *state_name[kNum_proto_types]; + +/* pt_thread_info_t: A simple (very simple, in fact) structure that allows us + * to pass an arbitrary number of params to the threads we create. Currently, + * that's just one single parameter: The socket which the thread should listen + * to. + */ +typedef struct { + int sock; +} pt_thread_info_t; + +/* pqueue_elem_t: An queue element in the pqueue structure (below). + */ +typedef struct pqueue_elem_t { + /** size of data buffer */ + unsigned long bytes; + /** next queue element (if any) */ + struct pqueue_elem_t *next; + /** optional data */ + char data[0]; +} pqueue_elem_t; + +/* pqueue_t: A simple queue strucutre. + */ +typedef struct { + pqueue_elem_t *head; + pqueue_elem_t *tail; + int elems; +} pqueue_t; + +#ifdef HAVE_PCAP +/* pcap_info_t: Structure to hold information related to packet capturing. + */ +typedef struct { + pcap_t *pcap_desc; + /** compiled filter program */ + struct bpf_program fp; + uint32_t netp; + uint32_t netmask; + /** buffers for error info */ + char *pcap_err_buf; + /** buffers for packet info */ + char *pcap_data_buf; + /** queue of packets to process */ + pqueue_t pkt_q; +} pcap_info_t; +#endif + +/* function Prototypes */ +void* pt_proxy(void *args); +void pcap_packet_handler(u_char *refcon, const struct pcap_pkthdr *hdr, + const u_char* pkt); + +void pt_forwarder(void); + +void print_statistics(xfer_stats_t *xfer, int is_continuous); + +void init_ip_packet(ip_packet_t *packet, uint16_t id, uint16_t frag_offset, + uint16_t pkt_len, uint8_t ttl, uint32_t src_ip, uint32_t dst_ip, + bool is_last_frag, bool dont_frag); + +uint16_t calc_icmp_checksum(uint16_t *data, int bytes); + +void send_termination_msg(proxy_desc_t *cur, int icmp_sock); + +#endif diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..0872619 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,73 @@ +#include <stdarg.h> + +#ifndef WIN32 +#include <syslog.h> +#endif +#include <sys/time.h> + +#include "utils.h" +#include "options.h" + +void pt_log(int level, const char *fmt, ...) { + va_list args; + const char *header[] = { "[err]: ", + "[inf]: ", + "[evt]: ", + "[vbs]: ", + "[dbg]: ", + "[xfr]: " }; +#ifndef WIN32 + int syslog_levels[] = {LOG_ERR, LOG_NOTICE, LOG_NOTICE, LOG_INFO, LOG_DEBUG, LOG_DEBUG}; +#endif /* !WIN32 */ + + if (level <= opts.log_level) { + va_start(args, fmt); +#ifndef WIN32 + if (opts.use_syslog) { + char log[255]; + int header_len; + header_len = snprintf(log,sizeof(log),"%s",header[level]); + vsnprintf(log+header_len,sizeof(log)-header_len,fmt,args); + syslog(syslog_levels[level], "%s", log); + } + else +#endif /* !WIN32 */ + fprintf(opts.log_file, "%s", header[level]), vfprintf(opts.log_file, fmt, args); + va_end(args); +#ifndef WIN32 + if (opts.log_file != stdout && !opts.use_syslog) +#else + if (opts.log_file != stdout) +#endif + fflush(opts.log_file); + } +} + +double time_as_double(void) { + double result; + struct timeval tt; + + gettimeofday(&tt, 0); + result = (double)tt.tv_sec + ((double)tt.tv_usec / (double)10e5); + return result; +} + +#if 0 +static const char hextab[] = "0123456789ABCDEF"; + +void print_hexstr(unsigned char *buf, size_t siz) { + char *out = (char *) calloc(3, siz+1); + unsigned char high, low; + + for (size_t i = 0; i < siz; ++i) { + high = (buf[i] & 0xF0) >> 4; + low = buf[i] & 0x0F; + out[i ] = hextab[high]; + out[i+1] = hextab[low]; + out[i+2] = ' '; + } + + printf("%s\n", out); + free(out); +} +#endif diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..7a2b551 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,14 @@ +#ifndef UTILS_H +#define UTILS_H 1 + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +void pt_log(int level, const char *fmt, ...); + +double time_as_double(void); + +#if 0 +void print_hexstr(unsigned char *buf, size_t siz); +#endif + +#endif |