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