1 // serverfiles.h
2 
3 // map management
4 
5 #define SERVERMAP_PATH          "packages/maps/servermaps/"
6 #define SERVERMAP_PATH_BUILTIN  "packages/maps/official/"
7 #define SERVERMAP_PATH_INCOMING "packages/maps/servermaps/incoming/"
8 
9 #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10)
10 
11 struct servermapbuffer  // sending of maps between clients
12 {
13     string mapname;
14     int cgzsize, cfgsize, cfgsizegz, revision, datasize;
15     uchar *data, *gzbuf;
16 
servermapbufferservermapbuffer17     servermapbuffer() : data(NULL) { gzbuf = new uchar[GZBUFSIZE]; }
~servermapbufferservermapbuffer18     ~servermapbuffer() { delete[] gzbuf; }
19 
clearservermapbuffer20     void clear() { DELETEA(data); revision = 0; }
21 
availableservermapbuffer22     int available()
23     {
24         if(data && !strcmp(mapname, behindpath(smapname)) && cgzsize == smapstats.cgzsize)
25         {
26             if( !revision || (revision == smapstats.hdr.maprevision)) return cgzsize;
27         }
28         return 0;
29     }
30 
setrevisionservermapbuffer31     void setrevision()
32     {
33         if(available() && !revision) revision = smapstats.hdr.maprevision;
34     }
35 
loadservermapbuffer36     void load(void)  // load currently played map into the buffer (if distributable), clear buffer otherwise
37     {
38         string cgzname, cfgname;
39         const char *name = behindpath(smapname);   // no paths allowed here
40 
41         clear();
42         formatstring(cgzname)(SERVERMAP_PATH "%s.cgz", name);
43         path(cgzname);
44         if(fileexists(cgzname, "r"))
45         {
46             formatstring(cfgname)(SERVERMAP_PATH "%s.cfg", name);
47         }
48         else
49         {
50             formatstring(cgzname)(SERVERMAP_PATH_INCOMING "%s.cgz", name);
51             path(cgzname);
52             formatstring(cfgname)(SERVERMAP_PATH_INCOMING "%s.cfg", name);
53         }
54         path(cfgname);
55         uchar *cgzdata = (uchar *)loadfile(cgzname, &cgzsize);
56         uchar *cfgdata = (uchar *)loadfile(cfgname, &cfgsize);
57         if(cgzdata && (!cfgdata || cfgsize < MAXCFGFILESIZE))
58         {
59             uLongf gzbufsize = GZBUFSIZE;
60             if(!cfgdata || compress2(gzbuf, &gzbufsize, cfgdata, cfgsize, 9) != Z_OK)
61             {
62                 cfgsize = 0;
63                 gzbufsize = 0;
64             }
65             cfgsizegz = (int) gzbufsize;
66             if(cgzsize + cfgsizegz < MAXMAPSENDSIZE)
67             { // map is ok, fill buffer
68                 copystring(mapname, name);
69                 datasize = cgzsize + cfgsizegz;
70                 data = new uchar[datasize];
71                 memcpy(data, cgzdata, cgzsize);
72                 memcpy(data + cgzsize, gzbuf, cfgsizegz);
73                 logline(ACLOG_INFO,"loaded map %s, %d + %d(%d) bytes.", cgzname, cgzsize, cfgsize, cfgsizegz);
74             }
75         }
76         DELETEA(cgzdata);
77         DELETEA(cfgdata);
78     }
79 
sendmapservermapbuffer80     bool sendmap(const char *nmapname, int nmapsize, int ncfgsize, int ncfgsizegz, uchar *ndata)
81     {
82         FILE *fp;
83         bool written = false;
84 
85         if(!nmapname[0] || nmapsize <= 0 || ncfgsizegz < 0 || nmapsize + ncfgsizegz > MAXMAPSENDSIZE || ncfgsize > MAXCFGFILESIZE) return false;  // malformed: probably modded client
86         int cfgsize = ncfgsize;
87         if(smode == GMODE_COOPEDIT && !strcmp(nmapname, behindpath(smapname)))
88         { // update mapbuffer only in coopedit mode (and on same map)
89             copystring(mapname, nmapname);
90             datasize = nmapsize + ncfgsizegz;
91             revision = 0;
92             DELETEA(data);
93             data = new uchar[datasize];
94             memcpy(data, ndata, datasize);
95         }
96 
97         defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", nmapname);
98         path(name);
99         fp = fopen(name, "wb");
100         if(fp)
101         {
102             fwrite(ndata, 1, nmapsize, fp);
103             fclose(fp);
104             formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", nmapname);
105             path(name);
106             fp = fopen(name, "wb");
107             if(fp)
108             {
109                 uLongf rawsize = ncfgsize;
110                 if(uncompress(gzbuf, &rawsize, ndata + nmapsize, ncfgsizegz) == Z_OK && rawsize - ncfgsize == 0)
111                     fwrite(gzbuf, 1, cfgsize, fp);
112                 fclose(fp);
113                 written = true;
114             }
115         }
116         return written;
117     }
118 
sendmapservermapbuffer119     void sendmap(client *cl, int chan)
120     {
121         if(!available()) return;
122         packetbuf p(MAXTRANS + datasize, ENET_PACKET_FLAG_RELIABLE);
123         putint(p, SV_RECVMAP);
124         sendstring(mapname, p);
125         putint(p, cgzsize);
126         putint(p, cfgsize);
127         putint(p, cfgsizegz);
128         putint(p, revision);
129         p.put(data, datasize);
130         sendpacket(cl->clientnum, chan, p.finalize());
131     }
132 };
133 
134 
135 // provide maps by the server
136 
137 enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_LOCAL, MAP_OFFICIAL, MAP_VOID };
138 static const char * const maplocstr[] = { "not found", "temporary", "custom", "local", "official", "void" };
139 #define readonlymap(x) ((x) >= MAP_CUSTOM)
140 #define distributablemap(x) ((x) == MAP_TEMP || (x) == MAP_CUSTOM)
141 
findmappath(const char * mapname,char * filename)142 int findmappath(const char *mapname, char *filename)
143 {
144     if(!mapname[0]) return MAP_NOTFOUND;
145     string tempname;
146     if(!filename) filename = tempname;
147     const char *name = behindpath(mapname);
148     formatstring(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name);
149     path(filename);
150     int loc = MAP_NOTFOUND;
151     if(getfilesize(filename) > 10) loc = MAP_OFFICIAL;
152     else
153     {
154 #ifndef STANDALONE
155         copystring(filename, setnames(name));
156         if(!isdedicated && getfilesize(filename) > 10) loc = MAP_LOCAL;
157         else
158         {
159 #endif
160             formatstring(filename)(SERVERMAP_PATH "%s.cgz", name);
161             path(filename);
162             if(isdedicated && getfilesize(filename) > 10) loc = MAP_CUSTOM;
163             else
164             {
165                 formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name);
166                 path(filename);
167                 if(isdedicated && getfilesize(filename) > 10) loc = MAP_TEMP;
168             }
169 #ifndef STANDALONE
170         }
171 #endif
172     }
173     return loc;
174 }
175 
getservermapstats(const char * mapname,bool getlayout,int * maploc)176 mapstats *getservermapstats(const char *mapname, bool getlayout, int *maploc)
177 {
178     string filename;
179     int ml;
180     if(!maploc) maploc = &ml;
181     *maploc = findmappath(mapname, filename);
182     if(getlayout) DELETEA(maplayout);
183     return *maploc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout);
184 }
185 
186 
187 // server config files
188 
init(const char * name)189 void serverconfigfile::init(const char *name)
190 {
191     copystring(filename, name);
192     path(filename);
193     read();
194 }
195 
load()196 bool serverconfigfile::load()
197 {
198     DELETEA(buf);
199     buf = loadfile(filename, &filelen);
200     if(!buf)
201     {
202         logline(ACLOG_INFO,"could not read config file '%s'", filename);
203         return false;
204     }
205     char *p;
206     if('\r' != '\n') // this is not a joke!
207     {
208         char c = strchr(buf, '\n') ? ' ' : '\n'; // in files without /n substitute /r with /n, otherwise remove /r
209         for(p = buf; (p = strchr(p, '\r')); p++) *p = c;
210     }
211     for(p = buf; (p = strstr(p, "//")); ) // remove comments
212     {
213         while(*p != '\n' && *p != '\0') p++[0] = ' ';
214     }
215     for(p = buf; (p = strchr(p, '\t')); p++) *p = ' ';
216     for(p = buf; (p = strchr(p, '\n')); p++) *p = '\0'; // one string per line
217     return true;
218 }
219 
220 // maprot.cfg
221 
222 #define CONFIG_MAXPAR 6
223 
224 struct configset
225 {
226     string mapname;
227     union
228     {
229         struct { int mode, time, vote, minplayer, maxplayer, skiplines; };
230         int par[CONFIG_MAXPAR];
231     };
232 };
233 
234 int FlagFlag = MINFF * 1000;
235 int Mvolume, Marea, SHhits, Mopen = 0;
236 float Mheight = 0;
237 
mapisok(mapstats * ms)238 bool mapisok(mapstats *ms)
239 {
240     if ( Mheight > MAXMHEIGHT ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The overall ceil height is too high (%.1f cubes)", Mheight); return false; }
241     if ( Mopen > MAXMAREA ) { logline(ACLOG_INFO, "MAP CHECK FAIL: There is a big open area in this (hint: use more solid walls)", Mheight); return false; }
242     if ( SHhits > MAXHHITS ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Too high height in some parts of the map (%d hits)", SHhits); return false; }
243 
244     if ( ms->hasflags ) // Check if flags are ok
245     {
246         struct { short x, y; } fl[2];
247         loopi(2)
248         {
249             if(ms->flags[i] == 1)
250             {
251                 short *fe = ms->entposs + ms->flagents[i] * 3;
252                 fl[i].x = *fe; fe++; fl[i].y = *fe;
253             }
254             else fl[i].x = fl[i].y = 0; // the map has no valid flags
255         }
256         FlagFlag = pow2(fl[0].x - fl[1].x) + pow2(fl[0].y - fl[1].y);
257     }
258     else FlagFlag = MINFF * 1000; // the map has no flags
259 
260     if ( FlagFlag < MINFF ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The flags are too close to each other"); return false; }
261 
262     for (int i = 0; i < ms->hdr.numents; i++)
263     {
264         int v = ms->enttypes[i];
265         if (v < I_CLIPS || v > I_AKIMBO) continue;
266         short *p = &ms->entposs[i*3];
267         float density = 0, hdensity = 0;
268         for(int j = 0; j < ms->hdr.numents; j++)
269         {
270             int w = ms->enttypes[j];
271             if (w < I_CLIPS || w > I_AKIMBO || i == j) continue;
272             short *q = &ms->entposs[j*3];
273             float r2 = 0;
274             loopk(3){ r2 += (p[k]-q[k])*(p[k]-q[k]); }
275             if ( r2 == 0.0f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[v], entnames[w],p[0],p[1]); return false; }
276             r2 = 1/r2;
277             if (r2 < 0.0025f) continue;
278             if (w != v)
279             {
280                 hdensity += r2;
281                 continue;
282             }
283             density += r2;
284         }
285 /*        if (hdensity > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK H %s %f", entnames[v], hdensity); }
286         if (density > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK D %s %f", entnames[v], density); }*/
287         if ( hdensity > 0.5f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],hdensity,p[0],p[1]); return false; }
288         switch(v)
289         {
290 #define LOGTHISSWITCH(X) if( density > X ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],density,p[0],p[1]); return false; }
291             case I_CLIPS:
292             case I_HEALTH: LOGTHISSWITCH(0.24f); break;
293             case I_AMMO: LOGTHISSWITCH(0.04f); break;
294             case I_HELMET: LOGTHISSWITCH(0.02f); break;
295             case I_ARMOUR:
296             case I_GRENADE:
297             case I_AKIMBO: LOGTHISSWITCH(0.005f); break;
298             default: break;
299 #undef LOGTHISSWITCH
300         }
301     }
302     return true;
303 }
304 
305 struct servermaprot : serverconfigfile
306 {
307     vector<configset> configsets;
308     int curcfgset;
309 
servermaprotservermaprot310     servermaprot() : curcfgset(-1) {}
311 
readservermaprot312     void read()
313     {
314         if(getfilesize(filename) == filelen) return;
315         configsets.shrink(0);
316         if(!load()) return;
317 
318         const char *sep = ": ";
319         configset c;
320         int i, line = 0;
321         char *l, *p = buf;
322         logline(ACLOG_VERBOSE,"reading map rotation '%s'", filename);
323         while(p < buf + filelen)
324         {
325             l = p; p += strlen(p) + 1; line++;
326             l = strtok(l, sep);
327             if(l)
328             {
329                 copystring(c.mapname, behindpath(l));
330                 for(i = 3; i < CONFIG_MAXPAR; i++) c.par[i] = 0;  // default values
331                 for(i = 0; i < CONFIG_MAXPAR; i++)
332                 {
333                     if((l = strtok(NULL, sep)) != NULL) c.par[i] = atoi(l);
334                     else break;
335                 }
336                 if(i > 2)
337                 {
338                     configsets.add(c);
339                     logline(ACLOG_VERBOSE," %s, %s, %d minutes, vote:%d, minplayer:%d, maxplayer:%d, skiplines:%d", c.mapname, modestr(c.mode, false), c.time, c.vote, c.minplayer, c.maxplayer, c.skiplines);
340                 }
341                 else logline(ACLOG_INFO," error in line %d, file %s", line, filename);
342             }
343         }
344         DELETEA(buf);
345         logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename);
346         return;
347     }
348 
349     int next(bool notify = true, bool nochange = false) // load next maprotation set
350     {
351 #ifndef STANDALONE
352         if(!isdedicated)
353         {
354             defformatstring(nextmapalias)("nextmap_%s", getclientmap());
355             const char *map = getalias(nextmapalias);     // look up map in the cycle
356             startgame(map && notify ? map : getclientmap(), getclientmode(), -1, notify);
357             return -1;
358         }
359 #endif
360         if(configsets.empty()) fatal("maprot unavailable");
361         int n = numclients();
362         int csl = configsets.length();
363         int ccs = curcfgset;
364         if(ccs >= 0 && ccs < csl) ccs += configsets[ccs].skiplines;
365         configset *c = NULL;
366         loopi(3 * csl + 1)
367         {
368             ccs++;
369             if(ccs >= csl || ccs < 0) ccs = 0;
370             c = &configsets[ccs];
371             if((n >= c->minplayer || i >= csl) && (!c->maxplayer || n <= c->maxplayer || i >= 2 * csl))
372             {
373                 mapstats *ms = NULL;
374                 if((ms = getservermapstats(c->mapname)) && mapisok(ms)) break;
375                 else logline(ACLOG_INFO, "maprot error: map '%s' %s", c->mapname, (ms ? "does not satisfy some basic requirements" : "not found"));
376             }
377             if(i >= 3 * csl) fatal("maprot unusable"); // not a single map in rotation can be found...
378         }
379         if(!nochange)
380         {
381             curcfgset = ccs;
382             startgame(c->mapname, c->mode, c->time, notify);
383         }
384         return ccs;
385     }
386 
387     void restart(bool notify = true) // restart current map
388     {
389 #ifndef STANDALONE
390         if(!isdedicated)
391         {
392             startgame(getclientmap(), getclientmode(), -1, notify);
393             return;
394         }
395 #endif
396     startgame(smapname, smode, -1, notify);
397     }
398 
currentservermaprot399     configset *current() { return configsets.inrange(curcfgset) ? &configsets[curcfgset] : NULL; }
getservermaprot400     configset *get(int ccs) { return configsets.inrange(ccs) ? &configsets[ccs] : NULL; }
get_nextservermaprot401     int get_next()
402     {
403         int ccs = curcfgset;
404         while(!strcmp(configsets[curcfgset].mapname,configsets[ccs].mapname))
405         {
406             ccs++;
407             if(!configsets.inrange(ccs)) ccs=0;
408             if (ccs == curcfgset) break;
409         }
410         curcfgset = ccs;
411         return ccs;
412     }
413 };
414 
415 // serverblacklist.cfg
416 
417 struct serveripblacklist : serverconfigfile
418 {
419     vector<iprange> ipranges;
420 
readserveripblacklist421     void read()
422     {
423         if(getfilesize(filename) == filelen) return;
424         ipranges.shrink(0);
425         if(!load()) return;
426 
427         iprange ir;
428         int line = 0, errors = 0;
429         char *l, *r, *p = buf;
430         logline(ACLOG_VERBOSE,"reading ip blacklist '%s'", filename);
431         while(p < buf + filelen)
432         {
433             l = p; p += strlen(p) + 1; line++;
434             if((r = (char *) atoipr(l, &ir)))
435             {
436                 ipranges.add(ir);
437                 l = r;
438             }
439             if(l[strspn(l, " ")])
440             {
441                 for(int i = (int)strlen(l) - 1; i > 0 && l[i] == ' '; i--) l[i] = '\0';
442                 logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, l);
443                 errors++;
444             }
445         }
446         DELETEA(buf);
447         ipranges.sort(cmpiprange);
448         int orglength = ipranges.length();
449         loopv(ipranges)
450         {
451             if(!i) continue;
452             if(ipranges[i].ur <= ipranges[i - 1].ur)
453             {
454                 if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur)
455                     logline(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i]));
456                 else
457                     logline(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1]));
458                 ipranges.remove(i--); continue;
459             }
460             if(ipranges[i].lr <= ipranges[i - 1].ur)
461             {
462                 logline(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]));
463                 ipranges[i - 1].ur = ipranges[i].ur;
464                 ipranges.remove(i--); continue;
465             }
466         }
467         loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i]));
468         logline(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors);
469     }
470 
checkserveripblacklist471     bool check(enet_uint32 ip) // ip: network byte order
472     {
473         iprange t;
474         t.lr = ENET_NET_TO_HOST_32(ip); // blacklist uses host byte order
475         t.ur = 0;
476         return ipranges.search(&t, cmpipmatch) != NULL;
477     }
478 };
479 
480 // nicknameblacklist.cfg
481 
482 #define MAXNICKFRAGMENTS 5
483 enum { NWL_UNLISTED = 0, NWL_PASS, NWL_PWDFAIL, NWL_IPFAIL };
484 
485 struct servernickblacklist : serverconfigfile
486 {
487     struct iprchain     { struct iprange ipr; const char *pwd; int next; };
clearservernickblacklist::blackline488     struct blackline    { int frag[MAXNICKFRAGMENTS]; bool ignorecase; int line; void clear() { loopi(MAXNICKFRAGMENTS) frag[i] = -1; } };
489     hashtable<const char *, int> whitelist;
490     vector<iprchain> whitelistranges;
491     vector<blackline> blacklines;
492     vector<const char *> blfraglist;
493 
destroylistsservernickblacklist494     void destroylists()
495     {
496         whitelistranges.setsize(0);
497         enumeratek(whitelist, const char *, key, delete key);
498         whitelist.clear(false);
499         blfraglist.deletecontents();
500         blacklines.setsize(0);
501     }
502 
readservernickblacklist503     void read()
504     {
505         if(getfilesize(filename) == filelen) return;
506         destroylists();
507         if(!load()) return;
508 
509         const char *sep = " ";
510         int line = 1, errors = 0;
511         iprchain iprc;
512         blackline bl;
513         char *l, *s, *r, *p = buf;
514         logline(ACLOG_VERBOSE,"reading nickname blacklist '%s'", filename);
515         while(p < buf + filelen)
516         {
517             l = p; p += strlen(p) + 1;
518             l = strtok(l, sep);
519             if(l)
520             {
521                 s = strtok(NULL, sep);
522                 int ic = 0;
523                 if(s && (!strcmp(l, "accept") || !strcmp(l, "a")))
524                 { // accept nickname IP-range
525                     int *i = whitelist.access(s);
526                     if(!i) i = &whitelist.access(newstring(s), -1);
527                     s += strlen(s) + 1;
528                     while(s < p)
529                     {
530                         r = (char *) atoipr(s, &iprc.ipr);
531                         s += strspn(s, sep);
532                         iprc.pwd = r && *s ? NULL : newstring(s, strcspn(s, sep));
533                         if(r || *s)
534                         {
535                             iprc.next = *i;
536                             *i = whitelistranges.length();
537                             whitelistranges.add(iprc);
538                             s = r ? r : s + strlen(iprc.pwd);
539                         }
540                         else break;
541                     }
542                     s = NULL;
543                 }
544                 else if(s && (!strcmp(l, "block") || !strcmp(l, "b") || ic++ || !strcmp(l, "blocki") || !strcmp(l, "bi")))
545                 { // block nickname fragments (ic == ignore case)
546                     bl.clear();
547                     loopi(MAXNICKFRAGMENTS)
548                     {
549                         if(ic) strtoupper(s);
550                         loopvj(blfraglist)
551                         {
552                             if(!strcmp(s, blfraglist[j])) { bl.frag[i] = j; break; }
553                         }
554                         if(bl.frag[i] < 0)
555                         {
556                             bl.frag[i] = blfraglist.length();
557                             blfraglist.add(newstring(s));
558                         }
559                         s = strtok(NULL, sep);
560                         if(!s) break;
561                     }
562                     bl.ignorecase = ic > 0;
563                     bl.line = line;
564                     blacklines.add(bl);
565                 }
566                 else { logline(ACLOG_INFO," error in line %d, file %s: unknown keyword '%s'", line, filename, l); errors++; }
567                 if(s && s[strspn(s, " ")]) { logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, s); errors++; }
568             }
569             line++;
570         }
571         DELETEA(buf);
572         logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems);
573         string text;
574         enumeratekt(whitelist, const char *, key, int, idx,
575         {
576             text[0] = '\0';
577             for(int i = idx; i >= 0; i = whitelistranges[i].next)
578             {
579                 iprchain &ic = whitelistranges[i];
580                 if(ic.pwd) concatformatstring(text, "  pwd:\"%s\"", hiddenpwd(ic.pwd));
581                 else concatformatstring(text, "  %s", iprtoa(ic.ipr));
582             }
583             logline(ACLOG_VERBOSE, "  accept %s%s", key, text);
584         });
585         logline(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length());
586         loopv(blacklines)
587         {
588             text[0] = '\0';
589             loopj(MAXNICKFRAGMENTS)
590             {
591                 int k = blacklines[i].frag[j];
592                 if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); }
593             }
594             logline(ACLOG_VERBOSE, "  %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text);
595         }
596         logline(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors);
597     }
598 
checkwhitelistservernickblacklist599     int checkwhitelist(const client &c)
600     {
601         if(c.type != ST_TCPIP) return NWL_PASS;
602         iprange ipr;
603         ipr.lr = ENET_NET_TO_HOST_32(c.peer->address.host); // blacklist uses host byte order
604         int *idx = whitelist.access(c.name);
605         if(!idx) return NWL_UNLISTED; // no matching entry
606         int i = *idx;
607         bool needipr = false, iprok = false, needpwd = false, pwdok = false;
608         while(i >= 0)
609         {
610             iprchain &ic = whitelistranges[i];
611             if(ic.pwd)
612             { // check pwd
613                 needpwd = true;
614                 if(pwdok || !strcmp(genpwdhash(c.name, ic.pwd, c.salt), c.pwd)) pwdok = true;
615             }
616             else
617             { // check IP
618                 needipr = true;
619                 if(!cmpipmatch(&ipr, &ic.ipr)) iprok = true; // range match found
620             }
621             i = whitelistranges[i].next;
622         }
623         if(needpwd && !pwdok) return NWL_PWDFAIL; // wrong PWD
624         if(needipr && !iprok) return NWL_IPFAIL; // wrong IP
625         return NWL_PASS;
626     }
627 
checkblacklistservernickblacklist628     int checkblacklist(const char *name)
629     {
630         if(blacklines.empty()) return -2;  // no nickname blacklist loaded
631         string nameuc;
632         copystring(nameuc, name);
633         strtoupper(nameuc);
634         loopv(blacklines)
635         {
636             loopj(MAXNICKFRAGMENTS)
637             {
638                 int k = blacklines[i].frag[j];
639                 if(k < 0) return blacklines[i].line; // no more fragments to check
640                 if(strstr(blacklines[i].ignorecase ? nameuc : name, blfraglist[k]))
641                 {
642                     if(j == MAXNICKFRAGMENTS - 1) return blacklines[i].line; // all fragments match
643                 }
644                 else break; // this line no match
645             }
646         }
647         return -1; // no match
648     }
649 };
650 
651 #define FORBIDDENSIZE 15
652 struct serverforbiddenlist : serverconfigfile
653 {
654     int num;
655     char entries[100][2][FORBIDDENSIZE+1]; // 100 entries and 2 words per entry is more than enough
656 
initlistserverforbiddenlist657     void initlist()
658     {
659         num = 0;
660         memset(entries,'\0',2*100*(FORBIDDENSIZE+1));
661     }
662 
addentryserverforbiddenlist663     void addentry(char *s)
664     {
665         int len = strlen(s);
666         if ( len > 128 || len < 3 ) return;
667         int n = 0;
668         string s1, s2;
669         char *c1 = s1, *c2 = s2;
670         if (num < 100 && (n = sscanf(s,"%s %s",s1,s2)) > 0 ) // no warnings
671         {
672             strncpy(entries[num][0],c1,FORBIDDENSIZE);
673             if ( n > 1 ) strncpy(entries[num][1],c2,FORBIDDENSIZE);
674             else entries[num][1][0]='\0';
675             num++;
676         }
677     }
678 
readserverforbiddenlist679     void read()
680     {
681         if(getfilesize(filename) == filelen) return;
682         initlist();
683         if(!load()) return;
684 
685         char *l, *p = buf;
686         logline(ACLOG_VERBOSE,"reading forbidden list '%s'", filename);
687         while(p < buf + filelen)
688         {
689             l = p; p += strlen(p) + 1;
690             addentry(l);
691         }
692         DELETEA(buf);
693     }
694 
canspeechserverforbiddenlist695     bool canspeech(char *s)
696     {
697         for (int i=0; i<num; i++){
698             if ( !findpattern(s,entries[i][0]) ) continue;
699             else if ( entries[i][1][0] == '\0' || findpattern(s,entries[i][1]) ) return false;
700         }
701         return true;
702     }
703 };
704 
705 // serverpwd.cfg
706 
707 #define ADMINPWD_MAXPAR 1
708 struct pwddetail
709 {
710     string pwd;
711     int line;
712     bool denyadmin;    // true: connect only
713 };
714 
715 int passtime = 0, passtries = 0;
716 enet_uint32 passguy = 0;
717 
718 struct serverpasswords : serverconfigfile
719 {
720     vector<pwddetail> adminpwds;
721     int staticpasses;
722 
serverpasswordsserverpasswords723     serverpasswords() : staticpasses(0) {}
724 
initserverpasswords725     void init(const char *name, const char *cmdlinepass)
726     {
727         if(cmdlinepass[0])
728         {
729             pwddetail c;
730             copystring(c.pwd, cmdlinepass);
731             c.line = 0;   // commandline is 'line 0'
732             c.denyadmin = false;
733             adminpwds.add(c);
734         }
735         staticpasses = adminpwds.length();
736         serverconfigfile::init(name);
737     }
738 
readserverpasswords739     void read()
740     {
741         if(getfilesize(filename) == filelen) return;
742         adminpwds.shrink(staticpasses);
743         if(!load()) return;
744 
745         pwddetail c;
746         const char *sep = " ";
747         int i, line = 1, par[ADMINPWD_MAXPAR];
748         char *l, *p = buf;
749         logline(ACLOG_VERBOSE,"reading admin passwords '%s'", filename);
750         while(p < buf + filelen)
751         {
752             l = p; p += strlen(p) + 1;
753             l = strtok(l, sep);
754             if(l)
755             {
756                 copystring(c.pwd, l);
757                 par[0] = 0;  // default values
758                 for(i = 0; i < ADMINPWD_MAXPAR; i++)
759                 {
760                     if((l = strtok(NULL, sep)) != NULL) par[i] = atoi(l);
761                     else break;
762                 }
763                 //if(i > 0)
764                 {
765                     c.line = line;
766                     c.denyadmin = par[0] > 0;
767                     adminpwds.add(c);
768                     logline(ACLOG_VERBOSE,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0);
769                 }
770             }
771             line++;
772         }
773         DELETEA(buf);
774         logline(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename);
775     }
776 
777     bool check(const char *name, const char *pwd, int salt, pwddetail *detail = NULL, enet_uint32 address = 0)
778     {
779         bool found = false;
780         if (address && passguy == address)
781         {
782             if (passtime + 3000 > servmillis || ( passtries > 5 && passtime + 10000 > servmillis ))
783             {
784                 passtries++;
785                 passtime = servmillis;
786                 return false;
787             }
788             else
789             {
790                 if ( passtime + 60000 < servmillis ) passtries = 0;
791             }
792             passtries++;
793         }
794         else
795         {
796             passtries = 0;
797         }
798         passguy = address;
799         passtime = servmillis;
loopvserverpasswords800         loopv(adminpwds)
801         {
802             if(!strcmp(genpwdhash(name, adminpwds[i].pwd, salt), pwd))
803             {
804                 if(detail) *detail = adminpwds[i];
805                 found = true;
806                 break;
807             }
808         }
809         return found;
810     }
811 };
812 
813 // serverinfo_en.txt, motd_en.txt
814 
815 #define MAXINFOLINELEN 100  // including color codes
816 
817 struct serverinfofile
818 {
819     struct serverinfotext { const char *type; char lang[3]; char *info; int lastcheck; };
820     vector<serverinfotext> serverinfotexts;
821     const char *infobase, *motdbase;
822 
initserverinfofile823     void init(const char *info, const char *motd) { infobase = info; motdbase = motd; }
824 
readinfofileserverinfofile825     char *readinfofile(const char *fnbase, const char *lang)
826     {
827         defformatstring(fname)("%s_%s.txt", fnbase, lang);
828         path(fname);
829         int len, n;
830         char *c, *s, *t, *buf = loadfile(fname, &len);
831         if(!buf) return NULL;
832         char *nbuf = new char[len + 2];
833         for(t = nbuf, s = strtok(buf, "\n\r"); s; s = strtok(NULL, "\n\r"))
834         {
835             c = strstr(s, "//");
836             if(c) *c = '\0'; // strip comments
837             for(n = (int)strlen(s) - 1; n >= 0 && s[n] == ' '; n--) s[n] = '\0'; // strip trailing blanks
838             filterrichtext(t, s + strspn(s, " "), MAXINFOLINELEN); // skip leading blanks
839             n = (int)strlen(t);
840             if(n) t += n + 1;
841         }
842         *t = '\0';
843         DELETEA(buf);
844         if(!*nbuf) DELETEA(nbuf);
845         logline(ACLOG_DEBUG,"read file \"%s\"", fname);
846         return nbuf;
847     }
848 
getinfocacheserverinfofile849     const char *getinfocache(const char *fnbase, const char *lang)
850     {
851         serverinfotext sn = { fnbase, { 0, 0, 0 }, NULL, 0} , *s = &sn;
852         filterlang(sn.lang, lang);
853         if(!sn.lang[0]) return NULL;
854         loopv(serverinfotexts)
855         {
856             serverinfotext &si = serverinfotexts[i];
857             if(si.type == s->type && !strcmp(si.lang, s->lang))
858             {
859                 if(servmillis - si.lastcheck > (si.info ? 15 : 45) * 60 * 1000)
860                 { // re-read existing files after 15 minutes; search missing again after 45 minutes
861                     DELETEA(si.info);
862                     s = &si;
863                 }
864                 else return si.info;
865             }
866         }
867         s->info = readinfofile(fnbase, lang);
868         s->lastcheck = servmillis;
869         if(s == &sn) serverinfotexts.add(*s);
870         if(fnbase == motdbase && s->info)
871         {
872             char *c = s->info;
873             while(*c) { c += strlen(c); if(c[1]) *c++ = '\n'; }
874             if(strlen(s->info) > MAXSTRLEN) s->info[MAXSTRLEN] = '\0'; // keep MOTD at sane lengths
875         }
876         return s->info;
877     }
878 
getinfoserverinfofile879     const char *getinfo(const char *lang)
880     {
881         return getinfocache(infobase, lang);
882     }
883 
getmotdserverinfofile884     const char *getmotd(const char *lang)
885     {
886         const char *motd;
887         if(*lang && (motd = getinfocache(motdbase, lang))) return motd;
888         return getinfocache(motdbase, "en");
889     }
890 };
891 
892 struct killmessagesfile : serverconfigfile
893 {
initkillmessagesfile894     void init(const char *name) { serverconfigfile::init(name); }
readkillmessagesfile895     void read()
896     {
897         if(getfilesize(filename) == filelen) return;
898         if(!load()) return;
899 
900         char *l, *s, *p = buf;
901         const char *sep = " \"";
902         int line = 0;
903         logline(ACLOG_VERBOSE,"reading kill messages file '%s'", filename);
904         while(p < buf + filelen)
905         {
906             l = p; p += strlen(p) + 1;
907             l = strtok(l, sep);
908 
909             char *message;
910             if(l)
911             {
912                 s = strtok(NULL, sep);
913                 bool fragmsg = !strcmp(l, "fragmessage");
914                 bool gibmsg = !strcmp(l, "gibmessage");
915                 if(s && (fragmsg || gibmsg))
916                 {
917                     int errors = 0;
918                     int gun = atoi(s);
919 
920                     s += strlen(s) + 1;
921                     while(s[0] == ' ') s++;
922                     int hasquotes = strspn(s, "\"");
923                     s += hasquotes;
924                     message = s;
925                     const char *seps = "\" \n", *end = NULL;
926                     char cursep;
927                     while( (cursep = *seps++) != '\0')
928                     {
929                         if(cursep == '"' && !hasquotes) continue;
930                         end = strchr(message, cursep);
931                         if(end) break;
932                     }
933                     if(end) message[end-message] = '\0';
934 
935                     if(gun < 0 || gun >= NUMGUNS)
936                     {
937                         logline(ACLOG_INFO, " error in line %i, invalid gun : %i", line, gun);
938                         errors++;
939                     }
940                     if(strlen(message)>MAXKILLMSGLEN)
941                     {
942                         logline(ACLOG_INFO, " error in line %i, too long message : string length is %i, max. allowed is %i", line, strlen(message), MAXKILLMSGLEN);
943                         errors++;
944                     }
945                     if(!errors)
946                     {
947                         if(fragmsg)
948                         {
949                             copystring(killmessages[0][gun], message);
950                             logline(ACLOG_VERBOSE, " added msg '%s' for frags with weapon %i ", message, gun);
951                         }
952                         else
953                         {
954                             copystring(killmessages[1][gun], message);
955                             logline(ACLOG_VERBOSE, " added msg '%s' for gibs with weapon %i ", message, gun);
956                         }
957                     }
958                     s = NULL;
959                     line++;
960                 }
961             }
962         }
963     }
964 };
965