diff --git a/inspircd-aux.c b/inspircd-aux.c new file mode 100644 index 0000000..a844a6f --- /dev/null +++ b/inspircd-aux.c @@ -0,0 +1,1634 @@ +/* + * Copyright (c) 2005-2006 Atheme Development Group + * Rights to this code are documented in doc/LICENSE. + * + * The People's Democratic Republic of InspIRCd common (across all protocols) support. + * + */ + +#include "atheme.h" +#include "uplink.h" +#include "pmodule.h" +#include "protocol/inspircd.h" + +DECLARE_MODULE_V1("protocol/inspircd-aux", true, _modinit, NULL, PACKAGE_STRING, "Atheme Development Group "); + +/* *INDENT-OFF* */ + +ircd_t InspIRCd = { + "InspIRCd 1.2+", /* IRCd name */ + "$", /* TLD Prefix, used by Global. */ + true, /* Whether or not we use IRCNet/TS6 UID */ + false, /* Whether or not we use RCOMMAND */ + true, /* Whether or not we support channel owners. */ + true, /* Whether or not we support channel protection. */ + true, /* Whether or not we support halfops. */ + false, /* Whether or not we use P10 */ + true, /* Whether or not we use vHosts. */ + CMODE_OPERONLY | CMODE_PERM | CMODE_IMMUNE, /* Oper-only cmodes */ + CSTATUS_OWNER, /* Integer flag for owner channel flag. */ + CSTATUS_PROTECT, /* Integer flag for protect channel flag. */ + CSTATUS_HALFOP, /* Integer flag for halfops. */ + "+q", /* Mode we set for owner. */ + "+a", /* Mode we set for protect. */ + "+h", /* Mode we set for halfops. */ + PROTOCOL_INSPIRCD, /* Protocol type */ + CMODE_PERM, /* Permanent cmodes */ + CMODE_IMMUNE, /* Oper-immune cmode */ + "beIg", /* Ban-like cmodes */ + 'e', /* Except mchar */ + 'I', /* Invex mchar */ + IRCD_CIDR_BANS | IRCD_HOLDNICK /* Flags */ +}; + +struct cmode_ inspircd_mode_list[] = { + { 'i', CMODE_INVITE }, + { 'm', CMODE_MOD }, + { 'n', CMODE_NOEXT }, + { 'p', CMODE_PRIV }, + { 's', CMODE_SEC }, + { 't', CMODE_TOPIC }, + { 'c', CMODE_NOCOLOR }, + { 'M', CMODE_MODREG }, + { 'R', CMODE_REGONLY }, + { 'O', CMODE_OPERONLY }, + { 'S', CMODE_STRIP }, + { 'K', CMODE_NOKNOCK }, + { 'A', CMODE_NOINVITE }, + { 'C', CMODE_NOCTCP }, + { 'N', CMODE_STICKY }, + { 'G', CMODE_CENSOR }, + { 'P', CMODE_PERM }, + { 'B', CMODE_NOCAPS }, + { 'z', CMODE_SSLONLY }, + { 'T', CMODE_NONOTICE }, + { 'u', CMODE_HIDING }, + { 'Q', CMODE_PEACE }, + { 'Y', CMODE_IMMUNE }, + { 'D', CMODE_DELAYJOIN }, + { '\0', 0 } +}; + +static bool check_flood(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_nickflood(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_jointhrottle(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_forward(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_rejoindelay(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_delaymsg(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); + +struct extmode inspircd_ignore_mode_list[] = { + { 'f', check_flood }, + { 'F', check_nickflood }, + { 'j', check_jointhrottle }, + { 'L', check_forward }, + { 'J', check_rejoindelay }, + { 'd', check_delaymsg }, + { '\0', 0 } +}; + +struct cmode_ inspircd_status_mode_list[] = { + { 'q', CSTATUS_OWNER }, + { 'a', CSTATUS_PROTECT }, + { 'o', CSTATUS_OP }, + { 'h', CSTATUS_HALFOP }, + { 'v', CSTATUS_VOICE }, + { '\0', 0 } +}; + +struct cmode_ inspircd_prefix_mode_list[] = { + { '~', CSTATUS_OWNER }, + { '&', CSTATUS_PROTECT }, + { '@', CSTATUS_OP }, + { '%', CSTATUS_HALFOP }, + { '+', CSTATUS_VOICE }, + { '\0', 0 } +}; + +struct cmode_ inspircd_user_mode_list[] = { + { 'i', UF_INVIS }, + { 'o', UF_IRCOP }, + { 'd', UF_DEAF }, + { '\0', 0 } +}; + +/* CAPABilities */ +static bool has_servicesmod = false; +static bool has_globopsmod = false; +static bool has_chghostmod = false; +static bool has_cbanmod = false; +static bool has_hidechansmod = false; +static bool has_servprotectmod = false; +static bool has_svshold = false; +static bool has_cloakingmod = false; +static bool has_shun = false; +static bool has_mlock = false; +static int has_protocol = 0; + +#define PROTOCOL_12BETA 1201 /* we do not support anything older than this */ +#define PROTOCOL_METADATA_TS 1204 + +/* find a user's server by extracting the SID and looking that up. --nenolod */ +static server_t *sid_find(char *name) +{ + char sid[4]; + mowgli_strlcpy(sid, name, 4); + return server_find(sid); +} + + +/* *INDENT-ON* */ + +static inline void channel_metadata_sts(channel_t *c, const char *key, const char *value) +{ + if (has_protocol < PROTOCOL_METADATA_TS) + { + sts(":%s METADATA %s %s :%s", ME, c->name, key, value); + return; + } + + sts(":%s METADATA %s %ld %s :%s", ME, c->name, c->ts, key, value); +} + +static bool check_flood(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + /* +F doesn't support *, so don't bother checking for it -- w00t */ + return check_jointhrottle(value, c, mc, u, mu); +} + +static bool check_nickflood(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + return *value == '*' ? check_jointhrottle(value + 1, c, mc, u, mu) : check_jointhrottle(value, c, mc, u, mu); +} + +static bool check_jointhrottle(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *p, *arg2; + + p = value, arg2 = NULL; + while (*p != '\0') + { + if (*p == ':') + { + if (arg2 != NULL) + return false; + arg2 = p + 1; + } + else if (!isdigit(*p)) + return false; + p++; + } + if (arg2 == NULL) + return false; + if (p - arg2 > 10 || arg2 - value - 1 > 10 || !atoi(value) || !atoi(arg2)) + return false; + return true; +} + +static bool check_forward(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + channel_t *target_c; + mychan_t *target_mc; + + if (*value != '#' || strlen(value) > 50) + return false; + if (u == NULL && mu == NULL) + return true; + target_c = channel_find(value); + target_mc = MYCHAN_FROM(target_c); + if (target_c == NULL && target_mc == NULL) + return false; + return true; +} + +static bool check_rejoindelay(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *ch = value; + + while (*ch) + { + if (!isdigit(*ch)) + return false; + ch++; + } + + /* don't allow mlocking a rejoin delay mode greater than 5 seconds. + it's extremely rude. --nenolod */ + if (atoi(value) <= 0 || atoi(value) >= 5) + { + return false; + } + else + { + return true; + } +} + +static bool check_delaymsg(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *ch = value; + + while (*ch) + { + if (!isdigit(*ch)) + return false; + ch++; + } + + if (atoi(value) <= 0) + { + return false; + } + else + { + return true; + } +} + +/* login to our uplink */ +static unsigned int inspircd_server_login(void) +{ + int ret; + + /* Check if we have a numeric set. InspIRCd 1.2 protocol + * requires it. -nenolod + */ + if (me.numeric == NULL) + { + slog(LG_ERROR, "inspircd_server_login(): inspircd 1.2 requires a unique identifier. set serverinfo::numeric."); + exit(EXIT_FAILURE); + } + + /* will be determined in CAPAB. */ + ircd->uses_owner = false; + ircd->uses_protect = false; + ircd->uses_halfops = false; + + ret = sts("SERVER %s %s 0 %s :%s", me.name, curr_uplink->send_pass, me.numeric, me.desc); + if (ret == 1) + return 1; + + me.bursting = true; + return 0; +} + +/* introduce a client */ +static void inspircd_introduce_nick(user_t *u) +{ + /* :penguin.omega.org.za UID 497AAAAAB 1188302517 OperServ 127.0.0.1 127.0.0.1 OperServ +s 127.0.0.1 :Operator Server */ + const char *umode = user_get_umodestr(u); + + sts(":%s UID %s %lu %s %s %s %s 0.0.0.0 %lu %s%s%s :%s", me.numeric, u->uid, (unsigned long)u->ts, u->nick, u->host, u->host, u->user, (unsigned long)u->ts, umode, has_hidechansmod ? "I" : "", has_servprotectmod ? "k" : "", u->gecos); + if (is_ircop(u)) + sts(":%s OPERTYPE Services", u->uid); +} + +static void inspircd_quit_sts(user_t *u, const char *reason) +{ + sts(":%s QUIT :%s", u->uid, reason); +} + +/* WALLOPS wrapper */ +static void inspircd_wallops_sts(const char *text) +{ + if (has_globopsmod) + sts(":%s SNONOTICE g :%s", me.numeric, text); + else + sts(":%s SNONOTICE A :%s", me.numeric, text); +} + +/* join a channel */ +static void inspircd_join_sts(channel_t *c, user_t *u, bool isnew, char *modes) +{ + if (isnew) + { + sts(":%s FJOIN %s %lu + :o,%s", me.numeric, c->name, (unsigned long)c->ts, u->uid); + if (modes[0] && modes[1]) + sts(":%s FMODE %s %lu %s", me.numeric, c->name, (unsigned long)c->ts, modes); + } + else + { + sts(":%s FJOIN %s %lu + :o,%s", me.numeric, c->name, (unsigned long)c->ts, u->uid); + } +} + +static void inspircd_chan_lowerts(channel_t *c, user_t *u) +{ + slog(LG_DEBUG, "inspircd_chan_lowerts(): lowering TS for %s to %lu", + c->name, (unsigned long)c->ts); + + sts(":%s FJOIN %s %lu + :o,%s", me.numeric, c->name, (unsigned long)c->ts, u->uid); + sts(":%s FMODE %s %lu %s", me.numeric, c->name, (unsigned long)c->ts, channel_modes(c, true)); +} + +/* kicks a user from a channel */ +static void inspircd_kick(user_t *source, channel_t *c, user_t *u, const char *reason) +{ + sts(":%s KICK %s %s :%s", source->uid, c->name, u->uid, reason); + + chanuser_delete(c, u); +} + +/* PRIVMSG wrapper */ +static void inspircd_msg(const char *from, const char *target, const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZE]; + user_t *user = user_find(target); + user_t *from_p = user_find(from); + + va_start(ap, fmt); + vsnprintf(buf, BUFSIZE, fmt, ap); + va_end(ap); + + sts(":%s PRIVMSG %s :%s", from_p->uid, *target != '#' ? user->uid : target, buf); +} + +static void inspircd_msg_global_sts(user_t *from, const char *mask, const char *text) +{ + sts(":%s PRIVMSG %s%s :%s", from ? from->uid : me.numeric, ircd->tldprefix, mask, text); +} + +/* NOTICE wrapper */ +static void inspircd_notice_user_sts(user_t *from, user_t *target, const char *text) +{ + sts(":%s NOTICE %s :%s", from ? from->uid : me.numeric, target->uid, text); +} + +static void inspircd_notice_global_sts(user_t *from, const char *mask, const char *text) +{ + sts(":%s NOTICE %s%s :%s", from ? from->uid : me.numeric, ircd->tldprefix, mask, text); +} + +static void inspircd_notice_channel_sts(user_t *from, channel_t *target, const char *text) +{ + sts(":%s NOTICE %s :%s", from ? from->uid : me.numeric, target->name, text); +} + +static void inspircd_numeric_sts(server_t *from, int numeric, user_t *target, const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZE]; + + va_start(ap, fmt); + vsnprintf(buf, BUFSIZE, fmt, ap); + va_end(ap); + + sts(":%s PUSH %s ::%s %d %s %s", from->sid, target->uid, from->name, numeric, target->nick, buf); +} + +/* KILL wrapper */ +static void inspircd_kill_id_sts(user_t *killer, const char *id, const char *reason) +{ + if (killer != NULL) + sts(":%s KILL %s :Killed (%s (%s))", CLIENT_NAME(killer), id, killer->nick, reason); + else + sts(":%s KILL %s :Killed (%s (%s))", ME, id, me.name, reason); +} + +/* PART wrapper */ +static void inspircd_part_sts(channel_t *c, user_t *u) +{ + sts(":%s PART %s :Leaving", u->uid, c->name); +} + +/* server-to-server KLINE wrapper */ +static void inspircd_kline_sts(const char *server, const char *user, const char *host, long duration, const char *reason) +{ + service_t *svs; + + /* :services-dev.chatspike.net ADDLINE G test@test.com Brain 1133994664 0 :You are banned from this network */ + svs = service_find("operserv"); + sts(":%s ADDLINE G %s@%s %s %lu %ld :%s", me.numeric, user, host, svs != NULL ? svs->nick : me.name, (unsigned long)CURRTIME, duration, reason); +} + +/* server-to-server UNKLINE wrapper */ +static void inspircd_unkline_sts(const char *server, const char *user, const char *host) +{ + service_t *svs; + + /* I know this looks wrong, but it's really not. Trust me. --w00t */ + svs = service_find("operserv"); + sts(":%s GLINE %s@%s", svs != NULL ? svs->me->uid : ME, user, host); +} + +/* server-to-server QLINE wrapper */ +static void inspircd_qline_sts(const char *server, const char *name, long duration, const char *reason) +{ + service_t *svs; + + svs = service_find("operserv"); + + if (*name != '#') + { + sts(":%s ADDLINE Q %s %s %lu %ld :%s", me.numeric, name, svs != NULL ? svs->nick : me.name, (unsigned long)CURRTIME, duration, reason); + return; + } + + if (has_cbanmod) + sts(":%s CBAN %s %ld :%s", svs != NULL ? svs->me->uid : ME, name, duration, reason); + else + slog(LG_INFO, "SQLINE: Could not set SQLINE on \2%s\2 due to m_cban not being loaded in inspircd.", name); +} + +/* server-to-server UNQLINE wrapper */ +static void inspircd_unqline_sts(const char *server, const char *name) +{ + if (*name != '#') + { + sts(":%s QLINE %s", ME, name); + return; + } + + if (has_cbanmod) + sts(":%s CBAN %s", ME, name); + else + slog(LG_INFO, "SQLINE: Could not remove SQLINE on \2%s\2 due to m_cban not being loaded in inspircd.", name); +} + +/* topic wrapper */ +static void inspircd_topic_sts(channel_t *c, user_t *source, const char *setter, time_t ts, time_t prevts, const char *topic) +{ + return_if_fail(c != NULL); + + /* If possible, try to use FTOPIC + * Note that because TOPIC does not contain topicTS, it may be + * off a few seconds on other servers, hence the 60 seconds here. + * -- jilles */ + /* Restoring old topic */ + if (ts > prevts + 60 || prevts == 0) + { + sts(":%s FTOPIC %s %lu %s :%s", source->uid, c->name, (unsigned long)ts, setter, topic); + return; + } + /* Tweaking a topic */ + else if (ts == prevts) + { + ts += 60; + sts(":%s FTOPIC %s %lu %s :%s", source->uid, c->name, (unsigned long)ts, setter, topic); + c->topicts = ts; + return; + } + sts(":%s TOPIC %s :%s", source->uid, c->name, topic); + c->topicts = CURRTIME; +} + +/* mode wrapper */ +static void inspircd_mode_sts(char *sender, channel_t *target, char *modes) +{ + user_t *sender_p; + + return_if_fail(sender != NULL); + return_if_fail(target != NULL); + return_if_fail(modes != NULL); + + sender_p = user_find(sender); + + return_if_fail(sender_p != NULL); + + sts(":%s FMODE %s %lu %s", sender_p->uid, target->name, (unsigned long)target->ts, modes); +} + +/* ping wrapper */ +static void inspircd_ping_sts(void) +{ + // XXX this is annoying, uplink_t contains no sid or link to server_t + server_t *u = server_find(curr_uplink->name); + + if (!u) + return; + + sts(":%s PING %s :%s", me.numeric, me.numeric, u->sid); +} + +/* protocol-specific stuff to do on login */ +static void inspircd_on_login(user_t *u, myuser_t *mu, const char *wantedhost) +{ + sts(":%s METADATA %s accountname :%s", me.numeric, u->uid, entity(mu)->name); +} + +/* protocol-specific stuff to do on logout */ +static bool inspircd_on_logout(user_t *u, const char *account) +{ + sts(":%s METADATA %s accountname :", me.numeric, u->uid); + return false; +} + +static void inspircd_jupe(const char *server, const char *reason) +{ + service_t *svs; + static char sid[3+1]; + int i; + server_t *s; + + svs = service_find("operserv"); + + s = server_find(server); + if (s != NULL) + { + /* We need to wait for the RSQUIT to be processed -- jilles */ + sts(":%s RSQUIT :%s", svs != NULL ? svs->me->uid : ME, server); + s->flags |= SF_JUPE_PENDING; + return; + } + + /* Remove any previous jupe first */ + sts(":%s SQUIT %s :%s", me.numeric, server, reason); + /* dirty dirty make up some sid */ + if (sid[0] == '\0') + mowgli_strlcpy(sid, me.numeric, sizeof sid); + do + { + i = 2; + for (;;) + { + if (sid[i] == 'Z') + { + sid[i] = '0'; + i--; + /* eek, no more sids */ + if (i < 0) + return; + continue; + } + else if (sid[i] == '9') + sid[i] = 'A'; + else sid[i]++; + break; + } + } while (server_find(sid)); + + sts(":%s SERVER %s * 1 %s :%s", me.numeric, server, sid, reason); +} + +static void inspircd_sethost_sts(user_t *source, user_t *target, const char *host) +{ + if (has_chghostmod) + { + sts(":%s CHGHOST %s %s", source->uid, target->uid, host); + + if (has_cloakingmod && !irccasecmp(target->host, host)) + sts(":%s SVSMODE %s +x", source->uid, target->uid); + } + else + slog(LG_INFO, "VHOST: Could not set \2%s\2 due to m_chghost not being loaded in inspircd.", host); +} + +static void inspircd_fnc_sts(user_t *source, user_t *u, char *newnick, int type) +{ + /* svsnick can only be sent by a server */ + sts(":%s SVSNICK %s %s %lu", me.numeric, u->uid, newnick, + (unsigned long)(CURRTIME - 60)); +} + + +/* invite a user to a channel */ +static void inspircd_invite_sts(user_t *sender, user_t *target, channel_t *channel) +{ + sts(":%s INVITE %s %s", sender->uid, target->uid, channel->name); +} + +static void inspircd_holdnick_sts(user_t *source, int duration, const char *nick, myuser_t *account) +{ + service_t *svs; + + svs = service_find("operserv"); + if (duration == 0) + { + if (has_svshold) + /* remove SVSHOLD */ + sts(":%s SVSHOLD %s", source->uid, nick); + else + sts(":%s QLINE %s", svs != NULL ? svs->me->uid : ME, nick); + } + else + { + if (has_svshold) + sts(":%s SVSHOLD %s %d :Registered nickname.", source->uid, nick, duration); + else + sts(":%s ADDLINE Q %s %s %lu %d :%s", me.numeric, nick, svs != NULL ? svs->nick : me.name, (unsigned long)CURRTIME, duration, "Nickname Enforcer"); + } +} + +static void inspircd_svslogin_sts(char *target, char *nick, char *user, char *host, char *login) +{ + user_t *tu = user_find(target); + + if(!tu && !ircd->uses_uid) + return; + + sts(":%s METADATA %s accountname :%s", me.numeric, target, login); +} + +static void inspircd_sasl_sts(char *target, char mode, char *data) +{ + service_t *svs; + server_t *s = sid_find(target); + + return_if_fail(s != NULL); + + svs = service_find("saslserv"); + if (svs == NULL) + return; + + sts(":%s ENCAP %s SASL %s %s %c %s", ME, s->sid, svs->me->uid, target, mode, data); +} + +static void inspircd_quarantine_sts(user_t *source, user_t *victim, long duration, const char *reason) +{ + if (has_shun) + sts(":%s ADDLINE SHUN *@%s %s %lu %ld :%s", me.numeric, victim->host, source->nick, (unsigned long) CURRTIME, duration, reason); +} + +static void inspircd_mlock_sts(channel_t *c) +{ + mychan_t *mc = MYCHAN_FROM(c); + + if (has_mlock == false) + return; + + if (mc == NULL) + return; + + channel_metadata_sts(c, "mlock", mychan_get_sts_mlock(mc)); +} + +static void m_topic(sourceinfo_t *si, int parc, char *parv[]) +{ + channel_t *c = channel_find(parv[0]); + + if (!c) + return; + + handle_topic_from(si, c, si->su->nick, time(NULL), parv[1]); +} + +static void m_ftopic(sourceinfo_t *si, int parc, char *parv[]) +{ + channel_t *c = channel_find(parv[0]); + time_t ts = atol(parv[1]); + + if (!c) + return; + + if (c->topic != NULL && c->topicts >= ts) + { + slog(LG_DEBUG, "m_ftopic(): ignoring older topic on %s", c->name); + return; + } + + handle_topic_from(si, c, parv[2], ts, parv[3]); +} + +static void m_ping(sourceinfo_t *si, int parc, char *parv[]) +{ + /* reply to PING's */ + if (parc == 1) + sts(":%s PONG %s", me.numeric, parv[0]); + else if (parc == 2) + sts(":%s PONG %s :%s", me.numeric, parv[1], parv[0]); +} + +static void m_pong(sourceinfo_t *si, int parc, char *parv[]) +{ + server_t *s; + + if (!parv[1]) + return; + s = server_find(parv[0]); + if (!s || s == me.me) + { + TAINT_ON(s = server_find(parv[1]), "inspircd bug #90 causes possible state desync -- upgrade your software"); + if (!s || s == me.me) + return; + } + + handle_eob(s); + + me.uplinkpong = CURRTIME; + + /* if pong source isn't origin, this isn't a complete burst. --nenolod */ + if (s != si->s) + return; + + /* -> :test.projectxero.net PONG test.projectxero.net :shrike.malkier.net */ + if (me.bursting) + { +#ifdef HAVE_GETTIMEOFDAY + e_time(burstime, &burstime); + + slog(LG_INFO, "m_pong(): finished synching with uplink (%d %s)", (tv2ms(&burstime) > 1000) ? (tv2ms(&burstime) / 1000) : tv2ms(&burstime), (tv2ms(&burstime) > 1000) ? "s" : "ms"); + + wallops("Finished synchronizing with network in %d %s.", (tv2ms(&burstime) > 1000) ? (tv2ms(&burstime) / 1000) : tv2ms(&burstime), (tv2ms(&burstime) > 1000) ? "s" : "ms"); +#else + slog(LG_INFO, "m_pong(): finished synching with uplink"); + wallops("Finished synchronizing with network."); +#endif + + me.bursting = false; + } +} + +static void m_privmsg(sourceinfo_t *si, int parc, char *parv[]) +{ + if (parc != 2) + return; + + handle_message(si, parv[0], false, parv[1]); +} + +static void m_notice(sourceinfo_t *si, int parc, char *parv[]) +{ + if (!me.recvsvr) + { + slog(LG_ERROR, "m_notice(): received NOTICE from uplink which is in unregistered state."); + slog(LG_ERROR, "m_notice(): this probably means that you are linking to a client port instead"); + slog(LG_ERROR, "m_notice(): of a server port on your inspircd server."); + slog(LG_ERROR, "m_notice(): atheme is giving up now. please correct your configuration and try again."); + exit(EXIT_FAILURE); + } + + if (parc != 2) + return; + + handle_message(si, parv[0], true, parv[1]); +} + +static void map_a_prefix(char prefix, char* prefixandnick, unsigned int *nlen) +{ + size_t j, k; + + /* does this char match a known prefix? */ + for (j = 0; status_mode_list[j].mode; j++) + { + /* yup. add it to the 'final' combination (@%w00t) */ + if (prefix == status_mode_list[j].mode) + { + for (k = 0; prefix_mode_list[k].mode; k++) + { + if (status_mode_list[j].value == prefix_mode_list[k].value) + { + prefixandnick[*nlen] = prefix_mode_list[k].mode; + (*nlen)++; + return; + } + } + } + } +} + +static void m_fjoin(sourceinfo_t *si, int parc, char *parv[]) +{ + /* :08X FJOIN #flaps 1234 +nt vh,0F8XXXXN ,08XGH75C ,001CCCC3 aq,00ABBBB1 */ + channel_t *c; + unsigned int userc; + unsigned int i; + unsigned int nlen; + bool prefix = true; + bool keep_new_modes = true; + char *userv[256]; + char prefixandnick[51]; + time_t ts; + + c = channel_find(parv[0]); + ts = atol(parv[1]); + + if (!c) + { + slog(LG_DEBUG, "m_fjoin(): new channel: %s", parv[0]); + c = channel_add(parv[0], ts, si->s); + return_if_fail(c != NULL); + } + + if (ts < c->ts) + { + chanuser_t *cu; + mowgli_node_t *n; + + /* the TS changed. a TS change requires us to do + * bugger all except update the TS, because in InspIRCd + * remote servers enforce the TS change - Brain + * + * This is no longer the case with 1.1, we need to remove modes etc + * as well as lowering the channel ts. Do both. -- w00t + */ + clear_simple_modes(c); + chanban_clear(c); + + /* + * Also reop services, and remove status from others. + */ + MOWGLI_ITER_FOREACH(n, c->members.head) + { + cu = (chanuser_t *)n->data; + if (cu->user->server == me.me) + { + /* it's a service, reop */ + sts(":%s FMODE %s %lu +o %s", me.numeric, c->name, (unsigned long)ts, cu->user->uid); + cu->modes = CSTATUS_OP; + } + else + cu->modes = 0; + } + + c->ts = ts; + hook_call_channel_tschange(c); + } + else if (ts > c->ts) + { + keep_new_modes = false; /* ignore statuses */ + } + + /* + * ok, here's the difference from 1.0 -> 1.1: + * 1.0 sent p[3] and up as individual users, prefixed with their 'highest' prefix, @, % or + + * in 1.1, this is more complex. All prefixes are sent, with the additional caveat that modules + * can add their own prefixes (dangerous!) - therefore, don't just chanuser_add(), split the prefix + * out and ignore unknown prefixes (probably the safest option). --w00t + */ + userc = sjtoken(parv[parc - 1], ' ', userv); + + if (keep_new_modes) + { + channel_mode(NULL, c, parc - 3, parv + 2); + } + + /* loop over all the users in this fjoin */ + for (i = 0; i < userc; i++) + { + nlen = 0; + prefix = true; + + slog(LG_DEBUG, "m_fjoin(): processing user: %s", userv[i]); + + /* + * ok, now look at the chars in the nick.. we have something like "@%,w00t", but need @%w00t.. and + * we also want to ignore unknown prefixes.. loop through the chars + */ + for (; *userv[i]; userv[i]++) + { + map_a_prefix(*userv[i], prefixandnick, &nlen); + + /* it's not a known prefix char, have we reached the end of the prefixes? */ + if (*userv[i] == ',') + { + /* yup, skip over the comma */ + userv[i]++; + + /* if we're ignoring status (keep_new_modes is false) then just add them to chan here.. */ + if (keep_new_modes == false) + { + /* This ignores the @%, and just adds 'w00t' to the chan */ + chanuser_add(c, userv[i]); + } + else + { + /* else, we do care about their prefixes.. add '@%w00t' to the chan */ + mowgli_strlcpy(prefixandnick + nlen, userv[i], sizeof(prefixandnick) - nlen); + chanuser_add(c, prefixandnick); + } + + /* added them.. break out of this loop, which will move us to the next user */ + break; + } + else + { + /* unknown prefix char */ + } + } + + /* go to the next user */ + } + + if (c->nummembers == 0 && !(c->modes & ircd->perm_mode)) + channel_delete(c); +} + +static void m_part(sourceinfo_t *si, int parc, char *parv[]) +{ + int chanc; + char *chanv[256]; + int i; + + chanc = sjtoken(parv[0], ',', chanv); + for (i = 0; i < chanc; i++) + { + slog(LG_DEBUG, "m_part(): user left channel: %s -> %s", si->su->nick, chanv[i]); + + chanuser_delete(channel_find(chanv[i]), si->su); + } +} + +static void m_uid(sourceinfo_t *si, int parc, char *parv[]) +{ + user_t *u; + + /* 1 2 3 4 5 6 7 8 9* 10* */ + /* -> :751 UID 751AAAAAA 1220196319 Brain brainwave.brainbox.cc netadmin.chatspike.net brain 192.168.1.10 1220196324 +Siosw +ACKNOQcdfgklnoqtx :Craig Edwards */ + + /* + * note: you can't rely on realname being p[10], it's actually p[parc - 1]. + * reason being that mode params may exist in p[9]+, or not at all. + */ + if (parc >= 10) + { + slog(LG_DEBUG, "m_uid(): new user on `%s': %s", si->s->name, parv[2]); + + /* char *nick, char *user, char *host, char *vhost, char *ip, char *uid, char *gecos, server_t *server, unsigned int ts */ + u = user_add(parv[2], parv[5], parv[3], parv[4], parv[6], parv[0], parv[parc - 1], si->s, atol(parv[1])); + if (u == NULL) + return; + user_mode(u, parv[8]); + + /* If server is not yet EOB we will do this later. + * This avoids useless "please identify" -- jilles */ + if (si->s->flags & SF_EOB) + handle_nickchange(u); + } + else + { + int i; + slog(LG_DEBUG, "m_uid(): got UID with wrong number of params"); + + for (i = 0; i < parc; i++) + slog(LG_DEBUG, "m_uid(): parv[%d] = %s", i, parv[i]); + } +} + +static void m_nick(sourceinfo_t *si, int parc, char *parv[]) +{ + /* if it's only 1 then it's a nickname change, if it's 2, it's a nickname change with a TS */ + if (parc == 1 || parc == 2) + { + if (!si->su) + { + slog(LG_DEBUG, "m_nick(): server trying to change nick: %s", si->s != NULL ? si->s->name : ""); + return; + } + + slog(LG_DEBUG, "m_nick(): nickname change from `%s': %s", si->su->nick, parv[0]); + + if (user_changenick(si->su, parv[0], parc == 2 ? atoi(parv[1]) : CURRTIME)) + return; + + /* It could happen that our PING arrived late and the + * server didn't acknowledge EOB yet even though it is + * EOB; don't send double notices in that case -- jilles */ + if (si->su->server->flags & SF_EOB) + handle_nickchange(si->su); + } + else + { + int i; + slog(LG_DEBUG, "m_nick(): got NICK with wrong number of params"); + + for (i = 0; i < parc; i++) + slog(LG_DEBUG, "m_nick(): parv[%d] = %s", i, parv[i]); + } +} + +static void m_quit(sourceinfo_t *si, int parc, char *parv[]) +{ + slog(LG_DEBUG, "m_quit(): user leaving: %s", si->su->nick); + + /* user_delete() takes care of removing channels and so forth */ + user_delete(si->su, parv[0]); +} + +static void inspircd_user_mode(user_t *u, const char *modes) +{ + int dir; + const char *p; + + return_if_fail(u != NULL); + + user_mode(u, modes); + dir = 0; + for (p = modes; *p; ++p) + switch (*p) + { + case '-': + dir = MTYPE_DEL; + break; + case '+': + dir = MTYPE_ADD; + break; + case 'x': + /* If +x is set then the users vhost is set to their cloaked host + * Note that -x etc works OK here, InspIRCd is nice enough to tell us + * everytime a users host changes. - Adam + */ + if (dir == MTYPE_ADD) + { + /* It is possible for the users vhost to not be their cloaked host after +x. + * This only occurs when a user is introduced after a netmerge with their + * vhost instead of their cloaked host. - Adam + */ + if (strcmp(u->vhost, u->chost)) + { + strshare_unref(u->chost); + u->chost = strshare_get(u->vhost); + } + } + break; + } +} + +static void m_saquit(sourceinfo_t *si, int parc, char *parv[]) +{ + user_t *u = user_find(parv[0]); + + slog(LG_DEBUG, "m_saquit(): user leaving: %s", parv[0]); + + /* user_delete() takes care of removing channels and so forth */ + user_delete(u, parv[1]); +} + +static void m_mode(sourceinfo_t *si, int parc, char *parv[]) +{ + if (*parv[0] == '#') + channel_mode(NULL, channel_find(parv[0]), parc - 1, &parv[1]); + else + inspircd_user_mode(user_find(parv[0]), parv[1]); +} + +static void m_fmode(sourceinfo_t *si, int parc, char *parv[]) +{ + channel_t *c; + time_t ts; + + /* :server.moo FMODE #blarp tshere +ntsklLg keymoo 1337 secks */ + if (*parv[0] == '#') + { + c = channel_find(parv[0]); + if (c == NULL) + { + slog(LG_DEBUG, "m_fmode(): nonexistant channel: %s", parv[0]); + return; + } + ts = atoi(parv[1]); + if (ts > c->ts) + { + return; + } + else if (ts < c->ts) + slog(LG_DEBUG, "m_fmode(): %s %s: incoming TS %lu is older than our TS %lu, possible desync", parv[0], parv[2], (unsigned long)ts, (unsigned long)c->ts); + channel_mode(NULL, c, parc - 2, &parv[2]); + } + else + inspircd_user_mode(user_find(parv[0]), parv[2]); +} + +static void m_kick(sourceinfo_t *si, int parc, char *parv[]) +{ + user_t *u = user_find(parv[1]); + channel_t *c = channel_find(parv[0]); + + /* -> :rakaur KICK #shrike rintaun :test */ + slog(LG_DEBUG, "m_kick(): user was kicked: %s -> %s", parv[1], parv[0]); + + if (!u) + { + slog(LG_DEBUG, "m_kick(): got kick for nonexistant user %s", parv[1]); + return; + } + + if (!c) + { + slog(LG_DEBUG, "m_kick(): got kick in nonexistant channel: %s", parv[0]); + return; + } + + if (!chanuser_find(c, u)) + { + slog(LG_DEBUG, "m_kick(): got kick for %s not in %s", u->nick, c->name); + return; + } + + chanuser_delete(c, u); + + /* if they kicked us, let's rejoin */ + if (is_internal_client(u)) + { + slog(LG_DEBUG, "m_kick(): %s got kicked from %s; rejoining", u->nick, parv[0]); + join(parv[0], u->nick); + } +} + +static void m_kill(sourceinfo_t *si, int parc, char *parv[]) +{ + handle_kill(si, parv[0], parc > 1 ? parv[1] : ""); +} + +static void m_squit(sourceinfo_t *si, int parc, char *parv[]) +{ + slog(LG_DEBUG, "m_squit(): server leaving: %s", parv[0]); + server_delete(parv[0]); +} + +static void m_server(sourceinfo_t *si, int parc, char *parv[]) +{ + server_t *s; + + slog(LG_DEBUG, "m_server(): new server: %s", parv[0]); + if (si->s == NULL) + { + sts(":%s BURST", me.numeric); + sts(":%s VERSION :%s. %s %s", + me.name, PACKAGE_STRING, me.numeric, get_conf_opts()); + services_init(); + sts(":%s ENDBURST", me.numeric); + } + s = handle_server(si, parv[0], parv[3], atoi(parv[2]), parv[4]); +} + +static void m_endburst(sourceinfo_t *si, int parc, char *parv[]) +{ + mowgli_node_t *n; + + /* elicit PONG for EOB detection. */ + sts(":%s PING %s %s", me.numeric, me.numeric, si->s->sid); + + MOWGLI_ITER_FOREACH(n, si->s->children.head) + { + server_t *s = n->data; + + sts(":%s PING %s %s", me.numeric, me.numeric, s->sid); + } +} + +static void m_stats(sourceinfo_t *si, int parc, char *parv[]) +{ + handle_stats(si->su, parv[0][0]); +} + +static void m_motd(sourceinfo_t *si, int parc, char *parv[]) +{ + handle_motd(si->su); +} + +static void m_admin(sourceinfo_t *si, int parc, char *parv[]) +{ + handle_admin(si->su); +} + +static void m_away(sourceinfo_t *si, int parc, char *parv[]) +{ + handle_away(si->su, parc >= 1 ? parv[0] : NULL); +} + +static void m_join(sourceinfo_t *si, int parc, char *parv[]) +{ + channel_t *c; + + c = channel_find(parv[0]); + if (!c) + { + slog(LG_DEBUG, "m_join(): new channel: %s (modes lost)", parv[0]); + c = channel_add(parv[0], parc > 1 ? atol(parv[1]) : CURRTIME, si->su->server); + return_if_fail(c != NULL); + channel_mode_va(NULL, c, 1, "+"); + } + chanuser_add(c, si->su->nick); +} + +static void m_sajoin(sourceinfo_t *si, int parc, char *parv[]) +{ + si->su = user_find(parv[0]); + if (si->su == NULL) + return; + m_join(si, 1, &parv[1]); +} + +static void m_sapart(sourceinfo_t *si, int parc, char *parv[]) +{ + si->su = user_find(parv[0]); + if (si->su == NULL) + return; + m_part(si, 1, &parv[1]); +} + +static void m_sanick(sourceinfo_t *si, int parc, char *parv[]) +{ + si->su = user_find(parv[0]); + if (si->su == NULL) + return; + m_nick(si, 1, &parv[1]); +} + +static void m_svsnick(sourceinfo_t *si, int parc, char *parv[]) +{ + si->su = user_find(parv[0]); + if (si->su == NULL || si->su->ts != atoi(parv[2])) + return; + if (is_internal_client(si->su)) + { + // we've already killed the colliding user in user_add/user_changenick + // XXX this could cause a services fight if we get desynced and haven't killed the camper + sts(":%s NICK %s %lu", si->su->uid, si->su->nick, (unsigned long)si->su->ts); + } + else + { + m_nick(si, 2, &parv[1]); + } +} + +static void m_samode(sourceinfo_t *si, int parc, char *parv[]) +{ + /* note that SAMODE is not checked in any way before propagation, + * and only works on channels, not users */ + channel_mode(NULL, channel_find(parv[0]), parc - 1, &parv[1]); +} + +static void m_error(sourceinfo_t *si, int parc, char *parv[]) +{ + slog(LG_INFO, "m_error(): error from server: %s", parv[0]); +} + +static void m_idle(sourceinfo_t *si, int parc, char *parv[]) +{ + if (parc == 1 && si->su != NULL) + { + sts(":%s IDLE %s %lu 0", parv[0], si->su->nick, (unsigned long)CURRTIME); + } + else + { + slog(LG_INFO, "m_idle(): Received an IDLE response but we didn't WHOIS anybody!"); + } +} + +static void m_opertype(sourceinfo_t *si, int parc, char *parv[]) +{ + /* + * Hope this works.. InspIRCd OPERTYPE means user is an oper, mark them so + * Later, we may want to look at saving their OPERTYPE for informational + * purposes, or not. --w00t + */ + user_mode(si->su, "+o"); +} + +static void m_fhost(sourceinfo_t *si, int parc, char *parv[]) +{ + strshare_unref(si->su->vhost); + si->su->vhost = strshare_get(parv[0]); +} + +static void m_encap(sourceinfo_t *si, int parc, char *parv[]) +{ + if (!irccasecmp(parv[1], "SASL")) + { + /* :08C ENCAP * SASL 08CAAAAAE * S d29vTklOSkFTAGRhdGEgaW4gZmlyc3QgbGluZQ== */ + sasl_message_t smsg; + + if (parc < 6) + return; + + smsg.uid = parv[2]; + smsg.mode = *parv[4]; + smsg.buf = parv[5]; + smsg.ext = parc >= 6 ? parv[6] : NULL; + hook_call_sasl_input(&smsg); + } +} + +static inline void verify_mlock(channel_t *c, time_t ts, const char *their_mlock) +{ + const char *mlock_str; + mychan_t *mc; + + mc = MYCHAN_FROM(c); + if (mc == NULL) + return; + + if (ts != 0 && ts != c->ts) + return mlock_sts(c); + + /* bounce MLOCK change if it doesn't match what we say it is */ + mlock_str = mychan_get_sts_mlock(mc); + if (strcmp(mlock_str, their_mlock)) + return mlock_sts(c); +} + +/* + * : METADATA : + * The sole piece of metadata we're interested in is 'accountname', set by Services, + * and kept by ircd. + * + * :services.barafranca METADATA w00t accountname :w00t + * :services.barafranca METADATA #moo 1274712456 mlock :nt + */ +static void m_metadata(sourceinfo_t *si, int parc, char *parv[]) +{ + user_t *u; + channel_t *c; + time_t ts; + char *certfp; + + if (parc > 3) + { + c = channel_find(parv[0]); + ts = atoi(parv[1]); + + if (!irccasecmp(parv[2], "mlock")) + verify_mlock(c, ts, parv[3]); + } + + if (!irccasecmp(parv[1], "accountname")) + { + /* find user */ + u = user_find(parv[0]); + + if (u == NULL) + return; + + if (parv[2][0] == '\0') + handle_clearlogin(si, u); + else if (si->s->flags & SF_EOB) + handle_setlogin(si, u, parv[2], 0); + else + handle_burstlogin(u, parv[2], 0); + } + else if (!irccasecmp(parv[1], "ssl_cert")) + { + // :419 METADATA 419AAAAAB ssl_cert :vTrse abcdef0123456789abcdef CN=danieldg + // :419 METADATA 419AAAAAC ssl_cert :vtrsE Error message for certificate + char *fpstr, *end; + size_t len; + + u = user_find(parv[0]); + + if (u == NULL) + return; + + fpstr = strchr(parv[2], ' '); + if (!fpstr++) + return; + // first, check for lack of valid certificate + end = strchr(parv[2], 'E'); + if (end && end < fpstr) + return; + end = strchr(fpstr, ' '); + + len = end ? (unsigned int)(end - fpstr) : strlen(fpstr); + + certfp = smalloc(len + 1); + memcpy(certfp, fpstr, len); + certfp[len] = '\0'; + + handle_certfp(si, u, certfp); + free(certfp); + } + else if (!irccasecmp(parv[1], "mlock")) + verify_mlock(c, 0, parv[2]); +} + +/* + * rsquit: + * remote/request squit + * when squitting a remote server, inspircd sends RSQUIT along the tree until it reaches the server that has + * the server to be squit as a local connection, which should then close it's connection and send SQUIT back + * to the rest of the network. + */ +static void m_rsquit(sourceinfo_t *si, int parc, char *parv[]) +{ + sts(":%s SQUIT %s :Jupe removed by %s", me.numeric, parv[0], si->su->nick); +} + +static void m_capab(sourceinfo_t *si, int parc, char *parv[]) +{ + int i, varc; + char *varv[256]; + + if (strcasecmp(parv[0], "START") == 0) + { + /* reset all our previously received CAPAB stuff */ + has_servicesmod = false; + has_globopsmod = false; + has_chghostmod = false; + has_cbanmod = false; + has_hidechansmod = false; + has_servprotectmod = false; + has_svshold = false; + has_shun = false; + has_protocol = 0; + } + else if (strcasecmp(parv[0], "CAPABILITIES") == 0 && parc > 1) + { + varc = sjtoken(parv[1], ' ', varv); + for (i = 0; i < varc; i++) + { + if (!strncmp(varv[i], "PROTOCOL=", 9)) + has_protocol = atoi(varv[i] + 9); + if(!strncmp(varv[i], "PREFIX=", 7)) + { + if (strstr(varv[i] + 7, "q")) + { + ircd->uses_owner = true; + } + if (strstr(varv[i] + 7, "a")) + { + ircd->uses_protect = true; + } + if (strstr(varv[i] + 7, "h")) + { + ircd->uses_halfops = true; + } + } + /* XXX check/store CHANMAX/IDENTMAX */ + } + } + else if (strcasecmp(parv[0], "MODULES") == 0 && parc > 1) + { + if (strstr(parv[1], "m_services_account.so")) + { + has_servicesmod = true; + } + if (strstr(parv[1], "m_cloaking.so")) + { + has_cloakingmod = true; + } + if (strstr(parv[1], "m_globops.so")) + { + has_globopsmod = true; + } + if (strstr(parv[1], "m_chghost.so")) + { + has_chghostmod = true; + } + if (strstr(parv[1], "m_cban.so")) + { + has_cbanmod = true; + } + if (strstr(parv[1], "m_hidechans.so")) + { + has_hidechansmod = true; + } + if (strstr(parv[1], "m_servprotect.so")) + { + has_servprotectmod = true; + } + if (strstr(parv[1], "m_svshold.so")) + { + has_svshold = true; + } + if (strstr(parv[1], "m_shun.so")) + { + has_shun = true; + } + if (strstr(parv[1], "m_mlock.so")) + { + has_mlock = true; + } + TAINT_ON(strstr(parv[1], "m_invisible.so") != NULL, "invisible (m_invisible) is not presently supported correctly in atheme, and won't be due to ethical obligations"); + TAINT_ON(strstr(parv[1], "m_serverbots.so") != NULL, "inspircd built-in services (m_serverbots) are not compatible with atheme"); + TAINT_ON(strstr(parv[1], "m_chanacl.so") != NULL, "inspircd built-in services (m_chanacl) are not compatible with atheme"); + TAINT_ON(strstr(parv[1], "m_chanregister.so") != NULL, "inspircd built-in services (m_chanregister) are not compatible with atheme"); + TAINT_ON(strstr(parv[1], "m_nickregister.so") != NULL, "inspircd built-in services (m_nickregister) are not compatible with atheme"); + TAINT_ON(strstr(parv[1], "m_namedmodes.so") != NULL, "namedmodes (m_namedmodes) are unsupported in Atheme due to the fact that any network can change modes around thus possibly breaking mlocks"); + TAINT_ON(strstr(parv[1], "m_opflags.so") != NULL, "inspircd built-in services (m_opflags) are not compatible with atheme"); + } + else if (strcasecmp(parv[0], "END") == 0) + { + if (has_servicesmod == false) + { + slog(LG_ERROR, "m_capab(): you didn't load m_services_account into inspircd. atheme support requires this module. exiting."); + exit(EXIT_FAILURE); + } + + if (has_chghostmod == false) + { + slog(LG_DEBUG, "m_capab(): you didn't load m_chghost into inspircd. vhost setting will not work."); + } + + if (has_cbanmod == false) + { + slog(LG_DEBUG, "m_capab(): you didn't load m_cban into inspircd. sqlines on channels will not work."); + } + + if (has_svshold == false) + { + slog(LG_INFO, "m_capab(): you didn't load m_svshold into inspircd. nickname enforcers will not work."); + } + + if (has_protocol < PROTOCOL_12BETA) + { + slog(LG_ERROR, "m_capab(): remote protocol version too old (%d). you may need another protocol module or a newer inspircd. exiting.", has_protocol); + exit(EXIT_FAILURE); + } + } + else + { + slog(LG_DEBUG, "m_capab(): unknown CAPAB type %s - out of date protocol module?", parv[0]); + } +} + +/* Server ended their burst: warn all their users if necessary -- jilles */ +static void server_eob(server_t *s) +{ + mowgli_node_t *n; + + MOWGLI_ITER_FOREACH(n, s->userlist.head) + { + handle_nickchange((user_t *)n->data); + } +} + +void _modinit(module_t * m) +{ + MODULE_TRY_REQUEST_DEPENDENCY(m, "transport/rfc1459"); + MODULE_TRY_REQUEST_DEPENDENCY(m, "protocol/base36uid"); + + /* Symbol relocation voodoo. */ + server_login = &inspircd_server_login; + introduce_nick = &inspircd_introduce_nick; + quit_sts = &inspircd_quit_sts; + wallops_sts = &inspircd_wallops_sts; + join_sts = &inspircd_join_sts; + chan_lowerts = &inspircd_chan_lowerts; + kick = &inspircd_kick; + msg = &inspircd_msg; + msg_global_sts = &inspircd_msg_global_sts; + notice_user_sts = &inspircd_notice_user_sts; + notice_global_sts = &inspircd_notice_global_sts; + notice_channel_sts = &inspircd_notice_channel_sts; + numeric_sts = &inspircd_numeric_sts; + kill_id_sts = &inspircd_kill_id_sts; + part_sts = &inspircd_part_sts; + kline_sts = &inspircd_kline_sts; + unkline_sts = &inspircd_unkline_sts; + qline_sts = &inspircd_qline_sts; + unqline_sts = &inspircd_unqline_sts; + topic_sts = &inspircd_topic_sts; + mode_sts = &inspircd_mode_sts; + ping_sts = &inspircd_ping_sts; + ircd_on_login = &inspircd_on_login; + ircd_on_logout = &inspircd_on_logout; + jupe = &inspircd_jupe; + sethost_sts = &inspircd_sethost_sts; + fnc_sts = &inspircd_fnc_sts; + invite_sts = &inspircd_invite_sts; + holdnick_sts = &inspircd_holdnick_sts; + svslogin_sts = &inspircd_svslogin_sts; + sasl_sts = &inspircd_sasl_sts; + quarantine_sts = &inspircd_quarantine_sts; + mlock_sts = &inspircd_mlock_sts; + + mode_list = inspircd_mode_list; + ignore_mode_list = inspircd_ignore_mode_list; + status_mode_list = inspircd_status_mode_list; + prefix_mode_list = inspircd_prefix_mode_list; + user_mode_list = inspircd_user_mode_list; + ignore_mode_list_size = ARRAY_SIZE(inspircd_ignore_mode_list); + + ircd = &InspIRCd; + + pcommand_add("PING", m_ping, 1, MSRC_USER | MSRC_SERVER); + pcommand_add("PONG", m_pong, 1, MSRC_SERVER); + pcommand_add("PRIVMSG", m_privmsg, 2, MSRC_USER | MSRC_SERVER); + pcommand_add("NOTICE", m_notice, 2, MSRC_USER | MSRC_SERVER | MSRC_UNREG); + pcommand_add("FJOIN", m_fjoin, 3, MSRC_SERVER); + pcommand_add("PART", m_part, 1, MSRC_USER); + pcommand_add("NICK", m_nick, 1, MSRC_USER | MSRC_SERVER); + pcommand_add("UID", m_uid, 9, MSRC_SERVER); + pcommand_add("QUIT", m_quit, 1, MSRC_USER); + pcommand_add("MODE", m_mode, 2, MSRC_USER | MSRC_SERVER); + pcommand_add("FMODE", m_fmode, 3, MSRC_USER | MSRC_SERVER); + pcommand_add("SAMODE", m_samode, 2, MSRC_USER); + pcommand_add("SAJOIN", m_sajoin, 2, MSRC_USER); + pcommand_add("SAPART", m_sapart, 2, MSRC_USER); + pcommand_add("SANICK", m_sanick, 2, MSRC_USER); + pcommand_add("SAQUIT", m_saquit, 1, MSRC_USER); + pcommand_add("SVSNICK", m_svsnick, 3, MSRC_USER | MSRC_SERVER); + pcommand_add("KICK", m_kick, 2, MSRC_USER | MSRC_SERVER); + pcommand_add("KILL", m_kill, 1, MSRC_USER | MSRC_SERVER); + pcommand_add("SQUIT", m_squit, 1, MSRC_USER | MSRC_SERVER); + pcommand_add("RSQUIT", m_rsquit, 1, MSRC_USER); + pcommand_add("SERVER", m_server, 4, MSRC_UNREG | MSRC_SERVER); + pcommand_add("STATS", m_stats, 2, MSRC_USER); + pcommand_add("MOTD", m_motd, 1, MSRC_USER); + pcommand_add("ADMIN", m_admin, 1, MSRC_USER); + pcommand_add("FTOPIC", m_ftopic, 4, MSRC_SERVER); + pcommand_add("JOIN", m_join, 1, MSRC_USER); + pcommand_add("ERROR", m_error, 1, MSRC_UNREG | MSRC_SERVER); + pcommand_add("TOPIC", m_topic, 2, MSRC_USER); + pcommand_add("FHOST", m_fhost, 1, MSRC_USER); + pcommand_add("IDLE", m_idle, 1, MSRC_USER); + pcommand_add("AWAY", m_away, 0, MSRC_USER); + pcommand_add("OPERTYPE", m_opertype, 1, MSRC_USER); + pcommand_add("METADATA", m_metadata, 3, MSRC_SERVER); + pcommand_add("CAPAB", m_capab, 1, MSRC_UNREG | MSRC_SERVER); + pcommand_add("ENCAP", m_encap, 2, MSRC_USER | MSRC_SERVER); + pcommand_add("ENDBURST", m_endburst, 0, MSRC_SERVER); + + hook_add_event("server_eob"); + hook_add_server_eob(server_eob); + + m->mflags = MODTYPE_CORE; + + pmodule_loaded = true; +} + +/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs + * vim:ts=8 + * vim:sw=8 + * vim:noexpandtab + */ diff --git a/inspircd12.c b/inspircd12.c new file mode 100644 index 0000000..81ddc36 --- /dev/null +++ b/inspircd12.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2005-2006 Atheme Development Group + * Rights to this code are documented in doc/LICENSE. + * + * The People's Democratic Republic of InspIRCd 1.2 support. + * + */ + +#include "atheme.h" +#include "uplink.h" +#include "pmodule.h" +#include "protocol/inspircd.h" + +DECLARE_MODULE_V1("protocol/inspircd12", true, _modinit, NULL, PACKAGE_STRING, "Atheme Developer Group "); + +/* *INDENT-OFF* */ + +ircd_t InspIRCd = { + "InspIRCd 1.2+", /* IRCd name */ + "$", /* TLD Prefix, used by Global. */ + true, /* Whether or not we use IRCNet/TS6 UID */ + false, /* Whether or not we use RCOMMAND */ + true, /* Whether or not we support channel owners. */ + true, /* Whether or not we support channel protection. */ + true, /* Whether or not we support halfops. */ + false, /* Whether or not we use P10 */ + true, /* Whether or not we use vHosts. */ + CMODE_OPERONLY | CMODE_PERM | CMODE_IMMUNE, /* Oper-only cmodes */ + CSTATUS_OWNER, /* Integer flag for owner channel flag. */ + CSTATUS_PROTECT, /* Integer flag for protect channel flag. */ + CSTATUS_HALFOP, /* Integer flag for halfops. */ + "+q", /* Mode we set for owner. */ + "+a", /* Mode we set for protect. */ + "+h", /* Mode we set for halfops. */ + PROTOCOL_INSPIRCD, /* Protocol type */ + CMODE_PERM, /* Permanent cmodes */ + CMODE_IMMUNE, /* Oper-immune cmode */ + "beIg", /* Ban-like cmodes */ + 'e', /* Except mchar */ + 'I', /* Invex mchar */ + IRCD_CIDR_BANS | IRCD_HOLDNICK /* Flags */ +}; + +struct cmode_ inspircd_mode_list[] = { + { 'i', CMODE_INVITE }, + { 'm', CMODE_MOD }, + { 'n', CMODE_NOEXT }, + { 'p', CMODE_PRIV }, + { 's', CMODE_SEC }, + { 't', CMODE_TOPIC }, + { 'c', CMODE_NOCOLOR }, + { 'M', CMODE_MODREG }, + { 'R', CMODE_REGONLY }, + { 'O', CMODE_OPERONLY }, + { 'S', CMODE_STRIP }, + { 'K', CMODE_NOKNOCK }, + { 'A', CMODE_NOINVITE }, + { 'C', CMODE_NOCTCP }, + { 'N', CMODE_STICKY }, + { 'G', CMODE_CENSOR }, + { 'P', CMODE_PERM }, + { 'B', CMODE_NOCAPS }, + { 'z', CMODE_SSLONLY }, + { 'T', CMODE_NONOTICE }, + { 'u', CMODE_HIDING }, + { 'Q', CMODE_PEACE }, + { 'Y', CMODE_IMMUNE }, + { 'D', CMODE_DELAYJOIN }, + { 'r', CMODE_CHANREG }, + { '\0', 0 } +}; + +static bool check_flood(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_nickflood(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_jointhrottle(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_history(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_forward(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_rejoindelay(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); +static bool check_delaymsg(const char *, channel_t *, mychan_t *, user_t *, myuser_t *); + +struct extmode inspircd_ignore_mode_list[] = { + { 'f', check_flood }, + { 'F', check_nickflood }, + { 'j', check_jointhrottle }, + { 'H', check_history }, + { 'L', check_forward }, + { 'J', check_rejoindelay }, + { 'd', check_delaymsg }, + { '\0', 0 } +}; + +struct cmode_ inspircd_status_mode_list[] = { + { 'q', CSTATUS_OWNER }, + { 'a', CSTATUS_PROTECT }, + { 'o', CSTATUS_OP }, + { 'h', CSTATUS_HALFOP }, + { 'v', CSTATUS_VOICE }, + { '\0', 0 } +}; + +struct cmode_ inspircd_prefix_mode_list[] = { + { '~', CSTATUS_OWNER }, + { '&', CSTATUS_PROTECT }, + { '@', CSTATUS_OP }, + { '%', CSTATUS_HALFOP }, + { '+', CSTATUS_VOICE }, + { '\0', 0 } +}; + +struct cmode_ inspircd_user_mode_list[] = { + { 'i', UF_INVIS }, + { 'o', UF_IRCOP }, + { 'd', UF_DEAF }, + { '\0', 0 } +}; + +#define PROTOCOL_12BETA 1201 /* we do not support anything older than this */ + +/* *INDENT-ON* */ + +static bool check_flood(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + /* +F doesn't support *, so don't bother checking for it -- w00t */ + return check_jointhrottle(value, c, mc, u, mu); +} + +static bool check_nickflood(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + return *value == '*' ? check_jointhrottle(value + 1, c, mc, u, mu) : check_jointhrottle(value, c, mc, u, mu); +} + +static bool check_jointhrottle(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *p, *arg2; + + p = value, arg2 = NULL; + while (*p != '\0') + { + if (*p == ':') + { + if (arg2 != NULL) + return false; + arg2 = p + 1; + } + else if (!isdigit(*p)) + return false; + p++; + } + if (arg2 == NULL) + return false; + if (p - arg2 > 10 || arg2 - value - 1 > 10 || !atoi(value) || !atoi(arg2)) + return false; + return true; +} + +static bool check_history(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + return check_jointhrottle(value, c, mc, u, mu); +} + +static bool check_forward(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + channel_t *target_c; + mychan_t *target_mc; + + if (*value != '#' || strlen(value) > 50) + return false; + if (u == NULL && mu == NULL) + return true; + target_c = channel_find(value); + target_mc = MYCHAN_FROM(target_c); + if (target_c == NULL && target_mc == NULL) + return false; + return true; +} + +static bool check_rejoindelay(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *ch = value; + + while (*ch) + { + if (!isdigit(*ch)) + return false; + ch++; + } + + if (atoi(value) <= 0) + { + return false; + } + else + { + return true; + } +} + +static bool check_delaymsg(const char *value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) +{ + const char *ch = value; + + while (*ch) + { + if (!isdigit(*ch)) + return false; + ch++; + } + + if (atoi(value) <= 0) + { + return false; + } + else + { + return true; + } +} + +void _modinit(module_t * m) +{ + MODULE_TRY_REQUEST_DEPENDENCY(m, "protocol/inspircd-aux"); + + /* Symbol relocation voodoo. */ + mode_list = inspircd_mode_list; + ignore_mode_list = inspircd_ignore_mode_list; + status_mode_list = inspircd_status_mode_list; + prefix_mode_list = inspircd_prefix_mode_list; + user_mode_list = inspircd_user_mode_list; + ignore_mode_list_size = ARRAY_SIZE(inspircd_ignore_mode_list); + + ircd = &InspIRCd; + + m->mflags = MODTYPE_CORE; + + pmodule_loaded = true; +} + +/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs + * vim:ts=8 + * vim:sw=8 + * vim:noexpandtab + */