]> arthur.barton.de Git - ngircd-alex.git/commitdiff
Merge branch 'capabilities'
authorAlexander Barton <alex@barton.de>
Fri, 27 Apr 2012 22:49:37 +0000 (00:49 +0200)
committerAlexander Barton <alex@barton.de>
Fri, 27 Apr 2012 22:49:37 +0000 (00:49 +0200)
* capabilities:
  "multi-prefix" capability 2/2: adjust NAME and WHO handlers
  "multi-prefix" capability 1/2: implement complete CAP infrastructure
  IRC_Send_NAMES(): Code cleanup
  New function Client_CapSet() in addition to Client_Cap{Add|Del}
  "CAP REQ" starts capability negotiation and delays user registration
  Correctly handle "CAP END", new client type CLIENT_WAITCAPEND
  Implement core IRC capability handling and "CAP" command
  New "login" source file
  Introduce_Client() => Client_Introduce(), and move it to client.c

16 files changed:
contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj
doc/Capabilities.txt [new file with mode: 0644]
doc/Makefile.am
src/ngircd/Makefile.am
src/ngircd/client-cap.c [new file with mode: 0644]
src/ngircd/client-cap.h [new file with mode: 0644]
src/ngircd/client.c
src/ngircd/client.h
src/ngircd/irc-cap.c [new file with mode: 0644]
src/ngircd/irc-cap.h [new file with mode: 0644]
src/ngircd/irc-info.c
src/ngircd/irc-login.c
src/ngircd/login.c [new file with mode: 0644]
src/ngircd/login.h [new file with mode: 0644]
src/ngircd/messages.h
src/ngircd/parse.c

index affb329bf26e77efee69fd3481a6f1461c2b068c..6da1cb3e1e9cac2baa588f56c3572671b0ff96bb 100644 (file)
@@ -41,6 +41,9 @@
                FAA3D27B0F139CDC00B2447E /* conn-ssl.c in Sources */ = {isa = PBXBuildFile; fileRef = FAA3D2790F139CDC00B2447E /* conn-ssl.c */; };
                FAA97C57124A271400D5BBA9 /* sighandlers.c in Sources */ = {isa = PBXBuildFile; fileRef = FAA97C55124A271400D5BBA9 /* sighandlers.c */; };
                FAACD5F514A6099C006ED74F /* class.c in Sources */ = {isa = PBXBuildFile; fileRef = FAACD5F314A6099C006ED74F /* class.c */; };
+               FAD5853215271AAB00328741 /* client-cap.c in Sources */ = {isa = PBXBuildFile; fileRef = FAD5853015271AAB00328741 /* client-cap.c */; };
+               FAD5853515271AB800328741 /* irc-cap.c in Sources */ = {isa = PBXBuildFile; fileRef = FAD5853315271AB800328741 /* irc-cap.c */; };
+               FAD5853815272C2600328741 /* login.c in Sources */ = {isa = PBXBuildFile; fileRef = FAD5853615272C2500328741 /* login.c */; };
                FAE5CC2E0CF2308A007D69B6 /* numeric.c in Sources */ = {isa = PBXBuildFile; fileRef = FAE5CC2D0CF2308A007D69B6 /* numeric.c */; };
 /* End PBXBuildFile section */
 
                FAA97C56124A271400D5BBA9 /* sighandlers.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = sighandlers.h; sourceTree = "<group>"; };
                FAACD5F314A6099C006ED74F /* class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = class.c; sourceTree = "<group>"; };
                FAACD5F414A6099C006ED74F /* class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = class.h; sourceTree = "<group>"; };
+               FAD5852F15271A7800328741 /* Capabilities.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Capabilities.txt; sourceTree = "<group>"; };
+               FAD5853015271AAB00328741 /* client-cap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "client-cap.c"; sourceTree = "<group>"; };
+               FAD5853115271AAB00328741 /* client-cap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "client-cap.h"; sourceTree = "<group>"; };
+               FAD5853315271AB800328741 /* irc-cap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "irc-cap.c"; sourceTree = "<group>"; };
+               FAD5853415271AB800328741 /* irc-cap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "irc-cap.h"; sourceTree = "<group>"; };
+               FAD5853615272C2500328741 /* login.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = login.c; sourceTree = "<group>"; };
+               FAD5853715272C2500328741 /* login.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = login.h; sourceTree = "<group>"; };
                FAE22BD215270EA300F1A5AB /* Bopm.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Bopm.txt; sourceTree = "<group>"; };
                FAE22BD415270EA300F1A5AB /* Contributing.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Contributing.txt; sourceTree = "<group>"; };
                FAE22BD515270EB500F1A5AB /* HowToRelease.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = HowToRelease.txt; sourceTree = "<group>"; };
                                FAACD5F414A6099C006ED74F /* class.h */,
                                FA322CDD0CEF74B1001761B3 /* client.c */,
                                FA322CDE0CEF74B1001761B3 /* client.h */,
+                               FAD5853015271AAB00328741 /* client-cap.c */,
+                               FAD5853115271AAB00328741 /* client-cap.h */,
                                FA322CDF0CEF74B1001761B3 /* conf.c */,
                                FA322CE00CEF74B1001761B3 /* conf.h */,
                                FAA3D2780F139CDC00B2447E /* conf-ssl.h */,
                                FA322CE90CEF74B1001761B3 /* hash.h */,
                                FA322CEA0CEF74B1001761B3 /* io.c */,
                                FA322CEB0CEF74B1001761B3 /* io.h */,
+                               FAD5853315271AB800328741 /* irc-cap.c */,
+                               FAD5853415271AB800328741 /* irc-cap.h */,
                                FA322CEC0CEF74B1001761B3 /* irc-channel.c */,
                                FA322CED0CEF74B1001761B3 /* irc-channel.h */,
                                FA322CEE0CEF74B1001761B3 /* irc-info.c */,
                                FA322CFF0CEF74B1001761B3 /* lists.h */,
                                FA322D000CEF74B1001761B3 /* log.c */,
                                FA322D010CEF74B1001761B3 /* log.h */,
