From: Alexander Barton Date: Fri, 27 Apr 2012 22:49:37 +0000 (+0200) Subject: Merge branch 'capabilities' X-Git-Tag: rel-19.2-rc1~20^2 X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=ngircd-alex.git;a=commitdiff_plain;h=76565022fbda9a4054d7f74a46439db4145e5655;hp=359732af85e8566d919a2e0bc580a490ead49d9e Merge branch 'capabilities' * 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 --- diff --git a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj index affb329b..6da1cb3e 100644 --- a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj +++ b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj @@ -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 */ @@ -231,6 +234,13 @@ FAA97C56124A271400D5BBA9 /* sighandlers.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = sighandlers.h; sourceTree = ""; }; FAACD5F314A6099C006ED74F /* class.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = class.c; sourceTree = ""; }; FAACD5F414A6099C006ED74F /* class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = class.h; sourceTree = ""; }; + FAD5852F15271A7800328741 /* Capabilities.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Capabilities.txt; sourceTree = ""; }; + FAD5853015271AAB00328741 /* client-cap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "client-cap.c"; sourceTree = ""; }; + FAD5853115271AAB00328741 /* client-cap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "client-cap.h"; sourceTree = ""; }; + FAD5853315271AB800328741 /* irc-cap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "irc-cap.c"; sourceTree = ""; }; + FAD5853415271AB800328741 /* irc-cap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "irc-cap.h"; sourceTree = ""; }; + FAD5853615272C2500328741 /* login.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = login.c; sourceTree = ""; }; + FAD5853715272C2500328741 /* login.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = login.h; sourceTree = ""; }; FAE22BD215270EA300F1A5AB /* Bopm.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Bopm.txt; sourceTree = ""; }; FAE22BD415270EA300F1A5AB /* Contributing.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Contributing.txt; sourceTree = ""; }; FAE22BD515270EB500F1A5AB /* HowToRelease.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = HowToRelease.txt; sourceTree = ""; }; @@ -313,6 +323,8 @@ 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 */, @@ -329,6 +341,8 @@ 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 */, @@ -351,6 +365,8 @@ FA322CFF0CEF74B1001761B3 /* lists.h */, FA322D000CEF74B1001761B3 /* log.c */, FA322D010CEF74B1001761B3 /* log.h */, + FAD5853615272C2500328741 /* login.c */, + FAD5853715272C2500328741 /* login.h */, FA322D030CEF74B1001761B3 /* match.c */, FA322D040CEF74B1001761B3 /* match.h */, FA322D050CEF74B1001761B3 /* messages.h */, @@ -571,6 +587,7 @@ children = ( FA322D9B0CEF752C001761B3 /* Makefile.am */, FAE22BD215270EA300F1A5AB /* Bopm.txt */, + FAD5852F15271A7800328741 /* Capabilities.txt */, FAE22BD415270EA300F1A5AB /* Contributing.txt */, FA322D9A0CEF752C001761B3 /* FAQ.txt */, FA407F380DB15AC700271AF1 /* GIT.txt */, @@ -730,6 +747,9 @@ 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 index 00000000..9a692ea6 --- /dev/null +++ b/doc/Capabilities.txt @@ -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: + + + +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. diff --git a/doc/Makefile.am b/doc/Makefile.am index 1a792c5f..92e019b8 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -18,6 +18,7 @@ SUFFIXES = .tmpl static_docs = \ Bopm.txt \ + Capabilities.txt \ FAQ.txt \ GIT.txt \ HowToRelease.txt \ diff --git a/src/ngircd/Makefile.am b/src/ngircd/Makefile.am index cacd1c3c..3a411a96 100644 --- a/src/ngircd/Makefile.am +++ b/src/ngircd/Makefile.am @@ -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 index 00000000..b0da8073 --- /dev/null +++ b/src/ngircd/client-cap.c @@ -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 + +#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 index 00000000..5532d331 --- /dev/null +++ b/src/ngircd/client-cap.h @@ -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 diff --git a/src/ngircd/client.c b/src/ngircd/client.c index 7e28e8fb..1b356848 100644 --- a/src/ngircd/client.c +++ b/src/ngircd/client.c @@ -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 diff --git a/src/ngircd/client.h b/src/ngircd/client.h index 7bb230b4..4dbcc7a0 100644 --- a/src/ngircd/client.h +++ b/src/ngircd/client.h @@ -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 @@ -29,12 +29,13 @@ #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 index 00000000..a6923ac6 --- /dev/null +++ b/src/ngircd/irc-cap.c @@ -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 +#include + +#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 index 00000000..7cd4c841 --- /dev/null +++ b/src/ngircd/irc-cap.h @@ -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- */ diff --git a/src/ngircd/irc-info.c b/src/ngircd/irc-info.c index 841e6e62..0ea85874 100644 --- a/src/ngircd/irc-info.c +++ b/src/ngircd/irc-info.c @@ -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; diff --git a/src/ngircd/irc-login.c b/src/ngircd/irc-login.c index 133a0e5f..bf3254c9 100644 --- a/src/ngircd/irc-login.c +++ b/src/ngircd/irc-login.c @@ -18,22 +18,16 @@ #include "imp.h" #include -#include #include -#include #include -#include -#include -#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" @@ -42,18 +36,7 @@ #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 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 index 00000000..38089976 --- /dev/null +++ b/src/ngircd/login.c @@ -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 +#include +#include +#include + +#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 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 index 00000000..6e3a21d6 --- /dev/null +++ b/src/ngircd/login.h @@ -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- */ diff --git a/src/ngircd/messages.h b/src/ngircd/messages.h index 90e0fdc2..96ff2dea 100644 --- a/src/ngircd/messages.h +++ b/src/ngircd/messages.h @@ -103,6 +103,7 @@ #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" diff --git a/src/ngircd/parse.c b/src/ngircd/parse.c index 02ab8935..66bfef53 100644 --- a/src/ngircd/parse.c +++ b/src/ngircd/parse.c @@ -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