]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/conn.c
setsockopt(): use IPPROTO_IP instead of SOL_IP to set IPTOS_LOWDELAY
[ngircd-alex.git] / src / ngircd / conn.c
index 33c374c318d64686e545bf00b5d8436da6b405f6..1e4ba0abd7604397e3ad8fa2d2a8ba1b84343be8 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2007 Alexander Barton (alex@barton.de)
+ * Copyright (c)2001-2009 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
 #define CONN_MODULE
 
 #include "portab.h"
+#include "conf-ssl.h"
 #include "io.h"
 
-static char UNUSED id[] = "$Id: conn.c,v 1.221 2008/02/26 22:04:17 fw Exp $";
-
 #include "imp.h"
 #include <assert.h>
 #ifdef PROTOTYPES
@@ -60,6 +59,7 @@ static char UNUSED id[] = "$Id: conn.c,v 1.221 2008/02/26 22:04:17 fw Exp $";
 #include "ngircd.h"
 #include "client.h"
 #include "conf.h"
+#include "conn-ssl.h"
 #include "conn-zip.h"
 #include "conn-func.h"
 #include "log.h"
@@ -75,13 +75,16 @@ static char UNUSED id[] = "$Id: conn.c,v 1.221 2008/02/26 22:04:17 fw Exp $";
 
 #define SERVER_WAIT (NONE - 1)
 
+#define MAX_COMMANDS 3
+#define MAX_COMMANDS_SERVER 10
+
 
 static bool Handle_Write PARAMS(( CONN_ID Idx ));
 static bool Conn_Write PARAMS(( CONN_ID Idx, char *Data, size_t Len ));
 static int New_Connection PARAMS(( int Sock ));
 static CONN_ID Socket2Index PARAMS(( int Sock ));
 static void Read_Request PARAMS(( CONN_ID Idx ));
-static bool Handle_Buffer PARAMS(( CONN_ID Idx ));
+static unsigned int Handle_Buffer PARAMS(( CONN_ID Idx ));
 static void Check_Connections PARAMS(( void ));
 static void Check_Servers PARAMS(( void ));
 static void Init_Conn_Struct PARAMS(( CONN_ID Idx ));
@@ -92,6 +95,7 @@ static int NewListener PARAMS(( const char *listen_addr, UINT16 Port ));
 
 static array My_Listeners;
 static array My_ConnArray;
+static size_t NumConnections;
 
 #ifdef TCPWRAP
 int allow_severity = LOG_INFO;
@@ -100,36 +104,86 @@ int deny_severity = LOG_ERR;
 
 static void server_login PARAMS((CONN_ID idx));
 
-static void cb_Read_Resolver_Result PARAMS(( int sock, UNUSED short what));
-static void cb_Connect_to_Server PARAMS(( int sock, UNUSED short what));
+#ifdef SSL_SUPPORT
+extern struct SSLOptions Conf_SSLOptions;
+static void cb_connserver_login_ssl PARAMS((int sock, short what));
+static void cb_clientserver_ssl PARAMS((int sock, short what));
+#endif
+static void cb_Read_Resolver_Result PARAMS((int sock, UNUSED short what));
+static void cb_Connect_to_Server PARAMS((int sock, UNUSED short what));
 static void cb_clientserver PARAMS((int sock, short what));
 
+
+/**
+ * IO callback for listening sockets: handle new connections. This callback
+ * gets called when a new non-SSL connection should be accepted.
+ * @param sock Socket descriptor
+ * @param irrelevant (ignored IO specification)
+ */
 static void
 cb_listen(int sock, short irrelevant)
 {
        (void) irrelevant;
-       New_Connection( sock );
+       (void) New_Connection(sock);
 }
 
 
