/** * @brief This function allows you to send network commands from commandline * @note This function is only for debugging and testing purposes * It is dangerous to leave this activated in final releases * packet [destination] [contents] * Contents allows \n escape character */ static void CL_Packet_f (void) { if (Cmd_Argc() != 4) { Com_Printf("Usage: %s <destination> <port> <contents>\n", Cmd_Argv(0)); return; } struct net_stream* s = NET_Connect(Cmd_Argv(1), Cmd_Argv(2)); if (!s) { Com_Printf("Could not connect to %s at port %s\n", Cmd_Argv(1), Cmd_Argv(2)); return; } const char* in = Cmd_Argv(3); const int l = strlen(in); char buf[MAX_STRING_TOKENS]; char* out = buf; for (int i = 0; i < l; i++) { if (in[i] == '\\' && in[i + 1] == 'n') { *out++ = '\n'; i++; } else { *out++ = in[i]; } } *out = 0; NET_OOB_Printf(s, "%s %i", out, PROTOCOL_VERSION); NET_StreamFinished(s); }
/** * @note Only call @c CL_Connect if there is no connection yet (@c cls.netStream is @c nullptr) * @sa CL_Disconnect * @sa CL_SendChangedUserinfos */ static void CL_Connect (void) { Com_SetUserinfoModified(false); assert(!cls.netStream); if (cls.servername[0] != '\0') { assert(cls.serverport[0] != '\0'); Com_Printf("Connecting to %s %s...\n", cls.servername, cls.serverport); cls.netStream = NET_Connect(cls.servername, cls.serverport, CL_FreeClientStream); } else { Com_Printf("Connecting to localhost...\n"); cls.netStream = NET_ConnectToLoopBack(CL_FreeClientStream); } if (cls.netStream) { char info[MAX_INFO_STRING]; NET_OOB_Printf(cls.netStream, SV_CMD_CONNECT " %i \"%s\"\n", PROTOCOL_VERSION, Cvar_Userinfo(info, sizeof(info))); cls.connectTime = CL_Milliseconds(); } else { if (cls.servername[0] != '\0') { assert(cls.serverport[0]); Com_Printf("Could not connect to %s %s\n", cls.servername, cls.serverport); } else { Com_Printf("Could not connect to localhost\n"); } } }
/** * @brief End the redirection of packets/output * @sa Com_BeginRedirect */ void Com_EndRedirect (void) { NET_OOB_Printf(rd_stream, SV_CMD_PRINT "\n%s", rd_buffer); rd_stream = nullptr; rd_buffer = nullptr; rd_buffersize = 0; }
/** * @brief Responds with short info for broadcast scans * @note The second parameter should be the current protocol version number. * @note Only a short server description - the user can determine whether he is * interested in a full status * @sa CL_ParseStatusMessage * @sa CL_ProcessPingReply */ static void SVC_Info (struct net_stream* s) { if (SVC_RateLimitAddress(*s)) { Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s)); return; } /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */ if (SVC_RateLimit(&outboundLeakyBucket)) { Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit exceeded, dropping request\n"); return; } if (sv_maxclients->integer == 1) { Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n"); return; /* ignore in single player */ } const int version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { char string[MAX_VAR]; Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION); NET_OOB_Printf(s, SV_CMD_PRINT "\n%s", string); return; } int count = 0; client_t* cl = nullptr; while ((cl = SV_GetNextClient(cl)) != nullptr) if (cl->state >= cs_spawning) count++; char infostring[MAX_INFO_STRING]; infostring[0] = '\0'; Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION)); Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name); Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count); Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION); NET_OOB_Printf(s, SV_CMD_INFO "\n%s", infostring); }
/** * @brief Responds with short info for broadcast scans * @note The second parameter should be the current protocol version number. * @note Only a short server description - the user can determine whether he is * interested in a full status * @sa CL_ParseStatusMessage * @sa CL_ProcessPingReply */ static void SVC_Info (struct net_stream *s) { int version; if (sv_maxclients->integer == 1) { Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n"); return; /* ignore in single player */ } version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { char string[MAX_VAR]; Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION); NET_OOB_Printf(s, "print\n%s", string); } else { client_t *cl; char infostring[MAX_INFO_STRING]; int count = 0; cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state >= cs_spawning) count++; infostring[0] = '\0'; Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION)); Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name); Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count); Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string); Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION); NET_OOB_Printf(s, "info\n%s", infostring); } }
/** * @note Both client and server can use this, and it will output * to the appropriate place. */ void Com_vPrintf (const char* fmt, va_list ap) { char msg[MAXPRINTMSG]; Q_vsnprintf(msg, sizeof(msg), fmt, ap); /* redirect the output? */ if (rd_buffer) { if ((strlen(msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { NET_OOB_Printf(rd_stream, SV_CMD_PRINT "\n%s", rd_buffer); rd_buffer[0] = '\0'; } Q_strcat(rd_buffer, sizeof(char) * rd_buffersize, "%s", msg); return; } Con_Print(msg); /* also echo to debugging console */ Sys_ConsoleOutput(msg); /* logfile */ if (logfile_active && logfile_active->integer) { if (!logfile.f) { if (logfile_active->integer > 2) FS_OpenFile(consoleLogName, &logfile, FILE_APPEND); else FS_OpenFile(consoleLogName, &logfile, FILE_WRITE); } if (logfile.f) { /* strip color codes */ const char* output = msg; if (output[strlen(output) - 1] == '\n') { char timestamp[40]; Com_MakeTimestamp(timestamp, sizeof(timestamp)); FS_Write(timestamp, strlen(timestamp), &logfile); FS_Write(" ", 1, &logfile); } FS_Write(output, strlen(output), &logfile); if (logfile_active->integer > 1) fflush(logfile.f); /* force it to save every time */ } } }
/** * @brief A connection request that did not come from the master * @sa CL_ConnectionlessPacket */ static void SVC_DirectConnect (struct net_stream *stream) { char userinfo[MAX_INFO_STRING]; client_t *cl; player_t *player; int playernum; int version; bool connected; char buf[256]; const char *peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false); Com_DPrintf(DEBUG_SERVER, "SVC_DirectConnect()\n"); if (sv->started || sv->spawned) { Com_Printf("rejected connect because match is already running\n"); NET_OOB_Printf(stream, "print\nGame has started already.\n"); return; } version = atoi(Cmd_Argv(1)); if (version != PROTOCOL_VERSION) { Com_Printf("rejected connect from version %i - %s\n", version, peername); NET_OOB_Printf(stream, "print\nServer is version %s.\n", UFO_VERSION); return; } Q_strncpyz(userinfo, Cmd_Argv(2), sizeof(userinfo)); if (userinfo[0] == '\0') { /* catch empty userinfo */ Com_Printf("Empty userinfo from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } if (strchr(userinfo, '\xFF')) { /* catch end of message in string exploit */ Com_Printf("Illegal userinfo contained xFF from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } if (strlen(Info_ValueForKey(userinfo, "ip"))) { /* catch spoofed ips */ Com_Printf("Illegal userinfo contained ip from %s\n", peername); NET_OOB_Printf(stream, "print\nConnection refused.\n"); return; } /* force the IP key/value pair so the game can filter based on ip */ Info_SetValueForKey(userinfo, sizeof(userinfo), "ip", peername); /* find a client slot */ cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state == cs_free) break; if (cl == NULL) { NET_OOB_Printf(stream, "print\nServer is full.\n"); Com_Printf("Rejected a connection - server is full.\n"); return; } /* build a new connection - accept the new client * this is the only place a client_t is ever initialized */ OBJZERO(*cl); playernum = cl - SV_GetClient(0); player = PLAYER_NUM(playernum); cl->player = player; cl->player->num = playernum; { const ScopedMutex scopedMutex(svs.serverMutex); connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo)); } /* get the game a chance to reject this connection or modify the userinfo */ if (!connected) { const char *rejmsg = Info_ValueForKey(userinfo, "rejmsg"); if (rejmsg[0] != '\0') { NET_OOB_Printf(stream, "print\n%s\nConnection refused.\n", rejmsg); Com_Printf("Game rejected a connection from %s. Reason: %s\n", peername, rejmsg); } else { NET_OOB_Printf(stream, "print\nConnection refused.\n"); Com_Printf("Game rejected a connection from %s.\n", peername); } return; } /* new player */ cl->player->inuse = true; cl->lastmessage = svs.realtime; /* parse some info from the info strings */ strncpy(cl->userinfo, userinfo, sizeof(cl->userinfo) - 1); SV_UserinfoChanged(cl); /* send the connect packet to the client */ if (sv_http_downloadserver->string[0]) NET_OOB_Printf(stream, "client_connect dlserver=%s", sv_http_downloadserver->string); else NET_OOB_Printf(stream, "client_connect"); SV_SetClientState(cl, cs_connected); Q_strncpyz(cl->peername, peername, sizeof(cl->peername)); cl->stream = stream; NET_StreamSetData(stream, cl); }
/** * @brief Responses to broadcasts, etc * @sa CL_ReadPackets * @sa CL_Frame * @sa SVC_DirectConnect * @param[in,out] msg The client stream message buffer to read from */ static void CL_ConnectionlessPacket (dbuffer* msg) { char s[512]; NET_ReadStringLine(msg, s, sizeof(s)); Cmd_TokenizeString(s, false); const char* c = Cmd_Argv(0); Com_DPrintf(DEBUG_CLIENT, "server OOB: %s (%s)\n", c, Cmd_Args()); /* server connection */ if (Q_streq(c, CL_CMD_CLIENT_CONNECT)) { int i; for (i = 1; i < Cmd_Argc(); i++) { if (char const* const p = Q_strstart(Cmd_Argv(i), "dlserver=")) { Com_sprintf(cls.downloadReferer, sizeof(cls.downloadReferer), "ufo://%s", cls.servername); CL_SetHTTPServer(p); if (cls.downloadServer[0]) Com_Printf("HTTP downloading enabled, URL: %s\n", cls.downloadServer); } } if (cls.state == ca_connected) { Com_Printf("Dup connect received. Ignored.\n"); return; } dbuffer buf(5); NET_WriteByte(&buf, clc_stringcmd); NET_WriteString(&buf, NET_STATE_NEW "\n"); NET_WriteMsg(cls.netStream, buf); GAME_InitMissionBriefing(_("Loading")); return; } /* remote command from gui front end */ if (Q_streq(c, CL_CMD_COMMAND)) { if (!NET_StreamIsLoopback(cls.netStream)) { Com_Printf("Command packet from remote host. Ignored.\n"); return; } else { char str[512]; NET_ReadString(msg, str, sizeof(str)); Cbuf_AddText("%s\n", str); } return; } /* ping from server */ if (Q_streq(c, CL_CMD_PING)) { NET_OOB_Printf(cls.netStream, SV_CMD_ACK); return; } /* echo request from server */ if (Q_streq(c, CL_CMD_ECHO)) { NET_OOB_Printf(cls.netStream, "%s", Cmd_Argv(1)); return; } /* print */ if (Q_streq(c, SV_CMD_PRINT)) { NET_ReadString(msg, popupText, sizeof(popupText)); /* special reject messages needs proper handling */ if (strstr(popupText, REJ_PASSWORD_REQUIRED_OR_INCORRECT)) { UI_PushWindow("serverpassword"); if (Q_strvalid(Cvar_GetString("password"))) { Cvar_Set("password", ""); CL_Drop(); UI_Popup(_("Connection failure"), _("The password you specified was wrong.")); } else { CL_Drop(); UI_Popup(_("Connection failure"), _("This server requires a password.")); } } else if (strstr(popupText, REJ_SERVER_FULL)) { CL_Drop(); UI_Popup(_("Connection failure"), _("This server is full.")); } else if (strstr(popupText, REJ_BANNED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("You are banned on this server.")); } else if (strstr(popupText, REJ_GAME_ALREADY_STARTED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The game has already started.")); } else if (strstr(popupText, REJ_SERVER_VERSION_MISMATCH)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The server is running a different version of the game.")); } else if (strstr(popupText, BAD_RCON_PASSWORD)) { Cvar_Set("rcon_password", ""); UI_Popup(_("Bad rcon password"), _("The rcon password you specified was wrong.")); } else if (strstr(popupText, REJ_CONNECTION_REFUSED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The server refused the connection.")); } else if (Q_strvalid(popupText)) { UI_Popup(_("Notice"), _(popupText)); } return; } if (!GAME_HandleServerCommand(c, msg)) Com_Printf("Unknown command received \"%s\"\n", c); }