Advertisement
pson

PostfixMailQueue

Feb 19th, 2016
222
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.57 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. """
  4. #define REC_TYPE_SIZE   'C'     /* first record, created by cleanup */
  5. #define REC_TYPE_TIME   'T'     /* time stamp, required */
  6. #define REC_TYPE_FULL   'F'     /* full name, optional */
  7. #define REC_TYPE_INSP   'I'     /* inspector transport */
  8. #define REC_TYPE_FILT   'L'     /* loop filter transport */
  9. #define REC_TYPE_FROM   'S'     /* sender, required */
  10. #define REC_TYPE_DONE   'D'     /* delivered recipient, optional */
  11. #define REC_TYPE_RCPT   'R'     /* todo recipient, optional */
  12. #define REC_TYPE_ORCP   'O'     /* original recipient, optional */
  13. #define REC_TYPE_WARN   'W'     /* warning message time */
  14. #define REC_TYPE_ATTR   'A'     /* named attribute for extensions */
  15.  
  16. #define REC_TYPE_RDR    '>'     /* redirect target */
  17. #define REC_TYPE_FLGS   'f'     /* cleanup processing flags */
  18.  
  19. #define REC_TYPE_MESG   'M'     /* start message records */
  20.  
  21. #define REC_TYPE_CONT   'L'     /* long data record */
  22. #define REC_TYPE_NORM   'N'     /* normal data record */
  23.  
  24. #define REC_TYPE_XTRA   'X'     /* start extracted records */
  25.  
  26. #define REC_TYPE_RRTO   'r'     /* return-receipt, from headers */
  27. #define REC_TYPE_ERTO   'e'     /* errors-to, from headers */
  28. #define REC_TYPE_PRIO   'P'     /* priority */
  29. #define REC_TYPE_VERP   'V'     /* VERP delimiters */
  30.  
  31. #define REC_TYPE_END    'E'     /* terminator, required */
  32. """
  33.  
  34. import os, sys, string, re, time
  35.  
  36. VRecordError = "VRecordError"
  37.  
  38. class VRecord :
  39.     def __init__(self,file=None) :
  40.     if not hasattr(self, "_allowed_types") :
  41.         self._allowed_types = "CTFILSDROWA>fMLNXrePVE"
  42.     if file == None :
  43.         self._type = chr(255)
  44.         self._length = 0
  45.         self._data = ""
  46.         return
  47.  
  48.     self._type = file.read(1)
  49.     if len(self._type) == 0 :
  50.         raise VRecordError, "Unable to read type"
  51.     if self._type not in self._allowed_types :
  52.         file.seek(file.tell()-1)
  53.         raise VRecordError, "Invalid record type '%s'" % self._type
  54.  
  55.     try :
  56.         len_byte = ord(file.read(1))
  57.     except TypeError :
  58.         file.seek(file.tell()-1)
  59.         raise VRecordError, "Unable to read first byte of length"
  60.  
  61.     length = len_byte & 0177
  62.     i = 1
  63.     while len_byte & 0200 :
  64.         try :
  65.         len_byte = ord(file.read(1))
  66.         except TypeError :
  67.         file.seek(file.tell()-1)
  68.         raise VRecordError, "Unable to read byte %d of length" % i+1
  69.         length = length + (len_byte & 0177) * 128 ** i
  70.         i = i+1
  71.        
  72.     self._length = length
  73.  
  74.     buffer = ""
  75.     while length >= 1024 :      # Should be able to read this much
  76.         buffer = buffer + file.read(1024)
  77.         length = length - 1024
  78.     self._data = buffer + file.read(length)
  79.  
  80.     def __str__(self) :
  81.     if self._data :
  82.         return '<VRecord(%s:%d) "%s">' % \
  83.            (self._type, self._length, self._data[:30])
  84.     else :
  85.         return '<VRecord(%s:%d) "">' % (self._type, self._length)
  86.  
  87. class EnvelopeRecord(VRecord) :
  88.     def __init__(self, file=None) :
  89.     self._allowed_types = "MCTFILSDROWVA"
  90.     VRecord.__init__(self, file)
  91.  
  92. class MailRecord(VRecord) :
  93.     def __init__(self, file=None) :
  94.     self._allowed_types = "N"
  95.     VRecord.__init__(self, file)
  96.  
  97. class QueueFile :
  98.     def __init__(self, filename) :
  99.     self._file = filename
  100.     self._envelope = []
  101.     self._mail = []
  102.     self._client = None
  103.     self._sender = None
  104.     self._recipients = None
  105.     self._mark = ""
  106.         try :
  107.             file = open(filename)
  108.         except :
  109.         raise ValueError, "Unable to read queue file %s" % self._file
  110.            
  111.            
  112.     record = VRecord(file)
  113.     while record._type in "MCTFILSDROWVA" :
  114.         self._envelope.append(record)
  115.         record = VRecord(file)
  116.     while record._type in "XNAe" :
  117.         self._mail.append(record)
  118.         record = VRecord(file)
  119.     if record._type != "E" :
  120.         print record
  121.         raise ValueError, "Unable to read queue file %s" % self._file
  122.  
  123.     def message(self, lf="\n", lines=-1) :
  124.     print type(self._mail[0]._data), type(lf)
  125.     buffer = self._mail[0]._data + lf
  126.     for record in self._mail[1:] :
  127.         buffer = buffer + record._data + lf
  128.         lines = lines - 1
  129.         if lines == 0 : break
  130.     return buffer
  131.  
  132.     def node(self) :
  133.     return string.split(self._file, os.sep)[-1]
  134.  
  135.     def client(self) :
  136.     if self._client == None :
  137.         for record in self._envelope :
  138.         if record._type == "A" and \
  139.            record._data[:20] == "reverse_client_name=" :
  140.             self._client = record._data[20:]
  141.             return self._client
  142.         self._client = ""
  143.     return self._client
  144.  
  145.     def sender(self) :
  146.     if self._sender == None :
  147.         self._sender = ""
  148.         for record in self._envelope :
  149.         if record._type == "S" :
  150.             self._sender = record._data
  151.             return self._sender
  152.     return self._sender
  153.  
  154.     def recipients(self) :
  155.     if self._recipients == None :
  156.         self._recipients = []
  157.         for record in self._envelope :
  158.         if record._type == "R" :
  159.             self._recipients.append(record._data)
  160.     return self._recipients
  161.  
  162.     def mark(self, mark=None) :
  163.     if mark != None :
  164.         self._mark = mark
  165.     return self._mark
  166.  
  167. class MonitorQueue :
  168.     """
  169.    This takes a top directory, and adds all files (in all sub-directories)
  170.    that are considered a Postfix mail queue files. It stores the clients,
  171.    the recipients and the senders from the envelopes.
  172.    """
  173.     def __init__(self, top) :
  174.         self._top = []
  175.         self._queue = {}                # The current set of evelope data
  176.         self.addDirectory(top)
  177.  
  178.     def addDirectory(self, top) :
  179.         if top[-1] == os.sep :
  180.             top = top[:-1]
  181.         if os.path.isdir(top) and \
  182.            not top in self._top :
  183.             self._top.append(top)
  184.         self.update(top)
  185.        
  186.     def update(self, top=None) :
  187.         if top == None :
  188.             count = 0
  189.             rd = {}
  190.             nd = {}
  191.             for top in self._top :
  192.                 c,r,n = self.update(top)
  193.                 count = count+c
  194.                 for x in r :
  195.                     rd[x] = None
  196.                 for x in n :
  197.                     nd[x] = None
  198.             return (count, rd.keys(), nd.keys())
  199.        
  200.         files = self._queue.keys()
  201.         new_files = []
  202.        
  203.         DIRS = [ top ]
  204.         count = 0
  205.         while DIRS :
  206.             DIR, DIRS = DIRS[0], DIRS[1:]
  207.             for name in os.listdir(DIR) :
  208.                 filename = DIR + os.sep + name
  209.                 if self._queue.has_key(filename) :
  210.                     count = count + 1
  211.                     continue          # No need to this more than once
  212.                 elif os.path.islink(filename) :
  213.                     continue          # You might or might not want this
  214.                 elif os.path.isdir(filename) :
  215.                     DIRS.append(filename)
  216.                 elif os.path.isfile(filename) :
  217.                     count = count+1
  218.                     try :
  219.                         qfile = QueueFile(filename)
  220.                     except ValueError :
  221.                         continue
  222.                     new_files.append(filename)
  223.                     client = qfile.client()
  224.                     sender = qfile.sender()
  225.                     recipients = qfile.recipients()
  226.                     self._queue[filename] = (client, sender, recipients)
  227.                 else :
  228.                     # print filename, "ignored"
  229.                     pass
  230.         removed = []
  231.         for filename in self._queue.keys() :
  232.             if not os.path.isfile(filename) :
  233.                 removed.append(filename)
  234.                 del self._queue[filename]
  235.         return (count, removed, new_files)
  236.  
  237.     def display(self) :
  238.         files = self._queue.keys()
  239.         files.sort()
  240.         for filename in files :
  241.             client = self._queue[filename][0]
  242.             sender = self._queue[filename][1]
  243.             recipients = ", ".join(self._queue[filename][2])
  244.             print "%s:%s:%s:%s" % (filename,client,sender,recipients)
  245.  
  246.     def status(self, amount=30) :
  247.         "Gives out the most common clients, sender domains, and recipient domains"
  248.         files = self._queue.keys()
  249.         files.sort()
  250.         clients = {}
  251.         senders = {}
  252.         recipients = {}
  253.         rx = re.compile(r".*@(.*)")
  254.         for filename in files :
  255.             client = self._queue[filename][0]
  256.             if not client :
  257.                 client = "<Local>"
  258.             try :
  259.                 clients[client] = clients[client] + 1
  260.             except KeyError :
  261.                 clients[client] = 1
  262.                
  263.             sender = self._queue[filename][1]
  264.             m = rx.match(sender)
  265.             if m :
  266.                 domain = m.group(1)
  267.                 try :
  268.                     senders[domain] = senders[domain] + 1
  269.                 except KeyError :
  270.                     senders[domain] = 1
  271.             elif sender :
  272.                 # print 'Unable to deal with sender "%s" for %s' % (sender, filename)
  273.                 pass
  274.  
  275.             for recipient in self._queue[filename][2] :
  276.                 m = rx.match(recipient)
  277.                 domain = m.group(1)
  278.                 try :
  279.                     recipients[domain] = recipients[domain] + 1
  280.                 except KeyError :
  281.                     recipients[domain] = 1
  282.  
  283.         keys = clients.keys()
  284.         keys.sort(lambda x,y,d=clients : cmp(d[y], d[x]))
  285.         rclient = map(lambda x,d=clients : (d[x], x), keys[:amount])
  286.  
  287.         keys = senders.keys()
  288.         keys.sort(lambda x,y,d=senders : cmp(d[y], d[x]))
  289.         rsender = map(lambda x,d=senders : (d[x], x), keys[:amount])
  290.  
  291.         keys = recipients.keys()
  292.         keys.sort(lambda x,y,d=recipients : cmp(d[y], d[x]))
  293.         rrecipient = map(lambda x,d=recipients : (d[x], x), keys[:amount])
  294.  
  295.         return (rclient, rsender, rrecipient)
  296.  
  297.     def show_status(self, old_status=None) :
  298.         rclient, rsender, rrecipient = status = self.status()
  299.  
  300.         # print old_status
  301.         # print status
  302.  
  303.         clients = {}
  304.         corder = map(lambda x :x[1], rclient)
  305.         for client in rclient :
  306.             clients[client[1]] = client[0]
  307.         # print clients
  308.         for value, client in old_status[0] :
  309.             if clients.has_key(client) :
  310.                 clients[client] = (clients[client], clients[client]-value)
  311.            
  312.         senders = {}
  313.         sorder = map(lambda x :x[1], rsender)
  314.         for sender in rsender :
  315.             senders[sender[1]] = sender[0]
  316.         for value, sender in old_status[1] :
  317.             if senders.has_key(sender) :
  318.                 senders[sender] = (senders[sender], senders[sender]-value)
  319.            
  320.         recipients = {}
  321.         corder = map(lambda x :x[1], rrecipient)
  322.         for recipient in rrecipient :
  323.             recipients[recipient[1]] = recipient[0]
  324.         for value, recipient in old_status[2] :
  325.             if recipients.has_key(recipient) :
  326.                 recipients[recipient] = (recipients[recipient], recipients[recipient]-value)
  327.         return status
  328.  
  329.     def monitor(self) :
  330.  
  331.         old_status = ([],[],[])
  332.         monitor = Monitor()
  333.         encounted = []
  334.         while 2+2 == 4 :
  335.             start = time.time()
  336.             x = self.update()
  337.             count, removed, new_files = x
  338.             encounted.append(count)
  339.             if len(encounted) > 50 :
  340.                 encounted = encounted[-50:]
  341.             updated = time.time()
  342.             # print "Removed %d and found %d in %3.1f seconds" % (len(removed), len(new_files), updated-start)
  343.             # Main part here
  344.             rclient, rsender, rrecipient = status = self.status()
  345.  
  346.             monitor.wmailq(0, "Mail Queue", encounted)
  347.             monitor.wdisplay(1, "Client", rclient, old_status[0])
  348.             monitor.wdisplay(2, "Sender", rsender, old_status[1])
  349.             monitor.wdisplay(3, "Recipient", rrecipient, old_status[2])
  350.            
  351.             old_status = status
  352.             # Wrap up for next lap
  353.             time.sleep(30.0 + start - updated)
  354.            
  355. import curses
  356. class Monitor :
  357.     def __init__(self) :
  358.         self._screen = curses.initscr()
  359.         h, w = self._screen.getmaxyx()
  360.         h2 = h/2-1
  361.         w2 = w/2-1
  362.         self._windows = []
  363.         self._windows.append(curses.newwin(h/2, w/2, 0, 0))
  364.         self._windows.append(curses.newwin(h/2, w/2, 0, w/2))
  365.         self._windows.append(curses.newwin(h/2, w/2, h/2, 0))
  366.         self._windows.append(curses.newwin(h/2, w/2, h/2, w/2))
  367.         for window in self._windows :
  368.             window.border()
  369.             window.refresh()
  370.  
  371.     def wtitle(self, n, title) :
  372.         window = self._windows[n]
  373.         window.clear()
  374.         window.border()
  375.         h, w = window.getmaxyx()
  376.         offset = (w - len(title) - 2)/2
  377.         window.addstr(0, offset, " %s " % title)
  378.  
  379.     def wline(self, n, i, text, value, old=None) :
  380.         window = self._windows[n]
  381.         h, w = window.getmaxyx()
  382.         if i < h-2 :
  383.             window.addnstr(i+1, 1, text, w-15)
  384.             window.addnstr(i+1, w-13, "%-6d" % value, 6)
  385.             if old :
  386.                 window.addnstr(i+1, w-7, "[%4d]" % (value-old), 6)
  387.  
  388.     def wmailq(self, n, title, queue) :
  389.         self.wtitle(n, title)
  390.         window = self._windows[n]
  391.         h, w = window.getmaxyx()
  392.         queue = queue[-(h-2):]
  393.         m = max(queue)
  394.         size = 1000
  395.         while size <= m :
  396.             size = size * 2
  397.         window.addstr(0, w-14, " Scale %d " % size)
  398.         i = 0
  399.         while i < len(queue) :
  400.             c = (size / (h-9))
  401.             window.addstr(i+1, 1, "*" * ((queue[i] * (w-9))/size))
  402.             window.addstr(i+1, w-7, "%-5d" % queue[i])
  403.             i = i + 1
  404.         window.refresh()
  405.  
  406.     def wdisplay(self, n, title, current, old) :        
  407.         self.wtitle(n, title)
  408.         data = {}
  409.         for value, name in old :
  410.             data[name] = value
  411.         i = 0
  412.         while i < len(current) :
  413.             value, name = current[i]
  414.             try :
  415.                 self.wline(n, i , name, value, data[name])
  416.             except KeyError :
  417.                 self.wline(n, i , name, value)
  418.             i = i+1
  419.         self._windows[n].refresh()
  420.  
  421. if __name__ == '__main__' :
  422.     queue = MonitorQueue("/postfix/var/spool/postfix/deferred/")
  423.     queue.addDirectory("/postfix/var/spool/postfix/active/")    
  424.     queue.monitor()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement