From 567adb42f1f2e706033d94041d6eef9c57487f57 Mon Sep 17 00:00:00 2001 From: divverent Date: Tue, 3 Nov 2009 21:04:39 +0000 Subject: [PATCH] first test of multi-account rcon: rcon_password "account1:1234 account2:2345" Any of the space separated passwords can be matched - the string before the : in each 'password' is printed to the log. So if that is set, clients can send commands both with rcon_password "account1:1234" as well as with rcon_password "account2:2345". Fully backwards compatible: if rcon_password contains no space, user name printing is not performed to keep the logs clean. Also supported for rcon_restricted of course. NOTE: anyone with rcon_password can read the passwords of the others by simply sending the command "rcon_password". Of course this will get logged, but makes all other passwords pointless. Better use this system only for RESTRICTED rcon, is what I am saying. And WATCH YOUR LOGS. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@9420 d7cf8633-e32d-0410-b094-e92efae38249 --- host_cmd.c | 34 +++++++++++------------------ netconn.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/host_cmd.c b/host_cmd.c index 620c1e44..66c08cbc 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -32,7 +32,7 @@ cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, an cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"}; cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"}; cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."}; -cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; +cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"}; cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; @@ -2344,7 +2344,8 @@ ProQuake rcon support */ void Host_PQRcon_f (void) { - int i; + int n; + const char *e; lhnetaddress_t to; lhnetsocket_t *mysocket; char peer_address[64]; @@ -2355,14 +2356,8 @@ void Host_PQRcon_f (void) return; } - for (i = 0;rcon_password.string[i];i++) - { - if (ISWHITESPACE(rcon_password.string[i])) - { - Con_Printf("rcon_password is not allowed to have any whitespace.\n"); - return; - } - } + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) { @@ -2384,7 +2379,7 @@ void Host_PQRcon_f (void) SZ_Clear(&net_message); MSG_WriteLong (&net_message, 0); MSG_WriteByte (&net_message, CCREQ_RCON); - MSG_WriteString (&net_message, rcon_password.string); + SZ_Write(&net_message, (void*)rcon_password.string, n); MSG_WriteString (&net_message, Cmd_Args()); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, net_message.data, net_message.cursize, &to); @@ -2406,7 +2401,8 @@ Host_Rcon_f */ void Host_Rcon_f (void) // credit: taken from QuakeWorld { - int i; + int i, n; + const char *e; lhnetaddress_t to; lhnetsocket_t *mysocket; @@ -2416,14 +2412,8 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld return; } - for (i = 0;rcon_password.string[i];i++) - { - if (ISWHITESPACE(rcon_password.string[i])) - { - Con_Printf("rcon_password is not allowed to have any whitespace.\n"); - return; - } - } + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) to = cls.netcon->peeraddress; @@ -2468,7 +2458,7 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld char argbuf[1500]; dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args()); memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24); - if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, strlen(rcon_password.string))) + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) { buf[40] = ' '; strlcpy(buf + 41, argbuf, sizeof(buf) - 41); @@ -2477,7 +2467,7 @@ void Host_Rcon_f (void) // credit: taken from QuakeWorld } else { - NetConn_WriteString(mysocket, va("\377\377\377\377rcon %s %s", rcon_password.string, Cmd_Args()), &to); + NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to); } } } diff --git a/netconn.c b/netconn.c index efa23d1b..2ddf6a38 100755 --- a/netconn.c +++ b/netconn.c @@ -84,7 +84,7 @@ static cvar_t net_slist_favorites = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "net_slist static cvar_t gameversion = {0, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"}; static cvar_t gameversion_min = {0, "gameversion_min", "-1", "minimum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; static cvar_t gameversion_max = {0, "gameversion_max", "-1", "maximum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; -static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode"}; +static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; static cvar_t rcon_restricted_commands = {0, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"}; static cvar_t rcon_secure_maxdiff = {0, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"}; extern cvar_t rcon_secure; @@ -1588,9 +1588,15 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat { char buf[1500]; char argbuf[1500]; + const char *e; + int n; dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]); memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29); - if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, strlen(rcon_password.string))) + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) { buf[45] = ' '; strlcpy(buf + 46, argbuf, sizeof(buf) - 46); @@ -2409,15 +2415,51 @@ qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, c /// returns a string describing the user level, or NULL for auth failure const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen) { - const char *text; + const char *text, *userpass_start, *userpass_end, *userpass_startpass; + char buf[MAX_INPUTLINE]; qboolean hasquotes; + qboolean restricted = false; + qboolean have_usernames = false; + + userpass_start = rcon_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto allow; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto allow; + } - if(comparator(peeraddress, rcon_password.string, password, cs, cslen)) - return "rcon"; + restricted = true; + have_usernames = false; + userpass_start = rcon_restricted_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto check; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto check; + } - if(!comparator(peeraddress, rcon_restricted_password.string, password, cs, cslen)) - return NULL; + return NULL; // DENIED +check: for(text = s; text != endpos; ++text) if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';')) return NULL; // block possible exploits against the parser/alias expansion @@ -2460,6 +2502,13 @@ match: s += l + 1; } +allow: + userpass_startpass = strchr(userpass_start, ':'); + if(have_usernames && userpass_startpass && userpass_startpass < userpass_end) + return va("%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start); + else + return va("%srcon", restricted ? "restricted " : ""); + return "restricted rcon"; } -- 2.39.2