summaryrefslogtreecommitdiff
path: root/src/chanserv.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/chanserv.c')
-rw-r--r--src/chanserv.c2674
1 files changed, 2674 insertions, 0 deletions
diff --git a/src/chanserv.c b/src/chanserv.c
new file mode 100644
index 000000000..98f3b9391
--- /dev/null
+++ b/src/chanserv.c
@@ -0,0 +1,2674 @@
+/* ChanServ functions.
+ *
+ * (C) 2003-2008 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for further details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id$
+ *
+ */
+
+/*************************************************************************/
+
+#include "services.h"
+#include "pseudo.h"
+
+/*************************************************************************/
+/* *INDENT-OFF* */
+
+ChannelInfo *chanlists[256];
+
+static int def_levels[][2] = {
+ { CA_AUTOOP, 5 },
+ { CA_AUTOVOICE, 3 },
+ { CA_AUTODEOP, -1 },
+ { CA_NOJOIN, -2 },
+ { CA_INVITE, 5 },
+ { CA_AKICK, 10 },
+ { CA_SET, ACCESS_INVALID },
+ { CA_CLEAR, ACCESS_INVALID },
+ { CA_UNBAN, 5 },
+ { CA_OPDEOP, 5 },
+ { CA_ACCESS_LIST, 1 },
+ { CA_ACCESS_CHANGE, 10 },
+ { CA_MEMO, 10 },
+ { CA_ASSIGN, ACCESS_INVALID },
+ { CA_BADWORDS, 10 },
+ { CA_NOKICK, 1 },
+ { CA_FANTASIA, 3 },
+ { CA_SAY, 5 },
+ { CA_GREET, 5 },
+ { CA_VOICEME, 3 },
+ { CA_VOICE, 5 },
+ { CA_GETKEY, 5 },
+ { CA_AUTOHALFOP, 4 },
+ { CA_AUTOPROTECT, 10 },
+ { CA_OPDEOPME, 5 },
+ { CA_HALFOPME, 4 },
+ { CA_HALFOP, 5 },
+ { CA_PROTECTME, 10 },
+ { CA_PROTECT, ACCESS_INVALID },
+ { CA_KICKME, 5 },
+ { CA_KICK, 5 },
+ { CA_SIGNKICK, ACCESS_INVALID },
+ { CA_BANME, 5 },
+ { CA_BAN, 5 },
+ { CA_TOPIC, ACCESS_INVALID },
+ { CA_INFO, ACCESS_INVALID },
+ { -1 }
+};
+
+
+LevelInfo levelinfo[] = {
+ { CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP },
+ { CA_AUTOHALFOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP },
+ { CA_AUTOOP, "AUTOOP", CHAN_LEVEL_AUTOOP },
+ { CA_AUTOPROTECT, "", CHAN_LEVEL_AUTOPROTECT },
+ { CA_AUTOVOICE, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE },
+ { CA_NOJOIN, "NOJOIN", CHAN_LEVEL_NOJOIN },
+ { CA_SIGNKICK, "SIGNKICK", CHAN_LEVEL_SIGNKICK },
+ { CA_ACCESS_LIST, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST },
+ { CA_ACCESS_CHANGE, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE },
+ { CA_AKICK, "AKICK", CHAN_LEVEL_AKICK },
+ { CA_SET, "SET", CHAN_LEVEL_SET },
+ { CA_BAN, "BAN", CHAN_LEVEL_BAN },
+ { CA_BANME, "BANME", CHAN_LEVEL_BANME },
+ { CA_CLEAR, "CLEAR", CHAN_LEVEL_CLEAR },
+ { CA_GETKEY, "GETKEY", CHAN_LEVEL_GETKEY },
+ { CA_HALFOP, "HALFOP", CHAN_LEVEL_HALFOP },
+ { CA_HALFOPME, "HALFOPME", CHAN_LEVEL_HALFOPME },
+ { CA_INFO, "INFO", CHAN_LEVEL_INFO },
+ { CA_KICK, "KICK", CHAN_LEVEL_KICK },
+ { CA_KICKME, "KICKME", CHAN_LEVEL_KICKME },
+ { CA_INVITE, "INVITE", CHAN_LEVEL_INVITE },
+ { CA_OPDEOP, "OPDEOP", CHAN_LEVEL_OPDEOP },
+ { CA_OPDEOPME, "OPDEOPME", CHAN_LEVEL_OPDEOPME },
+ { CA_PROTECT, "", CHAN_LEVEL_PROTECT },
+ { CA_PROTECTME, "", CHAN_LEVEL_PROTECTME },
+ { CA_TOPIC, "TOPIC", CHAN_LEVEL_TOPIC },
+ { CA_UNBAN, "UNBAN", CHAN_LEVEL_UNBAN },
+ { CA_VOICE, "VOICE", CHAN_LEVEL_VOICE },
+ { CA_VOICEME, "VOICEME", CHAN_LEVEL_VOICEME },
+ { CA_MEMO, "MEMO", CHAN_LEVEL_MEMO },
+ { CA_ASSIGN, "ASSIGN", CHAN_LEVEL_ASSIGN },
+ { CA_BADWORDS, "BADWORDS", CHAN_LEVEL_BADWORDS },
+ { CA_FANTASIA, "FANTASIA", CHAN_LEVEL_FANTASIA },
+ { CA_GREET, "GREET", CHAN_LEVEL_GREET },
+ { CA_NOKICK, "NOKICK", CHAN_LEVEL_NOKICK },
+ { CA_SAY, "SAY", CHAN_LEVEL_SAY },
+ { -1 }
+};
+int levelinfo_maxwidth = 0;
+
+CSModeUtil csmodeutils[] = {
+ { "DEOP", "deop", "-o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME },
+ { "OP", "op", "+o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME },
+ { "DEVOICE", "devoice", "-v", 0, CA_VOICE, CA_VOICEME },
+ { "VOICE", "voice", "+v", 0, CA_VOICE, CA_VOICEME },
+ { "DEHALFOP", "dehalfop", "-h", 0, CA_HALFOP, CA_HALFOPME },
+ { "HALFOP", "halfop", "+h", 0, CA_HALFOP, CA_HALFOPME },
+ { "DEPROTECT", "", "", 0, CA_PROTECT, CA_PROTECTME },
+ { "PROTECT", "", "", 0, CA_PROTECT, CA_PROTECTME },
+ { NULL }
+};
+
+/*************************************************************************/
+
+void moduleAddChanServCmds(void) {
+ modules_core_init(ChanServCoreNumber, ChanServCoreModules);
+}
+
+/* *INDENT-ON* */
+/*************************************************************************/
+/*************************************************************************/
+
+/* Returns modes for mlock in a nice way. */
+
+char *get_mlock_modes(ChannelInfo * ci, int complete)
+{
+ static char res[BUFSIZE];
+
+ char *end = res;
+
+ if (ci->mlock_on || ci->mlock_off) {
+ int n = 0;
+ CBModeInfo *cbmi = cbmodeinfos;
+
+ if (ci->mlock_on) {
+ *end++ = '+';
+ n++;
+
+ do {
+ if (ci->mlock_on & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+
+ cbmi = cbmodeinfos;
+ }
+
+ if (ci->mlock_off) {
+ *end++ = '-';
+ n++;
+
+ do {
+ if (ci->mlock_off & cbmi->flag)
+ *end++ = cbmi->mode;
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+
+ cbmi = cbmodeinfos;
+ }
+
+ if (ci->mlock_on && complete) {
+ do {
+ if (cbmi->csgetvalue && (ci->mlock_on & cbmi->flag)) {
+ char *value = cbmi->csgetvalue(ci);
+
+ if (value) {
+ *end++ = ' ';
+ while (*value)
+ *end++ = *value++;
+ }
+ }
+ } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
+ }
+ }
+
+ *end = 0;
+
+ return res;
+}
+
+/* Display total number of registered channels and info about each; or, if
+ * a specific channel is given, display information about that channel
+ * (like /msg ChanServ INFO <channel>). If count_only != 0, then only
+ * display the number of registered channels (the channel parameter is
+ * ignored).
+ */
+
+void listchans(int count_only, const char *chan)
+{
+ int count = 0;
+ ChannelInfo *ci;
+ int i;
+
+ if (count_only) {
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next)
+ count++;
+ }
+ printf("%d channels registered.\n", count);
+
+ } else if (chan) {
+
+ struct tm *tm;
+ char buf[BUFSIZE];
+
+ if (!(ci = cs_findchan(chan))) {
+ printf("Channel %s not registered.\n", chan);
+ return;
+ }
+ if (ci->flags & CI_VERBOTEN) {
+ printf("Channel %s is FORBIDden.\n", ci->name);
+ } else {
+ printf("Information about channel %s:\n", ci->name);
+ printf(" Founder: %s\n", ci->founder->display);
+ printf(" Description: %s\n", ci->desc);
+ tm = localtime(&ci->time_registered);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Registered: %s\n", buf);
+ tm = localtime(&ci->last_used);
+ strftime(buf, sizeof(buf),
+ getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
+ printf(" Last used: %s\n", buf);
+ if (ci->last_topic) {
+ printf(" Last topic: %s\n", ci->last_topic);
+ printf(" Topic set by: %s\n", ci->last_topic_setter);
+ }
+ if (ci->url)
+ printf(" URL: %s\n", ci->url);
+ if (ci->email)
+ printf(" E-mail address: %s\n", ci->email);
+ printf(" Options: ");
+ if (!ci->flags) {
+ printf("None\n");
+ } else {
+ int need_comma = 0;
+ static const char commastr[] = ", ";
+ if (ci->flags & CI_PRIVATE) {
+ printf("Private");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_KEEPTOPIC) {
+ printf("%sTopic Retention",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_TOPICLOCK) {
+ printf("%sTopic Lock", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECUREOPS) {
+ printf("%sSecure Ops", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_RESTRICTED) {
+ printf("%sRestricted Access",
+ need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_SECURE) {
+ printf("%sSecure", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ if (ci->flags & CI_NO_EXPIRE) {
+ printf("%sNo Expire", need_comma ? commastr : "");
+ need_comma = 1;
+ }
+ printf("\n");
+ }
+ if (ci->mlock_on || ci->mlock_off) {
+ printf(" Mode lock: %s\n", get_mlock_modes(ci, 1));
+ }
+ if (ci->flags & CI_SUSPENDED) {
+ printf
+ ("This nickname is currently suspended by %s, reason: %s\n",
+ ci->forbidby,
+ (ci->forbidreason ? ci->forbidreason : "No reason"));
+ }
+ }
+
+ } else {
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ printf(" %s %-20s %s\n",
+ ci->flags & CI_NO_EXPIRE ? "!" : " ", ci->name,
+ ci->flags & CI_VERBOTEN ?
+ "Disallowed (FORBID)" : (ci->flags & CI_SUSPENDED ?
+ "Disallowed (SUSPENDED)" :
+ ci->desc));
+ count++;
+ }
+ }
+ printf("%d channels registered.\n", count);
+
+ }
+}
+
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_chanserv_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ int i, j;
+ ChannelInfo *ci;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ count++;
+ mem += sizeof(*ci);
+ if (ci->desc)
+ mem += strlen(ci->desc) + 1;
+ if (ci->url)
+ mem += strlen(ci->url) + 1;
+ if (ci->email)
+ mem += strlen(ci->email) + 1;
+ mem += ci->accesscount * sizeof(ChanAccess);
+ mem += ci->akickcount * sizeof(AutoKick);
+ for (j = 0; j < ci->akickcount; j++) {
+ if (!(ci->akick[j].flags & AK_ISNICK)
+ && ci->akick[j].u.mask)
+ mem += strlen(ci->akick[j].u.mask) + 1;
+ if (ci->akick[j].reason)
+ mem += strlen(ci->akick[j].reason) + 1;
+ if (ci->akick[j].creator)
+ mem += strlen(ci->akick[j].creator) + 1;
+ }
+ if (ci->mlock_key)
+ mem += strlen(ci->mlock_key) + 1;
+ if (ircd->fmode) {
+ if (ci->mlock_flood)
+ mem += strlen(ci->mlock_flood) + 1;
+ }
+ if (ircd->Lmode) {
+ if (ci->mlock_redirect)
+ mem += strlen(ci->mlock_redirect) + 1;
+ }
+ if (ci->last_topic)
+ mem += strlen(ci->last_topic) + 1;
+ if (ci->entry_message)
+ mem += strlen(ci->entry_message) + 1;
+ if (ci->forbidby)
+ mem += strlen(ci->forbidby) + 1;
+ if (ci->forbidreason)
+ mem += strlen(ci->forbidreason) + 1;
+ if (ci->levels)
+ mem += sizeof(*ci->levels) * CA_SIZE;
+ mem += ci->memos.memocount * sizeof(Memo);
+ for (j = 0; j < ci->memos.memocount; j++) {
+ if (ci->memos.memos[j].text)
+ mem += strlen(ci->memos.memos[j].text) + 1;
+ }
+ if (ci->ttb)
+ mem += sizeof(*ci->ttb) * TTB_SIZE;
+ mem += ci->bwcount * sizeof(BadWord);
+ for (j = 0; j < ci->bwcount; j++)
+ if (ci->badwords[j].word)
+ mem += strlen(ci->badwords[j].word) + 1;
+ }
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* ChanServ initialization. */
+
+void cs_init(void)
+{
+ moduleAddChanServCmds();
+}
+
+/*************************************************************************/
+
+/* Main ChanServ routine. */
+
+void chanserv(User * u, char *buf)
+{
+ char *cmd, *s;
+
+ cmd = strtok(buf, " ");
+
+ if (!cmd) {
+ return;
+ } else if (stricmp(cmd, "\1PING") == 0) {
+ if (!(s = strtok(NULL, ""))) {
+ s = "";
+ }
+ anope_cmd_ctcp(s_ChanServ, u->nick, "PING %s", s);
+ } else if (skeleton) {
+ notice_lang(s_ChanServ, u, SERVICE_OFFLINE, s_ChanServ);
+ } else {
+ mod_run_cmd(s_ChanServ, u, CHANSERV, cmd);
+ }
+}
+
+/*************************************************************************/
+
+/* Load/save data files. */
+
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ if (!forceload) \
+ fatal("Read error on %s", ChanDBName); \
+ failed = 1; \
+ break; \
+ } \
+} while (0)
+
+void load_cs_dbase(void)
+{
+ dbFILE *f;
+ int ver, i, j, c;
+ ChannelInfo *ci, **last, *prev;
+ int failed = 0;
+
+ if (!(f = open_db(s_ChanServ, ChanDBName, "r", CHAN_VERSION)))
+ return;
+
+ ver = get_file_version(f);
+
+ for (i = 0; i < 256 && !failed; i++) {
+ uint16 tmp16;
+ uint32 tmp32;
+ int n_levels;
+ char *s;
+ NickAlias *na;
+
+ last = &chanlists[i];
+ prev = NULL;
+ while ((c = getc_db(f)) != 0) {
+ if (c != 1)
+ fatal("Invalid format in %s", ChanDBName);
+ ci = scalloc(sizeof(ChannelInfo), 1);
+ *last = ci;
+ last = &ci->next;
+ ci->prev = prev;
+ prev = ci;
+ SAFE(read_buffer(ci->name, f));
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->founder = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->founder = na->nc;
+ else
+ ci->founder = NULL;
+ }
+ free(s);
+ } else
+ ci->founder = NULL;
+ if (ver >= 7) {
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->successor = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->successor = na->nc;
+ else
+ ci->successor = NULL;
+ }
+ free(s);
+ } else
+ ci->successor = NULL;
+ } else {
+ ci->successor = NULL;
+ }
+ SAFE(read_buffer(ci->founderpass, f));
+ SAFE(read_string(&ci->desc, f));
+ if (!ci->desc)
+ ci->desc = sstrdup("");
+ SAFE(read_string(&ci->url, f));
+ SAFE(read_string(&ci->email, f));
+ SAFE(read_int32(&tmp32, f));
+ ci->time_registered = tmp32;
+ SAFE(read_int32(&tmp32, f));
+ ci->last_used = tmp32;
+ SAFE(read_string(&ci->last_topic, f));
+ SAFE(read_buffer(ci->last_topic_setter, f));
+ SAFE(read_int32(&tmp32, f));
+ ci->last_topic_time = tmp32;
+ SAFE(read_int32(&ci->flags, f));
+
+ /* Leaveops cleanup */
+ if (ver <= 13 && (ci->flags & 0x00000020))
+ ci->flags &= ~0x00000020;
+ /* Temporary flags cleanup */
+ ci->flags &= ~CI_INHABIT;
+
+ if (ver >= 9) {
+ SAFE(read_string(&ci->forbidby, f));
+ SAFE(read_string(&ci->forbidreason, f));
+ } else {
+ ci->forbidreason = NULL;
+ ci->forbidby = NULL;
+ }
+ if (ver >= 9)
+ SAFE(read_int16(&tmp16, f));
+ else
+ tmp16 = CSDefBantype;
+ ci->bantype = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ n_levels = tmp16;
+ ci->levels = scalloc(2 * CA_SIZE, 1);
+ reset_levels(ci);
+ for (j = 0; j < n_levels; j++) {
+ SAFE(read_int16(&tmp16, f));
+ if (j < CA_SIZE)
+ ci->levels[j] = (int16) tmp16;
+ }
+ /* To avoid levels list silly hacks */
+ if (ver < 10)
+ ci->levels[CA_OPDEOPME] = ci->levels[CA_OPDEOP];
+ if (ver < 11) {
+ ci->levels[CA_KICKME] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_KICK] = ci->levels[CA_OPDEOP];
+ }
+ if (ver < 15) {
+
+ /* Old Ultimate levels import */
+ /* We now conveniently use PROTECT internals for Ultimate's ADMIN support - ShadowMaster */
+ /* Doh, must of course be done before we change the values were trying to import - ShadowMaster */
+ ci->levels[CA_AUTOPROTECT] = ci->levels[32];
+ ci->levels[CA_PROTECTME] = ci->levels[33];
+ ci->levels[CA_PROTECT] = ci->levels[34];
+
+ ci->levels[CA_BANME] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_BAN] = ci->levels[CA_OPDEOP];
+ ci->levels[CA_TOPIC] = ACCESS_INVALID;
+
+
+ }
+
+ SAFE(read_int16(&ci->accesscount, f));
+ if (ci->accesscount) {
+ ci->access = scalloc(ci->accesscount, sizeof(ChanAccess));
+ for (j = 0; j < ci->accesscount; j++) {
+ SAFE(read_int16(&ci->access[j].in_use, f));
+ if (ci->access[j].in_use) {
+ SAFE(read_int16(&tmp16, f));
+ ci->access[j].level = (int16) tmp16;
+ SAFE(read_string(&s, f));
+ if (s) {
+ if (ver >= 13)
+ ci->access[j].nc = findcore(s);
+ else {
+ na = findnick(s);
+ if (na)
+ ci->access[j].nc = na->nc;
+ else
+ ci->access[j].nc = NULL;
+ }
+ free(s);
+ }
+ if (ci->access[j].nc == NULL)
+ ci->access[j].in_use = 0;
+ if (ver >= 11) {
+ SAFE(read_int32(&tmp32, f));
+ ci->access[j].last_seen = tmp32;
+ } else {
+ ci->access[j].last_seen = 0; /* Means we have never seen the user */
+ }
+ }
+ }
+ } else {
+ ci->access = NULL;
+ }
+
+ SAFE(read_int16(&ci->akickcount, f));
+ if (ci->akickcount) {
+ ci->akick = scalloc(ci->akickcount, sizeof(AutoKick));
+ for (j = 0; j < ci->akickcount; j++) {
+ if (ver >= 15) {
+ SAFE(read_int16(&ci->akick[j].flags, f));
+ } else {
+ SAFE(read_int16(&tmp16, f));
+ if (tmp16)
+ ci->akick[j].flags |= AK_USED;
+ }
+ if (ci->akick[j].flags & AK_USED) {
+ if (ver < 15) {
+ SAFE(read_int16(&tmp16, f));
+ if (tmp16)
+ ci->akick[j].flags |= AK_ISNICK;
+ }
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_ISNICK) {
+ if (ver >= 13) {
+ ci->akick[j].u.nc = findcore(s);
+ } else {
+ na = findnick(s);
+ if (na)
+ ci->akick[j].u.nc = na->nc;
+ else
+ ci->akick[j].u.nc = NULL;
+ }
+ if (!ci->akick[j].u.nc)
+ ci->akick[j].flags &= ~AK_USED;
+ free(s);
+ } else {
+ ci->akick[j].u.mask = s;
+ }
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_USED)
+ ci->akick[j].reason = s;
+ else if (s)
+ free(s);
+ if (ver >= 9) {
+ SAFE(read_string(&s, f));
+ if (ci->akick[j].flags & AK_USED) {
+ ci->akick[j].creator = s;
+ } else if (s) {
+ free(s);
+ }
+ SAFE(read_int32(&tmp32, f));
+ if (ci->akick[j].flags & AK_USED)
+ ci->akick[j].addtime = tmp32;
+ } else {
+ ci->akick[j].creator = NULL;
+ ci->akick[j].addtime = 0;
+ }
+ }
+
+ /* Bugfix */
+ if ((ver == 15) && ci->akick[j].flags > 8) {
+ ci->akick[j].flags = 0;
+ ci->akick[j].u.nc = NULL;
+ ci->akick[j].u.nc = NULL;
+ ci->akick[j].addtime = 0;
+ ci->akick[j].creator = NULL;
+ ci->akick[j].reason = NULL;
+ }
+ }
+ } else {
+ ci->akick = NULL;
+ }
+
+ if (ver >= 10) {
+ SAFE(read_int32(&ci->mlock_on, f));
+ SAFE(read_int32(&ci->mlock_off, f));
+ } else {
+ SAFE(read_int16(&tmp16, f));
+ ci->mlock_on = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->mlock_off = tmp16;
+ }
+ SAFE(read_int32(&ci->mlock_limit, f));
+ SAFE(read_string(&ci->mlock_key, f));
+ if (ver >= 10) {
+ if (ircd->fmode) {
+ SAFE(read_string(&ci->mlock_flood, f));
+ } else {
+ SAFE(read_string(&s, f));
+ if (s)
+ free(s);
+ }
+ if (ircd->Lmode) {
+ SAFE(read_string(&ci->mlock_redirect, f));
+ } else {
+ SAFE(read_string(&s, f));
+ if (s)
+ free(s);
+ }
+ }
+
+ SAFE(read_int16(&tmp16, f));
+ ci->memos.memocount = (int16) tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->memos.memomax = (int16) tmp16;
+ if (ci->memos.memocount) {
+ Memo *memos;
+ memos = scalloc(sizeof(Memo) * ci->memos.memocount, 1);
+ ci->memos.memos = memos;
+ for (j = 0; j < ci->memos.memocount; j++, memos++) {
+ SAFE(read_int32(&memos->number, f));
+ SAFE(read_int16(&memos->flags, f));
+ SAFE(read_int32(&tmp32, f));
+ memos->time = tmp32;
+ SAFE(read_buffer(memos->sender, f));
+ SAFE(read_string(&memos->text, f));
+ memos->moduleData = NULL;
+ }
+ }
+
+ SAFE(read_string(&ci->entry_message, f));
+
+ ci->c = NULL;
+
+ /* Some cleanup */
+ if (ver <= 11) {
+ /* Cleanup: Founder must be != than successor */
+ if (!(ci->flags & CI_VERBOTEN)
+ && ci->successor == ci->founder) {
+ alog("Warning: founder and successor of %s are equal. Cleaning up.", ci->name);
+ ci->successor = NULL;
+ }
+ }
+
+ /* BotServ options */
+
+ if (ver >= 8) {
+ int n_ttb;
+
+ SAFE(read_string(&s, f));
+ if (s) {
+ ci->bi = findbot(s);
+ free(s);
+ } else
+ ci->bi = NULL;
+
+ SAFE(read_int32(&tmp32, f));
+ ci->botflags = tmp32;
+ SAFE(read_int16(&tmp16, f));
+ n_ttb = tmp16;
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (j = 0; j < n_ttb; j++) {
+ SAFE(read_int16(&tmp16, f));
+ if (j < TTB_SIZE)
+ ci->ttb[j] = (int16) tmp16;
+ }
+ for (j = n_ttb; j < TTB_SIZE; j++)
+ ci->ttb[j] = 0;
+ SAFE(read_int16(&tmp16, f));
+ ci->capsmin = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->capspercent = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->floodlines = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->floodsecs = tmp16;
+ SAFE(read_int16(&tmp16, f));
+ ci->repeattimes = tmp16;
+
+ SAFE(read_int16(&ci->bwcount, f));
+ if (ci->bwcount) {
+ ci->badwords = scalloc(ci->bwcount, sizeof(BadWord));
+ for (j = 0; j < ci->bwcount; j++) {
+ SAFE(read_int16(&ci->badwords[j].in_use, f));
+ if (ci->badwords[j].in_use) {
+ SAFE(read_string(&ci->badwords[j].word, f));
+ SAFE(read_int16(&ci->badwords[j].type, f));
+ }
+ }
+ } else {
+ ci->badwords = NULL;
+ }
+ } else {
+ ci->bi = NULL;
+ ci->botflags = 0;
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (j = 0; j < TTB_SIZE; j++)
+ ci->ttb[j] = 0;
+ ci->bwcount = 0;
+ ci->badwords = NULL;
+ }
+
+ } /* while (getc_db(f) != 0) */
+
+ *last = NULL;
+
+ } /* for (i) */
+
+ close_db(f);
+
+ /* Check for non-forbidden channels with no founder.
+ Makes also other essential tasks. */
+ for (i = 0; i < 256; i++) {
+ ChannelInfo *next;
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (!(ci->flags & CI_VERBOTEN) && !ci->founder) {
+ alog("%s: database load: Deleting founderless channel %s",
+ s_ChanServ, ci->name);
+ delchan(ci);
+ continue;
+ }
+ if (ver < 13) {
+ ChanAccess *access, *access2;
+ AutoKick *akick, *akick2;
+ int k;
+
+ if (ci->flags & CI_VERBOTEN)
+ continue;
+ /* Need to regenerate the channel count for the founder */
+ ci->founder->channelcount++;
+ /* Check for eventual double entries in access/akick lists. */
+ for (j = 0, access = ci->access; j < ci->accesscount;
+ j++, access++) {
+ if (!access->in_use)
+ continue;
+ for (k = 0, access2 = ci->access; k < j;
+ k++, access2++) {
+ if (access2->in_use && access2->nc == access->nc) {
+ alog("%s: deleting %s channel access entry of %s because it is already in the list (this is OK).", s_ChanServ, access->nc->display, ci->name);
+ memset(access, 0, sizeof(ChanAccess));
+ break;
+ }
+ }
+ }
+ for (j = 0, akick = ci->akick; j < ci->akickcount;
+ j++, akick++) {
+ if (!(akick->flags & AK_USED)
+ || !(akick->flags & AK_ISNICK))
+ continue;
+ for (k = 0, akick2 = ci->akick; k < j; k++, akick2++) {
+ if ((akick2->flags & AK_USED)
+ && (akick2->flags & AK_ISNICK)
+ && akick2->u.nc == akick->u.nc) {
+ alog("%s: deleting %s channel akick entry of %s because it is already in the list (this is OK).", s_ChanServ, akick->u.nc->display, ci->name);
+ if (akick->reason)
+ free(akick->reason);
+ if (akick->creator)
+ free(akick->creator);
+ memset(akick, 0, sizeof(AutoKick));
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+#define SAFE(x) do { \
+ if ((x) < 0) { \
+ restore_db(f); \
+ log_perror("Write error on %s", ChanDBName); \
+ if (time(NULL) - lastwarn > WarningTimeout) { \
+ anope_cmd_global(NULL, "Write error on %s: %s", ChanDBName, \
+ strerror(errno)); \
+ lastwarn = time(NULL); \
+ } \
+ return; \
+ } \
+} while (0)
+
+void save_cs_dbase(void)
+{
+ dbFILE *f;
+ int i, j;
+ ChannelInfo *ci;
+ Memo *memos;
+ static time_t lastwarn = 0;
+
+ if (!(f = open_db(s_ChanServ, ChanDBName, "w", CHAN_VERSION)))
+ return;
+
+ for (i = 0; i < 256; i++) {
+ int16 tmp16;
+
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ SAFE(write_int8(1, f));
+ SAFE(write_buffer(ci->name, f));
+ if (ci->founder)
+ SAFE(write_string(ci->founder->display, f));
+ else
+ SAFE(write_string(NULL, f));
+ if (ci->successor)
+ SAFE(write_string(ci->successor->display, f));
+ else
+ SAFE(write_string(NULL, f));
+ SAFE(write_buffer(ci->founderpass, f));
+ SAFE(write_string(ci->desc, f));
+ SAFE(write_string(ci->url, f));
+ SAFE(write_string(ci->email, f));
+ SAFE(write_int32(ci->time_registered, f));
+ SAFE(write_int32(ci->last_used, f));
+ SAFE(write_string(ci->last_topic, f));
+ SAFE(write_buffer(ci->last_topic_setter, f));
+ SAFE(write_int32(ci->last_topic_time, f));
+ SAFE(write_int32(ci->flags, f));
+ SAFE(write_string(ci->forbidby, f));
+ SAFE(write_string(ci->forbidreason, f));
+ SAFE(write_int16(ci->bantype, f));
+
+ tmp16 = CA_SIZE;
+ SAFE(write_int16(tmp16, f));
+ for (j = 0; j < CA_SIZE; j++)
+ SAFE(write_int16(ci->levels[j], f));
+
+ SAFE(write_int16(ci->accesscount, f));
+ for (j = 0; j < ci->accesscount; j++) {
+ SAFE(write_int16(ci->access[j].in_use, f));
+ if (ci->access[j].in_use) {
+ SAFE(write_int16(ci->access[j].level, f));
+ SAFE(write_string(ci->access[j].nc->display, f));
+ SAFE(write_int32(ci->access[j].last_seen, f));
+ }
+ }
+
+ SAFE(write_int16(ci->akickcount, f));
+ for (j = 0; j < ci->akickcount; j++) {
+ SAFE(write_int16(ci->akick[j].flags, f));
+ if (ci->akick[j].flags & AK_USED) {
+ if (ci->akick[j].flags & AK_ISNICK)
+ SAFE(write_string(ci->akick[j].u.nc->display, f));
+ else
+ SAFE(write_string(ci->akick[j].u.mask, f));
+ SAFE(write_string(ci->akick[j].reason, f));
+ SAFE(write_string(ci->akick[j].creator, f));
+ SAFE(write_int32(ci->akick[j].addtime, f));
+ }
+ }
+
+ SAFE(write_int32(ci->mlock_on, f));
+ SAFE(write_int32(ci->mlock_off, f));
+ SAFE(write_int32(ci->mlock_limit, f));
+ SAFE(write_string(ci->mlock_key, f));
+ if (ircd->fmode) {
+ SAFE(write_string(ci->mlock_flood, f));
+ } else {
+ SAFE(write_string(NULL, f));
+ }
+ if (ircd->Lmode) {
+ SAFE(write_string(ci->mlock_redirect, f));
+ } else {
+ SAFE(write_string(NULL, f));
+ }
+ SAFE(write_int16(ci->memos.memocount, f));
+ SAFE(write_int16(ci->memos.memomax, f));
+ memos = ci->memos.memos;
+ for (j = 0; j < ci->memos.memocount; j++, memos++) {
+ SAFE(write_int32(memos->number, f));
+ SAFE(write_int16(memos->flags, f));
+ SAFE(write_int32(memos->time, f));
+ SAFE(write_buffer(memos->sender, f));
+ SAFE(write_string(memos->text, f));
+ }
+
+ SAFE(write_string(ci->entry_message, f));
+
+ if (ci->bi)
+ SAFE(write_string(ci->bi->nick, f));
+ else
+ SAFE(write_string(NULL, f));
+
+ SAFE(write_int32(ci->botflags, f));
+
+ tmp16 = TTB_SIZE;
+ SAFE(write_int16(tmp16, f));
+ for (j = 0; j < TTB_SIZE; j++)
+ SAFE(write_int16(ci->ttb[j], f));
+
+ SAFE(write_int16(ci->capsmin, f));
+ SAFE(write_int16(ci->capspercent, f));
+ SAFE(write_int16(ci->floodlines, f));
+ SAFE(write_int16(ci->floodsecs, f));
+ SAFE(write_int16(ci->repeattimes, f));
+
+ SAFE(write_int16(ci->bwcount, f));
+ for (j = 0; j < ci->bwcount; j++) {
+ SAFE(write_int16(ci->badwords[j].in_use, f));
+ if (ci->badwords[j].in_use) {
+ SAFE(write_string(ci->badwords[j].word, f));
+ SAFE(write_int16(ci->badwords[j].type, f));
+ }
+ }
+ } /* for (chanlists[i]) */
+
+ SAFE(write_int8(0, f));
+
+ } /* for (i) */
+
+ close_db(f);
+
+}
+
+#undef SAFE
+
+/*************************************************************************/
+
+void save_cs_rdb_dbase(void)
+{
+#ifdef USE_RDB
+ int i;
+ ChannelInfo *ci;
+
+ if (!rdb_open())
+ return;
+
+ if (rdb_tag_table("anope_cs_info") == 0) {
+ alog("Unable to tag table 'anope_cs_info' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table("anope_cs_access") == 0) {
+ alog("Unable to tag table 'anope_cs_access' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table("anope_cs_levels") == 0) {
+ alog("Unable to tag table 'anope_cs_levels' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table("anope_cs_akicks") == 0) {
+ alog("Unable to tag table 'anope_cs_akicks' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table("anope_cs_badwords") == 0) {
+ alog("Unable to tag table 'anope_cs_badwords' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table("anope_cs_ttb") == 0) {
+ alog("Unable to tag table 'anope_cs_ttb' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_tag_table_where("anope_ms_info", "serv='CHAN'") == 0) {
+ alog("Unable to tag table 'anope_ms_info' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = ci->next) {
+ if (rdb_save_cs_info(ci) == 0) {
+ alog("Unable to save ChanInfo for %s - ChanServ RDB save failed.", ci->name);
+ rdb_close();
+ return;
+ }
+ } /* for (chanlists[i]) */
+ } /* for (i) */
+
+ if (rdb_clean_table("anope_cs_info") == 0) {
+ alog("Unable to clean table 'anope_cs_info' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table("anope_cs_access") == 0) {
+ alog("Unable to clean table 'anope_cs_access' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table("anope_cs_levels") == 0) {
+ alog("Unable to clean table 'anope_cs_levels' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table("anope_cs_akicks") == 0) {
+ alog("Unable to clean table 'anope_cs_akicks' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table("anope_cs_badwords") == 0) {
+ alog("Unable to clean table 'anope_cs_badwords' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table("anope_cs_ttb") == 0) {
+ alog("Unable to clean table 'anope_cs_ttb' - ChanServ RDB save failed.");
+ rdb_close();
+ return;
+ }
+ if (rdb_clean_table_where("anope_ms_info", "serv='CHAN'") == 0)
+ alog("Unable to clean table 'anope_ms_info' - ChanServ RDB save failed.");
+
+ rdb_close();
+#endif
+}
+
+/*************************************************************************/
+
+/* Check the current modes on a channel; if they conflict with a mode lock,
+ * fix them.
+ *
+ * Also check to make sure that every mode set or unset is allowed by the
+ * defcon mlock settings. This is more important than any normal channel
+ * mlock'd mode. --gdex (21-04-07)
+ */
+
+void check_modes(Channel * c)
+{
+ char modebuf[64], argbuf[BUFSIZE], *end = modebuf, *end2 = argbuf;
+ uint32 modes;
+ ChannelInfo *ci;
+ CBModeInfo *cbmi;
+ CBMode *cbm;
+
+ if (!c) {
+ if (debug) {
+ alog("debug: check_modes called with NULL values");
+ }
+ return;
+ }
+
+ if (c->bouncy_modes)
+ return;
+
+ /* Check for mode bouncing */
+ if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) {
+ anope_cmd_global(NULL,
+ "Warning: unable to set modes on channel %s. "
+ "Are your servers' U:lines configured correctly?",
+ c->name);
+ alog("%s: Bouncy modes on channel %s", s_ChanServ, c->name);
+ c->bouncy_modes = 1;
+ return;
+ }
+
+ if (c->chanserv_modetime != time(NULL)) {
+ c->chanserv_modecount = 0;
+ c->chanserv_modetime = time(NULL);
+ }
+ c->chanserv_modecount++;
+
+ /* Check if the channel is registered; if not remove mode -r */
+ if (!(ci = c->ci)) {
+ if (ircd->regmode) {
+ if (c->mode & ircd->regmode) {
+ c->mode &= ~ircd->regmode;
+ anope_cmd_mode(whosends(ci), c->name, "-r");
+ }
+ }
+ return;
+ }
+
+ /* Initialize te modes-var to set all modes not set yet but which should
+ * be set as by mlock and defcon.
+ */
+ modes = ~c->mode & ci->mlock_on;
+ if (DefConModesSet)
+ modes |= (~c->mode & DefConModesOn);
+
+ /* Initialize the buffers */
+ *end++ = '+';
+ cbmi = cbmodeinfos;
+
+ do {
+ if (modes & cbmi->flag) {
+ *end++ = cbmi->mode;
+ c->mode |= cbmi->flag;
+
+ /* Add the eventual parameter and modify the Channel structure */
+ if (cbmi->getvalue && cbmi->csgetvalue) {
+ char *value;
+ /* Check if it's a defcon or mlock mode */
+ if (DefConModesOn & cbmi->flag)
+ value = cbmi->csgetvalue(&DefConModesCI);
+ else
+ value = cbmi->csgetvalue(ci);
+
+ cbm = &cbmodes[(int) cbmi->mode];
+ cbm->setvalue(c, value);
+
+ if (value) {
+ *end2++ = ' ';
+ while (*value)
+ *end2++ = *value++;
+ }
+ }
+ } else if (cbmi->getvalue && cbmi->csgetvalue
+ && ((ci->mlock_on & cbmi->flag)
+ || (DefConModesOn & cbmi->flag))
+ && (c->mode & cbmi->flag)) {
+ char *value = cbmi->getvalue(c);
+ char *csvalue;
+
+ /* Check if it's a defcon or mlock mode */
+ if (DefConModesOn & cbmi->flag)
+ csvalue = cbmi->csgetvalue(&DefConModesCI);
+ else
+ csvalue = cbmi->csgetvalue(ci);
+
+ /* Lock and actual values don't match, so fix the mode */
+ if (value && csvalue && strcmp(value, csvalue)) {
+ *end++ = cbmi->mode;
+
+ cbm = &cbmodes[(int) cbmi->mode];
+ cbm->setvalue(c, csvalue);
+
+ *end2++ = ' ';
+ while (*csvalue)
+ *end2++ = *csvalue++;
+ }
+ }
+ } while ((++cbmi)->flag != 0);
+
+ if (*(end - 1) == '+')
+ end--;
+
+ modes = c->mode & ci->mlock_off;
+ if (DefConModesSet)
+ modes |= (~c->mode & DefConModesOff);
+
+ if (modes) {
+ *end++ = '-';
+ cbmi = cbmodeinfos;
+
+ do {
+ if (modes & cbmi->flag) {
+ *end++ = cbmi->mode;
+ c->mode &= ~cbmi->flag;
+
+ /* Add the eventual parameter and clean up the Channel structure */
+ if (cbmi->getvalue) {
+ cbm = &cbmodes[(int) cbmi->mode];
+
+ if (!(cbm->flags & CBM_MINUS_NO_ARG)) {
+ char *value = cbmi->getvalue(c);
+
+ if (value) {
+ *end2++ = ' ';
+ while (*value)
+ *end2++ = *value++;
+ }
+ }
+
+ cbm->setvalue(c, NULL);
+ }
+ }
+ } while ((++cbmi)->flag != 0);
+ }
+
+ if (end == modebuf)
+ return;
+
+ *end = 0;
+ *end2 = 0;
+
+ anope_cmd_mode(whosends(ci), c->name, "%s%s", modebuf,
+ (end2 == argbuf ? "" : argbuf));
+}
+
+/*************************************************************************/
+
+int check_valid_admin(User * user, Channel * chan, int servermode)
+{
+ if (!chan || !chan->ci)
+ return 1;
+
+ if (!ircd->admin) {
+ return 0;
+ }
+
+ /* They will be kicked; no need to deop, no need to update our internal struct too */
+ if (chan->ci->flags & CI_VERBOTEN)
+ return 0;
+
+ if (servermode && !check_access(user, chan->ci, CA_AUTOPROTECT)) {
+ notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
+ anope_cmd_mode(whosends(chan->ci), chan->name, "%s %s",
+ ircd->adminunset, user->nick);
+ return 0;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP)) {
+ anope_cmd_mode(whosends(chan->ci), chan->name, "%s %s",
+ ircd->adminunset, user->nick);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Check whether a user is allowed to be opped on a channel; if they
+ * aren't, deop them. If serverop is 1, the +o was done by a server.
+ * Return 1 if the user is allowed to be opped, 0 otherwise. */
+
+int check_valid_op(User * user, Channel * chan, int servermode)
+{
+ char *tmp;
+ if (!chan || !chan->ci)
+ return 1;
+
+ /* They will be kicked; no need to deop, no need to update our internal struct too */
+ if (chan->ci->flags & CI_VERBOTEN)
+ return 0;
+
+ if (servermode && !check_access(user, chan->ci, CA_AUTOOP)) {
+ notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
+ if (ircd->halfop) {
+ if (ircd->owner && ircd->protect) {
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ tmp = stripModePrefix(ircd->ownerunset);
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "%so%s %s %s %s", ircd->adminunset,
+ tmp, user->nick,
+ user->nick, user->nick);
+ free(tmp);
+ } else {
+ tmp = stripModePrefix(ircd->ownerunset);
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "%sho%s %s %s %s %s",
+ ircd->adminunset, tmp,
+ user->nick, user->nick, user->nick,
+ user->nick);
+ free(tmp);
+ }
+ } else if (!ircd->owner && ircd->protect) {
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "%so %s %s", ircd->adminunset,
+ user->nick, user->nick);
+ } else {
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "%soh %s %s %s", ircd->adminunset,
+ user->nick, user->nick, user->nick);
+ }
+ } else {
+ if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
+ anope_cmd_mode(whosends(chan->ci), chan->name, "-o %s",
+ user->nick);
+ } else {
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "-ho %s %s", user->nick, user->nick);
+ }
+ }
+ } else {
+ anope_cmd_mode(whosends(chan->ci), chan->name, "-o %s",
+ user->nick);
+ }
+ return 0;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP)) {
+ if (ircd->halfop) {
+ if (ircd->owner && ircd->protect) {
+ tmp = stripModePrefix(ircd->ownerunset);
+ anope_cmd_mode(whosends(chan->ci), chan->name,
+ "%sho%s %s %s %s %s", ircd->adminunset,
+ tmp, user->nick, user->nick,
+ user->nick, user->nick);
+ free(tmp);
+ } else {
+ anope_cmd_mode(whosends(chan->ci), chan->name, "-ho %s %s",
+ user->nick, user->nick);
+ }
+ } else {
+ anope_cmd_mode(whosends(chan->ci), chan->name, "-o %s",
+ user->nick);
+ }
+ return 0;
+ }
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Check whether a user should be opped on a channel, and if so, do it.
+ * Return 1 if the user was opped, 0 otherwise. (Updates the channel's
+ * last used time if the user was opped.) */
+
+int check_should_op(User * user, char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if ((ci->flags & CI_SECURE) && !nick_identified(user))
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOOP)) {
+ anope_cmd_mode(whosends(ci), chan, "+o %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Check whether a user should be voiced on a channel, and if so, do it.
+ * Return 1 if the user was voiced, 0 otherwise. */
+
+int check_should_voice(User * user, char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if ((ci->flags & CI_SECURE) && !nick_identified(user))
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOVOICE)) {
+ anope_cmd_mode(whosends(ci), chan, "+v %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int check_should_halfop(User * user, char *chan)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOHALFOP)) {
+ anope_cmd_mode(whosends(ci), chan, "+h %s", user->nick);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int check_should_owner(User * user, char *chan)
+{
+ char *tmp;
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (((ci->flags & CI_SECUREFOUNDER) && is_real_founder(user, ci))
+ || (!(ci->flags & CI_SECUREFOUNDER) && is_founder(user, ci))) {
+ tmp = stripModePrefix(ircd->ownerset);
+ anope_cmd_mode(whosends(ci), chan, "+o%s %s %s", tmp, user->nick,
+ user->nick);
+ free(tmp);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+int check_should_protect(User * user, char *chan)
+{
+ char *tmp;
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
+ return 0;
+
+ if (check_access(user, ci, CA_AUTOPROTECT)) {
+ tmp = stripModePrefix(ircd->adminset);
+ anope_cmd_mode(whosends(ci), chan, "+o%s %s %s", tmp, user->nick,
+ user->nick);
+ free(tmp);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Tiny helper routine to get ChanServ out of a channel after it went in. */
+
+static void timeout_leave(Timeout * to)
+{
+ char *chan = to->data;
+ ChannelInfo *ci = cs_findchan(chan);
+
+ if (ci) /* Check cos the channel may be dropped in the meantime */
+ ci->flags &= ~CI_INHABIT;
+
+ anope_cmd_part(s_ChanServ, chan, NULL);
+ free(to->data);
+}
+
+
+/* Check whether a user is permitted to be on a channel. If so, return 0;
+ * else, kickban the user with an appropriate message (could be either
+ * AKICK or restricted access) and return 1. Note that this is called
+ * _before_ the user is added to internal channel lists (so do_kick() is
+ * not called). The channel TS must be given for a new channel.
+ */
+
+int check_kick(User * user, char *chan, time_t chants)
+{
+ ChannelInfo *ci = cs_findchan(chan);
+ Channel *c;
+ AutoKick *akick;
+ int i, set_modes = 0;
+ NickCore *nc;
+ char *av[4];
+ int ac;
+ char buf[BUFSIZE];
+ char mask[BUFSIZE];
+ const char *reason;
+ Timeout *t;
+
+ if (!ci)
+ return 0;
+
+ if (user->isSuperAdmin == 1)
+ return 0;
+
+ if (ci->flags & CI_VERBOTEN) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason =
+ ci->forbidreason ? ci->forbidreason : getstring(user->na,
+ CHAN_MAY_NOT_BE_USED);
+ set_modes = 1;
+ goto kick;
+ }
+
+ if (ci->flags & CI_SUSPENDED) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason =
+ ci->forbidreason ? ci->forbidreason : getstring(user->na,
+ CHAN_MAY_NOT_BE_USED);
+ set_modes = 1;
+ goto kick;
+ }
+
+ if (nick_recognized(user))
+ nc = user->na->nc;
+ else
+ nc = NULL;
+
+ /*
+ * Before we go through akick lists, see if they're excepted FIRST
+ * We cannot kick excempted users that are akicked or not on the channel access list
+ * as that will start services <-> server wars which ends up as a DoS against services.
+ *
+ * UltimateIRCd 3.x at least informs channel staff when a joining user is matching an exempt.
+ */
+ if (ircd->except && is_excepted(ci, user) == 1) {
+ return 0;
+ }
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED))
+ continue;
+ if ((akick->flags & AK_ISNICK && akick->u.nc == nc)
+ || (!(akick->flags & AK_ISNICK)
+ && match_usermask(akick->u.mask, user))) {
+ if (debug >= 2)
+ alog("debug: %s matched akick %s", user->nick,
+ (akick->flags & AK_ISNICK) ? akick->u.nc->
+ display : akick->u.mask);
+ if (akick->flags & AK_ISNICK)
+ get_idealban(ci, user, mask, sizeof(mask));
+ else
+ strcpy(mask, akick->u.mask);
+ reason = akick->reason ? akick->reason : CSAutokickReason;
+ goto kick;
+ }
+ }
+
+ if (is_ulined(user->server->name)) {
+ return 0;
+ }
+
+ if (check_access(user, ci, CA_NOJOIN)) {
+ get_idealban(ci, user, mask, sizeof(mask));
+ reason = getstring(user->na, CHAN_NOT_ALLOWED_TO_JOIN);
+ goto kick;
+ }
+
+ return 0;
+
+ kick:
+ if (debug)
+ alog("debug: channel: AutoKicking %s!%s@%s from %s", user->nick,
+ user->username, user->host, chan);
+
+ /* Remember that the user has not been added to our channel user list
+ * yet, so we check whether the channel does not exist OR has no user
+ * on it (before SJOIN would have created the channel structure, while
+ * JOIN would not). */
+ /* Don't check for CI_INHABIT before for the Channel record cos else
+ * c may be NULL even if it exists */
+ if ((!(c = findchan(chan)) || c->usercount == 0)
+ && !(ci->flags & CI_INHABIT)) {
+ anope_cmd_join(s_ChanServ, chan, (c ? c->creation_time : chants));
+ /* Lets hide the channel from /list just incase someone does /list
+ * while we are here. - katsklaw
+ * We don't want to block users from joining a legit chan though.. - Viper
+ */
+ if (set_modes)
+ anope_cmd_mode(s_ChanServ, chan, "+ntsi");
+ t = add_timeout(CSInhabit, timeout_leave, 0);
+ t->data = sstrdup(chan);
+ ci->flags |= CI_INHABIT;
+ }
+
+ if (c) {
+ if (ircdcap->tsmode) {
+ snprintf(buf, BUFSIZE - 1, "%ld", (long int) time(NULL));
+ av[0] = chan;
+ av[1] = buf;
+ av[2] = sstrdup("+b");
+ av[3] = mask;
+ ac = 4;
+ } else {
+ av[0] = chan;
+ av[1] = sstrdup("+b");
+ av[2] = mask;
+ ac = 3;
+ }
+
+ do_cmode(whosends(ci), ac, av);
+
+ if (ircdcap->tsmode)
+ free(av[2]);
+ else
+ free(av[1]);
+ }
+
+ anope_cmd_mode(whosends(ci), chan, "+b %s", mask);
+ anope_cmd_kick(whosends(ci), chan, user->nick, "%s", reason);
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Record the current channel topic in the ChannelInfo structure. */
+
+void record_topic(const char *chan)
+{
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (readonly)
+ return;
+
+ c = findchan(chan);
+ if (!c || !(ci = c->ci))
+ return;
+
+ if (ci->last_topic)
+ free(ci->last_topic);
+
+ if (c->topic)
+ ci->last_topic = sstrdup(c->topic);
+ else
+ ci->last_topic = NULL;
+
+ strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
+ ci->last_topic_time = c->topic_time;
+}
+
+/*************************************************************************/
+
+/* Restore the topic in a channel when it's created, if we should. */
+
+void restore_topic(char *chan)
+{
+ Channel *c = findchan(chan);
+ ChannelInfo *ci;
+
+ if (!c || !(ci = c->ci))
+ return;
+ /* We can be sure that the topic will be in sync when we return -GD */
+ c->topic_sync = 1;
+ if (!(ci->flags & CI_KEEPTOPIC)) {
+ /* We need to reset the topic here, since it's currently empty and
+ * should be updated with a TOPIC from the IRCd soon. -GD
+ */
+ ci->last_topic = NULL;
+ strscpy(ci->last_topic_setter, whosends(ci), NICKMAX);
+ ci->last_topic_time = time(NULL);
+ return;
+ }
+ if (c->topic)
+ free(c->topic);
+ if (ci->last_topic) {
+ c->topic = sstrdup(ci->last_topic);
+ strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
+ c->topic_time = ci->last_topic_time;
+ } else {
+ c->topic = NULL;
+ strscpy(c->topic_setter, whosends(ci), NICKMAX);
+ }
+ if (ircd->join2set) {
+ if (whosends(ci) == s_ChanServ) {
+ anope_cmd_join(s_ChanServ, chan, c->creation_time);
+ anope_cmd_mode(NULL, chan, "+o %s", s_ChanServ);
+ }
+ }
+ anope_cmd_topic(whosends(ci), c->name, c->topic_setter,
+ c->topic ? c->topic : "", c->topic_time);
+ if (ircd->join2set) {
+ if (whosends(ci) == s_ChanServ) {
+ anope_cmd_part(s_ChanServ, c->name, NULL);
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* See if the topic is locked on the given channel, and return 1 (and fix
+ * the topic) if so. */
+
+int check_topiclock(Channel * c, time_t topic_time)
+{
+ ChannelInfo *ci;
+
+ if (!c) {
+ if (debug) {
+ alog("debug: check_topiclock called with NULL values");
+ }
+ return 0;
+ }
+
+ if (!(ci = c->ci) || !(ci->flags & CI_TOPICLOCK))
+ return 0;
+
+ if (c->topic)
+ free(c->topic);
+ if (ci->last_topic) {
+ c->topic = sstrdup(ci->last_topic);
+ strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
+ } else {
+ c->topic = NULL;
+ /* Bot assigned & Symbiosis ON?, the bot will set the topic - doc */
+ /* Altough whosends() also checks for BSMinUsers -GD */
+ strscpy(c->topic_setter, whosends(ci), NICKMAX);
+ }
+
+ if (ircd->topictsforward) {
+ /* Because older timestamps are rejected */
+ /* Some how the topic_time from do_topic is 0 set it to current + 1 */
+ if (!topic_time) {
+ c->topic_time = time(NULL) + 1;
+ } else {
+ c->topic_time = topic_time + 1;
+ }
+ } else {
+ /* If no last topic, we can't use last topic time! - doc */
+ if (ci->last_topic)
+ c->topic_time = ci->last_topic_time;
+ else
+ c->topic_time = time(NULL) + 1;
+ }
+
+ if (ircd->join2set) {
+ if (whosends(ci) == s_ChanServ) {
+ anope_cmd_join(s_ChanServ, c->name, c->creation_time);
+ anope_cmd_mode(NULL, c->name, "+o %s", s_ChanServ);
+ }
+ }
+
+ anope_cmd_topic(whosends(ci), c->name, c->topic_setter,
+ c->topic ? c->topic : "", c->topic_time);
+
+ if (ircd->join2set) {
+ if (whosends(ci) == s_ChanServ) {
+ anope_cmd_part(s_ChanServ, c->ci->name, NULL);
+ }
+ }
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Remove all channels which have expired. */
+
+void expire_chans()
+{
+ ChannelInfo *ci, *next;
+ int i;
+ time_t now = time(NULL);
+
+ if (!CSExpire)
+ return;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (!ci->c && now - ci->last_used >= CSExpire
+ && !(ci->
+ flags & (CI_VERBOTEN | CI_NO_EXPIRE | CI_SUSPENDED)))
+ {
+ send_event(EVENT_CHAN_EXPIRE, 1, ci->name);
+ alog("Expiring channel %s (founder: %s)", ci->name,
+ (ci->founder ? ci->founder->display : "(none)"));
+ delchan(ci);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Remove a (deleted or expired) nickname from all channel lists. */
+
+void cs_remove_nick(const NickCore * nc)
+{
+ int i, j, k;
+ ChannelInfo *ci, *next;
+ ChanAccess *ca;
+ AutoKick *akick;
+
+ for (i = 0; i < 256; i++) {
+ for (ci = chanlists[i]; ci; ci = next) {
+ next = ci->next;
+ if (ci->founder == nc) {
+ if (ci->successor) {
+ NickCore *nc2 = ci->successor;
+ if (!nick_is_services_admin(nc2) && nc2->channelmax > 0
+ && nc2->channelcount >= nc2->channelmax) {
+ alog("%s: Successor (%s) of %s owns too many channels, " "deleting channel", s_ChanServ, nc2->display, ci->name);
+ delchan(ci);
+ continue;
+ } else {
+ alog("%s: Transferring foundership of %s from deleted " "nick %s to successor %s", s_ChanServ, ci->name, nc->display, nc2->display);
+ ci->founder = nc2;
+ ci->successor = NULL;
+ nc2->channelcount++;
+ }
+ } else {
+ alog("%s: Deleting channel %s owned by deleted nick %s", s_ChanServ, ci->name, nc->display);
+ if (ircd->regmode) {
+ /* Maybe move this to delchan() ? */
+ if ((ci->c) && (ci->c->mode & ircd->regmode)) {
+ ci->c->mode &= ~ircd->regmode;
+ anope_cmd_mode(whosends(ci), ci->name, "-r");
+ }
+ }
+
+ delchan(ci);
+ continue;
+ }
+ }
+
+ if (ci->successor == nc)
+ ci->successor = NULL;
+
+ for (ca = ci->access, j = ci->accesscount; j > 0; ca++, j--) {
+ if (ca->in_use && ca->nc == nc) {
+ ca->in_use = 0;
+ ca->nc = NULL;
+ }
+ }
+
+ for (akick = ci->akick, j = 0; j < ci->akickcount; akick++, j++) {
+ if ((akick->flags & AK_USED) && (akick->flags & AK_ISNICK)
+ && akick->u.nc == nc) {
+ if (akick->creator) {
+ free(akick->creator);
+ akick->creator = NULL;
+ }
+ if (akick->reason) {
+ free(akick->reason);
+ akick->reason = NULL;
+ }
+ akick->flags = 0;
+ akick->addtime = 0;
+ akick->u.nc = NULL;
+
+ /* Only one occurance can exist in every akick list.. ~ Viper */
+ break;
+ }
+ }
+
+ /* Are there any akicks behind us?
+ * If so, move all following akicks.. ~ Viper */
+ if (j < ci->akickcount - 1) {
+ for (k = j + 1; k < ci->akickcount; j++, k++) {
+ if (ci->akick[k].flags & AK_USED) {
+ /* Move the akick one place ahead and clear the original */
+ if (ci->akick[k].flags & AK_ISNICK) {
+ ci->akick[j].u.nc = ci->akick[k].u.nc;
+ ci->akick[k].u.nc = NULL;
+ } else {
+ ci->akick[j].u.mask = sstrdup(ci->akick[k].u.mask);
+ free(ci->akick[k].u.mask);
+ ci->akick[k].u.mask = NULL;
+ }
+
+ if (ci->akick[k].reason) {
+ ci->akick[j].reason = sstrdup(ci->akick[k].reason);
+ free(ci->akick[k].reason);
+ ci->akick[k].reason = NULL;
+ } else
+ ci->akick[j].reason = NULL;
+
+ ci->akick[j].creator = sstrdup(ci->akick[k].creator);
+ free(ci->akick[k].creator);
+ ci->akick[k].creator = NULL;
+
+ ci->akick[j].flags = ci->akick[k].flags;
+ ci->akick[k].flags = 0;
+
+ ci->akick[j].addtime = ci->akick[k].addtime;
+ ci->akick[k].addtime = 0;
+ }
+ }
+ }
+
+ /* After moving only the last entry should still be empty.
+ * Free the place no longer in use... ~ Viper */
+ ci->akickcount = j;
+ ci->akick = srealloc(ci->akick,sizeof(AutoKick) * ci->akickcount);
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Removes any reference to a bot */
+
+void cs_remove_bot(const BotInfo * bi)
+{
+ int i;
+ ChannelInfo *ci;
+
+ for (i = 0; i < 256; i++)
+ for (ci = chanlists[i]; ci; ci = ci->next)
+ if (ci->bi == bi)
+ ci->bi = NULL;
+}
+
+/*************************************************************************/
+
+/* Return the ChannelInfo structure for the given channel, or NULL if the
+ * channel isn't registered. */
+
+ChannelInfo *cs_findchan(const char *chan)
+{
+ ChannelInfo *ci;
+
+ if (!chan || !*chan) {
+ if (debug) {
+ alog("debug: cs_findchan() called with NULL values");
+ }
+ return NULL;
+ }
+
+ for (ci = chanlists[(unsigned char) tolower(chan[1])]; ci;
+ ci = ci->next) {
+ if (stricmp(ci->name, chan) == 0)
+ return ci;
+ }
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Return 1 if the user's access level on the given channel falls into the
+ * given category, 0 otherwise. Note that this may seem slightly confusing
+ * in some cases: for example, check_access(..., CA_NOJOIN) returns true if
+ * the user does _not_ have access to the channel (i.e. matches the NOJOIN
+ * criterion). */
+
+int check_access(User * user, ChannelInfo * ci, int what)
+{
+ int level;
+ int limit;
+
+ if (!user || !ci) {
+ return 0;
+ }
+
+ level = get_access(user, ci);
+ limit = ci->levels[what];
+
+ /* Resetting the last used time */
+ if (level > 0)
+ ci->last_used = time(NULL);
+
+ if (level >= ACCESS_FOUNDER)
+ return (what == CA_AUTODEOP || what == CA_NOJOIN) ? 0 : 1;
+ /* Hacks to make flags work */
+ if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS) && level == 0)
+ return 1;
+ if (limit == ACCESS_INVALID)
+ return 0;
+ if (what == CA_AUTODEOP || what == CA_NOJOIN)
+ return level <= ci->levels[what];
+ else
+ return level >= ci->levels[what];
+}
+
+/*************************************************************************/
+/*********************** ChanServ private routines ***********************/
+/*************************************************************************/
+
+/* Insert a channel alphabetically into the database. */
+
+void alpha_insert_chan(ChannelInfo * ci)
+{
+ ChannelInfo *ptr, *prev;
+ char *chan;
+
+ if (!ci) {
+ if (debug) {
+ alog("debug: alpha_insert_chan called with NULL values");
+ }
+ return;
+ }
+
+ chan = ci->name;
+
+ for (prev = NULL, ptr = chanlists[(unsigned char) tolower(chan[1])];
+ ptr != NULL && stricmp(ptr->name, chan) < 0;
+ prev = ptr, ptr = ptr->next);
+ ci->prev = prev;
+ ci->next = ptr;
+ if (!prev)
+ chanlists[(unsigned char) tolower(chan[1])] = ci;
+ else
+ prev->next = ci;
+ if (ptr)
+ ptr->prev = ci;
+}
+
+/*************************************************************************/
+
+/* Add a channel to the database. Returns a pointer to the new ChannelInfo
+ * structure if the channel was successfully registered, NULL otherwise.
+ * Assumes channel does not already exist. */
+
+ChannelInfo *makechan(const char *chan)
+{
+ int i;
+ ChannelInfo *ci;
+
+ ci = scalloc(sizeof(ChannelInfo), 1);
+ strscpy(ci->name, chan, CHANMAX);
+ ci->time_registered = time(NULL);
+ reset_levels(ci);
+ ci->ttb = scalloc(2 * TTB_SIZE, 1);
+ for (i = 0; i < TTB_SIZE; i++)
+ ci->ttb[i] = 0;
+ alpha_insert_chan(ci);
+ return ci;
+}
+
+/*************************************************************************/
+
+/* Remove a channel from the ChanServ database. Return 1 on success, 0
+ * otherwise. */
+
+int delchan(ChannelInfo * ci)
+{
+ int i;
+ NickCore *nc;
+ User *u;
+ struct u_chaninfolist *cilist, *cilist_next;
+
+ if (!ci) {
+ if (debug) {
+ alog("debug: delchan called with NULL values");
+ }
+ return 0;
+ }
+
+ nc = ci->founder;
+
+ if (debug >= 2) {
+ alog("debug: delchan removing %s", ci->name);
+ }
+
+ if (ci->bi) {
+ ci->bi->chancount--;
+ }
+
+ if (debug >= 2) {
+ alog("debug: delchan top of removing the bot");
+ }
+ if (ci->c) {
+ if (ci->bi && ci->c->usercount >= BSMinUsers) {
+ anope_cmd_part(ci->bi->nick, ci->c->name, NULL);
+ }
+ ci->c->ci = NULL;
+ }
+ if (debug >= 2) {
+ alog("debug: delchan() Bot has been removed moving on");
+ }
+
+ if (debug >= 2) {
+ alog("debug: delchan() founder cleanup");
+ }
+ for (i = 0; i < 1024; i++) {
+ for (u = userlist[i]; u; u = u->next) {
+ cilist = u->founder_chans;
+ while (cilist) {
+ cilist_next = cilist->next;
+ if (cilist->chan == ci) {
+ if (debug)
+ alog("debug: Dropping founder login of %s for %s",
+ u->nick, ci->name);
+ if (cilist->next)
+ cilist->next->prev = cilist->prev;
+ if (cilist->prev)
+ cilist->prev->next = cilist->next;
+ else
+ u->founder_chans = cilist->next;
+ free(cilist);
+ }
+ cilist = cilist_next;
+ }
+ }
+ }
+ if (debug >= 2) {
+ alog("debug: delchan() founder cleanup done");
+ }
+
+ if (ci->next)
+ ci->next->prev = ci->prev;
+ if (ci->prev)
+ ci->prev->next = ci->next;
+ else
+ chanlists[(unsigned char) tolower(ci->name[1])] = ci->next;
+ if (ci->desc)
+ free(ci->desc);
+ if (ci->url)
+ free(ci->url);
+ if (ci->email)
+ free(ci->email);
+ if (ci->entry_message)
+ free(ci->entry_message);
+
+ if (ci->mlock_key)
+ free(ci->mlock_key);
+ if (ircd->fmode) {
+ if (ci->mlock_flood)
+ free(ci->mlock_flood);
+ }
+ if (ircd->Lmode) {
+ if (ci->mlock_redirect)
+ free(ci->mlock_redirect);
+ }
+ if (ci->last_topic)
+ free(ci->last_topic);
+ if (ci->forbidby)
+ free(ci->forbidby);
+ if (ci->forbidreason)
+ free(ci->forbidreason);
+ if (ci->access)
+ free(ci->access);
+ if (debug >= 2) {
+ alog("debug: delchan() top of the akick list");
+ }
+ for (i = 0; i < ci->akickcount; i++) {
+ if (!(ci->akick[i].flags & AK_ISNICK) && ci->akick[i].u.mask)
+ free(ci->akick[i].u.mask);
+ if (ci->akick[i].reason)
+ free(ci->akick[i].reason);
+ if (ci->akick[i].creator)
+ free(ci->akick[i].creator);
+ }
+ if (debug >= 2) {
+ alog("debug: delchan() done with the akick list");
+ }
+ if (ci->akick)
+ free(ci->akick);
+ if (ci->levels)
+ free(ci->levels);
+ if (debug >= 2) {
+ alog("debug: delchan() top of the memo list");
+ }
+ if (ci->memos.memos) {
+ for (i = 0; i < ci->memos.memocount; i++) {
+ if (ci->memos.memos[i].text)
+ free(ci->memos.memos[i].text);
+ moduleCleanStruct(&ci->memos.memos[i].moduleData);
+ }
+ free(ci->memos.memos);
+ }
+ if (debug >= 2) {
+ alog("debug: delchan() done with the memo list");
+ }
+ if (ci->ttb)
+ free(ci->ttb);
+
+ if (debug >= 2) {
+ alog("debug: delchan() top of the badword list");
+ }
+ for (i = 0; i < ci->bwcount; i++) {
+ if (ci->badwords[i].word)
+ free(ci->badwords[i].word);
+ }
+ if (ci->badwords)
+ free(ci->badwords);
+ if (debug >= 2) {
+ alog("debug: delchan() done with the badword list");
+ }
+
+
+ if (debug >= 2) {
+ alog("debug: delchan() calling on moduleCleanStruct()");
+ }
+ moduleCleanStruct(&ci->moduleData);
+
+ free(ci);
+ if (nc)
+ nc->channelcount--;
+
+ if (debug >= 2) {
+ alog("debug: delchan() all done");
+ }
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Reset channel access level values to their default state. */
+
+void reset_levels(ChannelInfo * ci)
+{
+ int i;
+
+ if (!ci) {
+ if (debug) {
+ alog("debug: reset_levels called with NULL values");
+ }
+ return;
+ }
+
+ if (ci->levels)
+ free(ci->levels);
+ ci->levels = scalloc(CA_SIZE * sizeof(*ci->levels), 1);
+ for (i = 0; def_levels[i][0] >= 0; i++)
+ ci->levels[def_levels[i][0]] = def_levels[i][1];
+}
+
+/*************************************************************************/
+
+/* Does the given user have founder access to the channel? */
+
+int is_founder(User * user, ChannelInfo * ci)
+{
+ if (!user || !ci) {
+ return 0;
+ }
+
+ if (user->isSuperAdmin) {
+ return 1;
+ }
+
+ if (user->na && user->na->nc == ci->founder) {
+ if ((nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
+ return 1;
+ }
+ if (is_identified(user, ci))
+ return 1;
+ return 0;
+}
+
+/*************************************************************************/
+
+int is_real_founder(User * user, ChannelInfo * ci)
+{
+ if (user->isSuperAdmin) {
+ return 1;
+ }
+
+ if (user->na && user->na->nc == ci->founder) {
+ if ((nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
+ return 1;
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Has the given user password-identified as founder for the channel? */
+
+int is_identified(User * user, ChannelInfo * ci)
+{
+ struct u_chaninfolist *c;
+
+ for (c = user->founder_chans; c; c = c->next) {
+ if (c->chan == ci)
+ return 1;
+ }
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Returns the ChanAccess entry for an user */
+
+ChanAccess *get_access_entry(NickCore * nc, ChannelInfo * ci)
+{
+ ChanAccess *access;
+ int i;
+
+ if (!ci || !nc) {
+ return NULL;
+ }
+
+ for (access = ci->access, i = 0; i < ci->accesscount; access++, i++)
+ if (access->in_use && access->nc == nc)
+ return access;
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Return the access level the given user has on the channel. If the
+ * channel doesn't exist, the user isn't on the access list, or the channel
+ * is CS_SECURE and the user hasn't IDENTIFY'd with NickServ, return 0. */
+
+int get_access(User * user, ChannelInfo * ci)
+{
+ ChanAccess *access;
+
+ if (!ci || !user)
+ return 0;
+
+ /* SuperAdmin always has highest level */
+ if (user->isSuperAdmin)
+ return ACCESS_SUPERADMIN;
+
+ if (is_founder(user, ci))
+ return ACCESS_FOUNDER;
+
+ if (!user->na)
+ return 0;
+
+ if (nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
+ if ((access = get_access_entry(user->na->nc, ci)))
+ return access->level;
+
+ if (nick_identified(user))
+ return 0;
+
+ return 0;
+}
+
+/*************************************************************************/
+
+void update_cs_lastseen(User * user, ChannelInfo * ci)
+{
+ ChanAccess *access;
+
+ if (!ci || !user || !user->na)
+ return;
+
+ if (is_founder(user, ci) || nick_identified(user)
+ || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
+ if ((access = get_access_entry(user->na->nc, ci)))
+ access->last_seen = time(NULL);
+}
+
+/*************************************************************************/
+
+/* Returns the best ban possible for an user depending of the bantype
+ value. */
+
+int get_idealban(ChannelInfo * ci, User * u, char *ret, int retlen)
+{
+ char *mask;
+
+ if (!ci || !u || !ret || retlen == 0)
+ return 0;
+
+ switch (ci->bantype) {
+ case 0:
+ snprintf(ret, retlen, "*!%s@%s", common_get_vident(u),
+ common_get_vhost(u));
+ return 1;
+ case 1:
+ snprintf(ret, retlen, "*!%s%s@%s",
+ (strlen(common_get_vident(u)) <
+ (*(common_get_vident(u)) ==
+ '~' ? USERMAX + 1 : USERMAX) ? "*" : ""),
+ (*(common_get_vident(u)) ==
+ '~' ? common_get_vident(u) + 1 : common_get_vident(u)),
+ common_get_vhost(u));
+ return 1;
+ case 2:
+ snprintf(ret, retlen, "*!*@%s", common_get_vhost(u));
+ return 1;
+ case 3:
+ mask = create_mask(u);
+ snprintf(ret, retlen, "*!%s", mask);
+ free(mask);
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+/*************************************************************************/
+
+char *cs_get_flood(ChannelInfo * ci)
+{
+ if (!ci) {
+ return NULL;
+ } else {
+ return ci->mlock_flood;
+ }
+}
+
+/*************************************************************************/
+
+char *cs_get_key(ChannelInfo * ci)
+{
+ if (!ci) {
+ return NULL;
+ } else {
+ return ci->mlock_key;
+ }
+}
+
+/*************************************************************************/
+
+char *cs_get_limit(ChannelInfo * ci)
+{
+ static char limit[16];
+
+ if (!ci) {
+ return NULL;
+ }
+
+ if (ci->mlock_limit == 0)
+ return NULL;
+
+ snprintf(limit, sizeof(limit), "%lu",
+ (unsigned long int) ci->mlock_limit);
+ return limit;
+}
+
+/*************************************************************************/
+
+char *cs_get_redirect(ChannelInfo * ci)
+{
+ if (!ci) {
+ return NULL;
+ } else {
+ return ci->mlock_redirect;
+ }
+}
+
+/*************************************************************************/
+
+void cs_set_flood(ChannelInfo * ci, char *value)
+{
+ if (!ci) {
+ return;
+ }
+
+ if (ci->mlock_flood)
+ free(ci->mlock_flood);
+
+ /* This looks ugly, but it works ;) */
+ if (anope_flood_mode_check(value)) {
+ ci->mlock_flood = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~ircd->chan_fmode;
+ ci->mlock_flood = NULL;
+ }
+}
+
+/*************************************************************************/
+
+void cs_set_key(ChannelInfo * ci, char *value)
+{
+ if (!ci) {
+ return;
+ }
+
+ if (ci->mlock_key)
+ free(ci->mlock_key);
+
+ /* Don't allow keys with a coma */
+ if (value && *value != ':' && !strchr(value, ',')) {
+ ci->mlock_key = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~anope_get_key_mode();
+ ci->mlock_key = NULL;
+ }
+}
+
+/*************************************************************************/
+
+void cs_set_limit(ChannelInfo * ci, char *value)
+{
+ if (!ci) {
+ return;
+ }
+
+ ci->mlock_limit = value ? strtoul(value, NULL, 10) : 0;
+
+ if (ci->mlock_limit <= 0)
+ ci->mlock_on &= ~anope_get_limit_mode();
+}
+
+/*************************************************************************/
+
+void cs_set_redirect(ChannelInfo * ci, char *value)
+{
+ if (!ci) {
+ return;
+ }
+
+ if (ci->mlock_redirect)
+ free(ci->mlock_redirect);
+
+ /* Don't allow keys with a coma */
+ if (value && *value == '#') {
+ ci->mlock_redirect = sstrdup(value);
+ } else {
+ ci->mlock_on &= ~ircd->chan_lmode;
+ ci->mlock_redirect = NULL;
+ }
+}
+
+int get_access_level(ChannelInfo * ci, NickAlias * na)
+{
+ ChanAccess *access;
+ int num;
+
+ if (!ci || !na) {
+ return 0;
+ }
+
+ if (na->nc == ci->founder) {
+ return ACCESS_FOUNDER;
+ }
+
+ for (num = 0; num < ci->accesscount; num++) {
+
+ access = &ci->access[num];
+
+ if (access->nc && access->nc == na->nc && access->in_use) {
+ return access->level;
+ }
+
+ }
+
+ return 0;
+
+}
+
+const char *get_xop_level(int level)
+{
+ if (level < ACCESS_VOP) {
+ return "Err";
+ } else if (ircd->halfop && level < ACCESS_HOP) {
+ return "VOP";
+ } else if (!ircd->halfop && level < ACCESS_AOP) {
+ return "VOP";
+ } else if (ircd->halfop && level < ACCESS_AOP) {
+ return "HOP";
+ } else if (level < ACCESS_SOP) {
+ return "AOP";
+ } else if (level < ACCESS_FOUNDER) {
+ return "SOP";
+ } else {
+ return "Founder";
+ }
+
+}
+
+/*************************************************************************/
+/*********************** ChanServ command routines ***********************/
+/*************************************************************************/
+
+/*************************************************************************/
+
+
+/*************************************************************************/
+
+/* `last' is set to the last index this routine was called with
+ * `perm' is incremented whenever a permission-denied error occurs
+ */
+
+
+/*************************************************************************/
+
+/* Is the mask stuck? */
+
+AutoKick *is_stuck(ChannelInfo * ci, char *mask)
+{
+ int i;
+ AutoKick *akick;
+
+ if (!ci) {
+ return NULL;
+ }
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
+ || !(akick->flags & AK_STUCK))
+ continue;
+ /* Example: mask = *!*@*.org and akick->u.mask = *!*@*.anope.org */
+ if (match_wild_nocase(mask, akick->u.mask))
+ return akick;
+ if (ircd->reversekickcheck) {
+ /* Example: mask = *!*@irc.anope.org and akick->u.mask = *!*@*.anope.org */
+ if (match_wild_nocase(akick->u.mask, mask))
+ return akick;
+ }
+ }
+
+ return NULL;
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_mask(ChannelInfo * ci, AutoKick * akick)
+{
+ char *av[2];
+ Entry *ban;
+
+ if (!ci) {
+ return;
+ }
+
+ if (ci->c->bans && ci->c->bans->entries != 0) {
+ for (ban = ci->c->bans->entries; ban; ban = ban->next) {
+ /* If akick is already covered by a wider ban.
+ Example: c->bans[i] = *!*@*.org and akick->u.mask = *!*@*.epona.org */
+ if (entry_match_mask(ban, sstrdup(akick->u.mask), 0))
+ return;
+
+ if (ircd->reversekickcheck) {
+ /* If akick is wider than a ban already in place.
+ Example: c->bans[i] = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
+ if (match_wild_nocase(akick->u.mask, ban->mask))
+ return;
+ }
+ }
+ }
+
+ /* Falling there means set the ban */
+ av[0] = sstrdup("+b");
+ av[1] = akick->u.mask;
+ anope_cmd_mode(whosends(ci), ci->c->name, "+b %s", akick->u.mask);
+ chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
+ free(av[0]);
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_all(ChannelInfo * ci)
+{
+ int i;
+ char *av[2];
+ AutoKick *akick;
+
+ if (!ci) {
+ return;
+ }
+
+ for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
+ if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
+ || !(akick->flags & AK_STUCK))
+ continue;
+
+ av[0] = sstrdup("+b");
+ av[1] = akick->u.mask;
+ anope_cmd_mode(whosends(ci), ci->c->name, "+b %s", akick->u.mask);
+ chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
+ free(av[0]);
+ }
+}