Author: Kev <klmitch@mit.edu>
authorKevin L. Mitchell <klmitch@mit.edu>
Sat, 2 Jan 2010 20:33:10 +0000 (20:33 +0000)
committerKevin L. Mitchell <klmitch@mit.edu>
Sat, 2 Jan 2010 20:33:10 +0000 (20:33 +0000)
Log Message:

Support for extension queries.  Think of it like PRIVMSG and NOTICE for
servers; this allows pseudoservers to send each other messages.
Additionally, iauth can send messages to and receive messages from
pseudoservers.  This mechanism is introduced to make login-on-connect
easier to code, by providing an in-band mechanism for iauth to connect
the login server.

NOTE:  THIS PATCH HAS NOT YET BEEN COMPILED OR TESTED.  Sorry, but I
just got back from vacation, and will be flying out again tomorrow.  I
figured I could commit now and get others to help me test :)

git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1925 c9e4aea6-c8fd-4c43-8297-357d70d61c8c

ChangeLog
doc/readme.iauth
doc/readme.xquery [new file with mode: 0644]
include/handlers.h
include/msg.h
include/s_auth.h
ircd/m_xquery.c [new file with mode: 0644]
ircd/m_xreply.c [new file with mode: 0644]
ircd/parse.c
ircd/s_auth.c

index 4f93dac2989ad6b5e43316aa62a285ee30d71dd8..613e88eb82c863d0a518707aadcc4009869eed8e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2010-01-01  Kevin L. Mitchell  <klmitch@mit.edu>
+
+       * doc/readme.xquery: documentation for design and use of the
+       extension query mechanism
+
+       * doc/readme.iauth: document X iauth message and X and x server
+       messages
+
+       * include/msg.h: declare XQUERY/XQ and XREPLY/XR commands/tokens
+
+       * include/handlers.h: declare mo_xquery(), ms_xquery(), and
+       ms_xreply() message handlers
+
+       * include/s_auth.h: declare auth_send_xreply() to forward replies
+       to extension queries to the iauth instance
+
+       * ircd/m_xquery.c: process extension queries
+
+       * ircd/m_xreply.c: process replies to extension queries
+
+       * ircd/parse.c: add XQUERY and XREPLY commands to the list
+
+       * ircd/s_auth.c: add support for extension queries to iauth
+       protocol
+
 2010-01-01  Michael Poole <mdpoole@troilus.org>
 
        * ircd/s_auth.c (iauth_do_spawn): Add debug output to diagnose
index cb2b4849d555d2b90f5d95c6a3ac5341e8ceaa5f..8b4aea7ba0778283c045468272d453628b866169 100644 (file)
@@ -114,8 +114,8 @@ Comments: This is an example message description.  Each message is a
   message may be sent during those states (restrictions only make
   sense when Next State is -).  The Next State field indicates which
   new state is implied by the message; a hyphen indicates no state
-  change is implied.  The X (Example) message is not a real message
-  type.
+  change is implied.  This is an example, not a description of the
+  actual X message.
 Compatibility: If we believe ircu behavior is different than ircd's,
   this describes ircd's behavior or expectations.
 
@@ -235,6 +235,33 @@ Comments: Indicates the server's name and upper bound on client
 Compatibility: ircd does not include the <capacity> information.
   The <id> should be ignored: ircd sends 0 and ircu sends -1.
 
+X - Extension Query Reply
+Syntax: <id> X <servername> <routing> :<reply>
+Example: -1 X channels.undernet.org 5/127.0.0.1/6667 :OK kev Logged in
+States: N/A
+Next State: -
+Comments: Used to deliver the reply to an extension query to the iauth
+  instance.  The <servername> parameter indicates the origin of the
+  reply.  The <routing> parameter is the same as was used in the X
+  message from the iauth instance, and can be used to pair the reply
+  with the original request.  The <reply> parameter contains the text
+  of the reply.
+Compatibility: This is an Undernet extension and ircd does not send
+  it.
+
+x - Extension Query Server Not Linked
+Syntax: <id> x <servername> <routing> :Server not online
+Example: -1 x channels.undernet.org 5/127.0.0.1/6667 :Server not online
+States: N/A
+Next State: -
+Comments: Used to indicate to the iauth instance that the server
+  specified in the X message is not presently linked to the network.
+  This will not detect the extension query being lost due to a network
+  break, so iauth instances should further implement a timeout
+  mechanism for extension queries.
+Compatibility: This is an Undernet extension and ircd does not send
+  it.
+
 IAUTH MESSAGES
 ==============
 
