diff --git a/.gitignore b/.gitignore index bcd9169f..aca7538a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ config/servers.cfg config/configtemplates.zip config/mapmodelattributes.cfg config/entropy.dat +config/servervita* packages/maps/*.cfg !packages/maps/new_content.cfg packages/maps/*.cgz diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 9af8915a..6e1b57ec 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -40,6 +40,7 @@ // -H // server logfile path, default logs/ // -r // server maprot file, default config/maprot.cfg // -A // server parameter file, default config/serverparameters.cfg +// -U // vitapath, path & filename prefix to store the server vita database at, default: config/servervita // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg // -G // server geoip file, default config/geoip.cfg diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 402e5f6b..c15305a9 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -19,6 +19,14 @@ auth_verify_ip:0 // integer [0..1], default 1, 0:disabled, 1:enabled mandatory_auth:1 +// vitaautosave Vita file autosave interval in minutes (0: only when server is empty) +// integer [0..1440], default 10 +vitaautosave:10 + +// vitamaxage Omit vitas from autosave, if the last login has been more than the specified number of months ago +// integer [3..120], default 12 +vitamaxage:12 + // **** Maprot settings **** diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 8a955400..3c7781f2 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -321,7 +321,13 @@ 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; +struct session_s +{ + enet_uint32 serverip, clientip, clientipcensored, curpeerip; + int curpeerport, serverclock, clientclock, cn, clientsalt, datecodes; + uchar serverpubkey[32], clientsignature[64]; + string clan, publiccomment; +} session; void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { @@ -443,6 +449,18 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); p.get((uchar*)servauth, 64); + s->datecodes = getint(p); + getstring(text, p); + filtertext(s->clan, text, FTXT__VITACLAN); + getstring(text, p); + filtertext(s->publiccomment, text, FTXT__VITACOMMENT); +#ifdef _DEBUG + formatstring(text)("server-side%s", s->datecodes ? " timed flags:" : ""); + loopi(VS_NUMCOUNTERS) if(s->datecodes & (1 << i)) concatformatstring(text, " %s", vskeywords[i]); + if(s->clan[0]) concatformatstring(text, " clan tag: \"%s\"", s->clan); + if(s->publiccomment[0]) concatformatstring(text, " comment: \"%s\"", s->publiccomment); + if(strlen(text) > 12) conoutf("%s", text); +#endif } if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); if(sk && auth) diff --git a/source/src/command.cpp b/source/src/command.cpp index fffb24e6..a79ef267 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -1492,32 +1492,6 @@ COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); #ifndef STANDALONE -const char *escapestring(const char *s, bool force, bool noquotes) -{ - static vector strbuf[3]; - static int stridx = 0; - if(noquotes) force = false; - if(!s) return force ? "\"\"" : ""; - if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\n\r$"))) return s; - stridx = (stridx + 1) % 3; - vector &buf = strbuf[stridx]; - buf.setsize(0); - if(!noquotes) buf.add('"'); - for(; *s; s++) switch(*s) - { - case '\n': buf.put("\\n", 2); break; - case '\r': buf.put("\\n", 2); break; - case '\t': buf.put("\\t", 2); break; - case '\a': buf.put("\\a", 2); break; - case '\f': buf.put("\\f", 2); break; - case '"': buf.put("\\\"", 2); break; - case '\\': buf.put("\\\\", 2); break; - default: buf.add(*s); break; - } - if(!noquotes) buf.add('"'); - buf.add(0); - return buf.getbuf(); -} COMMANDF(escape, "s", (const char *s) { result(escapestring(s));}); int sortident(ident **a, ident **b) { return strcasecmp((*a)->name, (*b)->name); } diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index ef1c4df9..58587c51 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -1092,7 +1092,7 @@ static int certtypemaxrank[] = { 0, 1, 2, 3, 3, 3, 0 }; // max allowed rank of static int certtypeexpiration[] = { 0, 0, 0, 2 * CERTTIMEMONTH, CERTTIMEMONTH, 3 * CERTTIMEMONTH, CERTTIMEMONTH }; // time after which certs are usually re-signed, after 3x the time they expire vector certs; -hashtable certblacklist; +hashtable certblacklist; static inline int aktcerttime() { return time(NULL) / (time_t) 60; } // use minutes instead of seconds as timebase, so int will be more than enough @@ -1345,7 +1345,7 @@ void rebuildcerttree() // determines the rank of all certs; ages them; rem if(c->days2renew < 0) c->needsrenewal = true; if(c->days2expire < 0) { c->expired = true; c->isvalid = false; } } - if(c->pubkey && certblacklist.access(c->pubkey)) + if(c->pubkey && certblacklist.access(*((uchar32*)c->pubkey))) { c->blacklisted = true; c->isvalid = false; diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 0887c75a..49e71ffa 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -391,6 +391,7 @@ void filterrichtext(char *dst, const char *src, int len) case 'a': c = '\a'; break; case 't': c = '\t'; break; case 'n': c = '\n'; break; + case '_': c = ' '; break; case 'x': b = 16; c = *++src; @@ -410,6 +411,37 @@ void filterrichtext(char *dst, const char *src, int len) *dst = '\0'; } +// counterpart of filterrichtext + +const char *escapestring(const char *s, bool force, bool noquotes, vector *buf) +{ + static vector strbuf[3]; + static int stridx = 0; + extern bool isdedicated; + if(noquotes) force = false; + if(!s) return force ? "\"\"" : ""; + if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\a\n\r$"))) return s; + if(!buf) buf = &strbuf[stridx++]; + stridx %= 3; + buf->setsize(0); + if(!noquotes) buf->add('"'); + for(; *s; s++) switch(*s) + { + case '\n': buf->put("\\n", 2); break; + case '\r': buf->put("\\n", 2); break; + case '\t': buf->put("\\t", 2); break; + case '\a': buf->put("\\a", 2); break; + case '\f': buf->put("\\f", 2); break; + case '"': buf->put("\\\"", 2); break; + case '\\': buf->put("\\\\", 2); break; + case ' ': if(isdedicated) { buf->put("\\_", 2); break; } + default: buf->add(*s); break; + } + if(!noquotes) buf->add('"'); + buf->add(0); + return buf->getbuf(); +} + // ensures, that d is either two lowercase chars or "" void filterlang(char *d, const char *s) diff --git a/source/src/protocol.h b/source/src/protocol.h index 225c5c19..43e29ddc 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -232,3 +232,5 @@ enum #define FTXT__ZIPDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) #define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_TOLOWER) #define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK) +#define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) +#define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) diff --git a/source/src/protos.h b/source/src/protos.h index 0a20c48a..5e86b3af 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1019,7 +1019,6 @@ extern void writecfg(); extern void deletecfg(); extern void identnames(vector &names, bool builtinonly); extern void explodelist(const char *s, vector &elems); -extern const char *escapestring(const char *s, bool force = true, bool noquotes = false); extern int execcontext; extern void pushscontext(int newcontext); extern int popscontext(); @@ -1030,6 +1029,9 @@ extern void dumpexecutionstack(stream *f); extern const char *currentserver(int i); // server +enum { /* timecodes */ VS_FIRSTLOGIN = 0, VS_LASTLOGIN, VS_MUTE, VS_NOVOTE, VS_BAN, VS_ADMIN, VS_OWNER, VS_MAPUPLOAD, + /* stats/counters */ VS_MINUTESCONNECTED, VS_MINUTESACTIVE, VS_FLAGS, VS_FRAGS, VS_DEATHS, VS_TKS, VS_DAMAGE, VS_FF, VS_NUM, VS_NUMCOUNTERS = VS_MINUTESCONNECTED }; +extern const char *vskeywords[VS_NUM + 1], *vsnames[VS_NUM + 1]; extern int modeacronyms; extern void servertoclient(int chan, uchar *buf, int len, bool demo = false); extern void localservertoclient(int chan, uchar *buf, int len, bool demo = false); @@ -1073,6 +1075,7 @@ extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() extern bool validmapname(const char *s); // checks for length, allowed chars and special DOS filenames 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 const char *escapestring(const char *s, bool force = true, bool noquotes = false, vector *buf = NULL); 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); @@ -1155,7 +1158,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, *ssk; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -1165,7 +1168,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), vitabasename("config/servervita"), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { @@ -1222,6 +1225,7 @@ struct servercommandline break; case 'H': logfilepath = a; break; case 'A': parfilepath = a; break; + case 'U': vitabasename = a; break; case 'c': if(ai > 0) maxclients = min(ai, MAXCLIENTS); break; case 'k': { diff --git a/source/src/server.cpp b/source/src/server.cpp index 45734ea8..8d87490c 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -112,12 +112,14 @@ int cn2boot; const char *endis[] = { "disabled", "enabled", "" }; SERVPARLIST(dumpmaprot, 0, 1, 0, endis, "ddump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt)"); SERVPARLIST(dumpparameters, 0, 0, 0, endis, "ddump server parameters when updated"); +SERVPAR(vitaautosave, 0, 10, 24 * 60, "sVita file autosave interval in minutes (0: only when server is empty)"); +SERVPAR(vitamaxage, 3, 12, 120, "sOmit vitas from autosave, if the last login has been more than the specified number of months ago"); void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; - static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; - static bool debugmaprot = true; + static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0, lastvitasave = 0; + static bool debugmaprot = true, serverwasnotempty = false; switch(stage) // cycle through some tasks once per minute (mostly file polling) { @@ -188,7 +190,15 @@ void poll_serverthreads() // called once per mainloop-timeslice serverconfigs[nextserverconfig]->process(); // processing of config files is done in mainloop, one at a time nextserverconfig++; } - else stage++; + else + { + if(vitaupdatebuf) + { + mlog(ACLOG_INFO, "read/updated %d vitas from %s", parsevitas(vitaupdatebuf, vitaupdatebuflen), vitafilename_update_backup);; + DELETEA(vitaupdatebuf); + } + stage++; + } } break; } @@ -215,6 +225,25 @@ void poll_serverthreads() // called once per mainloop-timeslice delete f; } } + else if(!vitastosave && serverwasnotempty && (numclients() == 0 || (vitaautosave && (servmillis - lastvitasave) > 1000 * 60 * vitaautosave))) + { + serverwasnotempty = false; + lastvitasave = servmillis; + vitastosave = new vector; + vitakey_s vk; + int too_old = 0; + enumeratekt(vitas, uchar32, k, vita_s, v, + { + if((servclock - v.vs[VS_LASTLOGIN]) / (60 * 24 * 31) <= vitamaxage) + { + vk.k = &k; + vk.v = &v; + vitastosave->add(vk); + } + else too_old++; + }); + if(too_old) mlog(ACLOG_VERBOSE, "omitted %d vitas from autosave (exceeding maxage of %d months)", too_old, vitamaxage); + } else stage++; } case 5: // pause worker threads for a while (restart once a minute) @@ -228,6 +257,7 @@ void poll_serverthreads() // called once per mainloop-timeslice delete servermapstodelete.remove(i); // delete outdated servermaps } } + else serverwasnotempty = true; break; } } @@ -2994,11 +3024,24 @@ void polldeferredprocessing() client *cl = clients[sa->cn]; if(sa->wantauth) { - memcpy(cl->pubkey, sa->clientpubkey, 32); + memcpy(cl->pubkey.u, sa->clientpubkey, 32); memcpy(cl->servinforesponse, sa->clientmsg, sa->clientmsglen + 64); - bin2hex(cl->pubkeyhex, cl->pubkey, 32); + bin2hex(cl->pubkeyhex, cl->pubkey.u, 32); + cl->vita = vitas.access(cl->pubkey); + if(!cl->vita) + { + vita_s v; + memset(&v, 0, sizeof(v)); + cl->vita = &vitas.access(cl->pubkey, v); + cl->vita->vs[VS_FIRSTLOGIN] = servclock; + } + if(cl->country[0] != '-') cl->vita->addcc(cl->country); + cl->vita->addip(cl->ip); + cl->vita->vs[VS_LASTLOGIN] = servclock; cl->needsauth = true; // may have been voluntary, but it's done - sendf(sa->cn, 1, "riim", SV_SERVINFO_CONTD, 1, 64, sa->servermsg); + int datecodes = 0, *vs = cl->vita->vs; + loopi(VS_NUMCOUNTERS) datecodes |= vs[i] == 1 || (servclock - vs[i]) < 0 ? (1 << i) : 0; + sendf(sa->cn, 1, "riimiss", SV_SERVINFO_CONTD, 1, 64, sa->servermsg, datecodes, cl->vita->clan, cl->vita->publiccomment); } else sendf(sa->cn, 1, "rii", SV_SERVINFO_CONTD, 0); } @@ -3152,6 +3195,7 @@ void process(ENetPacket *packet, int sender, int chan) } } + if(cl->vita) cl->vita->addname(cl->name); sendwelcome(cl); if(restorescore(*cl)) { sendresume(*cl, true); senddisconnectedscores(-1); } else if(cl->type==ST_TCPIP) senddisconnectedscores(sender); @@ -3449,6 +3493,7 @@ void process(ENetPacket *packet, int sender, int chan) break; } } + if(cl->vita) cl->vita->addname(cl->name); } break; } @@ -4356,6 +4401,11 @@ void serverslice(uint timeout) // main server update, called from cube main lo linequalitystats(0); } serverhost->totalSentData = serverhost->totalReceivedData = 0; + loopv(clients) if(clients[i]->type == ST_TCPIP && clients[i]->isauthed && clients[i]->vita) + { + clients[i]->vita->vs[VS_MINUTESCONNECTED]++; + if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) clients[i]->vita->vs[VS_MINUTESACTIVE]++; + } } ENetEvent event; @@ -4650,6 +4700,15 @@ void initserver(bool dedicated) loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; if(scl.ssk) mlog(ACLOG_INFO,"server public key: %s", servpubkey); + formatstring(vitafilename)("%s.cfg", scl.vitabasename); + formatstring(vitafilename_backup)("%s_bak.cfg", scl.vitabasename); + formatstring(vitafilename_update)("%s_update.cfg", scl.vitabasename); + formatstring(vitafilename_update_backup_base)("%s_update_", scl.vitabasename); + path(vitafilename); path(vitafilename_backup); path(vitafilename_update); path(vitafilename_update_backup_base); + char *vn; + int gotvitas = readvitas((vn = vitafilename)); + if(gotvitas < 0) gotvitas = readvitas((vn = vitafilename_backup)); + if(gotvitas >= 0) mlog(ACLOG_INFO, "read %d player vitas from %s", gotvitas, vn); 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 e6093c98..f3b9b59d 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -223,6 +223,27 @@ struct medals } }; +#define VITANAMEHISTLEN 4 +#define VITAIPHISTLEN 4 +#define VITACCHISTLEN 4 +#define VITACOMMENTLEN 31 +#define VITACLANLEN 7 + +struct vita_s +{ + char namehist[VITANAMEHISTLEN][MAXNAMELEN + 1]; + enet_uint32 iphist[VITAIPHISTLEN]; + char privatecomment[VITACOMMENTLEN + 1], publiccomment[VITACOMMENTLEN + 1]; // private comments can be seen by admins and owners only, public comments can be seen by everyone + char clan[VITACLANLEN + 1]; + char cchist[VITACCHISTLEN * 2]; + int vs[VS_NUM]; + void addname(const char *name); + void addip(enet_uint32 ip); + void addcc(const char *cc); +}; + +struct vitakey_s { const vita_s *v; const uchar32 *k; }; + struct client // server side version of "dynent" type { int type; @@ -232,8 +253,9 @@ struct client // server side version of "dynent" type enet_uint32 ip, ip_censored; int connectclock; char country[4]; - uchar pubkey[32]; + uchar32 pubkey; char pubkeyhex[68]; + vita_s *vita; string servinforesponse; string name; int team; @@ -334,6 +356,7 @@ struct client // server side version of "dynent" type name[0] = pwd[0] = demoflags = 0; ip = ip_censored = 0; pubkeyhex[0] = '\0'; + vita = NULL; bottomRTT = ping = 9999; team = TEAM_SPECT; state.state = CS_SPECTATE; diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index e77a64d6..e2324fd9 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -715,6 +715,11 @@ void serverconfigfile::process() // in main thread: refill data structures with } } +string vitafilename, vitafilename_backup, vitafilename_update, vitafilename_update_backup_base, vitafilename_update_backup; +char *vitaupdatebuf = NULL; +int vitaupdatebuflen = 0; +vector *vitastosave = NULL; + int readserverconfigsthread(void *data) { while(1) @@ -723,6 +728,27 @@ int readserverconfigsthread(void *data) loopv(serverconfigs) serverconfigs[i]->trypreload(); + // special treatment for vita updates: read once and move out of the way + if(!vitaupdatebuf && getfilesize(vitafilename_update) > 0) + { + formatstring(vitafilename_update_backup)("%s%d.cfg", vitafilename_update_backup_base, (int)(time(NULL) / 60)); + backup(vitafilename_update, vitafilename_update_backup); + serverconfigfile vsf; + vsf.init(vitafilename_update_backup, false); + vsf.load(); + vitaupdatebuf = vsf.buf; + vitaupdatebuflen = vsf.filelen; + vsf.buf = NULL; + } + if(vitastosave) + { // mainloop prepared a selection of vitas to save + backup(vitafilename, vitafilename_backup); + stream *f = openfile(vitafilename, "w"); + extern void writevitas(stream *f, vector &vs); + if(f) writevitas(f, *vitastosave); + DELETEP(f); DELETEP(vitastosave); + } + entropy_save(); recheckallserverconfigs = false; } @@ -1804,4 +1830,153 @@ void serverparameters_dumpdocu(char *fname) } #endif +// player ID bookkeeping + +const char *vskeywords[VS_NUM + 1] = { "first", "last", "mute", "novote", "ban", "admin", "owner", "mapupload", "minconn", "minact", "flags", "frags", "deaths", "tks", "damage", "ff", "" }; +const char *vsnames[VS_NUM + 1] = { "first login", "last login", "muted until", "novote until", "banned until", "admin until", "owner until", "mapupload until", + "minutes connected", "minutes active", "flags", "frags", "deaths", "teamkills", "damage dealt", "team damage dealt", "" }; +hashtable vitas; + +void vita_s::addname(const char *name) +{ + int rem = 0; + for(; rem < VITANAMEHISTLEN - 1; rem++) if(!strcasecmp(name, namehist[rem])) break; + if(rem) memmove(namehist[1], namehist[0], rem * (MAXNAMELEN + 1)); + copystring(namehist[0], name, MAXNAMELEN + 1); +} + +void vita_s::addip(enet_uint32 ip) +{ + int rem = 0; + for(; rem < VITAIPHISTLEN - 1; rem++) if(iphist[rem] == ip) break; + if(rem) memmove(iphist + 1, iphist, rem * sizeof(ip)); + iphist[0] = ip; +} + +void vita_s::addcc(const char *cc) +{ + int rem = 0; + for(; rem < VITACCHISTLEN - 1; rem++) if(cchist[rem * 2] == cc[0] && cchist[rem * 2 + 1] == cc[1]) break; + if(rem) memmove(cchist + 2, iphist, rem * 2); + loopi(2) cchist[i] = cc[i]; +} + +int parsevitas(char *buf, int filelen) +{ + vita_s v, *vp = &v; + uchar32 pubkey; + string tmp; + enet_uint32 ip; + int res = 0; + char *l, *r, *p = buf, *pe = p + filelen, *b, *o; + while(p < pe) + { + l = p; p += strlen(p) + 1; + l = strtok_r(l, " \t", &b); + while(l && (r = strtok_r(NULL, " \t", &b))) + { + filterrichtext(r, r); + if(!strcmp(l, "PUBKEY")) + { + if(hex2bin(pubkey.u, r, 32) == 32) + { + if(!(vp = vitas.access(pubkey))) + { + memset(&v, 0, sizeof(v)); + vp = &vitas.access(pubkey, v); + } + res++; + } + else vp = &v; // bad pubkey: point to dummy + break; + } + else if(!strcmp(l, "NAME")) + { + filtertext(tmp, r, FTXT__PLAYERNAME, MAXNAMELEN); + if(*tmp) vp->addname(tmp); + } + else if(!strcmp(l, "PUBCOM") || !strcmp(l, "PRIVCOM")) + { + filtertext(tmp, r, FTXT__VITACOMMENT, VITACOMMENTLEN); + copystring(l[1] == 'U' ? vp->publiccomment : vp->privatecomment, tmp, VITACOMMENTLEN + 1); + break; + } + else if(!strcmp(l, "IP")) + { + if(atoip(r, &ip) && ip) vp->addip(ip); + } + else if(!strcmp(l, "CC")) + { + filtercountrycode(r, r); + if(*r != '-') vp->addcc(r); + } + else if(!strcmp(l, "CLAN")) + { + filtertext(tmp, r, FTXT__VITACLAN, VITACLANLEN); + copystring(vp->clan, tmp, VITACLANLEN + 1); + break; + } + else if(!strcmp(l, "STAT")) + { + if((o = strchr(r, ':'))) // key:value + { + *o++ = '\0'; // r is key, o is value + loopk(VS_NUM) if(!strcmp(vskeywords[k], r)) + { + vp->vs[k] = atoi(o); + break; + } + } + } + } + } + return res; +} + +bool readvitas(const char *fname) +{ + serverconfigfile vsf; + vsf.init(fname, false); + vsf.load(); + return vsf.buf ? parsevitas(vsf.buf, vsf.filelen) : -1; +} + +void writevitas(stream *f, vector &vs) +{ + string tmp; + vector ctmp; + f->printf("// AC server vita database\n// autosaved %s\n// (don't edit this file, while the server is running)\n\n", timestring(true, "%c", tmp)); + loopvj(vs) + { + const vita_s &v = *vs[j].v; + f->printf("PUBKEY %s", bin2hex(tmp, vs[j].k->u, 32)); + if(v.namehist[0][0]) + { + f->printf("\n\tNAME"); + loopirev(VITANAMEHISTLEN) if(v.namehist[i][0]) f->printf(" %s", escapestring(v.namehist[i], true, true, &ctmp)); + } + if(v.iphist[0]) + { + f->printf("\n\tIP"); + loopirev(VITAIPHISTLEN) if(v.iphist[i]) f->printf(" %s", iptoa(v.iphist[i], tmp)); + } + if(v.privatecomment[0]) f->printf("\n\tPRIVCOM %s", escapestring(v.privatecomment, true, true, &ctmp)); + if(v.publiccomment[0]) f->printf("\n\tPUBCOM %s", escapestring(v.publiccomment, true, true, &ctmp)); + if(v.clan[0]) f->printf("\n\tCLAN %s", escapestring(v.clan, true, true, &ctmp)); + if(v.cchist[0]) + { + f->printf("\n\tCC"); + loopirev(VITACCHISTLEN) if(v.cchist[i * 2]) f->printf(" %c%c", v.cchist[i * 2], v.cchist[i * 2 + 1]); + } + f->printf("\n\tSTAT"); + loopi(VS_NUM) if(v.vs[i]) f->printf(" %s:%d", vskeywords[i], v.vs[i]); + f->printf("\n\n"); + } +} + + + + + + diff --git a/source/src/tools.h b/source/src/tools.h index f984a88f..3b6be9d2 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -506,14 +506,16 @@ static inline bool htcmp(const char *x, const char *y) return !strcmp(x, y); } -static inline uint hthash(const uchar *key) +struct uchar32 { uchar u[32]; }; + +static inline uint hthash(const uchar32 &key) { - return *((uint *)key); + return *((uint *)&key); } -static inline bool htcmp(const uchar *x, const uchar *y) // assume "uchar *" points to 32byte public keys +static inline bool htcmp(const uchar32 &x, const uchar32 &y) { - return !memcmp(x, y, 32); + return !memcmp(&x, &y, 32); } static inline uint hthash(int key) @@ -590,10 +592,10 @@ template struct hashtable } chain *c = unused; unused = unused->next; - c->key = key; c->next = table[h]; - table[h] = c; + c->key = key; numelems++; + table[h] = c; return c; }