/** * ptlink.C: This file contains protocol support for ptlink ircd. * * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team * Rights to this code are as documented in COPYING. * * * Portions of this file were derived from sources bearing the following license: * Copyright © 2003-2004 E. Will et al. * Copyright © 2005-2006 Atheme Development Group * Rights to this code are documented in doc/pod/license.pod. * * $Id: ptlink.C,v 1.8 2007/09/22 14:27:28 pippijn Exp $ */ #include #include "atheme.h" #include #include #include "servers.h" #include #include "uplink.h" #include "pmodule.h" #include "protocol/ptlink.h" static char const rcsid[] = "$Id: ptlink.C,v 1.8 2007/09/22 14:27:28 pippijn Exp $"; /* *INDENT-OFF* */ namespace protocol { static ircd_t PTLink = { "PTLink IRCd", /* IRCd name */ "$", /* TLD Prefix, used by Global. */ false, /* Whether or not we use IRCNet/TS6 UID */ false, /* Whether or not we use RCOMMAND */ false, /* 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. */ 0, /* Oper-only cmodes */ 0, /* Integer flag for owner channel flag. */ CMODE_PROTECT, /* Integer flag for protect channel flag. */ CMODE_HALFOP, /* Integer flag for halfops. */ "+", /* Mode we set for owner. */ "+a", /* Mode we set for protect. */ "+h", /* Mode we set for halfops. */ PROTOCOL_PTLINK, /* Protocol type */ 0, /* Permanent cmodes */ "b", /* Ban-like cmodes */ 0, /* Except mchar */ 0, /* Invex mchar */ 0 /* Flags */ }; struct ptlink_handler : handler { ptlink_handler (); virtual ~ptlink_handler (); virtual unsigned int server_login (void); virtual void introduce_nick (user_t *u); virtual void invite_sts (user_t *source, user_t *target, channel_t *channel); virtual void quit_sts (user_t *u, char const * const reason); virtual void wallops_sts (char const * const text); virtual void join_sts (channel_t *c, user_t *u, bool isnew, char const * const modes); virtual void kick (char const * const from, char const * const channel, char const * const to, char const * const reason); virtual void privmsg (char const * const from, char const * const target, char const * const fmt, ...); virtual void notice_user_sts (user_t *from, user_t *target, char const * const text); virtual void notice_global_sts (user_t *from, char const * const mask, char const * const text); virtual void notice_channel_sts (user_t *from, channel_t *target, char const * const text); virtual void numeric_sts (char const * const from, int numeric, char const * const target, char const * const fmt, ...); virtual void skill (char const * const from, char const * const nick, char const * const fmt, ...); virtual void part_sts (channel_t *c, user_t *u); virtual void kline_sts (char const * const server, char const * const user, char const * const host, long duration, char const * const reason); virtual void unkline_sts (char const * const server, char const * const user, char const * const host); virtual void topic_sts (channel_t *c, char const * const setter, time_t ts, time_t prevts, char const * const topic); virtual void mode_sts (char const * const sender, channel_t *target, char const * const modes); virtual void ping_sts (void); virtual void ircd_on_login (char const * const origin, char const * const user, char const * const wantedhost); virtual bool ircd_on_logout (char const * const origin, char const * const user, char const * const wantedhost); virtual void jupe (char const * const server, char const * const reason); virtual void sethost_sts (char const * const source, char const * const target, char const * const host); }; static cmode_t ptlink_mode_list[] = { { 'i', CMODE_INVITE }, { 'm', CMODE_MOD }, { 'n', CMODE_NOEXT }, { 'p', CMODE_PRIV }, { 's', CMODE_SEC }, { 't', CMODE_TOPIC }, { 'c', CMODE_NOCOLOR }, { 'q', CMODE_NOQUIT }, { 'd', CMODE_NOREPEAT }, { 'B', CMODE_NOBOTS }, { 'C', CMODE_SSLONLY }, { 'K', CMODE_KNOCK }, { 'N', CMODE_STICKY }, { 'R', CMODE_REGONLY }, { 'S', CMODE_NOSPAM }, { '\0', 0 } }; static bool check_flood(char const * const , channel_t *, mychan_t *, user_t *, myuser_t *); static extmode_t ptlink_ignore_mode_list[] = { { 'f', check_flood }, { '\0', 0 } }; static cmode_t ptlink_status_mode_list[] = { { 'a', CMODE_PROTECT}, { 'o', CMODE_OP }, { 'h', CMODE_HALFOP}, { 'v', CMODE_VOICE }, { '\0', 0 } }; static cmode_t ptlink_prefix_mode_list[] = { { '.', CMODE_PROTECT}, { '@', CMODE_OP }, { '%', CMODE_HALFOP}, { '+', CMODE_VOICE }, { '\0', 0 } }; /* *INDENT-ON* */ static bool check_flood (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu) { char const *p = value; char const *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 > 2 || arg2 - value - 1 > 2 || atoi (value) <= 1 || !atoi (arg2)) return false; return true; } /* login to our uplink */ unsigned int ptlink_handler::server_login (void) { int ret; ret = sts ("PASS %s :TS", curr_uplink->pass); if (ret == 1) return 1; me.bursting = true; sts ("CAPAB :QS PTS4"); sts ("SERVER %s 1 " PACKAGE_NAME "-%s :%s", me.name, version, me.desc); sts ("SVINFO 10 3 0 :%ld", NOW); services_init (); return 0; } /* introduce a client */ void ptlink_handler::introduce_nick (user_t *u) { sts ("NICK %s 1 %ld +%sp %s %s %s %s :%s", u->nick, u->ts, "io", u->user, u->host, u->host, me.name, u->gecos); } /* invite a user to a channel */ void ptlink_handler::invite_sts (user_t *sender, user_t *target, channel_t *channel) { sts (":%s INVITE %s %s", sender->nick, target->nick, channel->name); } void ptlink_handler::quit_sts (user_t *u, char const * const reason) { if (!me.connected) return; sts (":%s QUIT :%s", u->nick, reason); } /* WALLOPS wrapper */ void ptlink_handler::wallops_sts (char const * const text) { sts (":%s WALLOPS :%s", me.name, text); } /* join a channel */ void ptlink_handler::join_sts (channel_t *c, user_t *u, bool isnew, char const * const modes) { if (isnew) sts (":%s SJOIN %ld %s %s :@%s", me.name, c->ts, c->name, modes, u->nick); else sts (":%s SJOIN %ld %s + :@%s", me.name, c->ts, c->name, u->nick); } /* kicks a user from a channel */ void ptlink_handler::kick (char const * const from, char const * const channel, char const * const to, char const * const reason) { channel_t *chan = channel_find (channel); user_t *user = user_find (to); if (!chan || !user) return; sts (":%s KICK %s %s :%s", from, channel, to, reason); chanuser_delete (chan, user); } /* PRIVMSG wrapper */ void ptlink_handler::privmsg (char const * const from, char const * const target, char const * const fmt, ...) { va_list ap; char buf[BUFSIZE]; va_start (ap, fmt); vsnprintf (buf, BUFSIZE, fmt, ap); va_end (ap); sts (":%s PRIVMSG %s :%s", from, target, buf); } /* NOTICE wrapper */ void ptlink_handler::notice_user_sts (user_t *from, user_t *target, char const * const text) { sts (":%s NOTICE %s :%s", from ? from->nick : me.name, target->nick, text); } void ptlink_handler::notice_global_sts (user_t *from, char const * const mask, char const * const text) { sts (":%s NOTICE %s%s :%s", from ? from->nick : me.name, ircd->tldprefix, mask, text); } void ptlink_handler::notice_channel_sts (user_t *from, channel_t *target, char const * const text) { sts (":%s NOTICE %s :%s", from ? from->nick : me.name, target->name, text); } /* numeric wrapper */ void ptlink_handler::numeric_sts (char const * const from, int numeric, char const * const target, char const * const fmt, ...) { va_list ap; char buf[BUFSIZE]; va_start (ap, fmt); vsnprintf (buf, BUFSIZE, fmt, ap); va_end (ap); sts (":%s %d %s %s", from, numeric, target, buf); } /* KILL wrapper */ void ptlink_handler::skill (char const * const from, char const * const nick, char const * const fmt, ...) { va_list ap; char buf[BUFSIZE]; va_start (ap, fmt); vsnprintf (buf, BUFSIZE, fmt, ap); va_end (ap); sts (":%s KILL %s :%s!%s!%s (%s)", from, nick, from, from, from, buf); } /* PART wrapper */ void ptlink_handler::part_sts (channel_t *c, user_t *u) { sts (":%s PART %s", u->nick, c->name); } /* server-to-server KLINE wrapper */ void ptlink_handler::kline_sts (char const * const server, char const * const user, char const * const host, long duration, char const * const reason) { if (!me.connected) return; /* we don't know the real setter here :( */ sts (":%s GLINE %s@%s %ld %s :%s", opersvs.nick, user, host, duration, opersvs.nick, reason); } /* server-to-server UNKLINE wrapper */ void ptlink_handler::unkline_sts (char const * const server, char const * const user, char const * const host) { if (!me.connected) return; sts (":%s UNGLINE %s@%s", opersvs.nick, user, host); } /* topic wrapper */ void ptlink_handler::topic_sts (channel_t *c, char const * const setter, time_t ts, time_t prevts, char const * const topic) { if (!me.connected || !c) return; sts (":%s TOPIC %s %s %ld :%s", chansvs.nick, c->name, setter, ts, topic); } /* mode wrapper */ void ptlink_handler::mode_sts (char const * const sender, channel_t *target, char const * const modes) { if (!me.connected) return; sts (":%s MODE %s %s", sender, target->name, modes); } /* ping wrapper */ void ptlink_handler::ping_sts (void) { if (!me.connected) return; sts ("PING :%s", me.name); } /* protocol-specific stuff to do on login */ void ptlink_handler::ircd_on_login (char const * const origin, char const * const user, char const * const wantedhost) { user_t *u = user_find (origin); if (!me.connected || u == NULL) return; /* Can only do this for nickserv, and can only record identified * state if logged in to correct nick, sorry -- jilles */ if (should_reg_umode (u)) sts (":%s SVSMODE %s +r", me.name, origin); } /* protocol-specific stuff to do on login */ bool ptlink_handler::ircd_on_logout (char const * const origin, char const * const user, char const * const wantedhost) { if (!me.connected) return false; if (!nicksvs.no_nick_ownership) sts (":%s SVSMODE %s -r", me.name, origin); return false; } void ptlink_handler::jupe (char const * const server, char const * const reason) { if (!me.connected) return; server_delete (server); sts (":%s SQUIT %s :%s", opersvs.nick, server, reason); sts (":%s SERVER %s 2 " PACKAGE_NAME "-%s-jupe :%s", me.name, server, version, reason); } void ptlink_handler::sethost_sts (char const * const source, char const * const target, char const * const host) { user_t *tu = user_find (target); if (!tu) return; sts (":%s NEWMASK %s :%s", me.name, host, tu->nick); } static void m_topic (sourceinfo_t *si, int parc, char *parv[]) { channel_t *c = channel_find (parv[0]); if (!c) return; /* Our uplink is trying to change the topic during burst, * and we have already set a topic. Assume our change won. * -- jilles */ if (si->s != NULL && si->s->uplink == me.me && !(si->s->flags & SF_EOB) && c->topic != NULL) return; handle_topic_from (si, c, parv[1], atol (parv[2]), parv[3]); } static void m_ping (sourceinfo_t *si, int parc, char *parv[]) { /* reply to PING's */ sts (":%s PONG %s %s", me.name, me.name, parv[0]); } static void m_pong (sourceinfo_t *si, int parc, char *parv[]) { server_t *s; /* someone replied to our PING */ if (!parv[0]) return; s = server_find (parv[0]); if (s == NULL) return; handle_eob (s); if (irccasecmp (me.actual, parv[0])) return; me.uplinkpong = NOW; /* -> :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 synching to 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 synching to 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 (parc != 2) return; handle_message (si, parv[0], true, parv[1]); } static void m_sjoin (sourceinfo_t *si, int parc, char *parv[]) { /* -> :proteus.malkier.net SJOIN 1073516550 #shrike +tn :@sycobuny @+rakaur */ channel_t *c; unsigned int userc; char *userv[256]; unsigned int i; time_t ts; /* :origin SJOIN ts chan modestr [key or limits] :users */ c = channel_find (parv[1]); ts = atol (parv[0]); if (!c) { slog (LG_DEBUG, "m_sjoin(): new channel: %s", parv[1]); c = channel_add (parv[1], ts, si->s); } if (ts < c->ts) { chanuser_t *cu; node_t *n; /* the TS changed. a TS change requires the following things * to be done to the channel: reset all modes to nothing, remove * all status modes on known users on the channel (including ours), * and set the new TS. */ clear_simple_modes (c); LIST_FOREACH (n, c->members.head) { cu = (chanuser_t *) n->data; if (cu->user->server == me.me) { /* it's a service, reop */ sts (":%s PART %s :Reop", cu->user->nick, c->name); sts (":%s SJOIN %ld %s + :@%s", me.name, ts, c->name, cu->user->nick); cu->modes = CMODE_OP; } else cu->modes = 0; } slog (LG_DEBUG, "m_sjoin(): TS changed for %s (%ld -> %ld)", c->name, c->ts, ts); c->ts = ts; c->callback.tschange (c); } channel_mode (NULL, c, parc - 3, parv + 2); userc = sjtoken (parv[parc - 1], ' ', userv); for (i = 0; i < userc; i++) chanuser_add (c, userv[i]); 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_nick (sourceinfo_t *si, int parc, char *parv[]) { server_t *s; user_t *u; bool realchange; /* got the right number of args for an introduction? */ if (parc == 9) { s = server_find (parv[7]); if (!s) { slog (LG_DEBUG, "m_nick(): new user on nonexistant server: %s", parv[6]); return; } slog (LG_DEBUG, "m_nick(): new user on `%s': %s", s->name, parv[0]); u = user_add (parv[0], parv[4], parv[5], parv[6], NULL, NULL, parv[8], s, atoi (parv[0])); user_mode (u, parv[3]); /* Ok, we have the user ready to go. * Here's the deal -- if the user's SVID is before * the start time, and not 0, then check to see * if it's a registered account or not. * * If it IS registered, deal with that accordingly, * via handle_burstlogin(). --nenolod */ /* Changed to just check umode +r for now -- jilles */ /* This is ok because this ircd clears +r on nick changes * -- jilles */ if (strchr (parv[3], 'r')) handle_burstlogin (u, NULL); handle_nickchange (u); } /* if it's only 2 then it's a nickname change */ else if (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]); realchange = irccasecmp (si->su->nick, parv[0]); user_changenick (si->su, parv[0], atoi (parv[1])); /* fix up +r if necessary -- jilles */ if (realchange && should_reg_umode (si->su)) /* changed nick to registered one, reset +r */ sts (":%s SVSMODE %s +r", me.name, parv[0]); 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); } 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 user_mode (user_find (parv[0]), parv[1]); } 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 from %s", parv[0], parv[1]); 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]); s = handle_server (si, parv[0], NULL, atoi (parv[1]), parv[2]); if (s != NULL && s->uplink != me.me) { /* elicit PONG for EOB detection; pinging uplink is * already done elsewhere -- jilles */ sts (":%s PING %s %s", me.name, me.name, s->name); } } static void m_stats (sourceinfo_t *si, int parc, char *parv[]) { handle_stats (si->su, parv[0][0]); } static void m_admin (sourceinfo_t *si, int parc, char *parv[]) { handle_admin (si->su); } static void m_version (sourceinfo_t *si, int parc, char *parv[]) { handle_version (si->su); } static void m_info (sourceinfo_t *si, int parc, char *parv[]) { handle_info (si->su); } static void m_whois (sourceinfo_t *si, int parc, char *parv[]) { handle_whois (si->su, parv[1]); } static void m_trace (sourceinfo_t *si, int parc, char *parv[]) { handle_trace (si->su, parv[0], parc >= 2 ? parv[1] : NULL); } 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[]) { chanuser_t *cu; node_t *n, *tn; /* JOIN 0 is really a part from all channels */ if (parv[0][0] == '0') { LIST_FOREACH_SAFE (n, tn, si->su->channels.head) { cu = (chanuser_t *) n->data; chanuser_delete (cu->chan, si->su); } } } static void m_pass (sourceinfo_t *si, int parc, char *parv[]) { if (strcmp (curr_uplink->pass, parv[0])) { slog (LG_INFO, "m_pass(): password mismatch from uplink; aborting"); runflags |= RF_SHUTDOWN; } } 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_newmask (sourceinfo_t *si, int parc, char *parv[]) { user_t *target; char *p; target = parc >= 2 ? user_find (parv[1]) : si->su; if (target == NULL) return; p = strchr (parv[0], '@'); if (p != NULL) { strlcpy (target->vhost, p + 1, sizeof target->vhost); if ((size_t) (p - parv[0]) < sizeof target->user && p > parv[0]) { memcpy (target->user, parv[0], p - parv[0]); target->user[p - parv[0]] = '\0'; } } else strlcpy (target->vhost, parv[0], sizeof target->vhost); slog (LG_DEBUG, "m_newmask(): %s -> %s@%s", target->nick, target->user, target->vhost); } static void m_motd (sourceinfo_t *si, int parc, char *parv[]) { handle_motd (si->su); } static void nick_group (mynick_t *mn, myuser_t *mu, sourceinfo_t *si) { user_t *u; if (si->su != NULL && !irccasecmp (si->su->nick, mn->nick)) u = si->su; else u = user_find_named (mn->nick); if (u != NULL && should_reg_umode (u)) sts (":%s SVSMODE %s +r", me.name, u->nick); } static void nick_ungroup (mynick_t *mn, myuser_t *mu, sourceinfo_t *si) { user_t *u; if (si->su != NULL && !irccasecmp (si->su->nick, mn->nick)) u = si->su; else u = user_find_named (mn->nick); if (u != NULL && !nicksvs.no_nick_ownership) sts (":%s SVSMODE %s -r", me.name, u->nick); } static pcommand_t const pcommands[] = { { "PING", m_ping, 1, MSRC_USER | MSRC_SERVER }, { "PONG", m_pong, 1, MSRC_SERVER }, { "PRIVMSG", m_privmsg, 2, MSRC_USER }, { "NOTICE", m_notice, 2, MSRC_UNREG | MSRC_USER | MSRC_SERVER }, { "SJOIN", m_sjoin, 4, MSRC_SERVER }, { "NJOIN", m_sjoin, 2, MSRC_USER | MSRC_SERVER }, { "PART", m_part, 1, MSRC_USER }, { "NICK", m_nick, 2, MSRC_USER | MSRC_SERVER }, { "NNICK", m_nick, 2, MSRC_USER | MSRC_SERVER }, { "QUIT", m_quit, 1, MSRC_USER }, { "MODE", m_mode, 2, MSRC_USER | MSRC_SERVER }, { "KICK", m_kick, 2, MSRC_USER | MSRC_SERVER }, { "KILL", m_kill, 1, MSRC_USER | MSRC_SERVER }, { "SQUIT", m_squit, 1, MSRC_USER | MSRC_SERVER }, { "SERVER", m_server, 3, MSRC_UNREG | MSRC_SERVER }, { "STATS", m_stats, 2, MSRC_USER }, { "ADMIN", m_admin, 1, MSRC_USER }, { "VERSION", m_version, 1, MSRC_USER }, { "INFO", m_info, 1, MSRC_USER }, { "WHOIS", m_whois, 2, MSRC_USER }, { "TRACE", m_trace, 1, MSRC_USER }, { "AWAY", m_away, 0, MSRC_USER }, { "JOIN", m_join, 1, MSRC_USER }, { "PASS", m_pass, 1, MSRC_UNREG }, { "ERROR", m_error, 1, MSRC_UNREG | MSRC_SERVER }, { "TOPIC", m_topic, 4, MSRC_USER | MSRC_SERVER }, { "NEWMASK", m_newmask, 1, MSRC_USER | MSRC_SERVER }, { "MOTD", m_motd, 1, MSRC_USER }, }; ptlink_handler::ptlink_handler () { mode_list = ptlink_mode_list; ignore_mode_list = ptlink_ignore_mode_list; status_mode_list = ptlink_status_mode_list; prefix_mode_list = ptlink_prefix_mode_list; ircd = &PTLink; pcommand_add (&pcommands); mynick_t::callback.group.attach (nick_group); mynick_t::callback.ungroup.attach (nick_ungroup); } ptlink_handler::~ptlink_handler () { mode_list = NULL; ignore_mode_list = NULL; status_mode_list = NULL; prefix_mode_list = NULL; ircd = NULL; mynick_t::callback.group.detach (nick_group); mynick_t::callback.ungroup.detach (nick_ungroup); pcommand_delete (&pcommands); } } #define FACREG_TYPE protocol::ptlink_handler #define FACREG_TYPE_NAME "ptlink" #define FACREG_INTERFACE_TYPE protocol::handler #include