From ee362b3bd2e31db4cb6b7832ca01e64a643f9b96 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 31 Mar 2012 15:24:30 +0200 Subject: [PATCH 1/9] Introduce_Client() => Client_Introduce(), and move it to client.c --- src/ngircd/client.c | 96 +++++++++++++++++++++++++++++++++++++ src/ngircd/client.h | 4 +- src/ngircd/irc-login.c | 106 ++--------------------------------------- 3 files changed, 103 insertions(+), 103 deletions(-) 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..def0549c 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 @@ -165,6 +165,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-login.c b/src/ngircd/irc-login.c index 133a0e5f..53069f00 100644 --- a/src/ngircd/irc-login.c +++ b/src/ngircd/irc-login.c @@ -46,11 +46,6 @@ 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 @@ -395,7 +390,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; } @@ -487,7 +482,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 +596,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 */ @@ -1057,7 +1052,7 @@ Hello_User_PostAuth(CLIENT *Client) if (Class_HandleServerBans(Client) != CONNECTED) return DISCONNECTED; - Introduce_Client(NULL, Client, CLIENT_USER); + Client_Introduce(NULL, Client, CLIENT_USER); if (!IRC_WriteStrClient (Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client))) @@ -1119,97 +1114,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- */ From edfcc2f9d5b796fd30f60138591e4f96d54cfcf6 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 31 Mar 2012 15:38:46 +0200 Subject: [PATCH 2/9] New "login" source file Rename Hello_User[_PostAuth] to Login_User[_PostAuth] and move it to the new login.c; and move cb_Read_Auth_Result(), too. This will enable further code to easily call Login_User() when required. --- .../MacOSX/ngIRCd.xcodeproj/project.pbxproj | 6 + src/ngircd/Makefile.am | 2 + src/ngircd/irc-login.c | 210 +--------------- src/ngircd/login.c | 234 ++++++++++++++++++ src/ngircd/login.h | 25 ++ 5 files changed, 271 insertions(+), 206 deletions(-) create mode 100644 src/ngircd/login.c create mode 100644 src/ngircd/login.h diff --git a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj index b6e51c7f..d3098f4d 100644 --- a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj +++ b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 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 */; }; + 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 +232,8 @@ 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 = ""; }; + 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 = ""; }; @@ -351,6 +354,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 */, @@ -730,6 +735,7 @@ FA2D564A11EA158B00D37A35 /* pam.c in Sources */, FAA97C57124A271400D5BBA9 /* sighandlers.c in Sources */, FAACD5F514A6099C006ED74F /* class.c in Sources */, + FAD5853815272C2600328741 /* login.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/ngircd/Makefile.am b/src/ngircd/Makefile.am index cacd1c3c..e96d14be 100644 --- a/src/ngircd/Makefile.am +++ b/src/ngircd/Makefile.am @@ -42,6 +42,7 @@ ngircd_SOURCES = \ irc-write.c \ lists.c \ log.c \ + login.c \ match.c \ numeric.c \ op.c \ @@ -81,6 +82,7 @@ noinst_HEADERS = \ irc-write.h \ lists.h \ log.h \ + login.h \ match.h \ messages.h \ numeric.h \ diff --git a/src/ngircd/irc-login.c b/src/ngircd/irc-login.c index 53069f00..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,13 +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 )); -#ifdef PAM -static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events)); -#endif /** * Handler for the IRC "PASS" command. @@ -280,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 { @@ -452,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; @@ -875,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", @@ -898,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; - - 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; -} - - /** * Kill all users with a specific nick name in the network. * diff --git a/src/ngircd/login.c b/src/ngircd/login.c new file mode 100644 index 00000000..2c305402 --- /dev/null +++ b/src/ngircd/login.c @@ -0,0 +1,234 @@ +/* + * 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 "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 + +#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- */ From bd3a7ccb158c9f2eac1af77804529b76d99c3e79 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 31 Mar 2012 15:59:06 +0200 Subject: [PATCH 3/9] Implement core IRC capability handling and "CAP" command This patch implements the core functions to support "IRC Capabilities" and the IRC "CAP" command as used by other servers and specified here: . It enables ngIRCd to support the defined handshake, but it doesn't implement any capabilities, so "CAP LS" and "CAP LIST" always return the empty set and "CAP REQ ..." always fails with "CAP NAK". --- .../MacOSX/ngIRCd.xcodeproj/project.pbxproj | 14 ++ doc/Capabilities.txt | 23 +++ doc/Makefile.am | 1 + src/ngircd/Makefile.am | 4 + src/ngircd/client-cap.c | 62 ++++++ src/ngircd/client-cap.h | 28 +++ src/ngircd/client.h | 3 +- src/ngircd/irc-cap.c | 192 ++++++++++++++++++ src/ngircd/irc-cap.h | 24 +++ src/ngircd/login.c | 5 + src/ngircd/messages.h | 1 + src/ngircd/parse.c | 2 + 12 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 doc/Capabilities.txt create mode 100644 src/ngircd/client-cap.c create mode 100644 src/ngircd/client-cap.h create mode 100644 src/ngircd/irc-cap.c create mode 100644 src/ngircd/irc-cap.h diff --git a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj index d3098f4d..d89d3792 100644 --- a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj +++ b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 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 */ @@ -232,6 +234,11 @@ 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 = ""; }; @@ -318,6 +325,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 */, @@ -332,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 */, @@ -576,6 +587,7 @@ children = ( FA322D9B0CEF752C001761B3 /* Makefile.am */, FAE22BD215270EA300F1A5AB /* Bopm.txt */, + FAD5852F15271A7800328741 /* Capabilities.txt */, FAE22BD415270EA300F1A5AB /* Contributing.txt */, FA322D9A0CEF752C001761B3 /* FAQ.txt */, FA407F380DB15AC700271AF1 /* GIT.txt */, @@ -735,6 +747,8 @@ 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 e96d14be..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 \ @@ -62,6 +64,7 @@ noinst_HEADERS = \ channel.h \ class.h \ client.h \ + client-cap.h \ conf.h \ conf-ssl.h \ conn.h \ @@ -72,6 +75,7 @@ noinst_HEADERS = \ hash.h \ io.h \ irc.h \ + irc-cap.h \ irc-channel.h \ irc-info.h \ irc-login.h \ diff --git a/src/ngircd/client-cap.c b/src/ngircd/client-cap.c new file mode 100644 index 00000000..edaf2603 --- /dev/null +++ b/src/ngircd/client-cap.c @@ -0,0 +1,62 @@ +/* + * 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_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..faec1c20 --- /dev/null +++ b/src/ngircd/client-cap.h @@ -0,0 +1,28 @@ +/* + * 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 */ + +GLOBAL int Client_Cap PARAMS((CLIENT *Client)); + +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.h b/src/ngircd/client.h index def0549c..bdad9ce9 100644 --- a/src/ngircd/client.h +++ b/src/ngircd/client.h @@ -34,7 +34,7 @@ #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 +58,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 diff --git a/src/ngircd/irc-cap.c b/src/ngircd/irc-cap.c new file mode 100644 index 00000000..926943c8 --- /dev/null +++ b/src/ngircd/irc-cap.c @@ -0,0 +1,192 @@ +/* + * 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)); + +/** + * 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); + + if (Client_Type(Client) != CLIENT_USER) + Client_CapAdd(Client, CLIENT_CAP_PENDING); + + Client_CapAdd(Client, CLIENT_CAP_SUPPORTED); + return IRC_WriteStrClient(Client, "CAP %s LS :", 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 :", Client_ID(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) +{ + assert(Client != NULL); + assert(Arg != NULL); + + return IRC_WriteStrClient(Client, "CAP %s NAK :%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) +{ + assert(Client != NULL); + + return IRC_WriteStrClient(Client, "CAP %s ACK :", Client_ID(Client)); +} + +/** + * 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_GOTUSER) { + /* Only "CAP END" was missing: log in! */ + return Login_User(Client); + } + } + + return CONNECTED; +} + +/* -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/login.c b/src/ngircd/login.c index 2c305402..ad45219e 100644 --- a/src/ngircd/login.c +++ b/src/ngircd/login.c @@ -26,6 +26,7 @@ #include "conn.h" #include "class.h" #include "client.h" +#include "client-cap.h" #include "channel.h" #include "conf.h" #include "io.h" @@ -78,6 +79,10 @@ Login_User(CLIENT * Client) } #endif + /* Still waiting for "CAP END" command? */ + if (Client_Cap(Client) & CLIENT_CAP_PENDING) + return CONNECTED; + #ifdef PAM if (!Conf_PAM) { /* Don't do any PAM authentication at all, instead emulate 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..41e3872f 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, CLIENT_UNKNOWN|CLIENT_GOTNICK|CLIENT_GOTPASS|CLIENT_GOTUSER|CLIENT_USER, 0, 0, 0 }, { "GET", IRC_QUIT_HTTP, CLIENT_UNKNOWN, 0, 0, 0 }, { "POST", IRC_QUIT_HTTP, CLIENT_UNKNOWN, 0, 0, 0 }, #endif From da4c1ebe81bbd1335356ef40c91741b953c9f8d8 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 31 Mar 2012 16:37:31 +0200 Subject: [PATCH 4/9] Correctly handle "CAP END", new client type CLIENT_WAITCAPEND --- src/ngircd/client.h | 1 + src/ngircd/irc-cap.c | 2 +- src/ngircd/login.c | 5 ++++- src/ngircd/parse.c | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ngircd/client.h b/src/ngircd/client.h index bdad9ce9..4dbcc7a0 100644 --- a/src/ngircd/client.h +++ b/src/ngircd/client.h @@ -29,6 +29,7 @@ #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 diff --git a/src/ngircd/irc-cap.c b/src/ngircd/irc-cap.c index 926943c8..956b3599 100644 --- a/src/ngircd/irc-cap.c +++ b/src/ngircd/irc-cap.c @@ -180,7 +180,7 @@ Handle_CAP_END(CLIENT *Client) /* User is still logging in ... */ Client_CapDel(Client, CLIENT_CAP_PENDING); - if (Client_Type(Client) == CLIENT_GOTUSER) { + if (Client_Type(Client) == CLIENT_WAITCAPEND) { /* Only "CAP END" was missing: log in! */ return Login_User(Client); } diff --git a/src/ngircd/login.c b/src/ngircd/login.c index ad45219e..38089976 100644 --- a/src/ngircd/login.c +++ b/src/ngircd/login.c @@ -80,8 +80,11 @@ Login_User(CLIENT * Client) #endif /* Still waiting for "CAP END" command? */ - if (Client_Cap(Client) & CLIENT_CAP_PENDING) + 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) { diff --git a/src/ngircd/parse.c b/src/ngircd/parse.c index 41e3872f..66bfef53 100644 --- a/src/ngircd/parse.c +++ b/src/ngircd/parse.c @@ -114,7 +114,7 @@ static COMMAND My_Commands[] = { "CHANINFO", IRC_CHANINFO, CLIENT_SERVER, 0, 0, 0 }, #endif #ifndef STRICT_RFC - { "CAP", IRC_CAP, CLIENT_UNKNOWN|CLIENT_GOTNICK|CLIENT_GOTPASS|CLIENT_GOTUSER|CLIENT_USER, 0, 0, 0 }, + { "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 From 2327b17656b329d6210628f24c77f51533c05620 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Fri, 27 Apr 2012 22:47:22 +0200 Subject: [PATCH 5/9] "CAP REQ" starts capability negotiation and delays user registration New helper function Set_CAP_Negotiation(). --- src/ngircd/irc-cap.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/ngircd/irc-cap.c b/src/ngircd/irc-cap.c index 956b3599..c92a3f39 100644 --- a/src/ngircd/irc-cap.c +++ b/src/ngircd/irc-cap.c @@ -40,6 +40,9 @@ 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)); + + /** * Handler for the IRCv3 "CAP" command. * @@ -96,10 +99,8 @@ Handle_CAP_LS(CLIENT *Client, UNUSED char *Arg) { assert(Client != NULL); - if (Client_Type(Client) != CLIENT_USER) - Client_CapAdd(Client, CLIENT_CAP_PENDING); + Set_CAP_Negotiation(Client); - Client_CapAdd(Client, CLIENT_CAP_SUPPORTED); return IRC_WriteStrClient(Client, "CAP %s LS :", Client_ID(Client)); } @@ -131,6 +132,8 @@ Handle_CAP_REQ(CLIENT *Client, char *Arg) assert(Client != NULL); assert(Arg != NULL); + Set_CAP_Negotiation(Client); + return IRC_WriteStrClient(Client, "CAP %s NAK :%s", Client_ID(Client), Arg); } @@ -189,4 +192,19 @@ Handle_CAP_END(CLIENT *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); +} + /* -eof- */ From 245782897b4a8ca092a87bf6d3961cbda1dea962 Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Fri, 27 Apr 2012 23:56:56 +0200 Subject: [PATCH 6/9] New function Client_CapSet() in addition to Client_Cap{Add|Del} --- src/ngircd/client-cap.c | 11 +++++++++++ src/ngircd/client-cap.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/ngircd/client-cap.c b/src/ngircd/client-cap.c index edaf2603..b0da8073 100644 --- a/src/ngircd/client-cap.c +++ b/src/ngircd/client-cap.c @@ -37,6 +37,17 @@ Client_Cap(CLIENT *Client) 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) { diff --git a/src/ngircd/client-cap.h b/src/ngircd/client-cap.h index faec1c20..e477dc00 100644 --- a/src/ngircd/client-cap.h +++ b/src/ngircd/client-cap.h @@ -22,6 +22,7 @@ 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)); From f0a9dbe3ad59d3518f406f0b32a90246977ac58e Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 28 Apr 2012 00:20:42 +0200 Subject: [PATCH 7/9] IRC_Send_NAMES(): Code cleanup --- src/ngircd/irc-info.c | 75 +++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/src/ngircd/irc-info.c b/src/ngircd/irc-info.c index 841e6e62..38b9a7d6 100644 --- a/src/ngircd/irc-info.c +++ b/src/ngircd/irc-info.c @@ -1524,60 +1524,71 @@ 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 (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; From 1d7e99531a6f713a04bbc91a6f7f2963c9ece75c Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 28 Apr 2012 00:36:41 +0200 Subject: [PATCH 8/9] "multi-prefix" capability 1/2: implement complete CAP infrastructure Now ngIRCd is able to handle "CAP LS", "CAP REQ", "CAP LIST", and "CAP CLEAR" commands. "multi-prefix" can be set/unset, but has no functionality - yet! --- src/ngircd/client-cap.h | 2 + src/ngircd/irc-cap.c | 89 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/ngircd/client-cap.h b/src/ngircd/client-cap.h index e477dc00..5532d331 100644 --- a/src/ngircd/client-cap.h +++ b/src/ngircd/client-cap.h @@ -20,6 +20,8 @@ #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)); diff --git a/src/ngircd/irc-cap.c b/src/ngircd/irc-cap.c index c92a3f39..a6923ac6 100644 --- a/src/ngircd/irc-cap.c +++ b/src/ngircd/irc-cap.c @@ -42,6 +42,8 @@ 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. @@ -101,7 +103,9 @@ Handle_CAP_LS(CLIENT *Client, UNUSED char *Arg) Set_CAP_Negotiation(Client); - return IRC_WriteStrClient(Client, "CAP %s LS :", Client_ID(Client)); + return IRC_WriteStrClient(Client, + "CAP %s LS :multi-prefix", + Client_ID(Client)); } /** @@ -116,7 +120,8 @@ Handle_CAP_LIST(CLIENT *Client, UNUSED char *Arg) { assert(Client != NULL); - return IRC_WriteStrClient(Client, "CAP %s LIST :", Client_ID(Client)); + return IRC_WriteStrClient(Client, "CAP %s LIST :%s", Client_ID(Client), + Get_CAP_String(Client_Cap(Client))); } /** @@ -129,12 +134,21 @@ Handle_CAP_LIST(CLIENT *Client, UNUSED char *Arg) bool Handle_CAP_REQ(CLIENT *Client, char *Arg) { + int new_cap; + assert(Client != NULL); assert(Arg != NULL); Set_CAP_Negotiation(Client); - return IRC_WriteStrClient(Client, "CAP %s NAK :%s", + 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); } @@ -163,9 +177,16 @@ Handle_CAP_ACK(CLIENT *Client, char *Arg) bool Handle_CAP_CLEAR(CLIENT *Client) { + int cap_old; + assert(Client != NULL); - return IRC_WriteStrClient(Client, "CAP %s ACK :", Client_ID(Client)); + 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)); } /** @@ -207,4 +228,64 @@ Set_CAP_Negotiation(CLIENT *Client) 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- */ From 4602cb9891a0234e391d56ac4b2491d134020f1b Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Sat, 28 Apr 2012 00:38:51 +0200 Subject: [PATCH 9/9] "multi-prefix" capability 2/2: adjust NAME and WHO handlers The NAME and WHO commands now return multiple usermode prfixes when the "multi-prefix" capability is in effect for the requesting client. See --- .../MacOSX/ngIRCd.xcodeproj/project.pbxproj | 2 +- src/ngircd/irc-info.c | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj index d89d3792..23026c64 100644 --- a/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj +++ b/contrib/MacOSX/ngIRCd.xcodeproj/project.pbxproj @@ -682,7 +682,7 @@ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0420; + LastUpgradeCheck = 0430; }; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "ngIRCd" */; compatibilityVersion = "Xcode 3.2"; diff --git a/src/ngircd/irc-info.c b/src/ngircd/irc-info.c index 38b9a7d6..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)); @@ -1569,10 +1578,16 @@ IRC_Send_NAMES(CLIENT * Client, CHANNEL * Chan) if (is_member || is_visible) { 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)); + 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)) {