/** * @brief Called when the player is totally leaving the server, either willingly * or unwillingly. This is NOT called if the entire server is quitting * or crashing. */ void SV_DropClient (client_t * drop, const char *message) { /* add the disconnect */ struct dbuffer *msg = new_dbuffer(); NET_WriteByte(msg, svc_disconnect); NET_WriteString(msg, message); NET_WriteMsg(drop->stream, msg); SV_BroadcastPrintf(PRINT_CHAT, "%s was dropped from the server - reason: %s\n", drop->name, message); if (drop->state == cs_spawned || drop->state == cs_spawning) { /* call the prog function for removing a client */ /* this will remove the body, among other things */ TH_MutexLock(svs.serverMutex); svs.ge->ClientDisconnect(drop->player); TH_MutexUnlock(svs.serverMutex); } NET_StreamFinished(drop->stream); drop->stream = NULL; drop->player->inuse = qfalse; SV_SetClientState(drop, cs_free); drop->name[0] = 0; if (svs.abandon) { int count = 0; client_t *cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state >= cs_connected) count++; if (count == 0) svs.killserver = qtrue; } }
/** * @sa SV_Spawn_f */ static void SV_Begin_f (client_t *cl) { qboolean began; Com_DPrintf(DEBUG_SERVER, "Begin() from %s\n", cl->name); /* could be abused to respawn or cause spam/other mod-specific problems */ if (cl->state != cs_spawning) { Com_Printf("EXPLOIT: Illegal 'begin' from %s (already spawned), client dropped.\n", cl->name); SV_DropClient(cl, "Illegal begin\n"); return; } /* call the game begin function */ TH_MutexLock(svs.serverMutex); began = svs.ge->ClientBegin(cl->player); TH_MutexUnlock(svs.serverMutex); if (!began) { SV_DropClient(cl, "'begin' failed\n"); return; } SV_SetClientState(cl, cs_began); Cbuf_InsertFromDefer(); }
/** * @sa free_element */ static struct dbuffer_element * allocate_element (void) { struct dbuffer_element *e; TH_MutexLock(dbuf_lock); if (free_elements == 0) { struct dbuffer_element *newBuf = (struct dbuffer_element *)Mem_PoolAlloc(sizeof(struct dbuffer_element), com_genericPool, 0); newBuf->next = free_element_list; free_element_list = newBuf; free_elements++; allocated_elements++; } assert(free_element_list); e = free_element_list; free_element_list = free_element_list->next; assert(free_elements > 0); free_elements--; e->space = DBUFFER_ELEMENT_SIZE; e->len = 0; e->next = NULL; TH_MutexUnlock(dbuf_lock); return e; }
/** * @brief Deallocate a dbuffer * @param[in,out] buf the dbuffer to deallocate * Deallocates a dbuffer, and all memory it uses */ void free_dbuffer (struct dbuffer *buf) { struct dbuffer_element *e; if (!buf) return; while ((e = buf->head)) { buf->head = e->next; free_element(e); } TH_MutexLock(dbuf_lock); buf->next_free = free_dbuffer_list; free_dbuffer_list = buf; free_dbuffers++; /* now we should free them, as we are still having a lot of them allocated */ while (free_dbuffers > DBUFFER_FREE_THRESHOLD) { buf = free_dbuffer_list; free_dbuffer_list = buf->next_free; Mem_Free(buf); free_dbuffers--; allocated_dbuffers--; } TH_MutexUnlock(dbuf_lock); }
/** * @brief Allocate a dbuffer * @return the newly allocated buffer * Allocates a new dbuffer and initialises it to be empty */ struct dbuffer * new_dbuffer (void) { struct dbuffer *buf; TH_MutexLock(dbuf_lock); if (free_dbuffers == 0) { struct dbuffer *newBuf = (struct dbuffer *)Mem_PoolAlloc(sizeof(struct dbuffer), com_genericPool, 0); newBuf->next_free = free_dbuffer_list; free_dbuffer_list = newBuf; free_dbuffers++; allocated_dbuffers++; } assert(free_dbuffer_list); buf = free_dbuffer_list; free_dbuffer_list = free_dbuffer_list->next_free; assert(free_dbuffers > 0); free_dbuffers--; TH_MutexUnlock(dbuf_lock); buf->len = 0; buf->space = DBUFFER_ELEMENT_SIZE; buf->head = buf->tail = allocate_element(); buf->start = buf->end = &buf->head->data[0]; return buf; }
/** * @brief Release the lock on the shared data. */ void ThreadUnlock (void) { if (threadstate.numthreads == 1) { /* do nothing */ } else if (lock != NULL && TH_MutexUnlock(lock) != -1) { /* already locked */ } else { Sys_Error("Couldn't unlock mutex (%p)!", (void*)lock); } }
/** * @brief Thread for the game frame function * @sa SV_RunGameFrame * @sa SV_Frame */ int SV_RunGameFrameThread (void *data) { do { TH_MutexLock(svs.serverMutex); TH_MutexCondWait(svs.serverMutex, svs.gameFrameCond); SV_RunGameFrame(); TH_MutexUnlock(svs.serverMutex); } while (!sv->endgame); return 0; }
/** * @brief Abort the server with a game error * @note The error message should not have a newline - it's added inside of this function */ static void SV_error (const char *fmt, ...) { char msg[1024]; va_list argptr; va_start(argptr, fmt); Q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); /* unlock the game thread loop */ TH_MutexUnlock(svs.serverMutex); Com_Error(ERR_DROP, "Game Error: %s", msg); }
/** * @sa SV_Begin_f */ static void SV_StartMatch_f (client_t *cl) { Com_DPrintf(DEBUG_SERVER, "StartMatch() from %s\n", cl->name); if (cl->state != cs_spawned) { SV_DropClient(cl, "Invalid state\n"); return; } TH_MutexLock(svs.serverMutex); svs.ge->ClientStartMatch(cl->player); TH_MutexUnlock(svs.serverMutex); Cbuf_InsertFromDefer(); }
/** * @sa allocate_element */ static void free_element (struct dbuffer_element *e) { TH_MutexLock(dbuf_lock); e->next = free_element_list; free_element_list = e; free_elements++; while (free_elements > DBUFFER_ELEMENTS_FREE_THRESHOLD) { e = free_element_list; free_element_list = e->next; Mem_Free(e); free_elements--; allocated_elements--; } TH_MutexUnlock(dbuf_lock); }
/** * @sa SV_ExecuteClientMessage */ static void SV_ExecuteUserCommand (client_t * cl, const char *s) { const ucmd_t *u; Cmd_TokenizeString(s, qfalse); for (u = ucmds; u->name; u++) if (Q_streq(Cmd_Argv(0), u->name)) { Com_DPrintf(DEBUG_SERVER, "SV_ExecuteUserCommand: %s\n", s); u->func(cl); return; } if (Com_ServerState() == ss_game) { Com_DPrintf(DEBUG_SERVER, "SV_ExecuteUserCommand: client command: %s\n", s); TH_MutexLock(svs.serverMutex); svs.ge->ClientCommand(cl->player); TH_MutexUnlock(svs.serverMutex); } }
/** * @brief Pull specific info from a newly changed userinfo string into a more C friendly form. */ void SV_UserinfoChanged (client_t * cl) { unsigned int i; /* call prog code to allow overrides */ TH_MutexLock(svs.serverMutex); svs.ge->ClientUserinfoChanged(cl->player, cl->userinfo); TH_MutexUnlock(svs.serverMutex); /* name of the player */ Q_strncpyz(cl->name, Info_ValueForKey(cl->userinfo, "cl_name"), sizeof(cl->name)); /* mask off high bit */ for (i = 0; i < sizeof(cl->name); i++) cl->name[i] &= 127; /* msg command */ cl->messagelevel = Info_IntegerForKey(cl->userinfo, "cl_msg"); Com_DPrintf(DEBUG_SERVER, "SV_UserinfoChanged: Changed userinfo for player %s\n", cl->name); }
/** * @brief Change the server to a new map, taking all connected clients along with it. * @note the full syntax is: @code map [day|night] [+]<map> [<assembly>] @endcode * @sa SV_AssembleMap * @sa CM_LoadMap * @sa Com_SetServerState */ void SV_Map (qboolean day, const char *levelstring, const char *assembly) { int i; unsigned checksum = 0; char * map = SV_GetConfigString(CS_TILES); char * pos = SV_GetConfigString(CS_POSITIONS); mapInfo_t *randomMap = NULL; client_t *cl; /* any partially connected client will be restarted */ Com_SetServerState(ss_restart); /* the game is just starting */ SV_InitGame(); if (!svs.initialized) { Com_Printf("Could not spawn the server\n"); return; } assert(levelstring[0] != '\0'); Com_DPrintf(DEBUG_SERVER, "SpawnServer: %s\n", levelstring); /* save name for levels that don't set message */ SV_SetConfigString(CS_NAME, levelstring); SV_SetConfigString(CS_LIGHTMAP, day); Q_strncpyz(sv->name, levelstring, sizeof(sv->name)); /* set serverinfo variable */ sv_mapname = Cvar_FullSet("sv_mapname", sv->name, CVAR_SERVERINFO | CVAR_NOSET); /* notify the client in case of a listening server */ SCR_BeginLoadingPlaque(); if (assembly) Q_strncpyz(sv->assembly, assembly, sizeof(sv->assembly)); else sv->assembly[0] = '\0'; /* leave slots at start for clients only */ cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) { /* needs to reconnect */ if (cl->state >= cs_spawning) SV_SetClientState(cl, cs_connected); } /* assemble and load the map */ if (levelstring[0] == '+') { randomMap = SV_AssembleMap(levelstring + 1, assembly, map, pos, 0); if (!randomMap) { Com_Printf("Could not load assembly for map '%s'\n", levelstring); return; } } else { SV_SetConfigString(CS_TILES, levelstring); SV_SetConfigString(CS_POSITIONS, assembly ? assembly : ""); } CM_LoadMap(map, day, pos, &sv->mapData, &sv->mapTiles); Com_Printf("checksum for the map '%s': %u\n", levelstring, sv->mapData.mapChecksum); SV_SetConfigString(CS_MAPCHECKSUM, sv->mapData.mapChecksum); checksum = Com_GetScriptChecksum(); Com_Printf("ufo script checksum %u\n", checksum); SV_SetConfigString(CS_UFOCHECKSUM, checksum); SV_SetConfigString(CS_OBJECTAMOUNT, csi.numODs); SV_SetConfigString(CS_VERSION, UFO_VERSION); SV_SetConfigString(CS_MAPTITLE, SV_GetMapTitle(randomMap, levelstring)); if (Q_strstart(SV_GetConfigString(CS_MAPTITLE), "b/")) { /* For base attack, CS_MAPTITLE contains too many chars */ SV_SetConfigString(CS_MAPTITLE, "Base attack"); SV_SetConfigString(CS_NAME, ".baseattack"); } /* clear random-map assembly data */ Mem_Free(randomMap); randomMap = NULL; /* clear physics interaction links */ SV_ClearWorld(); /* fix this! */ for (i = 1; i <= sv->mapData.numInline; i++) sv->models[i] = CM_InlineModel(&sv->mapTiles, va("*%i", i)); /* precache and static commands can be issued during map initialization */ Com_SetServerState(ss_loading); TH_MutexLock(svs.serverMutex); /* load and spawn all other entities */ svs.ge->SpawnEntities(sv->name, SV_GetConfigStringInteger(CS_LIGHTMAP), sv->mapData.mapEntityString); TH_MutexUnlock(svs.serverMutex); /* all precaches are complete */ Com_SetServerState(ss_game); Com_Printf("-------------------------------------\n"); Cbuf_CopyToDefer(); }
/** * @brief The current net_message is parsed for the given client */ void SV_ExecuteClientMessage (client_t * cl, int cmd, struct dbuffer *msg) { if (cmd == -1) return; switch (cmd) { default: Com_Printf("SV_ExecuteClientMessage: unknown command char '%d'\n", cmd); SV_DropClient(cl, "Unknown command\n"); return; case clc_nop: break; case clc_ack: cl->lastmessage = svs.realtime; break; case clc_userinfo: NET_ReadString(msg, cl->userinfo, sizeof(cl->userinfo)); Com_DPrintf(DEBUG_SERVER, "userinfo from client: %s\n", cl->userinfo); SV_UserinfoChanged(cl); break; case clc_stringcmd: { char str[1024]; NET_ReadString(msg, str, sizeof(str)); Com_DPrintf(DEBUG_SERVER, "stringcmd from client: %s\n", str); SV_ExecuteUserCommand(cl, str); if (cl->state == cs_free) return; /* disconnect command */ break; } case clc_action: /* client actions are handled by the game module */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientAction(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_endround: /* player wants to end round */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientEndRound(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_teaminfo: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientTeamInfo(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; SV_SetClientState(cl, cs_spawned); break; case clc_initactorstates: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientInitActorStates(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; } }
/** * @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; qboolean connected; char buf[256]; const char *peername = NET_StreamPeerToName(stream, buf, sizeof(buf), qfalse); 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; TH_MutexLock(svs.serverMutex); connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo)); TH_MutexUnlock(svs.serverMutex); /* 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 = qtrue; 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); }