]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/conn.c
Implement support for systemd(8) "socket activation"
[ngircd-alex.git] / src / ngircd / conn.c
index fd175971a16d9b48a546efa94df84dc8ff39ebc5..378509f96f09bc35eb9e3ebc2eb12140c24509fb 100644 (file)
 # include <netinet/ip.h>
 #endif
 
-#ifdef HAVE_STDINT_H
-# include <stdint.h>                   /* e.g. for Mac OS X */
-#endif
-
 #ifdef TCPWRAP
 # include <tcpd.h>                     /* for TCP Wrappers */
 #endif
@@ -67,6 +63,7 @@
 #include "client.h"
 #include "class.h"
 #include "conf.h"
+#include "conn-encoding.h"
 #include "conn-ssl.h"
 #include "conn-zip.h"
 #include "conn-func.h"
 #define MAX_COMMANDS_SERVER_MIN 10
 #define MAX_COMMANDS_SERVICE 10
 
+#define SD_LISTEN_FDS_START 3
+
 
 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 int New_Connection PARAMS(( int Sock, bool IsSSL ));
 static CONN_ID Socket2Index PARAMS(( int Sock ));
 static void Read_Request PARAMS(( CONN_ID Idx ));
 static unsigned int Handle_Buffer PARAMS(( CONN_ID Idx ));
@@ -123,6 +122,40 @@ static void cb_Connect_to_Server PARAMS((int sock, UNUSED short what));
 static void cb_clientserver PARAMS((int sock, short what));
 
 
+/**
+ * Get number of sockets available from systemd(8).
+ *
+ * ngIRCd needs to implement its own sd_listen_fds(3) function and can't
+ * use the one provided by systemd itself, becaus the sockets will be
+ * used in a forked child process with a new PID, and this would trigger
+ * an error in the standard implementation.
+ *
+ * @return Number of sockets available, -1 if sockets have already been
+ *         initialized, or 0 when no sockets have been passed.
+ */
+static int
+my_sd_listen_fds(void)
+{
+       const char *e;
+       long count;
+
+       /* Check if LISTEN_PID exists; but we ignore the result, because
+        * normally ngircd forks a child before checking this, and therefore
+        * the PID set in the environment is always wrong ... */
+       e = getenv("LISTEN_PID");
+       if (!e || !*e)
+               return 0;
+
+       e = getenv("LISTEN_FDS");
+       if (!e || !*e)
+               return -1;
+       count = atol(e);
+       unsetenv("LISTEN_FDS");
+
+       return count;
+}
+
+
 /**
  * IO callback for listening sockets: handle new connections. This callback
  * gets called when a new non-SSL connection should be accepted.
@@ -134,7 +167,7 @@ static void
 cb_listen(int sock, short irrelevant)
 {
        (void) irrelevant;
-       (void) New_Connection(sock);
+       (void) New_Connection(sock, false);
 }
 
 
@@ -152,7 +185,7 @@ cb_listen_ssl(int sock, short irrelevant)
        int fd;
 
        (void) irrelevant;
-       fd = New_Connection(sock);
+       fd = New_Connection(sock, true);
        if (fd < 0)
                return;
        io_event_setcb(My_Connections[fd].sock, cb_clientserver_ssl);
@@ -208,7 +241,7 @@ cb_connserver(int sock, UNUSED short what)
                            My_Connections[idx].host, Conf_Server[server].port,
                            idx, strerror(err));
 
-               Conn_Close(idx, "Can't connect!", NULL, false);
+               Conn_Close(idx, "Can't connect", NULL, false);
 
                if (ng_ipaddr_af(&Conf_Server[server].dst_addr[0])) {
                        /* more addresses to try... */
@@ -285,7 +318,7 @@ cb_connserver_login_ssl(int sock, short unused)
                return;
        case -1:
                Log(LOG_ERR, "SSL connection on socket %d failed!", sock);
-               Conn_Close(idx, "Can't connect!", NULL, false);
+               Conn_Close(idx, "Can't connect", NULL, false);
                return;
        }
 
@@ -457,7 +490,7 @@ Conn_CloseAllSockets(int ExceptOf)
  * @returns            Number of listening sockets created.
  */
 static unsigned int
