From d6af5be3da38538e74298c33748c049d1816fb67 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 21 Mar 2016 13:58:27 +0100 Subject: [PATCH] add auth to client server connect protocol --- config/serverparameters.cfg | 11 +++ source/src/bot/bot.cpp | 1 - source/src/clientgame.cpp | 1 - source/src/clients2c.cpp | 89 +++++++++++++++++++- source/src/crypto.cpp | 2 +- source/src/protocol.cpp | 18 +++- source/src/protocol.h | 7 +- source/src/protos.h | 24 ++---- source/src/server.cpp | 156 ++++++++++++++++++++++++++++++++--- source/src/server.h | 14 +++- source/src/serverbrowser.cpp | 1 - source/src/serverfiles.h | 4 +- source/src/serverms.cpp | 4 +- 13 files changed, 287 insertions(+), 45 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 75e5f639..456fdd4c 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -9,6 +9,17 @@ +// **** Server setup settings **** + +// auth_verify_ip Verify server IP reported by the client during auth +// integer [0..1], default 0, 0:disabled, 1:enabled +auth_verify_ip:0 + +// mandatory_auth Enforce IDs for all clients even on unlisted server/LAN game +// integer [0..1], default 1, 0:disabled, 1:enabled +mandatory_auth:1 + + // **** Maprot settings **** // gamepenalty_cutoff Number of minutes to remember that a map+mode combination has been played diff --git a/source/src/bot/bot.cpp b/source/src/bot/bot.cpp index 1fa0c83d..06ff761b 100644 --- a/source/src/bot/bot.cpp +++ b/source/src/bot/bot.cpp @@ -17,7 +17,6 @@ vector bots; extern int triggertime; extern itemstat itemstats[]; -extern ENetHost *clienthost; const vec g_vecZero(0, 0, 0); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index c26b753a..816c5d7f 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -370,7 +370,6 @@ const char *currentserver(int i) // [client version] // using the curpeer directly we can get the info of our currently connected server string r; r[0] = '\0'; - extern ENetPeer *curpeer; if(curpeer) { switch(i) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 565bce87..8a955400 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -320,6 +320,9 @@ void onChangeVote(int mod, int id) VARP(voicecomsounds, 0, 1, 2); bool medals_arrived=0; medalsst a_medals[END_MDS]; + +struct session_s { enet_uint32 serverip, clientip, clientipcensored, curpeerip; int curpeerport, serverclock, clientclock, cn, clientsalt; uchar serverpubkey[32], clientsignature[64]; } session; + void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { static char text[MAXTRANS]; @@ -356,7 +359,12 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { case SV_SERVINFO: // welcome message from the server { - int mycn = getint(p), prot = getint(p); + string tmp1, tmp2; + session_s *s = &session; + memset(s, 0, sizeof(session_s)); + + s->cn = getint(p); + int prot = getint(p); if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION)) { conoutf("\f3incompatible game protocol (local protocol: %d :: server protocol: %d)", CUR_PROTOCOL_VERSION, prot); @@ -365,9 +373,84 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) else disconnect(); return; } - sessionid = getint(p); - player1->clientnum = mycn; + int req_auth = getint(p); + if(!sk && req_auth) + { + conoutf("\f3server requires client ID to connect"); + conoutf("\f3please unlock your ID (by entering the corect password) or generate a new one"); + disconnect(); + return; + } if(getint(p) > 0) conoutf("INFO: this server is password protected"); + sessionid = getint(p); // salt + s->serverip = getip4(p); + s->clientip = getip4(p); + s->clientipcensored = getip4(p); + s->serverclock = getint(p); + char cc[3] = { (char)getint(p), (char)getint(p), 0 }; + filtercountrycode(cc, cc); + p.get(s->serverpubkey, 32); + entropy_get((uchar *)&(s->clientsalt), sizeof(int)); + s->clientclock = (int) (time(NULL) / (time_t) 60); + int clockoffset = s->serverclock - s->clientclock; + if(curpeer && iabs(clockoffset) > 60 * 24) conoutf("\f3warning: server and client clock are offset more than one day (%d minutes)", clockoffset); + + clientlogf("own IP: %s, censored own IP: %s, %s, clock offset %d hours %d minutes", iptoa(s->clientip, tmp1), iptoa(s->clientipcensored, tmp2), cc, clockoffset / 60, clockoffset % 60); + if(curpeer && s->serverip) + { + s->curpeerip = ENET_NET_TO_HOST_32(curpeer->address.host); + s->curpeerport = curpeer->address.port; + } + if(s->curpeerip && s->curpeerip != s->serverip) conoutf("\f3warning: server IP mismatch (true: %s, reported: %s)", iptoa(s->curpeerip, tmp1), iptoa(s->serverip, tmp2)); + + defformatstring(challenge)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", sessionid, s->cn, iptoa(s->clientipcensored, tmp1), cc, iptoa(s->serverip, tmp2), s->curpeerport); + concatformatstring(challenge, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, s->serverpubkey, 32), s->serverclock, s->clientclock, s->clientsalt); + clientlogf("auth challenge: %s", challenge); + int chlen = (int)strlen(challenge); + if(chlen >= MAXSTRLEN - 65) + { + conoutf("\f3ERROR: oversized auth challenge (%d), can't connect", chlen); + disconnect(); + return; + } + if(sk) ed25519_sign((uchar*)challenge, NULL, (uchar*)challenge, chlen, sk); + + packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(pr, SV_SERVINFO_RESPONSE); + putint(pr, s->clientsalt); + putint(pr, s->clientclock); + putint(pr, s->curpeerport); + putip4(pr, s->curpeerip); + int wantauth = sk && curpeer; + putint(pr, wantauth); + if(wantauth) + { + memcpy(s->clientsignature, challenge, 64); + pr.put(sk + 32, 32); + pr.put(s->clientsignature, 64); + } + sendpackettoserv(1, pr.finalize()); + break; + } + + case SV_SERVINFO_CONTD: // 2nd welcome message from the server + { + session_s *s = &session; + string tmp, servauth; + int auth = getint(p); + if(auth) + { + formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); + memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); + p.get((uchar*)servauth, 64); + } + if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); + if(sk && auth) + { + if(!ed25519_sign_check((uchar*)servauth, auth + 64, s->serverpubkey)) conoutf("\f3server authentication failed"); + else clientlogf("server authenticated as %s", bin2hex(tmp, s->serverpubkey, 32)); + } + player1->clientnum = s->cn; sendintro(); break; } diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index 1d39c514..ef1c4df9 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -726,7 +726,7 @@ VARP(authmemusage, 2, 24, (1<<10) - 1); // megabytes of RAM to use for passw VARP(authrounds, 0, 0, INT_MAX - 1); // create new password hashes with a fixed number of rounds (if authrounds > 0) VARP(authmaxtime, 1<<9, 1<<12, 1<<16); // create new password hashes with a fixed amount of time (in ms) -static uchar *sk = NULL; // game key +uchar *sk = NULL; // game key static struct { uchar *salt, *priv; uint pwdcfg; } passdargs; static int passdeferred(void *pass) // decrypt the private key in the background diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index bc0f4352..0887c75a 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -422,6 +422,17 @@ void filterlang(char *d, const char *s) *d = '\0'; } +void filtercountrycode(char *d, const char *s) // returns exactly two uppercase chars or "--" +{ + d[0] = d[1] = '-'; + d[2] = '\0'; + if(isalpha(s[0]) && isalpha(s[1]) && !s[2]) + { + d[0] = toupper(s[0]); + d[1] = toupper(s[1]); + } +} + void trimtrailingwhitespace(char *s) { for(int n = (int)strlen(s) - 1; n >= 0 && isspace(s[n]); n--) @@ -485,7 +496,8 @@ char *gmode_enum(int gm, char *buf) // convert mode bitmask to string with sorte static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { - SV_SERVINFO, 5, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, + SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, + SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_AUTHT, 0, SV_AUTHREQ, 0, SV_AUTHTRY, 0, SV_AUTHANS, 0, SV_AUTHCHAL, 0, SV_GIBDIED, 5, SV_DIED, 5, SV_GIBDAMAGE, 7, SV_DAMAGE, 7, SV_HITPUSH, 6, SV_SHOTFX, 6, SV_THROWNADE, 8, @@ -524,8 +536,8 @@ int msgsizelookup(int msg) const char *disc_reason(int reason) { - static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync" }, - *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"" }, + static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync", "auth failed" }, + *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"", "server overloaded" }, *removed[] = { "removed from server", "failed admin login", "inappropriate nickname", "annoyance limit exceeded", "SPAM", "DOS", "ludicrous speed", "fishslapped", "afk" }, *kick[] = { "kicked", "banned", "kicked by vote", "banned by vote", "kicked by server operator", "banned by server operator", "auto kicked - teamkilling", "auto banned - teamkilling", "auto kicked - friendly fire", "auto banned - friendly fire", "auto kick", "auto ban" }; diff --git a/source/src/protocol.h b/source/src/protocol.h index 57afd1a9..225c5c19 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -6,6 +6,7 @@ #define CUBE_SERVINFO_PORT_LAN 28762 #define CUBE_SERVINFO_PORT(serverport) (serverport+1) #define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1) +#define CLIENTIPCENSOR 12 // blank out the last 12 bits of client's IPs before telling them to others (compromise between "16" from AC 0.9x and "8" from AC 1.1) #define PROTOCOL_VERSION 1203 // bump when protocol changes (use negative numbers for mods!) #define DEMO_VERSION 2 // bump when demo format changes #define DEMO_MAGIC "ASSAULTCUBE_DEMO" @@ -24,7 +25,7 @@ extern bool modprotocol; // network messages codes, c2s, c2c, s2c enum { - SV_SERVINFO = 0, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, + SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_AUTHT, SV_AUTHREQ, SV_AUTHTRY, SV_AUTHANS, SV_AUTHCHAL, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, @@ -75,8 +76,8 @@ enum { EDITXY_HEIGHT = 0, EDITXY_TEX, EDITXY_TYPE, EDITXY_VDELTA, EDITXY_EQUALIS #define DNF 100.0f #define DVELF 4.0f -enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, // div. protocol errors - DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, // connection refused +enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, DISC_AUTH, // div. protocol errors + DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, DISC_OVERLOAD, // connection refused DISC_REMOVED = 32, DISC_SOPLOGINFAIL, DISC_BADNICK, DISC_ANNOY, DISC_SPAM, DISC_DOS, DISC_SPEEDING, DISC_FISHSLAP, DISC_AFK, // disconnected DISC_KICK = 48, DISC_BAN, DISC_VOTEKICK, DISC_VOTEBAN, DISC_OPKICK, DISC_OPBAN, DISC_TKKICK, DISC_TKBAN, DISC_FFKICK, DISC_FFBAN, DISC_AUTOKICK, DISC_AUTOBAN, DISC_UNKNOWN = 99, DISC_BECAUSE }; diff --git a/source/src/protos.h b/source/src/protos.h index c8ac7f15..3dfd3706 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -521,6 +521,7 @@ extern void gets2c(); extern void c2sinfo(playerent *d); extern void c2skeepalive(); extern void neterr(const char *s); +extern ENetPeer *curpeer; extern int getclientnum(); extern void getmap(char *name = NULL, char *callback = NULL); extern void newteam(char *name); @@ -964,6 +965,9 @@ extern void tigerhash_finish(uchar *hash, void *state); extern const char *bin2hex(char *d, const uchar *s, int len); extern int hex2bin(uchar *d, const char *s, int maxlen); extern void ed25519_pubkey_from_private(uchar *pubkey, const uchar *privkey); +extern void ed25519_sign(uchar *sm, int *smlen, const uchar *m, int mlen, const uchar *sk); +extern uchar *ed25519_sign_check(uchar *sm, int smlen, const uchar *pk); +extern uchar *sk; // client game key extern void loadcertdir(); // load all certs in "config/certs" #if 0 // crypto // for AUTH @@ -1070,11 +1074,13 @@ extern bool validmapname(const char *s); // checks for length, allowed chars and extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); extern void filterlang(char *d, const char *s); +extern void filtercountrycode(char *d, const char *s); // returns exactly two uppercase chars or "--" extern void trimtrailingwhitespace(char *s); extern int msgsizelookup(int msg); extern const char *disc_reason(int reason); extern string mastername; extern int masterport; +extern bool usemaster; extern ENetSocket connectmaster(); extern void serverms(int mode, 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, const char *servdesccur, int _interm); extern const char *genpwdhash(const char *name, const char *pwd, int salt); @@ -1146,8 +1152,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath; - uchar *ssk; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -1157,8 +1162,7 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), - ssk(NULL), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { @@ -1176,17 +1180,7 @@ struct servercommandline // client: dtwhzbsave switch(arg[1]) { // todo: gjlqEJQU - case 'Y': // private server key - { - uchar temp[64]; - if(hex2bin(temp, a, 32) == 32) - { - if(!ssk) ssk = new uchar[64]; - memcpy(ssk, temp, 32); - ed25519_pubkey_from_private(ssk + 32, ssk); - } - break; - } + case 'Y': ssk = a; break; // private server key case '-': if(!strncmp(arg, "--demofilenameformat=", 21)) { diff --git a/source/src/server.cpp b/source/src/server.cpp index b403635e..e120c929 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -74,6 +74,9 @@ struct servergame // config vector servermaps, gamesuggestions; // all available maps kept in memory +string servpubkey; +uchar ssk[64] = { 0 }, *spk = ssk + 32; +enet_uint32 servownip; // IP address of this server, as seen by the master or other clients/servers servercontroller *svcctrl = NULL; serverparameter serverparameters; @@ -91,7 +94,7 @@ serverinfofile serverinfomotd; bool isdedicated = false; ENetHost *serverhost = NULL; -int laststatus = 0, servmillis = 0; +int laststatus = 0, servmillis = 0, servclock = 0; vector clients; vector worldstates; @@ -116,7 +119,7 @@ void poll_serverthreads() // called once per mainloop-timeslice static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; static bool debugmaprot = true; - switch(stage) + switch(stage) // cycle through some tasks once per minute (mostly file polling) { default: // start first thread { @@ -125,6 +128,7 @@ void poll_serverthreads() // called once per mainloop-timeslice // wake readmapsthread logline(ACLOG_INFO,"waking readmapsthread"); startnewservermapsepoch = true; + servclock = (int) (time(NULL) / (time_t) 60); readmapsthread_sem->post(); stage = 1; lastworkerthreadstart = servmillis; @@ -626,7 +630,7 @@ void sendf(int cn, int chan, const char *format, ...) sendstring(va_arg(args, const char *), p); break; - case 'm': // raw message block (unused) + case 'm': // raw message block { int n = va_arg(args, int); p.put(va_arg(args, uchar *), n); @@ -2770,7 +2774,8 @@ bool restorescore(client &c) void sendservinfo(client &c) { - sendf(c.clientnum, 1, "ri5", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0); + sendf(c.clientnum, 1, "ri6IIIi3k", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.needsauth ? 1 : 0, scl.serverpassword[0] ? 1 : 0, c.salt, + servownip, c.ip, c.ip_censored, (c.connectclock = servclock), c.country[0], c.country[1], spk); } void putinitclient(client &c, packetbuf &p) @@ -2918,9 +2923,93 @@ int checktype(int type, client *cl) return type; } + +// process auth calculations in separate thread, since it may take a while + +struct servinfoauth +{ + string clientmsg, servermsg; // client message to verify, server message to sign + uchar clientpubkey[32]; + int type, wantauth, cn, clientsequence, clientmsglen, servermsglen, success; +}; + +ringbuf servinfoauths; +volatile servinfoauth *servinfoauth_dropbox = NULL; +volatile bool servinfoauth_done = false; +sl_semaphore servinfoauth_deferred(0, NULL); + +int deferredprocessingthread(void *nop) +{ + for(;;) + { + servinfoauth_deferred.wait(); + if(!servinfoauth_done && servinfoauths.length()) + { + servinfoauth *sa = servinfoauths.remove(); + servinfoauth_dropbox = sa; + sa->success = 0; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(!sa->wantauth) sa->success = 1; + else if(sa->clientmsglen && ed25519_sign_check((uchar*)sa->clientmsg, sa->clientmsglen + 64, sa->clientpubkey)) + { // client message checks out, now sign the answer + ed25519_sign((uchar*)sa->servermsg, NULL, (uchar*)sa->servermsg, sa->servermsglen, ssk); + sa->success = 1; + } + break; + } + } + servinfoauth_done = true; + } + } + return 0; +} + +void polldeferredprocessing() +{ + static void *deferredprocessingthread_ti = NULL; + if(!deferredprocessingthread_ti) deferredprocessingthread_ti = sl_createthread(deferredprocessingthread, NULL); + + if(servinfoauth_done) + { + servinfoauth *sa = (servinfoauth *)servinfoauth_dropbox; + servinfoauth_done = false; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(clients[sa->cn]->clientsequence == sa->clientsequence) // make sure, it's still the same client using that cn + { + if(!sa->success) disconnect_client(sa->cn, DISC_AUTH); + else + { // continue connecting + client *cl = clients[sa->cn]; + if(sa->wantauth) + { + memcpy(cl->pubkey, sa->clientpubkey, 32); + memcpy(cl->servinforesponse, sa->clientmsg, sa->clientmsglen + 64); + bin2hex(cl->pubkeyhex, cl->pubkey, 32); + cl->needsauth = true; // may have been voluntary, but it's done + sendf(sa->cn, 1, "riim", SV_SERVINFO_CONTD, 1, 64, sa->servermsg); + } + else sendf(sa->cn, 1, "rii", SV_SERVINFO_CONTD, 0); + } + } + break; + } + } + delete sa; + } + if(!servinfoauths.empty() && servinfoauth_deferred.getvalue() < 1) servinfoauth_deferred.post(); +} + // server side processing of updates: does very little and most state is tracked client only // could be extended to move more gameplay to server (at expense of lag) +SERVPARLIST(auth_verify_ip, 0, 0, 0, endis, "sVerify server IP reported by the client during auth"); + void process(ENetPacket *packet, int sender, int chan) { ucharbuf p(packet->data, packet->dataLength); @@ -2934,9 +3023,44 @@ void process(ENetPacket *packet, int sender, int chan) int clientrole = CR_DEFAULT; if(chan==0) return; - else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT); - else + else if(chan!=1 || ((type = getint(p)) != SV_SERVINFO_RESPONSE && type != SV_CONNECT)) disconnect_client(sender, DISC_TAGT); + else if(type == SV_SERVINFO_RESPONSE) { + if(servinfoauths.full()) disconnect_client(sender, DISC_OVERLOAD); + else + { + string tmp1, tmp2; + servinfoauth *sa = servinfoauths.stage(new servinfoauth); + memset(sa, 0, sizeof(servinfoauth)); + int clientsalt = getint(p), clientclock = getint(p), curpeerport = getint(p); + enet_uint32 curpeerip = getip4(p); + if((sa->wantauth = getint(p))) + { + p.get(sa->clientpubkey, 32); + p.get((uchar*)sa->clientmsg, 64); + } + bool ipfail = servownip && servownip != curpeerip; + if(ipfail) logline(ACLOG_INFO, "[%s] client reported different server IP %s:%d (known own IP is %s)", cl->hostname, iptoa(curpeerip, tmp1), curpeerport, iptoa(servownip, tmp2)); + formatstring(text)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", cl->salt, sender, iptoa(cl->ip_censored, tmp1), cl->country, iptoa(servownip, tmp2), curpeerport); + concatformatstring(text, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, spk, 32), cl->connectclock, clientclock, clientsalt); + sa->clientmsglen = (int)strlen(text); + if(sa->clientmsglen < MAXSTRLEN - 65) memcpy(sa->clientmsg + 64, text, sa->clientmsglen); + else sa->clientmsglen = 0; + formatstring(sa->servermsg)("SERVINFOSIGNED<%s>", bin2hex(tmp1, (uchar*)sa->clientmsg, 64)); + sa->servermsglen = (int)strlen(sa->servermsg); + if((!sa->wantauth && cl->needsauth) || (ipfail && auth_verify_ip)) { delete sa; disconnect_client(sender, DISC_AUTH); } + else + { + sa->type = type; + sa->cn = sender; + sa->clientsequence = cl->clientsequence; + servinfoauths.commit(); + } + } + return; + } + else + { // SV_CONNECT cl->acversion = getint(p); cl->acbuildtype = getint(p); defformatstring(tags)(", AC: %d|%x", cl->acversion, cl->acbuildtype); @@ -3950,6 +4074,7 @@ void localclienttoserver(int chan, ENetPacket *packet) client &addclient() { + static int clientsequencecounter = 0; client *c = NULL; loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; } if(!c) @@ -3959,6 +4084,7 @@ client &addclient() clients.add(c); } c->reset(); + c->clientsequence = ++clientsequencecounter; return *c; } @@ -4114,6 +4240,8 @@ void linequalitystats(int elapsed) } } +SERVPARLIST(mandatory_auth, 0, 1, 0, endis, "sEnforce IDs for all clients even on unlisted server/LAN game"); + void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop { static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0; @@ -4189,6 +4317,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo } resetserverifempty(); + polldeferredprocessing(); if(!isdedicated) return; // below is network only @@ -4239,18 +4368,21 @@ void serverslice(uint timeout) // main server update, called from cube main lo { client &c = addclient(); c.type = ST_TCPIP; + c.needsauth = usemaster || mandatory_auth; c.peer = event.peer; c.peer->data = (void *)(size_t)c.clientnum; c.connectmillis = servmillis; c.state.state = CS_SPECTATE; - c.salt = rnd(0x1000000)*((servmillis%1000)+1); + c.salt = (rnd(0x1000000)*((servmillis%1000)+1)) ^ rnd(0x1000000); char hn[1024]; copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); c.ip = ENET_NET_TO_HOST_32(c.peer->address.host); + c.ip_censored = c.ip & ~((1 << CLIENTIPCENSOR) - 1); const char *gi = geoiplist.check(c.ip); copystring(c.country, gi ? gi : "--", 3); + filtercountrycode(c.country, c.country); entropy_add_byte(c.ip); - logline(ACLOG_INFO,"[%s] client connected", c.hostname); + logline(ACLOG_INFO,"[%s] client connected (%s)", c.hostname, c.country); sendservinfo(c); totalclients++; break; @@ -4437,6 +4569,7 @@ void localconnect() c.type = ST_LOCAL; c.role = CR_ADMIN; c.salt = 0; + c.needsauth = false; copystring(c.hostname, "local"); sendservinfo(c); } @@ -4462,6 +4595,10 @@ void quitproc(int param) void initserver(bool dedicated) { + copystring(servpubkey, ""); + if(scl.ssk && hex2bin(ssk, scl.ssk, 32) != 32) scl.ssk = NULL; + ed25519_pubkey_from_private(spk, ssk); + bin2hex(servpubkey, spk, 32); sg->smapname[0] = '\0'; // start logging @@ -4484,8 +4621,7 @@ void initserver(bool dedicated) if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; - string pubkey; - if(scl.ssk) logline(ACLOG_VERBOSE,"server public key: %s", bin2hex(pubkey, scl.ssk + 32, 32)); + if(scl.ssk) logline(ACLOG_INFO,"server public key: %s", servpubkey); maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); diff --git a/source/src/server.h b/source/src/server.h index 55ded46e..e6093c98 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -226,11 +226,15 @@ struct medals struct client // server side version of "dynent" type { int type; - int clientnum; + int clientnum, clientsequence; ENetPeer *peer; string hostname; - enet_uint32 ip; + enet_uint32 ip, ip_censored; + int connectclock; char country[4]; + uchar pubkey[32]; + char pubkeyhex[68]; + string servinforesponse; string name; int team; char lang[3]; @@ -242,6 +246,7 @@ struct client // server side version of "dynent" type int mute, spam, lastvc; // server side voice comm spam control int acversion, acbuildtype; bool isauthed; // for passworded servers + bool needsauth; bool haswelcome; bool isonrightmap, loggedwrongmap, freshgame; bool timesync; @@ -327,6 +332,8 @@ struct client // server side version of "dynent" type void reset() { name[0] = pwd[0] = demoflags = 0; + ip = ip_censored = 0; + pubkeyhex[0] = '\0'; bottomRTT = ping = 9999; team = TEAM_SPECT; state.state = CS_SPECTATE; @@ -426,7 +433,8 @@ extern bool isdedicated; const char *messagenames[SV_NUM] = { - "SV_SERVINFO", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", + "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", + "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL", "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE", diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 0f07cb46..8e6ca75b 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -293,7 +293,6 @@ serverinfo *findserverinfo(ENetAddress address) serverinfo *getconnectedserverinfo() { - extern ENetPeer *curpeer; if(!curpeer) return NULL; return findserverinfo(curpeer->address); } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 14420188..578681f9 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1704,8 +1704,8 @@ struct serverparameter : serverconfigfile #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "" }; -const char *siddocchaptersorting = "gvmd"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "" }; +const char *siddocchaptersorting = "sgvmd"; void serverparameters_dumpdocu(char *fname) { diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index b52f77eb..2487ea3f 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -169,9 +169,9 @@ static inline void updatemasterserver(int millis, int port, int _interm) { if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { - string servdesc, pubkey; + string servdesc; extern string servpubkey; filtertext(servdesc, scl.servdesc_full[0] ? scl.servdesc_full : "noname", FTXT__GLOBALNAME, 20); - if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? bin2hex(pubkey, scl.ssk + 32, 32) : "nokey", AC_VERSION); + if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? servpubkey : "nokey", AC_VERSION); lastupdatemaster = millis + 1; } }