diff --git a/include/s_conf.h b/include/s_conf.h index 762dac1..6573e7b 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -97,6 +97,7 @@ struct ConfItem /* Generic flags... */ #define CONF_FLAGS_TEMPORARY 0x00800000 #define CONF_FLAGS_NEED_SSL 0x00000002 +#define CONF_FLAGS_MYOPER 0x00080000 /* need to rewrite info.oper on burst */ /* auth{} flags... */ #define CONF_FLAGS_NO_TILDE 0x00000004 #define CONF_FLAGS_NEED_IDENTD 0x00000008 @@ -347,6 +348,7 @@ extern void init_s_conf(void); extern struct ConfItem *make_conf(void); extern void free_conf(struct ConfItem *); +extern rb_dlink_node *find_prop_ban(unsigned int status, const char *user, const char *host); extern void deactivate_conf(struct ConfItem *, rb_dlink_node *); extern void read_conf_files(int cold); diff --git a/include/s_serv.h b/include/s_serv.h index c52c78d..73327c7 100644 --- a/include/s_serv.h +++ b/include/s_serv.h @@ -72,12 +72,14 @@ struct Capability #define CAP_SAVE 0x40000 /* supports SAVE (nick collision FNC) */ #define CAP_EUID 0x80000 /* supports EUID (ext UID + nonencap CHGHOST) */ #define CAP_EOPMOD 0x100000 /* supports EOPMOD (ext +z + ext topic) */ +#define CAP_BAN 0x200000 /* supports propagated bans */ #define CAP_MASK (CAP_QS | CAP_EX | CAP_CHW | \ CAP_IE | CAP_KLN | CAP_SERVICE |\ CAP_CLUSTER | CAP_ENCAP | \ CAP_ZIP | CAP_KNOCK | CAP_UNKLN | \ - CAP_RSFNC | CAP_SAVE | CAP_EUID | CAP_EOPMOD) + CAP_RSFNC | CAP_SAVE | CAP_EUID | CAP_EOPMOD | \ + CAP_BAN) #ifdef HAVE_LIBZ #define CAP_ZIP_SUPPORTED CAP_ZIP diff --git a/modules/Makefile.in b/modules/Makefile.in index 6c80e21..ad1aace 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -36,6 +36,7 @@ INCLUDES = -I../include -I../libratbox/include $(SSL_INCLUDES) CPPFLAGS = ${INCLUDES} @CPPFLAGS@ CORE_SRCS = \ + core/m_ban.c \ core/m_die.c \ core/m_error.c \ core/m_join.c \ diff --git a/modules/core/m_ban.c b/modules/core/m_ban.c new file mode 100644 index 0000000..fb19c57 --- /dev/null +++ b/modules/core/m_ban.c @@ -0,0 +1,241 @@ +/* + * charybdis: An advanced ircd. + * m_ban.c: Propagates network bans across servers. + * + * Copyright (C) 2010 Jilles Tjoelker + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1.Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2.Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stdinc.h" +#include "send.h" +#include "client.h" +#include "common.h" +#include "config.h" +#include "ircd.h" +#include "match.h" +#include "s_conf.h" +#include "msg.h" +#include "modules.h" +#include "hash.h" +#include "s_serv.h" +#include "operhash.h" +#include "reject.h" +#include "hostmask.h" + +static int ms_ban(struct Client *client_p, struct Client *source_p, int parc, const char *parv[]); + +struct Message ban_msgtab = { + "BAN", 0, 0, 0, MFLG_SLOW, + {mg_unreg, mg_ignore, {ms_ban, 10}, {ms_ban, 10}, mg_ignore, mg_ignore} +}; + +mapi_clist_av1 ban_clist[] = { &ban_msgtab, NULL }; +DECLARE_MODULE_AV1(ban, NULL, NULL, ban_clist, NULL, NULL, "$Revision: 1349 $"); + +/* ms_ban() + * + * parv[1] - +/- + * parv[2] - type + * parv[3] - username mask or * + * parv[4] - hostname mask + * parv[5] - creation TS + * parv[6] - duration (relative to creation) + * parv[7] - lifetime (relative to creation) + * parv[8] - oper or * + * parv[9] - reason (possibly with |operreason) + */ +static int +ms_ban(struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) +{ + rb_dlink_node *ptr; + struct ConfItem *aconf; + unsigned int ntype; + const char *oper, *stype; + time_t created, hold, lifetime; + char *p; + int act; + + if (strcmp(parv[1], "+") && strcmp(parv[1], "-")) + { + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "Unknown BAN operation %s from %s", + parv[1], source_p->name); + return 0; + } + if (strlen(parv[2]) != 1) + { + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "Unknown BAN type %s from %s", + parv[2], source_p->name); + return 0; + } + switch (parv[2][0]) + { + case 'K': + ntype = CONF_KILL; + stype = "K-Line"; + break; + default: + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "Unknown BAN type %s from %s", + parv[2], source_p->name); + return 0; + } + created = atol(parv[5]); + hold = created + atoi(parv[6]); + lifetime = created + atoi(parv[7]); + if (!strcmp(parv[8], "*")) + oper = IsServer(source_p) ? source_p->name : get_oper_name(source_p); + else + oper = parv[8]; + ptr = find_prop_ban(ntype, parv[3], parv[4]); + if (ptr != NULL) + { + aconf = ptr->data; + if (aconf->created >= created) + { + if (IsPerson(source_p)) + sendto_one_notice(source_p, + ":Your %s [%s%s%s] has been superseded", + stype, + aconf->user ? aconf->user : "", + aconf->user ? "@" : "", + aconf->host); + return 0; + } + act = !(aconf->status & CONF_ILLEGAL) || !strcmp(parv[1], "+"); + if (lifetime > aconf->lifetime) + aconf->lifetime = lifetime; + /* already expired, hmm */ + if (aconf->lifetime <= rb_current_time()) + return 0; + deactivate_conf(aconf, ptr); + rb_free(aconf->user); + aconf->user = NULL; + rb_free(aconf->host); + aconf->host = NULL; + operhash_delete(aconf->info.oper); + aconf->info.oper = NULL; + rb_free(aconf->passwd); + aconf->passwd = NULL; + rb_free(aconf->spasswd); + aconf->spasswd = NULL; + } + else + { + aconf = make_conf(); + aconf->status = CONF_ILLEGAL | ntype; + aconf->lifetime = lifetime; + rb_dlinkAddAlloc(aconf, &prop_bans); + act = !strcmp(parv[1], "+"); + } + aconf->flags &= ~CONF_FLAGS_MYOPER; + aconf->flags |= CONF_FLAGS_TEMPORARY; + aconf->user = ntype == CONF_KILL ? rb_strdup(parv[3]) : NULL; + aconf->host = rb_strdup(parv[4]); + aconf->info.oper = operhash_add(oper); + aconf->created = created; + aconf->hold = hold; + p = strchr(parv[parc - 1], '|'); + if (p == NULL) + aconf->passwd = rb_strdup(parv[parc - 1]); + else + { + aconf->passwd = rb_strndup(parv[parc - 1], p - parv[parc - 1] + 1); + aconf->spasswd = rb_strdup(p + 1); + } + if (!strcmp(parv[1], "+")) + { + /* Keep the notices in sync with modules/m_kline.c etc. */ + sendto_realops_snomask(SNO_GENERAL, L_ALL, + "%s added global %d min. %s%s%s for [%s%s%s] [%s]", + IsServer(source_p) ? source_p->name : get_oper_name(source_p), + (hold - rb_current_time()) / 60, + stype, + strcmp(parv[8], "*") ? " from " : "", + strcmp(parv[8], "*") ? parv[8] : "", + aconf->user ? aconf->user : "", + aconf->user ? "@" : "", + aconf->host, + parv[parc - 1]); + aconf->status &= ~CONF_ILLEGAL; + ilog(L_KLINE, "%s %s %d %s %s %s", parv[2], + IsServer(source_p) ? source_p->name : get_oper_name(source_p), + (hold - rb_current_time()) / 60, + aconf->user, aconf->host, + parv[parc - 1]); + } + else if (act) + { + sendto_realops_snomask(SNO_GENERAL, L_ALL, + "%s has removed the global %s for: [%s%s%s]%s%s", + IsServer(source_p) ? source_p->name : get_oper_name(source_p), + stype, + aconf->user ? aconf->user : "", + aconf->user ? "@" : "", + aconf->host, + strcmp(parv[8], "*") ? " on behalf of " : "", + strcmp(parv[8], "*") ? parv[8] : ""); + ilog(L_KLINE, "U%s %s %s %s", parv[2], + IsServer(source_p) ? source_p->name : get_oper_name(source_p), + aconf->user, aconf->host); + } + switch (ntype) + { + case CONF_KILL: + if (aconf->status & CONF_ILLEGAL) + remove_reject_mask(aconf->user, aconf->host); + else + { + add_conf_by_address(aconf->host, CONF_KILL, aconf->user, NULL, aconf); + if(ConfigFileEntry.kline_delay || + (IsServer(source_p) && + !HasSentEob(source_p))) + { + if(kline_queued == 0) + { + rb_event_addonce("check_klines", check_klines_event, NULL, + ConfigFileEntry.kline_delay); + kline_queued = 1; + } + } + else + check_klines(); + } + break; + } + sendto_server(NULL, NULL, CAP_BAN|CAP_TS6, NOCAPS, + ":%s BAN %s %s %s %s %s %s %s %s :%s", + source_p->id, + parv[1], + parv[2], + parv[3], + parv[4], + parv[5], + parv[6], + parv[7], + parv[8], + parv[parc - 1]); + return 0; +} diff --git a/modules/m_kline.c b/modules/m_kline.c index 2056e4d..4ba0561 100644 --- a/modules/m_kline.c +++ b/modules/m_kline.c @@ -79,11 +79,14 @@ static void apply_kline(struct Client *source_p, struct ConfItem *aconf, const char *reason, const char *oper_reason); static void apply_tkline(struct Client *source_p, struct ConfItem *aconf, const char *, const char *, int); +static void apply_prop_kline(struct Client *source_p, struct ConfItem *aconf, + const char *, const char *, int); static int already_placed_kline(struct Client *, const char *, const char *, int); static void handle_remote_unkline(struct Client *source_p, const char *user, const char *host); static void remove_permkline_match(struct Client *, struct ConfItem *); static int remove_temp_kline(struct Client *, struct ConfItem *); +static void remove_prop_kline(struct Client *, struct ConfItem *); /* mo_kline() * @@ -105,6 +108,7 @@ mo_kline(struct Client *client_p, struct Client *source_p, int parc, const char struct ConfItem *aconf; int tkline_time = 0; int loc = 1; + int propagated = 1; if(!IsOperK(source_p)) { @@ -153,9 +157,12 @@ mo_kline(struct Client *client_p, struct Client *source_p, int parc, const char /* If we are sending it somewhere that doesnt include us, stop */ if(!match(target_server, me.name)) return 0; + + /* Set as local-only. */ + propagated = 0; } /* if we have cluster servers, send it to them.. */ - else if(rb_dlink_list_length(&cluster_conf_list) > 0) + else if(!propagated && rb_dlink_list_length(&cluster_conf_list) > 0) cluster_generic(source_p, "KLINE", (tkline_time > 0) ? SHARED_TKLINE : SHARED_PKLINE, CAP_KLN, "%lu %s %s :%s", tkline_time, user, host, reason); @@ -164,6 +171,12 @@ mo_kline(struct Client *client_p, struct Client *source_p, int parc, const char !valid_wild_card(source_p, user, host) || !valid_comment(source_p, reason)) return 0; + if(propagated && tkline_time == 0) + { + sendto_one_notice(source_p, ":Cannot set a permanent global ban"); + return 0; + } + if(already_placed_kline(source_p, user, host, tkline_time)) return 0; @@ -187,7 +200,9 @@ mo_kline(struct Client *client_p, struct Client *source_p, int parc, const char } aconf->passwd = rb_strdup(reason); - if(tkline_time > 0) + if(propagated) + apply_prop_kline(source_p, aconf, reason, oper_reason, tkline_time); + else if(tkline_time > 0) apply_tkline(source_p, aconf, reason, oper_reason, tkline_time); else apply_kline(source_p, aconf, reason, oper_reason); @@ -324,6 +339,7 @@ mo_unkline(struct Client *client_p, struct Client *source_p, int parc, const cha char splat[] = "*"; char *h = LOCAL_COPY(parv[1]); struct ConfItem *aconf; + int propagated = 1; if(!IsOperUnkline(source_p)) { @@ -375,17 +391,32 @@ mo_unkline(struct Client *client_p, struct Client *source_p, int parc, const cha if(match(parv[3], me.name) == 0) return 0; + + propagated = 0; } - else if(rb_dlink_list_length(&cluster_conf_list) > 0) + + aconf = find_exact_conf_by_address(host, CONF_KILL, user); + + /* No clustering for removing a propagated kline */ + if(propagated && (aconf == NULL || !aconf->lifetime) && + rb_dlink_list_length(&cluster_conf_list) > 0) cluster_generic(source_p, "UNKLINE", SHARED_UNKLINE, CAP_UNKLN, "%s %s", user, host); - aconf = find_exact_conf_by_address(host, CONF_KILL, user); if(aconf == NULL) { sendto_one_notice(source_p, ":No K-Line for %s@%s", user, host); return 0; } + + if(aconf->lifetime) + { + if(propagated) + remove_prop_kline(source_p, aconf); + else + sendto_one_notice(source_p, ":Cannot remove global K-Line %s@%s on specific servers", user, host); + return 0; + } if(remove_temp_kline(source_p, aconf)) return 0; @@ -444,6 +475,11 @@ handle_remote_unkline(struct Client *source_p, const char *user, const char *hos sendto_one_notice(source_p, ":No K-Line for %s@%s", user, host); return; } + if(aconf->lifetime) + { + sendto_one_notice(source_p, ":Cannot remove global K-Line %s@%s on specific servers", user, host); + return; + } if(remove_temp_kline(source_p, aconf)) return; @@ -527,6 +563,70 @@ apply_tkline(struct Client *source_p, struct ConfItem *aconf, tkline_time / 60, aconf->user, aconf->host); } +static void +apply_prop_kline(struct Client *source_p, struct ConfItem *aconf, + const char *reason, const char *oper_reason, int tkline_time) +{ + rb_dlink_node *ptr; + struct ConfItem *oldconf; + + aconf->flags |= CONF_FLAGS_MYOPER | CONF_FLAGS_TEMPORARY; + aconf->hold = rb_current_time() + tkline_time; + aconf->lifetime = aconf->hold; + + ptr = find_prop_ban(aconf->status, aconf->user, aconf->host); + if(ptr != NULL) + { + oldconf = ptr->data; + /* Remember at least as long as the old one. */ + if(oldconf->lifetime > aconf->lifetime) + aconf->lifetime = oldconf->lifetime; + /* Force creation time to increase. */ + if(oldconf->created >= aconf->created) + aconf->created = oldconf->created + 1; + /* Tell deactivate_conf() to destroy it. */ + oldconf->lifetime = rb_current_time(); + deactivate_conf(oldconf, ptr); + } + + rb_dlinkAddAlloc(aconf, &prop_bans); + add_conf_by_address(aconf->host, CONF_KILL, aconf->user, NULL, aconf); + + /* no oper reason.. */ + if(EmptyString(oper_reason)) + { + sendto_realops_snomask(SNO_GENERAL, L_ALL, + "%s added global %d min. K-Line for [%s@%s] [%s]", + get_oper_name(source_p), tkline_time / 60, + aconf->user, aconf->host, reason); + ilog(L_KLINE, "K %s %d %s %s %s", + get_oper_name(source_p), tkline_time / 60, aconf->user, aconf->host, reason); + } + else + { + sendto_realops_snomask(SNO_GENERAL, L_ALL, + "%s added global %d min. K-Line for [%s@%s] [%s|%s]", + get_oper_name(source_p), tkline_time / 60, + aconf->user, aconf->host, reason, oper_reason); + ilog(L_KLINE, "K %s %d %s %s %s|%s", + get_oper_name(source_p), tkline_time / 60, + aconf->user, aconf->host, reason, oper_reason); + } + + sendto_one_notice(source_p, ":Added global %d min. K-Line [%s@%s]", + tkline_time / 60, aconf->user, aconf->host); + + sendto_server(NULL, NULL, CAP_BAN|CAP_TS6, NOCAPS, + ":%s BAN + K %s %s %lu %d %d * :%s%s%s", + source_p->id, aconf->user, aconf->host, + (unsigned long)aconf->created, + (int)(aconf->hold - aconf->created), + (int)(aconf->lifetime - aconf->created), + reason, + oper_reason ? "|" : "", + oper_reason ? oper_reason : ""); +} + /* find_user_host() * * inputs - client placing kline, user@host, user buffer, host buffer @@ -799,3 +899,38 @@ remove_temp_kline(struct Client *source_p, struct ConfItem *aconf) return NO; } + +static void +remove_prop_kline(struct Client *source_p, struct ConfItem *aconf) +{ + rb_dlink_node *ptr; + + ptr = rb_dlinkFind(aconf, &prop_bans); + if (!ptr) + return; + sendto_one_notice(source_p, + ":Un-klined [%s@%s] from global k-lines", + aconf->user, aconf->host); + sendto_realops_snomask(SNO_GENERAL, L_ALL, + "%s has removed the global K-Line for: [%s@%s]", + get_oper_name(source_p), aconf->user, + aconf->host); + + ilog(L_KLINE, "UK %s %s %s", + get_oper_name(source_p), aconf->user, aconf->host); + if(aconf->created < rb_current_time()) + aconf->created = rb_current_time(); + else + aconf->created++; + operhash_delete(aconf->info.oper); + aconf->info.oper = operhash_add(get_oper_name(source_p)); + aconf->flags |= CONF_FLAGS_MYOPER | CONF_FLAGS_TEMPORARY; + sendto_server(NULL, NULL, CAP_BAN|CAP_TS6, NOCAPS, + ":%s BAN - K %s %s %lu %d %d * :*", + source_p->id, aconf->user, aconf->host, + (unsigned long)aconf->created, + 0, + (int)(aconf->lifetime - aconf->created)); + remove_reject_mask(aconf->user, aconf->host); + deactivate_conf(aconf, ptr); +} diff --git a/src/modules.c b/src/modules.c index 43a2f51..25d8cc7 100644 --- a/src/modules.c +++ b/src/modules.c @@ -58,6 +58,7 @@ struct module **modlist = NULL; static const char *core_module_table[] = { + "m_ban", "m_die", "m_error", "m_join", diff --git a/src/s_conf.c b/src/s_conf.c index 6ccdeb4..55a9728 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -1007,6 +1007,25 @@ add_temp_dline(struct ConfItem *aconf) add_conf_by_address(aconf->host, CONF_DLINE, aconf->user, NULL, aconf); } +rb_dlink_node * +find_prop_ban(unsigned int status, const char *user, const char *host) +{ + rb_dlink_node *ptr; + struct ConfItem *aconf; + + RB_DLINK_FOREACH(ptr, prop_bans.head) + { + aconf = ptr->data; + + if((aconf->status & ~CONF_ILLEGAL) == status && + (!user || !aconf->user || + !irccmp(aconf->user, user)) && + !irccmp(aconf->host, host)) + return ptr; + } + return NULL; +} + void deactivate_conf(struct ConfItem *aconf, rb_dlink_node *ptr) { diff --git a/src/s_serv.c b/src/s_serv.c index 315e18a..e71b1f3 100644 --- a/src/s_serv.c +++ b/src/s_serv.c @@ -89,6 +89,7 @@ struct Capability captab[] = { { "SAVE", CAP_SAVE }, { "EUID", CAP_EUID }, { "EOPMOD", CAP_EOPMOD }, + { "BAN", CAP_BAN }, {0, 0} }; @@ -392,6 +393,67 @@ send_capabilities(struct Client *client_p, int cap_can_send) sendto_one(client_p, "CAPAB :%s", msgbuf); } +static void +burst_ban(struct Client *client_p) +{ + rb_dlink_node *ptr; + struct ConfItem *aconf; + const char *type, *oper; + /* +5 for !,@,{,} and null */ + char operbuf[NICKLEN + USERLEN + HOSTLEN + HOSTLEN + 5]; + char *p; + size_t melen; + + melen = strlen(me.name); + RB_DLINK_FOREACH(ptr, prop_bans.head) + { + aconf = ptr->data; + + /* Skip expired stuff. */ + if(aconf->lifetime < rb_current_time()) + continue; + switch(aconf->status & ~CONF_ILLEGAL) + { + case CONF_KILL: type = "K"; break; + case CONF_DLINE: type = "D"; break; + case CONF_XLINE: type = "X"; break; + case CONF_RESV_NICK: type = "R"; break; + case CONF_RESV_CHANNEL: type = "R"; break; + default: + continue; + } + oper = aconf->info.oper; + if(aconf->flags & CONF_FLAGS_MYOPER) + { + /* Our operator{} names may not be meaningful + * to other servers, so rewrite to our server + * name. + */ + rb_strlcpy(operbuf, aconf->info.oper, sizeof buf); + p = strrchr(operbuf, '{'); + if (operbuf + sizeof operbuf - p > (ptrdiff_t)(melen + 2)) + { + memcpy(p + 1, me.name, melen); + p[melen + 1] = '}'; + p[melen + 2] = '\0'; + oper = operbuf; + } + } + sendto_one(client_p, ":%s BAN %c %s %s %s %lu %d %d %s :%s%s%s", + me.id, + aconf->status & CONF_ILLEGAL ? '-' : '+', + type, + aconf->user ? aconf->user : "*", aconf->host, + (unsigned long)aconf->created, + (int)(aconf->hold - aconf->created), + (int)(aconf->lifetime - aconf->created), + oper, + aconf->passwd, + aconf->spasswd ? "|" : "", + aconf->spasswd ? aconf->spasswd : ""); + } +} + /* burst_modes_TS6() * * input - client to burst to, channel name, list to burst, mode flag @@ -881,6 +943,9 @@ server_estab(struct Client *client_p) target_p->serv->fullcaps); } + if(IsCapable(client_p, CAP_BAN)) + burst_ban(client_p); + burst_TS6(client_p); /* Always send a PING after connect burst is done */