diff --git a/src/mastersrv/master.c b/src/mastersrv/master.c
index 1e1bc7b0e1d0d4a901bf6c6d562e8923d8405df6..6bb4ffd22ef92de2ae8e551a4a32c1d0d948f2b1 100644 (file)
--- a/src/mastersrv/master.c
+++ b/src/mastersrv/master.c
+/* Copyright (C) Doom 2D: Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License ONLY.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
-#define ENET_DEBUG 1
#include <enet/enet.h>
#include <enet/types.h>
#define MS_URGENT_FILE "urgent.txt"
#define MS_MOTD_FILE "motd.txt"
#define MS_BAN_FILE "master_bans.txt"
+#define MS_PIPE_FILE "d2df_master.pipe"
#define DEFAULT_SPAM_CAP 10
#define DEFAULT_MAX_SERVERS MS_MAX_SERVERS
char map[MAX_STRLEN + 2];
time_t death_time;
time_t timestamp;
+ ENetPeer *peer; // who sent this server in
} server_t;
// real servers
/* common utility functions */
+static char *u_strstrip(char *p) {
+ if (!p) return p;
+ while (isspace(*p)) ++p;
+ const size_t len = strlen(p);
+ if (len) {
+ for (size_t i = len - 1; i && isspace(p[i]); --i)
+ p[i] = '\0';
+ }
+ return p;
+}
+
static char *u_vabuf(void) {
static char vabuf[4][MAX_STRLEN];
static int idx = 0;
}
static inline enet_uint32 u_masktoprefix(const enet_uint32 mask) {
- return (32 - __builtin_ctz(mask));
+ return (32 - __builtin_ctz(ENET_NET_TO_HOST_32(mask)));
}
static inline void u_printsv(const server_t *sv) {
/* server functions */
-static void sv_remove(const enet_uint32 host, const enet_uint16 port) {
- for (int i = 0; i < max_servers; ++i) {
- if (servers[i].host == host && servers[i].port == port) {
- servers[i].host = 0;
- servers[i].port = 0;
- --num_servers;
+static inline void sv_remove(server_t *sv) {
+ if (sv->host) {
+ // drop the associated peer, if any
+ if (sv->peer && sv->peer->state == ENET_PEER_STATE_CONNECTED && sv->peer->data == sv) {
+ sv->peer->data = NULL;
+ enet_peer_reset(sv->peer);
}
+ sv->host = 0;
+ sv->port = 0;
+ sv->peer = NULL;
+ --num_servers;
+ }
+}
+
+static void sv_remove_by_addr(const enet_uint32 host, const enet_uint16 port) {
+ for (int i = 0; i < max_servers; ++i) {
+ if (servers[i].host == host && servers[i].port == port)
+ sv_remove(servers + i);
}
}
static void sv_remove_by_host(enet_uint32 host, enet_uint32 mask) {
host &= mask;
for (int i = 0; i < max_servers; ++i) {
- if (servers[i].host && (servers[i].host & mask) == host) {
- servers[i].host = 0;
- servers[i].port = 0;
- --num_servers;
- }
+ if (servers[i].host && (servers[i].host & mask) == host)
+ sv_remove(servers + i);
}
}
@@ -398,6 +437,14 @@ static inline server_t *sv_find_or_add(const enet_uint32 host, const enet_uint32
return empty;
}
+static inline void sv_clear_peer(ENetPeer *peer) {
+ server_t *sv = peer->data;
+ if (sv) {
+ sv->peer = NULL;
+ peer->data = NULL;
+ }
+}
+
/* ban list functions */
static inline time_t ban_get_time(const int cnt) {
@@ -460,7 +507,7 @@ static ban_record_t *ban_record_add_addr(const enet_uint32 host, const enet_uint
return rec;
}
-static ban_record_t *ban_record_add_ip(const char *ip, const int cnt, const time_t cur) {
+static enet_uint32 ban_parse_ip_mask(const char *ip, enet_uint32 *out_mask) {
enet_uint32 prefix = 32;
// find and get the prefix length, if any
@@ -473,15 +520,23 @@ static ban_record_t *ban_record_add_ip(const char *ip, const int cnt, const time
}
ENetAddress addr = { 0 };
- if (enet_address_set_host_ip(&addr, ip_copy) != 0) {
- u_log(LOG_ERROR, "banlist: `%s` is not a valid IP address", ip_copy);
- return NULL;
- }
+ if (enet_address_set_host_ip(&addr, ip_copy) != 0)
+ return 0;
// transform prefix length into mask
- const enet_uint32 mask = u_prefixtomask(prefix);
+ *out_mask = u_prefixtomask(prefix);
- return ban_record_add_addr(addr.host, mask, cnt, cur);
+ return addr.host;
+}
+
+static ban_record_t *ban_record_add_ip(const char *ip, const int cnt, const time_t cur) {
+ enet_uint32 mask = 0;
+ const enet_uint32 host = ban_parse_ip_mask(ip, &mask);
+ if (!host) {
+ u_log(LOG_ERROR, "banlist: `%s` is not a valid address", ip);
+ return NULL;
+ }
+ return ban_record_add_addr(host, mask, cnt, cur);
}
static void ban_free_list(void) {
continue;
char ip[21] = { 0 }; // optionally includes the "/nn" prefix length at the end
- time_t exp = 0;
+ int64_t exp64 = 0;
int count = 0;
- if (sscanf(ln, "%20s %ld %d", ip, &exp, &count) < 3) {
+ if (sscanf(ln, "%20s %lld %d", ip, &exp64, &count) < 3) {
u_log(LOG_ERROR, "banlist: malformed line: `%s`", ln);
continue;
}
+ const time_t exp = (time_t)exp64; // shut up gcc
if (ban_record_add_ip(ip, count, exp))
u_log(LOG_NOTE, "banlist: banned %s until %s (ban level %d)", ip, u_strtime(exp), count);
}
for (ban_record_t *rec = banlist; rec; rec = rec->next) {
if (rec->ban_count)
- fprintf(f, "%s/%u %ld %d\n", u_iptostr(rec->host), u_masktoprefix(rec->mask), rec->cur_ban, rec->ban_count);
+ fprintf(f, "%s/%u %lld %d\n", u_iptostr(rec->host), u_masktoprefix(rec->mask), (int64_t)rec->cur_ban, rec->ban_count);
}
fclose(f);
return true;
}
+static void ban_add_mask(const enet_uint32 host, const enet_uint32 mask, const char *reason) {
+ const time_t now = time(NULL);
+
+ ban_record_t *rec = ban_record_add_addr(host, mask, 0, 0);
+ if (!rec) u_fatal("OOM trying to ban %s", u_iptostr(host));
+
+ rec->cur_ban = now + ban_get_time(rec->ban_count);
+ rec->ban_count++;
+
+ u_log(LOG_NOTE, "banned %s until %s, reason: %s, ban level: %d", u_iptostr(rec->host), u_strtime(rec->cur_ban), reason, rec->ban_count);
+
+ ban_save_list(MS_BAN_FILE);
+
+ sv_remove_by_host(host, mask);
+
+ if (host == cl_last_addr)
+ cl_last_addr = 0;
+}
+
static void ban_add(const enet_uint32 host, const char *reason) {
const time_t now = time(NULL);
static inline void ban_peer(ENetPeer *peer, const char *reason) {
if (peer) {
ban_add(peer->address.host, reason);
- peer->data = NULL;
+ sv_clear_peer(peer);
enet_peer_reset(peer);
}
}
/* main */
+#if ENABLE_PIPE
+
+static int io_fd = -1;
+
+static bool io_install_pipe(void) {
+ const int rc = mkfifo(MS_PIPE_FILE, 0664);
+ if (rc < 0 && errno != EEXIST) {
+ u_log(LOG_ERROR, "io_install_pipe(): mkfifo(): %s", strerror(errno));
+ return false;
+ }
+
+ io_fd = open(MS_PIPE_FILE, O_RDONLY | O_NONBLOCK);
+ if (io_fd < 0) {
+ u_log(LOG_ERROR, "io_install_pipe(): open(): %s", strerror(errno));
+ remove(MS_PIPE_FILE);
+ return false;
+ }
+
+ return true;
+}
+
+static void io_uninstall_pipe(void) {
+ if (io_fd >= 0) {
+ close(io_fd);
+ io_fd = -1;
+ }
+ remove(MS_PIPE_FILE);
+}
+
+static void io_read_commands(void) {
+ if (io_fd < 0)
+ return;
+
+ char cmd[128];
+ const int cmd_len = read(io_fd, cmd, sizeof(cmd) - 1);
+ if (cmd_len < 1)
+ return;
+ cmd[cmd_len] = '\0';
+
+ if (!strncmp(cmd, "ban ", 4)) {
+ const char *ip = u_strstrip(cmd + 4); // skip "ban "
+ enet_uint32 mask = 0;
+ enet_uint32 host = ban_parse_ip_mask(ip, &mask);
+ if (!host) {
+ u_log(LOG_ERROR, "ban: `%s` is not a valid address", ip);
+ return;
+ }
+ ban_add_mask(host, mask, "banned by console");
+ } else if (!strncmp(cmd, "reload", 6)) {
+ u_log(LOG_WARN, "reloading banlist");
+ ban_free_list();
+ ban_load_list(MS_BAN_FILE);
+ } else if (!strncmp(cmd, "die", 3)) {
+ u_log(LOG_WARN, "shutting down");
+ exit(0);
+ }
+}
+
+#endif
+
static void deinit(void) {
// ban_save_list(MS_BAN_FILE);
ban_free_list();
ms_host = NULL;
}
enet_deinitialize();
-}
-
-#ifdef SIGUSR1
-static void sigusr_handler(int signum) {
- if (signum == SIGUSR1) {
- u_log(LOG_WARN, "received SIGUSR1, reloading banlist");
- ban_free_list();
- ban_load_list(MS_BAN_FILE);
- }
-}
+#ifdef ENABLE_PIPE
+ io_uninstall_pipe();
#endif
+}
static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) {
server_t *sv = NULL;
// only then update the times
sv->death_time = now + ms_sv_timeout;
sv->timestamp = now;
+ // check if we're updating from a new peer
+ if (sv->peer != peer) {
+ // if there was an old one, kill it
+ if (sv->peer) {
+ sv->peer->data = NULL;
+ enet_peer_reset(sv->peer);
+ }
+ sv->peer = peer;
+ peer->data = sv;
+ }
u_log(LOG_NOTE, "updated server #%d:", sv - servers);
u_printsv(sv);
} else {
ban_peer(peer, "tripped sanity check");
return true;
}
+ sv->peer = peer;
+ peer->data = sv;
++num_servers;
u_log(LOG_NOTE, "added new server #%d:", sv - servers);
u_printsv(sv);
ban_peer(peer, "malformed MSG_RM");
return true;
}
- sv_remove(peer->address.host, tmpsv.port);
+ sv_remove_by_addr(peer->address.host, tmpsv.port);
+ // this peer can be disconnected pretty much immediately since he has no servers left, tell him to fuck off
+ sv_clear_peer(peer);
+ enet_peer_disconnect_later(peer, 0);
return true;
case NET_MSG_LIST:
enet_peer_send(peer, NET_CH_MAIN, p);
// enet_host_flush(ms_host);
+ // this peer can be disconnected pretty much immediately after receiving the server list, tell him to fuck off
+ sv_clear_peer(peer);
+ enet_peer_disconnect_later(peer, 0);
+
u_log(LOG_NOTE, "sent server list to %s:%d (ver %s)", u_iptostr(peer->address.host), peer->address.port, clientver[0] ? clientver : "<old>");
return true;
atexit(deinit);
-#ifdef SIGUSR1
- signal(SIGUSR1, sigusr_handler);
+#ifdef ENABLE_PIPE
+ io_install_pipe();
#endif
ENetAddress addr;
while (running) {
while (enet_host_service(ms_host, &event, 10) > 0) {
const time_t now = time(NULL);
- bool filtered = !event.peer || (ms_spam_cap && spam_filter(event.peer, now));
- if (!filtered && event.peer->data) {
- // kick people that have overstayed their welcome
- const time_t timeout = (time_t)(intptr_t)event.peer->data;
- if (timeout < now) filtered = true;
- }
+ const bool filtered = !event.peer || (ms_spam_cap && spam_filter(event.peer, now));
if (!filtered) {
switch (event.type) {
u_log(LOG_NOTE, "%s:%d connected", u_iptostr(event.peer->address.host), event.peer->address.port);
if (event.peer->channelCount != NET_CH_COUNT)
ban_peer(event.peer, "what is this");
- else // store timeout in the data field
- event.peer->data = (void *)(intptr_t)(now + ms_cl_timeout);
+ else
+ enet_peer_timeout(event.peer, 0, 0, ms_cl_timeout * 1000);
break;
case ENET_EVENT_TYPE_RECEIVE:
if (!handle_msg(msgid, event.peer)) {
// cheeky cunt sending invalid messages
ban_peer(event.peer, "unknown message");
- } else {
- // can't reset connection right now because we still have packets to dispatch
- enet_peer_disconnect_later(event.peer, 0);
}
break;
case ENET_EVENT_TYPE_DISCONNECT:
- event.peer->data = NULL;
+
// u_log(LOG_NOTE, "%s:%d disconnected", u_iptostr(event.peer->address.host), event.peer->address.port);
break;
}
} else if (event.peer) {
// u_log(LOG_WARN, "filtered event %d from %s", event.type, u_iptostr(event.peer->address.host));
- event.peer->data = NULL;
+ sv_clear_peer(event.peer);
enet_peer_reset(event.peer);
}
// time out servers
for (int i = 0; i < max_servers; ++i) {
- if (servers[i].host) {
- if (servers[i].death_time <= now) {
- u_log(LOG_NOTE, "server #%d %s:%d timed out", i, u_iptostr(servers[i].host), servers[i].port);
- servers[i].host = 0;
- servers[i].port = 0;
- --num_servers;
- }
+ if (servers[i].host && servers[i].death_time <= now) {
+ u_log(LOG_NOTE, "server #%d %s:%d timed out", i, u_iptostr(servers[i].host), servers[i].port);
+ sv_remove(servers + i);
}
}
- // time out clients
- if (ms_host && ms_host->peers) {
- for (size_t i = 0; i < ms_host->peerCount; ++i) {
- ENetPeer *peer = ms_host->peers + i;
- if ((peer->state >= ENET_PEER_STATE_CONNECTING && peer->state <= ENET_PEER_STATE_DISCONNECT_LATER) && peer->data) {
- const time_t timeout = (time_t)(intptr_t)peer->data;
- if (timeout < now) {
- u_log(LOG_NOTE, "client %s:%d timed out", u_iptostr(peer->address.host), peer->address.port);
- peer->data = NULL;
- enet_peer_reset(peer);
- }
- }
- }
- }
+#ifdef ENABLE_PIPE
+ // read commands from pipe
+ io_read_commands();
+#endif
}
}