]> jfr.im git - irc/freenode/solanum.git/commitdiff
Implement the resume extension origin/edk/resume
authorEd Kellett <redacted>
Sat, 25 Apr 2020 18:30:23 +0000 (19:30 +0100)
committerEd Kellett <redacted>
Sat, 17 Oct 2020 16:56:04 +0000 (17:56 +0100)
extensions/Makefile.am
extensions/resume.c [new file with mode: 0644]
include/client.h
include/s_user.h
ircd/s_user.c

index cf44a37024d9fd2b7562efbcbb22a16f8066fcdd..30aa7bee703d365e97a0b68b88cea990418f5be6 100644 (file)
@@ -79,6 +79,7 @@ extension_LTLIBRARIES =               \
   spy_trace_notice.la          \
   drain.la                     \
   identify_msg.la              \
+  resume.la                    \
   example_module.la
 
 if HAVE_HYPERSCAN
diff --git a/extensions/resume.c b/extensions/resume.c
new file mode 100644 (file)
index 0000000..bfec804
--- /dev/null
@@ -0,0 +1,708 @@
+#include "stdinc.h"
+#include "channel.h"
+#include "client.h"
+#include "hash.h"
+#include "hostmask.h"
+#include "ircd.h"
+#include "send.h"
+#include "packet.h"
+#include "s_conf.h"
+#include "s_user.h"
+#include "s_serv.h"
+#include "msg.h"
+#include "modules.h"
+
+static const char resume_desc[] = "Provides RESUME and BRB";
+
+#define BRB_TIMEOUT 300
+
+static int _modinit(void);
+static void _moddeinit(void);
+static void m_resume(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
+static void m_resumed(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
+static void m_brb(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
+static bool resume_visible(struct Client *);
+static void hook_cap_change(void *);
+static void hook_umode_changed(void *);
+static void hook_client_exit(void *);
+static void hook_sendq_cleared(void *);
+
+static unsigned int CLICAP_RESUME = 0;
+static unsigned int CAP_RESUME = 0;
+
+static rb_dictionary *resume_tree;
+static rb_dlink_list brb_list;
+static struct ev_entry *resume_check_ev;
+
+static struct Message resume_msgtab = {
+       "RESUME", 0, 0, 0, 0,
+       {{m_resume, 2}, {m_resume, 2}, mg_ignore, mg_ignore, mg_ignore, {m_resume, 2}}
+};
+
+static struct Message resumed_msgtab = {
+       "RESUMED", 0, 0, 0, 0,
+       {mg_ignore, mg_ignore, {m_resumed, 2}, mg_ignore, mg_ignore, mg_ignore}
+};
+
+static struct Message brb_msgtab = {
+       "BRB", 0, 0, 0, 0,
+       {mg_unreg, {m_brb, 1}, mg_ignore, mg_ignore, mg_ignore, mg_ignore}
+};
+
+static mapi_clist_av1 resume_clist[] = {
+       &resume_msgtab,
+       &resumed_msgtab,
+       &brb_msgtab,
+       NULL
+};
+
+static mapi_hfn_list_av1 resume_hfnlist[] = {
+       { "cap_change", hook_cap_change },
+       { "umode_changed", hook_umode_changed },
+       { "client_exit", hook_client_exit },
+       { "sendq_cleared", hook_sendq_cleared },
+       { NULL, NULL }
+};
+
+static struct ClientCapability resume_clicap = {
+       .visible = resume_visible
+};
+
+mapi_cap_list_av2 resume_cap_list[] = {
+       { MAPI_CAP_CLIENT, "resume", &resume_clicap, &CLICAP_RESUME },
+       { MAPI_CAP_SERVER, "RESUME", NULL, &CAP_RESUME },
+       { 0, NULL, NULL, NULL }
+};
+
+DECLARE_MODULE_AV2(resume, _modinit, _moddeinit, resume_clist, NULL, resume_hfnlist, resume_cap_list, NULL, resume_desc);
+
+enum brb_status
+{
+       BRB_NO,
+       BRB_ARMED,
+       BRB_DONE
+};
+
+struct resume_session
+{
+       struct Client *owner;
+       rb_dlink_node brb_node;
+       char *reason;
+       time_t brb_time;
+       enum brb_status brb;
+       unsigned char id[14];
+       unsigned char key[16];
+};
+
+static int cmp_resume(const void *a_, const void *b_)
+{
+       const char *a = a_, *b = b_;
+       return memcmp(a, b, sizeof ((struct resume_session){0}).id);
+}
+
+static bool resume_visible(struct Client *client)
+{
+       assert(MyConnect(client));
+       return IsSSL(client) && !IsOper(client);
+}
+
+static void resume_check(void *unused)
+{
+       rb_dlink_node *n, *tmp;
+       time_t now = rb_current_time();
+       static char reason[BUFSIZE];
+
+       RB_DLINK_FOREACH_SAFE(n, tmp, brb_list.head)
+       {
+               struct resume_session *rs = n->data;
+               if (now < rs->brb_time + BRB_TIMEOUT)
+                       break;
+               snprintf(reason, sizeof reason, "BRB: %s", rs->owner->user->away);
+               exit_client(NULL, rs->owner, rs->owner, reason);
+       }
+}
+
+static void sync_resumed_user(struct Client *target, bool brb)
+{
+       rb_dlink_node *ptr;
+       buf_head_t tempq;
+
+       if (brb)
+       {
+               tempq = target->localClient->buf_sendq;
+               rb_linebuf_newbuf(&target->localClient->buf_sendq);
+
+               /* XXX assumes the welcome can be sent instantly */
+               user_welcome(target);
+               sendto_one(target, ":%s NOTE RESUME PLAYBACK :History playback starts here", me.name);
+               send_queued(target);
+               target->localClient->buf_sendq = tempq;
+       }
+       else
+       {
+               user_welcome(target);
+       }
+
+       RB_DLINK_FOREACH(ptr, target->user->channel.head)
+       {
+               struct membership *msptr = ptr->data;
+               struct Channel *chptr = msptr->chptr;
+               char mode[10], modeval[NICKLEN * 2 + 2], *mptr = mode;
+
+               *modeval = '\0';
+
+               sendto_one(target, ":%s!%s@%s JOIN %s", target->name, target->username, target->host, chptr->chname);
+               channel_member_names(chptr, target, 1);
+
+               if (is_chanop(msptr))
+               {
+                       *mptr++ = 'o';
+                       strcat(modeval, target->name);
+                       strcat(modeval, " ");
+               }
+
+               if (is_voiced(msptr))
+               {
+                       *mptr++ = 'v';
+                       strcat(modeval, target->name);
+               }
+
+               if (*mode != '\0')
+                       sendto_one(target, ":%s MODE %s +%s %s", me.name, chptr->chname, mode, modeval);
+       }
+
+       sendto_one(target, "RESUME SUCCESS %s", target->name);
+
+       if (!brb)
+               sendto_one(target, ":%s WARN RESUME HISTORY_LOST :No history was preserved for this session", me.name);
+}
+
+static void announce_resume(struct Client *client, const char *oldhost, const char *status)
+{
+       rb_dlink_node *n;
+
+       sendto_server(client, NULL, CAP_RESUME | CAP_TS6, 0, ":%s RESUMED %s%s%s", use_id(client), client->host,
+               status ? " " : "",
+               status ? status : "");
+       sendto_server(client, NULL, CAP_EUID | CAP_TS6, CAP_RESUME, ":%s CHGHOST %s %s", use_id(client), use_id(client), client->host);
+       sendto_server(client, NULL, CAP_ENCAP | CAP_TS6, CAP_RESUME | CAP_EUID, ":%s ENCAP * CHGHOST %s %s", use_id(client), use_id(client), client->host);
+
+       sendto_common_channels_local_butone(client, CLICAP_RESUME, 0,
+               ":%s!%s@%s RESUMED %s%s%s", client->name, client->username, oldhost, client->host,
+               status ? " " : "",
+               status ? status : "");
+       sendto_common_channels_local_butone(client, 0, CLICAP_RESUME,
+               ":%s!%s@%s QUIT :Client reconnected", client->name, client->username, oldhost);
+
+       RB_DLINK_FOREACH(n, client->user->channel.head)
+       {
+               struct membership *msptr = n->data;
+               struct Channel *chptr = msptr->chptr;
+               char mode[10], modeval[NICKLEN * 2 + 2], *mptr = mode;
+
+               *modeval = '\0';
+
+               if (is_chanop(msptr))
+               {
+                       *mptr++ = 'o';
+                       strcat(modeval, client->name);
+                       strcat(modeval, " ");
+               }
+
+               if (is_voiced(msptr))
+               {
+                       *mptr++ = 'v';
+                       strcat(modeval, client->name);
+               }
+
+               *mptr = '\0';
+
+               sendto_channel_local_with_capability_butone(client, ALL_MEMBERS, NOCAPS, CLICAP_EXTENDED_JOIN | CLICAP_RESUME, chptr,
+                       ":%s!%s@%s JOIN %s", client->name, client->username, client->host, chptr->chname);
+               sendto_channel_local_with_capability_butone(client, ALL_MEMBERS, CLICAP_EXTENDED_JOIN, CLICAP_RESUME, chptr,
+                       ":%s!%s@%s JOIN %s %s :%s", client->name, client->username, client->host, chptr->chname,
+                       EmptyString(client->user->suser) ? "*" : client->user->suser, client->info);
+
+               if(*mode)
+                       sendto_channel_local_with_capability_butone(client, ALL_MEMBERS, NOCAPS, CLICAP_RESUME, chptr,
+                               ":%s MODE %s +%s %s", client->servptr->name, chptr->chname, mode, modeval);
+       }
+
+       /* Resend away message to away-notify enabled clients. */
+       if (client->user->away)
+               sendto_common_channels_local_butone(client, CLICAP_AWAY_NOTIFY, CLICAP_RESUME,
+                       ":%s!%s@%s AWAY :%s", client->name, client->username, client->host, client->user->away);
+}
+
+static void enable_resume(struct Client *client_p)
+{
+       struct resume_session *rs = rb_malloc(sizeof *rs);
+       static unsigned char token[sizeof rs->id + sizeof rs->key];
+       char *b64token;
+       rb_get_random(rs->id, sizeof rs->id);
+       rb_get_random(rs->key, sizeof rs->key);
+       rs->owner = client_p;
+       rs->brb = BRB_NO;
+       rs->reason = NULL;
+       client_p->localClient->resume = rs;
+       rb_dictionary_add(resume_tree, rs->id, rs);
+       memcpy(token, rs->id, sizeof rs->id);
+       memcpy(token + sizeof rs->id, rs->key, sizeof rs->key);
+       b64token = (char *)rb_base64_encode(token, sizeof token);
+       sendto_one(client_p, "RESUME TOKEN %s.%s", b64token, me.name);
+       rb_free(b64token);
+}
+
+static void disable_resume(struct Client *client_p)
+{
+       struct resume_session *rs = client_p->localClient->resume;
+       if (rs == NULL)
+               return;
+       client_p->localClient->resume = NULL;
+       client_p->localClient->caps &= ~CLICAP_RESUME;
+       if (rs->brb == BRB_DONE)
+               rb_dlinkDelete(&rs->brb_node, &brb_list);
+       if (rs->reason != NULL)
+               rb_free(rs->reason);
+       rb_dictionary_delete(resume_tree, rs->id);
+       rb_free(rs);
+}
+
+static int memneq(const void *a_, const void *b_, size_t s)
+{
+       volatile const unsigned char *a = a_, *b = b_;
+       volatile int r = 0;
+       for (size_t i = 0; i < s; i++)
+       {
+               r |= a[i] ^ b[i];
+       }
+       return r;
+}
+
+static bool invalidate_token(const char *tokstr)
+{
+       int tl;
+       struct rb_dictionary_element *elem;
+       struct resume_session *rs;
+       char *dot = strchr(tokstr, '.');
+       unsigned char *token = rb_base64_decode((const unsigned char *)tokstr,
+               dot != NULL ? dot - tokstr : strlen(tokstr), &tl);
+       struct Client *owner;
+
+       if (tl != sizeof rs->id + sizeof rs->key)
+       {
+               rb_free(token);
+               return false;
+       }
+
+       elem = rb_dictionary_find(resume_tree, token);
+       if (!elem)
+       {
+               rb_free(token);
+               return false;
+       }
+
+       rs = elem->data;
+       if (memneq(token + sizeof rs->id, rs->key, sizeof rs->key))
+       {
+               rb_free(token);
+               return false;
+       }
+
+       rb_free(token);
+
+       owner = rs->owner;
+       disable_resume(owner);
+       enable_resume(owner);
+       return true;
+}
+
+static void resync_connids(struct Client *client)
+{
+       rb_dlink_node *n;
+       RB_DLINK_FOREACH(n, client->localClient->connids.head)
+       {
+               uint32_t connid = RB_POINTER_TO_UINT(n->data);
+               del_from_cli_connid_hash(connid);
+               add_to_cli_connid_hash(client, connid);
+       }
+}
+
+static bool recheck_kline(struct Client *client)
+{
+       struct ConfItem *aconf = find_kline(client);
+
+       if(aconf == NULL)
+               return false;
+
+       if(IsExemptKline(client))
+       {
+               sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
+                       "KLINE over-ruled for %s, client is kline_exempt [%s@%s]",
+                       get_client_name(client, HIDE_IP),
+                       aconf->user, aconf->host);
+               return false;
+       }
+
+       sendto_realops_snomask(SNO_GENERAL, L_ALL,
+               "KLINE active for %s",
+               get_client_name(client, HIDE_IP));
+
+       notify_banned_client(client, aconf, K_LINED);
+       return true;
+}
+
+static void m_resume(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
+{
+       /* find resume session */
+       struct rb_dictionary_element *elem;
+       struct resume_session *rs;
+       int tl;
+
+       char *dot = strchr(parv[1], '.');
+
+       assert(client_p == source_p);
+
+       if (!IsSSL(source_p))
+       {
+               sendto_one(source_p, ":%s FAIL RESUME INSECURE_SESSION :You must use TLS to resume sessions", me.name);
+               if (invalidate_token(parv[1]))
+                       sendto_one_notice(source_p, "*** Your resume token was recognized, but has now been destroyed to protect against eavesdropping attacks.");
+               return;
+       }
+
+       if (!IsUnknown(source_p))
+       {
+               sendto_one(source_p, ":%s FAIL RESUME REGISTRATION_IS_COMPLETED :You have already registered", me.name);
+               return;
+       }
+
+       if (source_p->localClient->sasl_agent[0] != '\0' || source_p->localClient->sasl_complete)
+       {
+               sendto_one(source_p, ":%s FAIL RESUME CANNOT_RESUME :You must resume before starting SASL", me.name);
+               return;
+       }
+
+       if (dot != NULL && dot[1] != '\0' && irccmp(dot + 1, me.name))
+       {
+               sendto_one(source_p, ":%s FAIL RESUME WRONG_SERVER %s :This token must be redeemed on %s", me.name, dot + 1, dot + 1);
+               return;
+       }
+
+       unsigned char *token = rb_base64_decode((const unsigned char *)parv[1],
+               dot != NULL ? dot - parv[1] : strlen(parv[1]), &tl);
+
+       if (tl != sizeof rs->id + sizeof rs->key)
+       {
+               sendto_one(source_p, ":%s FAIL RESUME INVALID_TOKEN :Resume token unrecognized", me.name);
+               rb_free(token);
+               return;
+       }
+
+       elem = rb_dictionary_find(resume_tree, token);
+
+       if (!elem)
+       {
+               sendto_one(source_p, ":%s FAIL RESUME INVALID_TOKEN :Resume token unrecognized", me.name);
+               rb_free(token);
+               return;
+       }
+
+       rs = elem->data;
+
+       if (memneq(token + sizeof rs->id, rs->key, sizeof rs->key))
+       {
+               sendto_one(source_p, ":%s FAIL RESUME INVALID_TOKEN :Resume token unrecognized", me.name);
+               rb_free(token);
+               return;
+       }
+
+       rb_free(token);
+
+       struct Client *victim = rs->owner;
+       enum brb_status brb = rs->brb;
+       char *reason = rs->reason;
+       rs->reason = NULL;
+
+       disable_resume(victim);
+
+       if (IsOper(victim))
+       {
+               sendto_one(source_p, ":%s FAIL RESUME CANNOT_RESUME :Cowardly refusing to resume an oper", me.name);
+               rb_free(reason);
+               return;
+       }
+
+       rb_fde_t *tempF;
+       struct _ssl_ctl *tempssl;
+       struct ws_ctl *tempws;
+       buf_head_t tempq;
+       unsigned int tempcaps;
+       rb_dlink_list templ;
+       static char temphost[(HOSTLEN > HOSTIPLEN ? HOSTLEN : HOSTIPLEN) + 1];
+
+       tempF = source_p->localClient->F;
+       source_p->localClient->F = victim->localClient->F;
+       victim->localClient->F = tempF;
+       rb_setselect(victim->localClient->F, RB_SELECT_READ, read_packet, victim);
+
+       tempssl = source_p->localClient->ssl_ctl;
+       source_p->localClient->ssl_ctl = victim->localClient->ssl_ctl;
+       victim->localClient->ssl_ctl = tempssl;
+
+       tempws = source_p->localClient->ws_ctl;
+       source_p->localClient->ws_ctl = victim->localClient->ws_ctl;
+       victim->localClient->ws_ctl = tempws;
+
+       templ = source_p->localClient->connids;
+       source_p->localClient->connids = victim->localClient->connids;
+       victim->localClient->connids = templ;
+       resync_connids(source_p);
+       resync_connids(victim);
+
+       tempcaps = source_p->localClient->caps;
+       source_p->localClient->caps = victim->localClient->caps;
+       victim->localClient->caps = tempcaps;
+
+       strcpy(source_p->orighost, victim->orighost);
+       if (!IsIPSpoof(victim))
+       {
+               strcpy(victim->orighost, source_p->host);
+       }
+
+       strcpy(temphost, source_p->sockhost);
+       strcpy(source_p->sockhost, victim->sockhost);
+       strcpy(victim->sockhost, temphost);
+
+       strcpy(temphost, victim->host);
+       if (!IsIPSpoof(victim) && !IsDynSpoof(victim))
+       {
+               strcpy(victim->host, source_p->host);
+               strcpy(source_p->host, temphost);
+       }
+
+       if (irccmp(victim->host, victim->orighost))
+               SetDynSpoof(victim);
+       else
+               ClearDynSpoof(victim);
+
+       del_from_hostname_hash(source_p->orighost, victim);
+       add_to_hostname_hash(victim->orighost, victim);
+
+       rb_linebuf_donebuf(&victim->localClient->buf_recvq);
+       tempq = source_p->localClient->buf_recvq;
+       source_p->localClient->buf_recvq = victim->localClient->buf_recvq;
+       victim->localClient->buf_recvq = tempq;
+
+       if (source_p->localClient->resume != NULL)
+       {
+               victim->localClient->resume = source_p->localClient->resume;
+               victim->localClient->resume->owner = victim;
+               source_p->localClient->resume = NULL;
+       }
+
+       victim->localClient->sasl_complete = 0;
+       *victim->localClient->sasl_agent = '\0';
+
+       exit_client(source_p, source_p, source_p, "Connection resumed");
+
+       if (recheck_kline(victim))
+       {
+               rb_free(reason);
+               return;
+       }
+
+       if (brb != BRB_DONE)
+               rb_linebuf_donebuf(&victim->localClient->buf_sendq);
+
+       ClearFlush(victim);
+       sync_resumed_user(victim, brb == BRB_DONE);
+
+       if (IsAnyDead(victim))
+       {
+               rb_free(reason);
+               return;
+       }
+
+       if (brb == BRB_DONE)
+       {
+               if (reason == NULL)
+               {
+                       free_away(victim);
+                       sendto_server(victim, NULL, CAP_TS6, NOCAPS, ":%s AWAY", use_id(victim));
+                       sendto_common_channels_local_butone(victim, CLICAP_AWAY_NOTIFY, NOCAPS, ":%s!%s@%s AWAY",
+                                                           victim->name, victim->username, temphost);
+               }
+               else if (strncmp(victim->user->away, reason, AWAYLEN - 1))
+               {
+                       rb_strlcpy(victim->user->away, reason, AWAYLEN);
+                       sendto_server(victim, NULL, CAP_TS6, NOCAPS, ":%s AWAY :%s", use_id(victim), victim->user->away);
+                       sendto_common_channels_local_butone(victim, CLICAP_AWAY_NOTIFY, NOCAPS,
+                               ":%s!%s@%s AWAY :%s", victim->name, victim->username, temphost, victim->user->away);
+               }
+       }
+       rb_free(reason);
+
+       const char *status = brb == BRB_DONE ? "ok" : NULL;
+       announce_resume(victim, temphost, status);
+
+       if (!IsIPSpoof(victim))
+               sendto_server(NULL, NULL, CAP_EUID | CAP_TS6, 0, ":%s ENCAP * REALHOST %s", use_id(victim), victim->orighost);
+}
+
+static void m_resumed(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
+{
+       const char *status = NULL;
+       char oldhost[HOSTLEN + 1];
+       if (parc >= 3)
+               status = parv[2];
+       rb_strlcpy(oldhost, source_p->host, sizeof oldhost);
+       rb_strlcpy(source_p->host, parv[1], sizeof source_p->host);
+       announce_resume(source_p, oldhost, status);
+}
+
+static void do_brb(struct Client *client, const char *reason)
+{
+       struct resume_session *rs = client->localClient->resume;
+       rs->brb = BRB_DONE;
+       sendto_one(client, "BRB %d", BRB_TIMEOUT);
+       send_queued(client);
+       SetFlush(client);
+       rb_close(client->localClient->F);
+       client->localClient->F = NULL;
+       client->localClient->lasttime = rb_current_time() + BRB_TIMEOUT;
+       rs->brb_time = rb_current_time();
+       rb_dlinkAddTail(rs, &rs->brb_node, &brb_list);
+       if (rs->reason != NULL)
+       {
+               rb_free(rs->reason);
+               rs->reason = NULL;
+       }
+
+       if (client->user->away == NULL)
+               allocate_away(client);
+       if (strncmp(client->user->away, reason, AWAYLEN - 1))
+       {
+               if (client->user->away[0] != '\0')
+                       rs->reason = rb_strdup(client->user->away);
+               rb_strlcpy(client->user->away, reason, AWAYLEN);
+               sendto_server(client, NULL, CAP_TS6, NOCAPS, ":%s AWAY :%s", use_id(client), client->user->away);
+               sendto_common_channels_local_butone(client, CLICAP_AWAY_NOTIFY, NOCAPS,
+                       ":%s!%s@%s AWAY :%s", client->name, client->username, client->host, client->user->away);
+       }
+}
+
+static void m_brb(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
+{
+       struct resume_session *rs = source_p->localClient->resume;
+
+       if (rs == NULL)
+       {
+               sendto_one(source_p, ":%s FAIL BRB CANNOT_BRB :You do not have a resume token. CAP REQ resume first.", me.name);
+               return;
+       }
+
+       assert(rs->brb == BRB_NO);
+
+       rb_linebuf_donebuf(&source_p->localClient->buf_recvq);
+       rb_setselect(source_p->localClient->F, RB_SELECT_READ, NULL, NULL);
+
+       if (rb_linebuf_len(&source_p->localClient->buf_sendq) != 0)
+       {
+               rs->brb = BRB_ARMED;
+               rs->reason = rb_strdup(parv[1]);
+       }
+       else
+       {
+               do_brb(source_p, parv[1]);
+       }
+}
+
+static int _modinit(void)
+{
+       rb_dlink_node *n;
+       resume_tree = rb_dictionary_create("resume", cmp_resume);
+       RB_DLINK_FOREACH(n, lclient_list.head)
+       {
+               struct Client *client = n->data;
+               struct resume_session *rs = client->localClient->resume;
+               bool is_resume = (client->localClient->caps & CLICAP_RESUME) != 0;
+
+               assert(!is_resume || rs);
+
+               if (!is_resume && rs != NULL)
+               {
+                       client->localClient->resume = NULL;
+                       rb_free(rs);
+               }
+               else if (rs != NULL)
+               {
+                       if (IsOper(client))
+                               disable_resume(client);
+                       else
+                               rb_dictionary_add(resume_tree, rs->id, rs);
+               }
+       }
+
+       resume_check_ev = rb_event_add("resume_check", resume_check, NULL, 10);
+
+       return 0;
+}
+
+static void _moddeinit(void)
+{
+       rb_dictionary_destroy(resume_tree, NULL, NULL);
+       rb_event_delete(resume_check_ev);
+}
+
+static void hook_cap_change(void *data_)
+{
+       hook_data_cap_change *data = data_;
+
+       if (data->del & CLICAP_RESUME)
+               disable_resume(data->client);
+       else if (data->add & CLICAP_RESUME)
+               enable_resume(data->client);
+}
+
+static void hook_umode_changed(void *data_)
+{
+       hook_data_umode_changed *data = data_;
+       bool was_oper = !!(data->oldumodes & UMODE_OPER);
+
+       if (!MyClient(data->client))
+               return;
+
+       if (was_oper && !IsOper(data->client) && IsCapable(data->client, CLICAP_CAP_NOTIFY))
+               sendto_one(data->client, "CAP %s NEW :resume", data->client->name);
+       if (!was_oper && IsOper(data->client) && IsCapable(data->client, CLICAP_CAP_NOTIFY))
+               sendto_one(data->client, "CAP %s DEL :resume", data->client->name);
+
+       if (MyOper(data->client) && data->client->localClient->resume != NULL)
+       {
+               disable_resume(data->client);
+               sendto_one_notice(data->client, "You're too cool for resume");
+       }
+}
+
+static void hook_client_exit(void *data_)
+{
+       hook_data_client_exit *data = data_;
+
+       if (!MyClient(data->target))
+               return;
+
+       if (data->target->localClient->resume)
+               disable_resume(data->target);
+}
+
+static void hook_sendq_cleared(void *data_)
+{
+       hook_data_client *data = data_;
+       struct resume_session *rs = data->client->localClient->resume;
+
+       if (rs != NULL && rs->brb == BRB_ARMED)
+       {
+               do_brb(data->client, rs->reason);
+       }
+}
index cb5ec43d26210b8b8cf48e397748440538b4a06e..a9092d4c62628cfe036083613ee957ef53fb5c86 100644 (file)
@@ -281,6 +281,8 @@ struct LocalUser
        uint16_t cork_count;                    /* used for corking/uncorking connections */
        struct ev_entry *event;                 /* used for associated events */
 
+       struct resume_session *resume;
+
        char sasl_agent[IDLEN];
        unsigned char sasl_out;
        unsigned char sasl_complete;
index e0f41aa30cfc53c0d0aeb4d4edc510118685ed8d..3329760032212c492bcb375f01ea270093c14d44 100644 (file)
@@ -40,6 +40,7 @@ extern void send_umode(struct Client *, struct Client *, int, char *);
 extern void send_umode_out(struct Client *, struct Client *, int);
 extern void show_lusers(struct Client *source_p);
 extern int register_local_user(struct Client *, struct Client *);
+extern void user_welcome(struct Client *source_p);
 
 extern void introduce_client(struct Client *client_p, struct Client *source_p,
                            struct User *user, const char *nick, int use_euid);
index 9b9bb1c750a9166f29d59d7414b6f5e8aad608a9..e41aca2ea68a72a248d53c69ebc57963772f3e24 100644 (file)
@@ -53,7 +53,6 @@
 #include "s_assert.h"
 
 static void report_and_set_user_flags(struct Client *, struct ConfItem *);
-void user_welcome(struct Client *source_p);
 
 char umodebuf[128];