/* * channels.c -- part of channels.mod * support for channels within the bot * * $Id: channels.c,v 1.59 2002/01/02 03:46:37 guppy Exp $ */ /* * Copyright (C) 1997 Robey Pointer * Copyright (C) 1999, 2000, 2001, 2002 Eggheads 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define MODULE_NAME "channels" #define MAKING_CHANNELS #include #include "src/mod/module.h" static Function *global = NULL; static int setstatic; static int use_info; static int ban_time; static int exempt_time; /* If exempt_time = 0, never remove them */ static int invite_time; /* If invite_time = 0, never remove them */ static char chanfile[121]; static int chan_hack; static int quiet_save; static char glob_chanmode[64]; /* Default chanmode (drummer,990731) */ static struct udef_struct *udef; static int global_stopnethack_mode; static int global_revenge_mode; static int global_idle_kick; /* Default idle-kick setting. */ static int global_aop_min; static int global_aop_max; /* Global channel settings (drummer/dw) */ static char glob_chanset[512]; /* Global flood settings */ static int gfld_chan_thr; static int gfld_chan_time; static int gfld_deop_thr; static int gfld_deop_time; static int gfld_kick_thr; static int gfld_kick_time; static int gfld_join_thr; static int gfld_join_time; static int gfld_ctcp_thr; static int gfld_ctcp_time; static int gfld_nick_thr; static int gfld_nick_time; #include "channels.h" #include "cmdschan.c" #include "tclchan.c" #include "userchan.c" #include "udefchan.c" static void *channel_malloc(int size, char *file, int line) { char *p; #ifdef DEBUG_MEM p = ((void *) (global[0] (size, MODULE_NAME, file, line))); #else p = nmalloc(size); #endif egg_bzero(p, size); return p; } static void set_mode_protect(struct chanset_t *chan, char *set) { int i, pos = 1; char *s, *s1; /* Clear old modes */ chan->mode_mns_prot = chan->mode_pls_prot = 0; chan->limit_prot = 0; chan->key_prot[0] = 0; for (s = newsplit(&set); *s; s++) { i = 0; switch (*s) { case '+': pos = 1; break; case '-': pos = 0; break; case 'i': i = CHANINV; break; case 'p': i = CHANPRIV; break; case 's': i = CHANSEC; break; case 'm': i = CHANMODER; break; case 'c': i = CHANNOCLR; break; case 'R': i = CHANREGON; break; case 'M': i = CHANMODR; break; case 't': i = CHANTOPIC; break; case 'n': i = CHANNOMSG; break; case 'a': i = CHANANON; break; case 'q': i = CHANQUIET; break; case 'l': i = CHANLIMIT; chan->limit_prot = 0; if (pos) { s1 = newsplit(&set); if (s1[0]) chan->limit_prot = atoi(s1); } break; case 'k': i = CHANKEY; chan->key_prot[0] = 0; if (pos) { s1 = newsplit(&set); if (s1[0]) strcpy(chan->key_prot, s1); } break; } if (i) { if (pos) { chan->mode_pls_prot |= i; chan->mode_mns_prot &= ~i; } else { chan->mode_pls_prot &= ~i; chan->mode_mns_prot |= i; } } } /* Prevents a +s-p +p-s flood (fixed by drummer) */ if (chan->mode_pls_prot & CHANSEC) chan->mode_pls_prot &= ~CHANPRIV; } static void get_mode_protect(struct chanset_t *chan, char *s) { char *p = s, s1[121]; int ok = 0, i, tst; s1[0] = 0; for (i = 0; i < 2; i++) { ok = 0; if (i == 0) { tst = chan->mode_pls_prot; if ((tst) || (chan->limit_prot != 0) || (chan->key_prot[0])) *p++ = '+'; if (chan->limit_prot != 0) { *p++ = 'l'; sprintf(&s1[strlen(s1)], "%d ", chan->limit_prot); } if (chan->key_prot[0]) { *p++ = 'k'; sprintf(&s1[strlen(s1)], "%s ", chan->key_prot); } } else { tst = chan->mode_mns_prot; if (tst) *p++ = '-'; if (tst & CHANKEY) *p++ = 'k'; if (tst & CHANLIMIT) *p++ = 'l'; } if (tst & CHANINV) *p++ = 'i'; if (tst & CHANPRIV) *p++ = 'p'; if (tst & CHANSEC) *p++ = 's'; if (tst & CHANMODER) *p++ = 'm'; if (tst & CHANNOCLR) *p++ = 'c'; if (tst & CHANREGON) *p++ = 'R'; if (tst & CHANMODR) *p++ = 'M'; if (tst & CHANTOPIC) *p++ = 't'; if (tst & CHANNOMSG) *p++ = 'n'; if (tst & CHANANON) *p++ = 'a'; if (tst & CHANQUIET) *p++ = 'q'; } *p = 0; if (s1[0]) { s1[strlen(s1) - 1] = 0; strcat(s, " "); strcat(s, s1); } } /* Returns true if this is one of the channel masks */ static int ismodeline(masklist *m, char *user) { for (; m && m->mask[0]; m = m->next) if (!rfc_casecmp(m->mask, user)) return 1; return 0; } /* Returns true if user matches one of the masklist -- drummer */ static int ismasked(masklist *m, char *user) { for (; m && m->mask[0]; m = m->next) if (wild_match(m->mask, user)) return 1; return 0; } /* Unlink chanset element from chanset list. */ inline static int chanset_unlink(struct chanset_t *chan) { struct chanset_t *c, *c_old = NULL; for (c = chanset; c; c_old = c, c = c->next) { if (c == chan) { if (c_old) c_old->next = c->next; else chanset = c->next; return 1; } } return 0; } /* Completely removes a channel. * * This includes the removal of all channel-bans, -exempts and -invites, as * well as all user flags related to the channel. */ static void remove_channel(struct chanset_t *chan) { int i; module_entry *me; /* Remove the channel from the list, so that noone can pull it away from under our feet during the check_tcl_part() call. */ (void) chanset_unlink(chan); if ((me = module_find("irc", 1, 3)) != NULL) (me->funcs[IRC_DO_CHANNEL_PART])(chan); clear_channel(chan, 0); noshare = 1; /* Remove channel-bans */ while (chan->bans) u_delban(chan, chan->bans->mask, 1); /* Remove channel-exempts */ while (chan->exempts) u_delexempt(chan, chan->exempts->mask, 1); /* Remove channel-invites */ while (chan->invites) u_delinvite(chan, chan->invites->mask, 1); /* Remove channel specific user flags */ user_del_chan(chan->dname); noshare = 0; nfree(chan->channel.key); for (i = 0; i < 6 && chan->cmode[i].op; i++) nfree(chan->cmode[i].op); if (chan->key) nfree(chan->key); if (chan->rmkey) nfree(chan->rmkey); nfree(chan); } /* Bind this to chon and *if* the users console channel == *** * then set it to a specific channel */ static int channels_chon(char *handle, int idx) { struct flag_record fr = {FR_CHAN | FR_ANYWH | FR_GLOBAL, 0, 0, 0, 0, 0}; int find, found = 0; struct chanset_t *chan = chanset; if (dcc[idx].type == &DCC_CHAT) { if (!findchan_by_dname(dcc[idx].u.chat->con_chan) && ((dcc[idx].u.chat->con_chan[0] != '*') || (dcc[idx].u.chat->con_chan[1] != 0))) { get_user_flagrec(dcc[idx].user, &fr, NULL); if (glob_op(fr)) found = 1; if (chan_owner(fr)) find = USER_OWNER; else if (chan_master(fr)) find = USER_MASTER; else find = USER_OP; fr.match = FR_CHAN; while (chan && !found) { get_user_flagrec(dcc[idx].user, &fr, chan->dname); if (fr.chan & find) found = 1; else chan = chan->next; } if (!chan) chan = chanset; if (chan) strcpy(dcc[idx].u.chat->con_chan, chan->dname); else strcpy(dcc[idx].u.chat->con_chan, "*"); } } return 0; } static char *convert_element(char *src, char *dst) { int flags; Tcl_ScanElement(src, &flags); Tcl_ConvertElement(src, dst, flags); return dst; } #define PLSMNS(x) (x ? '+' : '-') /* * Note: * - We write chanmode "" too, so that the bot won't use default-chanmode * instead of "" * - We will write empty need-xxxx too, why not? (less code + lazyness) */ static void write_channels() { FILE *f; char s[121], w[1024], w2[1024], name[163]; char need1[242], need2[242], need3[242], need4[242], need5[242]; struct chanset_t *chan; struct udef_struct *ul; if (!chanfile[0]) return; sprintf(s, "%s~new", chanfile); f = fopen(s, "w"); chmod(s, userfile_perm); if (f == NULL) { putlog(LOG_MISC, "*", "ERROR writing channel file."); return; } if (!quiet_save) putlog(LOG_MISC, "*", "Writing channel file ..."); fprintf(f, "#Dynamic Channel File for %s (%s) -- written %s\n", botnetnick, ver, ctime(&now)); for (chan = chanset; chan; chan = chan->next) { convert_element(chan->dname, name); get_mode_protect(chan, w); convert_element(w, w2); convert_element(chan->need_op, need1); convert_element(chan->need_invite, need2); convert_element(chan->need_key, need3); convert_element(chan->need_unban, need4); convert_element(chan->need_limit, need5); fprintf(f, "channel %s %s%schanmode %s idle-kick %d stopnethack-mode %d \ revenge-mode %d \ need-op %s need-invite %s need-key %s need-unban %s need-limit %s \ flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d \ flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d aop-delay %d:%d \ %cenforcebans %cdynamicbans %cuserbans %cautoop %cbitch \ %cgreet %cprotectops %cprotectfriends %cdontkickops \ %cstatuslog %crevenge %crevengebot %cautovoice %csecret \ %cshared %ccycle %cseen %cinactive %cdynamicexempts %cuserexempts \ %cdynamicinvites %cuserinvites %cnodesynch ", channel_static(chan) ? "set" : "add", name, channel_static(chan) ? " " : " { ", w2, chan->idle_kick, /* idle-kick 0 is same as dont-idle-kick (less code)*/ chan->stopnethack_mode, chan->revenge_mode, need1, need2, need3, need4, need5, chan->flood_pub_thr, chan->flood_pub_time, chan->flood_ctcp_thr, chan->flood_ctcp_time, chan->flood_join_thr, chan->flood_join_time, chan->flood_kick_thr, chan->flood_kick_time, chan->flood_deop_thr, chan->flood_deop_time, chan->flood_nick_thr, chan->flood_nick_time, chan->aop_min, chan->aop_max, PLSMNS(channel_enforcebans(chan)), PLSMNS(channel_dynamicbans(chan)), PLSMNS(!channel_nouserbans(chan)), PLSMNS(channel_autoop(chan)), PLSMNS(channel_bitch(chan)), PLSMNS(channel_greet(chan)), PLSMNS(channel_protectops(chan)), PLSMNS(channel_protectfriends(chan)), PLSMNS(channel_dontkickops(chan)), PLSMNS(channel_logstatus(chan)), PLSMNS(channel_revenge(chan)), PLSMNS(channel_revengebot(chan)), PLSMNS(channel_autovoice(chan)), PLSMNS(channel_secret(chan)), PLSMNS(channel_shared(chan)), PLSMNS(channel_cycle(chan)), PLSMNS(channel_seen(chan)), PLSMNS(channel_inactive(chan)), PLSMNS(channel_dynamicexempts(chan)), PLSMNS(!channel_nouserexempts(chan)), PLSMNS(channel_dynamicinvites(chan)), PLSMNS(!channel_nouserinvites(chan)), PLSMNS(channel_nodesynch(chan))); for (ul = udef; ul; ul = ul->next) { if (ul->defined && ul->name) { if (ul->type == UDEF_FLAG) fprintf(f, "%c%s%s ", getudef(ul->values, chan->dname) ? '+' : '-', "udef-flag-", ul->name); else if (ul->type == UDEF_INT) fprintf(f, "%s%s %d ", "udef-int-", ul->name, getudef(ul->values, chan->dname)); else debug1("UDEF-ERROR: unknown type %d", ul->type); } } fprintf(f, "%s\n", channel_static(chan) ? "" : "}"); if (fflush(f)) { putlog(LOG_MISC, "*", "ERROR writing channel file."); fclose(f); return; } } fclose(f); unlink(chanfile); movefile(s, chanfile); } static void read_channels(int create) { struct chanset_t *chan, *chan_next; if (!chanfile[0]) return; for (chan = chanset; chan; chan = chan->next) if (!channel_static(chan)) chan->status |= CHAN_FLAGGED; chan_hack = 1; if (!readtclprog(chanfile) && create) { FILE *f; /* Assume file isnt there & therfore make it */ putlog(LOG_MISC, "*", "Creating channel file"); f = fopen(chanfile, "w"); if (!f) putlog(LOG_MISC, "*", "Couldn't create channel file: %s. Dropping", chanfile); else fclose(f); } chan_hack = 0; for (chan = chanset; chan; chan = chan_next) { chan_next = chan->next; if (chan->status & CHAN_FLAGGED) { putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname); remove_channel(chan); } } } static void backup_chanfile() { char s[125]; putlog(LOG_MISC, "*", "Backing up channel file..."); egg_snprintf(s, sizeof s, "%s~bak", chanfile); copyfile(chanfile, s); } static void channels_prerehash() { struct chanset_t *chan; /* Flag will be cleared as the channels are re-added by the * config file. Any still flagged afterwards will be removed. */ for (chan = chanset; chan; chan = chan->next) { chan->status |= CHAN_FLAGGED; /* Flag is only added to channels read from config file */ if (chan->status & CHAN_STATIC) chan->status &= ~CHAN_STATIC; } setstatic = 1; } static void channels_rehash() { struct chanset_t *chan; setstatic = 0; read_channels(1); /* Remove any extra channels, by checking the flag. */ chan = chanset; for (chan = chanset; chan;) { if (chan->status & CHAN_FLAGGED) { putlog(LOG_MISC, "*", "No longer supporting channel %s", chan->dname); remove_channel(chan); chan = chanset; } else chan = chan->next; } } static cmd_t my_chon[] = { {"*", "", (Function) channels_chon, "channels:chon"}, {NULL, NULL, NULL, NULL} }; static void channels_report(int idx, int details) { struct chanset_t *chan; int i; char s[1024], s2[100]; struct flag_record fr = {FR_CHAN | FR_GLOBAL, 0, 0, 0, 0, 0}; for (chan = chanset; chan; chan = chan->next) { if (idx != DP_STDOUT) get_user_flagrec(dcc[idx].user, &fr, chan->dname); if ((idx == DP_STDOUT) || glob_master(fr) || chan_master(fr)) { s[0] = 0; if (channel_greet(chan)) strcat(s, "greet, "); if (channel_autoop(chan)) strcat(s, "auto-op, "); if (channel_bitch(chan)) strcat(s, "bitch, "); if (s[0]) s[strlen(s) - 2] = 0; if (!s[0]) strcpy(s, MISC_LURKING); get_mode_protect(chan, s2); if (!channel_inactive(chan)) { if (channel_active(chan)) { /* If it's a !chan, we want to display it's unique name too */ if (chan->dname[0]=='!') { dprintf(idx, " %-10s: %2d member%s enforcing \"%s\" (%s), " "unique name %s\n", chan->dname, chan->channel.members, (chan->channel.members==1) ? "," : "s,", s2, s, chan->name); } else { dprintf(idx, " %-10s: %2d member%s enforcing \"%s\" (%s)\n", chan->dname, chan->channel.members, chan->channel.members == 1 ? "," : "s,", s2, s); } } else { dprintf(idx, " %-10s: (%s), enforcing \"%s\" (%s)\n", chan->dname, channel_pending(chan) ? "pending" : "not on channel", s2, s); } } else { dprintf(idx, " %-10s: channel is set +inactive\n", chan->dname); } if (details) { s[0] = 0; i = 0; if (channel_enforcebans(chan)) i += my_strcpy(s + i, "enforce-bans "); if (channel_dynamicbans(chan)) i += my_strcpy(s + i, "dynamic-bans "); if (channel_nouserbans(chan)) i += my_strcpy(s + i, "forbid-user-bans "); if (channel_autoop(chan)) i += my_strcpy(s + i, "op-on-join "); if (channel_bitch(chan)) i += my_strcpy(s + i, "bitch "); if (channel_greet(chan)) i += my_strcpy(s + i, "greet "); if (channel_protectops(chan)) i += my_strcpy(s + i, "protect-ops "); if (channel_protectfriends(chan)) i += my_strcpy(s + i, "protect-friends "); if (channel_dontkickops(chan)) i += my_strcpy(s + i, "dont-kick-ops "); if (channel_logstatus(chan)) i += my_strcpy(s + i, "log-status "); if (channel_revenge(chan)) i += my_strcpy(s + i, "revenge "); if (channel_secret(chan)) i += my_strcpy(s + i, "secret "); if (channel_shared(chan)) i += my_strcpy(s + i, "shared "); if (!channel_static(chan)) i += my_strcpy(s + i, "dynamic "); if (channel_autovoice(chan)) i += my_strcpy(s + i, "autovoice "); if (channel_cycle(chan)) i += my_strcpy(s + i, "cycle "); if (channel_seen(chan)) i += my_strcpy(s + i, "seen "); if (channel_dynamicexempts(chan)) i += my_strcpy(s + i, "dynamic-exempts "); if (channel_nouserexempts(chan)) i += my_strcpy(s + i, "forbid-user-exempts "); if (channel_dynamicinvites(chan)) i += my_strcpy(s + i, "dynamic-invites "); if (channel_nouserinvites(chan)) i += my_strcpy(s + i, "forbid-user-invites "); if (channel_inactive(chan)) i += my_strcpy(s + i, "inactive "); if (channel_nodesynch(chan)) i += my_strcpy(s + i, "nodesynch "); dprintf(idx, " Options: %s\n", s); if (chan->need_op[0]) dprintf(idx, " To get ops I do: %s\n", chan->need_op); if (chan->need_invite[0]) dprintf(idx, " To get invited I do: %s\n", chan->need_invite); if (chan->need_limit[0]) dprintf(idx, " To get the channel limit up'd I do: %s\n", chan->need_limit); if (chan->need_unban[0]) dprintf(idx, " To get unbanned I do: %s\n", chan->need_unban); if (chan->need_key[0]) dprintf(idx, " To get the channel key I do: %s\n", chan->need_key); if (chan->idle_kick) dprintf(idx, " Kicking idle users after %d min\n", chan->idle_kick); if (chan->stopnethack_mode) dprintf(idx, " stopnethack-mode %d\n", chan->stopnethack_mode); if (chan->revenge_mode) dprintf(idx, " revenge-mode %d\n", chan->revenge_mode); } } } if (details) { dprintf(idx, " Bans last %d mins.\n", ban_time); dprintf(idx, " Exemptions last %d mins.\n", exempt_time); dprintf(idx, " Invitations last %d mins.\n", invite_time); } } static int expmem_masklist(masklist *m) { int result = 0; for (; m; m = m->next) { result += sizeof(masklist); if (m->mask) result += strlen(m->mask) + 1; if (m->who) result += strlen(m->who) + 1; } return result; } static int channels_expmem() { int tot = 0, i; struct chanset_t *chan; for (chan = chanset; chan; chan = chan->next) { tot += sizeof(struct chanset_t); tot += strlen(chan->channel.key) + 1; if (chan->channel.topic) tot += strlen(chan->channel.topic) + 1; tot += (sizeof(struct memstruct) * (chan->channel.members + 1)); tot += expmem_masklist(chan->channel.ban); tot += expmem_masklist(chan->channel.exempt); tot += expmem_masklist(chan->channel.invite); for (i = 0; i < 6 && chan->cmode[i].op; i++) tot += strlen(chan->cmode[i].op) + 1; if (chan->key) tot += strlen(chan->key) + 1; if (chan->rmkey) tot += strlen(chan->rmkey) + 1; } tot += expmem_udef(udef); return tot; } static char *traced_globchanset(ClientData cdata, Tcl_Interp * irp, char *name1, char *name2, int flags) { char *s; char *t; int i; int items; char **item; if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) { Tcl_SetVar2(interp, name1, name2, glob_chanset, TCL_GLOBAL_ONLY); if (flags & TCL_TRACE_UNSETS) Tcl_TraceVar(interp, "global-chanset", TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS, traced_globchanset, NULL); } else { /* Write */ s = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY); Tcl_SplitList(interp, s, &items, &item); for (i = 0; i */ /* 24 - 27 */ (Function) & exempt_time, (Function) NULL, /* [25] used to be isinvited() */ (Function) & invite_time, (Function) NULL, /* 28 - 31 */ (Function) NULL, /* [28] used to be u_setsticky_exempt() */ (Function) u_delexempt, (Function) u_addexempt, (Function) NULL, /* 32 - 35 */ (Function) NULL,/* [32] used to be u_sticky_exempt() */ (Function) NULL, (Function) NULL, /* [34] used to be killchanset(). */ (Function) u_delinvite, /* 36 - 39 */ (Function) u_addinvite, (Function) tcl_channel_add, (Function) tcl_channel_modify, (Function) write_exempts, /* 40 - 43 */ (Function) write_invites, (Function) ismodeline, (Function) initudef, (Function) ngetudef, /* 44 - 47 */ (Function) expired_mask, (Function) remove_channel, }; char *channels_start(Function * global_funcs) { global = global_funcs; gfld_chan_thr = 10; gfld_chan_time = 60; gfld_deop_thr = 3; gfld_deop_time = 10; gfld_kick_thr = 3; gfld_kick_time = 10; gfld_join_thr = 5; gfld_join_time = 60; gfld_ctcp_thr = 5; gfld_ctcp_time = 60; global_idle_kick = 0; global_aop_min = 5; global_aop_max = 30; setstatic = 0; use_info = 1; ban_time = 60; exempt_time = 0; invite_time = 0; strcpy(chanfile, "chanfile"); chan_hack = 0; quiet_save = 0; strcpy(glob_chanmode, "nt"); udef = NULL; global_stopnethack_mode = 0; global_revenge_mode = 1; strcpy(glob_chanset, "\ -enforcebans +dynamicbans +userbans -autoop -bitch +greet \ +protectops +statuslog -revenge -secret -autovoice +cycle \ +dontkickops -inactive -protectfriends +shared -seen \ +userexempts +dynamicexempts +userinvites +dynamicinvites -revengebot \ -nodesynch" /* Do not remove this extra space: */ " "); module_register(MODULE_NAME, channels_table, 1, 0); if (!module_depend(MODULE_NAME, "eggdrop", 106, 7)) { module_undepend(MODULE_NAME); return "This module needs eggdrop1.6.7 or later"; } add_hook(HOOK_MINUTELY, (Function) check_expired_bans); add_hook(HOOK_MINUTELY, (Function) check_expired_exempts); add_hook(HOOK_MINUTELY, (Function) check_expired_invites); add_hook(HOOK_USERFILE, (Function) channels_writeuserfile); add_hook(HOOK_BACKUP, (Function) backup_chanfile); add_hook(HOOK_REHASH, (Function) channels_rehash); add_hook(HOOK_PRE_REHASH, (Function) channels_prerehash); Tcl_TraceVar(interp, "global-chanset", TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS, traced_globchanset, NULL); add_builtins(H_chon, my_chon); add_builtins(H_dcc, C_dcc_irc); add_tcl_commands(channels_cmds); add_tcl_strings(my_tcl_strings); add_help_reference("channels.help"); add_help_reference("chaninfo.help"); my_tcl_ints[0].val = &share_greet; add_tcl_ints(my_tcl_ints); add_tcl_coups(mychan_tcl_coups); read_channels(0); setstatic = 1; return NULL; }