"""A minimalistic syslogd.

2000-06-09/bb:  created
2000-06-12/bb:  added constants from syslog.h,
                config options for time format, stop string
"""

import sys, string, time, socket, re
from SocketServer import *

# constants from syslog.h
LOG_EMERG       = 0
LOG_ALERT       = 1
LOG_CRIT        = 2
LOG_ERR         = 3
LOG_WARNING     = 4
LOG_NOTICE      = 5
LOG_INFO        = 6
LOG_DEBUG       = 7

LOG_PRIMASK     = 0x07

def LOG_PRI(p): return p & LOG_PRIMASK
def LOG_MAKEPRI(fac, pri): return fac << 3 | pri

LOG_KERN        = 0 << 3
LOG_USER        = 1 << 3
LOG_MAIL        = 2 << 3
LOG_DAEMON      = 3 << 3
LOG_AUTH        = 4 << 3
LOG_SYSLOG      = 5 << 3
LOG_LPR         = 6 << 3
LOG_NEWS        = 7 << 3
LOG_UUCP        = 8 << 3
LOG_CRON        = 9 << 3
LOG_AUTHPRIV    = 10 << 3
LOG_FTP         = 11 << 3
LOG_LOCAL0      = 16 << 3
LOG_LOCAL1      = 17 << 3
LOG_LOCAL2      = 18 << 3
LOG_LOCAL3      = 19 << 3
LOG_LOCAL4      = 20 << 3
LOG_LOCAL5      = 21 << 3
LOG_LOCAL6      = 22 << 3
LOG_LOCAL7      = 23 << 3

LOG_NFACILITIES = 24
LOG_FACMASK     = 0x03F8
def LOG_FAC(p): return (p & LOG_FACMASK) >> 3

def LOG_MASK(pri): return 1 << pri
def LOG_UPTO(pri): return (1 << pri + 1) - 1
# end syslog.h

def LOG_UNPACK(p): return (p & LOG_FACMASK, LOG_PRI(p))

fac_values = {}     # mapping of facility constants to their values
fac_names = {}      # mapping of values to names
pri_values = {}
pri_names = {}
for i, j in globals().items():
    if i[:4] == 'LOG_' and type(j) == type(0):
        if j > LOG_PRIMASK or i == 'LOG_KERN':
            n, v = fac_names, fac_values
        else:
            n, v = pri_names, pri_values
        i = i[4:].lower()
        v[i] = j
        n[j] = i
del i, j, n, v


conf_pat = re.compile(r'''
    ^\s*            # leading space        
    ([\w*]+)        # facility
    \.([=!]?)       # separator plus optional modifier
    ([\w*]+)        # priority
    \s+
    ([|@]?)         # target type fifo/remote host
    (\S+)           # target
    ''', re.VERBOSE)


class InterruptibleServer:
    "Mix-in class for {TCP,UDP}Server that allows to stop the service"

    def serve(self):
        "Run the service until it is stopped with the stop() method."
        self.running = 1
        while self.running:
            try:
                self.handle_request()
            except KeyboardInterrupt:
                self.stop()

    def stop(self):             # called by handle_request()
        "Stop the service."
        self.running = 0


SYSLOG_PORT = socket.getservbyname('syslog', 'udp')

class Syslogd(ThreadingUDPServer, InterruptibleServer):

    def __init__(self, addr='', port=SYSLOG_PORT, pri=LOG_DEBUG, timefmt=None, magic=None):

        UDPServer.__init__(self, (addr, port), None)

        self.hostmap = {}       # client address/name mapping
        self.priority = pri
        self.timefmt = timefmt or '%b %d %H:%M:%S'
        self.stop_magic = magic or '_stop'

        print 'syslogd started'

    def finish_request(self, (msg, sock), client_address):

        # get the time when the message arrives
        tm = time.time()

        # Messages are in the form "[[float]]<int>message text...\n"
        # The [[float]] and <int> parts and the newline are optional.
        # The float denotes the original event time in case the
        # event comes through a gateway from a client that is not
        # syslog-compatible (Windows NT comes to mind :-).

        # extract original event time, if present
        if msg[:2] == '[[':
            pos = msg.find(']]')
            try:
                tm = float(msg[2:pos])
            except:
                pass
            msg = msg[pos+2:]   # use rest of message

        # extract log facility and priority, if present
        if msg[:1] == '<':
            pos = msg.find('>')
            fac, pri = LOG_UNPACK(int(msg[1:pos]))
            msg = msg[pos+1:]
        elif msg and msg[0] < ' ':
            fac, pri = LOG_KERN, ord(msg[0])
            msg = msg[1:]
        else:
            fac, pri = None, None

        # check if we can discard this message
        if pri is not None and pri > self.priority:
            return

        # build the client address/name mapping
        client = client_address[0]
        try:
            host = self.hostmap[client]
        except KeyError:
            try:
                host = socket.gethostbyaddr(client)[0]
                host = host.split('.')[0]       # keep only the host name
            except socket.error:
                host = client
            self.hostmap[client] = host

        # print the message
        tm = time.strftime(self.timefmt, time.localtime(tm))
        if 0: # fac is not None and pri is not None:
            fp = ' <%s,%s>' % (fac_names[fac], pri_names[pri])
        else:
            fp = ''
        sys.stdout.write(tm + ' ' + host + fp + ': ' + msg.strip() + '\n')

        # stop the server if the magic stop string appears in the message
        if msg.find(self.stop_magic) >= 0:
            print 'stopped.'
            self.stop()


if __name__ == '__main__':
    Syslogd(timefmt='%H:%M:%S').serve()