/* * Solanum: a slightly advanced ircd * * Copyright (C) 2006 charybdis development team * All rights reserved */ #include "stdinc.h" #include "modules.h" #include "hook.h" #include "client.h" #include "ircd.h" #include "send.h" #include "numeric.h" #include "hostmask.h" #include "s_conf.h" #include "s_newconf.h" #include "hash.h" #include "messages.h" #include "s_assert.h" /* {{{ Structures */ #define HURT_CUTOFF (10) /* protocol messages. */ #define HURT_DEFAULT_EXPIRE (7 * 24 * 60) /* minutes. */ #define HURT_EXIT_REASON "Hurt: Failed to identify to services" enum { HEAL_NICK = 0, HEAL_IP }; typedef struct _hurt_state { time_t start_time; uint32_t n_hurts; rb_dlink_list hurt_clients; uint16_t cutoff; time_t default_expire; const char *exit_reason; } hurt_state_t; typedef struct _hurt { char *ip; struct sockaddr *saddr; int saddr_bits; char *reason; time_t expire; } hurt_t; /* }}} */ /* {{{ Prototypes */ static void mo_hurt(struct MsgBuf *msgbuf_p, struct Client *, struct Client *, int, const char **); static void me_hurt(struct MsgBuf *msgbuf_p, struct Client *, struct Client *, int, const char **); static void mo_heal(struct MsgBuf *msgbuf_p, struct Client *, struct Client *, int, const char **); static void me_heal(struct MsgBuf *msgbuf_p, struct Client *, struct Client *, int, const char **); static int modinit(void); static void modfini(void); static void client_exit_hook(void *); static void new_local_user_hook(void *); static void doing_stats_hook(void *); static void hurt_check_event(void *); static void hurt_expire_event(void *); static hurt_t *hurt_new(time_t, const char *, const char *); static void hurt_add(hurt_t *); static void hurt_propagate(struct Client *, struct Client *, hurt_t *); static hurt_t *hurt_find(const char *ip); static hurt_t *hurt_find_exact(const char *ip); static void hurt_remove(const char *ip); static void hurt_destroy(void *hurt); static void heal_nick(struct Client *, struct Client *); /* }}} */ /* {{{ State containers */ rb_dlink_list hurt_confs = { NULL, NULL, 0 }; /* }}} */ /* {{{ Messages */ struct Message hurt_msgtab = { "HURT", 0, 0, 0, 0, { mg_ignore, mg_ignore, mg_ignore, mg_ignore, {me_hurt, 0}, {mo_hurt, 3} } }; struct Message heal_msgtab = { "HEAL", 0, 0, 0, 0, { mg_ignore, mg_ignore, mg_ignore, mg_ignore, {me_heal, 0}, {mo_heal, 2} } }; /* }}} */ /* {{{ Misc module stuff */ mapi_hfn_list_av1 hurt_hfnlist[] = { {"client_exit", client_exit_hook}, {"new_local_user", new_local_user_hook}, {"doing_stats", doing_stats_hook}, {NULL, NULL}, }; mapi_clist_av1 hurt_clist[] = { &hurt_msgtab, &heal_msgtab, NULL }; static const char hurt_desc[] = "Prevents \"hurt\" users from messaging anyone but operators or " "services until they identify or are \"healed\""; DECLARE_MODULE_AV2( hurt, modinit, modfini, hurt_clist, NULL, hurt_hfnlist, NULL, NULL, hurt_desc ); /* }}} */ hurt_state_t hurt_state = { .cutoff = HURT_CUTOFF, .default_expire = HURT_DEFAULT_EXPIRE, .exit_reason = HURT_EXIT_REASON, }; /* * Module constructor/destructor. */ /* {{{ static int modinit() */ struct ev_entry *hurt_expire_ev = NULL; struct ev_entry *hurt_check_ev = NULL; static int modinit(void) { /* set-up hurt_state. */ hurt_state.start_time = rb_current_time(); /* add our event handlers. */ hurt_expire_ev = rb_event_add("hurt_expire", hurt_expire_event, NULL, 60); hurt_check_ev = rb_event_add("hurt_check", hurt_check_event, NULL, 5); return 0; } /* }}} */ /* {{{ static void modfini() */ static void modfini(void) { rb_dlink_node *ptr, *next_ptr; /* and delete our events. */ rb_event_delete(hurt_expire_ev); rb_event_delete(hurt_check_ev); RB_DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_state.hurt_clients.head) { rb_dlinkDestroy(ptr, &hurt_state.hurt_clients); } } /* }}} */ /* * Message handlers. */ /* {{{ static void mo_hurt() * * HURT [] * * parv[1] - expire or ip * parv[2] - ip or reason * parv[3] - reason or NULL */ static void mo_hurt(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char **parv) { const char *ip, *expire, *reason; int expire_time; hurt_t *hurt; struct Client *target_p; if (!IsOperK(source_p)) { sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "kline"); return; } if (parc == 3) { expire = NULL; ip = parv[1]; reason = parv[2]; } else { expire = parv[1]; ip = parv[2]; reason = parv[3]; } if (!expire) expire_time = HURT_DEFAULT_EXPIRE; if (expire && (expire_time = valid_temp_time(expire)) < 1) { sendto_one_notice(source_p, ":Permanent HURTs are not supported"); return; } if (EmptyString(reason)) { sendto_one_notice(source_p, ":Empty HURT reasons are bad for business"); return; } /* Is this a client? */ if (strchr(ip, '.') == NULL && strchr(ip, ':') == NULL) { target_p = find_named_person(ip); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), ip); return; } ip = target_p->orighost; } else { if (!strncmp(ip, "*@", 2)) ip += 2; if (strchr(ip, '!') || strchr(ip, '@')) { sendto_one_notice(source_p, ":Invalid HURT mask [%s]", ip); return; } } if (hurt_find(ip) != NULL) { sendto_one(source_p, ":[%s] already HURT", ip); return; } /* * okay, we've got this far, now it's time to add the the HURT locally * and propagate it to other servers on the network. */ sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s added HURT on [%s] for %ld minutes with reason [%s]", get_oper_name(source_p), ip, (long) expire_time / 60, reason); hurt = hurt_new(expire_time, ip, reason); hurt_add(hurt); hurt_propagate(NULL, source_p, hurt); } /* }}} */ /* {{{ static void me_hurt() * * [ENCAP mask] HURT * * parv[1] - expire * parv[2] - ip * parv[3] - reason */ static void me_hurt(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char **parv) { time_t expire_time; hurt_t *hurt; /* * right... if we don't get enough arguments, or if we get any invalid * arguments, just ignore this request - shit happens, and it's not worth * dropping a server over. */ if (parc < 4 || !IsPerson(source_p)) return; if ((expire_time = atoi(parv[1])) < 1) return; if (hurt_find(parv[2]) != NULL) return; if (EmptyString(parv[3])) return; sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s added HURT on [%s] for %ld minutes with reason [%s]", get_oper_name(source_p), parv[2], (long) expire_time / 60, parv[3]); hurt = hurt_new(expire_time, parv[2], parv[3]); hurt_add(hurt); } /* }}} */ /* {{{ static void mo_heal() * * HURT | * * parv[1] - nick or ip */ static void mo_heal(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char **parv) { struct Client *target_p; if (!IsOperUnkline(source_p)) { sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "unkline"); return; } if (clean_nick(parv[1], 0)) { target_p = find_named_person(parv[1]); if (target_p == NULL) { sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), parv[1]); return; } if (MyConnect(target_p)) heal_nick(source_p, target_p); else sendto_one(target_p, ":%s ENCAP %s HEAL %s", get_id(source_p, target_p), target_p->servptr->name, get_id(target_p, target_p)); } else if (strchr(parv[1], '.')) { if (hurt_find_exact(parv[1]) == NULL) { sendto_one_notice(source_p, ":Mask [%s] is not HURT", parv[1]); return; } hurt_remove(parv[1]); sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s removed HURT on %s", get_oper_name(source_p), parv[1]); sendto_server(NULL, NULL, NOCAPS, NOCAPS, ":%s ENCAP * HEAL %s", source_p->name, parv[1]); } else { sendto_one(source_p, ":[%s] is not a valid IP address/nick", parv[1]); return; } } /* }}} */ static void me_heal(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char **parv) { struct Client *target_p; /* as noted in me_hurt(), if we don't get sufficient arguments... * *poof*, it's dropped... */ if (parc < 2) return; if (clean_nick(parv[1], 0)) { target_p = find_person(parv[1]); if (target_p != NULL && MyConnect(target_p)) heal_nick(source_p, target_p); } else if (strchr(parv[1], '.')) /* host or mask to remove ban for */ { if (hurt_find_exact(parv[1]) == NULL) return; hurt_remove(parv[1]); sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s removed HURT on %s", get_oper_name(source_p), parv[1]); } } /* * Event handlers. */ /* {{{ static void hurt_check_event() */ static void hurt_check_event(void *arg) { rb_dlink_node *ptr, *next_ptr; struct Client *client_p; RB_DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_state.hurt_clients.head) { client_p = ptr->data; if (!EmptyString(client_p->user->suser)) { rb_dlinkDestroy(ptr, &hurt_state.hurt_clients); sendto_one_notice(client_p, ":HURT restriction removed for this session"); client_p->localClient->target_last = rb_current_time(); /* don't ask --nenolod */ } else if (client_p->localClient->receiveM > hurt_state.cutoff) exit_client(NULL, client_p, &me, hurt_state.exit_reason); } } /* }}} */ /* {{{ static void hurt_expire_event() */ static void hurt_expire_event(void *unused) { rb_dlink_node *ptr, *next_ptr; hurt_t *hurt; RB_DLINK_FOREACH_SAFE (ptr, next_ptr, hurt_confs.head) { hurt = (hurt_t *) ptr->data; if (hurt->expire <= rb_current_time()) { rb_dlinkFindDestroy(hurt, &hurt_confs); hurt_destroy(hurt); } } } /* }}} */ /* * Hook functions. */ /* {{{ static void client_exit_hook() */ static void client_exit_hook(void *data_) { hook_data_client_exit *data = data_; s_assert(data != NULL); s_assert(data->target != NULL); rb_dlinkFindDestroy(data->target, &hurt_state.hurt_clients); } /* }}} */ /* {{{ static void new_local_user_hook() */ static void new_local_user_hook(void *data) { struct Client *source_p = data; if (IsAnyDead(source_p) || !EmptyString(source_p->user->suser) || IsExemptKline(source_p)) return; if (hurt_find(source_p->sockhost) || hurt_find(source_p->orighost)) { source_p->localClient->target_last = rb_current_time() + 600; /* don't ask --nenolod */ SetTGChange(source_p); rb_dlinkAddAlloc(source_p, &hurt_state.hurt_clients); sendto_one_notice(source_p, ":You are hurt. Please identify to services immediately, or use /stats p for assistance."); } } /* }}} */ /* {{{ static void doing_stats_hook() */ static void doing_stats_hook(void *data) { hook_data_int *hdata = data; rb_dlink_node *ptr; hurt_t *hurt; struct Client *source_p; s_assert(hdata); s_assert(hdata->client); source_p = hdata->client; if(hdata->arg2 != (int) 's') return; if((ConfigFileEntry.stats_k_oper_only == 2) && !IsOperGeneral(source_p)) return; if ((ConfigFileEntry.stats_k_oper_only == 1) && !IsOperGeneral(source_p)) { hurt = hurt_find(source_p->sockhost); if (hurt != NULL) { sendto_one_numeric(source_p, RPL_STATSKLINE, form_str(RPL_STATSKLINE), 's', "*", hurt->ip, hurt->reason, "", ""); return; } hurt = hurt_find(source_p->orighost); if (hurt != NULL) { sendto_one_numeric(source_p, RPL_STATSKLINE, form_str(RPL_STATSKLINE), 's', "*", hurt->ip, hurt->reason, "", ""); } return; } RB_DLINK_FOREACH(ptr, hurt_confs.head) { hurt = (hurt_t *) ptr->data; sendto_one_numeric(source_p, RPL_STATSKLINE, form_str(RPL_STATSKLINE), 's', "*", hurt->ip, hurt->reason, "", ""); } } /* }}} */ /* {{{ static void hurt_propagate() * * client_p - specific server to propagate HURT to, or NULL to propagate to all * servers. * source_p - source (oper who added the HURT) * hurt - HURT to be propagated */ static void hurt_propagate(struct Client *client_p, struct Client *source_p, hurt_t *hurt) { if (client_p) sendto_one(client_p, ":%s ENCAP %s HURT %ld %s :%s", source_p->name, client_p->name, (long)(hurt->expire - rb_current_time()), hurt->ip, hurt->reason); else sendto_server(&me, NULL, NOCAPS, NOCAPS, ":%s ENCAP * HURT %ld %s :%s", source_p->name, (long)(hurt->expire - rb_current_time()), hurt->ip, hurt->reason); } /* }}} */ /* {{{ static hurt_t *hurt_new() */ static hurt_t * hurt_new(time_t expire, const char *ip, const char *reason) { hurt_t *hurt; hurt = rb_malloc(sizeof(hurt_t)); hurt->ip = rb_strdup(ip); hurt->reason = rb_strdup(reason); hurt->expire = rb_current_time() + expire; return hurt; } /* }}} */ /* {{{ static void hurt_destroy() */ static void hurt_destroy(void *hurt) { hurt_t *h; if (!hurt) return; h = (hurt_t *) hurt; rb_free(h->ip); rb_free(h->reason); rb_free(h); } /* }}} */ static void hurt_add(hurt_t *hurt) { rb_dlinkAddAlloc(hurt, &hurt_confs); } static hurt_t * hurt_find_exact(const char *ip) { rb_dlink_node *ptr; hurt_t *hurt; RB_DLINK_FOREACH(ptr, hurt_confs.head) { hurt = (hurt_t *) ptr->data; if (!rb_strcasecmp(ip, hurt->ip)) return hurt; } return NULL; } static hurt_t * hurt_find(const char *ip) { rb_dlink_node *ptr; hurt_t *hurt; RB_DLINK_FOREACH(ptr, hurt_confs.head) { hurt = (hurt_t *) ptr->data; if (match(hurt->ip, ip)) return hurt; } return NULL; } static void hurt_remove(const char *ip) { hurt_t *hurt = hurt_find_exact(ip); rb_dlinkFindDestroy(hurt, &hurt_confs); hurt_destroy(hurt); } /* {{{ static void heal_nick() */ static void heal_nick(struct Client *source_p, struct Client *target_p) { if (rb_dlinkFindDestroy(target_p, &hurt_state.hurt_clients)) { sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s used HEAL on %s", get_oper_name(source_p), get_client_name(target_p, HIDE_IP)); sendto_one_notice(target_p, ":HURT restriction temporarily removed by operator"); sendto_one_notice(source_p, ":HURT restriction on %s temporarily removed", target_p->name); target_p->localClient->target_last = rb_current_time(); /* don't ask --nenolod */ } else { sendto_one_notice(source_p, ":%s was not hurt", target_p->name); } } /* }}} */ /* * vim: ts=8 sw=8 noet fdm=marker tw=80 */