diff --git a/dkimpy_milter/__init__.py b/dkimpy_milter/__init__.py index bcdf2a7..4c64891 100644 --- a/dkimpy_milter/__init__.py +++ b/dkimpy_milter/__init__.py @@ -354,7 +354,18 @@ def main(): Milter.set_flags(Milter.CHGHDRS + Milter.ADDHDRS) miltername = 'dkimpy-filter' socketname = milterconfig.get('Socket') - own_socketfile(milterconfig) + if socketname is None: + if int(os.environ.get('LISTEN_PID', '0')) == os.getpid(): + lfds = os.environ.get('LISTEN_FDS') + if lfds is not None: + if lfds != '1': + syslog.syslog('LISTEN_FDS is set to "{0}", but we only know how to deal with "1", ignoring it'. + format(lfds)) + else: + socketname = 'fd:3' + if socketname is None: + socketname = 'local:/var/run/dkimpy-milter/dkimpy-milter.sock' + own_socketfile(milterconfig, socketname) drop_privileges(milterconfig) sys.stdout.flush() Milter.runmilter(miltername, socketname, 240) diff --git a/dkimpy_milter/config.py b/dkimpy_milter/config.py index bf6551a..3e2c736 100644 --- a/dkimpy_milter/config.py +++ b/dkimpy_milter/config.py @@ -39,8 +39,8 @@ defaultConfigData = { 'SyslogFacility': 'mail', 'UMask': 0o07, 'Mode': 'sv', - 'Socket': 'local:/var/run/dkimpy-milter/dkimpy-milter.sock', - 'PidFile': '/var/run/dkimpy-milter/dkimpy-milter.pid', + 'Socket': None, + 'PidFile': None, 'UserID': 'dkimpy-milter', 'Canonicalization': 'relaxed/simple', 'InternalHosts': '127.0.0.1', diff --git a/dkimpy_milter/util.py b/dkimpy_milter/util.py index 17857d6..56a57a4 100644 --- a/dkimpy_milter/util.py +++ b/dkimpy_milter/util.py @@ -115,43 +115,49 @@ def write_pid(milterconfig): """Write PID in pidfile. Will not overwrite an existing file.""" import os import syslog - if not os.path.isfile(milterconfig.get('PidFile')): + pidfile = milterconfig.get('PidFile') + if pidfile is None: + return + if not os.path.isfile(pidfile): pid = str(os.getpid()) try: - f = open(milterconfig.get('PidFile'), 'w') + f = open(pidfile, 'w') except IOError as e: if str(e)[:35] == '[Errno 2] No such file or directory': - piddir = milterconfig.get('PidFile').rsplit('/', 1)[0] + piddir = pidfile.rsplit('/', 1)[0] os.mkdir(piddir) user, group = user_group(milterconfig.get('UserID')) os.chown(piddir, user, group) - f = open(milterconfig.get('PidFile'), 'w') + f = open(pidfile, 'w') if milterconfig.get('Syslog'): syslog.syslog('PID dir created: {0}'.format(piddir)) else: if milterconfig.get('Syslog'): syslog.syslog('Unable to write pidfle {0}. IOError: {1}' - .format(milterconfig.get('PidFile'), e)) + .format(pidfile, e)) raise f.write(pid) f.close() user, group = user_group(milterconfig.get('UserID')) - os.chown(milterconfig.get('PidFile'), user, group) + os.chown(pidfile, user, group) else: if milterconfig.get('Syslog'): syslog.syslog('Unable to write pidfle {0}. File exists.' - .format(milterconfig.get('PidFile'))) + .format(pidfile)) raise RuntimeError('Unable to write pidfle {0}. File exists.' - .format(milterconfig.get('PidFile'))) + .format(pidfile)) return pid -def own_socketfile(milterconfig): +def own_socketfile(milterconfig, sockname=None): """If socket is Unix socket, chown to UserID before dropping privileges""" import os user, group = user_group(milterconfig.get('UserID')) offset = None - sockname = milterconfig.get('Socket') + if sockname is None: + sockname = milterconfig.get('Socket') + if sockname is None: + return if sockname[:1] == '/': offset = 0 elif sockname[:6] == "local:": diff --git a/man/dkimpy-milter.conf.5 b/man/dkimpy-milter.conf.5 index a7e5d31..bb2a019 100644 --- a/man/dkimpy-milter.conf.5 +++ b/man/dkimpy-milter.conf.5 @@ -338,7 +338,7 @@ will be checked. [PeerList NOT IMPLEMENTED - included for reference only] .TP .I PidFile (string) Specifies the path to a file that should be created at process start -containing the process ID. +containing the process ID. If not specified, no such file will be created. .TP .I Selector (string) diff --git a/system/socket-activation/README.md b/system/socket-activation/README.md new file mode 100644 index 0000000..b4410ac --- /dev/null +++ b/system/socket-activation/README.md @@ -0,0 +1,32 @@ +This directory contains example systemd unit files for running a +supervised, socket-activated instance of dkimpy-milter. + +There are several advantages of using socket activation: + +- dkimpy-milter never runs with elevated privileges, they are dropped + before any dkimpy-milter code is executed. + +- The socket is opened before dkimpy-milter runs. This means that + clients can connect() to the socket immediately. So even if there + is a delay in dkimpy-milter startup, or in libmilter itself, the + connection will not fail. + +- You can set the privileges of a listening Unix-domain socket by an + override of ListenGroup= in dkimpy-milter.socket (see + systemd.unit(5) for how to override). This lets you control who has + access to the daemon with finer granularity than is available with + dkimpy-milter on its own. + +- dkimpy-milter will not consume system resources if it is not used. + +- A fully-supervised dkimpy-milter needs no PIDFile, UMask, UserID, or + Socket configuation. This eliminates common race conditions and + startup failures, and simplifies the resulting configuration file. + +There is one downside to using socket activation: + +- it will only work on systems where libmilter can support connection + strings like "fd:3". This has been supported on Debian and derived + systems since sendmail 8.14.4-6 (before Debian Jessie, in early + 2014), see for example: + https://sources.debian.org/src/sendmail/8.15.2-8/debian/patches/socket_activation.patch/ diff --git a/system/socket-activation/dkimpy-milter.service b/system/socket-activation/dkimpy-milter.service new file mode 100644 index 0000000..2116bb7 --- /dev/null +++ b/system/socket-activation/dkimpy-milter.service @@ -0,0 +1,11 @@ +[Unit] +Description=DKIMpy Milter +Documentation=man:dkimpy-milter(8) man:dkimpy-milter.conf(5) +Requires=dkimpy-milter.socket + +[Service] +ExecStart=/usr/bin/dkimpy-milter /etc/dkimpy-milter.conf +User=dkimpy-milter + +[Install] +Also=dkimpy-milter.socket diff --git a/system/socket-activation/dkimpy-milter.socket b/system/socket-activation/dkimpy-milter.socket new file mode 100644 index 0000000..d4338a3 --- /dev/null +++ b/system/socket-activation/dkimpy-milter.socket @@ -0,0 +1,12 @@ +[Unit] +Description=DKIMpy Milter socket +Documentation=man:dkimpy-milter(8) man:dkimpy-milter.conf(5) + +[Socket] +ListenStream=/run/dkimpy-milter/dkimpy-milter.sock +SocketMode=0660 +# override SocketGroup to grant access to members of another system group: +SocketGroup=dkimpy-milter + +[Install] +WantedBy=sockets.target