Commit Diff


commit - ac32d07aaff0e7a1c4a544353dadbf397859d8f9
commit + 628c14d65686c4c848a17381b8ef61c78dbcf405
blob - 09b43a68a8bb71ef5ba2fb3dd02f9cee1f04bf14
blob + 6d16f7cac50f8b177fb4d336201906733b0a3607
--- contrib/Makefile.am
+++ contrib/Makefile.am
@@ -1,6 +1,6 @@
 #
 # ngIRCd -- The Next Generation IRC Daemon
-# Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors
+# Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@ EXTRA_DIST = README \
 	ngIRCd-Logo.gif \
 	ngircd-redhat.init \
 	ngircd.service \
+	ngircd.socket \
 	ngircd.spec \
 	platformtest.sh \
 	systrace.policy
blob - 1aebd1ebf6fe0f0d9cd944a99f99b1e4cb5f3cdf
blob + 2d639e664076d19c8b20a23e50a4d6426ddd6953
--- contrib/README
+++ contrib/README
@@ -2,18 +2,21 @@
                      ngIRCd - Next Generation IRC Server
                            http://ngircd.barton.de/
 
-               (c)2001-2011 Alexander Barton and Contributors.
+               (c)2001-2013 Alexander Barton and Contributors.
                ngIRCd is free software and published under the
                    terms of the GNU General Public License.
 
-                            -- Contributions --
+                             -- Contributions --
 
 
 Debian/
  - Various files for building Debian GNU/Linux packages (".deb's").
+	- ngircd.init; ngircd.default: init script for Debian-based systems.
+	- ngircd.pam: example PAM configuraton.
 
 MacOSX/
  - Project files for XCode, the "project builder" of Apple Mac OS X.
+	- de.barton.ngircd.plist[.tmpl]: launchd(8) property list.
 
 ngindent
  - Script to indent the code of ngIRCd in the "standard way".
@@ -24,6 +27,12 @@ ngircd-bsd.sh
 ngircd-redhat.init
  - Start/stop script for RedHat-based distributions (like CentOS).
 
+ngircd.service
+ - systemd(8) service unit configuration file.
+
+ngircd.socket
+ - systemd(8) socket unit configuration file for "socket activation".
+
 ngircd.spec
  - RPM "spec" file.
 
blob - /dev/null
blob + 1c0cc004b176cdd15063ed1493d9994c42bdd330 (mode 644)
--- /dev/null
+++ contrib/ngircd.socket
@@ -0,0 +1,11 @@
+[Unit]
+Description=Next Generation IRC Daemon (Socket)
+
+[Socket]
+BindIPv6Only=ipv6-only
+ListenStream=0.0.0.0:6667
+#ListenStream=[::]:6667
+IPTOS=low-delay
+
+[Install]
+WantedBy=sockets.target
blob - 1c3998ad61368fca9448013206c2cd35e5187779
blob + 822fd5d4fb9dd5b99f2694b5be98ce88075c68c5
--- doc/sample-ngircd.conf.tmpl
+++ doc/sample-ngircd.conf.tmpl
@@ -88,6 +88,13 @@
 	# to not yet (or no longer) connected servers.
 	;ConnectRetry = 60
 
+	# Number of seconds after which the whole daemon should shutdown when
+	# no connections are left active after handling at least one client
+	# (0: never, which is the default).
+	# This can be useful for testing or when ngIRCd is started using
+	# "socket activation" with systemd(8), for example.
+	;IdleTimeout = 0
+
 	# Maximum number of simultaneous in- and outbound connections the
 	# server is allowed to accept (0: unlimited):
 	;MaxConnections = 0
blob - 859c6a8ac0eb547b915e73fbed78cbf4e726c8a5
blob + e5485dbf10edddc2c7c0532b79902bd696fedbc4
--- man/ngircd.conf.5.tmpl
+++ man/ngircd.conf.5.tmpl
@@ -1,7 +1,7 @@
 .\"
 .\" ngircd.conf(5) manual page template
 .\"
-.TH ngircd.conf 5 "Nov 2012" ngIRCd "ngIRCd Manual"
+.TH ngircd.conf 5 "Feb 2013" ngIRCd "ngIRCd Manual"
 .SH NAME
 ngircd.conf \- configuration file of ngIRCd
 .SH SYNOPSIS
@@ -169,6 +169,12 @@ should be safe, but it is wise to double-check :-)
 \fBConnectRetry\fR (number)
 The server tries every <ConnectRetry> seconds to establish a link to not yet
 (or no longer) connected servers. Default: 60.
