pmailq 9.2 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.3'
  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. # }}}
  26. # {{{ Class Proc
  27. class Proc:
  28. def run(self, command):
  29. proc = subprocess.Popen(command, bufsize=1, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
  30. proc.stdin.close()
  31. outfile = proc.stdout
  32. outfd = outfile.fileno()
  33. errfile = proc.stderr
  34. errfd = errfile.fileno()
  35. # avoid deadlocks
  36. self.set_no_block(outfd)
  37. self.set_no_block(errfd)
  38. outdata = errdata = bytes()
  39. outeof = erreof = False
  40. while True:
  41. # wait for activity
  42. ready = select.select([outfd, errfd], [], [])
  43. if outfd in ready[0]:
  44. outchunk = outfile.read()
  45. if outchunk == b'':
  46. outeof = True
  47. outdata = outdata + outchunk
  48. if errfd in ready[0]:
  49. errchunk = errfile.read()
  50. if errchunk == b'':
  51. erreof = True
  52. errdata = errdata + errchunk
  53. if outeof and erreof:
  54. break
  55. # give a little time for buffers to fill
  56. select.select([],[],[],.1)
  57. err = proc.wait()
  58. return err, outdata.decode('utf-8'), errdata.decode('utf-8')
  59. def set_no_block(self, fd):
  60. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  61. try:
  62. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
  63. except AttributeError:
  64. fcntl.fcntl(fd, fcntl.F_SETFL, fl | fcntl.FNDELAY)
  65. # }}}
  66. # {{{ MailQueue
  67. class MailQueue:
  68. def __init__(self):
  69. self.mailqueue = []
  70. self.filters = {
  71. 'email' : None,
  72. 'msg' : None,
  73. 'lowsize' : 0,
  74. 'upsize' : 0,
  75. 'active' : False,
  76. 'hold' : False
  77. }
  78. self.parse()
  79. def add_filter(self, key, value):
  80. self.filters[key] = value
  81. def parse(self):
  82. p_ret, p_stdout, p_stderr = Proc().run(MAILQ)
  83. # mail system down ?
  84. if p_ret != 0:
  85. sys.stderr.write ("ERR : %s\n" % ", ".join(p_stderr.strip().split("\n")))
  86. sys.exit (-1)
  87. buffer = p_stdout.strip().split('\n');
  88. # checking empty mail queue
  89. if len(buffer)>0 and buffer[0].strip() == "Mail queue is empty":
  90. sys.stderr.write ("INFO : %s\n" % buffer[0].strip())
  91. return None
  92. # skip first and last line
  93. buffer = "\n".join(buffer[1:-1]).strip()
  94. for block in buffer.split("\n\n"):
  95. lines = block.split("\n")
  96. headers = lines[0].split(' ')
  97. # squeeze repeated spaces
  98. while '' in headers:
  99. headers.remove('')
  100. queue = []
  101. dest = []
  102. info = ""
  103. for expl in lines[1:]:
  104. expl = expl.strip()
  105. if expl.startswith("(") and expl.endswith(")"):
  106. if info == "":
  107. info = expl[1:len(expl)-1]
  108. if dest != []:
  109. queue.append({ "info" : info , "dest" : dest })
  110. dest = []
  111. info = expl[1:len(expl)-1]
  112. else:
  113. dest.append(expl.lower())
  114. if dest != []:
  115. queue.append({ "info" : info , "dest" : dest })
  116. self.mailqueue.append({
  117. "id" : headers[0].rstrip("*!"),
  118. "active" : headers[0].endswith("*"),
  119. "hold" : headers[0].endswith("!"),
  120. "size" : headers[1],
  121. "date" : " ".join(headers[2:5]),
  122. "queue" : queue
  123. })
  124. def check(self, size, active, hold, dest, infos):
  125. if self.filters['email'] != None:
  126. match = False
  127. for e in dest:
  128. if fnmatch.fnmatch(e.lower(), self.filters['email'].lower()):
  129. match = True
  130. if not match:
  131. return False
  132. if self.filters['msg'] != None:
  133. match = False
  134. for i in infos:
  135. if fnmatch.fnmatch(i.lower(), self.filters['msg'].lower()):
  136. match = True
  137. if not match:
  138. return False
  139. if self.filters['active'] and not active:
  140. return False
  141. if self.filters['hold'] and not hold:
  142. return False
  143. if self.filters['lowsize'] != 0 and int(size) > self.filters['lowsize']:
  144. return False
  145. if self.filters['upsize'] != 0 and int(size) < self.filters['upsize']:
  146. return False
  147. return True
  148. def cmd_list(self):
  149. for m in self.mailqueue:
  150. out = "%s\n" % m['id']
  151. out += " -date: %s\n" % m['date']
  152. out += " -size: %s\n" % m['size']
  153. out += " -active: %s\n" % str(m['active'])
  154. out += " -hold: %s\n" % str(m['hold'])
  155. out += " -to:\n"
  156. to = []
  157. i = []
  158. for n in m['queue']:
  159. i.append(n['info'])
  160. to += n['dest']
  161. out += " + %s : [%s]\n" % (",".join(n['dest']), n['info'])
  162. if self.check(m['size'], m['active'], m['hold'], to, i):
  163. print(out)
  164. def cmd_parse(self):
  165. for m in self.mailqueue:
  166. e = []
  167. i = []
  168. for n in m['queue']:
  169. i.append(n['info'])
  170. for o in n['dest']:
  171. e.append(o)
  172. if self.check(m['size'], m['active'], m['hold'], e, i):
  173. print("%s|%s|%s|%d|%d|%s" % (m['id'], m['date'], m['size'], int(m['active']), int(m['hold']), ",".join(n['dest'])))
  174. def cmd_del(self):
  175. for m in self.mailqueue:
  176. e = []
  177. i=[]
  178. for n in m['queue']:
  179. i.append(n['info'])
  180. for o in n['dest']:
  181. e.append(o)
  182. if self.check(m['size'], m['active'], m['hold'], e, i):
  183. proc = popen2.Popen3("%s %s" % (DELQ, m['id']), True)
  184. p_ret = proc.wait()
  185. if p_ret != 0:
  186. print("deleting %s [FAILED] (%s)" % (m['id'], "".join(proc.childerr.readlines()).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