#!/usr/bin/python -tt
"""
Description will eventually go here.
"""
##
# Copyright (C) 2003 by Duke University
#
# 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 the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
# $Id: logins_mod.py,v 1.21.2.7 2005/03/31 19:37:42 icon Exp $
#
# @Author Konstantin Ryabitsev <icon@linux.duke.edu>
# @version $Date: 2005/03/31 19:37:42 $
#

import sys
import re

sys.path.insert(0, '../py/')
from epylog import Result, InternalModule

class logins_mod(InternalModule):
    def __init__(self, opts, logger):
        InternalModule.__init__(self)
        self.logger = logger
        self.opts = opts
        rc = re.compile

        self.ignore     = 0
        self.open       = 1
        self.failure    = 2
        self.root_open    = 11
        self.root_failure = 12
        ##
        # Ignore crond(pam_unix), to lessen the noise
        #
        self.pam_ignore = ['crond']
        self.xinetd_ignore = []

        ##
        # PAM reports
        #
        pam_map = {
            rc('\(pam_unix\)\S*:.*authentication\s*failure'): self.pam_failure,
            rc('\(pam_unix\)\S*:\ssession\sopened\sfor'): self.pam_open,
            rc('\(pam_unix\)\S*:\sbad\susername'): self.pam_baduser,
            rc('\(pam_unix\)\S*:\sauth\scould\snot'): self.pam_chelper_failure,
            rc('pam_krb5\S*:\s\S+\ssucceeds\sfor'): self.pam_krb5_open,
            rc('pam_krb5\S*:\s\S+\sfails\sfor'): self.pam_krb5_failure
            }
        ##
        # XINETD reports
        #
        xinetd_map = {
            rc('xinetd\S*: START:'): self.xinetd_start
            }
        ##
        # SSH reports
        #
        sshd_map = {
            rc('sshd\[\S*: Accepted'): self.sshd_open,
            rc('sshd\[\S*: Failed'): self.sshd_failure
            }
        ##
        # IMAPD and IPOP3D
        #
        uw_imap_map = {
            rc('imapd\[\S*: Login\sfail'): self.uw_imap_failure,
            rc('imapd\[\S*: Authenticated\suser'): self.uw_imap_open,
            rc('imapd\[\S*: Login\suser'): self.uw_imap_open,
            rc('ipop3d\[\S*: Login\sfail'): self.uw_imap_failure,
            rc('ipop3d\[\S*: Login\suser'): self.uw_imap_open,
            rc('ipop3d\[\S*: Auth\suser'): self.uw_imap_open
            }
        ##
        # IMP
        #
        imp_map = {
            rc('IMP\[\S*: Login'): self.imp2_open,
            rc('IMP\[\S*: FAILED'): self.imp2_failure,
            rc('HORDE\[\S*\s*\[imp\] Login'): self.imp3_open,
            rc('HORDE\[\S*\s*\[imp\] FAILED'): self.imp3_failure
            }
        ##
        # DOVECOT
        #
        dovecot_map = {
            rc('imap-login:\sLogin:\s'): self.dovecot_open,
            rc('imap-login:\sAborted\slogin\s'): self.dovecot_failure
            }
        ##
        # Courier-IMAP
        #
        courier_map = {
            rc('\sLOGIN,\suser=\S+,\sip=\[\S+\]'): self.courier_open,
            rc('\sLOGIN FAILED,\sip=\[\S+\]'): self.courier_failure
            }
        ##
        # Cyrus-IMAP
        #
        cyrus_map = {
            rc('imapd\[\S*: login:'): self.cyrus_open,
            rc('pop3d\[\S*: login:'): self.cyrus_open,
            rc('imapd\[\S*: badlogin:'): self.cyrus_failure,
            rc('pop3d\[\S*: badlogin:'): self.cyrus_failure
            }
        ##
        # Qpopper
        #
        qpopper_map = {
            rc('apop\[\S*:\s\S+\sat\s.*\s\(\S*\):\s-ERR\s\[AUTH\]'): self.qpopper_failure,
            rc('apop\[\S*:\s\S+\sat\s.*\s\(\S*\):\s-ERR\s\[IN-USE\]'): self.qpopper_failure,
            rc('apop\[\S*:\s\(\S*\)\sPOP\slogin'): self.qpopper_open
            }

        ##
        # ProFTPD
        #
        proftpd_map = {
            rc('proftpd\[\S*:.*USER.*Login successful'): self.proftpd_open,
            rc('proftpd\[\S*:.*no such user found'): self.proftpd_failure,
            rc('proftpd\[\S*:.*Login failed'): self.proftpd_failure
        }

        regex_map = {}
        if opts.get('enable_pam', "1") != "0": regex_map.update(pam_map)
        if opts.get('enable_xinetd', "1") != "0": regex_map.update(xinetd_map)
        if opts.get('enable_sshd', "1") != "0": 
            regex_map.update(sshd_map)
            self.pam_ignore.append('sshd')
        if opts.get('enable_uw_imap', "0") != "0":
            regex_map.update(uw_imap_map)
            self.xinetd_ignore.append('imaps')
        if opts.get('enable_imp', "0") != "0": regex_map.update(imp_map)
        if opts.get('enable_dovecot',"0") != "0": regex_map.update(dovecot_map)
        if opts.get('enable_courier',"0") != "0": regex_map.update(courier_map)
        if opts.get('enable_cyrus', "0") != "0": regex_map.update(cyrus_map)
        if opts.get('enable_qpopper',"0") != "0": regex_map.update(qpopper_map)
        if opts.get('enable_proftpd',"0") != "0":
            regex_map.update(proftpd_map)
            self.pam_ignore.append('ftp')
            self.xinetd_ignore.append('ftp')

        self.safe_domains = []
        safe_domains = opts.get('safe_domains', '.*')
        self.systems_collapse = int(opts.get('systems_collapse', '10'))
        for domain in safe_domains.split(','):
            domain = domain.strip()
            if domain:
                try:
                    domain_re = rc(domain)
                    self.safe_domains.append(domain_re)
                except:
                    logger.put(0, 'Error compiling domain regex: %s' % domain)
                    logger.put(0, 'Check config for Logins module!')

        self.regex_map = regex_map
        
        self.pam_service_re = rc('(\S+)\(pam_unix\)')
        self.pam_failure_re = rc('.*\slogname=(\S*).*\srhost=(\S*)')
        self.pam_failure_user_re = rc('\suser=(\S*)')
        self.pam_open_re = rc('.*for user (\S+) by\s(\S*)\s*\(uid=(\S+)\)')
        self.pam_failure_more_re = rc('(\S+)\smore\sauthentication\sfailures')
        self.pam_baduser_re = rc('\sbad\susername\s\[(.*)\]')
        self.pam_chelper_re = rc('password\sfor\s\[(.*)\]')
        self.pam_krb5_re = rc("^(\S+?)\[*\d*\]*:\spam_krb5\S*:\sauth.*\sfor\s`(\S+)'")
        self.xinetd_start_re = rc('START:\s*(\S*)\s')
        self.sshd_open_ruser_re = rc('Accepted\s(\S*)\sfor\s(\S*)\sfrom\s(\S*)\sport\s\d*\sruser\s(\S*)\s*(\S*)')
        self.sshd_open_re = rc('Accepted\s(\S*)\sfor\s(\S*)\sfrom\s(\S*)\sport\s\d+\s*(\S*)')
        self.sshd_fail_re = rc('Failed\s(\S*)\sfor.*\s(\S+)\sfrom\s(\S*)\sport\s\d*\s*(\S*)')
        self.uw_imap_fail_re = rc('auth=(.*)\shost=.*\[(\S*)\]')
        self.uw_imap_open_re = rc('user=(.*)\shost=.*\[(\S*)\]')
        self.uw_imap_service_re = rc('^(\S*)\[\d*\]:')
        self.dovecot_open_re = rc('Login:\s(\S+)\s\[(\S+)\]')
        self.dovecot_failure_re = rc('Aborted\slogin\s\[(\S+)\]')
        self.courier_open_re = rc('^(\S+?):.*\suser=(\S+),\sip=\[(\S+)\]')
        self.courier_failure_re = rc('^(\S+?):.*,\sip=\[(\S+)\]')
        self.imp2_open_re = rc('Login\s(\S*)\sto\s(\S*):\S*\sas\s(\S*)')
        self.imp2_fail_re = rc('FAILED\s(\S*)\sto\s(\S*):\S*\sas\s(\S*)')
        self.imp3_open_re = rc('success\sfor\s(\S*)\s\[(\S*)\]\sto\s\{(\S*):')
        self.imp3_fail_re = rc('LOGIN\s(\S*)\sto\s(\S*):\S*\sas\s(\S*)')
        self.proftpd_open_re = rc('proftpd\[\S*:.*\[(\S+)\].*USER\s(.*):\sLogin\ssuccessful')
        self.proftpd_failure_re = rc('proftpd\[\S*:.*\[(\S+)\].*USER\s([^:\s]*)')
        self.qpopper_open_re = rc('user "(.*)" at \(.*\)\s(\S*)')
        self.qpopper_fail_re = rc(':\s(.*)\sat\s(\S*)')
        self.cyrus_open_re = rc('login:.*\[(\S*)\]\s(\S*)\s')
        self.cyrus_fail_re = rc('badlogin:.*\[(\S*)\]\s\S\s(\S*)\sSASL')
        self.cyrus_service_re = rc('^(\S*)\[\d*\]:')
        
        self.sshd_methods = {'password': 'pw',
                             'publickey': 'pk',
                             'rhosts-rsa': 'rsa',
                             'rsa': 'rsa',
                             'hostbased': 'host',
                             'none': 'none'}

        self.report_wrap = '<table width="100%%" rules="cols" cellpadding="2">%s</table>'
        self.subreport_wrap = '<tr><th align="left" colspan="3"><h3>%s</h3></th></tr>\n%s\n'

        self.root_failures_title = '<font color="red">ROOT FAILURES</font>'
        self.root_logins_title = '<font color="blue">ROOT Logins</font>'
        self.user_failures_title = '<font color="red">User Failures</font>'
        self.user_logins_title = '<font color="blue">User Logins</font>'

        self.untrusted_host = '%(system)s::<font color="red">%(rhost)s</font>'

        self.flip = ' bgcolor="#dddddd"'

        self.line_rep = '<tr%s><td valign="top" width="15%%">%s</td><td valign="top" width="15%%">%s</td><td width="70%%">%s</td></tr>\n'
        self.collapsed_rep = '%s <font color="red">[%s more skipped]</font>'

    ##
    # LINE MATCHING ROUTINES
    #
    def general_ignore(self, linemap):
        restuple = (self.ignore, None, None, None)
        return {restuple: 1}

    def pam_failure(self, linemap):
        action = self.failure
        self.logger.put(5, 'pam_failure invoked')
        system, message, mult = self.get_smm(linemap)
        service = self._get_pam_service(message)
        mo = self.pam_failure_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam failure string: %s' % message)
            return None
        byuser, rhost = mo.groups()
        mo = self.pam_failure_user_re.search(message)
        if mo: user = mo.group(1)
        else: user = 'unknown'
        if ((service == 'xscreensaver' and user == 'root')
            or service == 'sshd' or service == 'imap'):
            ##
            # xscreensaver will always fail as root.
            # SSHD is better handled by sshd part itself.
            # Imap failures are caught by imap routines.
            # Ignore these.
            #
            result = self.general_ignore(linemap)
            return result
        mo = self.pam_failure_more_re.search(message)
        if mo: mult += int(mo.group(1))
        restuple = self._mk_restuple(action, system, service, user,
                                     byuser, rhost)
        return {restuple: mult}

    def pam_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        service = self._get_pam_service(message)
        if service in self.pam_ignore:
            ##
            # the service will do a much better job.
            #
            result = self.general_ignore(linemap)
            return result
        mo = self.pam_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam open string: %s' % message)
            return None
        user, byuser, byuid = mo.groups()
        if byuser == '': byuser = self.getuname(int(byuid))
        restuple = self._mk_restuple(action, system, service, user, byuser, '')
        return {restuple: mult}

    def pam_baduser(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.pam_baduser_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam bad user string: %s' % message)
            return None
        user = mo.group(1)
        service = self._get_pam_service(message)
        restuple = self._mk_restuple(action, system, service, user, '', '')
        return {restuple: mult}

    def pam_chelper_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.pam_chelper_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam console helper string: %s' % message)
            return None
        user = mo.group(1)
        service = self._get_pam_service(message)
        restuple = self._mk_restuple(action, system, service, user, '', '')
        return {restuple: mult}

    def pam_krb5_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.pam_krb5_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam_krb5 succeeds line: %s' % message)
            return None
        service = mo.group(1)
        user = mo.group(2)
        if service == 'sshd':
            ##
            # sshd_open will do a much better job.
            #
            result = self.general_ignore(linemap)
            return result
        restuple = self._mk_restuple(action, system, service, user, '', '')
        return {restuple: mult}

    def pam_krb5_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.pam_krb5_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd pam_krb5 failure line: %s' % message)
            return None
        service = mo.group(1)
        user = mo.group(2)
        if ((service == 'xscreensaver' and user == 'root')
            or service == 'sshd' or service == 'imap'):
            ##
            # xscreensaver will always fail as root.
            # SSHD is better handled by sshd part itself.
            # Imap failures are caught by imap routines.
            # Ignore these.
            #
            result = self.general_ignore(linemap)
            return result
        restuple = self._mk_restuple(action, system, service, user, '', '')
        return {restuple: mult}
        
    def xinetd_start(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.xinetd_start_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd xinetd start string: %s' % message)
            return None
        service = mo.group(1)
        if service in self.xinetd_ignore:
            ##
            # the service will do a much better job.
            #
            result = self.general_ignore(linemap)
            return result
        restuple = self._mk_restuple(action, system, service, '', '', '')
        return {restuple: mult}

    def sshd_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        ruser = ''
        mo1 = self.sshd_open_ruser_re.search(message)
        mo2 = self.sshd_open_re.search(message)
        if mo1: method, user, rhost, ruser, service = mo1.groups()
        elif mo2: method, user, rhost, service = mo2.groups()
        else:
            self.logger.put(3, 'Odd sshd open string: %s' % message)
            return None
        method = self.sshd_methods.get(method, '??')
        rhost = self.gethost(rhost)
        if not service: service = 'ssh1'
        service = '%s(%s)' % (service, method)
        restuple = self._mk_restuple(action, system, service, user,
                                     ruser, rhost)
        return {restuple: mult}

    def sshd_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.sshd_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd sshd FAILURE string: %s' % message)
            return None
        method, user, rhost, service = mo.groups()
        method = self.sshd_methods.get(method, '??')
        rhost = self.gethost(rhost)
        if not service: service = 'ssh1'
        service = '%s(%s)' % (service, method)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def uw_imap_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        service = self._get_uw_imap_service(message)
        service = '%s(uw)' % service
        mo = self.uw_imap_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd imap FAILURE string: %s' % message)
            return None
        user, rhost = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def uw_imap_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        service = self._get_uw_imap_service(message)
        service = '%s(uw)' % service
        mo = self.uw_imap_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd imap open string: %s' % message)
            return None
        user, rhost = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def dovecot_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        service = 'imap(dc)'
        mo = self.dovecot_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd dovecot OPEN string: %s' % message)
            return None
        user, rhost = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def dovecot_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        service = 'imap(dc)'
        mo = self.dovecot_failure_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd dovecot FAILURE string: %s' % message)
            return None
        rhost = mo.group(1)
        rhost = self.gethost(rhost)
        user = 'unknown'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def courier_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.courier_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd courier OPEN string: %s' % message)
            return None
        service, user, rhost = mo.groups()
        service = '%s(cr)' % service
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def courier_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.courier_failure_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd courier FAILURE string: %s' % message)
            return None
        service, rhost = mo.groups()
        service = '%s(cr)' % service
        rhost = self.gethost(rhost)
        user = 'unknown'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def proftpd_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.proftpd_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd ProFTPD OPEN string: %s' % message)
            return None
        service = 'ftp(pro)'
        rhost, user = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def proftpd_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.proftpd_failure_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd ProFTPD FAILURE string: %s' % message)
            return None
        service = 'ftp(pro)'
        rhost, user = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def imp2_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.imp2_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd IMP failure string: %s' % message)
            return None
        rhost, system, user = mo.groups()
        rhost = self.gethost(rhost)
        service = 'IMP2'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def imp2_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.imp2_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd IMP open string: %s' % message)
            return None
        rhost, system, user = mo.groups()
        rhost = self.gethost(rhost)
        service = 'IMP2'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def imp3_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.imp3_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd IMP failure string: %s' % message)
            return None
        rhost, system, user = mo.groups()
        rhost = self.gethost(rhost)
        service = 'IMP3'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def imp3_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.imp3_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd IMP open string: %s' % message)
            return None
        user, rhost, system = mo.groups()
        rhost = self.gethost(rhost)
        service = 'IMP3'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def cyrus_failure(self,linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        service = self._get_cyrus_service(message)
        mo = self.cyrus_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd cyrus FAILURE string: %s' % message)
            return None
        rhost, user = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def cyrus_open(self,linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        service = self._get_cyrus_service(message)
        mo = self.cyrus_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd cyrus open string: %s' % message)
            return None
        rhost, user = mo.groups()
        rhost = self.gethost(rhost)
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}

    def qpopper_failure(self, linemap):
        action = self.failure
        system, message, mult = self.get_smm(linemap)
        mo = self.qpopper_fail_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd qpopper FAILURE string: %s' % message)
            return None
        user, rhost = mo.groups()
        rhost = self.gethost(rhost)
        service = 'qpopper'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}
                                                                                                                       
    def qpopper_open(self, linemap):
        action = self.open
        system, message, mult = self.get_smm(linemap)
        mo = self.qpopper_open_re.search(message)
        if not mo:
            self.logger.put(3, 'Odd qpopper open string: %s' % message)
            return None
        user, rhost = mo.groups()
        rhost = self.gethost(rhost)
        service = 'qpopper'
        restuple = self._mk_restuple(action, system, service, user, '', rhost)
        return {restuple: mult}
    ##
    # HELPER METHODS
    #
    def _mk_restuple(self, action, system, service, user, byuser, rhost):
        if user == '': user = 'unknown'
        if user == 'root' or user == 'ROOT':
            action += 10
            remote = self._mk_userat(byuser, rhost)
            restuple = (action, system, service, remote)
        else:
            if rhost:
                match = 0
                for domain_re in self.safe_domains:
                    if domain_re.search(rhost):
                        match = 1
                        break
                if not match:
                    tmp = {'system': system, 'rhost': rhost}
                    system = self.untrusted_host % tmp
            restuple = (action, user, service, system)
        return restuple

    def _mk_dots(self, str, lim):
        if len(str) > lim:
            start = -(lim-2)
            str = '..' + str[start:]
        return str
        
    def _get_pam_service(self, str):
        service = 'unknown'
        mo = self.pam_service_re.search(str)
        if mo: service = mo.group(1)
        return service

    def _get_uw_imap_service(self, str):
        service = 'unknown'
        mo = self.uw_imap_service_re.search(str)
        if mo: service = mo.group(1)
        return service

    def _mk_userat(self, user, host):
        if user and host: userat = '%s@%s' % (user, host)
        elif user: userat = user
        elif host: userat = '@%s' % host
        else: userat = 'unknown'
        return userat

    def _get_cyrus_service(self, str):
        service = 'unknown'
        mo = self.cyrus_service_re.search(str)
        if mo: service = mo.group(1)
        return service

    ##
    # FINALIZE!!
    #
    def finalize(self, rs):
        logger = self.logger
        ##
        # Prepare report
        #
        report = ''
        rep = {}
        for action in [self.root_failure, self.root_open,
                       self.failure, self.open]:
            rep[action] = ''
            flipper = ''
            for key in rs.get_distinct((action,)):
                if flipper: flipper = ''
                else: flipper = self.flip
                service_rep = []
                for service in rs.get_distinct((action, key)):
                    mymap = rs.get_submap((action, key, service))
                    key2s = []
                    for key2 in mymap.keys():
                        loghost = key2[0]
                        key2s.append('%s(%d)' % (loghost, mymap[key2]))
                    if len(key2s) > self.systems_collapse:
                        loghosts = self.collapsed_rep % (key2s[0],len(key2s)-1)
                    else:
                        loghosts = ', '.join(key2s)
                    service_rep.append([service, loghosts])
                blank = 0
                for svcrep in service_rep:
                    if blank: key = '&nbsp;'
                    else: blank = 1
                    rep[action] += self.line_rep % (flipper, key,
                                                    svcrep[0], svcrep[1])
        
        if rep[self.root_failure]:
            report += self.subreport_wrap % (self.root_failures_title,
                                             rep[self.root_failure])
        if rep[self.root_open]:
            report += self.subreport_wrap % (self.root_logins_title,
                                             rep[self.root_open])
        if rep[self.failure]:
            report += self.subreport_wrap % (self.user_failures_title,
                                             rep[self.failure])
        if rep[self.open]:
            report += self.subreport_wrap % (self.user_logins_title,
                                             rep[self.open])

        report = self.report_wrap % report
        return report

if __name__ == '__main__':
    from epylog.helpers import ModuleTest
    ModuleTest(logins_mod, sys.argv)
    


syntax highlighted by Code2HTML, v. 0.9.1