diff --git a/bam.lua b/bam.lua index 4712c9084..a3ec2c636 100644 --- a/bam.lua +++ b/bam.lua @@ -206,6 +206,7 @@ function build(settings) -- build the small libraries wavpack = Compile(settings, Collect("src/engine/external/wavpack/*.c")) pnglite = Compile(settings, Collect("src/engine/external/pnglite/*.c")) + md5 = Compile(settings, "src/engine/external/md5/md5.c") -- build game components engine_settings = settings:Copy() @@ -271,16 +272,16 @@ function build(settings) tools = {} for i,v in ipairs(tools_src) do toolname = PathFilename(PathBase(v)) - tools[i] = Link(settings, toolname, Compile(settings, v), engine, zlib, pnglite) + tools[i] = Link(settings, toolname, Compile(settings, v), engine, zlib, pnglite, md5) end -- build client, server, version server and master server client_exe = Link(client_settings, "infclass_client", game_shared, game_client, - engine, client, game_editor, zlib, pnglite, wavpack, + engine, client, game_editor, zlib, pnglite, wavpack, md5, client_link_other, client_osxlaunch) server_exe = Link(server_settings, "infclass_srv", engine, server, - game_shared, game_server, zlib, server_link_other) + game_shared, game_server, zlib, server_link_other, md5) serverlaunch = {} if platform == "macosx" then @@ -288,10 +289,10 @@ function build(settings) end versionserver_exe = Link(server_settings, "versionsrv", versionserver, - engine, zlib) + engine, zlib, md5) masterserver_exe = Link(server_settings, "mastersrv", masterserver, - engine, zlib) + engine, zlib, md5) -- make targets c = PseudoTarget("client".."_"..settings.config_name, client_exe, client_depends) diff --git a/src/base/system.c b/src/base/system.c index 519bd7356..e96e0ca99 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -2044,6 +2044,70 @@ unsigned str_quickhash(const char *str) return hash; } +struct SECURE_RANDOM_DATA +{ + int initialized; +#if defined(CONF_FAMILY_WINDOWS) + HCRYPTPROV provider; +#else + IOHANDLE urandom; +#endif +}; + +static struct SECURE_RANDOM_DATA secure_random_data = { 0 }; + +int secure_random_init() +{ + if(secure_random_data.initialized) + { + return 0; + } +#if defined(CONF_FAMILY_WINDOWS) + if(CryptAcquireContext(&secure_random_data.provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + secure_random_data.initialized = 1; + return 0; + } + else + { + return 1; + } +#else + secure_random_data.urandom = io_open("/dev/urandom", IOFLAG_READ); + if(secure_random_data.urandom) + { + secure_random_data.initialized = 1; + return 0; + } + else + { + return 1; + } +#endif +} + +void secure_random_fill(void *bytes, unsigned length) +{ + if(!secure_random_data.initialized) + { + dbg_msg("secure", "called secure_random_fill before secure_random_init"); + dbg_break(); + } +#if defined(CONF_FAMILY_WINDOWS) + if(!CryptGenRandom(secure_random_data.provider, length, bytes)) + { + dbg_msg("secure", "CryptGenRandom failed, last_error=%d", GetLastError()); + dbg_break(); + } +#else + if(length != io_read(secure_random_data.urandom, bytes, length)) + { + dbg_msg("secure", "io_read returned with a short read"); + dbg_break(); + } +#endif +} + #if defined(__cplusplus) } diff --git a/src/base/system.h b/src/base/system.h index cbc4a42fc..172840cd5 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1308,6 +1308,27 @@ int str_utf8_encode(char *ptr, int chr); */ int str_utf8_check(const char *str); +/* + Function: secure_random_init + Initializes the secure random module. + You *MUST* check the return value of this function. + + Returns: + 0 - Initialization succeeded. + 1 - Initialization failed. +*/ +int secure_random_init(); + +/* + Function: secure_random_fill + Fills the buffer with the specified amount of random bytes. + + Parameters: + buffer - Pointer to the start of the buffer. + length - Length of the buffer. +*/ +void secure_random_fill(void *bytes, unsigned length); + #ifdef __cplusplus } #endif diff --git a/src/engine/external/md5/md5.c b/src/engine/external/md5/md5.c new file mode 100644 index 000000000..c35d96c5e --- /dev/null +++ b/src/engine/external/md5/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.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/src/engine/external/md5/md5.h b/src/engine/external/md5/md5.h new file mode 100644 index 000000000..698c995d8 --- /dev/null +++ b/src/engine/external/md5/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 6aad7be71..e3b0d47a1 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -863,6 +863,38 @@ void CServer::DoSnapshot() GameServer()->OnPostSnap(); } +int CServer::ClientRejoinCallback(int ClientID, void *pUser) +{ + CServer *pThis = (CServer *)pUser; + + pThis->m_aClients[ClientID].m_Authed = AUTHED_NO; + pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; + pThis->m_aClients[ClientID].m_Quitting = false; + + pThis->m_aClients[ClientID].Reset(); + + //Getback session about the client + IServer::CClientSession* pSession = pThis->m_NetSession.GetData(pThis->m_NetServer.ClientAddr(ClientID)); + if(pSession) + { + dbg_msg("infclass", "session found for the client %d. Round id = %d, class id = %d", ClientID, pSession->m_RoundId, pSession->m_Class); + pThis->m_aClients[ClientID].m_Session = *pSession; + pThis->m_NetSession.RemoveSession(pThis->m_NetServer.ClientAddr(ClientID)); + } + + //Getback accusation about the client + IServer::CClientAccusation* pAccusation = pThis->m_NetAccusation.GetData(pThis->m_NetServer.ClientAddr(ClientID)); + if(pAccusation) + { + dbg_msg("infclass", "%d accusation(s) found for the client %d", pAccusation->m_Num, ClientID); + pThis->m_aClients[ClientID].m_Accusation = *pAccusation; + pThis->m_NetAccusation.RemoveSession(pThis->m_NetServer.ClientAddr(ClientID)); + } + + pThis->SendMap(ClientID); + + return 0; +} int CServer::NewClientCallback(int ClientID, void *pUser) { @@ -1106,8 +1138,8 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) return; int Chunk = Unpacker.GetInt(); - unsigned int ChunkSize = 1024-128; - unsigned int Offset = Chunk * ChunkSize; + int ChunkSize = 1024-128; + int Offset = Chunk * ChunkSize; int Last = 0; m_FastDownloadLastAsk[ClientID] = Chunk; @@ -1155,7 +1187,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s", ClientID, aAddrStr); + str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%x addr=%s secure=%s", ClientID, aAddrStr, m_NetServer.HasSecurityToken(ClientID)?"yes":"no"); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_aClients[ClientID].m_State = CClient::STATE_READY; m_aClients[ClientID].m_WaitingTime = TickSpeed()*g_Config.m_InfConWaitingTime; @@ -1525,8 +1557,8 @@ void CServer::PumpNetwork() continue; int Chunk = m_FastDownloadLastSent[i]++; - unsigned int ChunkSize = 1024-128; - unsigned int Offset = Chunk * ChunkSize; + int ChunkSize = 1024-128; + int Offset = Chunk * ChunkSize; int Last = 0; // drop faulty map data requests @@ -1820,7 +1852,7 @@ int CServer::Run() } } - m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); + m_NetServer.SetCallbacks(NewClientCallback, ClientRejoinCallback, DelClientCallback, this); m_Econ.Init(Console(), &m_ServerBan); @@ -2109,16 +2141,25 @@ bool CServer::ConStatus(IConsole::IResult *pResult, void *pUser) break; } - const char *pAuthStr = pThis->m_aClients[i].m_Authed == CServer::AUTHED_ADMIN ? "Admin" : - pThis->m_aClients[i].m_Authed == CServer::AUTHED_MOD ? "Mod" : ""; - str_format(aBuf, sizeof(aBuf), "#%02i (%s) %s, [%s], cs:%i | ar:%i | language:%s", + //Add some padding to make the command more readable + char aBufName[18]; + str_copy(aBufName, pThis->ClientName(i), sizeof(aBufName)); + for(int c=str_length(aBufName); c<((int)sizeof(aBufName))-1; c++) + aBufName[c] = ' '; + aBufName[sizeof(aBufName)-1] = 0; + + const char *pAuthStr = pThis->m_aClients[i].m_Authed == CServer::AUTHED_ADMIN ? " Admin" : + pThis->m_aClients[i].m_Authed == CServer::AUTHED_MOD ? " Moderator" : "Player"; + + const char *pSecurityStr = pThis->m_NetServer.HasSecurityToken(i) ? " Protected" : "Unprotected"; + + str_format(aBuf, sizeof(aBuf), "#%02i | %s | %s | %s | %s | %s", i, + aBufName, aAddrStr, - pThis->ClientName(i), + aLangBuf, pAuthStr, - pThis->m_aClients[i].m_CustomSkin, - pThis->m_aClients[i].m_AlwaysRandom, - aLangBuf + pSecurityStr ); } else @@ -2440,6 +2481,12 @@ int main(int argc, const char **argv) // ignore_convention } #endif + if(secure_random_init() != 0) + { + dbg_msg("secure", "could not initialize secure RNG"); + return -1; + } + CServer *pServer = CreateServer(); IKernel *pKernel = IKernel::Create(); diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 5fb0017d3..e3fef3c08 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -222,6 +222,7 @@ class CServer : public IServer void DoSnapshot(); + static int ClientRejoinCallback(int ClientID, void *pUser); static int NewClientCallback(int ClientID, void *pUser); static int DelClientCallback(int ClientID, int Type, const char *pReason, void *pUser); diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 5ec60d1d0..30f36a7c2 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -110,6 +110,9 @@ MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings") MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CLIENT, "Server to stress") MACRO_CONFIG_INT(DbgResizable, dbg_resizable, 0, 0, 0, CFGFLAG_CLIENT, "Enables window resizing") +MACRO_CONFIG_INT(SvConnlimit, sv_connlimit, 4, 0, 100, CFGFLAG_SERVER, "Connlimit: Number of connections an IP is allowed to do in a timespan") +MACRO_CONFIG_INT(SvConnlimitTime, sv_connlimit_time, 20, 0, 1000, CFGFLAG_SERVER, "Connlimit: Time in which IP's connections are counted") + MACRO_CONFIG_INT(InfChallenge, inf_challenge, 0, 0, 1, CFGFLAG_SERVER, "Enable challenges") MACRO_CONFIG_INT(InfAccusationThreshold, inf_accusation_threshold, 4, 0, 8, CFGFLAG_SERVER, "Number of accusation needed to start a banvote") MACRO_CONFIG_INT(InfFastDownload, inf_fast_download, 1, 0, 1, CFGFLAG_SERVER, "Enables fast download of maps") diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp index ada4b18ab..674c8d7d7 100644 --- a/src/engine/shared/network.cpp +++ b/src/engine/shared/network.cpp @@ -101,7 +101,7 @@ void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void * net_udp_send(Socket, pAddr, aBuffer, 6+DataSize); } -void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket) +void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken) { unsigned char aBuffer[NET_MAX_PACKETSIZE]; int CompressedSize = -1; @@ -117,6 +117,14 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct io_flush(ms_DataLogSent); } + if (SecurityToken != NET_SECURITY_TOKEN_UNSUPPORTED) + { + // append security token + // if SecurityToken is NET_SECURITY_TOKEN_UNKNOWN we will still append it hoping to negotiate it + mem_copy(&pPacket->m_aChunkData[pPacket->m_DataSize], &SecurityToken, sizeof(SecurityToken)); + pPacket->m_DataSize += sizeof(SecurityToken); + } + // compress CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); @@ -225,8 +233,7 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct return 0; } - -void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize) +void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken) { CNetPacketConstruct Construct; Construct.m_Flags = NET_PACKETFLAG_CONTROL; @@ -237,11 +244,9 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize); // send the control message - CNetBase::SendPacket(Socket, pAddr, &Construct); + CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken); } - - unsigned char *CNetChunkHeader::Pack(unsigned char *pData) { pData[0] = ((m_Flags&3)<<6)|((m_Size>>4)&0x3f); diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 05ee2fd56..3f36aba2d 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -7,6 +7,10 @@ #include "ringbuffer.h" #include "huffman.h" +/* + * Thanks to DDNet for the Token system + */ + /* CURRENT: @@ -76,6 +80,8 @@ enum NET_CONN_BUFFERSIZE=1024*32, + NET_CONNLIMIT_IPS=16, + NET_ENUM_TERMINATOR }; @@ -91,8 +97,21 @@ enum CLIENTDROPTYPE_STRESSING }; +typedef int SECURITY_TOKEN; + +static const unsigned char SECURITY_TOKEN_MAGIC[] = {'T', 'K', 'E', 'N'}; + +SECURITY_TOKEN ToSecurityToken(const unsigned char* pData); + +enum +{ + NET_SECURITY_TOKEN_UNKNOWN = -1, + NET_SECURITY_TOKEN_UNSUPPORTED = 0, +}; + typedef int (*NETFUNC_DELCLIENT)(int ClientID, int Type, const char* pReason, void *pUser); typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); +typedef int (*NETFUNC_CLIENTREJOIN)(int ClientID, void *pUser); struct CNetChunk { @@ -148,11 +167,14 @@ class CNetConnection private: unsigned short m_Sequence; unsigned short m_Ack; + unsigned short m_PeerAck; unsigned m_State; int m_Token; + SECURITY_TOKEN m_SecurityToken; int m_RemoteClosed; bool m_BlockCloseMsg; + bool m_UnknownSeq; TStaticRingBuffer m_Buffer; @@ -168,8 +190,11 @@ class CNetConnection NETSOCKET m_Socket; NETSTATS m_Stats; - // - void Reset(); +public: + bool m_TimeoutProtected; + bool m_TimeoutSituation; + +private: void ResetStats(); void SetError(const char *pString); void AckChunks(int Ack); @@ -180,6 +205,8 @@ class CNetConnection void Resend(); public: + void Reset(bool Rejoin=false); + void Init(NETSOCKET Socket, bool BlockCloseMsg); int Connect(NETADDR *pAddr); void Disconnect(const char *pReason); @@ -203,7 +230,14 @@ class CNetConnection int64 LastRecvTime() const { return m_LastRecvTime; } int64 ConnectTime() const { return m_LastUpdateTime; } + int SecurityToken() const { return m_SecurityToken; } + int AckSequence() const { return m_Ack; } + + // anti spoof + void DirectInit(NETADDR &Addr, SECURITY_TOKEN SecurityToken); + void SetUnknownSeq() { m_UnknownSeq = true; } + void SetSequence(int Sequence) { m_Sequence = Sequence; } }; class CConsoleNetConnection @@ -263,6 +297,13 @@ class CNetServer CNetConnection m_Connection; }; + struct CSpamConn + { + NETADDR m_Addr; + int64 m_Time; + int m_Conns; + }; + NETSOCKET m_Socket; class CNetBan *m_pNetBan; CSlot m_aSlots[NET_MAX_CLIENTS]; @@ -271,8 +312,13 @@ class CNetServer NETFUNC_NEWCLIENT m_pfnNewClient; NETFUNC_DELCLIENT m_pfnDelClient; + NETFUNC_CLIENTREJOIN m_pfnClientRejoin; void *m_UserPtr; + unsigned char m_SecurityTokenSeed[16]; + + CSpamConn m_aSpamConns[NET_CONNLIMIT_IPS]; + CNetRecvUnpacker m_RecvUnpacker; struct CCaptcha @@ -283,6 +329,7 @@ class CNetServer public: int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_CLIENTREJOIN pfnClientRejoin, NETFUNC_DELCLIENT pfnDelClient, void *pUser); // bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags); @@ -296,8 +343,20 @@ class CNetServer // int Drop(int ClientID, int Type, const char *pReason); + void SendControl(NETADDR &Addr, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken); + int TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken); + void OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet); + void OnConnCtrlMsg(NETADDR &Addr, int ClientID, int ControlMsg, const CNetPacketConstruct &Packet); + void OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet); + bool ClientExists(const NETADDR &Addr) { return GetClientSlot(Addr) != -1; }; + int GetClientSlot(const NETADDR &Addr); + SECURITY_TOKEN GetToken(const NETADDR &Addr); + bool Connlimit(NETADDR Addr); + int NumClientsWithAddr(NETADDR Addr); + // status requests const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } + bool HasSecurityToken(int ClientID) const { return m_aSlots[ClientID].m_Connection.SecurityToken() != NET_SECURITY_TOKEN_UNSUPPORTED; } NETSOCKET Socket() const { return m_Socket; } class CNetBan *NetBan() const { return m_pNetBan; } int NetType() const { return m_Socket.type; } @@ -399,9 +458,9 @@ class CNetBase static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize); static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize); - static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize); + static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken); static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize); - static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket); + static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken); static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket); // The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp index 56feec489..ea2652c3a 100644 --- a/src/engine/shared/network_conn.cpp +++ b/src/engine/shared/network_conn.cpp @@ -4,23 +4,39 @@ #include "config.h" #include "network.h" +SECURITY_TOKEN ToSecurityToken(const unsigned char* pData) +{ + return (int)pData[0] | (pData[1] << 8) | (pData[2] << 16) | (pData[3] << 24); +} + void CNetConnection::ResetStats() { mem_zero(&m_Stats, sizeof(m_Stats)); } -void CNetConnection::Reset() +void CNetConnection::Reset(bool Rejoin) { m_Sequence = 0; m_Ack = 0; + m_PeerAck = 0; m_RemoteClosed = 0; - m_State = NET_CONNSTATE_OFFLINE; + if (!Rejoin) + { + m_TimeoutProtected = false; + m_TimeoutSituation = false; + + m_State = NET_CONNSTATE_OFFLINE; + m_Token = -1; + m_SecurityToken = NET_SECURITY_TOKEN_UNKNOWN; + } + m_LastSendTime = 0; m_LastRecvTime = 0; - m_LastUpdateTime = 0; - m_Token = -1; - mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + //m_LastUpdateTime = 0; + + //mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + m_UnknownSeq = false; m_Buffer.Init(); @@ -75,7 +91,7 @@ int CNetConnection::Flush() // send of the packets m_Construct.m_Ack = m_Ack; - CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct); + CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct, m_SecurityToken); // update send times m_LastSendTime = time_get(); @@ -134,6 +150,23 @@ int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int return 0; } +void CNetConnection::DirectInit(NETADDR &Addr, SECURITY_TOKEN SecurityToken) +{ + Reset(); + + m_State = NET_CONNSTATE_ONLINE; + + m_PeerAddr = Addr; + mem_zero(m_ErrorString, sizeof(m_ErrorString)); + + int64 Now = time_get(); + m_LastSendTime = Now; + m_LastRecvTime = Now; + m_LastUpdateTime = Now; + + m_SecurityToken = SecurityToken; +} + int CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData) { if(Flags&NET_CHUNKFLAG_VITAL) @@ -145,7 +178,7 @@ void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSi { // send the control message m_LastSendTime = time_get(); - CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize); + CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize, m_SecurityToken); } void CNetConnection::ResendChunk(CNetChunkResend *pResend) diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index 9b938738e..6a147e21b 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "netban.h" #include "network.h" @@ -31,6 +32,8 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma m_MaxClientsPerIP = MaxClientsPerIP; + secure_random_fill(m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + for(int i = 0; i < NET_MAX_CLIENTS; i++) m_aSlots[i].m_Connection.Init(m_Socket, true); @@ -45,6 +48,15 @@ int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT p return 0; } +int CNetServer::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_CLIENTREJOIN pfnClientRejoin, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnClientRejoin = pfnClientRejoin; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; + return 0; +} + int CNetServer::Close() { // TODO: implement me @@ -87,6 +99,241 @@ int CNetServer::Update() return 0; } +SECURITY_TOKEN CNetServer::GetToken(const NETADDR &Addr) +{ + md5_state_t md5; + md5_byte_t digest[16]; + SECURITY_TOKEN SecurityToken; + md5_init(&md5); + + md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + md5_append(&md5, (unsigned char*)&Addr, sizeof(Addr)); + + md5_finish(&md5, digest); + SecurityToken = ToSecurityToken(digest); + + if (SecurityToken == NET_SECURITY_TOKEN_UNKNOWN || + SecurityToken == NET_SECURITY_TOKEN_UNSUPPORTED) + SecurityToken = 1; + + return SecurityToken; +} + +void CNetServer::SendControl(NETADDR &Addr, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken) +{ + CNetBase::SendControlMsg(m_Socket, &Addr, 0, ControlMsg, pExtra, ExtraSize, SecurityToken); +} + +int CNetServer::NumClientsWithAddr(NETADDR Addr) +{ + NETADDR ThisAddr = Addr, OtherAddr; + + int FoundAddr = 0; + ThisAddr.port = 0; + + for(int i = 0; i < MaxClients(); ++i) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE || + (m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR && + (!m_aSlots[i].m_Connection.m_TimeoutProtected || + !m_aSlots[i].m_Connection.m_TimeoutSituation))) + continue; + + OtherAddr = *m_aSlots[i].m_Connection.PeerAddress(); + OtherAddr.port = 0; + if(!net_addr_comp(&ThisAddr, &OtherAddr)) + FoundAddr++; + } + + return FoundAddr; +} + +bool CNetServer::Connlimit(NETADDR Addr) +{ + int64 Now = time_get(); + int Oldest = 0; + + for(int i = 0; i < NET_CONNLIMIT_IPS; ++i) + { + if(!net_addr_comp(&m_aSpamConns[i].m_Addr, &Addr)) + { + if(m_aSpamConns[i].m_Time > Now - time_freq() * g_Config.m_SvConnlimitTime) + { + if(m_aSpamConns[i].m_Conns >= g_Config.m_SvConnlimit) + return true; + } + else + { + m_aSpamConns[i].m_Time = Now; + m_aSpamConns[i].m_Conns = 0; + } + m_aSpamConns[i].m_Conns++; + return false; + } + + if(m_aSpamConns[i].m_Time < m_aSpamConns[Oldest].m_Time) + Oldest = i; + } + + m_aSpamConns[Oldest].m_Addr = Addr; + m_aSpamConns[Oldest].m_Time = Now; + m_aSpamConns[Oldest].m_Conns = 1; + return false; +} + +int CNetServer::TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken) +{ + if (Connlimit(Addr)) + { + const char Msg[] = "Too many connections in a short time"; + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, Msg, sizeof(Msg), SecurityToken); + return -1; // failed to add client + } + + // check for sv_max_clients_per_ip + if (NumClientsWithAddr(Addr) + 1 > m_MaxClientsPerIP) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1, SecurityToken); + return -1; // failed to add client + } + + int Slot = -1; + for(int i = 0; i < MaxClients(); i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + { + Slot = i; + break; + } + } + + if (Slot == -1) + { + const char FullMsg[] = "This server is full"; + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg), SecurityToken); + + return -1; // failed to add client + } + + // init connection slot + m_aSlots[Slot].m_Connection.DirectInit(Addr, SecurityToken); + + m_pfnNewClient(Slot, m_UserPtr); + + return Slot; // done +} + +// connection-less msg packet without token-support +void CNetServer::OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet) +{ + bool IsCtrl = Packet.m_Flags&NET_PACKETFLAG_CONTROL; + int CtrlMsg = m_RecvUnpacker.m_Data.m_aChunkData[0]; + + if (IsCtrl && CtrlMsg == NET_CTRLMSG_CONNECT) + { + // accept client directy + SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, 0, 0, NET_SECURITY_TOKEN_UNSUPPORTED); + + TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED); + } +} + +void CNetServer::OnConnCtrlMsg(NETADDR &Addr, int ClientID, int ControlMsg, const CNetPacketConstruct &Packet) +{ + if (ControlMsg == NET_CTRLMSG_CONNECT) + { + // got connection attempt inside of valid session + // the client probably wants to reconnect + bool SupportsToken = Packet.m_DataSize >= + (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN)) && + !mem_comp(&Packet.m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); + + if (SupportsToken) + { + // response connection request with token + SECURITY_TOKEN Token = GetToken(Addr); + SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token); + } + + if (g_Config.m_Debug) + dbg_msg("security", "client %d wants to reconnect", ClientID); + } + else if (ControlMsg == NET_CTRLMSG_ACCEPT && Packet.m_DataSize == 1 + sizeof(SECURITY_TOKEN)) + { + SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]); + if (Token == GetToken(Addr)) + { + // correct token + // try to accept client + if (g_Config.m_Debug) + dbg_msg("security", "client %d reconnect"); + + // reset netconn and process rejoin + m_aSlots[ClientID].m_Connection.Reset(true); + m_pfnClientRejoin(ClientID, m_UserPtr); + } + } +} + +void CNetServer::OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet) +{ + if (ClientExists(Addr)) + return; // silently ignore + + + if (ControlMsg == NET_CTRLMSG_CONNECT) + { + bool SupportsToken = Packet.m_DataSize >= + (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN)) && + !mem_comp(&Packet.m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); + + if (SupportsToken) + { + // response connection request with token + SECURITY_TOKEN Token = GetToken(Addr); + SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token); + } + } + else if (ControlMsg == NET_CTRLMSG_ACCEPT && Packet.m_DataSize == 1 + sizeof(SECURITY_TOKEN)) + { + SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]); + if (Token == GetToken(Addr)) + { + // correct token + // try to accept client + if (g_Config.m_Debug) + dbg_msg("security", "new client (ddnet token)"); + TryAcceptClient(Addr, Token); + } + else + { + // invalid token + if (g_Config.m_Debug) + dbg_msg("security", "invalid token"); + } + } +} + +int CNetServer::GetClientSlot(const NETADDR &Addr) +{ + int Slot = -1; + + for(int i = 0; i < MaxClients(); i++) + { + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE && + m_aSlots[i].m_Connection.State() != NET_CONNSTATE_ERROR && + net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0) + + { + Slot = i; + } + } + + return Slot; +} + /* TODO: chopp up this function into smaller working parts */ @@ -106,18 +353,18 @@ int CNetServer::Recv(CNetChunk *pChunk) // no more packets for now if(Bytes <= 0) break; - + + // check if we just should drop the packet + char aBuf[128]; + if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf))) + { + // banned, reply with a message + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1, NET_SECURITY_TOKEN_UNSUPPORTED); + continue; + } + if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0) { - // check if we just should drop the packet - char aBuf[128]; - if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf))) - { - // banned, reply with a message - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1); - continue; - } - if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONNLESS) { pChunk->m_Flags = NETSENDFLAG_CONNLESS; @@ -129,110 +376,39 @@ int CNetServer::Recv(CNetChunk *pChunk) } else { - //If the message is NET_PACKETFLAG_CONTROL, send fake - //control message to force the client to send a NETMSG_INFO - if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT) + // drop invalid ctrl packets + if (m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && + m_RecvUnpacker.m_Data.m_DataSize == 0) + continue; + + // normal packet, find matching slot + int Slot = GetClientSlot(Addr); + + if (Slot != -1) { - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CONNECTACCEPT, 0, 0); + // found + + // control + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL) + OnConnCtrlMsg(Addr, Slot, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data); + + if(m_aSlots[Slot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) + { + if(m_RecvUnpacker.m_Data.m_DataSize) + m_RecvUnpacker.Start(&Addr, &m_aSlots[Slot].m_Connection, Slot); + } } else { - int ClientID = -1; - - for(int i = 0; i < MaxClients(); i++) - { - if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE && - net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0) - { - ClientID = i; - break; - } - } - - //The client is unknown, check for NETMSG_INFO - if(ClientID < 0) - { - //If the message is NETMSG_INFO, connect the client. - static const char m_NetMsgInfoHeader1[] = { 0x00, 0x00, 0x01 }; - static const char m_NetMsgInfoHeader2[] = { 0x01, 0x03 }; - if(Bytes > 10 - && (mem_comp(m_RecvUnpacker.m_aBuffer, m_NetMsgInfoHeader1, sizeof(m_NetMsgInfoHeader1)/sizeof(unsigned char)) == 0) - && (mem_comp(m_RecvUnpacker.m_aBuffer+5, m_NetMsgInfoHeader2, sizeof(m_NetMsgInfoHeader2)/sizeof(unsigned char)) == 0) - && m_RecvUnpacker.m_aBuffer[Bytes-1] == 0 - ) - { - //Check password if captcha are enabled - if(g_Config.m_InfCaptcha) - { - const unsigned char* pPassword = 0; - int Iter = 7; //Skin the header and the ID - while(m_RecvUnpacker.m_aBuffer[Iter] != 0 && Iter < Bytes) - ++Iter; - if(Iter+1 < Bytes) - pPassword = m_RecvUnpacker.m_aBuffer+Iter+1; - - if(!pPassword || (str_comp(GetCaptcha(&Addr, true), (const char*) pPassword) != 0)) - { - const char WrongPasswordMsg[] = "Wrong password"; - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, WrongPasswordMsg, sizeof(WrongPasswordMsg)); - return 0; - } - } - - // only allow a specific number of players with the same ip - NETADDR ThisAddr = Addr, OtherAddr; - int FoundAddr = 1; - ThisAddr.port = 0; - for(int i = 0; i < MaxClients(); ++i) - { - if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) - continue; - - OtherAddr = *m_aSlots[i].m_Connection.PeerAddress(); - OtherAddr.port = 0; - if(!net_addr_comp(&ThisAddr, &OtherAddr)) - { - if(FoundAddr++ >= m_MaxClientsPerIP) - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP); - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, sizeof(aBuf)); - return 0; - } - } - } - - bool Found = false; - for(int i = 0; i < MaxClients(); i++) - { - if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) - { - Found = true; - m_aSlots[i].m_Connection.SimulateConnexionWithInfo(&Addr); - if(m_pfnNewClient) - m_pfnNewClient(i, m_UserPtr); - ClientID = i; - break; - } - } - - if(!Found) - { - const char FullMsg[] = "This server is full"; - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg)); - } - } - //Otherwise, just drop the message - } - - if(ClientID >= 0) - { - if(m_aSlots[ClientID].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr)) - { - if(m_RecvUnpacker.m_Data.m_DataSize) - m_RecvUnpacker.Start(&Addr, &m_aSlots[ClientID].m_Connection, ClientID); - } - } + // not found, client that wants to connect + + if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && + m_RecvUnpacker.m_Data.m_DataSize > 1) + // got control msg with extra size (should support token) + OnTokenCtrlMsg(Addr, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data); + else + // got connection-less ctrl or sys msg + OnPreConnMsg(Addr, m_RecvUnpacker.m_Data); } } }