Implemented initial support for zombie mode by Jan Krüger <jk@jk.gs>
authorpk910 <philipp@zoelle1.de>
Fri, 11 May 2012 01:55:33 +0000 (03:55 +0200)
committerpk910 <philipp@zoelle1.de>
Fri, 11 May 2012 01:59:50 +0000 (03:59 +0200)
This allows users to regain their status on the network after a ping
timeout. When a user pings out, the client structure is marked as a
zombie (FLAG_NOTCONN). Now the user has 15 minutes to reattach to that
client structure, using a separate Login on Connect feature.

When a user turns into a zombie, a ZO token is sent across the network;
the zombie status is reported in net bursts as the +Z umode.

> YY ZO YYXXX

When a user re-attaches, he regains all previous state information
(except oper privileges which are cleared), but retains his current
numnick.

> ZZ ZU ZZQQQ YYXXX

Here, ZZQQQ takes over all the state information of YYXXX.

Zombie mode is designed to be completely transparent to other users in
the sense that the user's disappearance should not be noticeable. The
current implementation silently drops all messages addressed to the
zombie in order to achieve this.

15 files changed:
include/client.h
include/handlers.h
include/msg.h
include/s_bsd.h
include/s_misc.h
include/s_user.h
ircd/Makefile.in
ircd/ircd.c
ircd/m_unzombie.c [new file with mode: 0644]
ircd/m_zombie.c [new file with mode: 0644]
ircd/parse.c
ircd/s_bsd.c
ircd/s_misc.c
ircd/s_user.c
ircd/send.c

index 5d754bf1cfff2a706660a6b5036128f970aa9dcb..a86633d2652d3c0aece42f9a0dfb0dff575747f1 100644 (file)
@@ -179,6 +179,7 @@ enum Flag
     FLAG_DEBUG,                     /**< send global debug/anti-hack info */
     FLAG_ACCOUNT,                   /**< account name has been set */
     FLAG_HIDDENHOST,                /**< user's host is hidden */
+    FLAG_NOTCONN,                   /**< nobody is connected to this client */
     FLAG_FAKEHOST,                  /**< user has a fakehost */
     FLAG_FAKEIDENT,                  /**< user has a fakeident */
     FLAG_NOCHAN,                    /**< hide user's channels for non-opers */