+                               FAD5853615272C2500328741 /* login.c */,
+                               FAD5853715272C2500328741 /* login.h */,
                                FA322D030CEF74B1001761B3 /* match.c */,
                                FA322D040CEF74B1001761B3 /* match.h */,
                                FA322D050CEF74B1001761B3 /* messages.h */,
                        children = (
                                FA322D9B0CEF752C001761B3 /* Makefile.am */,
                                FAE22BD215270EA300F1A5AB /* Bopm.txt */,
+                               FAD5852F15271A7800328741 /* Capabilities.txt */,
                                FAE22BD415270EA300F1A5AB /* Contributing.txt */,
                                FA322D9A0CEF752C001761B3 /* FAQ.txt */,
                                FA407F380DB15AC700271AF1 /* GIT.txt */,
                                FA2D564A11EA158B00D37A35 /* pam.c in Sources */,
                                FAA97C57124A271400D5BBA9 /* sighandlers.c in Sources */,
                                FAACD5F514A6099C006ED74F /* class.c in Sources */,
+                               FAD5853215271AAB00328741 /* client-cap.c in Sources */,
+                               FAD5853515271AB800328741 /* irc-cap.c in Sources */,
+                               FAD5853815272C2600328741 /* login.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/doc/Capabilities.txt b/doc/Capabilities.txt
new file mode 100644 (file)
index 0000000..9a692ea
--- /dev/null
@@ -0,0 +1,23 @@
+
+                     ngIRCd - Next Generation IRC Server
+                           http://ngircd.barton.de/
+
+               (c)2001-2012 Alexander Barton and Contributors.
+               ngIRCd is free software and published under the
+                   terms of the GNU General Public License.
+
+                            -- Capabilities.txt --
+
+
+This document lists and describes the "IRC capabilities" that ngIRCd supports
+and can be requested by a IRC/IRCv3 client that supports the "CAP" command.
+
+ngIRCd implements the "IRC Client Capabilities Extension" as described here:
+<http://www.leeh.co.uk/draft-mitchell-irc-capabilities-02.html>
+
+
+I. Supported Capabilities
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+None. At the moment, ngIRCd supports the "CAP" command and its sub-commands
+but offers no capabilities that could be requested by a client.
index 1a792c5f95396e5fd1e2b894364a0e713b08f87c..92e019b832ad1bcd00b3ca0bdd4f4fe929a72472 100644 (file)
@@ -18,6 +18,7 @@ SUFFIXES = .tmpl
 
 static_docs = \
        Bopm.txt \
+       Capabilities.txt \
        FAQ.txt \
        GIT.txt \
        HowToRelease.txt \
index cacd1c3c8a8f0d5571d213b51949c1e52649da3f..3a411a964b266a2ab9874342ee618018c953ba86 100644 (file)
@@ -24,6 +24,7 @@ ngircd_SOURCES = \
        channel.c \
        class.c \
        client.c \
+       client-cap.c \
        conf.c \
        conn.c \
        conn-func.c \
@@ -32,6 +33,7 @@ ngircd_SOURCES = \
        hash.c \
        io.c \
        irc.c \
+       irc-cap.c \
        irc-channel.c \
        irc-info.c \
        irc-login.c \
@@ -42,6 +44,7 @@ ngircd_SOURCES = \
        irc-write.c \
        lists.c \
        log.c \
+       login.c \
        match.c \
        numeric.c \
        op.c \
@@ -61,6 +64,7 @@ noinst_HEADERS = \
        channel.h \
        class.h \
        client.h \
+       client-cap.h \
        conf.h \
        conf-ssl.h \
        conn.h \
@@ -71,6 +75,7 @@ noinst_HEADERS = \
        hash.h \
        io.h \
        irc.h \
+       irc-cap.h \
        irc-channel.h \
        irc-info.h \
        irc-login.h \
@@ -81,6 +86,7 @@ noinst_HEADERS = \
        irc-write.h \
        lists.h \
        log.h \
+       login.h \
        match.h \
        messages.h \
        numeric.h \
diff --git a/src/ngircd/client-cap.c b/src/ngircd/client-cap.c
new file mode 100644 (file)
index 0000000..b0da807
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#define __client_cap_c__
+
+#include "portab.h"
+
+/**
+ * @file
+ * Functions to deal with IRC Capabilities
+ */
+
+#include "imp.h"
+#include <assert.h>
+
+#include "defines.h"
+#include "conn.h"
+#include "client.h"
+#include "log.h"
+
+#include "exp.h"
+#include "client-cap.h"
+
+GLOBAL int
+Client_Cap(CLIENT *Client)
+{
+       assert (Client != NULL);
+
+       return Client->capabilities;
+}
+
+GLOBAL void
+Client_CapSet(CLIENT *Client, int Cap)
+{
+       assert(Client != NULL);
+       assert(Cap >= 0);
+
+       Client->capabilities = Cap;
+       LogDebug("Set new capability of \"%s\" to %d.",
+                Client_ID(Client), Client->capabilities);
+}
+
+GLOBAL void
+Client_CapAdd(CLIENT *Client, int Cap)
+{
+       assert(Client != NULL);
+       assert(Cap > 0);
+
+       Client->capabilities |= Cap;
+       LogDebug("Add capability %d, new capability of \"%s\" is %d.",
+                Cap, Client_ID(Client), Client->capabilities);
+}
+
+GLOBAL void
+Client_CapDel(CLIENT *Client, int Cap)
+{
+       assert(Client != NULL);
+       assert(Cap > 0);
+
+       Client->capabilities &= ~Cap;
+       LogDebug("Delete capability %d, new capability of \"%s\" is %d.",
+                Cap, Client_ID(Client), Client->capabilities);
+}
+
+/* -eof- */
diff --git a/src/ngircd/client-cap.h b/src/ngircd/client-cap.h
new file mode 100644 (file)
index 0000000..5532d33
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#ifndef __client_cap_h__
+#define __client_cap_h__
+
+/**
+ * @file
+ * Functions to deal with IRC Capabilities (header)
+ */
+
+#define CLIENT_CAP_PENDING 1           /* Capability negotiation pending */
+#define CLIENT_CAP_SUPPORTED 2         /* Client supports IRC capabilities */
+
+#define CLIENT_CAP_MULTI_PREFIX 4      /* multi-prefix */
+
+GLOBAL int Client_Cap PARAMS((CLIENT *Client));
+
+GLOBAL void Client_CapSet PARAMS((CLIENT *Client, int Cap));
+GLOBAL void Client_CapAdd PARAMS((CLIENT *Client, int Cap));
+GLOBAL void Client_CapDel PARAMS((CLIENT *Client, int Cap));
+
+#endif
index 7e28e8fb1035006d540e9f3e4b7ba7d720944662..1b356848090b835772db30921d8764c87da39869 100644 (file)
@@ -37,6 +37,7 @@
 #include "ngircd.h"
 #include "channel.h"
 #include "conf.h"
+#include "conn-func.h"
 #include "hash.h"
 #include "irc-write.h"
 #include "log.h"
@@ -69,6 +70,8 @@ static CLIENT *Init_New_Client PARAMS((CONN_ID Idx, CLIENT *Introducer,
 static void Destroy_UserOrService PARAMS((CLIENT *Client,const char *Txt, const char *FwdMsg,
                                        bool SendQuit));
 
+static void cb_introduceClient PARAMS((CLIENT *Client, CLIENT *Prefix,
+                                      void *i));
 
 GLOBAL void
 Client_Init( void )
@@ -1142,6 +1145,46 @@ Client_Reject(CLIENT *Client, const char *Reason, bool InformClient)
 }
 
 
+/**
+ * Introduce a new user or service client in the network.
+ *
+ * @param From Remote server introducing the client or NULL (local).
+ * @param Client New client.
+ * @param Type Type of the client (CLIENT_USER or CLIENT_SERVICE).
+ */
+GLOBAL void
+Client_Introduce(CLIENT *From, CLIENT *Client, int Type)
+{
+       /* Set client type (user or service) */
+       Client_SetType(Client, Type);
+
+       if (From) {
+               if (Conf_IsService(Conf_GetServer(Client_Conn(From)),
+                                  Client_ID(Client)))
+                       Client_SetType(Client, CLIENT_SERVICE);
+               LogDebug("%s \"%s\" (+%s) registered (via %s, on %s, %d hop%s).",
+                        Client_TypeText(Client), Client_Mask(Client),
+                        Client_Modes(Client), Client_ID(From),
+                        Client_ID(Client_Introducer(Client)),
+                        Client_Hops(Client), Client_Hops(Client) > 1 ? "s": "");
+       } else {
+               Log(LOG_NOTICE, "%s \"%s\" registered (connection %d).",
+                   Client_TypeText(Client), Client_Mask(Client),
+                   Client_Conn(Client));
+               Log_ServerNotice('c', "Client connecting: %s (%s@%s) [%s] - %s",
+                                Client_ID(Client), Client_User(Client),
+                                Client_Hostname(Client),
+                                Conn_IPA(Client_Conn(Client)),
+                                Client_TypeText(Client));
+       }
+
+       /* Inform other servers */
+       IRC_WriteStrServersPrefixFlag_CB(From,
+                               From != NULL ? From : Client_ThisServer(),
+                               '\0', cb_introduceClient, (void *)Client);
+} /* Client_Introduce */
+
+
 static unsigned long
 Count( CLIENT_TYPE Type )
 {
@@ -1361,6 +1404,59 @@ Destroy_UserOrService(CLIENT *Client, const char *Txt, const char *FwdMsg, bool
 } /* Destroy_UserOrService */
 
 
+/**
+ * Introduce a new user or service client to a remote server.
+ *
+ * This function differentiates between RFC1459 and RFC2813 server links and
+ * generates the appropriate commands to register the new user or service.
+ *
+ * @param To           The remote server to inform.
+ * @param Prefix       Prefix for the generated commands.
+ * @param data         CLIENT structure of the new client.
+ */
+static void
+cb_introduceClient(CLIENT *To, CLIENT *Prefix, void *data)
+{
+       CLIENT *c = (CLIENT *)data;
+       CONN_ID conn;
+       char *modes, *user, *host;
+
+       modes = Client_Modes(c);
+       user = Client_User(c) ? Client_User(c) : "-";
+       host = Client_Hostname(c) ? Client_Hostname(c) : "-";
+
+       conn = Client_Conn(To);
+       if (Conn_Options(conn) & CONN_RFC1459) {
+               /* RFC 1459 mode: separate NICK and USER commands */
+               Conn_WriteStr(conn, "NICK %s :%d", Client_ID(c),
+                             Client_Hops(c) + 1);
+               Conn_WriteStr(conn, ":%s USER %s %s %s :%s",
+                             Client_ID(c), user, host,
+                             Client_ID(Client_Introducer(c)), Client_Info(c));
+               if (modes[0])
+                       Conn_WriteStr(conn, ":%s MODE %s +%s",
+                                     Client_ID(c), Client_ID(c), modes);
+       } else {
+               /* RFC 2813 mode: one combined NICK or SERVICE command */
+               if (Client_Type(c) == CLIENT_SERVICE
+                   && strchr(Client_Flags(To), 'S'))
+                       IRC_WriteStrClientPrefix(To, Prefix,
+                                                "SERVICE %s %d * +%s %d :%s",
+                                                Client_Mask(c),
+                                                Client_MyToken(Client_Introducer(c)),
+                                                Client_Modes(c), Client_Hops(c) + 1,
+                                                Client_Info(c));
+               else
+                       IRC_WriteStrClientPrefix(To, Prefix,
+                                                "NICK %s %d %s %s %d +%s :%s",
+                                                Client_ID(c), Client_Hops(c) + 1,
+                                                user, host,
+                                                Client_MyToken(Client_Introducer(c)),
+                                                modes, Client_Info(c));
+       }
+} /* cb_introduceClient */
+
+
 #ifdef DEBUG
 
 GLOBAL void
index 7bb230b49d23edcb0d02fd3ea29ed48dbbd368d1..4dbcc7a072232eacfe7cf45020cd2136fb774eb2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2008 Alexander Barton (alex@barton.de)
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de)
  *
  * 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
 #ifndef STRICT_RFC
 # define CLIENT_WAITAUTHPING 512       /* waiting for AUTH PONG from client */
 #endif
+#define CLIENT_WAITCAPEND 1024         /* waiting for "CAP END" command */
 
 #define CLIENT_TYPE int
 
 #include "defines.h"
 
-#if defined(__client_c__) | defined(S_SPLINT_S)
+#if defined(__client_c__) | defined(__client_cap_c__) | defined(S_SPLINT_S)
 
 typedef struct _CLIENT
 {
@@ -58,6 +59,7 @@ typedef struct _CLIENT
        bool oper_by_me;                /* client is local IRC operator on this server? */
        char away[CLIENT_AWAY_LEN];     /* AWAY text (valid if mode 'a' is set) */
        char flags[CLIENT_FLAGS_LEN];   /* flags of the client */
+       int capabilities;               /* enabled IRC capabilities */
 } CLIENT;
 
 #else
@@ -165,6 +167,8 @@ GLOBAL const char *Client_TypeText PARAMS((CLIENT *Client));
 
 GLOBAL void Client_Reject PARAMS((CLIENT *Client, const char *Reason,
                                  bool InformClient));
+GLOBAL void Client_Introduce PARAMS((CLIENT *From, CLIENT *Client, int Type));
+
 
 #ifdef DEBUG
 GLOBAL void Client_DebugDump PARAMS((void));
diff --git a/src/ngircd/irc-cap.c b/src/ngircd/irc-cap.c
new file mode 100644 (file)
index 0000000..a6923ac
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#include "portab.h"
+
+/**
+ * @file
+ * Handler for IRC capability ("CAP") commands
+ */
+
+#include "imp.h"
+#include <assert.h>
+#include <string.h>
+
+#include "defines.h"
+#include "conn.h"
+#include "channel.h"
+#include "client-cap.h"
+#include "irc-write.h"
+#include "log.h"
+#include "login.h"
+#include "messages.h"
+#include "parse.h"
+
+#include "exp.h"
+#include "irc-cap.h"
+
+bool Handle_CAP_LS PARAMS((CLIENT *Client, char *Arg));
+bool Handle_CAP_LIST PARAMS((CLIENT *Client, char *Arg));
+bool Handle_CAP_REQ PARAMS((CLIENT *Client, char *Arg));
+bool Handle_CAP_ACK PARAMS((CLIENT *Client, char *Arg));
+bool Handle_CAP_CLEAR PARAMS((CLIENT *Client));
+bool Handle_CAP_END PARAMS((CLIENT *Client));
+
+void Set_CAP_Negotiation PARAMS((CLIENT *Client));
+
+int Parse_CAP PARAMS((int Capabilities, char *Args));
+char *Get_CAP_String PARAMS((int Capabilities));
+
+/**
+ * Handler for the IRCv3 "CAP" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Req Request structure with prefix and all parameters.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+GLOBAL bool
+IRC_CAP(CLIENT *Client, REQUEST *Req)
+{
+       assert(Client != NULL);
+       assert(Req != NULL);
+
+       /* Bad number of prameters? */
+       if (Req->argc < 1 || Req->argc > 2)
+               return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG,
+                                         Client_ID(Client), Req->command);
+
+       LogDebug("Got \"%s %s\" command from \"%s\" ...",
+                Req->command, Req->argv[0], Client_ID(Client));
+
+       if (Req->argc == 1) {
+               if (strcasecmp(Req->argv[0], "CLEAR") == 0)
+                       return Handle_CAP_CLEAR(Client);
+               if (strcasecmp(Req->argv[0], "END") == 0)
+                       return Handle_CAP_END(Client);
+       }
+       if (Req->argc >= 1 && Req->argc <= 2) {
+               if (strcasecmp(Req->argv[0], "LS") == 0)
+                       return Handle_CAP_LS(Client, Req->argv[1]);
+               if (strcasecmp(Req->argv[0], "LIST") == 0)
+                       return Handle_CAP_LIST(Client, Req->argv[1]);
+       }
+       if (Req->argc == 2) {
+               if (strcasecmp(Req->argv[0], "REQ") == 0)
+                       return Handle_CAP_REQ(Client, Req->argv[1]);
+               if (strcasecmp(Req->argv[0], "ACK") == 0)
+                       return Handle_CAP_ACK(Client, Req->argv[1]);
+       }
+
+       return IRC_WriteStrClient(Client, ERR_INVALIDCAP_MSG,
+                                 Client_ID(Client), Req->argv[0]);
+}
+
+/**
+ * Handler for the "CAP LS" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Arg Command argument or NULL.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_LS(CLIENT *Client, UNUSED char *Arg)
+{
+       assert(Client != NULL);
+
+       Set_CAP_Negotiation(Client);
+
+       return IRC_WriteStrClient(Client,
+                                 "CAP %s LS :multi-prefix",
+                                 Client_ID(Client));
+}
+
+/**
+ * Handler for the "CAP LIST" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Arg Command argument or NULL.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_LIST(CLIENT *Client, UNUSED char *Arg)
+{
+       assert(Client != NULL);
+
+       return IRC_WriteStrClient(Client, "CAP %s LIST :%s", Client_ID(Client),
+                                 Get_CAP_String(Client_Cap(Client)));
+}
+
+/**
+ * Handler for the "CAP REQ" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Arg Command argument.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_REQ(CLIENT *Client, char *Arg)
+{
+       int new_cap;
+
+       assert(Client != NULL);
+       assert(Arg != NULL);
+
+       Set_CAP_Negotiation(Client);
+
+       new_cap = Parse_CAP(Client_Cap(Client), Arg);
+
+       if (new_cap < 0)
+               return IRC_WriteStrClient(Client, "CAP %s NAK :%s",
+                                         Client_ID(Client), Arg);
+
+       Client_CapSet(Client, new_cap);
+       return IRC_WriteStrClient(Client, "CAP %s ACK :%s",
+                                 Client_ID(Client), Arg);
+}
+
+/**
+ * Handler for the "CAP ACK" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @param Arg Command argument.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_ACK(CLIENT *Client, char *Arg)
+{
+       assert(Client != NULL);
+       assert(Arg != NULL);
+
+       return CONNECTED;
+}
+
+/**
+ * Handler for the "CAP CLEAR" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_CLEAR(CLIENT *Client)
+{
+       int cap_old;
+
+       assert(Client != NULL);
+
+       cap_old = Client_Cap(Client);
+       if (cap_old & CLIENT_CAP_MULTI_PREFIX)
+               Client_CapDel(Client, CLIENT_CAP_MULTI_PREFIX);
+
+       return IRC_WriteStrClient(Client, "CAP %s ACK :%s", Client_ID(Client),
+                                 Get_CAP_String(cap_old));
+}
+
+/**
+ * Handler for the "CAP END" command.
+ *
+ * @param Client The client from which this command has been received.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+bool
+Handle_CAP_END(CLIENT *Client)
+{
+       assert(Client != NULL);
+
+       if (Client_Type(Client) != CLIENT_USER) {
+               /* User is still logging in ... */
+               Client_CapDel(Client, CLIENT_CAP_PENDING);
+
+               if (Client_Type(Client) == CLIENT_WAITCAPEND) {
+                       /* Only "CAP END" was missing: log in! */
+                       return Login_User(Client);
+               }
+       }
+
+       return CONNECTED;
+}
+
+/**
+ * Set CAP negotiation status and mark client as "supports capabilities".
+ *
+ * @param Client The client to handle.
+ */
+void
+Set_CAP_Negotiation(CLIENT *Client)
+{
+       assert(Client != NULL);
+
+       if (Client_Type(Client) != CLIENT_USER)
+               Client_CapAdd(Client, CLIENT_CAP_PENDING);
+       Client_CapAdd(Client, CLIENT_CAP_SUPPORTED);
+}
+
+/**
+ * Parse capability string and return numeric flag value.
+ *
+ * @param Args The string containing space-separated capability names.
+ * @return Changed capability flags or 0 on error.
+ */
+int
+Parse_CAP(int Capabilities, char *Args)
+{
+       static char tmp[COMMAND_LEN];
+       char *ptr;
+
+       assert(Args != NULL);
+
+       strlcpy(tmp, Args, sizeof(tmp));
+
+       ptr = strtok(tmp, " ");
+       while (ptr) {
+               if (*ptr == '-') {
+                       /* drop capabilities */
+                       ptr++;
+                       if (strcmp(ptr, "multi-prefix") == 0)
+                               Capabilities &= ~CLIENT_CAP_MULTI_PREFIX;
+                       else
+                               return -1;
+               } else {
+                       /* request capabilities */
+                       if (strcmp(ptr, "multi-prefix") == 0)
+                               Capabilities |= CLIENT_CAP_MULTI_PREFIX;
+                       else
+                               return -1;
+               }
+               ptr = strtok(NULL, " ");
+       }
+
+       return Capabilities;
+}
+
+/**
+ * Return textual representation of capability flags.
+ *
+ * Please note: this function returns a pointer to a global buffer and
+ * therefore isn't thread safe!
+ *
+ * @param Capabilities Capability flags (bitmask).
+ * @return Pointer to textual representation.
+ */
+char
+*Get_CAP_String(int Capabilities)
+{
+       static char txt[COMMAND_LEN];
+
+       txt[0] = '\0';
+
+       if (Capabilities & CLIENT_CAP_MULTI_PREFIX)
+               strlcat(txt, "multi-prefix ", sizeof(txt));
+
+       return txt;
+}
+
+/* -eof- */
diff --git a/src/ngircd/irc-cap.h b/src/ngircd/irc-cap.h
new file mode 100644 (file)
index 0000000..7cd4c84
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2010 Alexander Barton (alex@barton.de).
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#ifndef __irc_cap_h__
+#define __irc_cap_h__
+
+/**
+ * @file
+ * Handler for IRC capability ("CAP") commands (header)
+ */
+
+GLOBAL bool IRC_CAP PARAMS((CLIENT *Client, REQUEST *Req));
+
+#endif
+
+/* -eof- */
index 841e6e62739274a0b862ba4bab1284091605ff07..0ea85874862b7114b6cd355d59467371055a4013 100644 (file)
@@ -39,6 +39,7 @@
 #include "parse.h"
 #include "irc.h"
 #include "irc-write.h"
