commit 08a0a45c7a85fbd6102bf574c301da8a5f8dd25b Author: Matt Latusek Date: Sat Feb 20 21:28:34 2021 +0000 Allow modifying list of allowed characters in nicks This commit introduces the "AllowedNickChars" configuracion item which allows changing the default character list which used to be hardcoded to ";0123456789-". diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl index 58925579..5aaad4de 100644 --- a/doc/sample-ngircd.conf.tmpl +++ b/doc/sample-ngircd.conf.tmpl @@ -182,6 +182,10 @@ # commands, you can't set "a" (away) for example! Default: none. ;DefaultUserModes = i + # Extra characters allowed in nicks. Characters 'A'-'}' (letters, [, + # \, ], ^, _, `, {, |, and }) are always legal. + ; AllowedNickChars = ;0123456789- + # Do DNS lookups when a client connects to the server. ;DNS = yes diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl index e671b399..39e58f18 100644 --- a/man/ngircd.conf.5.tmpl +++ b/man/ngircd.conf.5.tmpl @@ -277,6 +277,11 @@ can be set that the client could set using regular MODE commands, you can't set "a" (away) for example! Default: none. .TP +\fBAllowedNickChars\fR (string) +Extra characters that are allowed in nicks. Letters, [, # \, ], ^, _, `, {, |, +and } are always legal. +Default: ;0123456789-. +.TP \fBDNS\fR (boolean) If set to false, ngIRCd will not make any DNS lookups when clients connect. If you configure the daemon to connect to other servers, ngIRCd may still diff --git a/src/ngircd/client.c b/src/ngircd/client.c index 67c02604..efc5449a 100644 --- a/src/ngircd/client.c +++ b/src/ngircd/client.c @@ -1218,22 +1218,19 @@ GLOBAL bool Client_IsValidNick(const char *Nick) { const char *ptr; - static const char goodchars[] = ";0123456789-"; assert (Nick != NULL); - if (strchr(goodchars, Nick[0])) + if (strchr(Conf_AllowedNickChars, Nick[0])) return false; if (strlen(Nick ) >= Conf_MaxNickLength) return false; - ptr = Nick; - while (*ptr) { - if (*ptr < 'A' && !strchr(goodchars, *ptr )) - return false; - if (*ptr > '}') + for (ptr = Nick; *ptr; ptr++) { + if (*ptr >= 'A' && *ptr <= '}') + continue; + if (!strchr(Conf_AllowedNickChars, *ptr )) return false; - ptr++; } return true; diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c index 140d0b9f..4e68065a 100644 --- a/src/ngircd/conf.c +++ b/src/ngircd/conf.c @@ -407,6 +407,7 @@ Conf_Test( void ) printf(" ConnectIPv6 = %s\n", yesno_to_str(Conf_ConnectIPv4)); #endif printf(" DefaultUserModes = %s\n", Conf_DefaultUserModes); + printf(" AllowedNickChars = %s\n", Conf_AllowedNickChars); printf(" DNS = %s\n", yesno_to_str(Conf_DNS)); #ifdef IDENT printf(" Ident = %s\n", yesno_to_str(Conf_Ident)); @@ -792,6 +793,7 @@ Set_Defaults(bool InitServers) Conf_ConnectIPv6 = false; #endif strcpy(Conf_DefaultUserModes, ""); + strcpy(Conf_AllowedNickChars, ";0123456789-"); Conf_DNS = true; #ifdef IDENTAUTH Conf_Ident = true; @@ -1642,6 +1644,21 @@ Handle_OPTIONS(const char *File, int Line, char *Var, char *Arg) } return; } + if (strcasecmp(Var, "AllowedNickChars") == 0) { + p = Arg; + Conf_AllowedNickChars[0] = '\0'; + while (*p) { + if (*p >= 'A' && *p <= '}') + continue; + if (strchr(Conf_AllowedNickChars, *p)) { + p++; + continue; + } + strncat(Conf_AllowedNickChars, p, 1); + p++; + } + return; + } if (strcasecmp(Var, "DNS") == 0) { Conf_DNS = Check_ArgIsTrue(Arg); return; diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h index b964c407..cb85be0a 100644 --- a/src/ngircd/conf.h +++ b/src/ngircd/conf.h @@ -213,6 +213,9 @@ GLOBAL bool Conf_ScrubCTCP; /** Default user modes for new local clients */ GLOBAL char Conf_DefaultUserModes[CLIENT_MODE_LEN]; +/** Characters allowed in nicks */ +GLOBAL char Conf_AllowedNickChars[196]; + /* * try to connect to remote systems using the ipv6 protocol, * if they have an ipv6 address? (default yes) commit c396d05bc3560ac3f8b5459f738521afc44889cc Author: Matt Latusek Date: Tue Mar 9 12:28:54 2021 +0000 Add Conf_NickAsUser This configuration option (default = no) forces the nickname to be the same as user name. Login attempts with different names are rejected (with ERR_ERRONEUSNICKNAME_MSG), and nick changing is prohibited. diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl index 5aaad4de..04b85b87 100644 --- a/doc/sample-ngircd.conf.tmpl +++ b/doc/sample-ngircd.conf.tmpl @@ -194,6 +194,10 @@ # prepended to their user name. ;Ident = yes + # Force the nickname to be the same as user name. They must be the same + # when logging in, and nick changing is prohibited. + ;NickAsUser = yes + # Directory containing configuration snippets (*.conf), that should # be read in after parsing this configuration file. ;IncludeDir = :ETCDIR:/conf.d diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl index 39e58f18..ee80905d 100644 --- a/man/ngircd.conf.5.tmpl +++ b/man/ngircd.conf.5.tmpl @@ -295,6 +295,11 @@ Users identified using IDENT are registered without the "~" character prepended to their user name. Default: yes. .TP +\fBNickAsUser\fR (boolean) +Force nickname to be the same as user name. They must be the same when logging +in, and nick changing is prohibited. +Default: no. +.TP \fBIncludeDir\fR (string) Directory containing configuration snippets (*.conf), that should be read in after parsing the current configuration file. diff --git a/src/ngircd/client.c b/src/ngircd/client.c index efc5449a..07bbed39 100644 --- a/src/ngircd/client.c +++ b/src/ngircd/client.c @@ -1040,7 +1040,9 @@ Client_CheckNick(CLIENT *Client, char *Nick) if (Client_Type(Client) != CLIENT_SERVER && Client_Type(Client) != CLIENT_SERVICE) { /* Make sure that this isn't a restricted/forbidden nickname */ - if (Conf_NickIsBlocked(Nick)) { + if (Conf_NickIsBlocked(Nick) || + (Conf_NickAsUser && strlen(Client_OrigUser(Client)) && + strcmp(Nick, Client_OrigUser(Client)))) { IRC_WriteErrClient(Client, ERR_FORBIDDENNICKNAME_MSG, Client_ID(Client), Nick); return false; diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c index 4e68065a..bdc70a91 100644 --- a/src/ngircd/conf.c +++ b/src/ngircd/conf.c @@ -412,6 +412,7 @@ Conf_Test( void ) #ifdef IDENT printf(" Ident = %s\n", yesno_to_str(Conf_Ident)); #endif + printf(" NickAsUser = %s\n", yesno_to_str(Conf_NickAsUser)); printf(" IncludeDir = %s\n", Conf_IncludeDir); printf(" MorePrivacy = %s\n", yesno_to_str(Conf_MorePrivacy)); printf(" NoticeBeforeRegistration = %s\n", yesno_to_str(Conf_NoticeBeforeRegistration)); @@ -800,6 +801,7 @@ Set_Defaults(bool InitServers) #else Conf_Ident = false; #endif + Conf_NickAsUser = false; strcpy(Conf_IncludeDir, ""); Conf_MorePrivacy = false; Conf_NoticeBeforeRegistration = false; @@ -1668,6 +1670,10 @@ Handle_OPTIONS(const char *File, int Line, char *Var, char *Arg) WarnIdent(File, Line); return; } + if (strcasecmp(Var, "NickAsUser") == 0) { + Conf_NickAsUser = Check_ArgIsTrue(Arg); + return; + } if (strcasecmp(Var, "IncludeDir") == 0) { if (Conf_IncludeDir[0]) { Config_Error(LOG_ERR, diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h index cb85be0a..ee3da765 100644 --- a/src/ngircd/conf.h +++ b/src/ngircd/conf.h @@ -192,6 +192,9 @@ GLOBAL bool Conf_DNS; /** Enable IDENT lookups, even when compiled with support for it */ GLOBAL bool Conf_Ident; +/** Force nickname to be the same as user name */ +GLOBAL bool Conf_NickAsUser; + /** Enable "more privacy" mode and "censor" some user-related information */ GLOBAL bool Conf_MorePrivacy; diff --git a/src/ngircd/irc-login.c b/src/ngircd/irc-login.c index 368a03d4..ed64608d 100644 --- a/src/ngircd/irc-login.c +++ b/src/ngircd/irc-login.c @@ -474,8 +474,13 @@ IRC_USER(CLIENT * Client, REQUEST * Req) LogDebug("Connection %d: got valid USER command ...", Client_Conn(Client)); - if (Client_Type(Client) == CLIENT_GOTNICK) + if (Client_Type(Client) == CLIENT_GOTNICK) { + if (Conf_NickAsUser && + strcmp (Client_OrigUser(Client), Client_ID(Client))) + return IRC_WriteErrClient(Client, ERR_ERRONEUSNICKNAME_MSG, + Client_ID(Client), Client_OrigUser(Client)); return Login_User(Client); + } else Client_SetType(Client, CLIENT_GOTUSER); return CONNECTED; commit d3d13fff8ea44cc1e24eb048057d3946d437efb8 Author: Matt Latusek Date: Tue Mar 9 12:49:00 2021 +0000 Add Conf_ConnectStats The "ConnectStats" setting allows disabling the NOTICE sent to the client when closing its connection as some of them pop up a new chat tab because of it. diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl index 04b85b87..5c5b145d 100644 --- a/doc/sample-ngircd.conf.tmpl +++ b/doc/sample-ngircd.conf.tmpl @@ -177,6 +177,9 @@ ;ConnectIPv6 = yes ;ConnectIPv4 = yes + # Show connection statistics when closing the connection + ;ConnectStats = yes + # Default user mode(s) to set on new local clients. Please note that # only modes can be set that the client could set using regular MODE # commands, you can't set "a" (away) for example! Default: none. diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl index ee80905d..2fb315b9 100644 --- a/man/ngircd.conf.5.tmpl +++ b/man/ngircd.conf.5.tmpl @@ -271,6 +271,11 @@ Set this to no if you do not want ngIRCd to connect to other IRC servers using the IPv6 protocol. Default: yes. .TP +\fBConnectStats\fR (boolean) +Should I/O statistics be sent as a NOTICE to the client when closing the +connection. +Default: yes. +.TP \fBDefaultUserModes\fR (string) Default user mode(s) to set on new local clients. Please note that only modes can be set that the client could set using regular MODE commands, you can't diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c index bdc70a91..580da634 100644 --- a/src/ngircd/conf.c +++ b/src/ngircd/conf.c @@ -406,6 +406,7 @@ Conf_Test( void ) printf(" ConnectIPv4 = %s\n", yesno_to_str(Conf_ConnectIPv6)); printf(" ConnectIPv6 = %s\n", yesno_to_str(Conf_ConnectIPv4)); #endif + printf(" ConnectStats = %s\n", yesno_to_str(Conf_ConnectStats)); printf(" DefaultUserModes = %s\n", Conf_DefaultUserModes); printf(" AllowedNickChars = %s\n", Conf_AllowedNickChars); printf(" DNS = %s\n", yesno_to_str(Conf_DNS)); @@ -793,6 +794,7 @@ Set_Defaults(bool InitServers) #else Conf_ConnectIPv6 = false; #endif + Conf_ConnectStats = true; strcpy(Conf_DefaultUserModes, ""); strcpy(Conf_AllowedNickChars, ";0123456789-"); Conf_DNS = true; @@ -1622,6 +1624,10 @@ Handle_OPTIONS(const char *File, int Line, char *Var, char *Arg) Conf_ConnectIPv4 = Check_ArgIsTrue(Arg); return; } + if (strcasecmp(Var, "ConnectStats") == 0) { + Conf_ConnectStats = Check_ArgIsTrue(Arg); + return; + } if (strcasecmp(Var, "DefaultUserModes") == 0) { p = Arg; Conf_DefaultUserModes[0] = '\0'; diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h index ee3da765..c8a1e508 100644 --- a/src/ngircd/conf.h +++ b/src/ngircd/conf.h @@ -228,6 +228,9 @@ GLOBAL bool Conf_ConnectIPv6; /** Try to connect to remote systems using the IPv4 protocol (true) */ GLOBAL bool Conf_ConnectIPv4; +/** Send connection statistics on socket close (true) */ +GLOBAL bool Conf_ConnectStats; + /** Idle timout (seconds), after which the daemon should exit */ GLOBAL int Conf_IdleTimeout; diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index 94c6c4a8..aa0ff04b 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -1045,7 +1045,7 @@ Conn_Close(CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClien if (InformClient) { #ifndef STRICT_RFC /* Send statistics to client if registered as user: */ - if ((c != NULL) && (Client_Type(c) == CLIENT_USER)) { + if (Conf_ConnectStats && (c != NULL) && (Client_Type(c) == CLIENT_USER)) { Conn_WriteStr( Idx, ":%s NOTICE %s :%sConnection statistics: client %.1f kb, server %.1f kb.", Client_ID(Client_ThisServer()), Client_ID(c), commit f3ba62ed1a10d258fa0c861ab595a8d09f9cc7c3 Author: Matt Latusek Date: Thu Mar 11 11:07:34 2021 +0000 Rework Send_Message() The following changes have been made to the Send_Message() function: 1. The actual delivery to the recipient connection has been moved to a separate function called Send_Message_To_User() to improve code clarity somewhat. 2. Sending to user@server has been fixed. Previously it would send to the first connection found, while there may be many connections from such user. 3. Sending to channels and server masks has been optimized. It does not search for all users before looking for channels anymore. diff --git a/src/ngircd/irc.c b/src/ngircd/irc.c index 3113a4ba..079a6cda 100644 --- a/src/ngircd/irc.c +++ b/src/ngircd/irc.c @@ -42,6 +42,9 @@ static char *Option_String PARAMS((CONN_ID Idx)); static bool Send_Message PARAMS((CLIENT *Client, REQUEST *Req, int ForceType, bool SendErrors)); +static bool Send_Message_To_User(CLIENT * Client, char * CurrentTarget, + char * Message, CLIENT * From, CLIENT * Recipient, + REQUEST * Req, int ForceType, bool SendErrors); static bool Send_Message_Mask PARAMS((CLIENT *from, char *command, char *targetMask, char *message, bool SendErrors)); @@ -527,7 +530,6 @@ static bool Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors) { CLIENT *cl, *from; - CL2CHAN *cl2chan; CHANNEL *chan; char *currentTarget = Req->argv[0]; char *strtok_last = NULL; @@ -586,171 +588,97 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors) goto send_next_target; } + /* $#: server/host mask, RFC 2812, sec. 3.3.1 */ + if (strchr("$#", currentTarget[0]) && strchr(currentTarget, '.')) { + if (ForceType == CLIENT_SERVICE) { + if (SendErrors && !IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG, + Client_ID(from), currentTarget)) + return DISCONNECTED; + } + else if (!Send_Message_Mask(from, Req->command, currentTarget, + message, SendErrors)) + return DISCONNECTED; + goto send_next_target; + } + + /* Target is a channel */ + if (strchr(Conf_AllowedChannelTypes, currentTarget[0])) { + if (ForceType == CLIENT_SERVICE + || !(chan = Channel_Search(currentTarget))) { + if (SendErrors && !IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG, + Client_ID(from), currentTarget)) + return DISCONNECTED; + } + else if (!Channel_Write(chan, from, Client, Req->command, + SendErrors, message)) + return DISCONNECTED; + goto send_next_target; + } + /* Check for and handle valid of form: * RFC 2812 2.3.1: * msgto = channel / ( user [ "%" host ] "@" servername ) * msgto =/ ( user "%" host ) / targetmask * msgto =/ nickname / ( nickname "!" user "@" host ) */ - if (strchr(currentTarget, '!') == NULL) - /* nickname */ - cl = Client_Search(currentTarget); - else - cl = NULL; - - if (cl == NULL) { - /* If currentTarget isn't a nickname check for: - * user ["%" host] "@" servername - * user "%" host - * nickname "!" user "@" host - */ - char target[COMMAND_LEN]; - char * nick = NULL; - char * user = NULL; - char * host = NULL; - char * server = NULL; - - strlcpy(target, currentTarget, COMMAND_LEN); - server = strchr(target, '@'); - if (server) { - *server = '\0'; - server++; - } - host = strchr(target, '%'); - if (host) { - *host = '\0'; - host++; - } - user = strchr(target, '!'); - if (user) { - /* msgto form: nick!user@host */ - *user = '\0'; - user++; - nick = target; - host = server; /* not "@server" but "@host" */ - } else { - user = target; - } - for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { - if (Client_Type(cl) != CLIENT_USER && - Client_Type(cl) != CLIENT_SERVICE) - continue; - if (nick != NULL && host != NULL) { - if (strcasecmp(nick, Client_ID(cl)) == 0 && - strcasecmp(user, Client_User(cl)) == 0 && - strcasecmp(host, Client_HostnameDisplayed(cl)) == 0) - break; - else - continue; - } - if (strcasecmp(user, Client_User(cl)) != 0) - continue; - if (host != NULL && strcasecmp(host, - Client_HostnameDisplayed(cl)) != 0) - continue; - if (server != NULL && strcasecmp(server, - Client_ID(Client_Introducer(cl))) != 0) - continue; - break; - } + char target[COMMAND_LEN]; + char * nick = NULL; + char * user = NULL; + char * host = NULL; + char * server = NULL; + + strlcpy(target, currentTarget, COMMAND_LEN); + if ((user = strchr(target, '!')) && (host = strchr(target, '@'))) { + /* msgto form: nick!user@host */ + nick = target; + *user++ = '\0'; + *host++ = '\0'; } + else if ((server = strchr(target, '@'))) { + /* msgto form: user[%host]@servername */ + user = target; + if ((host = strchr(target, '%'))) + *host++ = '\0'; + *server++ = '\0'; + } + else if ((host = strchr(target, '%'))) { + /* msgto form: user%host */ + user = target; + *host++ = '\0'; + } + else + nick = target; - if (cl) { - /* Target is a user, enforce type */ -#ifndef STRICT_RFC - if (Client_Type(cl) != ForceType && - !(ForceType == CLIENT_USER && - (Client_Type(cl) == CLIENT_USER || - Client_Type(cl) == CLIENT_SERVICE))) { -#else - if (Client_Type(cl) != ForceType) { -#endif - if (SendErrors && !IRC_WriteErrClient( - from, ERR_NOSUCHNICK_MSG,Client_ID(from), - currentTarget)) - return DISCONNECTED; - goto send_next_target; - } - -#ifndef STRICT_RFC - if (ForceType == CLIENT_SERVICE && - (Conn_Options(Client_Conn(Client_NextHop(cl))) - & CONN_RFC1459)) { - /* SQUERY command but RFC 1459 link: convert - * request to PRIVMSG command */ - Req->command = "PRIVMSG"; - } -#endif - if (Client_HasMode(cl, 'b') && - !Client_HasMode(from, 'R') && - !Client_HasMode(from, 'o') && - !(Client_Type(from) == CLIENT_SERVER) && - !(Client_Type(from) == CLIENT_SERVICE)) { - if (SendErrors && !IRC_WriteErrClient(from, - ERR_NONONREG_MSG, - Client_ID(from), Client_ID(cl))) - return DISCONNECTED; - goto send_next_target; - } - - if (Client_HasMode(cl, 'C') && - !Client_HasMode(from, 'o') && - !(Client_Type(from) == CLIENT_SERVER) && - !(Client_Type(from) == CLIENT_SERVICE)) { - cl2chan = Channel_FirstChannelOf(cl); - while (cl2chan) { - chan = Channel_GetChannel(cl2chan); - if (Channel_IsMemberOf(chan, from)) - break; - cl2chan = Channel_NextChannelOf(cl, cl2chan); - } - if (!cl2chan) { - if (SendErrors && !IRC_WriteErrClient( - from, ERR_NOTONSAMECHANNEL_MSG, - Client_ID(from), Client_ID(cl))) - return DISCONNECTED; - goto send_next_target; - } - } - - if (SendErrors && (Client_Type(Client) != CLIENT_SERVER) - && Client_HasMode(cl, 'a')) { - /* Target is away */ - if (!IRC_WriteStrClient(from, RPL_AWAY_MSG, - Client_ID(from), - Client_ID(cl), - Client_Away(cl))) - return DISCONNECTED; - } - if (Client_Conn(from) > NONE) { - Conn_UpdateIdle(Client_Conn(from)); - } - if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", - Req->command, Client_ID(cl), - message)) - return DISCONNECTED; - } else if (ForceType != CLIENT_SERVICE - && (chan = Channel_Search(currentTarget))) { - /* Target is a channel */ - if (!Channel_Write(chan, from, Client, Req->command, - SendErrors, message)) - return DISCONNECTED; - } else if (ForceType != CLIENT_SERVICE - && strchr("$#", currentTarget[0]) - && strchr(currentTarget, '.')) { - /* $#: server/host mask, RFC 2812, sec. 3.3.1 */ - if (!Send_Message_Mask(from, Req->command, currentTarget, - message, SendErrors)) - return DISCONNECTED; - } else { - if (!SendErrors) - return CONNECTED; - if (!IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG, - Client_ID(from), currentTarget)) + i = 0; + for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { + if (Client_Type(cl) != CLIENT_USER && + Client_Type(cl) != CLIENT_SERVICE) + continue; + if (nick != NULL && strcasecmp(nick, Client_ID(cl)) != 0) + continue; + if (user != NULL && strcasecmp(user, Client_User(cl)) != 0) + continue; + if (host != NULL && strcasecmp(host, + Client_HostnameDisplayed(cl)) != 0) + continue; + if (server != NULL && strcasecmp(server, + Client_ID(Client_Introducer(cl))) != 0) + continue; + i++; + if (!Send_Message_To_User(Client, currentTarget, message, from, cl, + Req, ForceType, SendErrors)) return DISCONNECTED; + if (user == NULL) + goto send_next_target; } + if (SendErrors + && i == 0 + && !IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG, + Client_ID(from), currentTarget)) + return DISCONNECTED; + send_next_target: currentTarget = strtok_r(NULL, ",", &strtok_last); if (!currentTarget) @@ -769,6 +697,106 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors) return CONNECTED; } /* Send_Message */ +/** + * Dispatch the message to the recipient's connection. + * + * This function is called from Send_Message() for each client connection + * the message should be sent to. + * + * @param Client The client from which this command has been received. + * @param CurrentTarget The textual name of the recipient. + * @param Message The message to be delivered. + * @param From Actual sender connection. + * @param Recipient Recipient connection. + * @param Req Request structure with prefix and all parameters. + * @param SendErrors Whether to report errors back to the client or not. + * @return CONNECTED or DISCONNECTED. + */ + +static bool +Send_Message_To_User(CLIENT * Client, char * CurrentTarget, char * Message, + CLIENT * From, CLIENT * Recipient, REQUEST * Req, + int ForceType, bool SendErrors) +{ + CL2CHAN *cl2chan; + CHANNEL *chan; + + /* Target is a user, enforce type */ +#ifndef STRICT_RFC + if (Client_Type(Recipient) != ForceType && + !(ForceType == CLIENT_USER && + (Client_Type(Recipient) == CLIENT_USER || + Client_Type(Recipient) == CLIENT_SERVICE))) { +#else + if (Client_Type(Recipient) != ForceType) { +#endif + if (SendErrors && !IRC_WriteErrClient( + From, ERR_NOSUCHNICK_MSG, Client_ID(From), + CurrentTarget)) + return DISCONNECTED; + return CONNECTED; + } + +#ifndef STRICT_RFC + if (ForceType == CLIENT_SERVICE && + (Conn_Options(Client_Conn(Client_NextHop(Recipient))) + & CONN_RFC1459)) { + /* SQUERY command but RFC 1459 link: convert + * request to PRIVMSG command */ + Req->command = "PRIVMSG"; + } +#endif + if (Client_HasMode(Recipient, 'b') && + !Client_HasMode(From, 'R') && + !Client_HasMode(From, 'o') && + !(Client_Type(From) == CLIENT_SERVER) && + !(Client_Type(From) == CLIENT_SERVICE)) { + if (SendErrors && !IRC_WriteErrClient(From, + ERR_NONONREG_MSG, + Client_ID(From), Client_ID(Recipient))) + return DISCONNECTED; + return CONNECTED; + } + + if (Client_HasMode(Recipient, 'C') && + !Client_HasMode(From, 'o') && + !(Client_Type(From) == CLIENT_SERVER) && + !(Client_Type(From) == CLIENT_SERVICE)) { + cl2chan = Channel_FirstChannelOf(Recipient); + while (cl2chan) { + chan = Channel_GetChannel(cl2chan); + if (Channel_IsMemberOf(chan, From)) + break; + cl2chan = Channel_NextChannelOf(Recipient, cl2chan); + } + if (!cl2chan) { + if (SendErrors && !IRC_WriteErrClient( + From, ERR_NOTONSAMECHANNEL_MSG, + Client_ID(From), Client_ID(Recipient))) + return DISCONNECTED; + return CONNECTED; + } + } + + if (SendErrors && (Client_Type(Client) != CLIENT_SERVER) + && Client_HasMode(Recipient, 'a')) { + /* Target is away */ + if (!IRC_WriteStrClient(From, RPL_AWAY_MSG, + Client_ID(From), + Client_ID(Recipient), + Client_Away(Recipient))) + return DISCONNECTED; + } + if (Client_Conn(From) > NONE) { + Conn_UpdateIdle(Client_Conn(From)); + } + if (!IRC_WriteStrClientPrefix(Recipient, From, "%s %s :%s", Req->command, + Client_ID(Recipient), Message)) + return DISCONNECTED; + + return CONNECTED; +} /* Send_Message_To_User */ + /** * Send a message to "target mask" target(s). *