]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/client.c
Allow longer usernames for authentication
[ngircd-alex.git] / src / ngircd / client.c
index 8402499936e02c530f81592c3c385a0e1f85a947..07d448fdbaf9a84873f32e22584636251e2d3fd5 100644 (file)
@@ -1,52 +1,45 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2010 Alexander Barton (alex@barton.de)
+ * Copyright (c)2001-2014 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.
- *
- * Client management.
  */
 
-
 #define __client_c__
 
-
 #include "portab.h"
 
-#include "imp.h"
+/**
+ * @file
+ * Client management.
+ */
+
 #include <assert.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <time.h>
 #include <netdb.h>
 
-#include "defines.h"
 #include "conn.h"
-
-#include "exp.h"
-#include "client.h"
-
-#include <imp.h>
 #include "ngircd.h"
 #include "channel.h"
 #include "conf.h"
+#include "conn-func.h"
 #include "hash.h"
 #include "irc-write.h"
 #include "log.h"
+#include "match.h"
 #include "messages.h"
 
-#include <exp.h>
-
-
 #define GETID_LEN (CLIENT_NICK_LEN-1) + 1 + (CLIENT_USER_LEN-1) + 1 + (CLIENT_HOST_LEN-1) + 1
 
-
 static CLIENT *This_Server, *My_Clients;
 
 static WHOWAS My_Whowas[MAX_WHOWAS];
@@ -61,6 +54,8 @@ static CLIENT *New_Client_Struct PARAMS(( void ));
 static void Generate_MyToken PARAMS(( CLIENT *Client ));
 static void Adjust_Counters PARAMS(( CLIENT *Client ));
 
+static void Free_Client PARAMS(( CLIENT **Client ));
+
 static CLIENT *Init_New_Client PARAMS((CONN_ID Idx, CLIENT *Introducer,
                                       CLIENT *TopServer, int Type, const char *ID,
                                       const char *User, const char *Hostname, const char *Info,
@@ -70,6 +65,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 )
@@ -84,7 +81,7 @@ Client_Init( void )
                exit( 1 );
        }
 
-       /* Client-Struktur dieses Servers */
+       /* Client structure for this server */
        This_Server->next = NULL;
        This_Server->type = CLIENT_SERVER;
        This_Server->conn_id = NONE;
@@ -93,7 +90,7 @@ Client_Init( void )
        This_Server->hops = 0;
 
        gethostname( This_Server->host, CLIENT_HOST_LEN );