+#include "client-cap.h"
 
 #include "exp.h"
 #include "irc-info.h"
@@ -807,8 +808,16 @@ who_flags_status(const char *client_modes)
 
 
 static const char *
-who_flags_qualifier(const char *chan_user_modes)
+who_flags_qualifier(CLIENT *Client, const char *chan_user_modes)
 {
+       assert(Client != NULL);
+
+       if (Client_Cap(Client) & CLIENT_CAP_MULTI_PREFIX) {
+               if (strchr(chan_user_modes, 'o') &&
+                   strchr(chan_user_modes, 'v'))
+                       return "@+";
+       }
+
        if (strchr(chan_user_modes, 'o'))
                return "@";
        else if (strchr(chan_user_modes, 'v'))
@@ -865,7 +874,7 @@ IRC_WHO_Channel(CLIENT *Client, CHANNEL *Chan, bool OnlyOps)
                                strlcat(flags, "*", sizeof(flags));
 
                        chan_user_modes = Channel_UserModes(Chan, c);
-                       strlcat(flags, who_flags_qualifier(chan_user_modes),
+                       strlcat(flags, who_flags_qualifier(c, chan_user_modes),
                                sizeof(flags));
 
                        if (!write_whoreply(Client, c, Channel_Name(Chan),
@@ -1078,7 +1087,7 @@ IRC_WHOIS_SendReply(CLIENT *Client, CLIENT *from, CLIENT *c)
                if (str[strlen(str) - 1] != ':')
                        strlcat(str, " ", sizeof(str));
 
-               strlcat(str, who_flags_qualifier(Channel_UserModes(chan, c)),
+               strlcat(str, who_flags_qualifier(c, Channel_UserModes(chan, c)),
                                                 sizeof(str));
                strlcat(str, Channel_Name(chan), sizeof(str));
 
@@ -1524,60 +1533,77 @@ IRC_Show_MOTD( CLIENT *Client )
 } /* IRC_Show_MOTD */
 
 
+/**
+ * Send NAMES reply for a specific client and channel.
+ *
+ * @param Client The client requesting the NAMES information.
+ * @param Chan The channel
+ * @return CONNECTED or DISCONNECTED.
+ */
 GLOBAL bool
-IRC_Send_NAMES( CLIENT *Client, CHANNEL *Chan )
+IRC_Send_NAMES(CLIENT * Client, CHANNEL * Chan)
 {
        bool is_visible, is_member;
        char str[LINE_LEN + 1];
        CL2CHAN *cl2chan;
        CLIENT *cl;
 
-       assert( Client != NULL );
-       assert( Chan != NULL );
+       assert(Client != NULL);
+       assert(Chan != NULL);
 
-       if( Channel_IsMemberOf( Chan, Client )) is_member = true;
-       else is_member = false;
+       if (Channel_IsMemberOf(Chan, Client))
+               is_member = true;
+       else
+               is_member = false;
 
        /* Do not print info on channel memberships to anyone that is not member? */
        if (Conf_MorePrivacy && !is_member)
                return CONNECTED;
 
        /* Secret channel? */
-       if( ! is_member && strchr( Channel_Modes( Chan ), 's' )) return CONNECTED;
+       if (!is_member && strchr(Channel_Modes(Chan), 's'))
+               return CONNECTED;
 
-       /* Alle Mitglieder suchen */
-       snprintf( str, sizeof( str ), RPL_NAMREPLY_MSG, Client_ID( Client ), "=", Channel_Name( Chan ));
-       cl2chan = Channel_FirstMember( Chan );
-       while( cl2chan )
-       {
-               cl = Channel_GetClient( cl2chan );
+       snprintf(str, sizeof(str), RPL_NAMREPLY_MSG, Client_ID(Client), "=",
+                Channel_Name(Chan));
+       cl2chan = Channel_FirstMember(Chan);
+       while (cl2chan) {
+               cl = Channel_GetClient(cl2chan);
 
-               if( strchr( Client_Modes( cl ), 'i' )) is_visible = false;
-               else is_visible = true;
+               if (strchr(Client_Modes(cl), 'i'))
+                       is_visible = false;
+               else
+                       is_visible = true;
 
-               if( is_member || is_visible )
-               {
-                       /* Nick anhaengen */
-                       if( str[strlen( str ) - 1] != ':' ) strlcat( str, " ", sizeof( str ));
-                       if( strchr( Channel_UserModes( Chan, cl ), 'o' )) strlcat( str, "@", sizeof( str ));
-                       else if( strchr( Channel_UserModes( Chan, cl ), 'v' )) strlcat( str, "+", sizeof( str ));
-                       strlcat( str, Client_ID( cl ), sizeof( str ));
+               if (is_member || is_visible) {
+                       if (str[strlen(str) - 1] != ':')
+                               strlcat(str, " ", sizeof(str));
+                       if (Client_Cap(cl) & CLIENT_CAP_MULTI_PREFIX) {
+                               if (strchr(Channel_UserModes(Chan, cl), 'o') &&
+                                   strchr(Channel_UserModes(Chan, cl), 'v'))
+                                       strlcat(str, "@+", sizeof(str));
+                       } else {
+                               if (strchr(Channel_UserModes(Chan, cl), 'o'))
+                                       strlcat(str, "@", sizeof(str));
+                               else if (strchr(Channel_UserModes(Chan, cl), 'v'))
+                                       strlcat(str, "+", sizeof(str));
+                       }
+                       strlcat(str, Client_ID(cl), sizeof(str));
 
-                       if( strlen( str ) > ( LINE_LEN - CLIENT_NICK_LEN - 4 ))
-                       {
-                               /* Zeile wird zu lang: senden! */
-                               if( ! IRC_WriteStrClient( Client, "%s", str )) return DISCONNECTED;
-                               snprintf( str, sizeof( str ), RPL_NAMREPLY_MSG, Client_ID( Client ), "=", Channel_Name( Chan ));
+                       if (strlen(str) > (LINE_LEN - CLIENT_NICK_LEN - 4)) {
+                               if (!IRC_WriteStrClient(Client, "%s", str))
+                                       return DISCONNECTED;
+                               snprintf(str, sizeof(str), RPL_NAMREPLY_MSG,
+                                        Client_ID(Client), "=",
+                                        Channel_Name(Chan));
                        }
                }
 
-               /* naechstes Mitglied suchen */
-               cl2chan = Channel_NextMember( Chan, cl2chan );
+               cl2chan = Channel_NextMember(Chan, cl2chan);
        }
-       if( str[strlen( str ) - 1] != ':')
-       {
-               /* Es sind noch Daten da, die gesendet werden muessen */
-               if( ! IRC_WriteStrClient( Client, "%s", str )) return DISCONNECTED;
+       if (str[strlen(str) - 1] != ':') {
+               if (!IRC_WriteStrClient(Client, "%s", str))
+                       return DISCONNECTED;
        }
 
        return CONNECTED;
index 133a0e5fa79ed7cda29bdf46b5736047a5ac7809..bf3254c985807e62ff24232e46ab1f6babc32752 100644 (file)
 
 #include "imp.h"
 #include <assert.h>
-#include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <strings.h>
-#include <signal.h>
-#include <unistd.h>
 
-#include "ngircd.h"
 #include "conn-func.h"
 #include "class.h"
 #include "conf.h"
 #include "channel.h"
-#include "io.h"
 #include "log.h"
+#include "login.h"
 #include "messages.h"
-#include "pam.h"
 #include "parse.h"
 #include "irc.h"
 #include "irc-info.h"
 #include "exp.h"
 #include "irc-login.h"
 
-
-static bool Hello_User PARAMS(( CLIENT *Client ));
-static bool Hello_User_PostAuth PARAMS(( CLIENT *Client ));
 static void Kill_Nick PARAMS(( char *Nick, char *Reason ));
-static void Introduce_Client PARAMS((CLIENT *To, CLIENT *Client, int Type));
-
-static void cb_introduceClient PARAMS((CLIENT *Client, CLIENT *Prefix,
-                                      void *i));
-
-#ifdef PAM
-static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events));
-#endif
 
 /**
  * Handler for the IRC "PASS" command.
@@ -285,7 +268,7 @@ IRC_NICK( CLIENT *Client, REQUEST *Req )
                        /* If we received a valid USER command already then
                         * register the new client! */
                        if( Client_Type( Client ) == CLIENT_GOTUSER )
-                               return Hello_User( Client );
+                               return Login_User( Client );
                        else
                                Client_SetType( Client, CLIENT_GOTNICK );
                } else {
@@ -395,7 +378,7 @@ IRC_NICK( CLIENT *Client, REQUEST *Req )
                                 Client_Mask(c));
                        Client_SetType(c, CLIENT_GOTNICK);
                } else
-                       Introduce_Client(Client, c, CLIENT_USER);
+                       Client_Introduce(Client, c, CLIENT_USER);
 
                return CONNECTED;
        }
@@ -457,7 +440,7 @@ IRC_USER(CLIENT * Client, REQUEST * Req)
                LogDebug("Connection %d: got valid USER command ...",
                    Client_Conn(Client));
                if (Client_Type(Client) == CLIENT_GOTNICK)
-                       return Hello_User(Client);
+                       return Login_User(Client);
                else
                        Client_SetType(Client, CLIENT_GOTUSER);
                return CONNECTED;
@@ -487,7 +470,7 @@ IRC_USER(CLIENT * Client, REQUEST * Req)
                /* RFC 1459 style user registration?
                 * Introduce client to network: */
                if (Client_Type(c) == CLIENT_GOTNICK)
-                       Introduce_Client(Client, c, CLIENT_USER);
+                       Client_Introduce(Client, c, CLIENT_USER);
 
                return CONNECTED;
        } else if (Client_Type(Client) == CLIENT_USER) {
@@ -601,7 +584,7 @@ IRC_SERVICE(CLIENT *Client, REQUEST *Req)
                return CONNECTED;
        }
 
-       Introduce_Client(Client, c, CLIENT_SERVICE);
+       Client_Introduce(Client, c, CLIENT_SERVICE);
        return CONNECTED;
 } /* IRC_SERVICE */
 
@@ -880,7 +863,7 @@ IRC_PONG(CLIENT *Client, REQUEST *Req)
                if (auth_ping == atoi(Req->argv[0])) {
                        Conn_SetAuthPing(conn, 0);
                        if (Client_Type(Client) == CLIENT_WAITAUTHPING)
-                               Hello_User(Client);
+                               Login_User(Client);
                } else
                        if (!IRC_WriteStrClient(Client,
                                        "To connect, type /QUOTE PONG %ld",
@@ -903,196 +886,6 @@ IRC_PONG(CLIENT *Client, REQUEST *Req)
 } /* IRC_PONG */
 
 
-/**
- * Initiate client registration.
- *
- * This function is called after the daemon received the required NICK and
- * USER commands of a new client. If the daemon is compiled with support for
- * PAM, the authentication sub-processs is forked; otherwise the global server
- * password is checked.
- *
- * @param Client       The client logging in.
- * @returns            CONNECTED or DISCONNECTED.
- */
-static bool
-Hello_User(CLIENT * Client)
-{
-#ifdef PAM
-       int pipefd[2], result;
-       pid_t pid;
-#endif
-       CONN_ID conn;
-
-       assert(Client != NULL);
-       conn = Client_Conn(Client);
-
-#ifndef STRICT_RFC
-       if (Conf_AuthPing) {
-               /* Did we receive the "auth PONG" already? */
-               if (Conn_GetAuthPing(conn)) {
-                       Client_SetType(Client, CLIENT_WAITAUTHPING);
-                       LogDebug("Connection %d: Waiting for AUTH PONG ...", conn);
-                       return CONNECTED;
-               }
-       }
-#endif
-
-#ifdef PAM
-       if (!Conf_PAM) {
-               /* Don't do any PAM authentication at all, instead emulate
-                * the beahiour of the daemon compiled without PAM support:
-                * because there can't be any "server password", all
-                * passwords supplied are classified as "wrong". */
-               if(Client_Password(Client)[0] == '\0')
-                       return Hello_User_PostAuth(Client);
-               Client_Reject(Client, "Non-empty password", false);
-               return DISCONNECTED;
-       }
-
-       if (Conf_PAMIsOptional && strcmp(Client_Password(Client), "") == 0) {
-               /* Clients are not required to send a password and to be PAM-
-                * authenticated at all. If not, they won't become "identified"
-                * and keep the "~" in their supplied user name.
-                * Therefore it is sensible to either set Conf_PAMisOptional or
-                * to enable IDENT lookups -- not both. */
-               return Hello_User_PostAuth(Client);
-       }
-
-       /* Fork child process for PAM authentication; and make sure that the
-        * process timeout is set higher than the login timeout! */
-       pid = Proc_Fork(Conn_GetProcStat(conn), pipefd,
-                       cb_Read_Auth_Result, Conf_PongTimeout + 1);
-       if (pid > 0) {
-               LogDebug("Authenticator for connection %d created (PID %d).",
-                        conn, pid);
-               return CONNECTED;
-       } else {
-               /* Sub process */
-               Log_Init_Subprocess("Auth");
-               Conn_CloseAllSockets(NONE);
-               result = PAM_Authenticate(Client);
-               if (write(pipefd[1], &result, sizeof(result)) != sizeof(result))
-                       Log_Subprocess(LOG_ERR,
-                                      "Failed to pipe result to parent!");
-               Log_Exit_Subprocess("Auth");
-               exit(0);
-       }
-#else
-       /* Check global server password ... */
-       if (strcmp(Client_Password(Client), Conf_ServerPwd) != 0) {
-               /* Bad password! */
-               Client_Reject(Client, "Bad server password", false);
-               return DISCONNECTED;
-       }
-       return Hello_User_PostAuth(Client);
-#endif
-}
-
-
-#ifdef PAM
-
-/**
- * Read result of the authenticatior sub-process from pipe
- *
- * @param r_fd         File descriptor of the pipe.
- * @param events       (ignored IO specification)
- */
-static void
-cb_Read_Auth_Result(int r_fd, UNUSED short events)
-{
-       CONN_ID conn;
-       CLIENT *client;
-       int result;
-       size_t len;
-       PROC_STAT *proc;
-
-       LogDebug("Auth: Got callback on fd %d, events %d", r_fd, events);
-       conn = Conn_GetFromProc(r_fd);
-       if (conn == NONE) {
-               /* Ops, none found? Probably the connection has already
-                * been closed!? We'll ignore that ... */
-               io_close(r_fd);
-               LogDebug("Auth: Got callback for unknown connection!?");
-               return;
-       }
-       proc = Conn_GetProcStat(conn);
-       client = Conn_GetClient(conn);
-
-       /* Read result from pipe */
-       len = Proc_Read(proc, &result, sizeof(result));
-       Proc_Close(proc);
-       if (len == 0)
-               return;
-
-       if (len != sizeof(result)) {
-               Log(LOG_CRIT, "Auth: Got malformed result!");
-               Client_Reject(client, "Internal error", false);
-               return;
-       }
-
-       if (result == true) {
-               Client_SetUser(client, Client_OrigUser(client), true);
-               (void)Hello_User_PostAuth(client);
-       } else
-               Client_Reject(client, "Bad password", false);
-}
-
-#endif
-
-
-/**
- * Finish client registration.
- *
- * Introduce the new client to the network and send all "hello messages"
- * to it after authentication has been succeeded.
- *
- * @param Client       The client logging in.
- * @returns            CONNECTED or DISCONNECTED.
- */
-static bool
-Hello_User_PostAuth(CLIENT *Client)
-{
-       assert(Client != NULL);
-
-       if (Class_HandleServerBans(Client) != CONNECTED)
-               return DISCONNECTED;
-
-       Introduce_Client(NULL, Client, CLIENT_USER);
-
-       if (!IRC_WriteStrClient
-           (Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client)))
-               return false;
-       if (!IRC_WriteStrClient
-           (Client, RPL_YOURHOST_MSG, Client_ID(Client),
-            Client_ID(Client_ThisServer()), PACKAGE_VERSION, TARGET_CPU,
-            TARGET_VENDOR, TARGET_OS))
-               return false;
-       if (!IRC_WriteStrClient
-           (Client, RPL_CREATED_MSG, Client_ID(Client), NGIRCd_StartStr))
-               return false;
-       if (!IRC_WriteStrClient
-           (Client, RPL_MYINFO_MSG, Client_ID(Client),
-            Client_ID(Client_ThisServer()), PACKAGE_VERSION, USERMODES,
-            CHANMODES))
-               return false;
-
-       /* Features supported by this server (005 numeric, ISUPPORT),
-        * see <http://www.irc.org/tech_docs/005.html> for details. */
-       if (!IRC_Send_ISUPPORT(Client))
-               return DISCONNECTED;
-
-       if (!IRC_Send_LUSERS(Client))
-               return DISCONNECTED;
-       if (!IRC_Show_MOTD(Client))
-               return DISCONNECTED;
-
-       /* Suspend the client for a second ... */
-       IRC_SetPenalty(Client, 1);
-
-       return CONNECTED;
-}
-
-
 /**
  * Kill all users with a specific nick name in the network.
  *
@@ -1119,97 +912,4 @@ Kill_Nick(char *Nick, char *Reason)
 } /* Kill_Nick */
 
 
-/**
- * Introduce a new user or service client in the network.
- *
- * @param From         Remote server introducing the client or NULL (local).
- * @param Client       New client.
- * @param Type         Type of the client (CLIENT_USER or CLIENT_SERVICE).
- */
-static void
-Introduce_Client(CLIENT *From, CLIENT *Client, int Type)
-{
-       /* Set client type (user or service) */
-       Client_SetType(Client, Type);
-
-       if (From) {
-               if (Conf_IsService(Conf_GetServer(Client_Conn(From)),
-                                  Client_ID(Client)))
-                       Client_SetType(Client, CLIENT_SERVICE);
-               LogDebug("%s \"%s\" (+%s) registered (via %s, on %s, %d hop%s).",
-                        Client_TypeText(Client), Client_Mask(Client),
-                        Client_Modes(Client), Client_ID(From),
-                        Client_ID(Client_Introducer(Client)),
-                        Client_Hops(Client), Client_Hops(Client) > 1 ? "s": "");
-       } else {
-               Log(LOG_NOTICE, "%s \"%s\" registered (connection %d).",
-                   Client_TypeText(Client), Client_Mask(Client),
-                   Client_Conn(Client));
-               Log_ServerNotice('c', "Client connecting: %s (%s@%s) [%s] - %s",
-                                Client_ID(Client), Client_User(Client),
-                                Client_Hostname(Client),
-                                Conn_IPA(Client_Conn(Client)),
-                                Client_TypeText(Client));
-       }
-
-       /* Inform other servers */
-       IRC_WriteStrServersPrefixFlag_CB(From,
-                               From != NULL ? From : Client_ThisServer(),
-                               '\0', cb_introduceClient, (void *)Client);
-} /* Introduce_Client */
-
-
-/**
- * Introduce a new user or service client to a remote server.
- *
- * This function differentiates between RFC1459 and RFC2813 server links and
- * generates the appropriate commands to register the new user or service.
- *
- * @param To           The remote server to inform.
- * @param Prefix       Prefix for the generated commands.
- * @param data         CLIENT structure of the new client.
- */
-static void
-cb_introduceClient(CLIENT *To, CLIENT *Prefix, void *data)
-{
-       CLIENT *c = (CLIENT *)data;
-       CONN_ID conn;
-       char *modes, *user, *host;
-
-       modes = Client_Modes(c);
-       user = Client_User(c) ? Client_User(c) : "-";
-       host = Client_Hostname(c) ? Client_Hostname(c) : "-";
-
-       conn = Client_Conn(To);
-       if (Conn_Options(conn) & CONN_RFC1459) {
-               /* RFC 1459 mode: separate NICK and USER commands */
-               Conn_WriteStr(conn, "NICK %s :%d", Client_ID(c),
-                             Client_Hops(c) + 1);
-               Conn_WriteStr(conn, ":%s USER %s %s %s :%s",
-                             Client_ID(c), user, host,
-                             Client_ID(Client_Introducer(c)), Client_Info(c));
-               if (modes[0])
-                       Conn_WriteStr(conn, ":%s MODE %s +%s",
-                                     Client_ID(c), Client_ID(c), modes);
-       } else {
-               /* RFC 2813 mode: one combined NICK or SERVICE command */
-               if (Client_Type(c) == CLIENT_SERVICE
-                   && strchr(Client_Flags(To), 'S'))
-                       IRC_WriteStrClientPrefix(To, Prefix,
-                                        "SERVICE %s %d * +%s %d :%s",
-                                        Client_Mask(c),
-                                        Client_MyToken(Client_Introducer(c)),
-                                        Client_Modes(c), Client_Hops(c) + 1,
-                                        Client_Info(c));
-               else
-                       IRC_WriteStrClientPrefix(To, Prefix,
-                                        "NICK %s %d %s %s %d +%s :%s",
-                                        Client_ID(c), Client_Hops(c) + 1,
-                                        user, host,
-                                        Client_MyToken(Client_Introducer(c)),
-                                        modes, Client_Info(c));
-       }
-} /* cb_introduceClient */
-
-
 /* -eof- */
diff --git a/src/ngircd/login.c b/src/ngircd/login.c
new file mode 100644 (file)
index 0000000..3808997
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#include "portab.h"
+
+/**
+ * @file
+ * Functions to deal with client logins
+ */
+
+#include "imp.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "defines.h"
+#include "conn.h"
+#include "class.h"
+#include "client.h"
+#include "client-cap.h"
+#include "channel.h"
+#include "conf.h"
+#include "io.h"
+#include "parse.h"
+#include "log.h"
+#include "messages.h"
+#include "ngircd.h"
+#include "pam.h"
+#include "irc-info.h"
+#include "irc-write.h"
+
+#include "exp.h"
+#include "login.h"
+
+#ifdef PAM
+static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events));
+#endif
+
+/**
+ * Initiate client login.
+ *
+ * This function is called after the daemon received the required NICK and
+ * USER commands of a new client. If the daemon is compiled with support for
+ * PAM, the authentication sub-processs is forked; otherwise the global server
+ * password is checked.
+ *
+ * @param Client The client logging in.
+ * @returns CONNECTED or DISCONNECTED.
+ */
+GLOBAL bool
+Login_User(CLIENT * Client)
+{
+#ifdef PAM
+       int pipefd[2], result;
+       pid_t pid;
+#endif
+       CONN_ID conn;
+
+       assert(Client != NULL);
+       conn = Client_Conn(Client);
+
+#ifndef STRICT_RFC
+       if (Conf_AuthPing) {
+               /* Did we receive the "auth PONG" already? */
+               if (Conn_GetAuthPing(conn)) {
+                       Client_SetType(Client, CLIENT_WAITAUTHPING);
+                       LogDebug("Connection %d: Waiting for AUTH PONG ...", conn);
+                       return CONNECTED;
+               }
+       }
+#endif
+
+       /* Still waiting for "CAP END" command? */
+       if (Client_Cap(Client) & CLIENT_CAP_PENDING) {
+               Client_SetType(Client, CLIENT_WAITCAPEND);
+               LogDebug("Connection %d: Waiting for CAP END ...", conn);
+               return CONNECTED;
+       }
+
+#ifdef PAM
+       if (!Conf_PAM) {
+               /* Don't do any PAM authentication at all, instead emulate
+                * the beahiour of the daemon compiled without PAM support:
+                * because there can't be any "server password", all
+                * passwords supplied are classified as "wrong". */
+               if(Client_Password(Client)[0] == '\0')
+                       return Login_User_PostAuth(Client);
+               Client_Reject(Client, "Non-empty password", false);
+               return DISCONNECTED;
+       }
+
+       if (Conf_PAMIsOptional && strcmp(Client_Password(Client), "") == 0) {
+               /* Clients are not required to send a password and to be PAM-
+                * authenticated at all. If not, they won't become "identified"
+                * and keep the "~" in their supplied user name.
+                * Therefore it is sensible to either set Conf_PAMisOptional or
+                * to enable IDENT lookups -- not both. */
+               return Login_User_PostAuth(Client);
+       }
+
+       /* Fork child process for PAM authentication; and make sure that the
+        * process timeout is set higher than the login timeout! */
+       pid = Proc_Fork(Conn_GetProcStat(conn), pipefd,
+                       cb_Read_Auth_Result, Conf_PongTimeout + 1);
+       if (pid > 0) {
+               LogDebug("Authenticator for connection %d created (PID %d).",
+                        conn, pid);
+               return CONNECTED;
+       } else {
+               /* Sub process */
+               Log_Init_Subprocess("Auth");
+               Conn_CloseAllSockets(NONE);
+               result = PAM_Authenticate(Client);
+               if (write(pipefd[1], &result, sizeof(result)) != sizeof(result))
+                       Log_Subprocess(LOG_ERR,
+                                      "Failed to pipe result to parent!");
+               Log_Exit_Subprocess("Auth");
+               exit(0);
+       }
+#else
+       /* Check global server password ... */
+       if (strcmp(Client_Password(Client), Conf_ServerPwd) != 0) {
+               /* Bad password! */
+               Client_Reject(Client, "Bad server password", false);
+               return DISCONNECTED;
+       }
+       return Login_User_PostAuth(Client);
+#endif
+}
+
+/**
+ * Finish client registration.
+ *
+ * Introduce the new client to the network and send all "hello messages"
+ * to it after authentication has been succeeded.
+ *
+ * @param Client The client logging in.
+ * @return CONNECTED or DISCONNECTED.
+ */
+GLOBAL bool
+Login_User_PostAuth(CLIENT *Client)
+{
+       assert(Client != NULL);
+
+       if (Class_HandleServerBans(Client) != CONNECTED)
+               return DISCONNECTED;
+
+       Client_Introduce(NULL, Client, CLIENT_USER);
+
+       if (!IRC_WriteStrClient
+           (Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client)))
+               return false;
+       if (!IRC_WriteStrClient
+           (Client, RPL_YOURHOST_MSG, Client_ID(Client),
+            Client_ID(Client_ThisServer()), PACKAGE_VERSION, TARGET_CPU,
+            TARGET_VENDOR, TARGET_OS))
+               return false;
+       if (!IRC_WriteStrClient
+           (Client, RPL_CREATED_MSG, Client_ID(Client), NGIRCd_StartStr))
+               return false;
+       if (!IRC_WriteStrClient
+           (Client, RPL_MYINFO_MSG, Client_ID(Client),
+            Client_ID(Client_ThisServer()), PACKAGE_VERSION, USERMODES,
+            CHANMODES))
+               return false;
+
+       /* Features supported by this server (005 numeric, ISUPPORT),
+        * see <http://www.irc.org/tech_docs/005.html> for details. */
+       if (!IRC_Send_ISUPPORT(Client))
+               return DISCONNECTED;
+
+       if (!IRC_Send_LUSERS(Client))
+               return DISCONNECTED;
+       if (!IRC_Show_MOTD(Client))
+               return DISCONNECTED;
+
+       /* Suspend the client for a second ... */
+       IRC_SetPenalty(Client, 1);
+
+       return CONNECTED;
+}
+
+#ifdef PAM
+
+/**
+ * Read result of the authenticatior sub-process from pipe
+ *
+ * @param r_fd         File descriptor of the pipe.
+ * @param events       (ignored IO specification)
+ */
+static void
+cb_Read_Auth_Result(int r_fd, UNUSED short events)
+{
+       CONN_ID conn;
+       CLIENT *client;
+       int result;
+       size_t len;
+       PROC_STAT *proc;
+
+       LogDebug("Auth: Got callback on fd %d, events %d", r_fd, events);
+       conn = Conn_GetFromProc(r_fd);
+       if (conn == NONE) {
+               /* Ops, none found? Probably the connection has already
+                * been closed!? We'll ignore that ... */
+               io_close(r_fd);
+               LogDebug("Auth: Got callback for unknown connection!?");
+               return;
+       }
+       proc = Conn_GetProcStat(conn);
+       client = Conn_GetClient(conn);
+
+       /* Read result from pipe */
+       len = Proc_Read(proc, &result, sizeof(result));
+       Proc_Close(proc);
+       if (len == 0)
+               return;
+
+       if (len != sizeof(result)) {
+               Log(LOG_CRIT, "Auth: Got malformed result!");
+               Client_Reject(client, "Internal error", false);
+               return;
+       }
+
+       if (result == true) {
+               Client_SetUser(client, Client_OrigUser(client), true);
+               (void)Login_User_PostAuth(client);
+       } else
+               Client_Reject(client, "Bad password", false);
+}
+
+#endif
+
+/* -eof- */
diff --git a/src/ngircd/login.h b/src/ngircd/login.h
new file mode 100644 (file)
index 0000000..6e3a21d
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ */
+
+#ifndef __login_h__
+#define __login_h__
+
+/**
+ * @file
+ * Functions to deal with client logins (header)
+ */
+
+GLOBAL bool Login_User PARAMS((CLIENT * Client));
+GLOBAL bool Login_User_PostAuth PARAMS((CLIENT *Client));
+
+#endif
+
+/* -eof- */
index 90e0fdc2b906fe063cfc76be2837ca380cfc120d..96ff2dea2cc8ab431eef03adb084e7f16d1e3f69 100644 (file)
 #define ERR_TOOMANYCHANNELS_MSG                "405 %s %s :You have joined too many channels"
 #define ERR_WASNOSUCHNICK_MSG          "406 %s %s :There was no such nickname"
 #define ERR_NOORIGIN_MSG               "409 %s :No origin specified"
+#define ERR_INVALIDCAP_MSG             "410 %s %s :Invalid CAP subcommand"
 #define ERR_NORECIPIENT_MSG            "411 %s :No recipient given (%s)"
 #define ERR_NOTEXTTOSEND_MSG           "412 %s :No text to send"
 #define ERR_WILDTOPLEVEL               "414 %s :Wildcard in toplevel domain"
index 02ab8935d6ca465d33c70ac29f8e731f1f46307b..66bfef53052322dd9c986d0dee19af9372f12e75 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "imp.h"
 #include "irc.h"
+#include "irc-cap.h"
 #include "irc-channel.h"
 #include "irc-info.h"
 #include "irc-login.h"
@@ -113,6 +114,7 @@ static COMMAND My_Commands[] =
        { "CHANINFO", IRC_CHANINFO, CLIENT_SERVER, 0, 0, 0 },
 #endif
 #ifndef STRICT_RFC
+       { "CAP", IRC_CAP, 0xFFFF, 0, 0, 0 },
        { "GET",  IRC_QUIT_HTTP, CLIENT_UNKNOWN, 0, 0, 0 },
        { "POST", IRC_QUIT_HTTP, CLIENT_UNKNOWN, 0, 0, 0 },
 #endif