summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlexander Barton <alex@barton.de>2013-02-10 20:43:56 +0100
committerAlexander Barton <alex@barton.de>2013-02-10 20:43:56 +0100
commit628c14d65686c4c848a17381b8ef61c78dbcf405 (patch)
tree3f2d891c1045651c04df7086976054153dc3a6e7
parentac32d07aaff0e7a1c4a544353dadbf397859d8f9 (diff)
parent2cb7023e2835e9c29bec5a6d3a911894c847005d (diff)
downloadngircd-628c14d65686c4c848a17381b8ef61c78dbcf405.tar.gz
ngircd-628c14d65686c4c848a17381b8ef61c78dbcf405.zip
Merge branch 'systemd'
* systemd:
  ngircd.sock: explicitely bind to IPv4 and IPv6 addresses
  Show address and port of sockets passed-in by systemd(8)
  Check type of sockets passed-in by systemd(8)
  Adjust severity levels of some log messages
  New configuration option "IdleTimeout": exit daemon when idle
  Implement support for systemd(8) "socket activation"
  contrib/README: add more files
-rw-r--r--contrib/Makefile.am3
-rw-r--r--contrib/README13
-rw-r--r--contrib/ngircd.socket11
-rw-r--r--doc/sample-ngircd.conf.tmpl7
-rw-r--r--man/ngircd.conf.5.tmpl8
-rw-r--r--src/ngircd/conf.c15
-rw-r--r--src/ngircd/conf.h5
-rw-r--r--src/ngircd/conn.c120
-rw-r--r--src/ngircd/log.c2
9 files changed, 173 insertions, 11 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 09b43a68..6d16f7ca 100644
--- a/contrib/Makefile.am
+++ b/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
diff --git a/contrib/README b/contrib/README
index 1aebd1eb..2d639e66 100644
--- a/contrib/README
+++ b/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.
 
diff --git a/contrib/ngircd.socket b/contrib/ngircd.socket
new file mode 100644
index 00000000..1c0cc004
--- /dev/null
+++ b/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
diff --git a/doc/sample-ngircd.conf.tmpl b/doc/sample-ngircd.conf.tmpl
index 1c3998ad..822fd5d4 100644
--- a/doc/sample-ngircd.conf.tmpl
+++ b/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
diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl
index 859c6a8a..e5485dbf 100644
--- a/man/ngircd.conf.5.tmpl
+++ b/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
@@ -170,6 +170,12 @@ should be safe, but it is wise to double-check :-)
 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
 allowed to accept (0: unlimited). Default: 0.
diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c
index 929ab054..e46dcfee 100644
--- a/src/ngircd/conf.c
+++ b/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, array *Destination)
 
 	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, array *Destination)
 
 		/* 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"))
diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h
index c203b570..bbf4f36c 100644
--- a/src/ngircd/conf.h
+++ b/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;
 
diff --git a/src/ngircd/conn.c b/src/ngircd/conn.c
index 14d337b9..3a430428 100644
--- a/src/ngircd/conn.c
+++ b/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,6 +82,8 @@
 #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 ));
@@ -119,6 +121,42 @@ 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
@@ -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);
@@ -836,6 +940,15 @@ Conn_Handler(void)
 			    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;
+		}
 	}
 
 	if (NGIRCd_SignalQuit)
@@ -1197,6 +1310,8 @@ Conn_Close( CONN_ID Idx, const char *LogMsg, const char *FwdMsg, bool InformClie
 		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).",
diff --git a/src/ngircd/log.c b/src/ngircd/log.c
index 375f4bc1..e5bed791 100644
--- a/src/ngircd/log.c
+++ b/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