Browse Source

Starting to rework amavisvt to use a daemon

tags/0.4_pre20160724
Johann Schmitz 3 years ago
parent
commit
4c65f1909c
Signed by: Johann Schmitz <johann@j-schmitz.net> GPG Key ID: A084064277C501ED
4 changed files with 194 additions and 1 deletions
  1. 74
    0
      amavisvt/amavisvtd.py
  2. 14
    0
      amavisvt/client.py
  3. 100
    0
      amavisvt/daemon.py
  4. 6
    1
      amavisvt_example.cfg

+ 74
- 0
amavisvt/amavisvtd.py View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
import threading
import signal
import logging
from argparse import ArgumentParser
from logging.handlers import SysLogHandler

import sys

from amavisvt.client import Configuration

from amavisvt.daemon import AmavisVTDaemon

logger = logging.getLogger(__file__)

if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('-v', '--verbose', action='count', help='Increase verbosity', default=2)
parser.add_argument('-d', '--debug', action='store_true', default=False, help='Send verbose log messages to stdout too')
parser.add_argument('-s', '--socket', help='Socket path')

args = parser.parse_args()

logging.basicConfig(
level=logging.FATAL - (10 * args.verbose),
format='%(asctime)s %(levelname)-7s [%(threadName)s] %(message)s',
)

logger = logging.getLogger()

if not args.debug:
for h in logger.handlers:
h.setLevel(logging.ERROR)

handler = SysLogHandler(address='/dev/log')
formatter = logging.Formatter('amavisvt: %(threadName)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("Starting up")
daemon = None
shutdown_sig = threading.Event()

def _sig_handler(sig, frame):
logger.debug("Handler received signal %s", sig)
if sig in (signal.SIGTERM, signal.SIGKILL, signal.SIGINT):
shutdown_sig.set()
daemon.stop()

signal.signal(signal.SIGUSR1, _sig_handler)
signal.signal(signal.SIGINT, _sig_handler)

error = False

try:
config = Configuration()
daemon = AmavisVTDaemon(config, socket_path=args.socket)
daemon.run_and_wait()

while True:
shutdown_sig.wait(5)
if shutdown_sig.is_set():
break
except KeyboardInterrupt:
pass
error = True
except:
logger.exception("Server error")
error = True
finally:
if daemon:
daemon.stop()

sys.exit(int(error))

+ 14
- 0
amavisvt/client.py View File

@@ -49,6 +49,8 @@ class Configuration(ConfigParser):
for k in [k for k in defaults.keys() if not defaults[k]]:
del defaults[k]

defaults.setdefault('socket-path', '/run/amavisvtd.sock')

defaults.setdefault('positive-expire', str(21 * 86400))
defaults.setdefault('negative-expire', str(12 * 3600))
defaults.setdefault('unknown-expire', str(12 * 3600))
@@ -81,6 +83,18 @@ class Configuration(ConfigParser):
def apikey(self):
return self.get('DEFAULT', 'api-key')

@property
def socket_path(self):
return self.get('daemon', 'socket-path')

@property
def socket_permissions(self):
return self.get('daemon', 'socket-perm')

@property
def socket_group(self):
return self.get('daemon', 'socket-group')

@property
def positive_expire(self):
return int(self.get('DEFAULT', 'positive-expire'))

+ 100
- 0
amavisvt/daemon.py View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
import socket
import threading
import logging
import os
import re

logger = logging.getLogger(__file__)

try:
import SocketServer as socketserver
except ImportError:
import socketserver

BUFFER_SIZE = 4096


class ThreadedRequestHandler(socketserver.BaseRequestHandler):

def handle(self):
data = self.request.recv(1024)
command, args = self.parse_command(data)

if command:
logger.info("Handling '%s' command", command)

if command == "PING":
self.send_response('PONG')
else:
self.send_response("ERROR: Unknown command '%s'" % command)

def parse_command(self, data):
m = re.match(r"^([\w\d]+)(?:\s?(.*))", data, re.IGNORECASE)
if m:
return (m.group(1), m.group(2))
return None, None

def send_response(self, msg):
self.request.sendall(msg)


class ThreadedUnixSocketServer(socketserver.ThreadingMixIn, socketserver.UnixStreamServer):
pass


class AmavisVTDaemon(object):

def __init__(self, config, socket_path=None):
self.server = None
self.server_thread = None
self.config = config
self.socket_path = socket_path or config.socket_path

def run_and_wait(self):
if os.path.exists(self.socket_path):
if self.is_socket_working(self.socket_path):
raise Exception("Cannot use %s - found working socket" % self.socket_path)
logger.info("Removing stale socket path %s", self.socket_path)
os.remove(self.socket_path)

try:
self.server = ThreadedUnixSocketServer(self.socket_path, ThreadedRequestHandler)

# Start a thread with the server -- that thread will then start one
# more thread for each request
self.server_thread = threading.Thread(target=self.server.serve_forever, name='master')
# Exit the server thread when the main thread terminates
self.server_thread.daemon = True
self.server_thread.start()
except:
self.stop()

def is_socket_working(self, socket_path):
"""Tests whether the socket in socket_path is working by sending the PING command"""
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(socket_path)
sock.sendall("PING")
data = sock.recv(BUFFER_SIZE)

if data and data.strip() == "PONG":
return True

logger.info("Received garbage from socket %s: '%s'", socket_path, data)
return False
except Exception as ex:
logger.error("Socket %s isn't working: %s", socket_path, ex)
return False

def stop(self):
"""Stops the daemon."""

if not self.server:
return

logger.info("Shutting down")
self.server.shutdown()
self.server.server_close()
self.server = None
os.remove(self.socket_path)

+ 6
- 1
amavisvt_example.cfg View File

@@ -56,4 +56,9 @@
# automatically report files to Virustotal if filename pattern matches (default: false)
# WARNING: This will send the actual content of the attachments to Virustotal. DO NOT enable this setting if
# a legitimate attachment may be caught by the pattern detection!
# auto-report = false
# auto-report = false


[daemon]
# Socket path
# socket-path = /run/amavisvtd.sock

Loading…
Cancel
Save