Example #1
0
File: tm.c Project: illwieckz/qstat
query_status_t deal_with_tm_packet( struct qserver *server, char *rawpkt, int pktlen )
{
	char *s;
	char *pkt = rawpkt;
	char *key = NULL, *value = NULL, *tmpp = NULL;
	char fullname[256];
	struct player *player = NULL;
	int pkt_max = server->saved_data.pkt_max;
	unsigned total_len, expected_len;
	int method_response = 1;
	debug( 2, "processing..." );

	s = rawpkt;

	// We may get the setup handle and the protocol version in one packet we may not
	// So we continue to parse if we see the handle
	if ( 4 <= pktlen && 0 == memcmp( pkt, "\x0b\x00\x00\x00", 4 ) )
	{
		// setup handle identifier
		// greater 2^31 = XML-RPC, less = callback
		server->challenge = 0x80000001;
		if ( 4 == pktlen )
		{
			return 0;
		}
		pktlen -= 4;
		pkt += 4;
	}

	if ( 11 <= pktlen && 1 == sscanf( pkt, "GBXRemote %d", &server->protocol_version ) )
	{
		// Got protocol version send request
		send_tm_request_packet( server );
		return 0;
	}

	if ( 8 <= pktlen && 0 == memcmp( pkt+4, &server->challenge, 4 ) )
	{
		// first 4 bytes = the length
		// Note: We use pkt_id to store the length of the expected packet
		// this could cause loss but very unlikely
		unsigned long len;
		memcpy( &len, rawpkt, 4 );

		// second 4 bytes = handle identifier we sent in the request
		if ( 8 == pktlen )
		{
			// split packet
			// we have at least one more packet coming
			if ( ! add_packet( server, len, 0, 2, pktlen, rawpkt, 1 ) )
			{
				// fatal error e.g. out of memory
				return -1;
			}
			return 0;
		}
		else
		{
			// ensure the length is stored
			server->saved_data.pkt_id = (int)len;
			s += 8;
		}
	}

	total_len = combined_length( server, server->saved_data.pkt_id );
	expected_len = server->saved_data.pkt_id;
	debug( 2, "total: %d, expected: %d\n", total_len, expected_len );
	if ( total_len < expected_len + 8 )
	{
		// we dont have a complete response add the packet
		int last, new_max;
		if ( total_len + pktlen >= expected_len + 8 )
		{
			last = 1;
			new_max = pkt_max;
		}
		else
		{
			last = 0;
			new_max = pkt_max + 1;
		}

		if ( ! add_packet( server, server->saved_data.pkt_id, pkt_max - 1, new_max, pktlen, rawpkt, 1 ) )
		{
			// fatal error e.g. out of memory
			return -1;
		}

		if ( last )
		{
			// we are the last packet run combine to call us back
			return combine_packets( server );
		}
		return 0;
	}

	server->n_servers++;
	if ( server->server_name == NULL)
	{
		server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );
	}
	else
	{
		gettimeofday( &server->packet_time1, NULL);
	}

	// Terminate the packet data
	pkt = (char*)malloc( pktlen + 1 );
	if ( NULL == pkt )
	{
		debug( 0, "Failed to malloc memory for packet terminator\n" );
		return MEM_ERROR;
	}
	memcpy( pkt, rawpkt, pktlen );
	pkt[pktlen] = '\0';

