#!/usr/pkg/bin/python3.7
# -*- coding: iso-8859-1 -*-
"""
mailqs - Summarize output from sendmail's mailq(8) command.

DESCRIPTION

This command is a convenience command that reads output from sendmail's
mailq(8) command and prints a summary of the data, with one sendmail queue
entry per line. The output of this command is easily run through something
like awk(1).

USAGE:

mailqs [OPTIONS]

OPTIONS

-i file        Read mailq(8) output from the specified file, instead of
--input file   piping from the mailq(8) command. A file of '-' reads from
               standard input.

-r 'sep'       Use the 'sep' string instead of ', ' to separate recipient
               addresses. (e.g., ' ' will separate recipients by a single
               space)

-s, --short    Truncate the display so that it doesn't exceed 80 characters
               per line. (An ellipsis will be used to indicate truncation.)

COPYRIGHT AND LICENSE

Copyright  2008 Brian M. Clapper

This is free software, released under the following BSD-like license:

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. The end-user documentation included with the redistribution, if any,
   must include the following acknowlegement:

      This product includes software developed by Brian M. Clapper
      (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
      copyright  2008 Brian M. Clapper.

    Alternately, this acknowlegement may appear in the software itself, if
    and wherever such third-party acknowlegements normally appear.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

$Id: mailqs 7202 2008-01-23 02:12:46Z bmc $
"""

# $Id: mailqs 7202 2008-01-23 02:12:46Z bmc $

# Info about the module
__version__   = "1.0"
__author__    = "Brian Clapper, bmc@clapper.org"
__url__       = "http://www.clapper.org/software/python/sendmail-admin/"
__copyright__ = " 2008 Brian M. Clapper"
__license__   = "BSD-style license"

# Package stuff

__all__     = ["mailqs"]


# ---------------------------------------------------------------------------
# Imports
# ---------------------------------------------------------------------------

import popen2
import re
import sys
import os
from getopt import getopt, GetoptError

# Use the built-in 'set' type if using Python 2.4 or better. Otherwise, use
# the old sets module.
try:
    set
except NameError:
    from sets import Set as set, ImmutableSet as frozenset

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

MAILQ = "mailq"

# Pattern for matching a line containing a queue ID.
QID_LINE_RE = r'^([a-zA-Z0-9]+)[*]?\s+[0-9]+\s+(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'

# Pattern for matching a recipient line
RECIPIENT_LINE_RE = r'^<.*>?$'

# Pattern for extracting an address
EXTRACT_ADDR_RE = r'^ *<([^>]*)> *$'

# ---------------------------------------------------------------------------
# Classes
# ---------------------------------------------------------------------------

class QueueEntry(object):

    def __init__(self, queue_id, sender):
        self.queue_id = queue_id
        self.sender = sender
        self.recipients = set()

    def add_recipient(self, recipient):
        self.recipients.add(recipient)

    def __cmp__(self, other):
        result = 0
        if other != None:
            result = cmp(self.queue_id, other.queue_id)
        return result

    def __hash__(self):
        return self.queue_id.__hash__()

    def __str__(self):
        return '%s %s (%s)' %\
               (self.queue_id, self.sender, ', '.join(self.recipients))

class MailQueue(object):

    def __init__(self):
        self.entries = set()
        self.max_sender = 0
        pass

    def load(self, input_file):
        # Pattern for matching a line with a queue ID.

        pat = re.compile(QID_LINE_RE)
        recv_pat = re.compile(RECIPIENT_LINE_RE)
        addr_pat = re.compile(EXTRACT_ADDR_RE)
        r = self._open_mailq(input_file)
        queue_entry = None
        for line in r.readlines():
            line = line.strip()
            fields = line.split()

            # Does this line match the queue ID pattern? If so, it's a new
            # queue entry.

            match = pat.match(line)
            if match:
                queue_id = match.group(1)
                sender = fields[len(fields) - 1]
                match = addr_pat.match(sender)
                if match:
                    sender = match.group(1)
                queue_entry = QueueEntry(queue_id, sender)
                self.entries.add(queue_entry)
                self.max_sender = max(self.max_sender, len(sender))

            # Otherwise, is this a recipient line? If so, add it to the
            # current queue entry.
            elif queue_entry and recv_pat.match(line):
                recipient = fields[0]
                match = addr_pat.match(recipient)
                if match:
                    recipient = match.group(1)
                queue_entry.add_recipient(recipient)
        r.close()
                
    def _open_mailq(self, input_file):
        result = None
        if input_file == None:
            r, w, e = popen2.popen3(MAILQ)
            w.close()
            e.close()
            result = r

        elif input_file == "-":
            result = sys.stdin

        else:
            result = open(input_file)

        return result

class Params(object):
    def __init__(self):
        self.short_format = False
        self.recipient_separator = ", "
        self.input_file = None

# ---------------------------------------------------------------------------
# Main Program
# ---------------------------------------------------------------------------

def usage(msg):
    if msg:
        sys.stderr.write('%s\n' % msg)

    sys.stderr.write(\
"""Usage: %s [OPTIONS] message-ID [message-ID] ...

OPTIONS:

-i file        Read mailq(8) output from the specified file, instead of
--input file   piping from the mailq(8) command. A file of '-' reads from
               standard input.

-r 'sep'       Use the 'sep' string instead of ', ' to separate recipient
               addresses. (e.g., ' ' will separate recipients by a single
               space)

-s, --short    Truncate the display so that it doesn't exceed 80 characters
               per line. (An ellipsis will be used to indicate truncation.)

""" % os.path.basename(sys.argv[0]))

    sys.exit(1)


def parse_params(argv):
    try:
        opts, args = getopt(argv[1:], "sr:i:", ["short", "input"])
    except GetoptError, ex:
        usage(str(ex))   # throws an exception

    result = Params()

    if len(args) > 0:
        usage("Too many parameters.")

    for o, a in opts:
        if o in ("--short", "-s"):
            result.short_format = True
            continue

        if o in ("-r"):
            result.recipient_separator = a
            continue

        if o in ("-i", "--input"):
            result.input_file = a
            continue

    return result

def main():

    params = parse_params(sys.argv)
    queue = MailQueue()
    queue.load(params.input_file)
    for qe in queue.entries:
        s = '%-15s %-*s  %s' %\
            (qe.queue_id, queue.max_sender, qe.sender, \
             params.recipient_separator.join(qe.recipients))
        if params.short_format and (len(s) > 79):
            s = s[0:75] + ' ...'
        print s

if __name__ == "__main__":
    main()