-ports_initlisteners(array *a, const char *listen_addr, void (*func)(int,short))
+Init_Listeners(array *a, const char *listen_addr, void (*func)(int,short))
 {
        unsigned int created = 0;
        size_t len;
@@ -473,8 +506,9 @@ ports_initlisteners(array *a, const char *listen_addr, void (*func)(int,short))
                        continue;
                }
                if (!io_event_create( fd, IO_WANTREAD, func )) {
-                       Log( LOG_ERR, "io_event_create(): Could not add listening fd %d (port %u): %s!",
-                                               fd, (unsigned int) *port, strerror(errno));
+                       Log(LOG_ERR,
+                           "io_event_create(): Can't add fd %d (port %u): %s!",
+                           fd, (unsigned int) *port, strerror(errno));
                        close(fd);
                        port++;
                        continue;
@@ -497,13 +531,43 @@ Conn_InitListeners( void )
        /* Initialize ports on which the server should accept connections */
        unsigned int created = 0;
        char *copy, *listen_addr;
+       int count, fd, i;
 
        assert(Conf_ListenAddress);
 
+       count = my_sd_listen_fds();
+       if (count < 0) {
+               Log(LOG_INFO,
+                   "Not re-initializing listening sockets of systemd(8) ...");
+               return 0;
+       }
+       if (count > 0) {
+               /* systemd(8) passed sockets to us, so don't try to initialize
+                * listening sockets on our own but use the passed ones */
+               LogDebug("Initializing %d systemd sockets ...", count);
+               for (i = 0; i < count; i++) {
+                       fd = SD_LISTEN_FDS_START + i;
+                       Init_Socket(fd);
+                       if (!io_event_create(fd, IO_WANTREAD, cb_listen)) {
+                               Log(LOG_ERR,
+                                   "io_event_create(): Can't add fd %d: %s!",
+                                   fd, strerror(errno));
+                               continue;
+                       }
+                       Log(LOG_INFO,
+                           "Initialized socket %d from systemd.", fd);
+                       created++;
+               }
+               return created;
+       }
+
+       /* not using systemd socket activation, initialize listening sockets: */
+
        /* can't use Conf_ListenAddress directly, see below */
        copy = strdup(Conf_ListenAddress);
        if (!copy) {
-               Log(LOG_CRIT, "Cannot copy %s: %s", Conf_ListenAddress, strerror(errno));
+               Log(LOG_CRIT, "Cannot copy %s: %s", Conf_ListenAddress,
+                   strerror(errno));
                return 0;
        }
        listen_addr = strtok(copy, ",");
@@ -511,9 +575,11 @@ Conn_InitListeners( void )
        while (listen_addr) {
                ngt_TrimStr(listen_addr);
                if (*listen_addr) {
-                       created += ports_initlisteners(&Conf_ListenPorts, listen_addr, cb_listen);
+                       created += Init_Listeners(&Conf_ListenPorts,
+                                                 listen_addr, cb_listen);
 #ifdef SSL_SUPPORT
-                       created += ports_initlisteners(&Conf_SSLOptions.ListenPorts, listen_addr, cb_listen_ssl);
+                       created += Init_Listeners(&Conf_SSLOptions.ListenPorts,
+                                                 listen_addr, cb_listen_ssl);
 #endif
                }
 
@@ -540,7 +606,12 @@ Conn_ExitListeners( void )
        int *fd;
        size_t arraylen;
 
+       /* Get number of listening sockets to shut down. There can be none
+        * if ngIRCd has been "socket activated" by systemd. */
        arraylen = array_length(&My_Listeners, sizeof (int));
+       if (arraylen < 1)
+               return;
+
        Log(LOG_INFO,
            "Shutting down all listening sockets (%d total) ...", arraylen);
        fd = array_start(&My_Listeners);
@@ -571,7 +642,7 @@ InitSinaddrListenAddr(ng_ipaddr_t *addr, const char *listen_addrstr, UINT16 Port
        ret = ng_ipaddr_init(addr, listen_addrstr, Port);
        if (!ret) {
                assert(listen_addrstr);
-               Log(LOG_CRIT, "Can't bind to [%s]:%u: can't convert ip address \"%s\"",
+               Log(LOG_CRIT, "Can't bind to [%s]:%u: can't convert ip address \"%s\"!",
                                                listen_addrstr, Port, listen_addrstr);
        }
        return ret;
@@ -623,8 +694,9 @@ NewListener(const char *listen_addr, UINT16 Port)
 
        af = ng_ipaddr_af(&addr);
        sock = socket(af, SOCK_STREAM, 0);
-       if( sock < 0 ) {
-               Log(LOG_CRIT, "Can't create socket (af %d) : %s!", af, strerror(errno));
+       if (sock < 0) {
+               Log(LOG_CRIT, "Can't create socket (af %d) : %s!", af,
+                   strerror(errno));
                return -1;
        }
 
@@ -634,22 +706,23 @@ NewListener(const char *listen_addr, UINT16 Port)
                return -1;
 
        if (bind(sock, (struct sockaddr *)&addr, ng_ipaddr_salen(&addr)) != 0) {
-               Log(LOG_CRIT, "Can't bind socket to address %s:%d - %s",
-                       ng_ipaddr_tostr(&addr), Port, strerror(errno));
+               Log(LOG_CRIT, "Can't bind socket to address %s:%d - %s!",
+                   ng_ipaddr_tostr(&addr), Port, strerror(errno));
                close(sock);
                return -1;
        }
 
-       if( listen( sock, 10 ) != 0 ) {
-               Log( LOG_CRIT, "Can't listen on socket: %s!", strerror( errno ));
-               close( sock );
+       if (listen(sock, 10) != 0) {
+               Log(LOG_CRIT, "Can't listen on socket: %s!", strerror(errno));
+               close(sock);
                return -1;
        }
 
        /* keep fd in list so we can close it when ngircd restarts/shuts down */
-       if (!array_catb( &My_Listeners,(char*) &sock, sizeof(int) )) {
-               Log( LOG_CRIT, "Can't add socket to My_Listeners array: %s!", strerror( errno ));
-               close( sock );
+       if (!array_catb(&My_Listeners, (char *)&sock, sizeof(int))) {
+               Log(LOG_CRIT, "Can't add socket to My_Listeners array: %s!",
+                   strerror(errno));
+               close(sock);
                return -1;
        }
 
@@ -865,6 +938,9 @@ va_dcl
 #endif
 {
        char buffer[COMMAND_LEN];
+#ifdef ICONV
+       char *ptr, *message;
+#endif
        size_t len;
        bool ok;
        va_list ap;
@@ -905,6 +981,16 @@ va_dcl
                        CUT_TXTSUFFIX);
        }
 
+#ifdef ICONV
+       ptr = strchr(buffer + 1, ':');
+       if (ptr) {
+               ptr++;
+               message = Conn_EncodingTo(Idx, ptr);
+               if (message != ptr)
+                       strlcpy(ptr, message, sizeof(buffer) - (ptr - buffer));
+       }
+#endif
+
 #ifdef SNIFFER
        if (NGIRCd_Sniffer)
                Log(LOG_DEBUG, " -> connection %d: '%s'.", Idx, buffer);
@@ -1068,7 +1154,7 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClie
        Conn_OPTION_ADD( &My_Connections[Idx], CONN_ISCLOSING );
 
        port = ng_ipaddr_getport(&My_Connections[Idx].addr);
-       Log(LOG_INFO, "Shutting down connection %d (%s) with %s:%d ...", Idx,
+       Log(LOG_INFO, "Shutting down connection %d (%s) with \"%s:%d\" ...", Idx,
            LogMsg ? LogMsg : FwdMsg, My_Connections[Idx].host, port);
 
        /* Search client, if any */
@@ -1142,7 +1228,7 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClie
                in_p = (int)(( in_k * 100 ) / in_z_k );
                out_p = (int)(( out_k * 100 ) / out_z_k );
                Log(LOG_INFO,
-                   "Connection %d with %s:%d closed (in: %.1fk/%.1fk/%d%%, out: %.1fk/%.1fk/%d%%).",
+                   "Connection %d with \"%s:%d\" closed (in: %.1fk/%.1fk/%d%%, out: %.1fk/%.1fk/%d%%).",
                    Idx, My_Connections[Idx].host, port,
                    in_k, in_z_k, in_p, out_k, out_z_k, out_p);
        }
@@ -1150,7 +1236,7 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClie
 #endif
        {
                Log(LOG_INFO,
-                   "Connection %d with %s:%d closed (in: %.1fk, out: %.1fk).",
+                   "Connection %d with \"%s:%d\" closed (in: %.1fk, out: %.1fk).",
                    Idx, My_Connections[Idx].host, port,
                    in_k, out_k);
        }
@@ -1362,17 +1448,18 @@ Count_Connections(ng_ipaddr_t *a)
  * Initialize new client connection on a listening socket.
  *
  * @param Sock Listening socket descriptor.
+ * @param IsSSL        true if this socket expects SSL-encrypted data.
  * @returns    Accepted socket descriptor or -1 on error.
  */
 static int
-New_Connection(int Sock)
+New_Connection(int Sock, UNUSED bool IsSSL)
 {
 #ifdef TCPWRAP
        struct request_info req;
 #endif
        ng_ipaddr_t new_addr;
        char ip_str[NG_INET_ADDRSTRLEN];
-       int new_sock, new_sock_len, identsock;
+       int new_sock, new_sock_len;
        CLIENT *c;
        long cnt;
 
@@ -1489,33 +1576,59 @@ New_Connection(int Sock)
 
        Client_SetHostname(c, My_Connections[new_sock].host);
 
-       Log(LOG_INFO, "Accepted connection %d from %s:%d on socket %d.",
+       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);
+       Account_Connection();
+
+#ifdef SSL_SUPPORT
+       /* Delay connection initalization until SSL handshake is finished */
+       if (!IsSSL)
+#endif
+               Conn_StartLogin(new_sock);
+
+       return new_sock;
+} /* New_Connection */
+
+
+/**
+ * Finish connection initialization, start resolver subprocess.
+ *
+ * @param Idx Connection index.
+ */
+GLOBAL void
+Conn_StartLogin(CONN_ID Idx)
+{
+       int ident_sock = -1;
+
+       assert(Idx >= 0);
+
+       /* Nothing to do if DNS (and resolver subprocess) is disabled */
+       if (!Conf_DNS)
+               return;
 
-       identsock = new_sock;
 #ifdef IDENTAUTH
-       if (!Conf_Ident)
-               identsock = -1;
+       /* Should we make an IDENT request? */
+       if (Conf_Ident)
+               ident_sock = My_Connections[Idx].sock;
 #endif
-       if (Conf_DNS) {
-               if (Conf_NoticeAuth) {
+
+       if (Conf_NoticeAuth) {
+               /* Send "NOTICE AUTH" messages to the client */
 #ifdef IDENTAUTH
-                       if (Conf_Ident)
-                               (void)Conn_WriteStr(new_sock,
-                                       "NOTICE AUTH :*** Looking up your hostname and checking ident");
-                       else
+               if (Conf_Ident)
+                       (void)Conn_WriteStr(Idx,
+                               "NOTICE AUTH :*** Looking up your hostname and checking ident");
+               else
 #endif
-                               (void)Conn_WriteStr(new_sock,
-                                       "NOTICE AUTH :*** Looking up your hostname");
-               }
-               Resolve_Addr(&My_Connections[new_sock].proc_stat, &new_addr,
-                            identsock, cb_Read_Resolver_Result);
+                       (void)Conn_WriteStr(Idx,
+                               "NOTICE AUTH :*** Looking up your hostname");
+               (void)Handle_Write(Idx);
        }
 
-       Account_Connection();
-       return new_sock;
-} /* New_Connection */
+       Resolve_Addr(&My_Connections[Idx].proc_stat, &My_Connections[Idx].addr,
+                    ident_sock, cb_Read_Resolver_Result);
+}
 
 
 /**
@@ -1592,13 +1705,10 @@ Read_Request( CONN_ID Idx )
 #endif
        len = read(My_Connections[Idx].sock, readbuf, sizeof(readbuf));
        if (len == 0) {
-               Log(LOG_INFO, "%s:%u (%s) is closing the connection ...",
-                               My_Connections[Idx].host,
-                               (unsigned int) ng_ipaddr_getport(&My_Connections[Idx].addr),
-                               ng_ipaddr_tostr(&My_Connections[Idx].addr));
-               Conn_Close(Idx,
-                          "Socket closed!", "Client closed connection",
-                          false);
+               LogDebug("Client \"%s:%u\" is closing connection %d ...",
+                        My_Connections[Idx].host,
+                        ng_ipaddr_tostr(&My_Connections[Idx].addr), Idx);
+               Conn_Close(Idx, NULL, "Client closed connection", false);
                return;
        }
 
@@ -1606,7 +1716,7 @@ Read_Request( CONN_ID Idx )
                if( errno == EAGAIN ) return;
                Log(LOG_ERR, "Read error on connection %d (socket %d): %s!",
                    Idx, My_Connections[Idx].sock, strerror(errno));
-               Conn_Close(Idx, "Read error!", "Client closed connection",
+               Conn_Close(Idx, "Read error", "Client closed connection",
                           false);
                return;
        }
@@ -2042,7 +2152,8 @@ New_Server( int Server , ng_ipaddr_t *dest)
        Client_SetToken( c, TOKEN_OUTBOUND );
 
        /* Register connection */
-       Conf_SetServer(Server, new_sock);
+       if (!Conf_SetServer(Server, new_sock))
+               return;
        My_Connections[new_sock].sock = new_sock;
        My_Connections[new_sock].addr = *dest;
        My_Connections[new_sock].client = c;
@@ -2082,6 +2193,11 @@ Init_Conn_Struct(CONN_ID Idx)
        My_Connections[Idx].lastdata = now;
        My_Connections[Idx].lastprivmsg = now;
        Proc_InitStruct(&My_Connections[Idx].proc_stat);
+
+#ifdef ICONV
+       My_Connections[Idx].iconv_from = (iconv_t)(-1);
+       My_Connections[Idx].iconv_to = (iconv_t)(-1);
+#endif
 } /* Init_Conn_Struct */
 
 
@@ -2295,6 +2411,10 @@ cb_Read_Resolver_Result( int r_fd, UNUSED short events )
                                        "NOTICE AUTH :*** No ident response");
                }
 #endif
+
+               if (Conf_NoticeAuth)
+                       (void)Handle_Write(i);
+
                Class_HandleServerBans(c);
        }
 #ifdef DEBUG