pax_global_header00006660000000000000000000000064135536016610014520gustar00rootroot0000000000000052 comment=6dfafe7ca2c77ba6b463442b065412e9f66828f4 pmailq/000077500000000000000000000000001355360166100123475ustar00rootroot00000000000000pmailq/AUTHORS000066400000000000000000000002021355360166100134110ustar00rootroot00000000000000pmailq - Postfix MAIL Queue manager =================================== Main developer : Emmanuel Bouthenot pmailq/CHANGELOG000066400000000000000000000011261355360166100135610ustar00rootroot00000000000000pmailq - Postfix MAIL Queue manager =================================== ChangeLog Version 0.5 (2011-01-05): * Some rewording in manpage. Version 0.4 (2010-12-29): * Replace the usage of the deprecated popen2.Popen3() with subprocess.Popen(). * Fix some errors and do some cleanup in manpage. Version 0.3 (2010-01-31): * Fix dead lock while processing a large amount of datas in mail queue Version 0.2 (2008-01-08): * Add Changelog * Fix typos in manpage * Fix exit code on errors (http://bugs.debian.org/457299) Version 0.1 (2007-11-27): * First release pmailq/COPYING000066400000000000000000000007741355360166100134120ustar00rootroot00000000000000 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar 14 rue de Plaisance, 75014 Paris, France Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. pmailq/Makefile000066400000000000000000000004141355360166100140060ustar00rootroot00000000000000 MANPAGE_SECTION = 8 all: man man: pmailq.${MANPAGE_SECTION} pmailq.${MANPAGE_SECTION}: pmailq.pod pod2man --center "" --date "" --release "" --section=${MANPAGE_SECTION} pmailq.pod > pmailq.${MANPAGE_SECTION} clean: rm -f pmailq.${MANPAGE_SECTION} .PHOMY: man pmailq/README.md000066400000000000000000000053061355360166100136320ustar00rootroot00000000000000# pmailq - Postfix Mail Queue manager ## Description pmailq processes the output of the Postfix mailq command, and can list, parse (machine-readably), or delete queued messages in batches. They can be selected by size, by queue status, and by pattern matching (with wildcards) on recipient addresses and server error messages. ## Commands Action to perform on selected entries: **list** Show (via ***postqueue -p***)a detailed listing of the selected entries. **parse** Show a listing of the selected entries in a machine readable format. **del** Delete (via ***postsuper -d***) the selected entries. ## Options **-e PATTERN, --email=PATTERN** Select entries in queue with email matching PATTERN (wildcards allowed). **-m PATTERN, --msg=PATTERN** Select entries in queue with error message matching PATTERN (wildcards allowed). **-l SIZE, --size-lower=SIZE** Select entries in queue with size lower than SIZE bytes. **-u SIZE, --size-upper=SIZE** Select entries in queue with size upper than SIZE bytes. **-a** Select "active" entries in queue. **-o** Select "on hold" entries in queue. **--version** Show program's version number and exit **-h, --help** Show help message and exit ## Examples **Example 1** : display all the entries in queue sent to an recipient address matching "*@example.com*" with a size between 3000 and 3200 bytes in a machine readable format: > pmailq -u 3000 -l 3200 -e "*@example.com" parse 7E75214643A4|Fri Aug 31|3154|1|0|edgar@example.com B0BDE146B640|Thu Aug 30|3199|1|0|paul@example.com B587E146B675|Wed Aug 29|3065|0|0|paul@example.com CF3C514656E4|Wed Aug 29|3161|0|0|abuse@example.com /\ /\ /\ \ \ queue id date size \ \ on hold (or not) \ active (or not) **Example 2**: remove all mails in queue not sent because of a connection timeout: > pmailq -m "*connection*timed*out" del deleting 00CF514616D3 [OK] deleting 12D911461924 [OK] deleting 269EF1461CA9 [OK] deleting 288DF1461CA0 [OK] deleting 3B3901460F62 [OK] deleting 3AE58147019F [OK] ## Author [Emmanuel Bouthenot](mailto:kolter@openics.org) ## Copyright and license Copyright 2007-2019, [Emmanuel Bouthenot](mailto:kolter@openics.org) DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. pmailq/pmailq000077500000000000000000000221301355360166100135560ustar00rootroot00000000000000#!/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 ' 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 pmailq/pmailq.pod000066400000000000000000000053421355360166100143420ustar00rootroot00000000000000=head1 NAME pmailq - Postfix Mail Queue manager =head1 SYNOPSIS pmailq [I