+.TP
+\fBIdleTimeout\fR (number)
+Number of seconds after which the whole daemon should shutdown when no
+connections are left active after handling at least one client (0: never). This
+can be useful for testing or when ngIRCd is started using "socket activation"
+with systemd(8), for example. Default: 0.
 .TP
 \fBMaxConnections\fR (number)
 Maximum number of simultaneous in- and outbound connections the server is
blob - 929ab05403734b8cd43e851955828072c38a0e33
blob + e46dcfee43669644091285586fd956b9c9a6c94c
--- src/ngircd/conf.c
+++ src/ngircd/conf.c
@@ -370,6 +370,7 @@ Conf_Test( void )
 
 	puts("[LIMITS]");
 	printf("  ConnectRetry = %d\n", Conf_ConnectRetry);
+	printf("  IdleTimeout = %d\n", Conf_IdleTimeout);
 	printf("  MaxConnections = %d\n", Conf_MaxConnections);
 	printf("  MaxConnectionsIP = %d\n", Conf_MaxConnectionsIP);
 	printf("  MaxJoins = %d\n", Conf_MaxJoins > 0 ? Conf_MaxJoins : -1);
@@ -736,6 +737,7 @@ Set_Defaults(bool InitServers)
 
 	/* Limits */
 	Conf_ConnectRetry = 60;
+	Conf_IdleTimeout = 0;
 	Conf_MaxConnections = 0;
 	Conf_MaxConnectionsIP = 5;
 	Conf_MaxJoins = 10;