+#ifdef SSL_SUPPORT
+/**
+ * IO callback for listening SSL sockets: handle new connections. This callback
+ * gets called when a new SSL-enabled connection should be accepted.
+ * @param sock Socket descriptor
+ * @param irrelevant (ignored IO specification)
+ */
+static void
+cb_listen_ssl(int sock, short irrelevant)
+{
+       int fd;
+
+       (void) irrelevant;
+       fd = New_Connection(sock);
+       if (fd < 0)
+               return;
+       io_event_setcb(My_Connections[fd].sock, cb_clientserver_ssl);
+}
+#endif
+
+
+/**
+ * IO callback for new outgoing non-SSL server connections.
+ * @param sock Socket descriptor
+ * @param what IO specification (IO_WANTREAD/IO_WANTWRITE/...)
+ */
 static void
 cb_connserver(int sock, UNUSED short what)
 {
-       int res, err;
+       int res, err, server;
        socklen_t sock_len;
        CONN_ID idx = Socket2Index( sock );
+
        if (idx <= NONE) {
                LogDebug("cb_connserver wants to write on unknown socket?!");
                io_close(sock);
                return;
        }
 
-       assert( what & IO_WANTWRITE);
+       assert(what & IO_WANTWRITE);
+
+       /* Make sure that the server is still configured; it could have been
+        * removed in the meantime! */
+       server = Conf_GetServer(idx);
+       if (server < 0) {
+               Log(LOG_ERR, "Connection on socket %d to \"%s\" aborted!",
+                   sock, My_Connections[idx].host);
+               Conn_Close(idx, "Connection aborted!", NULL, false);
+               return;
+       }
 
        /* connect() finished, get result. */
-       sock_len = sizeof( err );
-       res = getsockopt( My_Connections[idx].sock, SOL_SOCKET, SO_ERROR, &err, &sock_len );
-       assert( sock_len == sizeof( err ));
+       sock_len = (socklen_t)sizeof(err);
+       res = getsockopt(My_Connections[idx].sock, SOL_SOCKET, SO_ERROR,
+                        &err, &sock_len );
+       assert(sock_len == sizeof(err));
 
        /* Error while connecting? */
        if ((res != 0) || (err != 0)) {
@@ -139,37 +193,44 @@ cb_connserver(int sock, UNUSED short what)
                else
                        Log(LOG_CRIT,
                            "Can't connect socket to \"%s:%d\" (connection %d): %s!",
-                           My_Connections[idx].host,
-                           Conf_Server[Conf_GetServer(idx)].port,
+                           My_Connections[idx].host, Conf_Server[server].port,
                            idx, strerror(err));
 
-               res = Conf_GetServer(idx);
-               assert(res >= 0);
-
                Conn_Close(idx, "Can't connect!", NULL, false);
 
-               if (res < 0)
-                       return;
-               if (ng_ipaddr_af(&Conf_Server[res].dst_addr[0])) {
+               if (ng_ipaddr_af(&Conf_Server[server].dst_addr[0])) {
                        /* more addresses to try... */
-                       New_Server(res, &Conf_Server[res].dst_addr[0]);
-                       /* connection to dst_addr[0] in progress, remove this address... */
-                       Conf_Server[res].dst_addr[0] = Conf_Server[res].dst_addr[1];
-
-                       memset(&Conf_Server[res].dst_addr[1], 0, sizeof(&Conf_Server[res].dst_addr[1]));
+                       New_Server(res, &Conf_Server[server].dst_addr[0]);
+                       /* connection to dst_addr[0] is now in progress, so
+                        * remove this address... */
+                       Conf_Server[server].dst_addr[0] =
+                               Conf_Server[server].dst_addr[1];
+                       memset(&Conf_Server[server].dst_addr[1], 0,
+                              sizeof(Conf_Server[server].dst_addr[1]));
                }
                return;
        }
 
-       res = Conf_GetServer(idx);
-       assert(res >= 0);
-       if (res >= 0) /* connect succeeded, remove all additional addresses */
-               memset(&Conf_Server[res].dst_addr, 0, sizeof(&Conf_Server[res].dst_addr));
+       /* connect() succeeded, remove all additional addresses */
+       memset(&Conf_Server[server].dst_addr, 0,
+              sizeof(Conf_Server[server].dst_addr));
+
        Conn_OPTION_DEL( &My_Connections[idx], CONN_ISCONNECTING );
+#ifdef SSL_SUPPORT
+       if ( Conn_OPTION_ISSET( &My_Connections[idx], CONN_SSL_CONNECT )) {
+               io_event_setcb( sock, cb_connserver_login_ssl );
+               io_event_add( sock, IO_WANTWRITE|IO_WANTREAD );
+               return;
+       }
+#endif
        server_login(idx);
 }
 
 
+/**
+ * Login to a remote server.
+ * @param idx Connection index
+ */
 static void
 server_login(CONN_ID idx)
 {
@@ -185,41 +246,125 @@ server_login(CONN_ID idx)
 }
 
 
+#ifdef SSL_SUPPORT
+/**
+ * IO callback for new outgoing SSL-enabled server connections.
+ * @param sock Socket descriptor
+ * @param what IO specification (IO_WANTREAD/IO_WANTWRITE/...)
+ */
+static void
+cb_connserver_login_ssl(int sock, short unused)
+{
+       CONN_ID idx = Socket2Index(sock);
+
+       assert(idx >= 0);
+       if (idx < 0) {
+               io_close(sock);
+               return;
+       }
+       (void) unused;
+       switch (ConnSSL_Connect( &My_Connections[idx])) {
+       case 1: break;
+       case 0: LogDebug("ConnSSL_Connect: not ready");
+               return;
+       case -1:
+               Log(LOG_ERR, "SSL connection on socket %d failed!", sock);
+               Conn_Close(idx, "Can't connect!", NULL, false);
+               return;
+       }
+
+       Log( LOG_INFO, "SSL connection %d with \"%s:%d\" established.", idx,
+                       My_Connections[idx].host, Conf_Server[Conf_GetServer( idx )].port );
+
+       server_login(idx);
+}
+#endif
+
+
+/**
+ * IO callback for established non-SSL client and server connections.
+ * @param sock Socket descriptor
+ * @param what IO specification (IO_WANTREAD/IO_WANTWRITE/...)
+ */
 static void
 cb_clientserver(int sock, short what)
 {
-       CONN_ID idx = Socket2Index( sock );
-       if (idx <= NONE) {
-#ifdef DEBUG
-               Log(LOG_WARNING, "WTF: cb_clientserver wants to write on unknown socket?!");
+       CONN_ID idx = Socket2Index(sock);
+
+       assert(idx >= 0);
+
+       if (idx < 0) {
+               io_close(sock);
+               return;
+       }
+#ifdef SSL_SUPPORT
+       if (what & IO_WANTREAD
+           || (Conn_OPTION_ISSET(&My_Connections[idx], CONN_SSL_WANT_WRITE))) {
+               /* if TLS layer needs to write additional data, call
+                * Read_Request() instead so that SSL/TLS can continue */
+               Read_Request(idx);
+       }
+#else
+       if (what & IO_WANTREAD)
+               Read_Request(idx);
 #endif
+       if (what & IO_WANTWRITE)
+               Handle_Write(idx);
+}
+
+
+#ifdef SSL_SUPPORT
+/**
+ * IO callback for established SSL-enabled client and server connections.
+ * @param sock Socket descriptor
+ * @param what IO specification (IO_WANTREAD/IO_WANTWRITE/...)
+ */
+static void
+cb_clientserver_ssl(int sock, short what)
+{
+       CONN_ID idx = Socket2Index(sock);
+
+       assert(idx >= 0);
+
+       if (idx < 0) {
                io_close(sock);
                return;
        }
 
+       switch (ConnSSL_Accept(&My_Connections[idx])) {
+       case 1:
+               break;  /* OK */
+       case 0:
+               return; /* EAGAIN: callback will be invoked again by IO layer */
+       default:
+               Conn_Close(idx, "Socket closed!", "SSL accept error", false);
+               return;
+       }
        if (what & IO_WANTREAD)
-               Read_Request( idx );
+               Read_Request(idx);
 
        if (what & IO_WANTWRITE)
-               Handle_Write( idx );
+               Handle_Write(idx);
+
+       io_event_setcb(sock, cb_clientserver);  /* SSL handshake completed */
 }
+#endif
 
 
+/**
+ * Initialite connecion module.
+ */
 GLOBAL void
 Conn_Init( void )
 {
-       /* Modul initialisieren: statische Strukturen "ausnullen". */
-
        CONN_ID i;
 
        /* Speicher fuer Verbindungs-Pool anfordern */
        Pool_Size = CONNECTION_POOL;
-       if( Conf_MaxConnections > 0 )
-       {
-               /* konfiguriertes Limit beachten */
-               if( Pool_Size > Conf_MaxConnections ) Pool_Size = Conf_MaxConnections;
-       }
-       
+       if ((Conf_MaxConnections > 0) &&
+               (Pool_Size > Conf_MaxConnections))
+                       Pool_Size = Conf_MaxConnections;
+
        if (!array_alloc(&My_ConnArray, sizeof(CONNECTION), (size_t)Pool_Size)) {
                Log( LOG_EMERG, "Can't allocate memory! [Conn_Init]" );
                exit( 1 );
@@ -244,19 +389,17 @@ Conn_Init( void )
 } /* Conn_Init */
 
 
+/**
+ * Clean up connection module.
+ */
 GLOBAL void
 Conn_Exit( void )
 {
-       /* Modul abmelden: alle noch offenen Connections
-        * schliessen und freigeben. */
-
        CONN_ID idx;
 
-       LogDebug("Shutting down all connections ..." );
-
        Conn_ExitListeners();
 
-       /* Sockets schliessen */
+       LogDebug("Shutting down all connections ..." );
        for( idx = 0; idx < Pool_Size; idx++ ) {
                if( My_Connections[idx].sock > NONE ) {
                        Conn_Close( idx, NULL, NGIRCd_SignalRestart ?
@@ -301,6 +444,10 @@ ports_initlisteners(array *a, const char *listen_addr, void (*func)(int,short))
 }
 
 
+/**
+ * Initialize all listening sockets.
+ * @return Number of created listening sockets
+ */
 GLOBAL unsigned int
 Conn_InitListeners( void )
 {
@@ -325,19 +472,22 @@ Conn_InitListeners( void )
 
        while (listen_addr) {
                ngt_TrimStr(listen_addr);
-               if (*listen_addr)
+               if (*listen_addr) {
                        created += ports_initlisteners(&Conf_ListenPorts, listen_addr, cb_listen);
+#ifdef SSL_SUPPORT
+                       created += ports_initlisteners(&Conf_SSLOptions.ListenPorts, listen_addr, cb_listen_ssl);
+#endif
+               }
 
                listen_addr = strtok(NULL, ",");
        }
 
-       /*
-        * can't free() Conf_ListenAddress here. On /REHASH, if the config file
+       /* Can't free() Conf_ListenAddress here: on REHASH, if the config file
         * cannot be re-loaded, we'd end up with a NULL Conf_ListenAddress.
         * Instead, free() takes place in conf.c, before the config file
-        * is being parsed.
-        */
+        * is being parsed. */
        free(copy);
+
        return created;
 } /* Conn_InitListeners */
 
@@ -353,7 +503,8 @@ Conn_ExitListeners( void )
 #endif
 
        arraylen = array_length(&My_Listeners, sizeof (int));
-       Log( LOG_INFO, "Shutting down all listening sockets (%d total)...", arraylen );
+       Log(LOG_INFO,
+           "Shutting down all listening sockets (%d total) ...", arraylen);
        fd = array_start(&My_Listeners);
        while(arraylen--) {
                assert(fd != NULL);
@@ -390,7 +541,7 @@ set_v6_only(int af, int sock)
        if (af != AF_INET6)
                return;
 
-       if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)))
+       if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, (socklen_t)sizeof(on)))
                Log(LOG_ERR, "Could not set IPV6_V6ONLY: %s", strerror(errno));
 #else
        (void)af;
@@ -480,6 +631,45 @@ NewListener(const char *listen_addr, UINT16 Port)
 } /* NewListener */
 
 
+#ifdef SSL_SUPPORT
+/*
+ * SSL/TLS connections require extra treatment:
+ * When either CONN_SSL_WANT_WRITE or CONN_SSL_WANT_READ is set, we
+ * need to take care of that first, before checking read/write buffers.
+ * For instance, while we might have data in our write buffer, the
+ * TLS/SSL protocol might need to read internal data first for TLS/SSL
+ * writes to succeed.
+ *
+ * If this function returns true, such a condition is met and we have
+ * to reverse the condition (check for read even if we've data to write,
+ * do not check for read but writeability even if write-buffer is empty).
+ */
+static bool
+SSL_WantRead(const CONNECTION *c)
+{
+       if (Conn_OPTION_ISSET(c, CONN_SSL_WANT_READ)) {
+               io_event_add(c->sock, IO_WANTREAD);
+               return true;
+       }
+       return false;
+}
+static bool
+SSL_WantWrite(const CONNECTION *c)
+{
+       if (Conn_OPTION_ISSET(c, CONN_SSL_WANT_WRITE)) {
+               io_event_add(c->sock, IO_WANTWRITE);
+               return true;
+       }
+       return false;
+}
+#else
+static inline bool
+SSL_WantRead(UNUSED const CONNECTION *c) { return false; }
+static inline bool
+SSL_WantWrite(UNUSED const CONNECTION *c) { return false; }
+#endif
+
+
 /**
  * "Main Loop": Loop until shutdown or restart is signalled.
  * This function loops until a shutdown or restart of ngIRCd is signalled and
@@ -492,7 +682,7 @@ GLOBAL void
 Conn_Handler(void)
 {
        int i;
-       unsigned int wdatalen;
+       unsigned int wdatalen, bytes_processed;
        struct timeval tv;
        time_t t;
 
@@ -515,9 +705,19 @@ Conn_Handler(void)
                for (i = 0; i < Pool_Size; i++) {
                        if ((My_Connections[i].sock > NONE)
                            && (array_bytes(&My_Connections[i].rbuf) > 0)
-                           && (My_Connections[i].delaytime < t)) {
+                           && (My_Connections[i].delaytime <= t)) {
                                /* ... and try to handle the received data */
-                               Handle_Buffer(i);
+                               bytes_processed = Handle_Buffer(i);
+                               /* if we processed data, and there might be
+                                * more commands in the input buffer, do not
+                                * try to read any more data now */
+                               if (bytes_processed &&
+                                   array_bytes(&My_Connections[i].rbuf) > 2) {
+                                       LogDebug
+                                           ("Throttling connection %d: command limit reached!",
+                                            i);
+                                       Conn_SetPenalty(i, 1);
+                               }
                        }
                }
 
@@ -534,7 +734,8 @@ Conn_Handler(void)
                        if (wdatalen > 0)
 #endif
                        {
-                               /* Set the "WANTWRITE" flag on this socket */
+                               if (SSL_WantRead(&My_Connections[i]))
+                                       continue;
                                io_event_add(My_Connections[i].sock,
                                             IO_WANTWRITE);
                        }
@@ -544,7 +745,10 @@ Conn_Handler(void)
                for (i = 0; i < Pool_Size; i++) {
                        if (My_Connections[i].sock <= NONE)
                                continue;
-
+#ifdef SSL_SUPPORT
+                       if (SSL_WantWrite(&My_Connections[i]))
+                               continue; /* TLS/SSL layer needs to write data; deal with this first */
+#endif
                        if (Resolve_INPROGRESS(&My_Connections[i].res_stat)) {
                                /* Wait for completion of resolver sub-process ... */
                                io_event_del(My_Connections[i].sock,
@@ -747,13 +951,12 @@ Conn_Write( CONN_ID Idx, char *Data, size_t Len )
 
 
 GLOBAL void
-Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient )
+Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClient )
 {
        /* Close connection. Open pipes of asyncronous resolver
         * sub-processes are closed down. */
 
        CLIENT *c;
-       char *txt;
        double in_k, out_k;
        UINT16 port;
 #ifdef ZLIB
@@ -776,13 +979,6 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient )
        /* Mark link as "closing" */
        Conn_OPTION_ADD( &My_Connections[Idx], CONN_ISCLOSING );
 
-       if (LogMsg)
-               txt = LogMsg;
-       else
-               txt = FwdMsg;
-       if (! txt)
-               txt = "Reason unknown";
-
        port = ng_ipaddr_getport(&My_Connections[Idx].addr);
        Log(LOG_INFO, "Shutting down connection %d (%s) with %s:%d ...", Idx,
            LogMsg ? LogMsg : FwdMsg, My_Connections[Idx].host, port);
@@ -803,7 +999,7 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient )
                         (double)My_Connections[Idx].bytes_out / 1024);
                }
 #endif
-               /* Send ERROR to client (see RFC!) */
+               /* Send ERROR to client (see RFC 2812, section 3.1.7) */
                if (FwdMsg)
                        Conn_WriteStr(Idx, "ERROR :%s", FwdMsg);
                else
@@ -818,7 +1014,12 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient )
 
        /* Search client, if any (re-check!) */
        c = Conn_GetClient( Idx );
-
+#ifdef SSL_SUPPORT
+       if ( Conn_OPTION_ISSET( &My_Connections[Idx], CONN_SSL )) {
+               Log(LOG_INFO, "SSL connection %d shutting down ...", Idx);
+               ConnSSL_Free(&My_Connections[Idx]);
+       }
+#endif
        /* Shut down socket */
        if (! io_close(My_Connections[Idx].sock)) {
                /* Oops, we can't close the socket!? This is ... ugly! */
@@ -889,7 +1090,11 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient )
        /* Clean up connection structure (=free it) */
        Init_Conn_Struct( Idx );
 
-       LogDebug("Shutdown of connection %d completed.", Idx );
+       assert(NumConnections > 0);
+       if (NumConnections)
+               NumConnections--;
+       LogDebug("Shutdown of connection %d completed, %ld connection%s left.",
+                Idx, NumConnections, NumConnections != 1 ? "s" : "");
 } /* Conn_Close */
 
 
@@ -965,9 +1170,15 @@ Handle_Write( CONN_ID Idx )
            ("Handle_Write() called for connection %d, %ld bytes pending ...",
             Idx, wdatalen);
 
-       len = write(My_Connections[Idx].sock,
-                   array_start(&My_Connections[Idx].wbuf), wdatalen );
-
+#ifdef SSL_SUPPORT
+       if ( Conn_OPTION_ISSET( &My_Connections[Idx], CONN_SSL )) {
+               len = ConnSSL_Write(&My_Connections[Idx], array_start(&My_Connections[Idx].wbuf), wdatalen);
+       } else
+#endif
+       {
+               len = write(My_Connections[Idx].sock,
+                           array_start(&My_Connections[Idx].wbuf), wdatalen );
+       }
        if( len < 0 ) {
                if (errno == EAGAIN || errno == EINTR)
                        return true;
@@ -1001,25 +1212,26 @@ Count_Connections(ng_ipaddr_t *a)
 } /* Count_Connections */
 
 
+/**
+ * Initialize new client connection on a listening socket.
+ * @param Sock Listening socket descriptor
+ * @return Accepted socket descriptor or -1 on error
+ */
 static int
-New_Connection( int Sock )
+New_Connection(int Sock)
 {
-       /* Neue Client-Verbindung von Listen-Socket annehmen und
-        * CLIENT-Struktur anlegen. */
-
 #ifdef TCPWRAP
        struct request_info req;
 #endif
        ng_ipaddr_t new_addr;
        char ip_str[NG_INET_ADDRSTRLEN];
-       int new_sock, new_sock_len, new_Pool_Size;
+       int new_sock, new_sock_len, identsock;
        CLIENT *c;
        long cnt;
 
-       assert( Sock > NONE );
-       /* Connection auf Listen-Socket annehmen */
-       new_sock_len = (int)sizeof(new_addr);
+       assert(Sock > NONE);
 
+       new_sock_len = (int)sizeof(new_addr);
        new_sock = accept(Sock, (struct sockaddr *)&new_addr,
                          (socklen_t *)&new_sock_len);
        if (new_sock < 0) {
@@ -1031,96 +1243,122 @@ New_Connection( int Sock )
                Log(LOG_CRIT, "fd %d: Can't convert IP address!", new_sock);
                Simple_Message(new_sock, "ERROR :Internal Server Error");
                close(new_sock);
+               return -1;
        }
 
 #ifdef TCPWRAP
        /* Validate socket using TCP Wrappers */
-       request_init( &req, RQ_DAEMON, PACKAGE_NAME, RQ_FILE, new_sock, RQ_CLIENT_SIN, &new_addr, NULL );
+       request_init(&req, RQ_DAEMON, PACKAGE_NAME, RQ_FILE, new_sock,
+                    RQ_CLIENT_SIN, &new_addr, NULL);
        fromhost(&req);
        if (!hosts_access(&req)) {
-               Log (deny_severity, "Refused connection from %s (by TCP Wrappers)!", ip_str);
-               Simple_Message( new_sock, "ERROR :Connection refused" );
-               close( new_sock );
+               Log(deny_severity,
+                   "Refused connection from %s (by TCP Wrappers)!", ip_str);
+               Simple_Message(new_sock, "ERROR :Connection refused");
+               close(new_sock);
                return -1;
        }
 #endif
 
-       /* Socket initialisieren */
-       if (!Init_Socket( new_sock ))
+       if (!Init_Socket(new_sock))
+               return -1;
+
+       /* Check global connection limit */
+       if ((Conf_MaxConnections > 0) &&
+           (NumConnections >= (size_t) Conf_MaxConnections)) {
+               Log(LOG_ALERT, "Can't accept connection: limit (%d) reached!",
+                   Conf_MaxConnections);
+               Simple_Message(new_sock, "ERROR :Connection limit reached");
+               close(new_sock);
                return -1;
+       }
 
        /* Check IP-based connection limit */
        cnt = Count_Connections(&new_addr);
        if ((Conf_MaxConnectionsIP > 0) && (cnt >= Conf_MaxConnectionsIP)) {
                /* Access denied, too many connections from this IP address! */
-               Log( LOG_ERR, "Refused connection from %s: too may connections (%ld) from this IP address!", ip_str, cnt);
-               Simple_Message( new_sock, "ERROR :Connection refused, too many connections from your IP address!" );
-               close( new_sock );
+               Log(LOG_ERR,
+                   "Refused connection from %s: too may connections (%ld) from this IP address!",
+                   ip_str, cnt);
+               Simple_Message(new_sock,
+                              "ERROR :Connection refused, too many connections from your IP address!");
+               close(new_sock);
                return -1;
        }
 
-       if( new_sock >= Pool_Size ) {
-               new_Pool_Size = new_sock + 1;
-               /* No free Connection Structures, check if we may accept further connections */
-               if ((( Conf_MaxConnections > 0) && Pool_Size >= Conf_MaxConnections) ||
-                       (new_Pool_Size < Pool_Size))
-               {
-                       Log( LOG_ALERT, "Can't accept connection: limit (%d) reached!", Pool_Size );
-                       Simple_Message( new_sock, "ERROR :Connection limit reached" );
-                       close( new_sock );
-                       return -1;
-               }
-
+       if (new_sock >= Pool_Size) {
                if (!array_alloc(&My_ConnArray, sizeof(CONNECTION),
-                                (size_t)new_sock)) {
-                       Log( LOG_EMERG, "Can't allocate memory! [New_Connection]" );
-                       Simple_Message( new_sock, "ERROR: Internal error" );
-                       close( new_sock );
+                                (size_t) new_sock)) {
+                       Log(LOG_EMERG,
+                           "Can't allocate memory! [New_Connection]");
+                       Simple_Message(new_sock, "ERROR: Internal error");
+                       close(new_sock);
                        return -1;
                }
                LogDebug("Bumped connection pool to %ld items (internal: %ld items, %ld bytes)",
-                       new_sock, array_length(&My_ConnArray, sizeof(CONNECTION)), array_bytes(&My_ConnArray));
+                        new_sock, array_length(&My_ConnArray,
+                        sizeof(CONNECTION)), array_bytes(&My_ConnArray));
 
                /* Adjust pointer to new block */
                My_Connections = array_start(&My_ConnArray);
-               while (Pool_Size < new_Pool_Size)
+               while (Pool_Size <= new_sock)
                        Init_Conn_Struct(Pool_Size++);
        }
 
        /* register callback */
-       if (!io_event_create( new_sock, IO_WANTREAD, cb_clientserver)) {
-               Log(LOG_ALERT, "Can't accept connection: io_event_create failed!");
+       if (!io_event_create(new_sock, IO_WANTREAD, cb_clientserver)) {
+               Log(LOG_ALERT,
+                   "Can't accept connection: io_event_create failed!");
                Simple_Message(new_sock, "ERROR :Internal error");
                close(new_sock);
                return -1;
        }
 
-       c = Client_NewLocal(new_sock, ip_str, CLIENT_UNKNOWN, false );
-       if( ! c ) {
-               Log(LOG_ALERT, "Can't accept connection: can't create client structure!");
+       c = Client_NewLocal(new_sock, ip_str, CLIENT_UNKNOWN, false);
+       if (!c) {
+               Log(LOG_ALERT,
+                   "Can't accept connection: can't create client structure!");
                Simple_Message(new_sock, "ERROR :Internal error");
                io_close(new_sock);
                return -1;
        }
 
-       Init_Conn_Struct( new_sock );
+       Init_Conn_Struct(new_sock);
        My_Connections[new_sock].sock = new_sock;
        My_Connections[new_sock].addr = new_addr;
        My_Connections[new_sock].client = c;
 
-       Log( LOG_INFO, "Accepted connection %d from %s:%d on socket %d.", new_sock,
-                       ip_str, ng_ipaddr_getport(&new_addr), Sock);
-
-       /* Hostnamen ermitteln */
-       strlcpy(My_Connections[new_sock].host, ip_str, sizeof(My_Connections[new_sock].host));
+       /* Set initial hostname to IP address. This becomes overwritten when
+        * the DNS lookup is enabled and succeeds, but is used otherwise. */
+       if (ng_ipaddr_af(&new_addr) != AF_INET)
+               snprintf(My_Connections[new_sock].host,
+                        sizeof(My_Connections[new_sock].host), "[%s]", ip_str);
+       else
+               strlcpy(My_Connections[new_sock].host, ip_str,
+                       sizeof(My_Connections[new_sock].host));
 
        Client_SetHostname(c, My_Connections[new_sock].host);
 
+       Log(LOG_INFO, "Accepted connection %d from %s:%d on socket %d.",
+           new_sock, My_Connections[new_sock].host,
+           ng_ipaddr_getport(&new_addr), Sock);
+
+       identsock = new_sock;
+#ifdef IDENTAUTH
+       if (Conf_NoIdent)
+               identsock = -1;
+#endif
        if (!Conf_NoDNS)
                Resolve_Addr(&My_Connections[new_sock].res_stat, &new_addr,
-                       My_Connections[new_sock].sock, cb_Read_Resolver_Result);
+                            identsock, cb_Read_Resolver_Result);
 
+       /* ngIRCd waits up to 4 seconds for the result of the asynchronous
+        * DNS and IDENT resolver subprocess using the "penalty" mechanism.
+        * If there are results earlier, the delay is aborted. */
        Conn_SetPenalty(new_sock, 4);
+
+       NumConnections++;
+       LogDebug("Total number of connections now %ld.", NumConnections);
        return new_sock;
 } /* New_Connection */
 
@@ -1128,13 +1366,11 @@ New_Connection( int Sock )
 static CONN_ID
 Socket2Index( int Sock )
 {
-       /* zum Socket passende Connection suchen */
-
        assert( Sock >= 0 );
 
        if( Sock >= Pool_Size || My_Connections[Sock].sock != Sock ) {
-               /* die Connection wurde vermutlich (wegen eines
-                * Fehlers) bereits wieder abgebaut ... */
+               /* the Connection was already closed again, likely due to
+                * an error. */
                LogDebug("Socket2Index: can't get connection for socket %d!", Sock);
                return NONE;
        }
@@ -1150,7 +1386,9 @@ static void
 Read_Request( CONN_ID Idx )
 {
        ssize_t len;
+       static const unsigned int maxbps = COMMAND_LEN / 2;
        char readbuf[READBUFFER_LEN];
+       time_t t;
        CLIENT *c;
        assert( Idx > NONE );
        assert( My_Connections[Idx].sock > NONE );
@@ -1170,6 +1408,11 @@ Read_Request( CONN_ID Idx )
                return;
        }
 
+#ifdef SSL_SUPPORT
+       if (Conn_OPTION_ISSET(&My_Connections[Idx], CONN_SSL))
+               len = ConnSSL_Read( &My_Connections[Idx], readbuf, sizeof(readbuf));
+       else
+#endif
        len = read(My_Connections[Idx].sock, readbuf, sizeof(readbuf));
        if (len == 0) {
                Log(LOG_INFO, "%s:%u (%s) is closing the connection ...",
@@ -1222,44 +1465,68 @@ Read_Request( CONN_ID Idx )
        if (c && (Client_Type(c) == CLIENT_USER
                  || Client_Type(c) == CLIENT_SERVER
                  || Client_Type(c) == CLIENT_SERVICE)) {
-               My_Connections[Idx].lastdata = time(NULL);
+               t = time(NULL);
+               if (My_Connections[Idx].lastdata != t)
+                       My_Connections[Idx].bps = 0;
+
+               My_Connections[Idx].lastdata = t;
                My_Connections[Idx].lastping = My_Connections[Idx].lastdata;
        }
 
        /* Look at the data in the (read-) buffer of this connection */
-       Handle_Buffer(Idx);
+       My_Connections[Idx].bps += Handle_Buffer(Idx);
+       if (Client_Type(c) != CLIENT_SERVER
+           && My_Connections[Idx].bps >= maxbps) {
+               LogDebug("Throttling connection %d: BPS exceeded! (%u >= %u)",
+                        Idx, My_Connections[Idx].bps, maxbps);
+               Conn_SetPenalty(Idx, 1);
+       }
 } /* Read_Request */
 
 
 /**
- * Handle data in connection read-buffer.
- * @return true if a reuqest was handled, false otherwise (and on errors).
+ * Handle all data in the connection read-buffer.
+ * Data is processed until no complete command is left in the read buffer,
+ * or MAX_COMMANDS[_SERVER] commands were processed.
+ * When a fatal error occurs, the connection is shut down.
+ * @param Idx Index of the connection.
+ * @return number of bytes processed.
  */
-static bool
+static unsigned int
 Handle_Buffer(CONN_ID Idx)
 {
 #ifndef STRICT_RFC
-       char *ptr1, *ptr2;
+       char *ptr1, *ptr2, *first_eol;
 #endif
        char *ptr;
        size_t len, delta;
-       bool result;
        time_t starttime;
 #ifdef ZLIB
        bool old_z;
 #endif
+       unsigned int i, maxcmd = MAX_COMMANDS, len_processed = 0;
+       CLIENT *c;
+
+       c = Conn_GetClient(Idx);
+       assert( c != NULL);
+
+       /* Servers do get special command limits, so they can process
+        * all the messages that are required while peering. */
+       if (Client_Type(c) == CLIENT_SERVER)
+               maxcmd = MAX_COMMANDS_SERVER;
 
        starttime = time(NULL);
-       result = false;
-       for (;;) {
+       for (i=0; i < maxcmd; i++) {
                /* Check penalty */
                if (My_Connections[Idx].delaytime > starttime)
-                       return result;
+                       return 0;
 #ifdef ZLIB
                /* Unpack compressed data, if compression is in use */
                if (Conn_OPTION_ISSET(&My_Connections[Idx], CONN_ZIP)) {
+                       /* When unzipping fails, Unzip_Buffer() shuts
+                        * down the connection itself */
                        if (!Unzip_Buffer(Idx))
-                               return false;
+                               return 0;
                }
 #endif
 
@@ -1267,8 +1534,12 @@ Handle_Buffer(CONN_ID Idx)
                        break;
 
                /* Make sure that the buffer is NULL terminated */
-               if (!array_cat0_temporary(&My_Connections[Idx].rbuf))
-                       return false;
+               if (!array_cat0_temporary(&My_Connections[Idx].rbuf)) {
+                       Conn_Close(Idx, NULL,
+                                  "Can't allocate memory [Handle_Buffer]",
+                                  true);
+                       return 0;
+               }
 
                /* RFC 2812, section "2.3 Messages", 5th paragraph:
                 * "IRC messages are always lines of characters terminated
@@ -1277,19 +1548,28 @@ Handle_Buffer(CONN_ID Idx)
                ptr = strstr(array_start(&My_Connections[Idx].rbuf), "\r\n");
 
 #ifndef STRICT_RFC
-               if (!ptr) {
-                       /* Check for non-RFC-compliant request (only CR or
-                        * LF)? Unfortunately, there are quite a few clients
-                        * out there that do this -- incl. "mIRC" :-( */
-                       delta = 1;
-                       ptr1 = strchr(array_start(&My_Connections[Idx].rbuf), '\r');
-                       ptr2 = strchr(array_start(&My_Connections[Idx].rbuf), '\n');
+               /* Check for non-RFC-compliant request (only CR or LF)?
+                * Unfortunately, there are quite a few clients out there
+                * that do this -- e. g. mIRC, BitchX, and Trillian :-( */
+               ptr1 = strchr(array_start(&My_Connections[Idx].rbuf), '\r');
+               ptr2 = strchr(array_start(&My_Connections[Idx].rbuf), '\n');
+               if (ptr) {
+                       /* Check if there is a single CR or LF _before_ the
+                        * corerct CR+LF line terminator:  */
+                       first_eol = ptr1 < ptr2 ? ptr1 : ptr2;
+                       if (first_eol < ptr) {
+                               /* Single CR or LF before CR+LF found */
+                               ptr = first_eol;
+                               delta = 1;
+                       }
+               } else if (ptr1 || ptr2) {
+                       /* No CR+LF terminated command found, but single
+                        * CR or LF found ... */
                        if (ptr1 && ptr2)
-                               ptr = ptr1 > ptr2 ? ptr2 : ptr1;
-                       else if (ptr1)
-                               ptr = ptr1;
-                       else if (ptr2)
-                               ptr = ptr2;
+                               ptr = ptr1 < ptr2 ? ptr1 : ptr2;
+                       else
+                               ptr = ptr1 ? ptr1 : ptr2;
+                       delta = 1;
                }
 #endif
 
@@ -1309,16 +1589,16 @@ Handle_Buffer(CONN_ID Idx)
                            Idx, array_bytes(&My_Connections[Idx].rbuf),
                            COMMAND_LEN - 1);
                        Conn_Close(Idx, NULL, "Request too long", true);
-                       return false;
+                       return 0;
                }
 
+               len_processed += (unsigned int)len;
                if (len <= delta) {
                        /* Request is empty (only '\r\n', '\r' or '\n');
                         * delta is 2 ('\r\n') or 1 ('\r' or '\n'), see above */
                        array_moveleft(&My_Connections[Idx].rbuf, 1, len);
-                       break;
+                       continue;
                }
-
 #ifdef ZLIB
                /* remember if stream is already compressed */
                old_z = My_Connections[Idx].options & CONN_ZIP;
@@ -1327,9 +1607,7 @@ Handle_Buffer(CONN_ID Idx)
                My_Connections[Idx].msg_in++;
                if (!Parse_Request
                    (Idx, (char *)array_start(&My_Connections[Idx].rbuf)))
-                       return false;
-
-               result = true;
+                       return 0; /* error -> connection has been closed */
 
                array_moveleft(&My_Connections[Idx].rbuf, 1, len);
                LogDebug("Connection %d: %d bytes left in read buffer.",
@@ -1342,8 +1620,12 @@ Handle_Buffer(CONN_ID Idx)
                         * to the unzip buffer for decompression: */
                        if (!array_copy
                            (&My_Connections[Idx].zip.rbuf,
-                            &My_Connections[Idx].rbuf))
-                               return false;
+                            &My_Connections[Idx].rbuf)) {
+                               Conn_Close(Idx, NULL,
+                                          "Can't allocate memory [Handle_Buffer]",
+                                          true);
+                               return 0;
+                       }
 
                        array_trunc(&My_Connections[Idx].rbuf);
                        LogDebug
@@ -1352,7 +1634,7 @@ Handle_Buffer(CONN_ID Idx)
                }
 #endif
        }
-       return result;
+       return len_processed;
 } /* Handle_Buffer */
 
 
@@ -1363,6 +1645,7 @@ Check_Connections(void)
         * if this doesn't help either, disconnect client. */
        CLIENT *c;
        CONN_ID i;
+       char msg[64];
 
        for (i = 0; i < Pool_Size; i++) {
                if (My_Connections[i].sock < 0)
@@ -1382,8 +1665,8 @@ Check_Connections(void)
                                        LogDebug
                                            ("Connection %d: Ping timeout: %d seconds.",
                                             i, Conf_PongTimeout);
-                                       Conn_Close(i, NULL, "Ping timeout",
-                                                  true);
+                                       snprintf(msg, sizeof(msg), "Ping timeout: %d seconds", Conf_PongTimeout);
+                                       Conn_Close(i, NULL, msg, true);
                                }
                        } else if (My_Connections[i].lastdata <
                                   time(NULL) - Conf_PingTimeout) {
@@ -1518,6 +1801,8 @@ New_Server( int Server , ng_ipaddr_t *dest)
                return;
        }
 
+       /* Conn_Close() decrements this counter again */
+       NumConnections++;
        Client_SetIntroducer( c, c );
        Client_SetToken( c, TOKEN_OUTBOUND );
 
@@ -1536,9 +1821,19 @@ New_Server( int Server , ng_ipaddr_t *dest)
                Init_Conn_Struct( new_sock );
                Conf_Server[Server].conn_id = NONE;
        }
-
-       LogDebug("Registered new connection %d on socket %d.",
-                               new_sock, My_Connections[new_sock].sock );
+#ifdef SSL_SUPPORT
+       if (Conf_Server[Server].SSLConnect && !ConnSSL_PrepareConnect( &My_Connections[new_sock],
+                                                               &Conf_Server[Server] ))
+       {
+               Log(LOG_ALERT, "Could not initialize SSL for outgoing connection");
+               Conn_Close( new_sock, "Could not initialize SSL for outgoing connection", NULL, false );
+               Init_Conn_Struct( new_sock );
+               Conf_Server[Server].conn_id = NONE;
+               return;
+       }
+#endif
+       LogDebug("Registered new connection %d on socket %d (%ld in total).",
+                new_sock, My_Connections[new_sock].sock, NumConnections);
        Conn_OPTION_ADD( &My_Connections[new_sock], CONN_ISCONNECTING );
 } /* New_Server */
 
@@ -1582,12 +1877,13 @@ Init_Socket( int Sock )
        }
 
        /* Set type of service (TOS) */
-#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
+#if defined(IPPROTO_IP) && defined(IPTOS_LOWDELAY)
        value = IPTOS_LOWDELAY;
-       LogDebug("Setting option IP_TOS on socket %d to IPTOS_LOWDELAY (%d).", Sock, value );
-       if( setsockopt( Sock, SOL_IP, IP_TOS, &value, (socklen_t)sizeof( value )) != 0 )
-       {
-               Log( LOG_ERR, "Can't set socket option IP_TOS: %s!", strerror( errno ));
+       LogDebug("Setting IP_TOS on socket %d to IPTOS_LOWDELAY.", Sock);
+       if (setsockopt(Sock, IPPROTO_IP, IP_TOS, &value,
+                      (socklen_t) sizeof(value))) {
+               Log(LOG_ERR, "Can't set socket option IP_TOS: %s!",
+                   strerror(errno));
                /* ignore this error */
        }
 #endif
@@ -1596,7 +1892,6 @@ Init_Socket( int Sock )
 } /* Init_Socket */
 
 
-
 static void
 cb_Connect_to_Server(int fd, UNUSED short events)
 {
@@ -1605,7 +1900,8 @@ cb_Connect_to_Server(int fd, UNUSED short events)
        size_t len;
        ng_ipaddr_t dest_addrs[4];      /* we can handle at most 3; but we read up to
                                           four so we can log the 'more than we can handle'
-                                          condition */
+                                          condition. First result is tried immediately, rest
+                                          is saved for later if needed. */
 
        LogDebug("Resolver: Got forward lookup callback on fd %d, events %d", fd, events);
 
@@ -1630,15 +1926,15 @@ cb_Connect_to_Server(int fd, UNUSED short events)
 
        LogDebug("Got result from resolver: %u structs (%u bytes).", len/sizeof(ng_ipaddr_t), len);
 
-       memset(&Conf_Server[i].dst_addr, 0, sizeof(&Conf_Server[i].dst_addr));
+       memset(&Conf_Server[i].dst_addr, 0, sizeof(Conf_Server[i].dst_addr));
        if (len > sizeof(ng_ipaddr_t)) {
                /* more than one address for this hostname, remember them
                 * in case first address is unreachable/not available */
                len -= sizeof(ng_ipaddr_t);
-               if (len > sizeof(&Conf_Server[i].dst_addr)) {
-                       len = sizeof(&Conf_Server[i].dst_addr);
-                       Log(LOG_NOTICE, "Notice: Resolver returned more IP Addresses for host than we can handle,"
-                                       " additional addresses dropped");
+               if (len > sizeof(Conf_Server[i].dst_addr)) {
+                       len = sizeof(Conf_Server[i].dst_addr);
+                       Log(LOG_NOTICE,
+                               "Notice: Resolver returned more IP Addresses for host than we can handle, additional addresses dropped.");
                }
                memcpy(&Conf_Server[i].dst_addr, &dest_addrs[1], len);
        }
@@ -1742,20 +2038,59 @@ Simple_Message( int Sock, const char *Msg )
 } /* Simple_Error */
 
 
+/**
+ * Get CLIENT structure that belongs to a local connection identified by its
+ * index number. Each connection belongs to a client by definition, so it is
+ * not required that the caller checks for NULL return values.
+ * @param Idx Connection index number
+ * @return Pointer to CLIENT structure
+ */
 GLOBAL CLIENT *
 Conn_GetClient( CONN_ID Idx ) 
 {
-       /* return Client-Structure that belongs to the local Connection Idx.
-        * If none is found, return NULL.
-        */
        CONNECTION *c;
-       assert( Idx >= 0 );
 
+       assert(Idx >= 0);
        c = array_get(&My_ConnArray, sizeof (CONNECTION), (size_t)Idx);
-
        assert(c != NULL);
-
        return c ? c->client : NULL;
 }
 
+
+#ifdef SSL_SUPPORT
+
+/**
+ * Get information about used SSL chiper.
+ * @param Idx Connection index number
+ * @param buf Buffer for returned information text
+ * @param len Size of return buffer "buf"
+ * @return true on success, false otherwise
+ */
+GLOBAL bool
+Conn_GetCipherInfo(CONN_ID Idx, char *buf, size_t len)
+{
+       if (Idx < 0)
+               return false;
+       assert(Idx < (int) array_length(&My_ConnArray, sizeof(CONNECTION)));
+       return ConnSSL_GetCipherInfo(&My_Connections[Idx], buf, len);
+}
+
+
+/**
+ * Check if a connection is SSL-enabled or not.
+ * @param Idx Connection index number
+ * @return true if connection is SSL-enabled, false otherwise.
+ */
+GLOBAL bool
+Conn_UsesSSL(CONN_ID Idx)
+{
+       if (Idx < 0)
+               return false;
+       assert(Idx < (int) array_length(&My_ConnArray, sizeof(CONNECTION)));
+       return Conn_OPTION_ISSET(&My_Connections[Idx], CONN_SSL);
+}
+
+#endif
+
+
 /* -eof- */