pmailq 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #!/usr/bin/env python
  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. _NAME = 'pmailq'
  9. _HELP = "[OPTIONS] [ list | parse | del ]"
  10. _DESC = "%s postfix mail queue manager" % _NAME
  11. _VERSION = '0.3'
  12. _AUTHOR = 'Emmanuel Bouthenot <kolter@openics.org>'
  13. MAILQ = "postqueue -p"
  14. DELQ = "postsuper -d"
  15. from optparse import OptionParser, OptionGroup # needs python >= 2.3
  16. import sys, os, popen2, fcntl, select, fnmatch
  17. class Proc:
  18. def run(self, command):
  19. child = popen2.Popen3(command, 1)
  20. child.tochild.close()
  21. outfile = child.fromchild
  22. outfd = outfile.fileno()
  23. errfile = child.childerr
  24. errfd = errfile.fileno()
  25. # avoid deadlocks
  26. self.set_no_block(outfd)
  27. self.set_no_block(errfd)
  28. outdata = errdata = ''
  29. outeof = erreof = False
  30. while True:
  31. # wait for activity
  32. ready = select.select([outfd,errfd],[],[])
  33. if outfd in ready[0]:
  34. outchunk = outfile.read()
  35. if outchunk == '':
  36. outeof = True
  37. outdata = outdata + outchunk
  38. if errfd in ready[0]:
  39. errchunk = errfile.read()
  40. if errchunk == '':
  41. erreof = True
  42. errdata = errdata + errchunk
  43. if outeof and erreof:
  44. break
  45. # give a little time for buffers to fill
  46. select.select([],[],[],.1)
  47. err = child.wait()
  48. return err, outdata, errdata
  49. def set_no_block(self, fd):
  50. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  51. try:
  52. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
  53. except AttributeError:
  54. fcntl.fcntl(fd, fcntl.F_SETFL, fl | fcntl.FNDELAY)
  55. class Mailqueue:
  56. def __init__(self):
  57. self.mailqueue = []
  58. self.filters = {
  59. 'email' : None,
  60. 'msg' : None,
  61. 'lowsize' : 0,
  62. 'upsize' : 0,
  63. 'active' : False,
  64. 'hold' : False
  65. }
  66. self.parse()
  67. def add_filter(self, key, value):
  68. self.filters[key] = value
  69. def parse(self):
  70. p_ret, p_stdout, p_stderr = Proc().run(MAILQ)
  71. # mail system down ?
  72. if p_ret != 0:
  73. sys.stderr.write ("ERR : %s\n" % ", ".join(p_stderr.strip().split("\n")))
  74. sys.exit (-1)
  75. buffer = p_stdout.strip().split('\n');
  76. # checking empty mail queue
  77. if len(buffer)>0 and buffer[0].strip() == "Mail queue is empty":
  78. sys.stderr.write ("INFO : %s\n" % buffer[0].strip())
  79. return None
  80. # skip first and last line
  81. buffer = "\n".join(buffer[1:-1]).strip()
  82. for block in buffer.split("\n\n"):
  83. lines = block.split("\n")
  84. headers = lines[0].split(' ')
  85. # squeeze repeated spaces
  86. while '' in headers:
  87. headers.remove('')
  88. queue = []
  89. dest = []
  90. info = ""
  91. for expl in lines[1:]:
  92. expl = expl.strip()
  93. if expl.startswith("(") and expl.endswith(")"):
  94. if info == "":
  95. info = expl[1:len(expl)-1]
  96. if dest != []:
  97. queue.append({ "info" : info , "dest" : dest })
  98. dest = []
  99. info = expl[1:len(expl)-1]
  100. else:
  101. dest.append(expl.lower())
  102. if dest != []:
  103. queue.append({ "info" : info , "dest" : dest })
  104. self.mailqueue.append({
  105. "id" : headers[0].rstrip("*!"),
  106. "active" : headers[0].endswith("*"),
  107. "hold" : headers[0].endswith("!"),
  108. "size" : headers[1],
  109. "date" : " ".join(headers[2:5]),
  110. "queue" : queue
  111. })
  112. def check(self, size, active, hold, dest, infos):
  113. if self.filters['email'] != None:
  114. match = False
  115. for e in dest:
  116. if fnmatch.fnmatch(e.lower(), self.filters['email'].lower()):
  117. match = True
  118. if not match:
  119. return False
  120. if self.filters['msg'] != None:
  121. match = False
  122. for i in infos:
  123. if fnmatch.fnmatch(i.lower(), self.filters['msg'].lower()):
  124. match = True
  125. if not match:
  126. return False
  127. if self.filters['active'] and not active:
  128. return False
  129. if self.filters['hold'] and not hold:
  130. return False
  131. if self.filters['lowsize'] != 0 and int(size) > self.filters['lowsize']:
  132. return False
  133. if self.filters['upsize'] != 0 and int(size) < self.filters['upsize']:
  134. return False
  135. return True
  136. def cmd_list(self):
  137. for m in self.mailqueue:
  138. out = "%s\n" % m['id']
  139. out += " -date: %s\n" % m['date']
  140. out += " -size: %s\n" % m['size']
  141. out += " -active: %s\n" % str(m['active'])
  142. out += " -hold: %s\n" % str(m['hold'])
  143. out += " -to:\n"
  144. to = []
  145. i = []
  146. for n in m['queue']:
  147. i.append(n['info'])
  148. to += n['dest']
  149. out += " + %s : [%s]\n" % (",".join(n['dest']), n['info'])
  150. if self.check(m['size'], m['active'], m['hold'], to, i):
  151. print out
  152. def cmd_parse(self):
  153. for m in self.mailqueue:
  154. e = []
  155. i = []
  156. for n in m['queue']:
  157. i.append(n['info'])
  158. for o in n['dest']:
  159. e.append(o)
  160. if self.check(m['size'], m['active'], m['hold'], e, i):
  161. print "%s|%s|%s|%d|%d|%s" % (m['id'], m['date'], m['size'], int(m['active']), int(m['hold']), ",".join(n['dest']))
  162. def cmd_del(self):
  163. for m in self.mailqueue:
  164. e = []
  165. i=[]
  166. for n in m['queue']:
  167. i.append(n['info'])
  168. for o in n['dest']:
  169. e.append(o)
  170. if self.check(m['size'], m['active'], m['hold'], e, i):
  171. proc = popen2.Popen3("%s %s" % (DELQ, m['id']), True)
  172. p_ret = proc.wait()
  173. if p_ret != 0:
  174. print "deleting %s [FAILED] (%s)" % (m['id'], "".join(proc.childerr.readlines()).strip())
  175. else:
  176. print "deleting %s [OK]" % m['id']
  177. def main():
  178. usage = "%prog " + _HELP
  179. desc = _DESC
  180. parser = OptionParser(usage=usage, description=desc, version=("%s %s" % (_NAME, _VERSION)))
  181. opts = OptionGroup(parser, "filters")
  182. opts.add_option("-e", "--email", dest="email", type="string", metavar="PATTERN", help="select entries in queue with email matching PATTERN")
  183. parser.set_defaults(email=None)
  184. opts.add_option("-m", "--msg", dest="msg", type="string", metavar="PATTERN", help="select entries in queue with error message matching PATTERN")
  185. parser.set_defaults(msg=None)
  186. opts.add_option("-l", "--size-lower", dest="lowsize", type="int", metavar="SIZE", help="select entries in queue with size lower than SIZE bytes")
  187. parser.set_defaults(lowsize=0)
  188. opts.add_option("-u", "--size-upper", dest="upsize", type="int", metavar="SIZE", help="select entries in queue with size upper than SIZE bytes")
  189. parser.set_defaults(upsize=0)
  190. opts.add_option("-a", "--active", dest="active", default=False, action="store_true", help="select 'active' entries in queue (default: no)")
  191. opts.add_option("-o", "--hold", dest="hold", default=False, action="store_true", help="select 'on hold' entries in queue (default: no)")
  192. parser.add_option_group(opts)
  193. (options, args) = parser.parse_args()
  194. m = Mailqueue()
  195. m.add_filter("email", options.email)
  196. m.add_filter("msg", options.msg)
  197. m.add_filter("lowsize", options.lowsize)
  198. m.add_filter("upsize", options.upsize)
  199. m.add_filter("active", options.active)
  200. m.add_filter("hold", options.hold)
  201. if args == []:
  202. m.cmd_list()
  203. elif args[0] == "list":
  204. m.cmd_list()
  205. elif args[0] == "parse":
  206. m.cmd_parse()
  207. elif args[0] == "del":
  208. m.cmd_del()
  209. else:
  210. print "%s %s" % (_NAME, _HELP)
  211. if __name__ == "__main__":
  212. main()