@@ -830,8 +832,8 @@ Read_TextFile(const char *Filename, const char *Name, 
 
 	fp = fopen(Filename, "r");
 	if (!fp) {
-		Config_Error(LOG_WARNING, "Can't read %s file \"%s\": %s",
-					Name, Filename, strerror(errno));
+		Config_Error(LOG_ERR, "Can't read %s file \"%s\": %s",
+			     Name, Filename, strerror(errno));
 		return false;
 	}
 
@@ -841,7 +843,7 @@ Read_TextFile(const char *Filename, const char *Name, 
 
 		/* add text including \0 */
 		if (!array_catb(Destination, line, strlen(line) + 1)) {
-			Log(LOG_WARNING, "Cannot read/add \"%s\", line %d: %s",
+			Log(LOG_ERR, "Cannot read/add \"%s\", line %d: %s",
 			    Filename, line_no, strerror(errno));
 			break;
 		}
@@ -1241,6 +1243,7 @@ CheckLegacyGlobalOption(int Line, char *Var, char *Arg
 		return "[Options]";
 	}
 	if (strcasecmp(Var, "ConnectRetry") == 0
+	    || strcasecmp(Var, "IdleTimeout") == 0
 	    || strcasecmp(Var, "MaxConnections") == 0
 	    || strcasecmp(Var, "MaxConnectionsIP") == 0
 	    || strcasecmp(Var, "MaxJoins") == 0
@@ -1490,6 +1493,12 @@ Handle_LIMITS(int Line, char *Var, char *Arg)
 		}
 		return;
 	}
+	if (strcasecmp(Var, "IdleTimeout") == 0) {
+		Conf_IdleTimeout = atoi(Arg);
+		if (!Conf_IdleTimeout && strcmp(Arg, "0"))
+			Config_Error_NaN(Line, Var);
+		return;
+	}
 	if (strcasecmp(Var, "MaxConnections") == 0) {
 		Conf_MaxConnections = atoi(Arg);
 		if (!Conf_MaxConnections && strcmp(Arg, "0"))
blob - c203b57032a9558a3d3e6efcf44dad9019e5a355
blob + bbf4f36c24c4ff29d1c2b3dc99ba58897f658443
--- src/ngircd/conf.h
+++ src/ngircd/conf.h
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ * Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -211,6 +211,9 @@ GLOBAL bool Conf_ConnectIPv6;
 /** Try to connect to remote systems using the IPv4 protocol (true) */
 GLOBAL bool Conf_ConnectIPv4;
 
+/** Idle timout (seconds), after which the daemon should exit */
+GLOBAL int Conf_IdleTimeout;
+
 /** Maximum number of simultaneous connections to this server */
 GLOBAL int Conf_MaxConnections;
 
blob - 14d337b9408265b1213a157cd36cf66a8efc057e
blob + 3a430428adc9832b8f06f16ddbe4052737548a33
--- src/ngircd/conn.c
+++ src/ngircd/conn.c
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
+ * Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -82,7 +82,9 @@
 #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, bool IsSSL ));
@@ -118,8 +120,44 @@ static void cb_clientserver_ssl PARAMS((int sock, shor
 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));
+
+time_t idle_t = 0;
+
+
+/**
+ * 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.
@@ -494,10 +532,71 @@ Conn_InitListeners( void )
 {
 	/* Initialize ports on which the server should accept connections */
 	unsigned int created = 0;
-	char *copy, *listen_addr;
+	char *af_str, *copy, *listen_addr;
+	int count, fd, i, addr_len;
+	ng_ipaddr_t addr;
 
 	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;
+			addr_len = (int)sizeof(addr);
+			getsockname(fd, (struct sockaddr *)&addr, (socklen_t*)&addr_len);
+#ifdef WANT_IPV6
+			if (addr.sin4.sin_family != AF_INET && addr.sin4.sin_family != AF_INET6)
+#else
+			if (addr.sin4.sin_family != AF_INET)
+#endif
+			{
+				/* Socket is of unsupported type! For example, systemd passed in
+				 * an IPv6 socket but ngIRCd isn't compiled with IPv6 support. */
+				switch (addr.sin4.sin_family)
+				{
+					case AF_UNSPEC: af_str = "AF_UNSPEC"; break;
+					case AF_UNIX: af_str = "AF_UNIX"; break;
+					case AF_INET: af_str = "AF_INET"; break;
+#ifdef AF_INET6
+					case AF_INET6: af_str = "AF_INET6"; break;
+#endif
+#ifdef AF_NETLINK
+					case AF_NETLINK: af_str = "AF_NETLINK"; break;
+#endif
+					default: af_str = "unknown"; break;
+				}
+				Log(LOG_CRIT,
+				    "Socket %d is of unsupported type \"%s\" (%d), have to ignore it!",
+				    fd, af_str, addr.sin4.sin_family);
+				close(fd);
+				continue;
+			}
 
+			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(8): %s:%d.", fd,
+			    ng_ipaddr_tostr(&addr), ng_ipaddr_getport(&addr));
+			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) {
@@ -541,7 +640,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);
@@ -835,6 +939,15 @@ Conn_Handler(void)
 			Log(LOG_ALERT, "%s exiting due to fatal errors!",
 			    PACKAGE_NAME);
 			exit(1);
+		}
+
+		/* Should ngIRCd timeout when idle? */
+		if (Conf_IdleTimeout > 0 && NumConnectionsAccepted > 0
+		    && idle_t > 0 && time(NULL) - idle_t >= Conf_IdleTimeout) {
+			LogDebug("Server idle timeout reached: %d second%s. Initiating shutdown ...",
+				 Conf_IdleTimeout,
+				 Conf_IdleTimeout == 1 ? "" : "s");
+			NGIRCd_SignalQuit = true;
 		}
 	}
 
@@ -1197,6 +1310,8 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const cha
 		NumConnections--;
 	LogDebug("Shutdown of connection %d completed, %ld connection%s left.",
 		 Idx, NumConnections, NumConnections != 1 ? "s" : "");
+
+	idle_t = NumConnections > 0 ? 0 : time(NULL);
 } /* Conn_Close */
 
 
@@ -1568,6 +1683,7 @@ static void
 Account_Connection(void)
 {
 	NumConnections++;
+	idle_t = 0;
 	if (NumConnections > NumConnectionsMax)
 		NumConnectionsMax = NumConnections;
 	LogDebug("Total number of connections now %lu (max %lu).",
blob - 375f4bc1f6ab605ecbfe659367c82812b981a1a0
blob + e5bed7912f28401ab93884ee5e84ad4c0c5f7465
--- src/ngircd/log.c
+++ src/ngircd/log.c
@@ -109,7 +109,7 @@ Log_ReInit(void)
 GLOBAL void
 Log_Exit( void )
 {
-	Log(LOG_NOTICE, "%s done%s, served %lu connection%s.", PACKAGE_NAME,
+	Log(LOG_INFO, "%s done%s, served %lu connection%s.", PACKAGE_NAME,
 	    NGIRCd_SignalRestart ? " (restarting)" : "", Conn_CountAccepted(),
 	    Conn_CountAccepted() == 1 ? "" : "s");
 #ifdef SYSLOG