예제 #1
0
/**
 * @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);
}
예제 #2
0
/**
 * @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");
		}
	}
}
예제 #3
0
/**
 * @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;
}
예제 #4
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);
}
예제 #5
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)
{
	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);
	}
}
예제 #6
0
/**
 * @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 */
		}
	}
}
예제 #7
0
/**
 * @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);
}
예제 #8
0
/**
 * @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);
}