@@ -626,6 +627,8 @@ struct Client {
 #define IsAccount(x)            HasFlag(x, FLAG_ACCOUNT)
 /** Return non-zero if the client has set mode +x (hidden host). */
 #define IsHiddenHost(x)         HasFlag(x, FLAG_HIDDENHOST)
+/** Return non-zero if nobody is connected to this client structure */
+#define IsNotConn(x)            HasFlag(x, FLAG_NOTCONN)
 /** Return non-zero if the client has an active PING request. */
 #define IsPingSent(x)           HasFlag(x, FLAG_PINGSENT)
 /** Return non-zero if the client's channels are hidden. */
@@ -696,6 +699,8 @@ struct Client {
 #define SetAccount(x)           SetFlag(x, FLAG_ACCOUNT)
 /** Mark a client as having mode +x (hidden host). */
 #define SetHiddenHost(x)        SetFlag(x, FLAG_HIDDENHOST)
+/** Mark a client as not having anyone connected to it */
+#define SetNotConn(x)           SetFlag(x, FLAG_NOTCONN)
 /** Mark a client as having a fakehost. */
 #define SetFakeHost(x)          SetFlag(x, FLAG_FAKEHOST)
 #define SetFakeIdent(x)          SetFlag(x, FLAG_FAKEIDENT)
@@ -752,6 +757,8 @@ struct Client {
 #define ClearServNotice(x)      ClrFlag(x, FLAG_SERVNOTICE)
 /** Remove mode +x (hidden host) from the client. */
 #define ClearHiddenHost(x)      ClrFlag(x, FLAG_HIDDENHOST)
+/** Mark client as having someone connected to it */
+#define ClearNotConn(x)         ClrFlag(x, FLAG_NOTCONN)
 /** Remove fakehost flag from the flient. */
 #define ClearFakeHost(x)        ClrFlag(x, FLAG_FAKEHOST)
 #define ClearFakeIdent(x)        ClrFlag(x, FLAG_FAKEIDENT)
index 34d9be02d8bf6f64a65b70eb2e71be4ec4f5d393..ee594303c25dedb0bb4268a63b2744bd7d228c47 100644 (file)
@@ -235,6 +235,7 @@ extern int ms_svsjoin(struct Client*, struct Client*, int, char*[]);
 extern int ms_svspart(struct Client*, struct Client*, int, char*[]);
 extern int ms_topic(struct Client*, struct Client*, int, char*[]);
 extern int ms_trace(struct Client*, struct Client*, int, char*[]);
+extern int ms_unzombie(struct Client*, struct Client*, int, char*[]);
 extern int ms_uping(struct Client*, struct Client*, int, char*[]);
 extern int ms_version(struct Client*, struct Client*, int, char*[]);
 extern int ms_wallchops(struct Client*, struct Client*, int, char*[]);
@@ -242,6 +243,7 @@ extern int ms_wallops(struct Client*, struct Client*, int, char*[]);
 extern int ms_wallusers(struct Client*, struct Client*, int, char*[]);
 extern int ms_wallvoices(struct Client*, struct Client*, int, char*[]);
 extern int ms_whois(struct Client*, struct Client*, int, char*[]);
+extern int ms_zombie(struct Client*, struct Client*, int, char*[]);
 
 #endif /* INCLUDED_handlers_h */
 
index 59f921cf769dbbac4fca0818000c87eecaa3a141..9eb6572d06eb2642746ddd907d4123691360c9a6 100644 (file)
@@ -336,6 +336,14 @@ struct Client;
 #define TOK_ACCOUNT            "AC"
 #define CMD_ACCOUNT            MSG_ACCOUNT, TOK_ACCOUNT
 
+#define MSG_ZOMBIE             "ZOMBIE"        /* ZOMB */
+#define TOK_ZOMBIE             "ZO"
+#define CMD_ZOMBIE             MSG_ZOMBIE, TOK_ZOMBIE
+
+#define MSG_UNZOMBIE           "UNZOMBIE"      /* UZMB */
+#define TOK_UNZOMBIE           "ZU"
+#define CMD_UNZOMBIE           MSG_UNZOMBIE, TOK_UNZOMBIE
+
 #define MSG_ASLL               "ASLL"          /* ASLL */
 #define TOK_ASLL               "LL"
 #define CMD_ASLL               MSG_ASLL, TOK_ASLL
index 5bf4e675be9c32d9217a45f6d6d1c741d329ca4d..d842a4420a0bce551aa6cfa5b6ada398f357de8c 100644 (file)
@@ -66,6 +66,7 @@ extern unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf);
 extern int connect_server(struct ConfItem* aconf, struct Client* by);
 extern int  net_close_unregistered_connections(struct Client* source);
 extern void close_connection(struct Client *cptr);
+extern void connection_switch_to_client(struct Client *source, struct Client *target);
 extern void add_connection(struct Listener* listener, int fd, ssl_session_t *ssl);
 extern int  read_message(time_t delay);
 extern void init_server_identity(void);
index 13d7e2d982708e43c8bf409f1f94ebc2c1da3f1c..59dc5e45d1b730c3c1c1ed315e815b771d3c7010 100644 (file)
@@ -76,6 +76,10 @@ struct ServerStatistics {
 
 extern int check_registered(struct Client *sptr);
 extern int check_registered_user(struct Client *sptr);
+extern void zombie_client(struct Client *cptr, struct Client *killer,
+                          struct Client *victim);
+extern void unzombie_client(struct Client *cptr, struct Client *sptr,
+                            struct Client *acptr, struct Client *victim);
 extern int exit_client(struct Client *cptr, struct Client *bcptr,
     struct Client *sptr, const char *comment);
 extern char *myctime(time_t value);
index 970e45bc358c517326c424a21d456bf0f3ed3806..78035035da577ef33e271961781d71592f054d9c 100644 (file)
@@ -75,6 +75,8 @@ extern int set_nick_name(struct Client* cptr, struct Client* sptr,
                          const char* nick, int parc, char* parv[], unsigned int force);
 extern void send_umode_out(struct Client* cptr, struct Client* sptr,
                           struct Flags* old, int prop);
+extern void send_umode(struct Client *cptr, struct Client *sptr, struct Flags *old,
+                       int sendset);
 extern int whisper(struct Client* source, const char* nick,
                    const char* channel, const char* text, int is_notice);
 extern void send_user_info(struct Client* to, char* names, int rpl,
index 2e3cf13002608e9efb866ecbc1898d776e4277ce..476b540baf9479f1f3b624493ebe89a344780911 100644 (file)
@@ -179,6 +179,7 @@ IRCD_SRC = \
        m_time.c \
        m_topic.c \
        m_trace.c \
+       m_unzombie.c \
        m_uping.c \
        m_user.c \
        m_userhost.c \
@@ -192,6 +193,7 @@ IRCD_SRC = \
        m_who.c \
        m_whois.c \
        m_whowas.c \
+       m_zombie.c \
        match.c \
        memdebug.c \
        motd.c \
index f5f0eeb54d513403c0885f549129d52cb851895c..91ab9b41f6d5de57e42b85ce7153e62713039b5f 100644 (file)
@@ -339,7 +339,19 @@ static void check_pings(struct Event* ev) {
    
     if (!cptr)
       continue;
-     
+    
+    /* We don't need to check zombies here */
+    if (IsNotConn(cptr)) {
+      assert(IsUser(cptr));
+      /* for now: reap after fixed time (15 minutes) */
+      if ((CurrentTime - cli_user(cptr)->last) >= 900) {
+        SetFlag(cptr, FLAG_DEADSOCKET);
+        /* this will be used as exit message */
+        ircd_strncpy(cli_info(cptr), "Ping timeout", REALLEN);
+      } else
+        continue;
+    }
+    
     assert(&me != cptr);  /* I should never be in the local client array! */
    
 
@@ -420,6 +432,14 @@ static void check_pings(struct Event* ev) {
         sendto_opmask_butone(0, SNO_OLDSNO,
                              "No response from %s, closing link",
                              cli_name(cptr));
+      /*
+       * Keep client structure around when a user pings out, so that they can
+       * reconnect to it later
+       */
+      if (IsUser(cptr) && IsAccount(cptr)) {
+        zombie_client(&me, &me, cptr);
+        continue;
+      }
       exit_client_msg(cptr, cptr, &me, "Ping timeout");
       continue;
     }
diff --git a/ircd/m_unzombie.c b/ircd/m_unzombie.c
new file mode 100644 (file)
index 0000000..32adefa
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * IRC - Internet Relay Chat, ircd/m_unzombie.c
+ * Copyright (C) 2011 Jan Krueger <jk@jk.gs>
+ *
+ * See file AUTHORS in IRC package for additional names of
+ * the programmers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id$
+ */
+
+#include "config.h"
+
+#include "client.h"
+#include "ircd.h"
+#include "ircd_log.h"
+#include "ircd_reply.h"
+#include "ircd_string.h"
+#include "msg.h"
+#include "numnicks.h"
+#include "s_debug.h"
+#include "s_misc.h"
+#include "s_user.h"
+#include "send.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/** Handle an UNZOMBIE message from a server connection.
+ *
+ * \a parv has the following elements:
+ * \li \a parv[1] is the numnick of the client attaching to the zombie
+ * \li \a parv[2] is the numnick of the zombie
+ *
+ * See @ref m_functions for discussion of the arguments.
+ * @param[in] cptr Client that sent us the message.
+ * @param[in] sptr Original source of message.
+ * @param[in] parc Number of arguments.
+ * @param[in] parv Argument vector.
+ */
+int ms_unzombie(struct Client* cptr, struct Client* sptr, int parc,
+              char* parv[])
+{
+  struct Client *acptr;
+  struct Client *victim;
+
+  if (parc < 3)
+    return need_more_params(sptr, "UNZOMBIE");
+
+  if (!IsServer(sptr))
+    return protocol_violation(cptr, "UNZOMBIE from non-server %s",
+                             cli_name(sptr));
+
+  if (!(acptr = findNUser(parv[1])))
+    return 0; /* If this is colliding with a QUIT, let the QUIT win */
+
+  if (!(victim = findNUser(parv[2])))
+    /* TODO send error */
+    ;
+
+  if (!IsNotConn(victim))
+    return protocol_violation(cptr, "UNZOMBIE trying to attach to non-zombie %s",
+                             cli_name(victim));
+  assert(IsAccount(victim));
+
+  unzombie_client(cptr, sptr, acptr, victim);
+  return 0;
+}
diff --git a/ircd/m_zombie.c b/ircd/m_zombie.c
new file mode 100644 (file)
index 0000000..cd4b26d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * IRC - Internet Relay Chat, ircd/m_zombie.c
+ * Copyright (C) 2011 Jan Krueger <jk@jk.gs>
+ *
+ * See file AUTHORS in IRC package for additional names of
+ * the programmers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id$
+ */
+
+#include "config.h"
+
+#include "client.h"
+#include "ircd.h"
+#include "ircd_log.h"
+#include "ircd_reply.h"
+#include "ircd_string.h"
+#include "msg.h"
+#include "numnicks.h"
+#include "s_debug.h"
+#include "s_misc.h"
+#include "s_user.h"
+#include "send.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/** Handle a ZOMBIE message from a server connection.
+ *
+ * \a parv has the following elements:
+ * \li \a parv[1] is the numnick of the client to act on
+ *
+ * See @ref m_functions for discussion of the arguments.
+ * @param[in] cptr Client that sent us the message.
+ * @param[in] sptr Original source of message.
+ * @param[in] parc Number of arguments.
+ * @param[in] parv Argument vector.
+ */
+int ms_zombie(struct Client* cptr, struct Client* sptr, int parc,
+              char* parv[])
+{
+  struct Client *acptr;
+
+  if (parc < 2)
+    return need_more_params(sptr, "ZOMBIE");
+
+  if (!IsServer(sptr))
+    return protocol_violation(cptr, "ZOMBIE from non-server %s",
+                             cli_name(sptr));
+
+  if (!(acptr = findNUser(parv[1])))
+    return 0; /* Ignore for a user that QUIT; probably crossed (however unlikely) */
+
+  if (!IsAccount(acptr))
+    return protocol_violation(cptr, "ZOMBIE for user without account (%s)",
+                             cli_name(acptr));
+
+  zombie_client(cptr, sptr, acptr);
+  return 0;
+}
index 3c3c3d5c7e5bcb8e0e34ef18855a3240461d77ee..27608219400226b592cfc5c304687814c1c6173a 100644 (file)
@@ -610,6 +610,20 @@ struct Message msgtab[] = {
     /* UNREG, CLIENT, SERVER, OPER, SERVICE */
     { m_ignore, m_ignore, ms_account, m_ignore, m_ignore }
   },
+  {
+    MSG_ZOMBIE,
+    TOK_ZOMBIE,
+    0, MAXPARA, MFLG_SLOW, 0, NULL,
+    /* UNREG, CLIENT, SERVER, OPER, SERVICE */
+    { m_ignore, m_ignore, ms_zombie, m_ignore, m_ignore }
+  },
+  {
+    MSG_UNZOMBIE,
+    TOK_UNZOMBIE,
+    0, MAXPARA, MFLG_SLOW, 0, NULL,
+    /* UNREG, CLIENT, SERVER, OPER, SERVICE */
+    { m_ignore, m_ignore, ms_unzombie, m_ignore, m_ignore }
+  },
   {
     MSG_ASLL,
     TOK_ASLL,
index 00f7d8edc42ddba7005a0037bb8def01a6e72bed..fb842764a4152c36de12749765db47110bcb908e 100644 (file)
@@ -459,6 +459,34 @@ void close_connection(struct Client *cptr)
   }
 }
 
+/**
+ * Switches a client's connection over to a different, zombied client.
+ *
+ * @param source client to attach to zombied client
+ * @param target zombied client
+ */
+void connection_switch_to_client(struct Client *source, struct Client *target)
+{
+  assert(IsNotConn(target));
+  assert(MyConnect(source));
+  assert(-1 < cli_fd(source));
+  cli_connect(target) = cli_connect(source);
+  /* conveniently, this makes it much easier to get rid of source later on */
+  cli_from(target) = target;
+  LocalClientArray[cli_fd(source)] = target;
+
+  /* make main loop remove source soonish */
+  SetFlag(source, FLAG_DEADSOCKET);
+
+  /* need to copy over data from old client */
+  cli_user(target)->server = cli_user(source)->server;
+  strcpy(cli_yxx(target), cli_yxx(source));
+  cli_hopcount(target) = cli_hopcount(source);
+  cli_ip(target) = cli_ip(source);
+  strcpy(cli_username(target), cli_username(source));
+  strcpy(cli_user(target)->realhost, cli_user(source)->realhost);
+}
+
 /** Close all unregistered connections.
  * @param source Oper who requested the close.
  * @return Number of closed connections.
index 48b2dd94a93c92183b9cb16d182b48a7027d7bec..9993f58492ddf84e1167fbfe75a8d9be46e6ecaa 100644 (file)
@@ -320,6 +320,91 @@ static void exit_downlinks(struct Client *cptr, struct Client *sptr, char *comme
   }
 }
 
+/**
+ * Marks a local client as disconnected, and close its link if it is a local client.
+ *
+ * @param cptr server that notified us
+ * @param killer origin of decision to zombie \a victim
+ * @param victim zombied client
+ */
+void zombie_client(struct Client *cptr, struct Client *killer, struct Client *victim)
+{
+  assert(IsServer(cptr) || IsMe(cptr));
+  assert(IsServer(killer) || IsMe(killer));
+  assert(IsUser(victim));
+
+  /*
+   * Stop a running /LIST clean
+   */
+  if (MyUser(victim) && cli_listing(victim)) {
+    MyFree(cli_listing(victim));
+    cli_listing(victim) = NULL;
+  }
+
+  if (MyConnect(victim))
+    close_connection(victim);
+  /* need this so that main loop doesn't exit the client */
+  ClrFlag(victim, FLAG_DEADSOCKET);
+
+  SetNotConn(victim);
+  sendcmdto_serv_butone(killer, CMD_ZOMBIE, cptr, "%C", victim);
+}
+
+/**
+ * Attaches a client to a zombied client, removing the superfluous client in the process.
+ *
+ * @param cptr server that notified us
+ * @param sptr origin server of unzombie operation
+ * @param acptr client that is attaching to \a victim
+ * @param victim zombied client that someone is attaching to
+ */
+void unzombie_client(struct Client *cptr, struct Client *sptr, struct Client *acptr, struct Client *victim)
+{
+  assert(IsServer(cptr) || IsMe(cptr));
+  assert(IsServer(sptr) || IsMe(sptr));
+  assert(IsUser(acptr));
+  assert(IsNotConn(victim));
+
+  if (MyConnect(acptr))
+    connection_switch_to_client(acptr, victim);
+
+  ClearOper(victim);
+  ClearNotConn(victim);
+
+  if (MyConnect(victim)) {
+    /* inform client about "new" modes */
+    struct Flags setflags = cli_flags(acptr);
+    struct Membership *chan;
+    sendcmdto_one(acptr, CMD_NICK, victim, "%C", victim);
+    send_umode(victim, victim, &setflags, ALL_UMODES);
+
+    /*
+     * mark current client as zombie on all channels so that it does not show
+     * up in the memberships we'll resend below
+     */
+    for (chan = cli_user(acptr)->channel; chan; chan = chan->next_channel) {
+      SetZombie(chan);
+    }
+
+    /* resend channel memberships */
+    for (chan = cli_user(victim)->channel; chan; chan = chan->next_channel) {
+      struct Channel *chptr = chan->channel;
+      /* pretty unlikely to happen but let's handle this anyway */
+      if (IsZombie(chan))
+        continue;
+      sendcmdto_one(victim, CMD_JOIN, victim, ":%H", chptr);
+      if (chptr->topic[0]) {
+        send_reply(victim, RPL_TOPIC, chptr->chname, chptr->topic);
+        send_reply(victim, RPL_TOPICWHOTIME, chptr->chname, chptr->topic_nick,
+                  chptr->topic_time);
+      }
+      do_names(victim, chptr, NAMES_ALL|NAMES_EON); /* send /names list */
+    }
+  }
+
+  sendcmdto_serv_butone(sptr, CMD_UNZOMBIE, cptr, "%C %C", acptr, victim);
+}
+
 /* exit_client, rewritten 25-9-94 by Run */
 /**
  * Exits a client of *any* type (user, server, etc)
index 53cebf2602df7e646dd136b4238b39635972a014..f42cf9a0130438611eb59540029820c7ee176904 100644 (file)
@@ -589,7 +589,8 @@ static const struct UserMode {
   { FLAG_WEBIRC,      'W' },
   { FLAG_SEE_IDLETIME,'t' },
   { FLAG_SECURITY_SERV,'D' },
-  { FLAG_HIDDENHOST,  'x' }
+  { FLAG_HIDDENHOST,  'x' },
+  { FLAG_NOTCONN,     'Z' }
 };
 
 /** Length of #userModeList. */
@@ -1299,6 +1300,12 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc,
       case 'z': /* Formerly SSL mode; we ignore it. */
         break;
 #endif
+      case 'Z':
+        if (what == MODE_ADD)
+          SetNotConn(sptr);
+        else
+          ClearNotConn(sptr);
+        break;
       default:
         send_reply(sptr, ERR_UMODEUNKNOWNFLAG, *m);
         break;
@@ -1321,6 +1328,8 @@ int set_user_mode(struct Client *cptr, struct Client *sptr, int parc,
       ClrFlag(sptr, FLAG_FAKEHOST);
     if (!FlagHas(&setflags, FLAG_SEE_IDLETIME) && IsSeeIdletime(sptr))
       ClrFlag(sptr, FLAG_SEE_IDLETIME);
+    if (!FlagHas(&setflags, FLAG_NOTCONN) && IsNotConn(sptr))
+      ClrFlag(sptr, FLAG_NOTCONN);
     /*
      * new umode; servers and privileged opers can set it, local users cannot;
      * prevents users from /kick'ing or /mode -o'ing
index ff8d1158b0f0b06e25fbdaae4c539c4b0d3c64a1..921b37db28559e11c4e8ccd33bb05e6618964286 100644 (file)
@@ -108,7 +108,7 @@ static void dead_link(struct Client *to, char *notice)
 static int can_send(struct Client* to)
 {
   assert(0 != to);
-  return (IsDead(to) || IsMe(to) || -1 == cli_fd(to)) ? 0 : 1;
+  return (IsDead(to) || IsMe(to) || IsNotConn(to) || -1 == cli_fd(to)) ? 0 : 1;
 }
 
 /** Close the connection with the highest sendq.