-       if (!Conf_NoDNS) {
+       if (Conf_DNS) {
                h = gethostbyname( This_Server->host );
                if (h) strlcpy(This_Server->host, h->h_name, sizeof(This_Server->host));
        }
@@ -117,14 +114,15 @@ Client_Exit( void )
        
        cnt = 0;
        c = My_Clients;
-       while( c )
-       {
+       while(c) {
                cnt++;
                next = (CLIENT *)c->next;
-               free( c );
+               Free_Client(&c);
                c = next;
        }
-       if( cnt ) Log( LOG_INFO, "Freed %d client structure%s.", cnt, cnt == 1 ? "" : "s" );
+       if (cnt)
+               Log(LOG_INFO, "Freed %d client structure%s.",
+                   cnt, cnt == 1 ? "" : "s");
 } /* Client_Exit */
 
 
@@ -187,7 +185,6 @@ Init_New_Client(CONN_ID Idx, CLIENT *Introducer, CLIENT *TopServer,
 
        assert(Idx >= NONE);
        assert(Introducer != NULL);
-       assert(Hostname != NULL);
 
        client = New_Client_Struct();
        if (!client)
@@ -215,8 +212,8 @@ Init_New_Client(CONN_ID Idx, CLIENT *Introducer, CLIENT *TopServer,
        if (Type == CLIENT_SERVER)
                Generate_MyToken(client);
 
-       if (strchr(client->modes, 'a'))
-               strlcpy(client->away, DEFAULT_AWAY_MSG, sizeof(client->away));
+       if (Client_HasMode(client, 'a'))
+               client->away = strndup(DEFAULT_AWAY_MSG, CLIENT_AWAY_LEN - 1);
 
        client->next = (POINTER *)My_Clients;
        My_Clients = client;
@@ -233,14 +230,14 @@ Client_Destroy( CLIENT *Client, const char *LogMsg, const char *FwdMsg, bool Sen
        /* remove a client */
        
        CLIENT *last, *c;
-       char msg[LINE_LEN];
+       char msg[COMMAND_LEN];
        const char *txt;
 
        assert( Client != NULL );
 
-       if( LogMsg ) txt = LogMsg;
-       else txt = FwdMsg;
-       if( ! txt ) txt = "Reason unknown.";
+       txt = LogMsg ? LogMsg : FwdMsg;
+       if (!txt)
+               txt = "Reason unknown";
 
        /* netsplit message */
        if( Client->type == CLIENT_SERVER ) {
@@ -280,10 +277,15 @@ Client_Destroy( CLIENT *Client, const char *LogMsg, const char *FwdMsg, bool Sen
                                Destroy_UserOrService(c, txt, FwdMsg, SendQuit);
                        else if( c->type == CLIENT_SERVER )
                        {
-                               if( c != This_Server )
-                               {
-                                       if( c->conn_id != NONE ) Log( LOG_NOTICE|LOG_snotice, "Server \"%s\" unregistered (connection %d): %s", c->id, c->conn_id, txt );
-                                       else Log( LOG_NOTICE|LOG_snotice, "Server \"%s\" unregistered: %s", c->id, txt );
+                               if (c != This_Server) {
+                                       if (c->conn_id != NONE)
+                                               Log(LOG_NOTICE|LOG_snotice,
+                                                   "Server \"%s\" unregistered (connection %d): %s.",
+                                               c->id, c->conn_id, txt);
+                                       else
+                                               Log(LOG_NOTICE|LOG_snotice,
+                                                   "Server \"%s\" unregistered: %s.",
+                                                   c->id, txt);
                                }
 
                                /* inform other servers */
@@ -295,17 +297,23 @@ Client_Destroy( CLIENT *Client, const char *LogMsg, const char *FwdMsg, bool Sen
                        }
                        else
                        {
-                               if( c->conn_id != NONE )
-                               {
-                                       if( c->id[0] ) Log( LOG_NOTICE, "Client \"%s\" unregistered (connection %d): %s", c->id, c->conn_id, txt );
-                                       else Log( LOG_NOTICE, "Client unregistered (connection %d): %s", c->conn_id, txt );
+                               if (c->conn_id != NONE) {
+                                       if (c->id[0])
+                                               Log(LOG_NOTICE,
+                                                   "Client \"%s\" unregistered (connection %d): %s.",
+                                                   c->id, c->conn_id, txt);
+                                       else
+                                               Log(LOG_NOTICE,
+                                                   "Client unregistered (connection %d): %s.",
+                                                   c->conn_id, txt);
                                } else {
-                                       Log(LOG_WARNING, "Unregistered unknown client \"%s\": %s",
-                                                               c->id[0] ? c->id : "(No Nick)", txt );
+                                       Log(LOG_WARNING,
+                                           "Unregistered unknown client \"%s\": %s",
+                                           c->id[0] ? c->id : "(No Nick)", txt);
                                }
                        }
 
-                       free( c );
+                       Free_Client(&c);
                        break;
                }
                last = c;
@@ -314,16 +322,60 @@ Client_Destroy( CLIENT *Client, const char *LogMsg, const char *FwdMsg, bool Sen
 } /* Client_Destroy */
 
 
+/**
+ * Set client hostname.
+ *
+ * If global hostname cloaking is in effect, don't set the real hostname
+ * but the configured one.
+ *
+ * @param Client The client of which the hostname should be set.
+ * @param Hostname The new hostname.
+ */
 GLOBAL void
 Client_SetHostname( CLIENT *Client, const char *Hostname )
 {
-       assert( Client != NULL );
-       assert( Hostname != NULL );
+       assert(Client != NULL);
+       assert(Hostname != NULL);
+
+       if (Conf_CloakHost[0]) {
+               char cloak[GETID_LEN];
+
+               strlcpy(cloak, Hostname, GETID_LEN);
+               strlcat(cloak, Conf_CloakHostSalt, GETID_LEN);
+               snprintf(cloak, GETID_LEN, Conf_CloakHost, Hash(cloak));
 
-       strlcpy( Client->host, Hostname, sizeof( Client->host ));
+               LogDebug("Updating hostname of \"%s\": \"%s\" -> \"%s\"",
+                       Client_ID(Client), Client->host, cloak);
+               strlcpy(Client->host, cloak, sizeof(Client->host));
+       } else {
+               LogDebug("Updating hostname of \"%s\": \"%s\" -> \"%s\"",
+                        Client_ID(Client), Client->host, Hostname);
+               strlcpy(Client->host, Hostname, sizeof(Client->host));
+       }
 } /* Client_SetHostname */
 
 
+/**
+ * Set IP address to display for a client.
+ *
+ * @param Client The client.
+ * @param IPAText Textual representation of the IP address or NULL to unset.
+ */
+GLOBAL void
+Client_SetIPAText(CLIENT *Client, const char *IPAText)
+{
+       assert(Client != NULL);
+
+       if (Client->ipa_text)
+               free(Client->ipa_text);
+
+       if (*IPAText)
+               Client->ipa_text = strndup(IPAText, CLIENT_HOST_LEN - 1);
+       else
+               Client->ipa_text = NULL;
+}
+
+
 GLOBAL void
 Client_SetID( CLIENT *Client, const char *ID )
 {
@@ -332,6 +384,11 @@ Client_SetID( CLIENT *Client, const char *ID )
        
        strlcpy( Client->id, ID, sizeof( Client->id ));
 
+       if (Conf_CloakUserToNick) {
+               strlcpy( Client->user, ID, sizeof( Client->user ));
+               strlcpy( Client->info, ID, sizeof( Client->info ));
+       }
+
        /* Hash */
        Client->hash = Hash( Client->id );
 } /* Client_SetID */
@@ -345,7 +402,9 @@ Client_SetUser( CLIENT *Client, const char *User, bool Idented )
        assert( Client != NULL );
        assert( User != NULL );
 
-       if (Idented) {
+       if (Conf_CloakUserToNick) {
+               strlcpy(Client->user, Client->id, sizeof(Client->user));
+       } else if (Idented) {
                strlcpy(Client->user, User, sizeof(Client->user));
        } else {
                Client->user[0] = '~';
@@ -363,11 +422,12 @@ Client_SetUser( CLIENT *Client, const char *User, bool Idented )
  * @param User User name to set.
  */
 GLOBAL void
-Client_SetOrigUser(CLIENT *Client, const char *User) {
+Client_SetOrigUser(CLIENT UNUSED *Client, const char UNUSED *User)
+{
        assert(Client != NULL);
        assert(User != NULL);
 
-#if defined(PAM) && defined(IDENTAUTH)
+#if defined(PAM)
        strlcpy(Client->orig_user, User, sizeof(Client->orig_user));
 #endif
 } /* Client_SetOrigUser */
@@ -381,7 +441,10 @@ Client_SetInfo( CLIENT *Client, const char *Info )
        assert( Client != NULL );
        assert( Info != NULL );
 
-       strlcpy(Client->info, Info, sizeof(Client->info));
+       if (Conf_CloakUserToNick)
+               strlcpy(Client->info, Client->id, sizeof(Client->info));
+       else
+               strlcpy(Client->info, Info, sizeof(Client->info));
 } /* Client_SetInfo */
 
 
@@ -406,15 +469,19 @@ Client_SetFlags( CLIENT *Client, const char *Flags )
 
 
 GLOBAL void
-Client_SetPassword( CLIENT *Client, const char *Pwd )
+Client_SetAccountName(CLIENT *Client, const char *AccountName)
 {
-       /* set password sent by client */
+       assert(Client != NULL);
 
-       assert( Client != NULL );
-       assert( Pwd != NULL );
+       if (Client->account_name)
+               free(Client->account_name);
 
-       strlcpy(Client->pwd, Pwd, sizeof(Client->pwd));
-} /* Client_SetPassword */
+       if (*AccountName)
+               Client->account_name = strndup(AccountName,
+                                              CLIENT_NICK_LEN - 1);
+       else
+               Client->account_name = NULL;
+}
 
 
 GLOBAL void
@@ -425,7 +492,11 @@ Client_SetAway( CLIENT *Client, const char *Txt )
        assert( Client != NULL );
        assert( Txt != NULL );
 
-       strlcpy( Client->away, Txt, sizeof( Client->away ));
+       if (Client->away)
+               free(Client->away);
+
+       Client->away = strndup(Txt, CLIENT_AWAY_LEN - 1);
+
        LogDebug("%s \"%s\" is away: %s", Client_TypeText(Client),
                 Client_Mask(Client), Txt);
 } /* Client_SetAway */
@@ -466,19 +537,11 @@ Client_SetIntroducer( CLIENT *Client, CLIENT *Introducer )
 } /* Client_SetIntroducer */
 
 
-GLOBAL void
-Client_SetOperByMe( CLIENT *Client, bool OperByMe )
-{
-       assert( Client != NULL );
-       Client->oper_by_me = OperByMe;
-} /* Client_SetOperByMe */
-
-
 GLOBAL bool
 Client_ModeAdd( CLIENT *Client, char Mode )
 {
        /* Set Mode.
-        * If Client already alread had Mode, return false.
+        * If Client already had Mode, return false.
         * If the Mode was newly set, return true.
         */
 
@@ -487,7 +550,7 @@ Client_ModeAdd( CLIENT *Client, char Mode )
        assert( Client != NULL );
 
        x[0] = Mode; x[1] = '\0';
-       if (!strchr( Client->modes, x[0])) {
+       if (!Client_HasMode(Client, x[0])) {
                strlcat( Client->modes, x, sizeof( Client->modes ));
                return true;
        }
@@ -522,13 +585,14 @@ Client_ModeDel( CLIENT *Client, char Mode )
 } /* Client_ModeDel */
 
 
+/**
+ * Search CLIENT structure of a given nick name.
+ *
+ * @return Pointer to CLIENT structure or NULL if not found.
+ */
 GLOBAL CLIENT *
 Client_Search( const char *Nick )
 {
-       /* return Client-Structure that has the corresponding Nick.
-        * If none is found, return NULL.
-        */
-
        char search_id[CLIENT_ID_LEN], *ptr;
        CLIENT *c = NULL;
        UINT32 search_hash;
@@ -549,20 +613,54 @@ Client_Search( const char *Nick )
                c = (CLIENT *)c->next;
        }
        return NULL;
-} /* Client_Search */
+}
 
 
+/**
+ * Search first CLIENT structure matching a given mask of a server.
+ *
+ * The order of servers is arbitrary, but this function makes sure that the
+ * local server is always returned if the mask matches it.
+ *
+ * @return Pointer to CLIENT structure or NULL if no server could be found.
+ */
 GLOBAL CLIENT *
-Client_GetFromToken( CLIENT *Client, int Token )
+Client_SearchServer(const char *Mask)
 {
-       /* Client-Struktur, die den entsprechenden Introducer (=Client)
-        * und das gegebene Token hat, liefern. Wird keine gefunden,
-        * so wird NULL geliefert. */
+       CLIENT *c;
+
+       assert(Mask != NULL);
+
+       /* First check if mask matches the local server */
+       if (MatchCaseInsensitive(Mask, Client_ID(Client_ThisServer())))
+               return Client_ThisServer();
+
+       c = My_Clients;
+       while (c) {
+               if (Client_Type(c) == CLIENT_SERVER) {
+                       /* This is a server: check if Mask matches */
+                       if (MatchCaseInsensitive(Mask, c->id))
+                               return c;
+               }
+               c = (CLIENT *)c->next;
+       }
+       return NULL;
+}
+
 
+/**
+ * Get client structure ("introducer") identfied by a server token.
+ * @return CLIENT structure or NULL if none could be found.
+ */
+GLOBAL CLIENT *
+Client_GetFromToken( CLIENT *Client, int Token )
+{
        CLIENT *c;
 
        assert( Client != NULL );
-       assert( Token > 0 );
+
+       if (!Token)
+               return NULL;
 
        c = My_Clients;
        while (c) {
@@ -633,35 +731,123 @@ Client_User( CLIENT *Client )
  */
 GLOBAL char *
 Client_OrigUser(CLIENT *Client) {
-#ifndef IDENTAUTH
-       char *user = Client->user;
-
-       if (user[0] == '~')
-               user++;
-       return user;
-#else
        return Client->orig_user;
-#endif
 } /* Client_OrigUser */
 
 #endif
 
-
+/**
+ * Return the hostname of a client.
+ * @param Client Pointer to client structure
+ * @return Pointer to client hostname
+ */
 GLOBAL char *
-Client_Hostname( CLIENT *Client )
+Client_Hostname(CLIENT *Client)
 {
-       assert( Client != NULL );
+       assert (Client != NULL);
        return Client->host;
-} /* Client_Hostname */
+}
 
+/**
+ * Return the cloaked hostname of a client, if set.
+ * @param Client Pointer to the client structure.
+ * @return Pointer to the cloaked hostname or NULL if not set.
+ */
+GLOBAL char *
+Client_HostnameCloaked(CLIENT *Client)
+{
+       assert(Client != NULL);
+       return Client->cloaked;
+}
 
+/**
+ * Get (potentially cloaked) hostname of a client to display it to other users.
+ *
+ * If the client has not enabled cloaking, the real hostname is used.
+ *
+ * @param Client Pointer to client structure
+ * @return Pointer to client hostname
+ */
 GLOBAL char *
-Client_Password( CLIENT *Client )
+Client_HostnameDisplayed(CLIENT *Client)
 {
-       assert( Client != NULL );
-       return Client->pwd;
-} /* Client_Password */
+       assert(Client != NULL);
+
+       /* Client isn't cloaked at all, return real hostname: */
+       if (!Client_HasMode(Client, 'x'))
+               return Client_Hostname(Client);
+
+       /* Use an already saved cloaked hostname, if there is one */
+       if (Client->cloaked)
+               return Client->cloaked;
+
+       Client_UpdateCloakedHostname(Client, NULL, NULL);
+       return Client->cloaked;
+}
 
+GLOBAL const char *
+Client_IPAText(CLIENT *Client)
+{
+       assert(Client != NULL);
+
+       /* Not a local client? */
+       if (Client_Conn(Client) <= NONE)
+               return "0.0.0.0";
+
+       if (!Client->ipa_text)
+               return Conn_GetIPAInfo(Client_Conn(Client));
+       else
+               return Client->ipa_text;
+}
+
+/**
+ * Update (and generate, if necessary) the cloaked hostname of a client.
+ *
+ * The newly set cloaked hostname is announced in the network using METADATA
+ * commands to peers that support this feature.
+ *
+ * @param Client The client of which the cloaked hostname should be updated.
+ * @param Origin The originator of the hostname change, or NULL if this server.
+ * @param Hostname The new cloaked hostname, or NULL if it should be generated.
+ */
+GLOBAL void
+Client_UpdateCloakedHostname(CLIENT *Client, CLIENT *Origin,
+                            const char *Hostname)
+{
+       char Cloak_Buffer[CLIENT_HOST_LEN];
+
+       assert(Client != NULL);
+       if (!Origin)
+               Origin = Client_ThisServer();
+
+       if (!Client->cloaked) {
+               Client->cloaked = malloc(CLIENT_HOST_LEN);
+               if (!Client->cloaked)
+                       return;
+       }
+
+       if (!Hostname) {
+               /* Generate new cloaked hostname */
+               if (*Conf_CloakHostModeX) {
+                       strlcpy(Cloak_Buffer, Client->host,
+                               sizeof(Cloak_Buffer));
+                       strlcat(Cloak_Buffer, Conf_CloakHostSalt,
+                               sizeof(Cloak_Buffer));
+                       snprintf(Client->cloaked, CLIENT_HOST_LEN,
+                                Conf_CloakHostModeX, Hash(Cloak_Buffer));
+               } else
+                       strlcpy(Client->cloaked, Client_ID(Client->introducer),
+                               CLIENT_HOST_LEN);
+       } else
+               strlcpy(Client->cloaked, Hostname, CLIENT_HOST_LEN);
+       LogDebug("Cloaked hostname of \"%s\" updated to \"%s\"",
+                Client_ID(Client), Client->cloaked);
+
+       /* Inform other servers in the network */
+       IRC_WriteStrServersPrefixFlag(Client_NextHop(Origin), Origin, 'M',
+                                     "METADATA %s cloakhost :%s",
+                                     Client_ID(Client), Client->cloaked);
+}
 
 GLOBAL char *
 Client_Modes( CLIENT *Client )
@@ -679,14 +865,6 @@ Client_Flags( CLIENT *Client )
 } /* Client_Flags */
 
 
-GLOBAL bool
-Client_OperByMe( CLIENT *Client )
-{
-       assert( Client != NULL );
-       return Client->oper_by_me;
-} /* Client_OperByMe */
-
-
 GLOBAL int
 Client_Hops( CLIENT *Client )
 {
@@ -727,23 +905,59 @@ Client_NextHop( CLIENT *Client )
 
 
 /**
- * return Client-ID ("client!user@host"), this ID is needed for e.g.
- * prefixes.  Returnes pointer to static buffer.
+ * Return ID of a client: "client!user@host"
+ * This client ID is used for IRC prefixes, for example.
+ * Please note that this function uses a global static buffer, so you can't
+ * nest invocations without overwriting earlier results!
+ * @param Client Pointer to client structure
+ * @return Pointer to global buffer containing the client ID
  */
 GLOBAL char *
 Client_Mask( CLIENT *Client )
 {
-       static char GetID_Buffer[GETID_LEN];
+       static char Mask_Buffer[GETID_LEN];
 
-       assert( Client != NULL );
+       assert (Client != NULL);
 
-       if( Client->type == CLIENT_SERVER ) return Client->id;
+       /* Servers: return name only, there is no "mask" */
+       if (Client->type == CLIENT_SERVER)
+               return Client->id;
 
-       snprintf(GetID_Buffer, GETID_LEN, "%s!%s@%s", Client->id, Client->user, Client->host);
-       return GetID_Buffer;
+       snprintf(Mask_Buffer, GETID_LEN, "%s!%s@%s",
+                Client->id, Client->user, Client->host);
+       return Mask_Buffer;
 } /* Client_Mask */
 
 
+/**
+ * Return ID of a client with cloaked hostname: "client!user@server-name"
+ *
+ * This client ID is used for IRC prefixes, for example.
+ * Please note that this function uses a global static buffer, so you can't
+ * nest invocations without overwriting earlier results!
+ * If the client has not enabled cloaking, the real hostname is used.
+ *
+ * @param Client Pointer to client structure
+ * @return Pointer to global buffer containing the client ID
+ */
+GLOBAL char *
+Client_MaskCloaked(CLIENT *Client)
+{
+       static char Mask_Buffer[GETID_LEN];
+
+       assert (Client != NULL);
+
+       /* Is the client using cloaking at all? */
+       if (!Client_HasMode(Client, 'x'))
+               return Client_Mask(Client);
+
+       snprintf(Mask_Buffer, GETID_LEN, "%s!%s@%s", Client->id, Client->user,
+                Client_HostnameDisplayed(Client));
+
+       return Mask_Buffer;
+} /* Client_MaskCloaked */
+
+
 GLOBAL CLIENT *
 Client_Introducer( CLIENT *Client )
 {
@@ -768,6 +982,14 @@ Client_HasMode( CLIENT *Client, char Mode )
 } /* Client_HasMode */
 
 
+GLOBAL bool
+Client_HasFlag( CLIENT *Client, char Flag )
+{
+       assert( Client != NULL );
+       return strchr( Client->flags, Flag ) != NULL;
+} /* Client_HasFlag */
+
+
 GLOBAL char *
 Client_Away( CLIENT *Client )
 {
@@ -776,23 +998,55 @@ Client_Away( CLIENT *Client )
 } /* Client_Away */
 
 
-GLOBAL bool
-Client_CheckNick( CLIENT *Client, char *Nick )
+GLOBAL char *
+Client_AccountName(CLIENT *Client)
 {
-       assert( Client != NULL );
-       assert( Nick != NULL );
+       assert(Client != NULL);
+       return Client->account_name;
+}
 
-       if (! Client_IsValidNick( Nick ))
-       {
-               IRC_WriteStrClient( Client, ERR_ERRONEUSNICKNAME_MSG, Client_ID( Client ), Nick );
+
+/**
+ * Make sure that a given nickname is valid.
+ *
+ * If the nickname is not valid for the given client, this function sends back
+ * the appropriate error messages.
+ *
+ * @param      Client Client that wants to change the nickname.
+ * @param      Nick New nickname.
+ * @returns    true if nickname is valid, false otherwise.
+ */
+GLOBAL bool
+Client_CheckNick(CLIENT *Client, char *Nick)
+{
+       assert(Client != NULL);
+       assert(Nick != NULL);
+
+       if (!Client_IsValidNick(Nick)) {
+               if (strlen(Nick ) >= Conf_MaxNickLength)
+                       IRC_WriteErrClient(Client, ERR_NICKNAMETOOLONG_MSG,
+                                          Client_ID(Client), Nick,
+                                          Conf_MaxNickLength - 1);
+               else
+                       IRC_WriteErrClient(Client, ERR_ERRONEUSNICKNAME_MSG,
+                                          Client_ID(Client), Nick);
                return false;
        }
 
-       /* Nick bereits vergeben? */
-       if( Client_Search( Nick ))
-       {
-               /* den Nick gibt es bereits */
-               IRC_WriteStrClient( Client, ERR_NICKNAMEINUSE_MSG, Client_ID( Client ), 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)) {
+                       IRC_WriteErrClient(Client, ERR_FORBIDDENNICKNAME_MSG,
+                                          Client_ID(Client), Nick);
+                       return false;
+               }
+       }
+
+       /* Nickname already registered? */
+       if (Client_Search(Nick)) {
+               IRC_WriteErrClient(Client, ERR_NICKNAMEINUSE_MSG,
+                       Client_ID(Client), Nick);
                return false;
        }
 
@@ -812,7 +1066,8 @@ Client_CheckID( CLIENT *Client, char *ID )
 
        /* ID too long? */
        if (strlen(ID) > CLIENT_ID_LEN) {
-               IRC_WriteStrClient(Client, ERR_ERRONEUSNICKNAME_MSG, Client_ID(Client), ID);
+               IRC_WriteErrClient(Client, ERR_ERRONEUSNICKNAME_MSG,
+                                  Client_ID(Client), ID);
                return false;
        }
 
@@ -910,7 +1165,8 @@ Client_OperCount( void )
        c = My_Clients;
        while( c )
        {
-               if( c && ( c->type == CLIENT_USER ) && ( strchr( c->modes, 'o' ))) cnt++;
+               if (c && c->type == CLIENT_USER && Client_HasMode(c, 'o' ))
+                       cnt++;
                c = (CLIENT *)c->next;
        }
        return cnt;
@@ -948,23 +1204,31 @@ Client_MyMaxUserCount( void )
 } /* Client_MyMaxUserCount */
 
 
+/**
+ * Check that a given nickname is valid.
+ *
+ * @param      Nick the nickname to check.
+ * @returns    true if nickname is valid, false otherwise.
+ */
 GLOBAL bool
-Client_IsValidNick( const char *Nick )
+Client_IsValidNick(const char *Nick)
 {
        const char *ptr;
        static const char goodchars[] = ";0123456789-";
 
-       assert( Nick != NULL );
+       assert (Nick != NULL);
 
-       if( Nick[0] == '#' ) return false;
-       if( strchr( goodchars, Nick[0] )) return false;
-       if( strlen( Nick ) >= Conf_MaxNickLength) return false;
+       if (strchr(goodchars, 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 > '}' ) return false;
+       while (*ptr) {
+               if (*ptr < 'A' && !strchr(goodchars, *ptr ))
+                       return false;
+               if (*ptr > '}')
+                       return false;
                ptr++;
        }
 
@@ -1004,6 +1268,79 @@ Client_StartTime(CLIENT *Client)
 } /* Client_Uptime */
 
 
+/**
+ * Reject a client when logging in.
+ *
+ * This function is called when a client isn't allowed to connect to this
+ * server. Possible reasons are bad server password, bad PAM password,
+ * or that the client is G/K-Line'd.
+ *
+ * After calling this function, the client isn't connected any more.
+ *
+ * @param Client The client to reject.
+ * @param Reason The reason why the client has been rejected.
+ * @param InformClient If true, send the exact reason to the client.
+ */
+GLOBAL void
+Client_Reject(CLIENT *Client, const char *Reason, bool InformClient)
+{
+       char info[COMMAND_LEN];
+
+       assert(Client != NULL);
+       assert(Reason != NULL);
+
+       if (InformClient)
+               snprintf(info, sizeof(info), "Access denied: %s", Reason);
+       else
+               strcpy(info, "Access denied: Bad password?");
+
+       Log(LOG_ERR,
+           "User \"%s\" rejected (connection %d): %s!",
+           Client_Mask(Client), Client_Conn(Client), Reason);
+       Conn_Close(Client_Conn(Client), Reason, info, true);
+}
+
+
+/**
+ * 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_NickIsService(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 )
 {
@@ -1036,11 +1373,14 @@ MyCount( CLIENT_TYPE Type )
 } /* MyCount */
 
 
+/**
+ * Allocate and initialize new CLIENT strcuture.
+ *
+ * @return Pointer to CLIENT structure or NULL on error.
+ */
 static CLIENT *
 New_Client_Struct( void )
 {
-       /* Neue CLIENT-Struktur pre-initialisieren */
-
        CLIENT *c;
 
        c = (CLIENT *)malloc( sizeof( CLIENT ));
@@ -1054,14 +1394,34 @@ New_Client_Struct( void )
 
        c->type = CLIENT_UNKNOWN;
        c->conn_id = NONE;
-       c->oper_by_me = false;
        c->hops = -1;
        c->token = -1;
        c->mytoken = -1;
 
        return c;
-} /* New_Client */
+}
 
+/**
+ * Free a CLIENT structure and its member variables.
+ */
+static void
+Free_Client(CLIENT **Client)
+{
+       assert(Client != NULL);
+       assert(*Client != NULL);
+
+       if ((*Client)->account_name)
+               free((*Client)->account_name);
+       if ((*Client)->away)
+               free((*Client)->away);
+       if ((*Client)->cloaked)
+               free((*Client)->cloaked);
+       if ((*Client)->ipa_text)
+               free((*Client)->ipa_text);
+
+       free(*Client);
+       *Client = NULL;
+}
 
 static void
 Generate_MyToken( CLIENT *Client )
@@ -1075,7 +1435,7 @@ Generate_MyToken( CLIENT *Client )
        {
                if( c->mytoken == token )
                {
-                       /* Das Token wurde bereits vergeben */
+                       /* The token is already in use */
                        token++;
                        c = My_Clients;
                        continue;
@@ -1121,6 +1481,10 @@ Client_RegisterWhowas( CLIENT *Client )
 
        assert( Client != NULL );
 
+       /* Don't register WHOWAS information when "MorePrivacy" is enabled. */
+       if (Conf_MorePrivacy)
+               return;
+
        now = time(NULL);
        /* Don't register clients that were connected less than 30 seconds. */
        if( now - Client->starttime < 30 )
@@ -1138,7 +1502,7 @@ Client_RegisterWhowas( CLIENT *Client )
                 sizeof( My_Whowas[slot].id ));
        strlcpy( My_Whowas[slot].user, Client_User( Client ),
                 sizeof( My_Whowas[slot].user ));
-       strlcpy( My_Whowas[slot].host, Client_Hostname( Client ),
+       strlcpy( My_Whowas[slot].host, Client_HostnameDisplayed( Client ),
                 sizeof( My_Whowas[slot].host ));
        strlcpy( My_Whowas[slot].info, Client_Info( Client ),
                 sizeof( My_Whowas[slot].info ));
@@ -1178,7 +1542,7 @@ Destroy_UserOrService(CLIENT *Client, const char *Txt, const char *FwdMsg, bool
        if(Client->conn_id != NONE) {
                /* Local (directly connected) client */
                Log(LOG_NOTICE,
-                   "%s \"%s\" unregistered (connection %d): %s",
+                   "%s \"%s\" unregistered (connection %d): %s.",
                    Client_TypeText(Client), Client_Mask(Client),
                    Client->conn_id, Txt);
                Log_ServerNotice('c', "Client exiting: %s (%s@%s) [%s]",
@@ -1196,7 +1560,7 @@ Destroy_UserOrService(CLIENT *Client, const char *Txt, const char *FwdMsg, bool
                }
        } else {
                /* Remote client */
-               LogDebug("%s \"%s\" unregistered: %s",
+               LogDebug("%s \"%s\" unregistered: %s.",
                         Client_TypeText(Client), Client_Mask(Client), Txt);
 
                if(SendQuit) {
@@ -1219,4 +1583,133 @@ Destroy_UserOrService(CLIENT *Client, const char *Txt, const char *FwdMsg, bool
 } /* Destroy_UserOrService */
 
 
+/**
+ * Introduce a new user or service client to a remote server.
+ *
+ * @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;
+
+       (void)Client_Announce(To, Prefix, c);
+
+} /* cb_introduceClient */
+
+
+/**
+ * Announce an user or service to a server.
+ *
+ * This function differentiates between RFC1459 and RFC2813 server links and
+ * generates the appropriate commands to register the user or service.
+ *
+ * @param Client       Server
+ * @param Prefix       Prefix for the generated commands
+ * @param User         User to announce
+ */
+GLOBAL bool
+Client_Announce(CLIENT * Client, CLIENT * Prefix, CLIENT * User)
+{
+       CONN_ID conn;
+       char *modes, *user, *host;
+
+       modes = Client_Modes(User);
+       user = Client_User(User) ? Client_User(User) : "-";
+       host = Client_Hostname(User) ? Client_Hostname(User) : "-";
+
+       conn = Client_Conn(Client);
+       if (Conn_Options(conn) & CONN_RFC1459) {
+               /* RFC 1459 mode: separate NICK and USER commands */
+               if (! Conn_WriteStr(conn, "NICK %s :%d",
+                                   Client_ID(User), Client_Hops(User) + 1))
+                       return DISCONNECTED;
+               if (! Conn_WriteStr(conn, ":%s USER %s %s %s :%s",
+                                    Client_ID(User), user, host,
+                                    Client_ID(Client_Introducer(User)),
+                                    Client_Info(User)))
+                       return DISCONNECTED;
+               if (modes[0]) {
+                       if (! Conn_WriteStr(conn, ":%s MODE %s +%s",
+                                    Client_ID(User), Client_ID(User),
+                                    modes))
+                               return DISCONNECTED;
+               }
+       } else {
+               /* RFC 2813 mode: one combined NICK or SERVICE command */
+               if (Client_Type(User) == CLIENT_SERVICE
+                   && Client_HasFlag(Client, 'S')) {
+                       if (!IRC_WriteStrClientPrefix(Client, Prefix,
+                                       "SERVICE %s %d * +%s %d :%s",
+                                       Client_Mask(User),
+                                       Client_MyToken(Client_Introducer(User)),
+                                       modes, Client_Hops(User) + 1,
+                                       Client_Info(User)))
+                               return DISCONNECTED;
+               } else {
+                       if (!IRC_WriteStrClientPrefix(Client, Prefix,
+                                       "NICK %s %d %s %s %d +%s :%s",
+                                       Client_ID(User), Client_Hops(User) + 1,
+                                       user, host,
+                                       Client_MyToken(Client_Introducer(User)),
+                                       modes, Client_Info(User)))
+                               return DISCONNECTED;
+               }
+       }
+
+       if (Client_HasFlag(Client, 'M')) {
+               /* Synchronize metadata */
+               if (Client_HostnameCloaked(User)) {
+                       if (!IRC_WriteStrClientPrefix(Client, Prefix,
+                                       "METADATA %s cloakhost :%s",
+                                       Client_ID(User),
+                                       Client_HostnameCloaked(User)))
+                               return DISCONNECTED;
+               }
+
+               if (Client_AccountName(User)) {
+                       if (!IRC_WriteStrClientPrefix(Client, Prefix,
+                                       "METADATA %s accountname :%s",
+                                       Client_ID(User),
+                                       Client_AccountName(User)))
+                               return DISCONNECTED;
+               }
+
+               if (Conn_GetCertFp(Client_Conn(User))) {
+                       if (!IRC_WriteStrClientPrefix(Client, Prefix,
+                                       "METADATA %s certfp :%s",
+                                       Client_ID(User),
+                                       Conn_GetCertFp(Client_Conn(User))))
+                               return DISCONNECTED;
+               }
+       }
+
+       return CONNECTED;
+} /* Client_Announce */
+
+
+#ifdef DEBUG
+
+GLOBAL void
+Client_DebugDump(void)
+{
+       CLIENT *c;
+
+       Log(LOG_DEBUG, "Client status:");
+       c = My_Clients;
+       while (c) {
+               Log(LOG_DEBUG,
+                   " - %s: type=%d, host=%s, user=%s, conn=%d, start=%ld, flags=%s",
+                   Client_ID(c), Client_Type(c), Client_Hostname(c),
+                   Client_User(c), Client_Conn(c), Client_StartTime(c),
+                   Client_Flags(c));
+               c = (CLIENT *)c->next;
+       }
+} /* Client_DumpClients */
+
+#endif
+
+
 /* -eof- */