Package: nagios-statd-server
Version: 3.12-1
Severity: wishlist
Tags: patch ipv6 upstream

I am on a train and got fed up, so I wrote this patch series without
the ability to check the Debian BTS. Let's hope I am first.

The patch series started out because I needed nagios-statd to listen
on AF_INET6 sockets. Given that net.ipv6.bindv6only is usually set
and I would not want v6-only listening, I had to change the socket
code quite a bit so that it could listen on multiple sockets (which
required the addition of threading). And I had to replace the
dreadful gethostbyname call with getaddrinfo.

To make a long story short: nagios-statd can now listen on multiple
IPs (but only a single port). By default, that's 0.0.0.0 and ::, so
it listens on all v4 and v6 addresses.

You can specify a comma-separated list of addresses with -b to limit
the set of IPs. This is compatible with old behaviour, meaning that
a single IP just produces a single listening socket.

Additionally, the -4 and -6 switches disable IPv6/IPv4 listening
respectively.

Finally, the first patch is just for convenience and introduces
a foreground-run option.

-- System Information:
Debian Release: squeeze/sid
  APT prefers unstable
  APT policy: (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)

Kernel: Linux 2.6.35-trunk-amd64 (SMP w/4 CPU cores)
Locale: LANG=en_NZ, LC_CTYPE=en_NZ.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages nagios-statd-server depends on:
ii  python                        2.6.5-12   interactive high-level object-orie

nagios-statd-server recommends no packages.

nagios-statd-server suggests no packages.

-- no debconf information

