X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fmastersrv%2Fmaster.c;h=c68d59c1042f9b090632971b5e696648df28ec3e;hb=f5e6473ba0ddfeba497a6e5d4902833c2aba46ed;hp=1e1bc7b0e1d0d4a901bf6c6d562e8923d8405df6;hpb=24fc48efd28ea931fc2797517dc8b14b19840685;p=d2df-sdl.git diff --git a/src/mastersrv/master.c b/src/mastersrv/master.c index 1e1bc7b..c68d59c 100644 --- a/src/mastersrv/master.c +++ b/src/mastersrv/master.c @@ -1,3 +1,18 @@ +/* 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 . + */ + #include #include #include @@ -7,8 +22,11 @@ #include #include #include +#include +#include +#include +#include -#define ENET_DEBUG 1 #include #include @@ -18,6 +36,7 @@ #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 @@ -92,6 +111,7 @@ typedef struct server_s { char map[MAX_STRLEN + 2]; time_t death_time; time_t timestamp; + ENetPeer *peer; // who sent this server in } server_t; // real servers @@ -145,6 +165,17 @@ static int cl_spam_cnt; /* 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; @@ -242,7 +273,7 @@ static inline enet_uint32 u_prefixtomask(const enet_uint32 prefix) { } 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) { @@ -343,24 +374,32 @@ void b_write_server(enet_buf_t *buf, const server_t *s) { /* 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) { @@ -494,6 +549,30 @@ static void ban_free_list(void) { banlist = NULL; } +static bool ban_record_remove_addr(const enet_uint32 host, const enet_uint32 mask) { + for (ban_record_t *b = banlist; b; b = b->next) { + if ((b->host == host) && (b->mask == mask)) { + if (b == banlist) + banlist = b->next; + if (b->next) b->next->prev = b->prev; + if (b->prev) b->prev->next = b->next; + free(b); + return true; + } + } + return false; +} + +static bool ban_record_remove_ip(const char *ip) { + enet_uint32 mask = 0; + const enet_uint32 host = ban_parse_ip_mask(ip, &mask); + if (!host) { + u_log(LOG_ERROR, "unban: `%s` is not a valid address", ip); + return NULL; + } + return ban_record_remove_addr(host, mask); +} + static void ban_load_list(const char *fname) { FILE *f = fopen(fname, "r"); if (!f) { @@ -512,13 +591,14 @@ static void ban_load_list(const char *fname) { continue; char ip[21] = { 0 }; // optionally includes the "/nn" prefix length at the end - time_t exp = 0; + int expd = 0; int count = 0; - if (sscanf(ln, "%20s %ld %d", ip, &exp, &count) < 3) { + if (sscanf(ln, "%20s %d %d", ip, &expd, &count) < 3) { u_log(LOG_ERROR, "banlist: malformed line: `%s`", ln); continue; } + const time_t exp = (time_t)expd; // 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); } @@ -535,7 +615,7 @@ static void ban_save_list(const char *fname) { 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 %d %d\n", u_iptostr(rec->host), u_masktoprefix(rec->mask), (int)rec->cur_ban, rec->ban_count); } fclose(f); @@ -563,6 +643,25 @@ static bool ban_sanity_check(const server_t *srv) { 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); @@ -582,16 +681,94 @@ static void ban_add(const enet_uint32 host, const char *reason) { cl_last_addr = 0; } +static void ban_remove_mask(const enet_uint32 host, const enet_uint32 mask) { + if (!ban_record_remove_addr(host, mask)) { + u_log(LOG_ERROR, "could not find %s in ban list", u_iptostr(host)); + return; + } + u_log(LOG_NOTE, "unbanned %s", u_iptostr(host)); + ban_save_list(MS_BAN_FILE); +} + 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, "unban ", 6)) { + const char *ip = u_strstrip(cmd + 6); // skip "unban " + 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_remove_mask(host, mask); + } 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(); @@ -600,17 +777,10 @@ static void deinit(void) { 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; @@ -656,6 +826,16 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { // 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 { @@ -689,6 +869,8 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { 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); @@ -701,7 +883,10 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { 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: @@ -749,6 +934,10 @@ static bool handle_msg(const enet_uint8 msgid, ENetPeer *peer) { 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 : ""); return true; @@ -872,8 +1061,8 @@ int main(int argc, char **argv) { atexit(deinit); -#ifdef SIGUSR1 - signal(SIGUSR1, sigusr_handler); +#ifdef ENABLE_PIPE + io_install_pipe(); #endif ENetAddress addr; @@ -891,12 +1080,7 @@ int main(int argc, char **argv) { 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) { @@ -904,8 +1088,8 @@ int main(int argc, char **argv) { 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: @@ -923,14 +1107,11 @@ int main(int argc, char **argv) { 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; @@ -939,7 +1120,7 @@ int main(int argc, char **argv) { } } 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); } @@ -953,29 +1134,15 @@ int main(int argc, char **argv) { // 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 } }