/* * Copyright (C) Volition, Inc. 2005. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ //Validate tracker user class #ifndef _WIN32 #include #include #include #include #endif #ifdef SCP_BSD #include #endif #include "network/multi.h" #include "network/ptrack.h" #include "network/valid.h" #include "network/psnet2.h" #include "network/multi_fstracker.h" #include "io/timer.h" // check structs for size compatibility SDL_COMPILE_TIME_ASSERT(vmt_validate_mission_req_struct, sizeof(vmt_validate_mission_req_struct) == 104); // Variables static udp_packet_header PacketHeader; static validate_id_request *ValidIDReq; static int ValidState; static SOCKADDR_STORAGE rtrackaddr; static int ValidFirstSent; static int ValidLastSent; static char *Psztracker_id; // mission validation int MissionValidState; static int MissionValidFirstSent; static int MissionValidLastSent; // squad war validation int SquadWarValidState; static int SquadWarFirstSent; static int SquadWarLastSent; // data validation int DataValidState; static SCP_vector DataValidStatus; static int DataValidFirstSent; static int DataValidLastSent; // squad war response static squad_war_response SquadWarValidateResponse; static int SerializeValidatePacket(const udp_packet_header *uph, ubyte *data) { unsigned int packet_size = 0; int i; PXO_ADD_DATA(uph->type); PXO_ADD_USHORT(uph->len); PXO_ADD_UINT(uph->code); PXO_ADD_USHORT(uph->xcode); PXO_ADD_UINT(uph->sig); PXO_ADD_UINT(uph->security); switch (uph->type) { // no extra data for this case UNT_CONTROL: break; case UNT_LOGIN_AUTH_REQUEST: { auto id_req = reinterpret_cast(&uph->data); PXO_ADD_DATA(id_req->login); PXO_ADD_DATA(id_req->password); PXO_ADD_DATA(id_req->tracker_id); // junk here, just for size break; } case UNT_VALID_MSN_REQ: { auto mis_req = reinterpret_cast(&uph->data); PXO_ADD_UINT(mis_req->checksum); memcpy(data+packet_size, mis_req->file_name, strlen(mis_req->file_name)); packet_size += static_cast(strlen(mis_req->file_name)); data[packet_size] = '\0'; packet_size++; break; } case UNT_VALID_DATA_REQ: { // as a dynamically sized packet this should have already been // packed, so just copy the data over const unsigned int dsize = uph->len - PACKED_HEADER_ONLY_SIZE; memcpy(data+packet_size, uph->data, dsize); packet_size += dsize; break; } case UNT_VALID_SW_MSN_REQ: { auto sw_req = reinterpret_cast(&uph->data); for (i = 0; i < MAX_SQUAD_PLAYERS; i++) { PXO_ADD_INT(sw_req->squad_plr1[i]); } for (i = 0; i < MAX_SQUAD_PLAYERS; i++) { PXO_ADD_INT(sw_req->squad_plr2[i]); } PXO_ADD_DATA(sw_req->squad_count1); PXO_ADD_DATA(sw_req->squad_count2); PXO_ADD_DATA(sw_req->match_code); PXO_ADD_DATA(sw_req->mission_filename); PXO_ADD_INT(sw_req->mission_checksum); break; } // we shouldn't be sending any other packet types default: Int3(); break; } Assert(packet_size >= PACKED_HEADER_ONLY_SIZE); Assert(packet_size == uph->len); Assert(packet_size <= MAX_PACKET_SIZE); return static_cast(packet_size); } static void DeserializeValidatePacket(const ubyte *data, const int data_size, udp_packet_header *uph) { int offset = 0; memset(uph, 0, sizeof(udp_packet_header)); // make sure we received a complete base packet if (data_size < static_cast(PACKED_HEADER_ONLY_SIZE)) { uph->len = 0; uph->type = 0xff; return; } PXO_GET_DATA(uph->type); PXO_GET_USHORT(uph->len); PXO_GET_UINT(uph->code); PXO_GET_USHORT(uph->xcode); PXO_GET_UINT(uph->sig); PXO_GET_UINT(uph->security); // sanity check data size to make sure we reveived all of the expected packet // (the -1 is because psnet2 pops off one byte) if (static_cast(uph->len)-1 > data_size) { uph->len = 0; uph->type = 0xff; return; } switch (uph->type) { // no extra data for these case UNT_CONTROL: case UNT_CONTROL_VALIDATION: case UNT_LOGIN_NO_AUTH: case UNT_VALID_MSN_RSP: break; case UNT_VALID_TBL_RSP: { short game_id; PXO_GET_SHORT(game_id); memcpy(uph->data, &game_id, sizeof(short)); SDL_strlcpy(reinterpret_cast(uph->data+sizeof(short)), reinterpret_cast(data+offset), SDL_arraysize(uph->data) - sizeof(short)); break; } case UNT_VALID_DATA_RSP: { // as a dynamically sized packet we can't really handle it here // that easily so just copy the data over for later use const unsigned int dsize = uph->len - PACKED_HEADER_ONLY_SIZE; memcpy(uph->data, data+offset, dsize); offset += dsize; break; } case UNT_LOGIN_AUTHENTICATED: { SDL_strlcpy(reinterpret_cast(uph->data), reinterpret_cast(data+offset), SDL_arraysize(uph->data)); break; } case UNT_VALID_SW_MSN_RSP: { auto sw_resp = reinterpret_cast(&uph->data); PXO_GET_DATA(sw_resp->reason); PXO_GET_DATA(sw_resp->accepted); break; } default: break; } //Assert(offset == data_size); } int InitValidateClient() { ValidFirstSent = 0; ValidLastSent = 0; ValidState = VALID_STATE_IDLE; MissionValidFirstSent = 0; MissionValidLastSent = 0; MissionValidState = VALID_STATE_IDLE; SquadWarFirstSent = 0; SquadWarLastSent = 0; SquadWarValidState = VALID_STATE_IDLE; DataValidFirstSent = 0; DataValidLastSent = 0; DataValidState = VALID_STATE_IDLE; psnet_get_addr(Multi_options_g.user_tracker_ip, REGPORT, &rtrackaddr); return 1; } //Call with a valid struct to validate a user //Call with NULL to poll //Return codes: // -3 Still waiting (returned if we were waiting for a tracker response and ValidateUser was called with a non-NULL value // -2 Timeout waiting for tracker to respond // -1 User invalid // 0 Still waiting for response from tracker/Idle // 1 User valid int ValidateUser(validate_id_request *valid_id, char *trackerid) { ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; ValidIdle(); if(valid_id==nullptr) { switch(ValidState) { case VALID_STATE_IDLE: return 0; case VALID_STATE_WAITING: return 0; case VALID_STATE_VALID: ValidState = VALID_STATE_IDLE; return 1; case VALID_STATE_INVALID: ValidState = VALID_STATE_IDLE; return -1; case VALID_STATE_TIMEOUT: ValidState = VALID_STATE_IDLE; return -2; } return 0; } else { if(ValidState==VALID_STATE_IDLE) { //First, flush the input buffer for the socket fd_set read_fds; struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=0; FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); while(SELECT(static_cast(Psnet_socket+1),&read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_VALIDATION)) { int addrsize; SOCKADDR_STORAGE fromaddr; udp_packet_header inpacket; addrsize = sizeof(fromaddr); RECVFROM(Psnet_socket, reinterpret_cast(&inpacket), sizeof(udp_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_VALIDATION); } Psztracker_id = trackerid; //Build the request packet SDL_zero(PacketHeader); PacketHeader.type = UNT_LOGIN_AUTH_REQUEST; PacketHeader.xcode = static_cast(Multi_fs_tracker_game_id); PacketHeader.len = PACKED_HEADER_ONLY_SIZE+sizeof(validate_id_request); ValidIDReq = reinterpret_cast(&PacketHeader.data); SDL_strlcpy(ValidIDReq->login, valid_id->login, SDL_arraysize(ValidIDReq->login)); SDL_strlcpy(ValidIDReq->password, valid_id->password, SDL_arraysize(ValidIDReq->password)); packet_length = SerializeValidatePacket(&PacketHeader, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); ValidState = VALID_STATE_WAITING; ValidFirstSent = timer_get_milliseconds(); ValidLastSent = timer_get_milliseconds(); return 0; } else { return -3; } } } void ValidIdle() { fd_set read_fds; struct timeval timeout; ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; PSNET_TOP_LAYER_PROCESS(); timeout.tv_sec=0; timeout.tv_usec=0; FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); if(SELECT(static_cast(Psnet_socket+1),&read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_VALIDATION)){ int bytesin; int addrsize; SOCKADDR_STORAGE fromaddr; udp_packet_header inpacket; SDL_zero(inpacket); addrsize = sizeof(fromaddr); bytesin = RECVFROM(Psnet_socket, reinterpret_cast(&packet_data), sizeof(udp_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_VALIDATION); if (bytesin > 0) { DeserializeValidatePacket(packet_data, bytesin, &inpacket); // decrease packet size by 1 inpacket.len--; #ifndef NDEBUG } else { int wserr=WSAGetLastError(); mprintf(("recvfrom() failure. WSAGetLastError() returned %d\n",wserr)); #endif } FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); //Check to make sure the packets ok if ( (bytesin > 0) && (bytesin == inpacket.len) ) { const ubyte *data = inpacket.data; unsigned int offset = 0; switch(inpacket.type) { case UNT_LOGIN_NO_AUTH: if(ValidState == VALID_STATE_WAITING) { ValidState = VALID_STATE_INVALID; } break; case UNT_LOGIN_AUTHENTICATED: if(ValidState == VALID_STATE_WAITING) { ValidState = VALID_STATE_VALID; SDL_strlcpy(Psztracker_id, reinterpret_cast(&inpacket.data), TRACKER_ID_LEN); } break; // fs2 mission validation response case UNT_VALID_MSN_RSP: if(MissionValidState == VALID_STATE_WAITING){ if(inpacket.code==2){ MissionValidState = VALID_STATE_VALID; } else { MissionValidState = VALID_STATE_INVALID; } } break; // fs2 squad war validation response case UNT_VALID_SW_MSN_RSP: if(SquadWarValidState == VALID_STATE_WAITING){ // copy the data Assert((static_cast(bytesin) - PACKED_HEADER_ONLY_SIZE) == sizeof(squad_war_response)); if((static_cast(bytesin) - PACKED_HEADER_ONLY_SIZE) == sizeof(squad_war_response)){ memset(&SquadWarValidateResponse, 0, sizeof(squad_war_response)); memcpy(&SquadWarValidateResponse, inpacket.data, sizeof(squad_war_response)); // now check to see if we're good if(SquadWarValidateResponse.accepted){ SquadWarValidState = VALID_STATE_VALID; } else { SquadWarValidState = VALID_STATE_INVALID; } } else { SquadWarValidState = VALID_STATE_INVALID; } } break; // data validation response case UNT_VALID_DATA_RSP: { if (DataValidState == VALID_STATE_WAITING) { if (inpacket.code == 2) { DataValidState = VALID_STATE_VALID; } else { DataValidState = VALID_STATE_INVALID; } DataValidStatus.clear(); // special error case if (inpacket.code == 0) { break; } uint8_t flags; PXO_GET_DATA(flags); if (flags & VDR_FLAG_IDENT) { char text[100]; PXO_GET_SHORT(Multi_fs_tracker_game_id); PXO_GET_STRING(text); Multi_fs_tracker_game_tag = text; PXO_GET_STRING(text); Multi_fs_tracker_game_name = text; } if (flags & VDR_FLAG_STATUS) { uint8_t status_count; uint32_t status; PXO_GET_DATA(status_count); for (auto i = 0; i < status_count; ++i) { PXO_GET_UINT(status); DataValidStatus.push_back(status); } } } break; } case UNT_CONTROL_VALIDATION: Int3(); break; case UNT_CONTROL: Int3(); break; } AckValidServer(inpacket.sig); } } if(ValidState == VALID_STATE_WAITING) { if((timer_get_milliseconds()-ValidFirstSent)>=PILOT_REQ_TIMEOUT) { ValidState = VALID_STATE_TIMEOUT; } else if((timer_get_milliseconds()-ValidLastSent)>=PILOT_REQ_RESEND_TIME) { //Send 'da packet packet_length = SerializeValidatePacket(&PacketHeader, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); ValidLastSent = timer_get_milliseconds(); } } if(MissionValidState == VALID_STATE_WAITING) { if((timer_get_milliseconds()-MissionValidFirstSent)>=PILOT_REQ_TIMEOUT) { MissionValidState = VALID_STATE_TIMEOUT; } } if(SquadWarValidState == VALID_STATE_WAITING) { if((timer_get_milliseconds()-SquadWarFirstSent)>=PILOT_REQ_TIMEOUT) { SquadWarValidState = VALID_STATE_TIMEOUT; } } if (DataValidState == VALID_STATE_WAITING) { if ( (timer_get_milliseconds() - DataValidFirstSent) >= PILOT_REQ_TIMEOUT ) { DataValidState = VALID_STATE_TIMEOUT; } } } //Send an ACK to the server void AckValidServer(unsigned int sig) { udp_packet_header ack_pack{}; ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; ack_pack.type = UNT_CONTROL; ack_pack.sig = sig; ack_pack.code = CMD_CLIENT_RECEIVED; ack_pack.len = PACKED_HEADER_ONLY_SIZE; packet_length = SerializeValidatePacket(&ack_pack, packet_data); Assert(packet_length == PACKED_HEADER_ONLY_SIZE); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); } // call with a valid struct to validate a mission // call with NULL to poll // Return codes: // -3 Still waiting (returned if we were waiting for a tracker response and ValidateMission was called with a non-NULL value // -2 Timeout waiting for tracker to respond // -1 User invalid // 0 Still waiting for response from tracker/Idle // 1 User valid int ValidateMission(vmt_validate_mission_req_struct *valid_msn) { ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; ValidIdle(); if(valid_msn==nullptr) { switch(MissionValidState) { case VALID_STATE_IDLE: return 0; case VALID_STATE_WAITING: return 0; case VALID_STATE_VALID: MissionValidState = VALID_STATE_IDLE; return 1; case VALID_STATE_INVALID: MissionValidState = VALID_STATE_IDLE; return -1; case VALID_STATE_TIMEOUT: MissionValidState = VALID_STATE_IDLE; return -2; } return 0; } else { if(MissionValidState==VALID_STATE_IDLE) { //First, flush the input buffer for the socket fd_set read_fds; struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=0; while(SELECT(static_cast(Psnet_socket+1),&read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_VALIDATION)) { int addrsize; SOCKADDR_STORAGE fromaddr; udp_packet_header inpacket; FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); addrsize = sizeof(fromaddr); RECVFROM(Psnet_socket, reinterpret_cast(&inpacket), sizeof(udp_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_VALIDATION); } //only send the header, the checksum and the string length plus the null SDL_zero(PacketHeader); PacketHeader.type = UNT_VALID_MSN_REQ; PacketHeader.xcode = static_cast(Multi_fs_tracker_game_id); PacketHeader.len = static_cast(PACKED_HEADER_ONLY_SIZE + sizeof(int)+1+strlen(valid_msn->file_name)); memcpy(PacketHeader.data,valid_msn,PacketHeader.len-PACKED_HEADER_ONLY_SIZE); packet_length = SerializeValidatePacket(&PacketHeader, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); MissionValidState = VALID_STATE_WAITING; MissionValidFirstSent = timer_get_milliseconds(); MissionValidLastSent = timer_get_milliseconds(); return 0; } else { return -3; } } } // query the usertracker to validate a squad war match // call with a valid struct to validate a mission // call with NULL to poll // Return codes: // -3 Still waiting (returned if we were waiting for a tracker response and ValidateSquadWae was called with a non-NULL value // -2 Timeout waiting for tracker to respond // -1 match invalid // 0 Still waiting for response from tracker/Idle // 1 match valid int ValidateSquadWar(squad_war_request *sw_req, squad_war_response *sw_resp) { ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; ValidIdle(); if(sw_req==nullptr){ switch(SquadWarValidState){ case VALID_STATE_IDLE: return 0; case VALID_STATE_WAITING: return 0; // fill in the response case VALID_STATE_VALID: SquadWarValidState = VALID_STATE_IDLE; if(sw_resp != nullptr){ memcpy(sw_resp, &SquadWarValidateResponse, sizeof(squad_war_response)); } return 1; // fill in the response case VALID_STATE_INVALID: SquadWarValidState = VALID_STATE_IDLE; if(sw_resp != nullptr){ memcpy(sw_resp, &SquadWarValidateResponse, sizeof(squad_war_response)); } return -1; case VALID_STATE_TIMEOUT: SquadWarValidState = VALID_STATE_IDLE; return -2; } return 0; } else { if(SquadWarValidState==VALID_STATE_IDLE){ // First, flush the input buffer for the socket fd_set read_fds; struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=0; while(SELECT(static_cast(Psnet_socket+1),&read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_VALIDATION)){ int addrsize; SOCKADDR_STORAGE fromaddr; udp_packet_header inpacket; FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); addrsize = sizeof(fromaddr); RECVFROM(Psnet_socket, reinterpret_cast(&inpacket), sizeof(udp_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_VALIDATION); } // only send the header, the checksum and the string length plus the null SDL_zero(PacketHeader); PacketHeader.type = UNT_VALID_SW_MSN_REQ; PacketHeader.xcode = static_cast(Multi_fs_tracker_game_id); PacketHeader.len = static_cast(PACKED_HEADER_ONLY_SIZE + sizeof(squad_war_request)); memcpy(PacketHeader.data, sw_req, PacketHeader.len-PACKED_HEADER_ONLY_SIZE); packet_length = SerializeValidatePacket(&PacketHeader, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); SquadWarValidState = VALID_STATE_WAITING; SquadWarFirstSent = timer_get_milliseconds(); SquadWarLastSent = timer_get_milliseconds(); return 0; } else { return -3; } } } // custom packer for valid data packet since we can't easily serialize it through // the normal function static unsigned short PackValidDataRequest(const vmt_valid_data_req_struct *vreq, ubyte *data) { unsigned short packet_size = 0; PXO_ADD_DATA(vreq->type); PXO_ADD_DATA(vreq->flags); PXO_ADD_DATA(vreq->num_files); for (auto &file : vreq->files) { Assert(packet_size+sizeof(uint32_t)+file.name.length()+1 < MAX_UDP_DATA_LENGH); PXO_ADD_UINT(file.crc); PXO_ADD_STRING(file.name.c_str(), MAX_UDP_DATA_LENGH); } return packet_size; } // call with a valid struct to validate a table // call with NULL to poll // Return codes: // -3 Still waiting (returned if we were waiting for a tracker response and ValidateTable was called with a non-NULL value // -2 Timeout waiting for tracker to respond // -1 table invalid // 0 Still waiting for response from tracker/Idle // 1 table valid int ValidateData(const vmt_valid_data_req_struct *vreq) { ubyte packet_data[sizeof(udp_packet_header)]; int packet_length = 0; ValidIdle(); if (vreq == nullptr) { switch (DataValidState) { case VALID_STATE_IDLE: return 0; case VALID_STATE_WAITING: return 0; case VALID_STATE_VALID: DataValidState = VALID_STATE_IDLE; return 1; case VALID_STATE_INVALID: DataValidState = VALID_STATE_IDLE; return -1; case VALID_STATE_TIMEOUT: DataValidState = VALID_STATE_IDLE; return -2; } return 0; } else { if (DataValidState == VALID_STATE_IDLE) { fd_set read_fds; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; while ( SELECT(static_cast(Psnet_socket+1), &read_fds, nullptr, nullptr, &timeout, PSNET_TYPE_VALIDATION) ) { int addrsize; SOCKADDR_STORAGE fromaddr; udp_packet_header inpacket; FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); addrsize = sizeof(fromaddr); RECVFROM(Psnet_socket, reinterpret_cast(&inpacket), sizeof(udp_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_VALIDATION); } SDL_zero(PacketHeader); PacketHeader.type = UNT_VALID_DATA_REQ; PacketHeader.xcode = static_cast(Multi_fs_tracker_game_id); PacketHeader.len = PackValidDataRequest(vreq, PacketHeader.data) + PACKED_HEADER_ONLY_SIZE; packet_length = SerializeValidatePacket(&PacketHeader, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0,reinterpret_cast(&rtrackaddr), sizeof(rtrackaddr), PSNET_TYPE_VALIDATION); DataValidState = VALID_STATE_WAITING; DataValidStatus.clear(); DataValidFirstSent = timer_get_milliseconds(); DataValidLastSent = timer_get_milliseconds(); return 0; } else { return -1; } } } // if VDR_FLAG_STATUS was set then this should return the valid status of the // file at 'idx' in the packet bool IsDataIndexValid(const unsigned int idx) { unsigned int count = idx / 32; unsigned int index = idx % 32; if (DataValidStatus.empty() || count > DataValidStatus.size()) { return false; } return (DataValidStatus[count] & 1<