X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fngircd%2Fconn.c;h=6994c8be8d7fb3876bc88961c95cac258a16f5d3;hb=8605e9c0fe7ffa42149271c9af31288bd4a0dfac;hp=f0a97f9cdddec0437f883d7f0716c24f4d6b9286;hpb=9f067a059d9f64c68a73a2f7fbf5308ee342fe2a;p=ngircd-alex.git diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c index f0a97f9c..6994c8be 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 @@ -37,6 +37,9 @@ #include #ifdef HAVE_NETINET_IP_H +# ifdef HAVE_NETINET_IN_SYSTM_H +# include +# endif # include #endif @@ -75,13 +78,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 )); @@ -89,10 +95,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; +static size_t NumConnections, NumConnectionsMax, NumConnectionsAccepted; #ifdef TCPWRAP int allow_severity = LOG_INFO; @@ -106,55 +114,81 @@ 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_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; - if (New_Connection( sock ) >= 0) - NumConnections++; - LogDebug("Total number of connections now %ld.", NumConnections); + (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; - - NumConnections++; - LogDebug("Total number of connections now %ld.", NumConnections); 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)) { @@ -164,32 +198,28 @@ 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 )) { @@ -202,6 +232,10 @@ cb_connserver(int sock, UNUSED short what) } +/** + * Login to a remote server. + * @param idx Connection index + */ static void server_login(CONN_ID idx) { @@ -218,6 +252,11 @@ 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) { @@ -234,12 +273,12 @@ cb_connserver_login_ssl(int sock, short unused) case 0: LogDebug("ConnSSL_Connect: not ready"); return; case -1: - Log(LOG_INFO, "SSL connection on socket %d failed", sock); + 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, + Log( LOG_INFO, "SSL connection %d with \"%s:%d\" established.", idx, My_Connections[idx].host, Conf_Server[Conf_GetServer( idx )].port ); server_login(idx); @@ -247,6 +286,11 @@ cb_connserver_login_ssl(int sock, short unused) #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) { @@ -259,18 +303,27 @@ cb_clientserver(int sock, short what) return; } #ifdef SSL_SUPPORT - if (what & IO_WANTREAD || (Conn_OPTION_ISSET(&My_Connections[idx], CONN_SSL_WANT_WRITE))) - Read_Request( idx ); /* if TLS layer needs to write additional data, call Read_Request instead so SSL/TLS can continue */ + 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 ); + Read_Request(idx); #endif if (what & IO_WANTWRITE) - Handle_Write( idx ); + 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) { @@ -284,11 +337,13 @@ cb_clientserver_ssl(int sock, short what) } switch (ConnSSL_Accept(&My_Connections[idx])) { - case 1: break; /* OK */ - case 0: return; /* EAGAIN: this callback will be invoked again by the io layer */ - default: - Conn_Close( idx, "Socket closed!", "SSL accept error", false ); - return; + 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); @@ -301,11 +356,12 @@ cb_clientserver_ssl(int sock, short what) #endif +/** + * Initialite connecion module. + */ GLOBAL void Conn_Init( void ) { - /* Modul initialisieren: statische Strukturen "ausnullen". */ - CONN_ID i; /* Speicher fuer Verbindungs-Pool anfordern */ @@ -315,8 +371,8 @@ Conn_Init( void ) 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 @@ -324,33 +380,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 ? @@ -395,6 +447,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 ) { @@ -429,13 +485,12 @@ Conn_InitListeners( void ) 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 */ @@ -451,7 +506,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); @@ -488,7 +544,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; @@ -577,6 +633,7 @@ NewListener(const char *listen_addr, UINT16 Port) return sock; } /* NewListener */ + #ifdef SSL_SUPPORT /* * SSL/TLS connections require extra treatment: @@ -628,7 +685,7 @@ GLOBAL void Conn_Handler(void) { int i; - unsigned int wdatalen; + unsigned int wdatalen, bytes_processed; struct timeval tv; time_t t; @@ -651,9 +708,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); + } } } @@ -887,13 +954,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 @@ -916,13 +982,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); @@ -943,7 +1002,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 @@ -960,7 +1019,7 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient ) 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 ); + Log(LOG_INFO, "SSL connection %d shutting down ...", Idx); ConnSSL_Free(&My_Connections[Idx]); } #endif @@ -1042,6 +1101,27 @@ Conn_Close( CONN_ID Idx, char *LogMsg, char *FwdMsg, bool InformClient ) } /* 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 ) { @@ -1156,31 +1236,33 @@ 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; + 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); @@ -1191,49 +1273,56 @@ New_Connection( int Sock ) #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 ); - return -1; - } - - 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 ); + 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 ) { + 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); @@ -1242,53 +1331,81 @@ New_Connection( int Sock ) } /* 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; } @@ -1304,7 +1421,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 ); @@ -1381,21 +1500,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 @@ -1407,31 +1539,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: @@ -1467,7 +1609,7 @@ Handle_Buffer(CONN_ID Idx) #endif if (!ptr) - return; + break; /* Complete (=line terminated) request found, handle it! */ *ptr = '\0'; @@ -1482,16 +1624,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; @@ -1500,7 +1642,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.", @@ -1517,7 +1659,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); @@ -1527,6 +1669,7 @@ Handle_Buffer(CONN_ID Idx) } #endif } + return len_processed; } /* Handle_Buffer */ @@ -1537,6 +1680,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) @@ -1556,8 +1700,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) { @@ -1692,6 +1836,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 ); @@ -1718,9 +1864,9 @@ New_Server( int Server , ng_ipaddr_t *dest) 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 - NumConnections++; 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 ); @@ -1766,12 +1912,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 @@ -1780,7 +1927,6 @@ Init_Socket( int Sock ) } /* Init_Socket */ - static void cb_Connect_to_Server(int fd, UNUSED short events) { @@ -1789,7 +1935,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); @@ -1814,15 +1961,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); } @@ -1887,10 +2034,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) { @@ -1909,52 +2060,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 -/* we cannot access My_Connections in irc-info.c */ + +/** + * 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- */