-- 
 .''`.   martin f. krafft <madd...@d.o>      Related projects:
: :'  :  proud Debian developer               http://debiansystem.info
`. `'`   http://people.debian.org/~madduck    http://vcs-pkg.org
  `-  Debian - when you have better things to do than fixing systems
From 69743887d4d607606c545b5228e8ac8637803df9 Mon Sep 17 00:00:00 2001
From: martin f. krafft <madd...@madduck.net>
Date: Tue, 10 Aug 2010 16:56:35 +0200
Subject: [PATCH 1/5] Add -F/--foreground option to facilitate debugging

This adds a flag to prevent nagios-statd from daemonising. This makes it
easier to debug, and might be useful in other use-cases too. Passing -F
simply means that there won't be a fork() call, no session IDs will be
set, the file descriptors aren't closed, and stdin/out/err aren't
connected to /dev/null.

Signed-off-by: martin f. krafft <madd...@madduck.net>
---
 nagios-statd |   48 +++++++++++++++++++++++++++---------------------
 1 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/nagios-statd b/nagios-statd
index 688aa86..e350604 100755
--- a/nagios-statd
+++ b/nagios-statd
@@ -157,6 +157,7 @@ class ReUsingServer (SocketServer.ForkingTCPServer):
 class Initialization:
 	"Methods for interacting with user - initial code entry point."
 	def __init__(self):
+	        self.foreground = False
 		self.port = 1040
 		self.ip = ''
 		# Run this through Functions initially, to make sure the platform is supported.
@@ -166,7 +167,7 @@ class Initialization:
 	def getoptions(self):
 		"Parses command line"
 		try:
-			opts, args = getopt.getopt(sys.argv[1:], "a:b:ip:P:Vh", ["allowedhosts=","bindto=","inetd","port=","pid=","version","help"])
+			opts, args = getopt.getopt(sys.argv[1:], "a:b:Fip:P:Vh", ["allowedhosts=","bindto=","foreground","inetd","port=","pid=","version","help"])
 		except getopt.GetoptError, (msg, opt):
 			print sys.argv[0] + ": " + msg
 			print "Try '" + sys.argv[0] + " --help' for more information."
@@ -177,6 +178,8 @@ class Initialization:
 				self.allowedhosts = value.split(",")
 			elif option in ("-b","--bindto"):
 				self.ip = value
+			elif option in ("-F","--foreground"):
+				self.foreground = 1
 			elif option in ("-i","--inetd"):
 				self.runfrominetd = 1
 			elif option in ("-p","--port"):
@@ -210,15 +213,33 @@ class Initialization:
 			print "Unable to bind to port %s: %s - exiting." % (self.port, msg)
 			sys.exit(2)
 
-		# Detach from terminal
+		# Detach from terminal unless we are running in foreground
+		if not self.foreground: self.daemonise()
+
+		# Be polite and chdir to /
+		os.chdir('/')
+
+		# Set the path
+		os.environ["PATH"] = "/bin:/usr/bin:/usr/local/bin:/usr/sbin"
+
+		# Reap children automatically
+		signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+
+		# Save pid if user requested it
+		if hasattr(self,"pidfile"):
+			self.savepid(self.pidfile)
+
+		server = ReUsingServer((self.ip,self.port),NagiosStatd)
+		if hasattr(self,"allowedhosts"):
+			server.allowedhosts = self.allowedhosts
+		server.serve_forever()
+
+	def daemonise(self):
 		if os.fork() == 0:
 
 			# Make this the controlling process
 			os.setsid()
 
-			# Be polite and chdir to /
-			os.chdir('/')
-
 			# Try to close all open filehandles
 			for i in range(0,256):
 				try: os.close(i)
@@ -229,22 +250,6 @@ class Initialization:
 			sys.stdout = open('/dev/null','w')
 			sys.stderr = open('/dev/null','w')
 
-			# Set the path
-			os.environ["PATH"] = "/bin:/usr/bin:/usr/local/bin:/usr/sbin"
-
-			# Reap children automatically
-			signal.signal(signal.SIGCHLD, signal.SIG_IGN)
-
-			# Save pid if user requested it
-			if hasattr(self,"pidfile"):
-				self.savepid(self.pidfile)
-
-			# Create a forking TCP/IP server and start processing	
-			server = ReUsingServer((self.ip,self.port),NagiosStatd)
-			if hasattr(self,"allowedhosts"):
-				server.allowedhosts = self.allowedhosts
-			server.serve_forever()
-
 		# Get rid of the parent 
 		else:
 			sys.exit(0)
@@ -263,6 +268,7 @@ class Initialization:
 		print "nagios-statd daemon - remote UNIX system monitoring tool for Nagios.\n"
 		print "-a, --allowedhosts=HOSTS   Comma delimited list of IPs/hosts allowed to connect."
 		print "-b, --bindto=IP            IP address for the daemon to bind to."
+		print "-F, --foreground           Stay in foreground, do not daemonise."
 		print "-i, --inetd                Run from inetd."
 		print "-p, --port=PORT            Port to listen on."
 		print "-P, --pid=FILE             Save pid to FILE."
-- 
1.7.1

From 77747f4fb131fed83e229fd14f0fce3093230dc6 Mon Sep 17 00:00:00 2001
From: martin f. krafft <madd...@madduck.net>
Date: Tue, 10 Aug 2010 16:59:58 +0200
Subject: [PATCH 2/5] Factor out server function for better readability

Signed-off-by: martin f. krafft <madd...@madduck.net>
---
 nagios-statd |   11 +++++++----
 1 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/nagios-statd b/nagios-statd
index e350604..141f201 100755
--- a/nagios-statd
+++ b/nagios-statd
@@ -229,10 +229,7 @@ class Initialization:
 		if hasattr(self,"pidfile"):
 			self.savepid(self.pidfile)
 
-		server = ReUsingServer((self.ip,self.port),NagiosStatd)
-		if hasattr(self,"allowedhosts"):
-			server.allowedhosts = self.allowedhosts
-		server.serve_forever()
+		self.serve_forever()
 
 	def daemonise(self):
 		if os.fork() == 0:
@@ -254,6 +251,12 @@ class Initialization:
 		else:
 			sys.exit(0)
 
+	def serve_forever(self):
+		server = ReUsingServer((self.ip,self.port),NagiosStatd)
+		if hasattr(self,"allowedhosts"):
+			server.allowedhosts = self.allowedhosts
+		server.serve_forever()
+
 	def savepid(self,file):
 		try:
 			fh = open(file,"w")
-- 
1.7.1

From 3b706e10ab9d48a93499b315cd3bfa1b143b159d Mon Sep 17 00:00:00 2001
From: martin f. krafft <madd...@madduck.net>
Date: Tue, 10 Aug 2010 16:42:20 +0200
Subject: [PATCH 3/5] Replace gethostbyname with getaddrinfo

gethostbyname(2) is horrible when it comes to IPv6, but also in other
contexts. It is generally accepted that getaddrinfo(2) is the preferred
alternative.

This patch incorporates getaddrinfo(2) into the logic of checking
whether a connecting client matches an entry in the allowedhosts list.
It simplifies the logic a bit on the way.

Signed-off-by: martin f. krafft <madd...@madduck.net>
---
 nagios-statd |   13 ++++++++-----
 1 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/nagios-statd b/nagios-statd
index 141f201..500d19f 100755
--- a/nagios-statd
+++ b/nagios-statd
@@ -96,15 +96,18 @@ class NagiosStatd(SocketServer.StreamRequestHandler):
 		if hasattr(self.server,"allowedhosts") == 0:
 			return 0 
 		for i in self.server.allowedhosts:
-			if i == self.client_address[0]:  # Address is in list
-				return 0
 			try: # Do an IP lookup of host in blocked list
-				i_ip = socket.gethostbyname(i)
+				gai = socket.getaddrinfo(i, 1040) # port does not matter
+				i_ips = dict(map(lambda x: (x[4][0], None), gai)).keys()
 			except:
 				self.error = "ERROR DNS lookup of blocked host \"%s\" failed. Denying by default." % i
 				return 1
-			if i_ip != i:        # If address in list isn't an IP
-				if socket.getfqdn(i) == socket.getfqdn(self.client_address[0]):
+
+			if self.client_address[0] in i_ips:
+				return 0
+
+			if socket.getfqdn(self.client_address[0]) in \
+				map(socket.getfqdn, i_ips):
 					return 0
 		self.error = "ERROR Client is not among hosts allowed to connect."
 		return 1
-- 
1.7.1

From fefb7c9559ae87220440fd662b7c2408e4fe52f8 Mon Sep 17 00:00:00 2001
From: martin f. krafft <madd...@madduck.net>
Date: Tue, 10 Aug 2010 17:02:40 +0200
Subject: [PATCH 4/5] Enable multiple-sockets and address families

Previously, nagios-statd could only bind to 0.0.0.0 or a single IPv4. In
order to let it bind to IPv6 as well, assuming that net.ipv6.bindv6only
is set, it needs to bind to multiple sockets.

This required a bit of re-engineering, as well as the addition of
threading at the level of nagios-statd.

Signed-off-by: martin f. krafft <madd...@madduck.net>
---
 nagios-statd |   60 ++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 42 insertions(+), 18 deletions(-)

diff --git a/nagios-statd b/nagios-statd
index 500d19f..d057874 100755
--- a/nagios-statd
+++ b/nagios-statd
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-import getopt, os, sys, signal, socket, SocketServer
+import getopt, os, sys, signal, socket, SocketServer, threading
 
 class Functions:
 	"Contains a set of methods for gathering data from the server." 
@@ -155,14 +155,18 @@ class GenericHandler:
 
 
 class ReUsingServer (SocketServer.ForkingTCPServer):
-	allow_reuse_address = True
+	def __init__(self, af, socket, handler):
+		self.allow_reuse_address = True
+		self.address_family = af
+		SocketServer.ForkingTCPServer.__init__(self, socket, handler)
 
 class Initialization:
 	"Methods for interacting with user - initial code entry point."
 	def __init__(self):
 	        self.foreground = False
 		self.port = 1040
-		self.ip = ''
+		self.ips = '0.0.0.0,::'
+		self.addrfam = (socket.AF_INET, socket.AF_INET6)
 		# Run this through Functions initially, to make sure the platform is supported.
 		i = Functions()
 		del(i)
@@ -180,7 +184,7 @@ class Initialization:
 				value = value.replace(" ","")
 				self.allowedhosts = value.split(",")
 			elif option in ("-b","--bindto"):
-				self.ip = value
+				self.ips = value
 			elif option in ("-F","--foreground"):
 				self.foreground = 1
 			elif option in ("-i","--inetd"):
@@ -195,6 +199,17 @@ class Initialization:
 			elif option in ("-h","--help"):
 				self.usage()
 
+		ips = self.ips.split(',')
+		self.ips = dict(zip(self.addrfam, ([],[])))
+		for ip in ips:
+			af = socket.AF_INET if ip.find(':') < 0 else socket.AF_INET6
+			if af in self.ips:
+				self.ips[af].append(ip)
+			
+		if sum(map(len, self.ips.itervalues())) == 0:
+			print >>sys.stderr, "No sockets found to bind to."
+			sys.exit(4)
+
 	def main(self):
 		# Retrieve command line options
 		self.getoptions()
@@ -206,15 +221,17 @@ class Initialization:
 			sys.exit(0)
 
 		# Check to see if the port is available
-		try:
-			s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-			s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-			s.bind((self.ip, self.port))
-			s.close()
-			del(s)
-		except socket.error, (errno, msg):
-			print "Unable to bind to port %s: %s - exiting." % (self.port, msg)
-			sys.exit(2)
+		for af, ips in self.ips.iteritems():
+			for ip in ips:
+				try:
+					s = socket.socket(af, socket.SOCK_STREAM)
+					s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+					s.bind((ip, self.port))
+					s.close()
+					del(s)
+				except socket.error, (errno, msg):
+					print "Unable to bind to socket %s:%s: %s - exiting." % (ip, self.port, msg)
+					sys.exit(2)
 
 		# Detach from terminal unless we are running in foreground
 		if not self.foreground: self.daemonise()
@@ -255,10 +272,17 @@ class Initialization:
 			sys.exit(0)
 
 	def serve_forever(self):
-		server = ReUsingServer((self.ip,self.port),NagiosStatd)
-		if hasattr(self,"allowedhosts"):
-			server.allowedhosts = self.allowedhosts
-		server.serve_forever()
+		threads = []
+		for af, ips in self.ips.iteritems():
+			for ip in ips:
+				server = ReUsingServer(af,(ip,self.port),NagiosStatd)
+				if hasattr(self,"allowedhosts"):
+					server.allowedhosts = self.allowedhosts
+				thread = threading.Thread(target=server.serve_forever)
+				thread.start()
+				threads.append(thread)
+
+		for t in threads: t.join()
 
 	def savepid(self,file):
 		try:
@@ -273,7 +297,7 @@ class Initialization:
 		print "Usage: " + sys.argv[0] + " [OPTION]"
 		print "nagios-statd daemon - remote UNIX system monitoring tool for Nagios.\n"
 		print "-a, --allowedhosts=HOSTS   Comma delimited list of IPs/hosts allowed to connect."
-		print "-b, --bindto=IP            IP address for the daemon to bind to."
+		print "-b, --bindto=IP            Comma delimited list of IPs to bind to."
 		print "-F, --foreground           Stay in foreground, do not daemonise."
 		print "-i, --inetd                Run from inetd."
 		print "-p, --port=PORT            Port to listen on."
-- 
1.7.1

From 56d868ea109b667a64bf4e830040c1be484987e6 Mon Sep 17 00:00:00 2001
From: martin f. krafft <madd...@madduck.net>
Date: Tue, 10 Aug 2010 17:02:54 +0200
Subject: [PATCH 5/5] Allow limiting to a single address family

For those who might not want to bind to both, IPv4/IPv6 by default, this
patch adds -4/-6 (--v4-only/--v6-only) switches in similar spirit to
other tools to limit binding to sockets of the respective address
family.

Signed-off-by: martin f. krafft <madd...@madduck.net>
---
 nagios-statd |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)

diff --git a/nagios-statd b/nagios-statd
index d057874..2683cc3 100755
--- a/nagios-statd
+++ b/nagios-statd
@@ -174,7 +174,7 @@ class Initialization:
 	def getoptions(self):
 		"Parses command line"
 		try:
-			opts, args = getopt.getopt(sys.argv[1:], "a:b:Fip:P:Vh", ["allowedhosts=","bindto=","foreground","inetd","port=","pid=","version","help"])
+			opts, args = getopt.getopt(sys.argv[1:], "a:b:64Fip:P:Vh", ["allowedhosts=","bindto=","v6only","v4only","foreground","inetd","port=","pid=","version","help"])
 		except getopt.GetoptError, (msg, opt):
 			print sys.argv[0] + ": " + msg
 			print "Try '" + sys.argv[0] + " --help' for more information."
@@ -185,6 +185,10 @@ class Initialization:
 				self.allowedhosts = value.split(",")
 			elif option in ("-b","--bindto"):
 				self.ips = value
+			elif option in ("-6","--v6oply"):
+				self.addrfam = (socket.AF_INET6,)
+			elif option in ("-4","--v4oply"):
+				self.addrfam = (socket.AF_INET,)
 			elif option in ("-F","--foreground"):
 				self.foreground = 1
 			elif option in ("-i","--inetd"):
@@ -298,6 +302,8 @@ class Initialization:
 		print "nagios-statd daemon - remote UNIX system monitoring tool for Nagios.\n"
 		print "-a, --allowedhosts=HOSTS   Comma delimited list of IPs/hosts allowed to connect."
 		print "-b, --bindto=IP            Comma delimited list of IPs to bind to."
+		print "-4, --v4only               Only bind to IPv4 sockets."
+		print "-6, --v6only               Only bind to IPv6 sockets."
 		print "-F, --foreground           Stay in foreground, do not daemonise."
 		print "-i, --inetd                Run from inetd."
 		print "-p, --port=PORT            Port to listen on."
-- 
1.7.1

Attachment: digital_signature_gpg.asc
Description: Digital signature (see http://martin-krafft.net/gpg/)

Reply via email to