pmailq 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # This program is free software. It comes without any warranty, to
  4. # the extent permitted by applicable law. You can redistribute it
  5. # and/or modify it under the terms of the Do What The Fuck You Want
  6. # To Public License, Version 2, as published by Sam Hocevar. See
  7. # http://sam.zoy.org/wtfpl/COPYING for more details.
  8. # {{{ Constants
  9. _NAME = 'pmailq'
  10. _HELP = "[OPTIONS] [ list | parse | del ]"
  11. _DESC = "%s postfix mail queue manager" % _NAME
  12. _VERSION = '0.6'
  13. _AUTHOR = 'Emmanuel Bouthenot <kolter@openics.org>'
  14. MAILQ = "postqueue -p"
  15. DELQ = "postsuper -d"
  16. # }}}
  17. # {{{ Imports
  18. import sys
  19. import os
  20. import subprocess
  21. import fcntl
  22. import select
  23. import fnmatch
  24. import argparse
  25. import re
  26. # }}}
  27. # {{{ Class Proc
  28. class Proc:
  29. def run(self, command):
  30. proc = subprocess.Popen(command, bufsize=1, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
  31. proc.stdin.close()
  32. outfile = proc.stdout
  33. outfd = outfile.fileno()
  34. errfile = proc.stderr
  35. errfd = errfile.fileno()
  36. # avoid deadlocks
  37. self.set_no_block(outfd)
  38. self.set_no_block(errfd)
  39. outdata = errdata = bytes()
  40. outeof = erreof = False
  41. while True:
  42. # wait for activity
  43. ready = select.select([outfd, errfd], [], [])
  44. if outfd in ready[0]:
  45. outchunk = outfile.read()
  46. if outchunk == b'':
  47. outeof = True
  48. outdata = outdata + outchunk
  49. if errfd in ready[0]:
  50. errchunk = errfile.read()
  51. if errchunk == b'':
  52. erreof = True
  53. errdata = errdata + errchunk
  54. if outeof and erreof:
  55. break
  56. # give a little time for buffers to fill
  57. select.select([],[],[],.1)
  58. err = proc.wait()
  59. return err, outdata.decode('utf-8'), errdata.decode('utf-8')
  60. def set_no_block(self, fd):
  61. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  62. try:
  63. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
  64. except AttributeError:
  65. fcntl.fcntl(fd, fcntl.F_SETFL, fl | fcntl.FNDELAY)
  66. # }}}
  67. # {{{ MailQueue
  68. class MailQueue:
  69. def __init__(self):
  70. self.mailqueue = []
  71. self.filters = {
  72. 'email' : None,
  73. 'msg' : None,
  74. 'lowsize' : 0,
  75. 'upsize' : 0,
  76. 'active' : False,
  77. 'hold' : False
  78. }
  79. self.parse()
  80. def add_filter(self, key, value):
  81. self.filters[key] = value
  82. def parse(self):
  83. p_ret, p_stdout, p_stderr = Proc().run(MAILQ)
  84. # mail system down ?
  85. if p_ret != 0:
  86. sys.stderr.write ("ERR : %s\n" % ", ".join(p_stderr.strip().split("\n")))
  87. sys.exit (-1)
  88. buffer = p_stdout.strip().split('\n');
  89. # checking empty mail queue
  90. if len(buffer)>0 and buffer[0].strip() == "Mail queue is empty":
  91. sys.stderr.write ("INFO : %s\n" % buffer[0].strip())
  92. return None
  93. # skip first and last line
  94. buffer = "\n".join(buffer[1:-1]).strip()
  95. for block in buffer.split("\n\n"):
  96. lines = block.split("\n")
  97. headers = lines[0].split(' ')
  98. # squeeze repeated spaces
  99. while '' in headers:
  100. headers.remove('')
  101. queue = []
  102. dest = []
  103. info = ""
  104. for expl in lines[1:]:
  105. expl = expl.strip()
  106. if expl.startswith("(") and expl.endswith(")"):
  107. if info == "":
  108. info = expl[1:len(expl)-1]
  109. if dest != []:
  110. queue.append({ "info" : info , "dest" : dest })
  111. dest = []
  112. info = expl[1:len(expl)-1]
  113. else:
  114. dest.append(expl.lower())
  115. if dest != []:
  116. queue.append({ "info" : info , "dest" : dest })
  117. self.mailqueue.append({
  118. "id" : headers[0].rstrip("*!"),
  119. "active" : headers[0].endswith("*"),
  120. "hold" : headers[0].endswith("!"),
  121. "size" : headers[1],
  122. "date" : " ".join(headers[2:5]),
  123. "queue" : queue
  124. })
  125. def check(self, size, active, hold, dest, infos):
  126. if self.filters['email'] != None:
  127. match = False
  128. for e in dest:
  129. if fnmatch.fnmatch(e.lower(), self.filters['email'].lower()):
  130. match = True
  131. if not match:
  132. return False
  133. if self.filters['msg'] != None:
  134. match = False
  135. for i in infos:
  136. if fnmatch.fnmatch(i.lower(), self.filters['msg'].lower()):
  137. match = True
  138. if not match:
  139. return False
  140. if self.filters['active'] and not active:
  141. return False
  142. if self.filters['hold'] and not hold:
  143. return False
  144. if self.filters['lowsize'] != 0 and int(size) > self.filters['lowsize']:
  145. return False
  146. if self.filters['upsize'] != 0 and int(size) < self.filters['upsize']:
  147. return False
  148. return True
  149. def cmd_list(self):
  150. for m in self.mailqueue:
  151. out = "%s\n" % m['id']
  152. out += " -date: %s\n" % m['date']
  153. out += " -size: %s\n" % m['size']
  154. out += " -active: %s\n" % str(m['active'])
  155. out += " -hold: %s\n" % str(m['hold'])
  156. out += " -to:\n"
  157. to = []
  158. i = []
  159. for n in m['queue']:
  160. i.append(n['info'])
  161. to += n['dest']
  162. out += " + %s : [%s]\n" % (",".join(n['dest']), n['info'])
  163. if self.check(m['size'], m['active'], m['hold'], to, i):
  164. print(out)
  165. def cmd_parse(self):
  166. for m in self.mailqueue:
  167. e = []
  168. i = []
  169. for n in m['queue']:
  170. i.append(n['info'])
  171. for o in n['dest']:
  172. e.append(o)
  173. if self.check(m['size'], m['active'], m['hold'], e, i):
  174. print("%s|%s|%s|%d|%d|%s" % (m['id'], m['date'], m['size'], int(m['active']), int(m['hold']), ",".join(n['dest'])))
  175. def cmd_del(self):
  176. for m in self.mailqueue:
  177. e = []
  178. i=[]
  179. for n in m['queue']:
  180. i.append(n['info'])
  181. for o in n['dest']:
  182. e.append(o)
  183. if self.check(m['size'], m['active'], m['hold'], e, i):
  184. p_ret, _, p_stderr = Proc().run('%s %s' % (DELQ, m['id']))
  185. if p_ret != 0:
  186. print("deleting %s [FAILED] (%s)" % (m['id'], re.sub('\s+', ' ', p_stderr).strip()))
  187. else:
  188. print("deleting %s [OK]" % m['id'])
  189. # }}}
  190. # {{{ main
  191. def main():
  192. parser = argparse.ArgumentParser(prog=_NAME, description=_DESC)
  193. parser.add_argument(
  194. '-v', '--version',
  195. action='version', version=_VERSION)
  196. parser.add_argument(
  197. '-e', '--email',
  198. dest='email', default=None, metavar='PATTERN',
  199. help='select entries in queue with email matching PATTERN')
  200. parser.add_argument(
  201. '-m', '--msg',
  202. dest='msg', default=None, metavar='PATTERN',
  203. help='select entries in queue with error message matching PATTERN')
  204. parser.add_argument(
  205. '-l', '--size-lower',
  206. dest='lowsize', default=0, type=int, metavar='SIZE',
  207. help='select entries in queue with size lower than SIZE bytes')
  208. parser.add_argument(
  209. '-u', '--size-upper',
  210. dest='upsize', default=0, type=int, metavar='SIZE',
  211. help='select entries in queue with size upper than SIZE bytes')
  212. parser.add_argument(
  213. '-a', '--active',
  214. dest='active', default=False, action='store_true',
  215. help='select "active" entries in queue (default: no)')
  216. parser.add_argument(
  217. '-o', '--hold',
  218. dest='hold', default=False, action='store_true',
  219. help='select "on hold" entries in queue (default: no)')
  220. subparsers = parser.add_subparsers(dest='action')
  221. subparsers.add_parser(
  222. 'list',
  223. help='Show a detailed listing of the selected entries')
  224. subparsers.add_parser(
  225. 'parse',
  226. help='Show a listing of the selected entries in a machine readable format')
  227. subparsers.add_parser(
  228. 'del',
  229. help='Delete the selected entries')
  230. options = parser.parse_args()
  231. if options.action is None:
  232. options.action = 'list'
  233. m = MailQueue()
  234. m.add_filter("email", options.email)
  235. m.add_filter("msg", options.msg)
  236. m.add_filter("lowsize", options.lowsize)
  237. m.add_filter("upsize", options.upsize)
  238. m.add_filter("active", options.active)
  239. m.add_filter("hold", options.hold)
  240. if 'cmd_' + options.action not in dir(m):
  241. parser.print_help()
  242. else:
  243. getattr(m, 'cmd_' + options.action)()
  244. if __name__ == "__main__":
  245. main()
  246. # }}}
  247. # vim: foldmethod=marker foldlevel=0 foldenable