// all server side masterserver and pinging functionality #include "cube.h" #ifdef STANDALONE bool resolverwait(const char *name, ENetAddress *address) { return enet_address_set_host(address, name) >= 0; } int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remoteaddress) { int result = enet_socket_connect(sock, &remoteaddress); if(result<0) enet_socket_destroy(sock); return result; } #endif bool canreachauthserv = false; ENetSocket httpgetsend(ENetAddress &remoteaddress, const char *hostname, const char *req, const char *agent, ENetAddress *localaddress = NULL) { if (remoteaddress.host == ENET_HOST_ANY) { remoteaddress.port = masterport; #if defined AC_MASTER_DOMAIN && defined AC_MASTER_IPS if (!strcmp(hostname, AC_MASTER_DOMAIN)) { logline(ACLOG_INFO, "[%s] using %s...", AC_MASTER_IPS, AC_MASTER_DOMAIN); if (!resolverwait(AC_MASTER_IPS, &remoteaddress)) return ENET_SOCKET_NULL; } else #endif { logline(ACLOG_INFO, "looking up %s...", hostname); if (!resolverwait(hostname, &remoteaddress)) return ENET_SOCKET_NULL; char hn[1024]; logline(ACLOG_INFO, "[%s] resolved %s", (!enet_address_get_host_ip(&remoteaddress, hn, sizeof(hn))) ? hn : "unknown", hostname); } } ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM); if (sock != ENET_SOCKET_NULL && localaddress && enet_socket_bind(sock, localaddress) < 0) { enet_socket_destroy(sock); sock = ENET_SOCKET_NULL; } if (sock == ENET_SOCKET_NULL || connectwithtimeout(sock, hostname, remoteaddress)<0) { logline(ACLOG_WARNING, sock == ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); return ENET_SOCKET_NULL; } ENetBuffer buf; defformatstring(httpget)("GET %s HTTP/1.0\nHost: %s\nUser-Agent: %s\n\n", req, hostname, agent); buf.data = httpget; buf.dataLength = strlen((char *)buf.data); //logline(ACLOG_INFO, "sending request to %s...", hostname); logline(ACLOG_VERBOSE, "sending request to %s: GET %s", hostname, req); enet_socket_send(sock, NULL, &buf, 1); canreachauthserv = true; return sock; } bool httpgetreceive(ENetSocket sock, ENetBuffer &buf, int timeout = 0) { if (sock == ENET_SOCKET_NULL) return false; enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; if (enet_socket_wait(sock, &events, timeout) >= 0 && events) { int len = enet_socket_receive(sock, NULL, &buf, 1); if (len <= 0) { enet_socket_destroy(sock); return false; } buf.data = ((char *)buf.data) + len; ((char*)buf.data)[0] = 0; buf.dataLength -= len; } return true; } uchar *stripheader(uchar *b) { char *s = strstr((char *)b, "\n\r\n"); if (!s) s = strstr((char *)b, "\n\n"); return s ? (uchar *)s : b; } ENetSocket mastersock = ENET_SOCKET_NULL; ENetAddress masteraddress = { ENET_HOST_ANY, 80 }; ENetAddress serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY }; string masterbase, masterpath; int masterport = AC_MASTER_PORT; int lastupdatemaster = INT_MIN, lastresolvemaster = INT_MIN, lastauthreqprocessed = INT_MIN; #define MAXMASTERTRANS MAXTRANS // enlarge if response is big... uchar masterrep[MAXMASTERTRANS]; ENetBuffer masterb; // FIXME: a linked list makes more sense for these: vector<authrequest> authrequests; vector<connectrequest> connectrequests; enum { MSR_REG = 0, MSR_CONNECT, MSR_AUTH_ANSWER }; struct msrequest { int type; union { void *data; authrequest *a; connectrequest *c; }; } *currentmsrequest = NULL; void freeconnectcheck(int cn) { if (currentmsrequest && currentmsrequest->type == MSR_CONNECT && currentmsrequest->c && cn == currentmsrequest->c->cn) { delete currentmsrequest->c; DELETEP(currentmsrequest); } loopv(connectrequests) if (connectrequests[i].cn == cn) connectrequests.remove(i--); } void connectcheck(int cn, int guid, const char *hostname, int authreq, int authuser) { freeconnectcheck(cn); extern bool isdedicated; if (!isdedicated) return; connectrequest &creq = connectrequests.add(); creq.cn = cn; creq.guid = guid; creq.hostname = newstring(hostname); creq.id = authreq; creq.user = authuser; } // send alive signal to masterserver every 40 minutes of uptime #define MSKEEPALIVE (40*60*1000) // re-resolve the master-server domain every 4 hours #define MSRERESOLVE (4*60*60*1000) static inline void updatemasterserver(int millis, int port) { if (mastersock != ENET_SOCKET_NULL || currentmsrequest) return; // busy string path; path[0] = '\0'; if (millis > lastupdatemaster + MSKEEPALIVE) { logline(ACLOG_INFO, "sending registration request to master server"); currentmsrequest = new msrequest; currentmsrequest->type = MSR_REG; currentmsrequest->data = NULL; formatstring(path)("%s/r?v=%lu&p=%u&guid32=%lu", masterpath, PROTOCOL_VERSION, port, *&genguid(546545656, 23413376U, 3453455, "h6ji54ehjwo345gjio34s5jig")); lastupdatemaster = millis + 1; } else if (millis > lastauthreqprocessed + 2500 && authrequests.length()) { authrequest *r = new authrequest(authrequests.remove(0)); currentmsrequest = new msrequest; currentmsrequest->type = MSR_AUTH_ANSWER; currentmsrequest->a = r; char cbuf[2*48+1], abuf[2*32+1]; cbuf[2*48] = '\0'; abuf[2*32] = '\0'; loopi(48) { cbuf[i*2] = "0123456789abcdef"[r->crandom[i] >> 4]; cbuf[i*2+1] = "0123456789abcdef"[r->crandom[i] & 0xF]; } loopi(32) { abuf[i*2] = "0123456789abcdef"[r->canswer[i] >> 4]; abuf[i*2+1] = "0123456789abcdef"[r->canswer[i] & 0xF]; } formatstring(path)("%s/v?p=%u&i=%lu&a=%s&c=%s", masterpath, port, r->id, abuf, cbuf); lastauthreqprocessed = millis; } else if (connectrequests.length()) { if (!canreachauthserv) connectrequests.shrink(0); else { connectrequest *c = new connectrequest(connectrequests.remove(0)); currentmsrequest = new msrequest; currentmsrequest->type = MSR_CONNECT; currentmsrequest->c = c; // FIXME: this assumes we have IPv4 hostnames if (c->id) formatstring(path)("%s/a?p=%u&a=::ffff:%s&guid32=%lu&i=%u&u=%u", masterpath, port, c->hostname, c->guid, c->id, c->user); else formatstring(path)("%s/a?p=%u&a=::ffff:%s&guid32=%lu", masterpath, port, c->hostname, c->guid); delete[] c->hostname; } } if (!path[0]) return; // no request if (millis > lastresolvemaster + MSRERESOLVE) { masteraddress.host = ENET_HOST_ANY; lastresolvemaster = millis + 1; } defformatstring(agent)("ACR-Server/%d", AC_VERSION); mastersock = httpgetsend(masteraddress, masterbase, path, agent, &serveraddress); masterrep[0] = 0; masterb.data = masterrep; masterb.dataLength = MAXMASTERTRANS - 1; } void checkmasterreply() { if (mastersock == ENET_SOCKET_NULL || httpgetreceive(mastersock, masterb)) return; mastersock = ENET_SOCKET_NULL; char replytext[MAXMASTERTRANS]; char *text = replytext; filtertext(text, (const char *)stripheader(masterrep), 2, MAXMASTERTRANS - 1); while (isspace(*text)) text++; char *replytoken = strtok(text, "\n"); while (replytoken) { // process commands char *tp = replytoken; if (*tp++ == '*') { bool error = true; if (currentmsrequest) { if (*tp == 'a' || *tp == 'b') // verdict: allow/ban connect { if (currentmsrequest->type == MSR_CONNECT && currentmsrequest->c) { // extern void mastermute(int cn); extern void masterdisc(int cn, int result); int disc = DISC_NONE; if (*tp == 'b') switch (*++tp) { // GOOD reasons case 'm': // muted and not allowed to speak // mastermute(currentmsrequest->c->cn); // fallthrough case 'w': // IP whitelisted, not actually a banned verdict disc = DISC_NONE; break; // BAD reasons case 'i': // IP banned disc = DISC_MBAN; break; default: // unknown reason disc = DISC_NUM; break; } error = false; masterdisc(currentmsrequest->c->cn, disc); } } else if (*tp == 'd' || *tp == 'f' || *tp == 's' || *tp == 'c') // auth { char t = *tp++; /*char *bar = strchr(tp, '|'); if(bar) *bar = 0; uint authid = atoi(tp); if(bar && bar[1]) tp = bar + 1; */ error = true; uint authid = 0; if (currentmsrequest->type == MSR_AUTH_ANSWER && currentmsrequest->a) authid = currentmsrequest->a->id; else if (currentmsrequest->type == MSR_CONNECT && currentmsrequest->c) authid = currentmsrequest->c->id; if (authid) switch (t) { case 'd': // fail to claim case 'f': // failure error = false; extern void authfailed(uint id, bool fail); authfailed(authid, t == 'd'); break; case 's': // succeed { if (!*tp) break; char privk = *tp++; if (!privk) break; string name; filtertext(name, tp, 1, MAXNAMELEN); if (!*name) copystring(name, "<unnamed>"); error = false; extern void authsucceeded(uint id, int priv, const char *name); authsucceeded(authid, privk >= '0' && privk <= '3' ? privk - '0' : -1, name); break; } case 'c': // challenge if (!*tp) break; error = false; extern void authchallenged(uint id, const char *chal); authchallenged(authid, tp); break; } } } if (error) logline(ACLOG_INFO, "masterserver sent an unknown command: %s", replytoken); } else { while (isspace(*replytoken)) replytoken++; if (*replytoken) logline(ACLOG_INFO, "masterserver reply: %s", replytoken); } replytoken = strtok(NULL, "\n"); } if (currentmsrequest) { switch (currentmsrequest->type) { case MSR_REG: break; case MSR_AUTH_ANSWER: delete currentmsrequest->a; break; case MSR_CONNECT: delete currentmsrequest->c; break; } DELETEP(currentmsrequest); } } ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL; extern int getpongflags(enet_uint32 ip); void serverms(int mode, int muts, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version) { checkmasterreply(); updatemasterserver(millis, localaddr.port); static ENetSocketSet sockset; ENET_SOCKETSET_EMPTY(sockset); ENetSocket maxsock = pongsock; ENET_SOCKETSET_ADD(sockset, pongsock); if(mastersock != ENET_SOCKET_NULL) { maxsock = max(maxsock, mastersock); ENET_SOCKETSET_ADD(sockset, mastersock); } if(lansock != ENET_SOCKET_NULL) { maxsock = max(maxsock, lansock); ENET_SOCKETSET_ADD(sockset, lansock); } if(enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0) return; // reply all server info requests static uchar data[MAXTRANS]; ENetBuffer buf; ENetAddress addr; buf.data = data; int len; loopi(2) { ENetSocket sock = i ? lansock : pongsock; if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(sockset, sock)) continue; buf.dataLength = sizeof(data); len = enet_socket_receive(sock, &addr, &buf, 1); if(len < 0) continue; // ping & pong buf ucharbuf pi(data, len), po(&data[len], sizeof(data)-len); bool std = false; if(getint(pi) != 0) // std pong { extern struct servercommandline scl; extern string servdesc_current; (*mnum)++; *mrec += len; std = true; putint(po, protocol_version); putint(po, mode); putint(po, muts); putint(po, numplayers); putint(po, minremain); sendstring(smapname, po); sendstring(servdesc_current, po); putint(po, scl.maxclients); putint(po, getpongflags(addr.host)); if(pi.remaining()) { int query = getint(pi); switch(query) { case EXTPING_NAMELIST: { extern void extping_namelist(ucharbuf &p); putint(po, query); extping_namelist(po); break; } case EXTPING_SERVERINFO: { extern void extping_serverinfo(ucharbuf &pi, ucharbuf &po); putint(po, query); extping_serverinfo(pi, po); break; } case EXTPING_MAPROT: { extern void extping_maprot(ucharbuf &po); putint(po, query); extping_maprot(po); break; } case EXTPING_UPLINKSTATS: { extern void extping_uplinkstats(ucharbuf &po); putint(po, query); extping_uplinkstats(po); break; } case EXTPING_NOP: default: putint(po, EXTPING_NOP); break; } } } else // ext pong - additional server infos { (*cnum)++; *crec += len; int extcmd = getint(pi); putint(po, EXT_ACK); putint(po, EXT_VERSION); switch(extcmd) { case EXT_UPTIME: // uptime in seconds { putint(po, uint(millis)/1000); break; } case EXT_PLAYERSTATS: // playerstats { int cn = getint(pi); // get requested player, -1 for all if(!valid_client(cn) && cn != -1) { putint(po, EXT_ERROR); break; } putint(po, EXT_ERROR_NONE); // add no error flag int bpos = po.length(); // remember buffer position putint(po, EXT_PLAYERSTATS_RESP_IDS); // send player ids following extinfo_cnbuf(po, cn); *csend += int(buf.dataLength = len + po.length()); enet_socket_send(pongsock, &addr, &buf, 1); // send all available player ids po.len = bpos; extinfo_statsbuf(po, cn, bpos, pongsock, addr, buf, len, csend); return; } case EXT_TEAMSCORE: extinfo_teamscorebuf(po); break; default: putint(po,EXT_ERROR); break; } } buf.dataLength = len + po.length(); enet_socket_send(pongsock, &addr, &buf, 1); if(std) *msend += (int)buf.dataLength; else *csend += (int)buf.dataLength; } } // this function should be made better, because it is used just ONCE (no need of so much parameters) void servermsinit(const char *master, const char *ip, int infoport, bool listen) { const char *mid = strstr(master, "/"); if (mid) { copystring(masterbase, master, mid - master + 1); copystring(masterpath, mid + 1); } else { copystring(masterbase, master); copystring(masterpath, ""); } if(listen) { ENetAddress address = { ENET_HOST_ANY, (enet_uint16)infoport }; if(*ip) { if(enet_address_set_host(&address, ip)<0) logline(ACLOG_WARNING, "server ip not resolved"); else serveraddress.host = address.host; } pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) { enet_socket_destroy(pongsock); pongsock = ENET_SOCKET_NULL; } if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket"); else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); address.port = CUBE_SERVINFO_PORT_LAN; lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) { enet_socket_destroy(lansock); lansock = ENET_SOCKET_NULL; } if(lansock == ENET_SOCKET_NULL) logline(ACLOG_WARNING, "could not create LAN server info socket"); else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); } }