From 3e69400aac1842c4ed7f9131c1cd67cafc3d1084 Mon Sep 17 00:00:00 2001 From: havoc Date: Thu, 23 Feb 2006 14:50:41 +0000 Subject: [PATCH] added qw compatible "rcon" support (allows a client who knows the server's rcon_password to run a command on the server), had to move NetConn_ServerFrame outside of the SV_VM_Begin block in Host_ServerFrame, and add relevant SV_VM_Begin blocks to various parts of host_cmd.c added qw print command packet to client packet processing (needed for remote rcon when not connected to the server) added qw "packet" command (sends a text message to the specified address) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6017 d7cf8633-e32d-0410-b094-e92efae38249 --- client.h | 3 ++ console.c | 11 ++++- console.h | 4 ++ host.c | 12 +++-- host_cmd.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++ netconn.c | 71 +++++++++++++++++++++++++--- netconn.h | 8 ++++ sv_user.c | 8 +++- 8 files changed, 238 insertions(+), 14 deletions(-) diff --git a/client.h b/client.h index b917e488..f3a8bace 100644 --- a/client.h +++ b/client.h @@ -659,6 +659,9 @@ extern cvar_t cl_pmodel; extern cvar_t cl_playermodel; extern cvar_t cl_playerskin; +extern cvar_t rcon_password; +extern cvar_t rcon_address; + extern cvar_t cl_upspeed; extern cvar_t cl_forwardspeed; extern cvar_t cl_backspeed; diff --git a/console.c b/console.c index 7c197715..cd11547d 100644 --- a/console.c +++ b/console.c @@ -53,6 +53,11 @@ int con_vislines; qboolean con_initialized; +// used for server replies to rcon command +qboolean rcon_redirect = false; +int rcon_redirect_bufferpos = 0; +char rcon_redirect_buffer[1400]; + /* ============================================================================== @@ -561,9 +566,13 @@ void Con_Print(const char *msg) for (;*msg;msg++) { + // if this print is in response to an rcon command, add the character + // to the rcon redirect buffer + if (rcon_redirect && rcon_redirect_bufferpos < (int)sizeof(rcon_redirect_buffer) - 1) + rcon_redirect_buffer[rcon_redirect_bufferpos++] = *msg; + // if this is the beginning of a new line, print timestamp if (index == 0) { - // if this is the beginning of a new line, print timestamp const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : ""; // reset the color // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7! diff --git a/console.h b/console.h index 644aac08..4b8048c9 100644 --- a/console.h +++ b/console.h @@ -28,6 +28,10 @@ extern int con_totallines; extern int con_backscroll; extern qboolean con_initialized; +extern qboolean rcon_redirect; +extern int rcon_redirect_bufferpos; +extern char rcon_redirect_buffer[1400]; + void Con_CheckResize (void); void Con_Init (void); void Con_Init_Commands (void); diff --git a/host.c b/host.c index e63a7f7c..6edefc97 100644 --- a/host.c +++ b/host.c @@ -122,6 +122,9 @@ void Host_Error (const char *error, ...) static qboolean hosterror = false; va_list argptr; + // turn off rcon redirect if it was active when the crash occurred + rcon_redirect = false; + va_start (argptr,error); dpvsnprintf (hosterrorstring1,sizeof(hosterrorstring1),error,argptr); va_end (argptr); @@ -697,10 +700,6 @@ void Host_ServerFrame (void) // set the time and clear the general datagram SV_ClearDatagram(); - // check for network packets to the server each world step incase they - // come in midframe (particularly if host is running really slow) - NetConn_ServerFrame(); - // move things around and think unless paused if (sv.frametime) SV_Physics(); @@ -770,6 +769,11 @@ void _Host_Frame (float time) // //------------------- + // receive server packets now, which might contain rcon commands, which + // may change level or other such things we don't want to have happen in + // the middle of Host_Frame + NetConn_ServerFrame(); + // check for commands typed to the host Host_GetConsoleCommands(); diff --git a/host_cmd.c b/host_cmd.c index 5aae255e..70572dab 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -22,6 +22,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. int current_skill; cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"}; +cvar_t rcon_password = {0, "rcon_password", "", "password to authenticate rcon commands"}; +cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; qboolean allowcheats = false; /* @@ -788,6 +790,7 @@ void Host_Name_f (void) host_client->nametime = sv.time + 5; // point the string back at updateclient->name to keep it safe + SV_VM_Begin(); strlcpy (host_client->name, newName, sizeof (host_client->name)); host_client->edict->fields.server->netname = PRVM_SetEngineString(host_client->name); if (strcmp(host_client->old_name, host_client->name)) @@ -800,6 +803,7 @@ void Host_Name_f (void) MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->name); } + SV_VM_End(); } /* @@ -848,6 +852,7 @@ void Host_Playermodel_f (void) host_client->nametime = sv.time + 5; */ + SV_VM_Begin(); // point the string back at updateclient->name to keep it safe strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel)); if( eval_playermodel ) @@ -860,6 +865,7 @@ void Host_Playermodel_f (void) MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/ } + SV_VM_End(); } /* @@ -907,6 +913,7 @@ void Host_Playerskin_f (void) host_client->nametime = sv.time + 5; */ + SV_VM_Begin(); // point the string back at updateclient->name to keep it safe strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin)); if( eval_playerskin ) @@ -921,6 +928,7 @@ void Host_Playerskin_f (void) MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/ } + SV_VM_End(); } void Host_Version_f (void) @@ -1114,6 +1122,7 @@ void Host_Color_f(void) return; } + SV_VM_Begin(); if (host_client->edict && (f = PRVM_ED_FindFunction ("SV_ChangeTeam")) && (SV_ChangeTeam = (func_t)(f - prog->functions))) { Con_DPrint("Calling SV_ChangeTeam\n"); @@ -1141,6 +1150,7 @@ void Host_Color_f(void) MSG_WriteByte (&sv.reliable_datagram, host_client->colors); } } + SV_VM_End(); } cvar_t cl_rate = {CVAR_SAVE, "_cl_rate", "10000", "internal storage cvar for current rate (changed by rate command)"}; @@ -1187,9 +1197,11 @@ void Host_Kill_f (void) return; } + SV_VM_Begin(); prog->globals.server->time = sv.time; prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict); PRVM_ExecuteProgram (prog->globals.server->ClientKill, "QC function ClientKill is missing"); + SV_VM_End(); } @@ -1248,8 +1260,10 @@ static void Host_PModel_f (void) return; } + SV_VM_Begin(); if (host_client->edict && (val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_pmodel))) val->_float = i; + SV_VM_End(); } //=========================================================================== @@ -1320,6 +1334,7 @@ void Host_Spawn_f (void) // SZ_Clear (&host_client->netconnection->message); // run the entrance script + SV_VM_Begin(); if (sv.loadgame) { // loaded games are fully initialized already @@ -1354,10 +1369,12 @@ void Host_Spawn_f (void) PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing"); } + SV_VM_End(); if (!host_client->netconnection) return; + SV_VM_Begin(); // send time of update MSG_WriteByte (&host_client->netconnection->message, svc_time); MSG_WriteFloat (&host_client->netconnection->message, sv.time); @@ -1420,6 +1437,7 @@ void Host_Spawn_f (void) MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); MSG_WriteByte (&host_client->netconnection->message, 3); + SV_VM_End(); } /* @@ -1555,6 +1573,7 @@ void Host_Give_f (void) t = Cmd_Argv(1); v = atoi (Cmd_Argv(2)); + SV_VM_Begin(); switch (t[0]) { case '0': @@ -1684,6 +1703,7 @@ void Host_Give_f (void) } break; } + SV_VM_End(); } prvm_edict_t *FindViewthing (void) @@ -1962,6 +1982,116 @@ static void MaxPlayers_f(void) //============================================================================= +// QuakeWorld commands + +char emodel_name[] = + { 'e' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; +char pmodel_name[] = + { 'p' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; +char prespawn_name[] = + { 'p'^0xff, 'r'^0xff, 'e'^0xff, 's'^0xff, 'p'^0xff, 'a'^0xff, 'w'^0xff, 'n'^0xff, + ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '0'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; +char modellist_name[] = + { 'm'^0xff, 'o'^0xff, 'd'^0xff, 'e'^0xff, 'l'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, + ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; +char soundlist_name[] = + { 's'^0xff, 'o'^0xff, 'u'^0xff, 'n'^0xff, 'd'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, + ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; + +/* +===================== +Host_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void Host_Rcon_f (void) // credit: taken from QuakeWorld +{ + lhnetaddress_t to; + lhnetsocket_t *mysocket; + + if (!rcon_password.string) + { + Con_Printf ("You must set rcon_password before issuing an rcon command.\n"); + return; + } + + if (cls.netcon) + to = cls.netcon->peeraddress; + else + { + if (!rcon_address.integer || !rcon_address.string[0]) + { + Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); + return; + } + LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer); + } + mysocket = NetConn_ChooseClientSocketForAddress(&to); + if (mysocket) + { + // simply put together the rcon packet and send it + NetConn_WriteString(mysocket, va("\377\377\377\377rcon %s %s", rcon_password.string, Cmd_Args()), &to); + } +} + +/* +==================== +Host_Packet_f + +packet + +Contents allows \n escape character +==================== +*/ +void Host_Packet_f (void) // credit: taken from QuakeWorld +{ + char send[2048]; + int i, l; + const char *in; + char *out; + lhnetaddress_t address; + lhnetsocket_t *mysocket; + + if (Cmd_Argc() != 3) + { + Con_Printf ("packet \n"); + return; + } + + if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer)) + { + Con_Printf ("Bad address\n"); + return; + } + + in = Cmd_Argv(2); + out = send+4; + send[0] = send[1] = send[2] = send[3] = 0xff; + + l = strlen (in); + for (i=0 ; i= send + sizeof(send) - 1) + break; + if (in[i] == '\\' && in[i+1] == 'n') + { + *out++ = '\n'; + i++; + } + else + *out++ = in[i]; + } + *out = 0; + + mysocket = NetConn_ChooseClientSocketForAddress(&address); + if (mysocket) + NetConn_WriteString(mysocket, send, &address); +} + +//============================================================================= + /* ================== Host_InitCommands @@ -2037,6 +2167,11 @@ void Host_InitCommands (void) Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC"); // By [515] + Cvar_RegisterVariable (&rcon_password); + Cvar_RegisterVariable (&rcon_address); + Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); + Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string"); + Cvar_RegisterVariable(&sv_cheats); } diff --git a/netconn.c b/netconn.c index ef47f7e6..e9125953 100755 --- a/netconn.c +++ b/netconn.c @@ -930,9 +930,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat stringbuf[length] = 0; string = stringbuf; + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + if (developer.integer) { - LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2); Com_HexDumpToConsole(data, length); } @@ -941,7 +942,6 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat { char protocolnames[1400]; Protocol_Names(protocolnames, sizeof(protocolnames)); - LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("\"%s\" received, sending connect request back to %s\n", string, addressstring2); M_Update_Return_Reason("Got challenge response"); NetConn_WriteString(mysocket, va("\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s\\challenge\\%s", protocolnames, string + 10), peeraddress); @@ -1086,6 +1086,11 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat if (!strncmp(string, "ack", 3)) return true; */ + if (string[0] == 'n') + { + // qw print command + Con_Printf("QW print command from server at %s:\n", addressstring2, string + 1); + } // we may not have liked the packet, but it was a command packet, so // we're done processing this packet now return true; @@ -1393,7 +1398,16 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat double besttime; client_t *client; netconn_t *conn; - char *s, *string, response[512], addressstring2[128], stringbuf[16384]; + char *s, *string, response[1400], addressstring2[128], stringbuf[16384]; + + // see if we can identify the sender as a local player + // (this is necessary for rcon to send a reliable reply if the client is + // actually on the server, not sending remotely) + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress)) + break; + if (i == svs.maxclients) + host_client = NULL; if (sv.active) { @@ -1500,7 +1514,9 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); // now set up the client + SV_VM_Begin(); SV_ConnectClient(clientnum, conn); + SV_VM_End(); NetConn_Heartbeat(1); } } @@ -1549,6 +1565,46 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } return true; } + if (length >= 5 && !memcmp(string, "rcon ", 5)) + { + int i; + char *s = string + 5; + char password[64]; + for (i = 0;*s > ' ';s++) + if (i < (int)sizeof(password) - 1) + password[i++] = *s; + password[i] = 0; + if (!strcmp(rcon_password.string, password)) + { + // looks like a legitimate rcon command with the correct password + Con_Printf("server received rcon command from %s:\n%s\n", host_client ? host_client->name : addressstring2, s); + rcon_redirect = true; + rcon_redirect_bufferpos = 0; + Cmd_ExecuteString(s, src_command); + rcon_redirect_buffer[rcon_redirect_bufferpos] = 0; + rcon_redirect = false; + // print resulting text to client + // if client is playing, send a reliable reply instead of + // a command packet + if (host_client) + { + // if the netconnection is loop, then this is the + // local player on a listen mode server, and it would + // result in duplicate printing to the console + // (not that the local player should be using rcon + // when they have the console) + if (host_client->netconnection && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) + SV_ClientPrintf("%s", rcon_redirect_buffer); + } + else + { + // qw print command + dpsnprintf(response, sizeof(response), "\377\377\377\377n%s", rcon_redirect_buffer); + NetConn_WriteString(mysocket, response, peeraddress); + } + } + return true; + } /* if (!strncmp(string, "ping", 4)) { @@ -1654,7 +1710,9 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); SZ_Clear(&net_message); // now set up the client struct + SV_VM_Begin(); SV_ConnectClient(clientnum, conn); + SV_VM_End(); NetConn_Heartbeat(1); } else @@ -1767,12 +1825,11 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } } #endif - for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client) { - if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress)) + if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length)) == 2) { - if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length)) == 2) - SV_ReadClientMessage(); + SV_ReadClientMessage(); return ret; } } diff --git a/netconn.h b/netconn.h index 85841309..cd44ab16 100755 --- a/netconn.h +++ b/netconn.h @@ -291,6 +291,11 @@ extern sizebuf_t net_message; extern cvar_t cl_netlocalping; +extern cvar_t cl_netport; +extern cvar_t sv_netport; +extern cvar_t net_address; +//extern cvar_t net_netaddress_ipv6; + int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data); void NetConn_CloseClientPorts(void); void NetConn_OpenClientPorts(void); @@ -304,6 +309,9 @@ void NetConn_Shutdown(void); netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress); void NetConn_Close(netconn_t *conn); void NetConn_Listen(qboolean state); +int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress); +int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress); +int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress); int NetConn_IsLocalGame(void); void NetConn_ClientFrame(void); void NetConn_ServerFrame(void); diff --git a/sv_user.c b/sv_user.c index d2fafa04..cddaf7f7 100644 --- a/sv_user.c +++ b/sv_user.c @@ -604,10 +604,13 @@ SV_ReadClientMove */ qboolean SV_ReadClientMove (void) { + qboolean kickplayer = false; int i; double oldmovetime; usercmd_t *move = &host_client->cmd; + SV_VM_Begin(); + oldmovetime = move->time; // if this move has been applied, clear it, and start accumulating new data @@ -694,7 +697,7 @@ qboolean SV_ReadClientMove (void) // this fixes the timestamp to prevent a speed cheat from working move->time = sv.time; // but we kick the player for good measure - return true; + kickplayer = true; } else { @@ -711,7 +714,8 @@ qboolean SV_ReadClientMove (void) prog->globals.server->frametime = oldframetime; } } - return false; + SV_VM_End(); + return kickplayer; } void SV_ApplyClientMove (void) -- 2.39.2