diff --git a/config/nicknameblacklist.cfg b/config/nicknameblacklist.cfg index 348f3faa..93329ccf 100644 --- a/config/nicknameblacklist.cfg +++ b/config/nicknameblacklist.cfg @@ -2,7 +2,8 @@ // ===================================== // WARNING: You can NOT use CubeScript in here!! -// accept nickname [IP range ...] // add this nickname to the whitelist (if IP ranges are specified, only connects from that ranges are allowed) +// accept nickname [IP range ...] [password ...] // add this nickname to the whitelist +// (if IP ranges are specified, only connects from that ranges are allowed; if one or more passwords are specified, the connect password has to match) // 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 @@ -15,13 +16,14 @@ // 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' +// accept ipsum foo bar // add two possible passwords 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 +// accept DEV|foo 1.2.3.4 // foo has a static IP; no password required +// accept DEV|bar 192.168.0.0/16 pwdpwd // bar has a dynamic IP from that range; in addition, the connect password has to match 'pwdpwd' // 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 f7ef3b94..7269c397 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -261,6 +261,7 @@ struct client // server side version of "dynent" type bool at3_dontmove; int spawnindex; int salt; + string pwd; int mapcollisions, farpickups; gameevent &addevent() @@ -1747,10 +1748,10 @@ bool checkipblacklist(enet_uint32 ip) // ip: network byte order } #define MAXNICKFRAGMENTS 5 -enum { NWL_UNLISTED = 0, NWL_PASS, NWL_FAIL }; +enum { NWL_UNLISTED = 0, NWL_PASS, NWL_PWDFAIL, NWL_IPFAIL }; struct nickblacklist { - struct iprchain { struct iprange ipr; int next; }; + struct iprchain { struct iprange ipr; const char *pwd; int next; }; struct blackline { int frag[MAXNICKFRAGMENTS]; bool ignorecase; int line; void clear() { loopi(MAXNICKFRAGMENTS) frag[i] = -1; } }; hashtable whitelist; vector whitelistranges; @@ -1795,17 +1796,21 @@ struct nickblacklist { int *i = whitelist.access(s); if(!i) i = &whitelist.access(newstring(s), -1); s += strlen(s) + 1; - if(s < p) + while(s < p) { - while((r = (char *) atoipr(s, &iprc.ipr))) + r = (char *) atoipr(s, &iprc.ipr); + s += strspn(s, sep); + iprc.pwd = r && *s ? NULL : newstring(s, strcspn(s, sep)); + if(r || *s) { iprc.next = *i; *i = whitelistranges.length(); whitelistranges.add(iprc); - s = r; + s = r ? r : s + strlen(iprc.pwd); } + else break; } - else s = NULL; + s = NULL; } else if(s && (!strcmp(l, "block") || !strcmp(l, "b") || ic++ || !strcmp(l, "blocki") || !strcmp(l, "bi"))) { // block nickname fragments (ic == ignore case) @@ -1842,7 +1847,12 @@ struct nickblacklist { enumeratekt(whitelist, const char *, key, int, idx, { text[0] = '\0'; - for(int i = idx; i >= 0; i = whitelistranges[i].next) { s_strcat(text, " "); s_strcat(text, iprtoa(whitelistranges[i].ipr)); } + for(int i = idx; i >= 0; i = whitelistranges[i].next) + { + iprchain &ic = whitelistranges[i]; + if(ic.pwd) s_strcatf(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); + else s_strcatf(text, " %s", iprtoa(ic.ipr)); + } logger->writeline(log::info, " accept %s%s", key, text); }); logger->writeline(log::info," nickname blacklist (%d entries):", blacklines.length()); @@ -1868,13 +1878,25 @@ struct nickblacklist { int *idx = whitelist.access(c.name); if(!idx) return NWL_UNLISTED; // no matching entry int i = *idx; - if(i < 0) return NWL_PASS; // no IP ranges specified + bool needipr = false, iprok = false, needpwd = false, pwdok = false; while(i >= 0) { - if(!cmpipmatch(&ipr, &whitelistranges[i].ipr)) return NWL_PASS; // range match found + iprchain &ic = whitelistranges[i]; + if(ic.pwd) + { // check pwd + needpwd = true; + if(pwdok || !strcmp(genpwdhash(c.name, ic.pwd, c.salt), c.pwd)) pwdok = true; + } + else + { // check IP + needipr = true; + if(!cmpipmatch(&ipr, &ic.ipr)) iprok = true; // range match found + } i = whitelistranges[i].next; } - return NWL_FAIL; // wrong IP + if(needpwd && !pwdok) return NWL_PWDFAIL; // wrong PWD + if(needipr && !iprok) return NWL_IPFAIL; // wrong IP + return NWL_PASS; } int checknickblacklist(const char *name) @@ -1953,13 +1975,7 @@ void readpwdfile(const char *name) c.line = line; c.denyadmin = par[0] > 0; adminpwds.add(c); - 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); - } + if(scl.verbose) logger->writeline(log::info,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0); } } line++; @@ -2856,16 +2872,18 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 s_strncpy(cl->name, text, MAXNAMELEN+1); getstring(text, p); + s_strcpy(cl->pwd, text); int wantrole = getint(p); cl->state.nextprimary = getint(p); bool banned = isbanned(sender); bool srvfull = numnonlocalclients() > scl.maxclients; bool srvprivate = mastermode == MM_PRIVATE; int bl = 0, wl = nbl.checknickwhitelist(*cl); + const char *wlp = wl == NWL_PASS ? ", nickname whitelist match" : ""; if(wl == NWL_UNLISTED) bl = nbl.checknickblacklist(cl->name); - if(wl == NWL_FAIL) - { // nickname matches whitelist, but IP is not in the required range - logger->writeline(log::info, "[%s] '%s' matches nickname whitelist: wrong IP", cl->hostname, cl->name); + if(wl == NWL_IPFAIL || wl == NWL_PWDFAIL) + { // nickname matches whitelist, but IP is not in the required range or PWD doesn't match + logger->writeline(log::info, "[%s] '%s' matches nickname whitelist: wrong %s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD"); disconnect_client(sender, DISC_BADNICK); } else if(bl > 0) @@ -2890,29 +2908,24 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 break; } } - logger->writeline(log::info, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, banremoved ? ", (ban removed)" : ""); + logger->writeline(log::info, "[%s] %s logged in using the admin password in line %d%s%s", cl->hostname, cl->name, pd.line, wlp, banremoved ? ", (ban removed)" : ""); } else if(scl.serverpassword[0] && !(srvprivate || srvfull || banned)) { // server password required if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), text)) { cl->isauthed = true; - logger->writeline(log::info, "[%s] %s client logged in (using serverpassword)", cl->hostname, cl->name); + logger->writeline(log::info, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, wlp); } else disconnect_client(sender, DISC_WRONGPW); } else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE); else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS); else if(banned) disconnect_client(sender, DISC_BANREFUSE); - else if(wl == NWL_PASS) - { - cl->isauthed = true; - logger->writeline(log::info, "[%s] %s client logged in (match on nickname whitelist)", cl->hostname, cl->name); - } else { cl->isauthed = true; - logger->writeline(log::info, "[%s] %s logged in (default)", cl->hostname, cl->name); + logger->writeline(log::info, "[%s] %s logged in (default)%s", cl->hostname, cl->name, wlp); } } if(!cl->isauthed) return; @@ -3036,8 +3049,9 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { switch(nbl.checknickwhitelist(*cl)) { - case NWL_FAIL: - logger->writeline(log::info, "[%s] '%s' matches nickname whitelist: wrong IP", cl->hostname, cl->name); + case NWL_PWDFAIL: + case NWL_IPFAIL: + logger->writeline(log::info, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name); disconnect_client(sender, DISC_BADNICK); break; @@ -3967,7 +3981,7 @@ void initserver(bool dedicated) if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logger->writeline(log::info,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); logger->writeline(log::info,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold); if(scl.master) logger->writeline(log::info,"master server URL: \"%s\"", scl.master); - if(scl.serverpassword[0]) logger->writeline(log::info,"server password: \"%s\"", scl.serverpassword); + if(scl.serverpassword[0]) logger->writeline(log::info,"server password: \"%s\"", hiddenpwd(scl.serverpassword)); } } diff --git a/source/src/tools.cpp b/source/src/tools.cpp index f46f9834..7d18c6a6 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -146,12 +146,17 @@ void sethomedir(const char *dir) const char substitute[] = "?MYDOCUMENTS?"; if(!strncmp(dir, substitute, strlen(substitute))) { - char *mydocuments = getregszvalue(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", "Personal"); + const char *regpath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + char *mydocuments = getregszvalue(HKEY_CURRENT_USER, regpath, "Personal"); if(mydocuments) { s_sprintf(tmpdir)("%s%s", mydocuments, dir+strlen(substitute)); delete[] mydocuments; } + else + { + printf("failed to retrieve 'Personal' path from '%s'\n", regpath); + } } #endif @@ -585,6 +590,21 @@ const char *iprtoa(const struct iprange &ipr) return s[buf]; } +char *s_strcatf(char *d, const char *s, ...) +{ + static s_sprintfdv(temp, s); + return s_strcat(d, temp); +} + +const char *hiddenpwd(const char *pwd, int showchars) +{ + static int sc = 3; + static string text; + s_strcpy(text, pwd); + if(showchars > 0) sc = showchars; + for(int i = strlen(text) - 1; i >= sc; i--) text[i] = '*'; + return text; +} //////////////// 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 5d0d3505..9f724364 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -108,7 +108,7 @@ inline void formatstring(char *d, const char *fmt, va_list v) { _vsnprintf(d, _M inline char *s_strncpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[m-1] = 0; return d; } inline char *s_strcpy(char *d, const char *s) { return s_strncpy(d,s,_MAXDEFSTR); } inline char *s_strcat(char *d, const char *s) { size_t n = strlen(d); return s_strncpy(d+n,s,_MAXDEFSTR-n); } - +extern char *s_strcatf(char *d, const char *s, ...); struct s_sprintf_f { @@ -607,6 +607,7 @@ 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); +extern const char *hiddenpwd(const char *pwd, int showchars = 0); #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) extern void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep);