X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=ngircd-alex.git;a=blobdiff_plain;f=src%2Fngircd%2Fconn.c;h=495c54f1d81a34ec00505ad7695f56ca369b3bf5;hp=2e23ade8e8bf406a7c2c2f3771e8fe17abc1fce6;hb=54e67ea9ee6c2b00c43f759edc55b57b969c9e2d;hpb=a84f7dcee5a1b32c74188aa5374d30eddd24852b diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index 2e23ade8..495c54f1 100644 --- a/src/ngircd/conn.c +++ b/src/ngircd/conn.c @@ -1,6 +1,6 @@ /* * ngIRCd -- The Next Generation IRC Daemon - * Copyright (c)2001-2007 Alexander Barton (alex@barton.de) + * Copyright (c)2001-2010 Alexander Barton * * 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 @@ -15,14 +15,15 @@ #define CONN_MODULE #include "portab.h" +#include "conf-ssl.h" #include "io.h" #include "imp.h" #include #ifdef PROTOTYPES -# include +# include #else -# include +# include #endif #include #include @@ -36,6 +37,9 @@ #include #ifdef HAVE_NETINET_IP_H +# ifdef HAVE_NETINET_IN_SYSTM_H +# include +# endif # include #endif @@ -49,19 +53,23 @@ #include "array.h" #include "defines.h" -#include "resolve.h" #include "exp.h" #include "conn.h" #include "imp.h" #include "ngircd.h" +#include "array.h" #include "client.h" #include "conf.h" +#include "conn-ssl.h" #include "conn-zip.h" #include "conn-func.h" #include "log.h" +#include "ng_ipaddr.h" #include "parse.h" +#include "proc.h" +#include "resolve.h" #include "tool.h" #ifdef ZEROCONF @@ -73,13 +81,16 @@ #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 void 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 )); @@ -87,9 +98,12 @@ static bool Init_Socket PARAMS(( int Sock )); static void New_Server PARAMS(( int Server, ng_ipaddr_t *dest )); static void Simple_Message PARAMS(( int Sock, const char *Msg )); static int NewListener PARAMS(( const char *listen_addr, UINT16 Port )); +static void Account_Connection PARAMS((void)); + static array My_Listeners; static array My_ConnArray; +static size_t NumConnections, NumConnectionsMax, NumConnectionsAccepted; #ifdef TCPWRAP int allow_severity = LOG_INFO; @@ -98,36 +112,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)) { @@ -137,37 +201,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) { @@ -183,44 +254,128 @@ 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, "SSL accept error, closing socket", "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 ); + Log(LOG_EMERG, "Can't allocate memory! [Conn_Init]"); + exit(1); } /* FIXME: My_Connetions/Pool_Size is needed by other parts of the @@ -228,33 +383,29 @@ Conn_Init( void ) My_Connections = (CONNECTION*) array_start(&My_ConnArray); LogDebug("Allocated connection pool for %d items (%ld bytes).", - array_length(&My_ConnArray, sizeof( CONNECTION )), array_bytes(&My_ConnArray)); + array_length(&My_ConnArray, sizeof(CONNECTION)), + array_bytes(&My_ConnArray)); - assert( array_length(&My_ConnArray, sizeof( CONNECTION )) >= (size_t) Pool_Size); + assert(array_length(&My_ConnArray, sizeof(CONNECTION)) >= (size_t)Pool_Size); array_free( &My_Listeners ); - /* Connection-Struktur initialisieren */ - for( i = 0; i < Pool_Size; i++ ) Init_Conn_Struct( i ); - - /* Global write counter */ - WCounter = 0; + for (i = 0; i < Pool_Size; i++) + Init_Conn_Struct(i); } /* 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 ? @@ -299,6 +450,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 ) { @@ -323,19 +478,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 */ @@ -351,7 +509,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); @@ -388,7 +547,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; @@ -478,6 +637,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 @@ -490,7 +688,7 @@ GLOBAL void Conn_Handler(void) { int i; - unsigned int wdatalen; + unsigned int wdatalen, bytes_processed; struct timeval tv; time_t t; @@ -513,9 +711,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); + } } } @@ -532,7 +740,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); } @@ -542,8 +751,11 @@ Conn_Handler(void) for (i = 0; i < Pool_Size; i++) { if (My_Connections[i].sock <= NONE) continue; - - if (Resolve_INPROGRESS(&My_Connections[i].res_stat)) { +#ifdef SSL_SUPPORT + if (SSL_WantWrite(&My_Connections[i])) + continue; /* TLS/SSL layer needs to write data; deal with this first */ +#endif + if (Proc_InProgress(&My_Connections[i].res_stat)) { /* Wait for completion of resolver sub-process ... */ io_event_del(My_Connections[i].sock, IO_WANTREAD); @@ -600,12 +812,12 @@ Conn_Handler(void) */ #ifdef PROTOTYPES GLOBAL bool -Conn_WriteStr( CONN_ID Idx, char *Format, ... ) +Conn_WriteStr(CONN_ID Idx, const char *Format, ...) #else GLOBAL bool -Conn_WriteStr( Idx, Format, va_alist ) +Conn_WriteStr(Idx, Format, va_alist) CONN_ID Idx; -char *Format; +const char *Format; va_dcl #endif { @@ -745,13 +957,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 @@ -774,13 +985,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); @@ -801,7 +1005,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 @@ -816,7 +1020,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! */ @@ -865,8 +1074,8 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient ) } /* cancel running resolver */ - if (Resolve_INPROGRESS(&My_Connections[Idx].res_stat)) - Resolve_Shutdown(&My_Connections[Idx].res_stat); + if (Proc_InProgress(&My_Connections[Idx].res_stat)) + Proc_Kill(&My_Connections[Idx].res_stat); /* Servers: Modify time of next connect attempt? */ Conf_UnsetServer( Idx ); @@ -887,10 +1096,35 @@ 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 */ +GLOBAL long +Conn_Count(void) +{ + return NumConnections; +} /* Conn_Count */ + + +GLOBAL long +Conn_CountMax(void) +{ + return NumConnectionsMax; +} /* Conn_CountMax */ + + +GLOBAL long +Conn_CountAccepted(void) +{ + return NumConnectionsAccepted; +} /* Conn_CountAccepted */ + + GLOBAL void Conn_SyncServerStruct( void ) { @@ -963,9 +1197,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; @@ -999,140 +1239,176 @@ 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) { Log(LOG_CRIT, "Can't accept connection: %s!", strerror(errno)); return -1; } + NumConnectionsAccepted++; if (!ng_ipaddr_tostr_r(&new_addr, ip_str)) { 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); + + Account_Connection(); return new_sock; } /* New_Connection */ +static void +Account_Connection(void) +{ + NumConnections++; + if (NumConnections > NumConnectionsMax) + NumConnectionsMax = NumConnections; + LogDebug("Total number of connections now %lu (max %lu).", + NumConnections, NumConnectionsMax); +} /* Account_Connection */ + + 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; } @@ -1148,7 +1424,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 ); @@ -1168,6 +1446,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 ...", @@ -1220,21 +1503,34 @@ 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 all data in the connection read-buffer. - * All data is precessed until no complete command is left. When a fatal - * error occurs, the connection is shut down. + * 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 void +static unsigned int Handle_Buffer(CONN_ID Idx) { #ifndef STRICT_RFC @@ -1246,31 +1542,41 @@ Handle_Buffer(CONN_ID Idx) #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); - for (;;) { + for (i=0; i < maxcmd; i++) { /* Check penalty */ if (My_Connections[Idx].delaytime > starttime) - return; + 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; + return 0; } #endif if (0 == array_bytes(&My_Connections[Idx].rbuf)) - return; + break; /* Make sure that the buffer is NULL terminated */ if (!array_cat0_temporary(&My_Connections[Idx].rbuf)) { Conn_Close(Idx, NULL, "Can't allocate memory [Handle_Buffer]", true); - return; + return 0; } /* RFC 2812, section "2.3 Messages", 5th paragraph: @@ -1306,7 +1612,7 @@ Handle_Buffer(CONN_ID Idx) #endif if (!ptr) - return; + break; /* Complete (=line terminated) request found, handle it! */ *ptr = '\0'; @@ -1321,16 +1627,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; + 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); - return; + continue; } - #ifdef ZLIB /* remember if stream is already compressed */ old_z = My_Connections[Idx].options & CONN_ZIP; @@ -1339,7 +1645,7 @@ Handle_Buffer(CONN_ID Idx) My_Connections[Idx].msg_in++; if (!Parse_Request (Idx, (char *)array_start(&My_Connections[Idx].rbuf))) - return; + return 0; /* error -> connection has been closed */ array_moveleft(&My_Connections[Idx].rbuf, 1, len); LogDebug("Connection %d: %d bytes left in read buffer.", @@ -1356,7 +1662,7 @@ Handle_Buffer(CONN_ID Idx) Conn_Close(Idx, NULL, "Can't allocate memory [Handle_Buffer]", true); - return; + return 0; } array_trunc(&My_Connections[Idx].rbuf); @@ -1366,6 +1672,7 @@ Handle_Buffer(CONN_ID Idx) } #endif } + return len_processed; } /* Handle_Buffer */ @@ -1376,6 +1683,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) @@ -1395,8 +1703,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) { @@ -1458,7 +1766,7 @@ Check_Servers( void ) /* Okay, try to connect now */ Conf_Server[i].lasttry = time_now; Conf_Server[i].conn_id = SERVER_WAIT; - assert(Resolve_Getfd(&Conf_Server[i].res_stat) < 0); + assert(Proc_GetPipeFd(&Conf_Server[i].res_stat) < 0); Resolve_Name(&Conf_Server[i].res_stat, Conf_Server[i].host, cb_Connect_to_Server); } } /* Check_Servers */ @@ -1531,6 +1839,8 @@ New_Server( int Server , ng_ipaddr_t *dest) return; } + /* Conn_Close() decrements this counter again */ + Account_Connection(); Client_SetIntroducer( c, c ); Client_SetToken( c, TOKEN_OUTBOUND ); @@ -1549,9 +1859,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 */ @@ -1569,7 +1889,7 @@ Init_Conn_Struct(CONN_ID Idx) My_Connections[Idx].signon = now; My_Connections[Idx].lastdata = now; My_Connections[Idx].lastprivmsg = now; - Resolve_Init(&My_Connections[Idx].res_stat); + Proc_InitStruct(&My_Connections[Idx].res_stat); } /* Init_Conn_Struct */ @@ -1595,12 +1915,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 @@ -1609,7 +1930,6 @@ Init_Socket( int Sock ) } /* Init_Socket */ - static void cb_Connect_to_Server(int fd, UNUSED short events) { @@ -1618,12 +1938,13 @@ 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); for (i=0; i < MAX_SERVERS; i++) { - if (Resolve_Getfd(&Conf_Server[i].res_stat) == fd ) + if (Proc_GetPipeFd(&Conf_Server[i].res_stat) == fd ) break; } @@ -1643,15 +1964,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); } @@ -1682,7 +2003,7 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events ) /* Search associated connection ... */ for( i = 0; i < Pool_Size; i++ ) { if(( My_Connections[i].sock != NONE ) - && ( Resolve_Getfd(&My_Connections[i].res_stat) == r_fd )) + && (Proc_GetPipeFd(&My_Connections[i].res_stat) == r_fd)) break; } if( i >= Pool_Size ) { @@ -1716,10 +2037,14 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events ) c = Conn_GetClient( i ); assert( c != NULL ); - /* Only update client information of unregistered clients */ - if( Client_Type( c ) == CLIENT_UNKNOWN ) { - strlcpy(My_Connections[i].host, readbuf, sizeof( My_Connections[i].host)); - Client_SetHostname( c, readbuf); + /* Only update client information of unregistered clients. + * Note: user commands (e. g. WEBIRC) are always read _after_ reading + * the resolver results, so we don't have to worry to override settings + * from these commands here. */ + if(Client_Type(c) == CLIENT_UNKNOWN) { + strlcpy(My_Connections[i].host, readbuf, + sizeof(My_Connections[i].host)); + Client_SetHostname(c, readbuf); #ifdef IDENTAUTH ++identptr; if (*identptr) { @@ -1738,37 +2063,88 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events ) } /* cb_Read_Resolver_Result */ +/** + * Write a "simple" (error) message to a socket. + * The message is sent without using the connection write buffers, without + * compression/encryption, and even without any error reporting. It is + * designed for error messages of e.g. New_Connection(). */ static void -Simple_Message( int Sock, const char *Msg ) +Simple_Message(int Sock, const char *Msg) { char buf[COMMAND_LEN]; size_t len; - /* Write "simple" message to socket, without using compression - * or even the connection write buffers. Used e.g. for error - * messages by New_Connection(). */ - assert( Sock > NONE ); - assert( Msg != NULL ); - - strlcpy( buf, Msg, sizeof buf - 2); - len = strlcat( buf, "\r\n", sizeof buf); - (void)write(Sock, buf, len); + + assert(Sock > NONE); + assert(Msg != NULL); + + strlcpy(buf, Msg, sizeof buf - 2); + len = strlcat(buf, "\r\n", sizeof buf); + if (write(Sock, buf, len) < 0) { + /* Because this function most probably got called to log + * an error message, any write error is ignored here to + * avoid an endless loop. But casting the result of write() + * to "void" doesn't satisfy the GNU C code attribute + * "warn_unused_result" which is used by some versions of + * glibc (e.g. 2.11.1), therefore this silly error + * "handling" code here :-( */ + return; + } } /* 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- */