diff --git a/config/nicknameblacklist.cfg b/config/nicknameblacklist.cfg index 1455ec44..348f3faa 100644 --- a/config/nicknameblacklist.cfg +++ b/config/nicknameblacklist.cfg @@ -1,14 +1,27 @@ -// Server Nickname Blacklist -// ========================= +// Server Nickname Blacklist + Whitelist +// ===================================== // WARNING: You can NOT use CubeScript in here!! -// accept nickname [IP range] // add this nickname to the whitelist (if an IP range is specified, only connects from that range are allowed) +// accept nickname [IP range ...] // add this nickname to the whitelist (if IP ranges are specified, only connects from that ranges are allowed) // block nicknamefragment1 [nicknamefragment2 ...] // block nicknames that contain all specified fragments (up to 5) // blocki nicknamefragment1 [nicknamefragment2 ...] // block nicknames that contain all specified fragments (up to 5), ignore cases // accept, block and blocki can be shortened to a, b and bi -// the whitelist has higher priority than the blacklist -// to add multiple IP ranges for one nickname, use several 'accept' lines +// 'accept' has higher priority than 'block' +// accept matches whole nicknames; block matches parts of nicknames +// examples: +// accept lorem // 'lorem' is an allowed nickname, no IP restrictions, no blacklist entries can block this name +// accept ipsum 192.168.0.0/16 // accept 'ipsum' only, if he connects with an IP in the range 192.168.0.0-192.168.255.255 +// accept ipsum 10.0.0.0/8 // add another allowed IP range for 'ipsum' +// block dolor // block all nicknames that contain the substring 'dolor', for example 'dolores' or 'odolor'; does not block 'DOLOR' +// block sit amet // block all nicknames that contain both substrings, for example 'sitamet' or 'dametosit' +// blocki consectetur // block all nicknames that contain the substring 'consectetur', ignore case; block for example 'aconsectetur', 'oConSecTeTuR' +// accept foobar 192.168.0.0/16 127.0.0.1 1.2.3.4-2.3.4.5 // accept 'foobar'; allow several IP ranges + +// clan whitelist example: a clan named 'DEV' with two members, 'foo' and 'bar' +// accept DEV|foo 1.2.3.4 // foo has a static IP +// accept DEV|bar 192.168.0.0/16 // bar has a dynamit IP from that range +// block DEV| // don't allow anyone else to wear the clan tag diff --git a/source/src/server.cpp b/source/src/server.cpp index a442d7a8..b6da3845 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -1525,22 +1525,23 @@ char *loadcfgfile(char *cfg, const char *name, int *len) s_strcpy(cfg, name); path(cfg); } - char *buf = loadfile(cfg, len); + char *p, *buf = loadfile(cfg, len); if(!buf) { if(name && logger) logger->writeline(log::info,"could not read config file '%s'", name); return NULL; } - char *p = buf; - while((p = strstr(p, "//")) != NULL) // remove comments - while(p[0] != '\n' && p[0] != '\0') p++[0] = ' '; if('\r' != '\n') // this is not a joke! { - p = buf; - while((p = strchr(p, '\r')) != NULL) p++[0] = ' '; + char c = strchr(buf, '\n') ? ' ' : '\n'; // in files without /n substitute /r with /n, otherwise remove /r + for(p = buf; (p = strchr(p, '\r')); p++) *p = c; } - p = buf; - while((p = strchr(p, '\n')) != NULL) p++[0] = 0; + for(p = buf; (p = strstr(p, "//")); ) // remove comments + { + while(*p != '\n' && *p != '\0') p++[0] = ' '; + } + for(p = buf; (p = strchr(p, '\t')); p++) *p = ' '; + for(p = buf; (p = strchr(p, '\n')); p++) *p = '\0'; // one string per line return buf; } @@ -1548,10 +1549,10 @@ void readscfg(const char *name) { static string cfgfilename; static int cfgfilesize; - const char *sep = ": \t"; + const char *sep = ": "; configset c; char *p, *l; - int i, len; + int i, len, line = 0; if(!name && getfilesize(cfgfilename) == cfgfilesize) return; configsets.setsize(0); @@ -1562,7 +1563,7 @@ void readscfg(const char *name) if(scl.verbose) logger->writeline(log::info,"reading map rotation '%s'", cfgfilename); while(p < buf + len) { - l = p; p += strlen(p) + 1; + l = p; p += strlen(p) + 1; line++; l = strtok(l, sep); if(l) { @@ -1578,7 +1579,11 @@ void readscfg(const char *name) if(i > 2) { configsets.add(c); - if(scl.verbose) logger->writeline(log::info," %s, %s, %d minutes, vote:%d, minplayer:%d, maxplayer:%d, skiplines:%d", c.mapname, modestr(c.mode, true), c.time, c.vote, c.minplayer, c.maxplayer, c.skiplines); + if(scl.verbose) logger->writeline(log::info," %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); + } + else + { + logger->writeline(log::info," error in line %d, file %s", line, cfgfilename); } } } @@ -1586,8 +1591,6 @@ void readscfg(const char *name) logger->writeline(log::info,"read %d map rotation entries from '%s'", configsets.length(), cfgfilename); } -struct iprange { enet_uint32 lr, ur; }; - int cmpiprange(const struct iprange *a, const struct iprange *b) { if(a->lr < b->lr) return -1; @@ -1597,117 +1600,75 @@ int cmpiprange(const struct iprange *a, const struct iprange *b) int cmpipmatch(const struct iprange *a, const struct iprange *b) { return - (a->lr < b->lr) + (a->lr > b->ur); } -bool atoip(const char *s, enet_uint32 *ip) -{ - unsigned int d[4]; - if(!s || sscanf(s, "%u.%u.%u.%u", d, d + 1, d + 2, d + 3) != 4) return false; - *ip = 0; - loopi(4) - { - if(d[i] > 255) return false; - *ip = (*ip << 8) + d[i]; - } - return true; -} +vector ipblacklist; -bool atoipr(const char *s, iprange *ir) -{ - const char *p; - if(!atoip(s, &ir->lr)) return false; - ir->ur = ir->lr; - if((p = strchr(s, '-'))) - { - if(!atoip(p + 1, &ir->ur) || ir->lr > ir->ur) return false; - } - else if((p = strchr(s, '/'))) - { - int m = atoi(p + 1); - if(m < 0 && m > 32) return false; - unsigned long bm = (1 << (32 - m)) - 1; - ir->lr &= ~bm; - ir->ur |= bm; - } - return true; -} - -const char *iptoa(enet_uint32 ip) -{ - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - s_sprintf(s[buf])("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); - return s[buf]; -} - -const char *iprtoa(const struct iprange &ipr) -{ - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - if(ipr.lr == ipr.ur) s_strcpy(s[buf], iptoa(ipr.lr)); - else s_sprintf(s[buf])("%s-%s", iptoa(ipr.lr), iptoa(ipr.ur)); - return s[buf]; -} - -vector blacklist; - -void readblacklist(const char *name) +void readipblacklist(const char *name) { static string blfilename; static int blfilesize; - char *p, *l; + char *p, *l, *r; iprange ir; - int m, len; + int m, len, line = 0, errors = 0; if(!name && getfilesize(blfilename) == blfilesize) return; - blacklist.setsize(0); + ipblacklist.setsize(0); char *buf = loadcfgfile(blfilename, name, &len); blfilesize = len; if(!buf) return; p = buf; - if(scl.verbose) logger->writeline(log::info,"reading blacklist '%s'", blfilename); + if(scl.verbose) logger->writeline(log::info,"reading ip blacklist '%s'", blfilename); while(p < buf + len) { - l = p; p += strlen(p) + 1; - if(atoipr(l, &ir)) blacklist.add(ir); + l = p; p += strlen(p) + 1; line++; + if((r = (char *) atoipr(l, &ir))) + { + ipblacklist.add(ir); + l = r; + } + if(l[strspn(l, " ")]) + { + for(int i = strlen(l) - 1; i > 0 && l[i] == ' '; i--) l[i] = '\0'; + logger->writeline(log::info," error in line %d, file %s: ignored '%s'", line, blfilename, l); + errors++; + } } delete[] buf; - blacklist.sort(cmpiprange); - int orglength = blacklist.length(); - loopv(blacklist) + ipblacklist.sort(cmpiprange); + int orglength = ipblacklist.length(); + loopv(ipblacklist) { if(!i) continue; - if(blacklist[i].ur <= blacklist[i - 1].ur) + if(ipblacklist[i].ur <= ipblacklist[i - 1].ur) { if(scl.verbose) { - if(blacklist[i].lr == blacklist[i - 1].lr && blacklist[i].ur == blacklist[i - 1].ur) - logger->writeline(log::info," blacklist entry %s got dropped (double entry)", iprtoa(blacklist[i])); + if(ipblacklist[i].lr == ipblacklist[i - 1].lr && ipblacklist[i].ur == ipblacklist[i - 1].ur) + logger->writeline(log::info," blacklist entry %s got dropped (double entry)", iprtoa(ipblacklist[i])); else - logger->writeline(log::info," blacklist entry %s got dropped (already covered by %s)", iprtoa(blacklist[i]), iprtoa(blacklist[i - 1])); + logger->writeline(log::info," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipblacklist[i]), iprtoa(ipblacklist[i - 1])); } - blacklist.remove(i--); continue; + ipblacklist.remove(i--); continue; } - if(blacklist[i].lr <= blacklist[i - 1].ur) + if(ipblacklist[i].lr <= ipblacklist[i - 1].ur) { - if(scl.verbose) logger->writeline(log::info," blacklist entries %s and %s are joined due to overlap", iprtoa(blacklist[i - 1]), iprtoa(blacklist[i])); - blacklist[i - 1].ur = blacklist[i].ur; - blacklist.remove(i--); continue; + if(scl.verbose) logger->writeline(log::info," blacklist entries %s and %s are joined due to overlap", iprtoa(ipblacklist[i - 1]), iprtoa(ipblacklist[i])); + ipblacklist[i - 1].ur = ipblacklist[i].ur; + ipblacklist.remove(i--); continue; } } if(scl.verbose) { - loopv(blacklist) logger->writeline(log::info," %s", iprtoa(blacklist[i])); + loopv(ipblacklist) logger->writeline(log::info," %s", iprtoa(ipblacklist[i])); } - logger->writeline(log::info,"read %d (%d) blacklist entries from '%s'", blacklist.length(), orglength, blfilename); + logger->writeline(log::info,"read %d (%d) blacklist entries from '%s', %d errors", ipblacklist.length(), orglength, blfilename, errors); } -bool checkblacklist(enet_uint32 ip) // ip: network byte order +bool checkipblacklist(enet_uint32 ip) // ip: network byte order { iprange t; t.lr = ntohl(ip); // blacklist uses host byte order t.ur = 0; - return blacklist.search(&t, cmpipmatch) != NULL; + return ipblacklist.search(&t, cmpipmatch) != NULL; } #define MAXNICKFRAGMENTS 5 @@ -1734,8 +1695,8 @@ struct nickblacklist { { static string nbfilename; static int nbfilesize; - const char *sep = " \t"; - int len, line = 1; + const char *sep = " "; + int len, line = 1, errors = 0; iprchain iprc; blackline bl; @@ -1744,7 +1705,7 @@ struct nickblacklist { char *buf = loadcfgfile(nbfilename, name, &len); nbfilesize = len; if(!buf) return; - char *l, *s, *p = buf; + char *l, *s, *r, *p = buf; if(scl.verbose) logger->writeline(log::info,"reading nickname blacklist '%s'", nbfilename); while(p < buf + len) { @@ -1759,12 +1720,17 @@ struct nickblacklist { int *i = whitelist.access(s); if(!i) i = &whitelist.access(newstring(s), -1); s += strlen(s) + 1; - if(s < p && atoipr(s, &iprc.ipr)) + if(s < p) { - iprc.next = *i; - *i = whitelistranges.length(); - whitelistranges.add(iprc); + while((r = (char *) atoipr(s, &iprc.ipr))) + { + iprc.next = *i; + *i = whitelistranges.length(); + whitelistranges.add(iprc); + s = r; + } } + else s = NULL; } else if(s && (!strcmp(l, "block") || !strcmp(l, "b") || ic++ || !strcmp(l, "blocki") || !strcmp(l, "bi"))) { // block nickname fragments (ic == ignore case) @@ -1788,7 +1754,8 @@ struct nickblacklist { bl.line = line; blacklines.add(bl); } - else logger->writeline(log::info,"unknown keyword '%s' in line %d, file %s", l, line, nbfilename); + else { logger->writeline(log::info," error in line %d, file %s: unknown keyword '%s'", line, nbfilename, l); errors++; } + if(s && s[strspn(s, " ")]) { logger->writeline(log::info," error in line %d, file %s: ignored '%s'", line, nbfilename, s); errors++; } } line++; } @@ -1815,7 +1782,7 @@ struct nickblacklist { logger->writeline(log::info, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } } - logger->writeline(log::info,"read %d + %d entries from nickname blacklist file '%s'", whitelist.numelems, blacklines.length(), nbfilename); + logger->writeline(log::info,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), nbfilename, errors); } int checknickwhitelist(const char *name, enet_uint32 ip) // ip: network byte order @@ -1871,7 +1838,7 @@ void readpwdfile(const char *name) { static string pwdfilename; static int pwdfilesize; - const char *sep = " \t"; + const char *sep = " "; pwddetail c; char *p, *l; int i, len, line, par[ADMINPWD_MAXPAR]; @@ -1910,7 +1877,13 @@ void readpwdfile(const char *name) c.line = line; c.denyadmin = par[0] > 0; adminpwds.add(c); - if(scl.verbose) logger->writeline(log::info,"line%4d: %s %d", c.line, c.pwd, c.denyadmin ? 1 : 0); + if(scl.verbose) + { + static string text; + s_strcpy(text, c.pwd); + for(i = strlen(text) - 1; i > 2; i--) text[i] = '*'; + logger->writeline(log::info,"line%4d: %s %d", c.line, text, c.denyadmin ? 1 : 0); + } } } line++; @@ -2227,7 +2200,7 @@ bool isbanned(int cn) if(b.millis < servmillis) { bans.remove(i--); } if(b.address.host == c.peer->address.host) { return true; } } - return checkblacklist(c.peer->address.host); + return checkipblacklist(c.peer->address.host); } int serveroperator() @@ -3434,7 +3407,7 @@ void rereadcfgs(void) cfgupdate = servmillis; readscfg(NULL); readpwdfile(NULL); - readblacklist(NULL); + readipblacklist(NULL); nbl.readnickblacklist(NULL); } } @@ -3628,7 +3601,7 @@ int getpongflags(enet_uint32 ip) int flags = mastermode << PONGFLAG_MASTERMODE; flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0; loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; } - flags |= checkblacklist(ip) ? 1 << PONGFLAG_BLACKLIST : 0; + flags |= checkipblacklist(ip) ? 1 << PONGFLAG_BLACKLIST : 0; return flags; } @@ -3776,7 +3749,7 @@ void initserver(bool dedicated) readscfg(scl.maprot); readpwdfile(scl.pwdfile); - readblacklist(scl.blfile); + readipblacklist(scl.blfile); nbl.readnickblacklist(scl.nbfile); if(scl.verbose) { diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 09fb86ea..ecb83efc 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -466,6 +466,60 @@ void strtoupper(char *s) } } +const char *atoip(const char *s, enet_uint32 *ip) +{ + unsigned int d[4]; + int n; + if(!s || sscanf(s, "%u.%u.%u.%u%n", d, d + 1, d + 2, d + 3, &n) != 4) return NULL; + *ip = 0; + loopi(4) + { + if(d[i] > 255) return NULL; + *ip = (*ip << 8) + d[i]; + } + return s + n; +} + +const char *atoipr(const char *s, iprange *ir) +{ + if((s = atoip(s, &ir->lr)) == NULL) return NULL; + ir->ur = ir->lr; + s += strspn(s, " \t"); + if(*s == '-') + { + if(!(s = atoip(s + 1, &ir->ur)) || ir->lr > ir->ur) return NULL; + } + else if(*s == '/') + { + int m, n; + if(sscanf(s + 1, "%d%n", &m, &n) != 1 || m < 0 || m > 32) return NULL; + unsigned long bm = (1 << (32 - m)) - 1; + ir->lr &= ~bm; + ir->ur |= bm; + s += 1 + n; + } + return s; +} + +const char *iptoa(const enet_uint32 ip) +{ + static string s[2]; + static int buf = 0; + buf = (buf + 1) % 2; + s_sprintf(s[buf])("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); + return s[buf]; +} + +const char *iprtoa(const struct iprange &ipr) +{ + static string s[2]; + static int buf = 0; + buf = (buf + 1) % 2; + if(ipr.lr == ipr.ur) s_strcpy(s[buf], iptoa(ipr.lr)); + else s_sprintf(s[buf])("%s-%s", iptoa(ipr.lr), iptoa(ipr.ur)); + return s[buf]; +} + //////////////// geometry utils //////////////// static inline float det2x2(float a, float b, float c, float d) { return a*d - b*c; } diff --git a/source/src/tools.h b/source/src/tools.h index a419e292..9c59f066 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -602,6 +602,12 @@ extern void endianswap(void *, int, int); extern bool isbigendian(); extern void strtoupper(char *s); +struct iprange { enet_uint32 lr, ur; }; +extern const char *atoip(const char *s, enet_uint32 *ip); +extern const char *atoipr(const char *s, iprange *ir); +extern const char *iptoa(const enet_uint32 ip); +extern const char *iprtoa(const struct iprange &ipr); + #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) extern void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep); #endif