Skip to content

Commit e9683fd

Browse files
committed
Add network security: rate limiting, connection limits, monitor auth
1 parent 1f8b63d commit e9683fd

5 files changed

Lines changed: 146 additions & 12 deletions

File tree

src/core/CNProtocol.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include "CNStructs.hpp"
33

44
#include <assert.h>
5+
#include <map>
6+
7+
static std::map<uint32_t, int> connectionsPerIP;
58

69
// ========================================================[[ CNSocketEncryption ]]========================================================
710

@@ -462,6 +465,21 @@ void CNServer::start() {
462465
if (!setSockNonblocking(sock, newConnectionSocket))
463466
continue;
464467

468+
uint32_t connIP = address.sin_addr.s_addr;
469+
auto ipIt = connectionsPerIP.find(connIP);
470+
if (settings::MAXPERIP > 0 && ipIt != connectionsPerIP.end() && ipIt->second >= settings::MAXPERIP) {
471+
std::cout << "[WARN] Rejecting connection from " << inet_ntoa(address.sin_addr) << " (too many connections)" << std::endl;
472+
#ifdef _WIN32
473+
shutdown(newConnectionSocket, SD_BOTH);
474+
closesocket(newConnectionSocket);
475+
#else
476+
shutdown(newConnectionSocket, SHUT_RDWR);
477+
close(newConnectionSocket);
478+
#endif
479+
continue;
480+
}
481+
connectionsPerIP[connIP]++;
482+
465483
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
466484

467485
addPollFD(newConnectionSocket);
@@ -507,6 +525,10 @@ void CNServer::start() {
507525
CNSocket *cSock = it->second;
508526

509527
if (!cSock->isAlive()) {
528+
uint32_t deadIP = cSock->sockaddr.sin_addr.s_addr;
529+
if (connectionsPerIP.count(deadIP) && --connectionsPerIP[deadIP] <= 0)
530+
connectionsPerIP.erase(deadIP);
531+
510532
killConnection(cSock);
511533
it = connections.erase(it);
512534

src/servers/CNLoginServer.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,52 @@
1313

1414
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
1515

16+
struct LoginAttempt {
17+
int failCount;
18+
time_t firstFail;
19+
time_t blockedUntil;
20+
};
21+
static std::map<uint32_t, LoginAttempt> loginAttempts;
22+
23+
static bool isLoginRateLimited(uint32_t ip) {
24+
if (settings::LOGINRATELIMIT <= 0)
25+
return false;
26+
27+
auto it = loginAttempts.find(ip);
28+
if (it == loginAttempts.end())
29+
return false;
30+
31+
time_t now = getTime();
32+
if (it->second.blockedUntil > now)
33+
return true;
34+
35+
// reset if window expired
36+
if (now - it->second.firstFail > 60000) {
37+
loginAttempts.erase(it);
38+
return false;
39+
}
40+
41+
return false;
42+
}
43+
44+
static void recordLoginFailure(uint32_t ip) {
45+
if (settings::LOGINRATELIMIT <= 0)
46+
return;
47+
48+
time_t now = getTime();
49+
auto it = loginAttempts.find(ip);
50+
if (it == loginAttempts.end() || now - it->second.firstFail > 60000) {
51+
loginAttempts[ip] = { 1, now, 0 };
52+
return;
53+
}
54+
55+
it->second.failCount++;
56+
if (it->second.failCount >= settings::LOGINRATELIMIT) {
57+
it->second.blockedUntil = now + 60000;
58+
std::cout << "[WARN] Rate limiting login attempts from IP" << std::endl;
59+
}
60+
}
61+
1662
namespace LoginServer {
1763
std::vector<std::string> WheelFirstNames;
1864
std::vector<std::string> WheelMiddleNames;
@@ -113,6 +159,12 @@ void loginFail(LoginError errorCode, std::string userLogin, CNSocket* sock) {
113159
void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
114160
auto login = (sP_CL2LS_REQ_LOGIN*)data->buf;
115161

162+
uint32_t ip = sock->sockaddr.sin_addr.s_addr;
163+
if (isLoginRateLimited(ip)) {
164+
loginFail(LoginError::LOGIN_ERROR, "", sock);
165+
return;
166+
}
167+
116168
std::string userLogin;
117169
std::string userToken; // could be password or auth cookie
118170

@@ -159,11 +211,13 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
159211
return newAccount(sock, userLogin, userToken, login->iClientVerC);
160212
}
161213

214+
recordLoginFailure(ip);
162215
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
163216
}
164217

165218
// make sure either a valid cookie or password was sent
166219
if (!CNLoginServer::checkToken(sock, findUser, userToken, isCookieAuth)) {
220+
recordLoginFailure(ip);
167221
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
168222
}
169223

src/servers/Monitor.cpp

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include "settings.hpp"
99

1010
#include <cstdio>
11+
#include <set>
1112

1213
static SOCKET listener;
1314
static std::mutex sockLock; // guards socket list
1415
static std::list<SOCKET> sockets;
16+
static std::set<SOCKET> pendingAuth; // sockets that haven't authenticated yet
1517
static sockaddr_in address;
1618

1719
std::vector<std::string> Monitor::chats;
@@ -21,28 +23,33 @@ std::vector<std::string> Monitor::namereqs;
2123

2224
using namespace Monitor;
2325

26+
static void closeSock(SOCKET sock) {
27+
#ifdef _WIN32
28+
shutdown(sock, SD_BOTH);
29+
closesocket(sock);
30+
#else
31+
shutdown(sock, SHUT_RDWR);
32+
close(sock);
33+
#endif
34+
}
35+
2436
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
2537
int n = 0;
2638
int sock = *it;
2739

2840
while (n < len) {
29-
n += send(sock, buff+n, len-n, 0);
30-
if (SOCKETERROR(n)) {
41+
int ret = send(sock, buff+n, len-n, 0);
42+
if (SOCKETERROR(ret)) {
3143
printSocketError("send");
3244

33-
#ifdef _WIN32
34-
shutdown(sock, SD_BOTH);
35-
closesocket(sock);
36-
#else
37-
shutdown(sock, SHUT_RDWR);
38-
close(sock);
39-
#endif
45+
closeSock(sock);
4046

4147
std::cout << "[INFO] Disconnected a monitor" << std::endl;
4248

4349
it = sockets.erase(it);
4450
return false;
4551
}
52+
n += ret;
4653
}
4754

4855
return true;
@@ -67,7 +74,7 @@ static int process_email(char *buff, std::string email) {
6774
int i = 6;
6875

6976
for (char c : email) {
70-
if (i == BUFSIZE-2)
77+
if (i >= BUFSIZE-3)
7178
break;
7279

7380
buff[i++] = c;
@@ -86,6 +93,38 @@ static void tick(CNServer *serv, time_t delta) {
8693
char buff[BUFSIZE];
8794
int n;
8895

96+
// check pending auth sockets for incoming password
97+
for (auto it = pendingAuth.begin(); it != pendingAuth.end(); ) {
98+
SOCKET s = *it;
99+
char authbuf[256] = {};
100+
int recved = recv(s, authbuf, sizeof(authbuf) - 1, 0);
101+
if (recved > 0) {
102+
// strip trailing newline/whitespace
103+
std::string pass(authbuf, recved);
104+
while (!pass.empty() && (pass.back() == '\n' || pass.back() == '\r' || pass.back() == ' '))
105+
pass.pop_back();
106+
if (timingSafeStrcmp(pass.c_str(), settings::MONITORPASS.c_str()) == 0) {
107+
sockets.push_back(s);
108+
std::cout << "[INFO] Monitor client authenticated" << std::endl;
109+
} else {
110+
std::cout << "[WARN] Monitor client failed authentication" << std::endl;
111+
closeSock(s);
112+
}
113+
it = pendingAuth.erase(it);
114+
} else if (recved == 0) {
115+
closeSock(s);
116+
it = pendingAuth.erase(it);
117+
} else {
118+
// EWOULDBLOCK is fine, just wait
119+
if (OF_ERRNO != OF_EWOULD) {
120+
closeSock(s);
121+
it = pendingAuth.erase(it);
122+
} else {
123+
++it;
124+
}
125+
}
126+
}
127+
89128
auto it = sockets.begin();
90129
outer:
91130
while (it != sockets.end()) {
@@ -179,7 +218,11 @@ bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
179218
{
180219
std::lock_guard<std::mutex> lock(sockLock);
181220

182-
sockets.push_back(sock);
221+
if (settings::MONITORPASS.empty()) {
222+
sockets.push_back(sock);
223+
} else {
224+
pendingAuth.insert(sock);
225+
}
183226
}
184227

185228
return true;

src/settings.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ std::string settings::SANDBOXEXTRAPATH = "";
1414
int settings::LOGINPORT = 23000;
1515
bool settings::APPROVEWHEELNAMES = true;
1616
bool settings::APPROVECUSTOMNAMES = true;
17-
bool settings::AUTOCREATEACCOUNTS = true;
17+
bool settings::AUTOCREATEACCOUNTS = false;
1818
std::string settings::AUTHMETHODS = "password";
1919
int settings::DBSAVEINTERVAL = 240;
2020

@@ -77,6 +77,15 @@ bool settings::IZRACESCORECAPPED = true;
7777
// drop fixes enabled
7878
bool settings::DROPFIXESENABLED = false;
7979

80+
// monitor auth
81+
std::string settings::MONITORPASS = "";
82+
83+
// per-IP connection limit
84+
int settings::MAXPERIP = 5;
85+
86+
// login rate limit (max failed attempts per 60s)
87+
int settings::LOGINRATELIMIT = 5;
88+
8089
void settings::init() {
8190
INIReader reader("config.ini");
8291

@@ -130,6 +139,9 @@ void settings::init() {
130139
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
131140
MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP);
132141
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
142+
MONITORPASS = reader.Get("monitor", "password", MONITORPASS);
143+
MAXPERIP = reader.GetInteger("shard", "maxperip", MAXPERIP);
144+
LOGINRATELIMIT = reader.GetInteger("login", "ratelimit", LOGINRATELIMIT);
133145

134146
if (DROPFIXESENABLED) {
135147
std::cout << "[INFO] Drop fixes enabled" << std::endl;

src/settings.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ namespace settings {
4646
extern bool DISABLEFIRSTUSEFLAG;
4747
extern bool IZRACESCORECAPPED;
4848
extern bool DROPFIXESENABLED;
49+
extern std::string MONITORPASS;
50+
extern int MAXPERIP;
51+
extern int LOGINRATELIMIT;
4952

5053
void init();
5154
}

0 commit comments

Comments
 (0)