@@ -251,6 +278,7 @@ Comments: This is an example message description.  Each message is a
   accept this message.  Clients in other states should ignore the
   message or treat it as an error.  The Next State field, where
   present, indicates what the next state should be for the client.
+  This is an example, not a description of the actual X message.
 Compatibility: If we believe ircu behavior is different than ircd's,
   this describes ircd's behavior or expectations.
 
@@ -440,3 +468,15 @@ Comments: Indicates that the iauth instance believes the specified
   should be assigned to that class.
 Compatibility: This is an Undernet extension and ircd does not support
   this message.
+
+X - Extension Query
+Syntax: X <servername> <routing> :<query>
+Example: X channels.undernet.org 5/127.0.0.1/6667 :login kev pass
+Comments: Used by the iauth instance to send an extension query to
+  the server specified by <servername>.  The <routing> parameter is
+  not interpreted by the servers; it will be returned unchanged in
+  the extension query reply message (the X server message) and may be
+  used to pair the query with its reply.  The <query> parameter is
+  sent to <servername>.
+Compatibility: This is an Undernet extension and ircd does not support
+  this message.
diff --git a/doc/readme.xquery b/doc/readme.xquery
new file mode 100644 (file)
index 0000000..8ab9658
--- /dev/null
@@ -0,0 +1,96 @@
+OVERVIEW
+========
+
+The extension query mechanism provides a means by which servers may
+send queries to other servers and receive replies.  Obviously,
+ordinary ircu servers have no need of this mechanism, but it allows
+pseudo-server services to communicate with each other.  Additionally,
+extensions have been made to the iauth protocol (see readme.iauth) to
+allow iauth instances to send and receive extension queries.  This
+could be used, for instance, to submit client information for
+immediate proxy scanning by a centralized service, or to query a
+centralized database for log-in parameters.
+
+DETAILED DESCRIPTION
+====================
+
+The extension query mechanism consists of a pair of commands, the
+XQUERY command (token XQ) and the XREPLY command (token XR).  Servers
+and IRC operators may send an XQUERY, naming a target service, an
+opaque "routing" token, and the query; the target service is expected
+to reply with an XREPLY, which will include the routing token from the
+query and the service's reply to the query.
+
+The query syntax is:
+
+  <prefix> XQ <target> <routing> :<query>
+
+where <target> is the target service's numeric nick, <routing> is the
+opaque "routing" token, and <query> is the query for the service to
+act upon.  IRC operators may also issue queries, using the XQUERY
+command with the same parameters, with <target> permitted to be a
+server name mask; this is largely intended for debugging purposes.
+Ordinary users cannot issue XQUERY commands, in order to encourage use
+of the regular PRIVMSG and NOTICE commands.
+
+The reply syntax is:
+
+  <prefix> XR <target> <routing> :<reply>
+
+where <target> is the origin of the original query, <routing> is the
+opaque "routing" token from the query, and <reply> is the service's
+reply to the query.  This command can only be issued by servers.
+
+USE WITH IAUTH
+==============
+
+Three message extensions have been made to the iauth protocol.  An
+iauth instance can issue an XQUERY through the use of the "X" client
+message with the following syntax:
+
+  X <servername> <routing> :<query>
+
+If <servername> is not presently linked to the network, ircu will
+respond with an "x" server message, having the following syntax:
+
+  <id> x <servername> <routing> :Server not online
+
+If, on the other hand, <servername> names a valid, on-line server,
+ircu will prepend "iauth:" to the "routing" token and forward the
+query to that server.  If an XREPLY is received from the service, ircu
+will strip off the "iauth:" prefix on the "routing" token and send the
+reply to the iauth instance with the "X" server message:
+
+  <id> X <servername> <routing> :<reply>
+
+Having the "iauth:" prefix on the "routing" token enables future ircu
+extensions which wish to use the extension query mechanism to be
+differentiated from extension queries originated from iauth.
+
+RATIONALE
+=========
+
+The extension query mechanism was originated as part of an effort to
+establish a reliable login-on-connect system for Undernet.  Previous
+attempts at such a system required out-of-band parallel connections,
+and could possibly result in a compromise of hidden IPs (such as the
+IP of X's database server).  Further, without extensive extensions to
+GNUWorld, certain login restrictions--such as the maximum logged-in
+client count--could not be reliably enforced.  By providing an in-band
+signalling mechanism that iauth can make direct use of, these problems
+are eliminated; the only remaining problem is what to do if iauth is
+unable to communicate with the login service, which can be solved
+through policy decisions and timeouts implemented within the iauth
+instance.
+
+The rationale for the opaque "routing" token is to provide pairing
+between replies and queries.  The lack of such pairing is one of the
+shortcomings of the IRC protocol, as specified in RFC 1459; only one
+Undernet extension has attempted to provide such a pairing--a
+little-used extension to the /WHO command.  In an iauth context, such
+pairing is critical; otherwise, iauth could potentially apply a reply
+to the wrong client.  Although the pairing could be part of the query,
+it makes sense to make it part of the base protocol message, making it
+explicit.  This also allows ircu to add routing data to the token,
+making it possible for more extensions than just iauth to make use of
+extension queries.
index 6746a6d4a5b80bcfd42709bb168e657f0ab64ac3..1b96ee2c5901ac41d7a5a73ce0f63aa15e634a17 100644 (file)
@@ -170,6 +170,7 @@ extern int mo_uping(struct Client*, struct Client*, int, char*[]);
 extern int mo_version(struct Client*, struct Client*, int, char*[]);
 extern int mo_wallops(struct Client*, struct Client*, int, char*[]);
 extern int mo_wallusers(struct Client*, struct Client*, int, char*[]);
+extern int mo_xquery(struct Client*, struct Client*, int, char*[]);
 extern int mr_error(struct Client*, struct Client*, int, char*[]);
 extern int mr_error(struct Client*, struct Client*, int, char*[]);
 extern int mr_pong(struct Client*, struct Client*, int, char*[]);
@@ -225,6 +226,8 @@ 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_xquery(struct Client*, struct Client*, int, char*[]);
+extern int ms_xreply(struct Client*, struct Client*, int, char*[]);
 
 #endif /* INCLUDED_handlers_h */
 
index c9ded3a113e7e6e2ce610441983b8e73f5f21197..18b5ba37719478defc5a22a6f0d2ce6328b802f5 100644 (file)
@@ -360,6 +360,14 @@ struct Client;
 #define TOK_CAP                        "CAP"
 #define CMD_CAP                        MSG_CAP, TOK_CAP
 
+#define MSG_XQUERY             "XQUERY"
+#define TOK_XQUERY             "XQ"
+#define CMD_XQUERY             MSG_XQUERY, TOK_XQUERY
+
+#define MSG_XREPLY             "XREPLY"
+#define TOK_XREPLY             "XR"
+#define CMD_XREPLY             MSG_XREPLY, TOK_XREPLY
+
 /*
  * Constants
  */
index 14c55a1210b7d55832906e19d5ccd6888138eedc..e286d1e7a3f4e30e36ef162f4618fe01ffed9175 100644 (file)
@@ -45,6 +45,7 @@ extern void destroy_auth_request(struct AuthRequest *req);
 
 extern int auth_spawn(int argc, char *argv[]);
 extern void auth_send_exit(struct Client *cptr);
+extern void auth_send_xreply(struct Client *sptr, const char *routing, const char *reply);
 extern void auth_mark_closing(void);
 extern void auth_close_unused(void);
 extern void report_iauth_conf(struct Client *cptr, const struct StatDesc *sd, char *param);
diff --git a/ircd/m_xquery.c b/ircd/m_xquery.c
new file mode 100644 (file)
index 0000000..49d7e7e
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * IRC - Internet Relay Chat, ircd/m_xquery.c
+ * Copyright (C) 2010 Kevin L. Mitchell <klmitch@mit.edu>
+ *
+ * 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$
+ */
+
+/*
+ * m_functions execute protocol messages on this server:
+ *
+ *    cptr    is always NON-NULL, pointing to a *LOCAL* client
+ *            structure (with an open socket connected!). This
+ *            identifies the physical socket where the message
+ *            originated (or which caused the m_function to be
+ *            executed--some m_functions may call others...).
+ *
+ *    sptr    is the source of the message, defined by the
+ *            prefix part of the message if present. If not
+ *            or prefix not found, then sptr==cptr.
+ *
+ *            (!IsServer(cptr)) => (cptr == sptr), because
+ *            prefixes are taken *only* from servers...
+ *
+ *            (IsServer(cptr))
+ *                    (sptr == cptr) => the message didn't
+ *                    have the prefix.
+ *
+ *                    (sptr != cptr && IsServer(sptr) means
+ *                    the prefix specified servername. (?)
+ *
+ *                    (sptr != cptr && !IsServer(sptr) means
+ *                    that message originated from a remote
+ *                    user (not local).
+ *
+ *            combining
+ *
+ *            (!IsServer(sptr)) means that, sptr can safely
+ *            taken as defining the target structure of the
+ *            message in this server.
+ *
+ *    *Always* true (if 'parse' and others are working correct):
+ *
+ *    1)      sptr->from == cptr  (note: cptr->from == cptr)
+ *
+ *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
+ *            *cannot* be a local connection, unless it's
+ *            actually cptr!). [MyConnect(x) should probably
+ *            be defined as (x == x->from) --msa ]
+ *
+ *    parc    number of variable parameter strings (if zero,
+ *            parv is allowed to be NULL)
+ *
+ *    parv    a NULL terminated list of parameter pointers,
+ *
+ *                    parv[0], sender (prefix string), if not present
+ *                            this points to an empty string.
+ *                    parv[1]...parv[parc-1]
+ *                            pointers to additional parameters
+ *                    parv[parc] == NULL, *always*
+ *
+ *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
+ *                    non-NULL pointers.
+ */
+#include "config.h"
+
+#include "client.h"
+#include "ircd.h"
+#include "ircd_log.h"
+#include "ircd_string.h"
+#include "msg.h"
+#include "numeric.h"
+#include "numnicks.h"
+#include "send.h"
+
+#include <string.h>
+
+/*
+ * m_xquery - extension message handler
+ *
+ * parv[0] = sender prefix
+ * parv[1] = target server
+ * parv[2] = routing information
+ * parv[3] = extension message
+ */
+int mo_xquery(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
+{
+  struct client* acptr;
+
+  if (parc < 4) /* have enough parameters? */
+    return need_more_params(sptr, "XQUERY");
+
+  /* Look up the target server */
+  if (!(acptr = find_match_server(parv[1])))
+    return send_reply(sptr, ERR_NOSUCHSERVER, parv[1]);
+
+  /* If it's to us, do nothing; otherwise, forward the query */
+  if (!IsMe(acptr))
+    sendcmdto_one(sptr, CMD_XQUERY, acptr, "%C %s :%s", acptr, parv[2],
+                 parv[3]);
+
+  return 0;
+}
+
+/*
+ * ms_xquery - extension message handler
+ *
+ * parv[0] = sender prefix
+ * parv[1] = target server numeric
+ * parv[2] = routing information
+ * parv[3] = extension message
+ */
+int ms_xquery(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
+{
+  struct client* acptr;
+
+  if (parc < 4) /* have enough parameters? */
+    return need_more_params(sptr, "XQUERY");
+
+  /* Look up the target server */
+  if (!(acptr = FindNServer(parv[1])))
+    return send_reply(sptr, SND_EXPLICIT | ERR_NOSUCHSERVER,
+                     "* :Server has disconnected");
+
+  /* If it's to us, do nothing; otherwise, forward the query */
+  if (!IsMe(acptr))
+    sendcmdto_one(sptr, CMD_XQUERY, acptr, "%C %s :%s", acptr, parv[2],
+                 parv[3]);
+
+  return 0;
+}
diff --git a/ircd/m_xreply.c b/ircd/m_xreply.c
new file mode 100644 (file)
index 0000000..d43c9fe
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * IRC - Internet Relay Chat, ircd/m_xreply.c
+ * Copyright (C) 2010 Kevin L. Mitchell <klmitch@mit.edu>
+ *
+ * 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$
+ */
+
+/*
+ * m_functions execute protocol messages on this server:
+ *
+ *    cptr    is always NON-NULL, pointing to a *LOCAL* client
+ *            structure (with an open socket connected!). This
+ *            identifies the physical socket where the message
+ *            originated (or which caused the m_function to be
+ *            executed--some m_functions may call others...).
+ *
+ *    sptr    is the source of the message, defined by the
+ *            prefix part of the message if present. If not
+ *            or prefix not found, then sptr==cptr.
+ *
+ *            (!IsServer(cptr)) => (cptr == sptr), because
+ *            prefixes are taken *only* from servers...
+ *
+ *            (IsServer(cptr))
+ *                    (sptr == cptr) => the message didn't
+ *                    have the prefix.
+ *
+ *                    (sptr != cptr && IsServer(sptr) means
+ *                    the prefix specified servername. (?)
+ *
+ *                    (sptr != cptr && !IsServer(sptr) means
+ *                    that message originated from a remote
+ *                    user (not local).
+ *
+ *            combining
+ *
+ *            (!IsServer(sptr)) means that, sptr can safely
+ *            taken as defining the target structure of the
+ *            message in this server.
+ *
+ *    *Always* true (if 'parse' and others are working correct):
+ *
+ *    1)      sptr->from == cptr  (note: cptr->from == cptr)
+ *
+ *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
+ *            *cannot* be a local connection, unless it's
+ *            actually cptr!). [MyConnect(x) should probably
+ *            be defined as (x == x->from) --msa ]
+ *
+ *    parc    number of variable parameter strings (if zero,
+ *            parv is allowed to be NULL)
+ *
+ *    parv    a NULL terminated list of parameter pointers,
+ *
+ *                    parv[0], sender (prefix string), if not present
+ *                            this points to an empty string.
+ *                    parv[1]...parv[parc-1]
+ *                            pointers to additional parameters
+ *                    parv[parc] == NULL, *always*
+ *
+ *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
+ *                    non-NULL pointers.
+ */
+#include "config.h"
+
+#include "client.h"
+#include "ircd.h"
+#include "ircd_log.h"
+#include "ircd_string.h"
+#include "msg.h"
+#include "numeric.h"
+#include "s_auth.h"
+#include "send.h"
+
+#include <string.h>
+
+/*
+ * ms_xreply - extension message reply handler
+ *
+ * parv[0] = sender prefix
+ * parv[1] = target server numeric
+ * parv[2] = routing information
+ * parv[3] = extension message reply
+ */
+int ms_xreply(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
+{
+  struct client* acptr;
+  const char* routing;
+  const char* reply;
+
+  if (parc < 4) /* have enough parameters? */
+    return need_more_params(sptr, "XREPLY");
+
+  routing = parv[2];
+  reply = parv[3];
+
+  /* Look up the target */
+  if (!(acptr = FindNServer(parv[1])) && !(acptr = FindNUser(parv[1])))
+    return send_reply(sptr, SND_EXPLICIT | ERR_NOSUCHSERVER,
+                     "* :Server has disconnected");
+
+  /* If it's not to us, forward the reply */
+  if (!IsMe(acptr)) {
+    sendcmdto_one(sptr, CMD_XREPLY, acptr, "%C %s :%s", acptr, routing,
+                 reply);
+    return 0;
+  }
+
+  /* OK, figure out where to route the message */
+  if (!ircd_strncmp("iauth:", routing, 6)) {
+    /* Forward the reply to the iauth */
+    routing += 6;
+
+    auth_send_xreply(sptr, routing, reply);
+  } else
+    /* If we don't know where to route it, log it and drop it */
+    log_write(LS_SYSTEM, L_NOTICE, 0, "Received unroutable extension reply "
+             "from %#C to %#C routing %s; message: %s", sptr, acptr,
+             routing, reply);
+
+  return 0;
+}
index 9b6b5b3d4cece3f8f0268f68d2cea40f5369191f..65b7d020844bcee4ba8e83e64836095f99153db6 100644 (file)
@@ -617,6 +617,20 @@ struct Message msgtab[] = {
     /* UNREG, CLIENT, SERVER, OPER, SERVICE */
     { m_ignore, m_not_oper, ms_asll, mo_asll, m_ignore }
    },
+  {
+    MSG_XQUERY,
+    TOK_XQUERY,
+    0, MAXPARA, MFLG_SLOW, 0, NULL,
+    /* UNREG, CLIENT, SERVER, OPER, SERVICE */
+    { m_ignore, m_ignore, ms_xquery, mo_xquery, m_ignore }
+  },
+  {
+    MSG_XREPLY,
+    TOK_XREPLY,
+    0, MAXPARA, MFLG_SLOW, 0, NULL,
+    /* UNREG, CLIENT, SERVER, OPER, SERVICE */
+    { m_ignore, m_ignore, ms_xreply, m_ignore, m_ignore }
+  },
 #if WE_HAVE_A_REAL_CAPABILITY_NOW
   {
     MSG_CAP,
index d70ab971072a56b83e953760cd3ce28c24f02dba..02a91497e6cd89111924a404c379e3fa6e473f02 100644 (file)
@@ -52,6 +52,7 @@
 #include "list.h"
 #include "msg.h"       /* for MAXPARA */
 #include "numeric.h"
+#include "numnicks.h"
 #include "querycmds.h"
 #include "random.h"
 #include "res.h"
@@ -1140,6 +1141,17 @@ void auth_send_exit(struct Client *cptr)
   sendto_iauth(cptr, "D");
 }
 
+/** Forward an XREPLY on to iauth.
+ * @param[in] sptr Source of the XREPLY.
+ * @param[in] routing Routing information for the original XQUERY.
+ * @param[in] reply Contents of the reply.
+ */
+void auth_send_xreply(struct Client *sptr, const char *routing,
+                     const char *reply)
+{
+  sendto_iauth(NULL, "X %#C %s :%s", sptr, routing, reply);
+}
+
 /** Mark that a user has started capabilities negotiation.
  * This blocks authorization until auth_cap_done() is called.
  * @param[in] auth Authorization request for client.
@@ -2004,6 +2016,55 @@ static int iauth_cmd_challenge(struct IAuth *iauth, struct Client *cli,
   return 0;
 }
 
+/** Send an extension query to a specified remote server.
+ * @param[in] iauth Active IAuth session.
+ * @param[in] cli Client referenced by command.
+ * @param[in] parc Number of parameters (3).
+ * @param[in] params Remote server, routing information, and query.
+ * @return Zero.
+ */
+static int iauth_cmd_xquery(struct IAuth *iauth, struct Client *cli,
+                           int parc, char **params)
+{
+  const char *serv;
+  const char *routing;
+  const char *query;
+  struct Client *acptr;
+
+  /* Process parameters */
+  if (EmptyString(params[0])) {
+    sendto_iauth(cli, "E Missing :Missing server parameter");
+    return 0;
+  } else
+    serv = params[0];
+
+  if (EmptyString(params[1])) {
+    sendto_iauth(cli, "E Missing :Missing routing parameter");
+    return 0;
+  } else
+    routing = params[1];
+
+  if (EmptyString(params[2])) {
+    sendto_iauth(cli, "E Missing :Missing query parameter");
+    return 0;
+  } else
+    query = params[2];
+
+  /* Try to find the specified server */
+  if (!(acptr = find_match_server(serv))) {
+    sendto_iauth(cli, "x %s %s :Server not online", serv, routing);
+    return 0;
+  }
+
+  /* If it's to us, do nothing; otherwise, forward the query */
+  if (!IsMe(acptr))
+    /* The "iauth:" prefix helps ircu route the reply to iauth */
+    sendcmdto_one(&me, CMD_XQUERY, acptr, "%C iauth:%s :%s", acptr, routing,
+                 query);
+
+  return 0;
+}
+
 /** Parse a \a message from \a iauth.
  * @param[in] iauth Active IAuth session.
  * @param[in] message Message to be parsed.
@@ -2028,6 +2089,7 @@ static void iauth_parse(struct IAuth *iauth, char *message)
   case 'A': handler = iauth_cmd_config; has_cli = 0; break;
   case 's': handler = iauth_cmd_newstats; has_cli = 0; break;
   case 'S': handler = iauth_cmd_stats; has_cli = 0; break;
+  case 'X': handler = iauth_cmd_xquery; has_cli = 0; break;
   case 'o': handler = iauth_cmd_username_forced; has_cli = 1; break;
   case 'U': handler = iauth_cmd_username_good; has_cli = 1; break;
   case 'u': handler = iauth_cmd_username_bad; has_cli = 1; break;