//fprintf( stderr, "S=%s\n", s );
	s = strtok( pkt + 8, "\015\012" );
	while ( NULL != s )
	{
//fprintf( stderr, "S=%s\n", s );
		if ( 0 == strncmp( s, "<member><name>", 14 ) )
		{
			key = s + 14;
			tmpp = strstr( key, "</name>" );
			if ( NULL != tmpp )
			{
				*tmpp = '\0';
			}
			s = strtok( NULL, "\015\012" );
			value = NULL;
			continue;
		}
		else if ( NULL != key && 0 == strncmp( s, "<value>", 7 ) )
		{
			// value
			s += 7;
			if ( 0 == strncmp( s, "<string>", 8 ) )
			{
				// String
				value = s+8;
				tmpp = strstr( s, "</string>" );
			}
			else if ( 0 == strncmp( s, "<i4>", 4 ) )
			{
				// Int
				value = s+4;
				tmpp = strstr( s, "</i4>" );
			}
			else if ( 0 == strncmp( s, "<boolean>", 9 ) )
			{
				// Boolean
				value = s+9;
				tmpp = strstr( s, "</boolean>" );
			}
			else if ( 0 == strncmp( s, "<double>", 8 ) )
			{
				// Double
				value = s+8;
				tmpp = strstr( s, "</double>" );
			}
			// also have struct and array but not interested in those

			if ( NULL != tmpp )
			{
				*tmpp = '\0';
			}

			if ( NULL != value )
			{
				debug( 4, "%s = %s\n", key, value );
			}
		}
		else if ( 0 == strncmp( s, "</struct>", 9 ) && 3 > method_response )
		{
			// end of method response
			method_response++;
		}

		if ( NULL != value && NULL != key )
		{
			switch( method_response )
			{
			case 1:
				// GetServerOptions response
				if ( 0 == strcmp( "Name", key ) )
				{
					server->server_name = strdup( value );
				}
				else if ( 0 == strcmp( "CurrentMaxPlayers", key ) )
				{
					server->max_players = atoi( value );
				}
				else
				{
					sprintf( fullname, "server.%s", key );
					add_rule( server, fullname, value, NO_FLAGS);
				}
				break;
			case 2:
				// GetCurrentChallengeInfo response
				if ( 0 == strcmp( "Name", key ) )
				{
					server->map_name = strdup( value );
				}
				else
				{
					sprintf( fullname, "challenge.%s", key );
					add_rule( server, fullname, value, NO_FLAGS);
				}
				break;
			case 3:
				// GetPlayerList response
				// Player info
				if ( 0 == strcmp( "Login", key ) )
				{
					player = add_player( server, server->n_player_info );
					server->num_players++;
				}
				else if ( NULL != player )
				{
					if ( 0 == strcmp( "NickName", key ) )
					{
						player->name = strdup( value );
					}
					else if ( 0 == strcmp( "PlayerId", key ) )
					{
						//player->number = atoi( value );
					}
					else if ( 0 == strcmp( "TeamId", key ) )
					{
						player->team = atoi( value );
					}
					else if ( 0 == strcmp( "IsSpectator", key ) )
					{
						player->flags = player->flags & 1;
					}
					else if ( 0 == strcmp( "IsInOfficialMode", key ) )
					{
						player->flags = player->flags & 2;
					}
					else if ( 0 == strcmp( "LadderRanking", key ) )
					{
						player->score = atoi( value );
					}
				}
				break;
			}
			value = NULL;
		}
		s = strtok( NULL, "\015\012" );
	}

	free( pkt );

	if ( 0 == strncmp( rawpkt + pktlen - 19, "</methodResponse>", 17 ) )
	{
		// last packet seen
		return DONE_FORCE;
	}

	return INPROGRESS;
}
Example #2
0
query_status_t
deal_with_crysis_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	char *s, *val, *line;
	query_status_t state = INPROGRESS;

	debug(2, "processing...");

	if (!server->combined) {
		state = valid_crysis_response(server, rawpkt, pktlen);
		server->retry1 = n_retries;
		if (0 == server->n_requests) {
			server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
			server->n_requests++;
		}

		switch (state) {
		case INPROGRESS:
		{
			// response fragment recieved
			int pkt_id;
			int pkt_max;

			// We're expecting more to come
			debug(5, "fragment recieved...");
			pkt_id = packet_count(server);
			pkt_max = pkt_id++;
			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
				// fatal error e.g. out of memory
				return (MEM_ERROR);
			}

			// combine_packets will call us recursively
			return (combine_packets(server));
		}

		case DONE_FORCE:
			break;  // single packet response fall through

		default:
			return (state);
		}
	}

	if (DONE_FORCE != state) {
		state = valid_crysis_response(server, rawpkt, pktlen);
		switch (state) {
		case DONE_FORCE:
			break;  // actually process

		default:
			return (state);
		}
	}

	debug(3, "packet: challenge = %ld", server->challenge);
	s = NULL;
	switch (server->challenge) {
	case 1:
		s = crysis_response(server, rawpkt, pktlen);
		if (NULL != s) {
			server->challenge_string = s;
			return (send_crysis_request_packet(server));
		}
		return (REQ_ERROR);

	case 2:
		s = crysis_response(server, rawpkt, pktlen);
		if (NULL == s) {
			return (REQ_ERROR);
		}
		if (0 != strncmp(s, "authorized", 10)) {
			free(s);
			return (REQ_ERROR);
		}
		free(s);
		return (send_crysis_request_packet(server));

	case 3:
		s = crysis_response(server, rawpkt, pktlen);
		if (NULL == s) {
			return (REQ_ERROR);
		}
	}

	// Correct ping
	// Not quite right but gives a good estimate
	server->ping_total = (server->ping_total * server->n_requests) / 2;

	debug(3, "processing response...");

	s = decode_crysis_val(s);
	line = strtok(s, "\012");

	// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
	while (NULL != line) {
		debug(4, "LINE: %s\n", line);
		val = strstr(line, ":");
		if (NULL != val) {
			*val = '\0';
			val += 2;
			debug(4, "var: %s, val: %s", line, val);
			if (0 == strcmp("name", line)) {
				server->server_name = strdup(val);
			} else if (0 == strcmp("level", line)) {
				server->map_name = strdup(val);
			} else if (0 == strcmp("players", line)) {
				if (2 == sscanf(val, "%d/%d", &server->num_players, &server->max_players)) {
				}
			} else if (
				(0 == strcmp("version", line)) ||
				(0 == strcmp("gamerules", line)) ||
				(0 == strcmp("time remaining", line))
				) {
				add_rule(server, line, val, NO_FLAGS);
			}
		}

		line = strtok(NULL, "\012");
	}

	gettimeofday(&server->packet_time1, NULL);

	return (DONE_FORCE);
}
Example #3
0
query_status_t
deal_with_ts3_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	char *s, *player_name = "unknown";
	int valid_response = 0, mode = 0, all_servers = 0;
	char last_char;
	unsigned short port = 0, down = 0, auth_seen = 0;

	debug(2, "processing...");

	if (0 == pktlen) {
		// Invalid password
		return (REQ_ERROR);
	}

	last_char = rawpkt[pktlen - 1];
	rawpkt[pktlen - 1] = '\0';
	s = rawpkt;
	all_servers = all_ts3_servers(server);

	debug(3, "packet: combined = %d, challenge = %ld, n_servers = %d", server->combined, server->challenge, server->n_servers);
	if (!server->combined) {
		server->retry1 = n_retries;
		if (0 == server->n_requests) {
			server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
			server->n_requests++;
		}

		if (server->n_servers >= server->challenge) {
			// response fragment recieved
			int pkt_id;
			int pkt_max;

			// We're expecting more to come
			debug(5, "fragment recieved...");
			pkt_id = packet_count(server);
			pkt_max = pkt_id + 1;
			rawpkt[pktlen - 1] = last_char; // restore the last character
			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
				// fatal error e.g. out of memory
				return (MEM_ERROR);
			}

			// combine_packets will call us recursively
			return (combine_packets(server));
		}
	} else {
		valid_response = valid_ts3_response(server, rawpkt + server->master_pkt_len, pktlen - server->master_pkt_len);

		debug(2, "combined packet: valid_response: %d, challenge: %ld, n_servers: %d, offset: %d", valid_response, server->challenge, server->n_servers, server->master_pkt_len);

		if (0 > valid_response) {
			// Error occured
			return (valid_response);
		}

		server->challenge += valid_response;

		if (valid_response) {
			// Got a valid response, send the next request
			int ret = send_ts3_request_packet(server);
			if (0 != ret) {
				// error sending packet
				debug(4, "Request failed: %d", ret);
				return (ret);
			}
		}

		if (server->n_servers > server->challenge) {
			// recursive call which is still incomplete
			return (INPROGRESS);
		}
	}

	// Correct ping
	// Not quite right but gives a good estimate
	server->ping_total = (server->ping_total * server->n_requests) / 2;

	debug(3, "processing response...");

	s = strtok(rawpkt, "\012\015 |");

	// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
	while (NULL != s) {
		debug(4, "LINE: %d, %s\n", mode, s);
		switch (mode) {
		case 0:
			// prompt, use or serverlist response
			if (0 == strcmp("TS3", s)) {
				// nothing to do unless in all servers mode
				if (1 == all_servers) {
					mode++;
				}
			} else if (0 == strncmp("error", s, 5)) {
				// end of use response
				mode++;
			}
			break;

		case 1:
			// serverinfo or serverlist response including condition authentication
			if ((0 == auth_seen) && (0 != strlen(get_param_value(server, "password", ""))) && (0 == strncmp("error", s, 5))) {
				// end of auth response
				auth_seen = 1;
			} else if (0 == strncmp("error", s, 5)) {
				// end of serverinfo response
				mode++;
			} else {
				// Server Rule
				char *key = s;
				char *value = strchr(key, '=');
				if (NULL != value) {
					*value = '\0';
					value++;
					debug(6, "Rule: %s = %s\n", key, value);
					if (0 == strcmp("virtualserver_name", key)) {
						if (1 == all_servers) {
							struct qserver *new_server = add_qserver_byaddr(ntohl(server->ipaddr), port, server->type, NULL);
							if (NULL != new_server) {
								if (down) {
									// Status indicates this server is actually offline
									new_server->server_name = DOWN;
								} else {
									new_server->max_players = server->max_players;
									new_server->num_players = server->num_players;
									new_server->server_name = strdup(decode_ts3_val(value));
									new_server->map_name = strdup("N/A");
									new_server->ping_total = server->ping_total;
									new_server->n_requests = server->n_requests;
								}
								cleanup_qserver(new_server, FORCE);
							}
							down = 0;
						} else {
							server->server_name = strdup(decode_ts3_val(value));
						}
					} else if (0 == strcmp("virtualserver_port", key)) {
						port = atoi(value);
						change_server_port(server, port, 0);
						add_rule(server, key, value, NO_FLAGS);
					} else if (0 == strcmp("virtualserver_maxclients", key)) {
						server->max_players = atoi(value);
					} else if (0 == strcmp("virtualserver_clientsonline", key)) {
						server->num_players = atoi(value);
					} else if (0 == strcmp("virtualserver_queryclientsonline", key)) {
						// clientsonline includes queryclientsonline so remove these from our count
						server->num_players -= atoi(value);
					} else if ((0 == strcmp("virtualserver_status", key)) && (0 != strcmp("online", value))) {
						// Server is actually offline to client so display as down
						down = 1;
						if (1 != all_servers) {
							server->server_name = DOWN;
							//server->saved_data.pkt_index = 0;
							return (DONE_FORCE);
						}
					} else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) {
						// Ignore details from the response code
					} else if (1 != all_servers) {
						add_rule(server, key, value, NO_FLAGS);
					}
				}
			}
			break;

		case 2:
			// clientlist response
			if (0 == strncmp("error", s, 5)) {
				// end of serverinfo response
				mode++;
			} else {
				// Client
				char *key = s;
				char *value = strchr(key, '=');
				if (NULL != value) {
					*value = '\0';
					value++;
					debug(6, "Player: %s = %s\n", key, value);
					if (0 == strcmp("client_nickname", key)) {
						player_name = value;
					} else if (0 == strcmp("clid", key)) {
					} else if ((0 == strcmp("client_type", key)) && (0 == strcmp("0", value))) {
						struct player *player = add_player(server, server->n_player_info);
						if (NULL != player) {
							player->name = strdup(decode_ts3_val(player_name));
						}
					} else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) {
						// Ignore details from the response code
					}
				}
			}
			break;
		}
		s = strtok(NULL, "\012\015 |");
	}

	gettimeofday(&server->packet_time1, NULL);

	server->map_name = strdup("N/A");
	return (DONE_FORCE);
}
Example #4
0
query_status_t
deal_with_dirtybomb_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	unsigned char *s, *l, pkt_id, pkt_max;

	debug(2, "processing...");
	
	if (8 > pktlen) {
		// not valid response
		malformed_packet(server, "packet too small");
		return PKT_ERROR;
	}

	if (rawpkt[4] != 0x01) {
		// not a known response
		malformed_packet(server, "unknown packet type 0x%02hhx", rawpkt[4]);
		return PKT_ERROR;
	}

	// Response ID - long
	// Response Type - byte
	// Current Packet Number - byte
	pkt_id = rawpkt[5];

	// Last Packet Number - byte
	pkt_max = rawpkt[6];

	// Payload Blob - msgpack map
	s = (unsigned char*)rawpkt + 7;
	
	if (!server->combined) {
		int pkt_cnt = packet_count(server);

		server->retry1 = n_retries;
		if (0 == server->n_requests) {
			server->ping_total = time_delta( &packet_recv_time, &server->packet_time1 );
			server->n_requests++;
		}

		if (pkt_cnt < pkt_max) {
			// We're expecting more to come
			debug( 5, "fragment recieved..." );

			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
				// fatal error e.g. out of memory
				return MEM_ERROR;
			}

			// combine_packets will call us recursively
			return combine_packets(server);
		}
	}

	// Correct ping
	// Not quite right but gives a good estimate
	server->ping_total = (server->ping_total * server->n_requests) / 2;

	debug(3, "processing response...");

	gettimeofday(&server->packet_time1, NULL);

	l = (unsigned char *)rawpkt + pktlen - 1;
	if (!unpack_msgpack(server, s, l)) {
		return PKT_ERROR;
	}

	return DONE_FORCE;
}
Example #5
0
query_status_t deal_with_fl_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	struct fl_status* status = (struct fl_status*)server->master_query_tag;
	char* pkt = rawpkt;
	char buf[16];
	char* str;
	unsigned cnt;
	unsigned short tmp_short;

	if(server->server_name == NULL)
	{
		server->ping_total += time_delta( &packet_recv_time, &server->packet_time1);
		server->n_requests++;
	}

	if(pktlen < 5) goto out_too_short;

	if( 0 == memcmp(pkt, "\xFF\xFF\xFF\xFE", 4) )
	{
		// fragmented packet
		unsigned char pkt_index, pkt_max;
		unsigned int pkt_id;
		SavedData *sdata;

		if(pktlen < 9) goto out_too_short;

		// format:
		// int Header
		// int RequestId
		// byte PacketNumber
		// byte NumPackets
		// Short SizeOfPacketSplits

		// Header
		pkt += 4;

		// RequestId
		pkt_id = ntohl( *(long *)pkt );
		debug( 3, "RequestID: %d", pkt_id );
		pkt += 4;

		// The next two bytes are:
		// 1. the max packets sent ( byte )
		// 2. the index of this packet starting from 0 ( byte )
		// 3. Size of the split ( short )
		if(pktlen < 10) goto out_too_short;

		// PacketNumber
		pkt_index = ((unsigned char)*pkt);

		// NumPackates
		pkt_max = ((unsigned char)*(pkt+1));

		// SizeOfPacketSplits
		debug( 3, "packetid[2]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max );
		pkt+=4;
		pktlen -= 12;

		// pkt_max is the total number of packets expected
		// pkt_index is a bit mask of the packets received.
		if ( server->saved_data.data == NULL )
		{
			sdata = &server->saved_data;
		}
		else
		{
			sdata = (SavedData*) calloc( 1, sizeof(SavedData));
			sdata->next = server->saved_data.next;
			server->saved_data.next = sdata;
		}

		sdata->pkt_index = pkt_index;
		sdata->pkt_max = pkt_max;
		sdata->pkt_id = pkt_id;
		sdata->datalen = pktlen;
		sdata->data= (char*)malloc( pktlen );
		if ( NULL == sdata->data )
		{
			malformed_packet(server, "Out of memory");
			return MEM_ERROR;
		}

		memcpy( sdata->data, pkt, sdata->datalen );

		// combine_packets will call us recursively
		return combine_packets( server );
	}
	else if ( 0 != memcmp(pkt, "\xFF\xFF\xFF\xFF", 4) )
	{
		malformed_packet(server, "invalid packet header");
		return PKT_ERROR;
	}

	pkt += 4;
	pktlen -= 4;

	pktlen -= 1;
	debug( 2, "FL type = 0x%x", *pkt );
	switch(*pkt++)
	{
	case FL_CHALLENGERESPONSE:
		if(pktlen < 4) goto out_too_short;
		memcpy(&status->challenge, pkt, 4);
		// do not count challenge as retry
		if(!status->have_challenge && server->retry1 != n_retries)
		{
			++server->retry1;
			if(server->n_retries)
			{
				--server->n_retries;
			}
		}
		status->have_challenge = 1;
		debug(3, "challenge %x", status->challenge);
		break;

	case FL_INFORESPONSE:
		if(pktlen < 1) goto out_too_short;
		status->type = *pkt;
		if ( *pkt > 1 && ( get_server_rules || get_player_info ) )
		{
			 server->next_rule = ""; // trigger calling send_fl_rule_request_packet
		}
		snprintf(buf, sizeof(buf), "%hhX", *pkt);
		add_rule(server, "protocol", buf, 0);
		pktlen--;
		pkt++;

		// ServerName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->server_name = strdup(pkt);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// MapName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->map_name = strdup(pkt);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// ModName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->game = strdup(pkt);
		add_rule(server, "modname", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameMode
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gamemode", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameDescription
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gamedescription", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameVersion
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gameversion", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		if( pktlen < 13 )
		{
			goto out_too_short;
		}

		// GamePort
		tmp_short = ((unsigned short)pkt[0] <<8 ) | ((unsigned short)pkt[1]);
		change_server_port( server, tmp_short, 0 );
		pkt += 2;

		// Num Players
		server->num_players = (unsigned char)*pkt++;

		// Max Players
		server->max_players = (unsigned char)*pkt++;

		// Dedicated
		add_rule(server, "dedicated", ( 'd' == *pkt++) ? "1" : "0", 0);

		// OS
		switch( *pkt )
		{
		case 'l':
			add_rule(server, "sv_os", "linux", 0);
			break;

		case 'w':
			add_rule(server, "sv_os", "windows", 0);
			break;

		default:
			buf[0] = *pkt;
			buf[1] = '\0';
			add_rule(server, "sv_os", buf, 0);
			break;
		}
		pkt++;

		// Passworded
		add_rule(server, "passworded", ( *pkt++ ) ? "1" : "0" , 0);

		// Anticheat
		add_rule(server, "passworded", ( *pkt++ ) ? "1" : "0" , 0);

		// FrameTime
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "frametime", buf , 0);

		// Round
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "round", buf , 0);

		// RoundMax
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "roundmax", buf , 0);

		// RoundSeconds
		tmp_short = ((unsigned short)pkt[0] <<8 ) | ((unsigned short)pkt[1]);
		sprintf( buf, "%hu", tmp_short );
		add_rule(server, "roundseconds", buf , 0);
		pkt += 2;

		status->have_info = 1;

		server->retry1 = n_retries;

		server->next_player_info = server->num_players;

		break;

	case FL_RULESRESPONSE:
		if(pktlen < 2) goto out_too_short;

		cnt = ((unsigned char)pkt[0] << 8 ) + ((unsigned char)pkt[1]);
		pktlen -= 2;
		pkt += 2;

		debug(3, "num_rules: %d", cnt);

		for(;cnt && pktlen > 0; --cnt)
		{
			char* key, *value;
			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			key = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			value = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			add_rule(server, key, value, NO_FLAGS);
		}

		if(cnt)
		{
			malformed_packet(server, "packet contains too few rules, missing %d", cnt);
			server->missing_rules = 1;
		}
		if(pktlen)
		malformed_packet(server, "garbage at end of rules, %d bytes left", pktlen);

		status->have_rules = 1;

		server->retry1 = n_retries;

		break;

	case FL_PLAYERRESPONSE:

		if(pktlen < 1) goto out_too_short;

		cnt = (unsigned char)pkt[0];
		pktlen -= 1;
		pkt += 1;

		debug(3, "num_players: %d", cnt);

		for(;cnt && pktlen > 0; --cnt)
		{
			unsigned idx;
			const char* name;
			struct player* p;

			// Index
			idx = *pkt++;
			--pktlen;

			// PlayerName
			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			name = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			if(pktlen < 8) goto out_too_short;

			debug(3, "player index %d", idx);
			p = add_player(server, server->n_player_info);
			if(p)
			{
				union
				{
					int i;
					float fl;
				} temp;

				p->name = strdup(name);

				// Score
				p->frags = ntohl( *(unsigned int *)pkt );

				// TimeConnected
				temp.i = ntohl( *(unsigned int *)(pkt+4) );
				p->connect_time = temp.fl;

				// Ping
				p->ping = 0;
				p->ping = ntohs( *(unsigned int *)(pkt+8) );
				//((unsigned char*)&p->ping)[0] = pkt[9];
				//((unsigned char*)&p->ping)[1] = pkt[8];

				// ProfileId
				//p->profileid = ntohl( *(unsigned int *)pkt+10 );

				// Team
				p->team = *(pkt+14);
//fprintf( stderr, "Player: '%s', Frags: %u, Time: %u, Ping: %hu, Team: %d\n", p->name, p->frags,  p->connect_time, p->ping, p->team );
			}
			pktlen -= 15;
			pkt += 15;
		}

#if 0 // seems to be a rather normal condition
		if(cnt)
		{
			malformed_packet(server, "packet contains too few players, missing %d", cnt);
		}
#endif
		if(pktlen)
		malformed_packet(server, "garbage at end of player info, %d bytes left", pktlen);

		status->have_player = 1;

		server->retry1 = n_retries;

		break;

	default:
		malformed_packet(server, "invalid packet id %hhx", *--pkt);
		return PKT_ERROR;
	}

	if(
		(!get_player_info || (get_player_info && status->have_player)) &&
		(!get_server_rules || (get_server_rules && status->have_rules))
	)
	{
		server->next_rule = NULL;
	}

	return DONE_AUTO;

out_too_short:
	malformed_packet(server, "packet too short");

	return PKT_ERROR;
}