// See the following for protocol details: // http://dev.kquery.com/index.php?article=42 query_status_t deal_with_gs2_packet( struct qserver *server, char *rawpkt, int pktlen ) { char *ptr = rawpkt; char *end = rawpkt + pktlen; unsigned char type = 0; unsigned char no_players = 0; unsigned char total_players = 0; unsigned char no_teams = 0; unsigned char total_teams = 0; unsigned char no_headers = 0; char **headers = NULL; debug( 2, "processing packet..." ); if ( pktlen < 15 ) { // invalid packet? return PKT_ERROR; } 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); } // Could check the header here should // match the 4 byte id sent ptr += 5; while ( 0 == type && ptr < end ) { // server info: // name value pairs null seperated // empty name && value signifies the end of section char *var, *val; int var_len, val_len; var = ptr; var_len = strlen( var ); if ( ptr + var_len + 2 > end ) { if ( 0 != var_len ) { malformed_packet( server, "no rule value" ); } else if ( get_player_info ) { malformed_packet( server, "no player headers" ); } return PKT_ERROR; } ptr += var_len + 1; val = ptr; val_len = strlen( val ); ptr += val_len + 1; debug( 2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len ); // Lets see what we've got if ( 0 == strcmp( var, "hostname" ) ) { server->server_name = strdup( val ); } else if( 0 == strcmp( var, "game_id" ) ) { server->game = strdup( val ); } else if( 0 == strcmp( var, "gamever" ) ) { // format: // v1.0 server->protocol_version = atoi( val+1 ); add_rule( server, var, val, NO_FLAGS ); } else if( 0 == strcmp( var, "mapname" ) ) { server->map_name = strdup( val ); } else if( 0 == strcmp( var, "map" ) ) { // For BF2MC compatibility server->map_name = strdup( val ); } else if( 0 == strcmp( var, "maxplayers" ) ) { server->max_players = atoi( val ); } else if( 0 == strcmp( var, "numplayers" ) ) { server->num_players = no_players = atoi( val ); } else if( 0 == strcmp( var, "hostport" ) ) { change_server_port( server, atoi( val ), 0 ); } else if ( 0 == var_len ) { // check for end of section type = 1; } else { add_rule( server, var, val, NO_FLAGS ); } } if ( 1 != type ) { // no more info should be player headers here as we // requested it malformed_packet( server, "no player headers" ); return PKT_ERROR; } // player info header // format: // first byte = player count // followed by null seperated header no_players = (unsigned char)*ptr; debug( 2, "No Players:%d\n", no_players ); ptr++; if ( ptr >= end ) { malformed_packet( server, "no player headers" ); return PKT_ERROR; } while ( 1 == type && ptr < end ) { // first we have the headers null seperated char **tmpp; char *head = ptr; int head_len = strlen( head ); no_headers++; tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) ); if ( NULL == tmpp ) { debug( 0, "Failed to realloc memory for headers\n" ); if ( NULL != headers ) { free( headers ); } return MEM_ERROR; } headers = tmpp; headers[no_headers-1] = head; ptr += head_len + 1; // end of headers check if ( 0x00 == *ptr ) { type = 2; ptr++; } debug( 2, "player header[%d] = '%s'", no_headers-1, head ); } if ( 2 != type ) { // no more info should be player info here as we // requested it malformed_packet( server, "no players" ); return PKT_ERROR; } while( 2 == type && ptr < end ) { // now each player details // add the player if ( 0x00 == *ptr ) { // no players if ( 0 != no_players ) { malformed_packet( server, "no players" ); return PKT_ERROR; } } else { struct player *player = add_player( server, total_players ); int i; for ( i = 0; i < no_headers; i++ ) { char *header = headers[i]; char *val; int val_len; if ( ptr >= end ) { malformed_packet( server, "short player detail" ); return PKT_ERROR; } val = ptr; val_len = strlen( val ); ptr += val_len + 1; // lets see what we got if ( 0 == strcmp( header, "player_" ) ) { player->name = strdup( val ); } else if ( 0 == strcmp( header, "score_" ) ) { player->score = atoi( val ); } else if ( 0 == strcmp( header, "deaths_" ) ) { player->deaths = atoi( val ); } else if ( 0 == strcmp( header, "ping_" ) ) { player->ping = atoi( val ); } else if ( 0 == strcmp( header, "kills_" ) ) { player->frags = atoi( val ); } else if ( 0 == strcmp( header, "team_" ) ) { player->team = atoi( val ); } else { int len = strlen( header ); if ( '_' == header[len-1] ) { header[len-1] = '\0'; } player_add_info( player, header, val, NO_FLAGS ); } debug( 2, "Player[%d][%s]=%s\n", total_players, headers[i], val ); } total_players++; } if ( total_players > no_players ) { malformed_packet( server, "to many players %d > %d", total_players, no_players ); return PKT_ERROR; } // check for end of player info if ( 0x00 == *ptr ) { if ( total_players != no_players ) { malformed_packet( server, "bad number of players %d != %d", total_players, no_players ); return PKT_ERROR; } type = 3; ptr++; } } if ( 3 != type ) { // no more info should be team info here as we // requested it malformed_packet( server, "no teams" ); return PKT_ERROR; } no_teams = (unsigned char)*ptr; ptr++; debug( 2, "No teams:%d\n", no_teams ); no_headers = 0; while ( 3 == type && ptr < end ) { // first we have the headers null seperated char **tmpp; char *head = ptr; int head_len = strlen( head ); no_headers++; tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) ); if ( NULL == tmpp ) { debug( 0, "Failed to realloc memory for headers\n" ); if ( NULL != headers ) { free( headers ); } return MEM_ERROR; } headers = tmpp; headers[no_headers-1] = head; ptr += head_len + 1; // end of headers check if ( 0x00 == *ptr ) { type = 4; ptr++; } } if ( 4 != type ) { // no more info should be team info here as we // requested it malformed_packet( server, "no teams" ); return PKT_ERROR; } while( 4 == type && ptr < end ) { // now each teams details int i; for ( i = 0; i < no_headers; i++ ) { char *val; int val_len; if ( ptr >= end ) { malformed_packet( server, "short team detail" ); return PKT_ERROR; } val = ptr; val_len = strlen( val ); ptr += val_len + 1; // lets see what we got if ( 0 == strcmp( headers[i], "team_t" ) ) { // BF being stupid again teams 1 based instead of 0 players_set_teamname( server, total_teams + 1, val ); } debug( 2, "Team[%d][%s]=%s\n", total_teams, headers[i], val ); } total_teams++; if ( total_teams > no_teams ) { malformed_packet( server, "to many teams" ); return PKT_ERROR; } } return DONE_FORCE; }
query_status_t deal_with_ottd_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *ptr = (unsigned char*)rawpkt; unsigned char *end = (unsigned char*)(rawpkt + pktlen); unsigned char type; char* str; char buf[32]; unsigned ver; server->n_servers++; if ( server->server_name == NULL) { server->ping_total += time_delta( &packet_recv_time, &server->packet_time1); server->n_requests++; } else { gettimeofday( &server->packet_time1, NULL); } FAIL_IF(pktlen < 4 || swap_short_from_little(rawpkt) > pktlen, "invalid packet"); type = ptr[2]; ver = ptr[3]; ptr += 4; debug(3, "len %hu type %hhu ver %hhu", swap_short_from_little(rawpkt), type, ver); FAIL_IF(ver != 4 && ver != 5, "only version 4 and 5 servers are supported"); if(type == 1) // info packet { unsigned numgrf = *ptr; FAIL_IF(ptr + numgrf * 20 + 1 > end, "invalid newgrf number"); ptr += numgrf * 20 + 1; snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); add_rule(server, "date_days", buf, NO_FLAGS); ptr += 4; snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); add_rule(server, "startdate_days", buf, NO_FLAGS); ptr += 4; FAIL_IF(ptr + 3 > end, "invalid packet"); snprintf(buf, sizeof(buf), "%hhu", ptr[0]); add_rule(server, "maxcompanies", buf, NO_FLAGS); snprintf(buf, sizeof(buf), "%hhu", ptr[1]); add_rule(server, "numcompanies", buf, NO_FLAGS); server->max_spectators = ptr[2]; ptr += 3; GET_STRING; server->server_name = strdup(str); GET_STRING; add_rule(server, "version", str, NO_FLAGS); FAIL_IF(ptr + 7 > end, "invalid packet"); { static const char* langs[] = { "any", "English", "German", "French" }; unsigned i = *ptr++; if(i > 3) i = 0; add_rule(server, "language", (char*)langs[i], NO_FLAGS); } add_rule(server, "password", *ptr++ ? "1" : "0", NO_FLAGS); server->max_players = *ptr++; server->num_players = *ptr++; server->num_spectators = *ptr++; GET_STRING; server->map_name = strdup(str); snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); add_rule(server, "map_width", buf, NO_FLAGS); snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); add_rule(server, "map_height", buf, NO_FLAGS); { static const char* sets[] = { "temperate", "arctic", "desert", "toyland" }; unsigned i = *ptr++; if(i > 3) i = 0; add_rule(server, "map_set", (char*)sets[i], NO_FLAGS); } add_rule(server, "dedicated", *ptr++ ? "1" : "0", NO_FLAGS); } else if(type == 3) // player packet { unsigned i, j; INVALID_IF(ptr + 2 > end); server->num_players = *ptr++; for(i = 0; i < server->num_players; ++i) { unsigned long long lli; struct player* player; unsigned char nr; nr = *ptr++; debug(3, "player number %d", nr); player = add_player(server, i); FAIL_IF(!player, "can't allocate player"); GET_STRING; player->name = strdup(str); debug(3, "name %s", str); player->frags = 0; INVALID_IF(ptr + 4 + 3*8 + 2 + 1 + 2*MAX_VEHICLE_TYPES + 2*MAX_STATION_TYPES > end); snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); player_add_info(player, "startdate", buf, 0); ptr += 4; lli = swap_long_from_little(ptr+4); lli <<= 32; lli += swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "value", buf, 0); ptr += 8; lli = swap_long_from_little(ptr+4); lli <<= 32; lli = swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "money", buf, 0); ptr += 8; lli = swap_long_from_little(ptr+4); lli <<= 32; lli += swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "income", buf, 0); ptr += 8; snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, "performance", buf, 0); ptr += 2; player_add_info(player, "password", *ptr?"1":"0", 0); ++ptr; for (j = 0; j < MAX_VEHICLE_TYPES; ++j) { snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, (char*)vehicle_types[j], buf, 0); ptr += 2; } for (j = 0; j < MAX_STATION_TYPES; ++j) { snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, (char*)station_types[j], buf, 0); ptr += 2; } if (ver != 5) { // connections while(ptr + 1 < end && *ptr) { ++ptr; GET_STRING; // client name debug(3, "%s played by %s", str, player->name); GET_STRING; // id INVALID_IF(ptr + 4 > end); ptr += 4; } ++ptr; // record terminated by zero byte } } // spectators while(ptr + 1 < end && *ptr) { ++ptr; GET_STRING; // client name debug(3, "spectator %s", str); GET_STRING; // id INVALID_IF(ptr + 4 > end); ptr += 4; } ++ptr; // record terminated by zero byte server->next_rule = NO_SERVER_RULES; // we're done server->next_player_info = server->num_players; // we're done } else { malformed_packet( server, "invalid type" ); return PKT_ERROR; } server->retry1 = n_retries; // we're done with this packet, reset retry counter return DONE_AUTO; }
static query_status_t _deal_with_doom3_packet( struct qserver *server, char *rawpkt, int pktlen, unsigned version ) { char *ptr = rawpkt; char *end = rawpkt + pktlen; int type = 0; int size = 0; int tail_size = 4; int viewers = 0; int tv = 0; unsigned num_players = 0; unsigned challenge = 0; unsigned protocolver = 0; char tmp[32]; 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); } // Check if correct reply if ( pktlen < sizeof(doom3_inforesponse) +4 +4 +1 || memcmp( doom3_inforesponse, ptr, sizeof(doom3_inforesponse)) != 0 ) { malformed_packet(server, "too short or invalid response"); return PKT_ERROR; } ptr += sizeof(doom3_inforesponse); if ( 5 == version ) { // TaskID ptr += 4; // osmask + ranked tail_size++; } challenge = swap_long_from_little(ptr); ptr += 4; protocolver = swap_long_from_little(ptr); ptr += 4; snprintf(tmp, sizeof(tmp), "%u.%u", protocolver >> 16, protocolver & 0xFFFF); debug(2, "challenge: 0x%08X, protocol: %s (0x%X)", challenge, tmp, protocolver); server->protocol_version = protocolver; add_rule( server, "protocol", tmp, NO_FLAGS ); if ( 5 == version ) { // Size Long size = swap_long_from_little(ptr); debug( 2, "Size = %d", size ); ptr += 4; } // Commented out until we have a better way to specify the expected version // This is due to prey demo requiring version 4 yet prey retail version 3 /* if( protocolver >> 16 != version ) { malformed_packet(server, "protocol version %u, expected %u", protocolver >> 16, version ); return PKT_ERROR; } */ while ( ptr < end ) { // server info: // name value pairs null seperated // empty name && value signifies the end of section char *key, *val; unsigned keylen, vallen; key = ptr; ptr = memchr(ptr, '\0', end-ptr); if ( !ptr ) { malformed_packet( server, "no rule key" ); return PKT_ERROR; } keylen = ptr - key; val = ++ptr; ptr = memchr(ptr, '\0', end-ptr); if ( !ptr ) { malformed_packet( server, "no rule value" ); return PKT_ERROR; } vallen = ptr - val; ++ptr; if( keylen == 0 && vallen == 0 ) { type = 1; break; // end } debug( 2, "key:%s=%s:", key, val); // Lets see what we've got if ( 0 == strcasecmp( key, "si_name" ) ) { server->server_name = strdup( val ); } else if( 0 == strcasecmp( key, "fs_game" ) ) { server->game = strdup( val ); } #if 0 else if( 0 == strcasecmp( key, "si_version" ) ) { // format: x // DOOM 1.0.1262.win-x86 Jul 8 2004 16:46:37 server->protocol_version = atoi( val+1 ); } #endif else if( 0 == strcasecmp( key, "si_map" ) ) { if ( 5 == version || 6 == version ) { // ET:QW reports maps/<mapname>.entities // so we strip that here if it exists char *tmpp = strstr( val, ".entities" ); if ( NULL != tmpp ) { *tmpp = '\0'; } if ( 0 == strncmp( val, "maps/", 5 ) ) { val += 5; } } server->map_name = strdup( val ); } else if ( 0 == strcasecmp( key, "si_maxplayers" ) ) { server->max_players = atoi( val ); } else if ( 0 == strcasecmp( key, "ri_maxViewers" ) ) { char max[20]; sprintf( max, "%d", server->max_players ); add_rule( server, "si_maxplayers", max, NO_FLAGS ); server->max_players = atoi( val ); } else if ( 0 == strcasecmp( key, "ri_numViewers" ) ) { viewers = atoi( val ); tv = 1; } add_rule( server, key, val, NO_FLAGS ); } if ( type != 1 ) { // no more info should be player headers here as we // requested it malformed_packet( server, "player info missing" ); return PKT_ERROR; } // now each player details while( ptr < end - tail_size ) { struct player *player; char *val; unsigned char player_id = *ptr++; short ping = 0; unsigned rate = 0; if( ( 6 == version && MAX_WOLF_ASYNC_CLIENTS == player_id ) || MAX_DOOM3_ASYNC_CLIENTS == player_id ) { break; } debug( 2, "ID = %d\n", player_id ); // Note: id's are not steady if ( ptr + 7 > end ) // 2 ping + 4 rate + empty player name ('\0') { // run off the end and shouldnt have malformed_packet( server, "player info too short" ); return PKT_ERROR; } /* if ( 6 == version ) { // Playerid is broken in wolf its always 0 player_id = num_players; } */ player = add_player( server, player_id ); if( ! player ) { malformed_packet( server, "duplicate player id" ); return PKT_ERROR; } // doesnt support score so set a sensible default player->score = 0; player->frags = 0; // Ping ping = swap_short_from_little(ptr); player->ping = ping; ptr += 2; if ( 5 != version || 0xa0013 >= protocolver ) // No Rate in ETQW since v1.4 ( protocol v10.19 ) { // Rate rate = swap_long_from_little(ptr); { char buf[16]; snprintf(buf, sizeof(buf), "%u", rate); player_add_info(player, "rate", buf, NO_FLAGS); } ptr += 4; } if ( 5 == version && ( ( 0xd0009 == protocolver || 0xd000a == protocolver ) && 0 != num_players ) ) // v13.9 or v13.10 { // Fix the packet offset due to the single bit used for bot // which realigns at the byte boundary for the player name ptr++; } // Name val = ptr; ptr = memchr(ptr, '\0', end-ptr); if ( !ptr ) { malformed_packet( server, "player name not null terminated" ); return PKT_ERROR; } player->name = strdup( val ); ptr++; switch( version ) { case 2: // Quake 4 val = ptr; ptr = memchr(ptr, '\0', end-ptr); if ( !ptr ) { malformed_packet( server, "player clan not null terminated" ); return PKT_ERROR; } player->tribe_tag = strdup( val ); ptr++; debug( 2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, clan %s", num_players, player->name, ping, rate, player_id, player->tribe_tag); break; case 5: // ETQW case 6: // Wolfenstien if ( 0xa0011 <= protocolver ) // clan tag since 10.17 { // clantag position ptr++; // clantag val = ptr; ptr = memchr(ptr, '\0', end-ptr); if ( !ptr ) { malformed_packet( server, "player clan not null terminated" ); return PKT_ERROR; } player->tribe_tag = strdup( val ); ptr++; } // Bot flag if ( 0xd0009 == protocolver || 0xd000a == protocolver ) // v13.9 or v13.10 { // Bot flag is a single bit so need to realign everything from here on in :( int i; unsigned char *tp = (unsigned char*)ptr; player->type_flag = (*tp)<<7; // alignment is reset at the end for( i = 0; i < 8 && tp < (unsigned char*)end; i++ ) { *tp = (*tp)>>1 | *(tp+1)<<7; tp++; } } else { player->type_flag = *ptr++; } if ( 0xa0011 <= protocolver ) // clan tag since 10.17 { debug( 2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu, clan %s", num_players, player->name, ping, rate, player_id, player->type_flag, player->tribe_tag); } else { debug( 2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu", num_players, player->name, ping, rate, player_id, player->type_flag ); } break; default: debug( 2, "Player[%d] = %s, ping %hu, rate %u, id %hhu", num_players, player->name, ping, rate, player_id ); }