/* * ircd-ratbox: A slightly useful ircd. * m_gungline.c: Votes towards removing a gline. * * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center * Copyright (C) 1996-2002 Hybrid Development Team * Copyright (C) 2002-2012 ircd-ratbox development team * * 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; either version 2 of the License, or * (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA * * $Id: m_gline.c 26421 2009-01-18 17:38:16Z jilles $ */ #include "stdinc.h" #include "struct.h" #include "s_gline.h" #include "client.h" #include "match.h" #include "ircd.h" #include "hostmask.h" #include "numeric.h" #include "s_conf.h" #include "s_newconf.h" #include "scache.h" #include "send.h" #include "s_serv.h" #include "hash.h" #include "parse.h" #include "modules.h" #include "s_log.h" #include "hook.h" static int mo_gungline(struct Client *, struct Client *, int, const char **); static int me_gungline(struct Client *, struct Client *, int, const char **); static void h_gungline_stats(hook_data_int *); static int modinit(void); static void moddeinit(void); struct Message gungline_msgtab = { "GUNGLINE", 0, 0, 0, MFLG_SLOW, {mg_unreg, mg_not_oper, mg_ignore, mg_ignore, {me_gungline, 4}, {mo_gungline, 3}} }; mapi_clist_av1 gungline_clist[] = { &gungline_msgtab, NULL }; mapi_hfn_list_av1 gungline_hfnlist[] = { {"doing_stats", (hookfn) h_gungline_stats}, {NULL, NULL} }; DECLARE_MODULE_AV1(gungline, modinit, moddeinit, gungline_clist, NULL, gungline_hfnlist, "$Revision: 26421 $"); static int majority_ungline(struct Client *source_p, const char *user, const char *host, const char *reason); static int invalid_gline(struct Client *, const char *, char *); static int remove_temp_gline(const char *, const char *); static void expire_pending_gunglines(void *unused); static struct ev_entry *pending_gungline_ev; static void flush_pending_gunglines(void); static rb_dlink_list pending_gunglines; static int modinit(void) { pending_gungline_ev = rb_event_addish("expire_pending_gunglines", expire_pending_gunglines, NULL, CLEANUP_GLINES_TIME); return 0; } static void moddeinit(void) { rb_event_delete(pending_gungline_ev); if (rb_dlink_list_length(&pending_gunglines) > 0) sendto_realops_flags(UMODE_ALL, L_ALL, "Discarding pending gunglines because of module unload"); flush_pending_gunglines(); } /* mo_gungline() * * inputs - The usual for a m_ function * output - * side effects - remove a gline if 3 opers agree */ static int mo_gungline(struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) { const char *user = NULL; char *host = NULL; char *reason = NULL; char splat[] = "*"; if(!ConfigFileEntry.glines) { sendto_one_notice(source_p, ":GUNGLINE disabled"); return 0; } if(!IsOperUnkline(source_p) || !IsOperGline(source_p)) { sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "ungline"); return 0; } host = strchr(parv[1], '@'); /* specific user@host */ if(host != NULL) { user = parv[1]; *(host++) = '\0'; /* gline for "@host", use *@host */ if(*user == '\0') user = splat; } /* just a host? */ else { /* ok, its not a host.. abort */ if(strchr(parv[1], '.') == NULL) { sendto_one_notice(source_p, ":Invalid parameters"); return 0; } user = splat; host = LOCAL_COPY(parv[1]); } reason = LOCAL_COPY(parv[2]); if(invalid_gline(source_p, user, reason)) return 0; /* inform users about the gline before we call majority_ungline() * so already voted comes below gline request --fl */ sendto_realops_flags(UMODE_ALL, L_ALL, "%s!%s@%s on %s is requesting ungline for [%s@%s] [%s]", source_p->name, source_p->username, source_p->host, me.name, user, host, reason); ilog(L_GLINE, "RU %s %s %s %s %s %s %s", source_p->name, source_p->username, source_p->host, source_p->servptr->name, user, host, reason); /* If at least 3 opers agree this user should be G lined then do it */ majority_ungline(source_p, user, host, reason); sendto_server(client_p, NULL, CAP_ENCAP | CAP_TS6, NOCAPS, ":%s ENCAP * GUNGLINE %s %s :%s", use_id(source_p), user, host, reason); sendto_server(client_p, NULL, CAP_ENCAP, CAP_TS6, ":%s ENCAP * GUNGLINE %s %s :%s", source_p->name, user, host, reason); return 0; } /* mc_gungline() */ static int me_gungline(struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) { struct Client *acptr; const char *user; const char *host; char *reason; if (!IsClient(source_p)) return 0; acptr = source_p; user = parv[1]; host = parv[2]; reason = LOCAL_COPY(parv[3]); if(invalid_gline(acptr, user, reason)) return 0; if(!ConfigFileEntry.glines) return 0; sendto_realops_flags(UMODE_ALL, L_ALL, "%s!%s@%s on %s is requesting ungline for [%s@%s] [%s]", acptr->name, acptr->username, acptr->host, acptr->servptr->name, user, host, reason); ilog(L_GLINE, "RU %s %s %s %s %s %s %s", source_p->name, source_p->username, source_p->host, source_p->servptr->name, user, host, reason); /* If at least 3 opers agree this user should be G lined then do it */ majority_ungline(acptr, user, host, reason); return 0; } /* invalid_gline * * inputs - pointer to source client, ident, host and reason * outputs - 1 if invalid, 0 if valid * side effects - */ static int invalid_gline(struct Client *source_p, const char *luser, char *lreason) { if(strchr(luser, '!')) { sendto_one_notice(source_p, ":Invalid character '!' in gline"); return 1; } if(strlen(lreason) > REASONLEN) lreason[REASONLEN] = '\0'; return 0; } /* * remove_local_gline * * inputs - pointer to oper nick/username/host/server, * victim user/host and reason * output - NONE * side effects - */ static void remove_local_gline(struct Client *source_p, const char *user, const char *host, const char *reason) { if (!remove_temp_gline(user, host)) return; sendto_realops_flags(UMODE_ALL, L_ALL, "%s!%s@%s on %s has triggered ungline for [%s@%s] [%s]", source_p->name, source_p->username, source_p->host, source_p->servptr->name, user, host, reason); ilog(L_GLINE, "TU %s %s %s %s %s %s %s", source_p->name, source_p->username, source_p->host, source_p->servptr->name, user, host, reason); } /* majority_ungline() * * input - client doing gline, user, host and reason of gline * output - YES if there are 3 different opers/servers agree, else NO * side effects - */ static int majority_ungline(struct Client *source_p, const char *user, const char *host, const char *reason) { rb_dlink_node *pending_node; struct gline_pending *pending; /* to avoid desync.. --fl */ expire_pending_gunglines(NULL); RB_DLINK_FOREACH(pending_node, pending_gunglines.head) { pending = pending_node->data; if((irccmp(pending->user, user) == 0) && (irccmp(pending->host, host) == 0)) { /* check oper or server hasnt already voted */ if(((irccmp(pending->oper_user1, source_p->username) == 0) && (irccmp(pending->oper_host1, source_p->host) == 0))) { sendto_realops_flags(UMODE_ALL, L_ALL, "oper has already voted"); return NO; } else if(irccmp(pending->oper_server1, source_p->servptr->name) == 0) { sendto_realops_flags(UMODE_ALL, L_ALL, "server has already voted"); return NO; } if(pending->oper_user2[0] != '\0') { /* if two other opers on two different servers have voted yes */ if(((irccmp(pending->oper_user2, source_p->username) == 0) && (irccmp(pending->oper_host2, source_p->host) == 0))) { sendto_realops_flags(UMODE_ALL, L_ALL, "oper has already voted"); return NO; } else if(irccmp(pending->oper_server2, source_p->servptr->name) == 0) { sendto_realops_flags(UMODE_ALL, L_ALL, "server has already voted"); return NO; } /* trigger the gline using the original reason --fl */ remove_local_gline(source_p, user, host, pending->reason1); expire_pending_gunglines(pending); return YES; } else { rb_strlcpy(pending->oper_nick2, source_p->name, sizeof(pending->oper_nick2)); rb_strlcpy(pending->oper_user2, source_p->username, sizeof(pending->oper_user2)); rb_strlcpy(pending->oper_host2, source_p->host, sizeof(pending->oper_host2)); pending->reason2 = rb_strdup(reason); pending->oper_server2 = scache_add(source_p->servptr->name); pending->last_gline_time = rb_current_time(); pending->time_request2 = rb_current_time(); return NO; } } } /* no pending ungline, create a new one */ pending = (struct gline_pending *)rb_malloc(sizeof(struct gline_pending)); rb_strlcpy(pending->oper_nick1, source_p->name, sizeof(pending->oper_nick1)); rb_strlcpy(pending->oper_user1, source_p->username, sizeof(pending->oper_user1)); rb_strlcpy(pending->oper_host1, source_p->host, sizeof(pending->oper_host1)); pending->oper_server1 = scache_add(source_p->servptr->name); rb_strlcpy(pending->user, user, sizeof(pending->user)); rb_strlcpy(pending->host, host, sizeof(pending->host)); pending->reason1 = rb_strdup(reason); pending->reason2 = NULL; pending->last_gline_time = rb_current_time(); pending->time_request1 = rb_current_time(); rb_dlinkAddAlloc(pending, &pending_gunglines); return NO; } /* remove_temp_gline() * * inputs - username, hostname to ungline * outputs - * side effects - tries to ungline anything that matches */ static int remove_temp_gline(const char *user, const char *host) { struct ConfItem *aconf; rb_dlink_node *ptr; struct rb_sockaddr_storage addr, caddr; int bits, cbits; int mtype, gtype; mtype = parse_netmask(host, (struct sockaddr *)&addr, &bits); RB_DLINK_FOREACH(ptr, glines.head) { aconf = ptr->data; gtype = parse_netmask(aconf->host, (struct sockaddr *)&caddr, &cbits); if(gtype != mtype || (user && irccmp(user, aconf->user))) continue; if(gtype == HM_HOST) { if(irccmp(aconf->host, host)) continue; } else if(bits != cbits || !comp_with_mask_sock((struct sockaddr *)&addr, (struct sockaddr *)&caddr, bits)) continue; rb_dlinkDestroy(ptr, &glines); delete_one_address_conf(aconf->host, aconf); return YES; } return NO; } static void h_gungline_stats(hook_data_int * data) { char statchar = (char)data->arg2; if(ConfigFileEntry.glines && statchar == 'g' && IsOper(data->client)) { rb_dlink_node *pending_node; struct gline_pending *glp_ptr; char timebuffer[MAX_DATE_STRING]; struct tm *tmptr; RB_DLINK_FOREACH(pending_node, pending_gunglines.head) { glp_ptr = pending_node->data; tmptr = gmtime(&glp_ptr->time_request1); strftime(timebuffer, MAX_DATE_STRING, "%Y/%m/%d %H:%M:%S", tmptr); sendto_one_notice(data->client, ":1) %s!%s@%s on %s requested ungline at %s for %s@%s [%s]", glp_ptr->oper_nick1, glp_ptr->oper_user1, glp_ptr->oper_host1, glp_ptr->oper_server1, timebuffer, glp_ptr->user, glp_ptr->host, glp_ptr->reason1); if(glp_ptr->oper_nick2[0]) { tmptr = gmtime(&glp_ptr->time_request2); strftime(timebuffer, MAX_DATE_STRING, "%Y/%m/%d %H:%M:%S", tmptr); sendto_one_notice(data->client, ":2) %s!%s@%s on %s requested ungline at %s for %s@%s [%s]", glp_ptr->oper_nick2, glp_ptr->oper_user2, glp_ptr->oper_host2, glp_ptr->oper_server2, timebuffer, glp_ptr->user, glp_ptr->host, glp_ptr->reason2); } } if(rb_dlink_list_length(&pending_gunglines) > 0) sendto_one_notice(data->client, ":End of Pending G-line Removals"); } } /* * expire_pending_gunglines * * inputs - NONE * output - NONE * side effects - * * Go through the pending gungline list, expire any that haven't had * enough "votes" in the time period allowed */ static void expire_pending_gunglines(void *vptr) { rb_dlink_node *pending_node; rb_dlink_node *next_node; struct gline_pending *glp_ptr; RB_DLINK_FOREACH_SAFE(pending_node, next_node, pending_gunglines.head) { glp_ptr = pending_node->data; if((glp_ptr->last_gline_time + GLINE_PENDING_EXPIRE) <= rb_current_time() || vptr == glp_ptr) { rb_free(glp_ptr->reason1); rb_free(glp_ptr->reason2); rb_free(glp_ptr); rb_dlinkDestroy(pending_node, &pending_gunglines); } } } static void flush_pending_gunglines(void) { rb_dlink_node *pending_node; rb_dlink_node *next_node; struct gline_pending *glp_ptr; RB_DLINK_FOREACH_SAFE(pending_node, next_node, pending_gunglines.head) { glp_ptr = pending_node->data; rb_free(glp_ptr->reason1); rb_free(glp_ptr->reason2); rb_free(glp_ptr); rb_dlinkDestroy(pending_node, &pending_gunglines); } }