// 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);
}
}
153
pages