123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- # This program is free software. It comes without any warranty, to
- # the extent permitted by applicable law. You can redistribute it
- # and/or modify it under the terms of the Do What The Fuck You Want
- # To Public License, Version 2, as published by Sam Hocevar. See
- # http://sam.zoy.org/wtfpl/COPYING for more details.
- # {{{ Constants
- _NAME = 'pmailq'
- _HELP = "[OPTIONS] [ list | parse | del ]"
- _DESC = "%s postfix mail queue manager" % _NAME
- _VERSION = '0.6'
- _AUTHOR = 'Emmanuel Bouthenot <kolter@openics.org>'
- MAILQ = "postqueue -p"
- DELQ = "postsuper -d"
- # }}}
- # {{{ Imports
- import sys
- import os
- import subprocess
- import fcntl
- import select
- import fnmatch
- import argparse
- import re
- # }}}
- # {{{ Class Proc
- class Proc:
- def run(self, command):
- proc = subprocess.Popen(command, bufsize=1, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
- proc.stdin.close()
- outfile = proc.stdout
- outfd = outfile.fileno()
- errfile = proc.stderr
- errfd = errfile.fileno()
- # avoid deadlocks
- self.set_no_block(outfd)
- self.set_no_block(errfd)
- outdata = errdata = bytes()
- outeof = erreof = False
- while True:
- # wait for activity
- ready = select.select([outfd, errfd], [], [])
- if outfd in ready[0]:
- outchunk = outfile.read()
- if outchunk == b'':
- outeof = True
- outdata = outdata + outchunk
- if errfd in ready[0]:
- errchunk = errfile.read()
- if errchunk == b'':
- erreof = True
- errdata = errdata + errchunk
- if outeof and erreof:
- break
- # give a little time for buffers to fill
- select.select([],[],[],.1)
- err = proc.wait()
- return err, outdata.decode('utf-8'), errdata.decode('utf-8')
- def set_no_block(self, fd):
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- try:
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
- except AttributeError:
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | fcntl.FNDELAY)
- # }}}
- # {{{ MailQueue
- class MailQueue:
- def __init__(self):
- self.mailqueue = []
- self.filters = {
- 'email' : None,
- 'msg' : None,
- 'lowsize' : 0,
- 'upsize' : 0,
- 'active' : False,
- 'hold' : False
- }
- self.parse()
- def add_filter(self, key, value):
- self.filters[key] = value
- def parse(self):
- p_ret, p_stdout, p_stderr = Proc().run(MAILQ)
- # mail system down ?
- if p_ret != 0:
- sys.stderr.write ("ERR : %s\n" % ", ".join(p_stderr.strip().split("\n")))
- sys.exit (-1)
- buffer = p_stdout.strip().split('\n');
- # checking empty mail queue
- if len(buffer)>0 and buffer[0].strip() == "Mail queue is empty":
- sys.stderr.write ("INFO : %s\n" % buffer[0].strip())
- return None
- # skip first and last line
- buffer = "\n".join(buffer[1:-1]).strip()
- for block in buffer.split("\n\n"):
- lines = block.split("\n")
- headers = lines[0].split(' ')
- # squeeze repeated spaces
- while '' in headers:
- headers.remove('')
- queue = []
- dest = []
- info = ""
- for expl in lines[1:]:
- expl = expl.strip()
- if expl.startswith("(") and expl.endswith(")"):
- if info == "":
- info = expl[1:len(expl)-1]
- if dest != []:
- queue.append({ "info" : info , "dest" : dest })
- dest = []
- info = expl[1:len(expl)-1]
- else:
- dest.append(expl.lower())
- if dest != []:
- queue.append({ "info" : info , "dest" : dest })
- self.mailqueue.append({
- "id" : headers[0].rstrip("*!"),
- "active" : headers[0].endswith("*"),
- "hold" : headers[0].endswith("!"),
- "size" : headers[1],
- "date" : " ".join(headers[2:5]),
- "queue" : queue
- })
- def check(self, size, active, hold, dest, infos):
- if self.filters['email'] != None:
- match = False
- for e in dest:
- if fnmatch.fnmatch(e.lower(), self.filters['email'].lower()):
- match = True
- if not match:
- return False
- if self.filters['msg'] != None:
- match = False
- for i in infos:
- if fnmatch.fnmatch(i.lower(), self.filters['msg'].lower()):
- match = True
- if not match:
- return False
- if self.filters['active'] and not active:
- return False
- if self.filters['hold'] and not hold:
- return False
- if self.filters['lowsize'] != 0 and int(size) > self.filters['lowsize']:
- return False
- if self.filters['upsize'] != 0 and int(size) < self.filters['upsize']:
- return False
- return True
- def cmd_list(self):
- for m in self.mailqueue:
- out = "%s\n" % m['id']
- out += " -date: %s\n" % m['date']
- out += " -size: %s\n" % m['size']
- out += " -active: %s\n" % str(m['active'])
- out += " -hold: %s\n" % str(m['hold'])
- out += " -to:\n"
- to = []
- i = []
- for n in m['queue']:
- i.append(n['info'])
- to += n['dest']
- out += " + %s : [%s]\n" % (",".join(n['dest']), n['info'])
- if self.check(m['size'], m['active'], m['hold'], to, i):
- print(out)
- def cmd_parse(self):
- for m in self.mailqueue:
- e = []
- i = []
- for n in m['queue']:
- i.append(n['info'])
- for o in n['dest']:
- e.append(o)
- if self.check(m['size'], m['active'], m['hold'], e, i):
- print("%s|%s|%s|%d|%d|%s" % (m['id'], m['date'], m['size'], int(m['active']), int(m['hold']), ",".join(n['dest'])))
- def cmd_del(self):
- for m in self.mailqueue:
- e = []
- i=[]
- for n in m['queue']:
- i.append(n['info'])
- for o in n['dest']:
- e.append(o)
- if self.check(m['size'], m['active'], m['hold'], e, i):
- p_ret, _, p_stderr = Proc().run('%s %s' % (DELQ, m['id']))
- if p_ret != 0:
- print("deleting %s [FAILED] (%s)" % (m['id'], re.sub('\s+', ' ', p_stderr).strip()))
- else:
- print("deleting %s [OK]" % m['id'])
- # }}}
- # {{{ main
- def main():
- parser = argparse.ArgumentParser(prog=_NAME, description=_DESC)
- parser.add_argument(
- '-v', '--version',
- action='version', version=_VERSION)
- parser.add_argument(
- '-e', '--email',
- dest='email', default=None, metavar='PATTERN',
- help='select entries in queue with email matching PATTERN')
- parser.add_argument(
- '-m', '--msg',
- dest='msg', default=None, metavar='PATTERN',
- help='select entries in queue with error message matching PATTERN')
- parser.add_argument(
- '-l', '--size-lower',
- dest='lowsize', default=0, type=int, metavar='SIZE',
- help='select entries in queue with size lower than SIZE bytes')
- parser.add_argument(
- '-u', '--size-upper',
- dest='upsize', default=0, type=int, metavar='SIZE',
- help='select entries in queue with size upper than SIZE bytes')
- parser.add_argument(
- '-a', '--active',
- dest='active', default=False, action='store_true',
- help='select "active" entries in queue (default: no)')
- parser.add_argument(
- '-o', '--hold',
- dest='hold', default=False, action='store_true',
- help='select "on hold" entries in queue (default: no)')
- subparsers = parser.add_subparsers(dest='action')
- subparsers.add_parser(
- 'list',
- help='Show a detailed listing of the selected entries')
- subparsers.add_parser(
- 'parse',
- help='Show a listing of the selected entries in a machine readable format')
- subparsers.add_parser(
- 'del',
- help='Delete the selected entries')
- options = parser.parse_args()
- if options.action is None:
- options.action = 'list'
- m = MailQueue()
- m.add_filter("email", options.email)
- m.add_filter("msg", options.msg)
- m.add_filter("lowsize", options.lowsize)
- m.add_filter("upsize", options.upsize)
- m.add_filter("active", options.active)
- m.add_filter("hold", options.hold)
- if 'cmd_' + options.action not in dir(m):
- parser.print_help()
- else:
- getattr(m, 'cmd_' + options.action)()
- if __name__ == "__main__":
- main()
- # }}}
- # vim: foldmethod=marker foldlevel=0 foldenable
|