getmail-4.43.0/0000755000175000017500000000000012206542270014616 5ustar charlesccharlesc00000000000000getmail-4.43.0/PKG-INFO0000644000175000017500000000255412206542270015721 0ustar charlesccharlesc00000000000000Metadata-Version: 1.0 Name: getmail Version: 4.43.0 Summary: a mail retrieval, sorting, and delivering system Home-page: http://pyropus.ca/software/getmail/ Author: Charles Cazabon Author-email: charlesc-getmail@pyropus.ca License: GNU GPL version 2 Download-URL: http://pyropus.ca/software/getmail/#download Description: getmail is a multi-protocol mail retrieval system withsupport for simple and domain POP3 and IMAP4 mailboxes, domain SDPS mailboxes, POP3-over-SSL and IMAP-over-SSL, mail sorting, message filtering, and delivery to Maildirs, Mboxrd files, external MDAs, and other advanced features. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Topic :: Communications :: Email Classifier: Topic :: Communications :: Email :: Filters Classifier: Topic :: Communications :: Email :: Post-Office :: IMAP Classifier: Topic :: Communications :: Email :: Post-Office :: POP3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities getmail-4.43.0/getmail_maildir0000755000175000017500000000517611770242175017706 0ustar charlesccharlesc00000000000000#!/usr/bin/env python '''getmail_maildir Reads a message from stdin and delivers it to a maildir specified as a commandline argument. Expects the envelope sender address to be in the environment variable SENDER. Copyright (C) 2001-2012 Charles Cazabon This program is free software; you can redistribute it and/or modify it under the terms of version 2 (only) of the GNU General Public License as published by the Free Software Foundation. A copy of this license should be included in the file COPYING. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ''' import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3 ' 'or later') import os from getmailcore.message import Message from getmailcore.utilities import * from getmailcore.exceptions import * hostname = localhostname() verbose = False path = None for arg in sys.argv[1:]: if arg in ('-h', '--help'): sys.stdout.write('Usage: %s maildirpath\n' % sys.argv[0]) raise SystemExit elif arg in ('-v', '--verbose'): verbose = True elif not path: path = arg else: raise SystemExit('Error: maildir path specified twice (was %s, now %s)' % (path, arg)) if os.name == 'posix' and (os.geteuid() == 0 or os.getegid() == 0): raise SystemExit('Error: do not run this program as user root') if not (path and (path.startswith('.') or path.startswith('/')) and path.endswith('/')): raise SystemExit('Error: maildir must start with . or / and end with /') if not is_maildir(path): raise SystemExit('Error: %s is not a maildir' % path) msg = Message(fromfile=sys.stdin) if os.environ.has_key('SENDER'): msg.sender = os.environ['SENDER'] if os.environ.has_key('RECIPIENT'): msg.recipient = os.environ['RECIPIENT'] try: d = deliver_maildir(path, msg.flatten(True, False), hostname) except getmailDeliveryError, o: raise SystemExit('Error: delivery error delivering to maildir %s (%s)' % (path, o)) except StandardError, o: raise SystemExit('Error: other error delivering to maildir %s (%s)' % (path, o)) if verbose: sys.stdout.write('Delivered to maildir %s\n' % path) getmail-4.43.0/README0000644000175000017500000000457211236652542015514 0ustar charlesccharlesc00000000000000getmail version 4 -- a flexible, extensible mail retrieval system with support for POP3, IMAP4, SSL variants of both, maildirs, mboxrd files, external MDAs, arbitrary message filtering, single-user and domain-mailboxes, and many other useful features. getmail is Copyright (C) 1998-2009 Charles Cazabon. getmail is licensed for use under the GNU General Public License version 2 (only). See docs/COPYING for specific terms and distribution information. getmail version 4 requires Python version 2.3.3 or later. You can have several installed versions of Python peacefully co-existing, see python.org for details. To install: getmail v.4 uses the standard Python distutils. Do the following as a regular user: python setup.py build Then (probably as root), do: python setup.py install This will install the software as follows: -the four scripts will be installed in /bin/ -the Python files will be installed under the site-packages directory of your Python installation (typically /usr/local/lib/pythonXXX or /usr/lib/pythonXXX, but may be elsewhere -- this is detected automatically) -the documentation will be installed under /doc/getmail-/ -the man pages will be installed under /man/ is the directory Python was configured to install under (typically either /usr/local/ or /usr/, but may be another value). See the file docs/documentation.html or docs/documentation.txt for details on installing files to directories other than these defaults. The four scripts included in the package (getmail, getmail_maildir, getmail_mbox, and getmail_fetch) will have their "#!"-interpreter line automatically modified to invoke the Python interpreter you run the setup script with. If you later remove this version of Python, and the newer version does not provide a link to the interpreter with that name, you will have to modify the scripts or re-run the setup.py script. See the HTML documentation for details on setting up and using getmail. It is included in the docs subdirectory of the distribution tarball, and will be installed in /doc/getmail-/ (by default). 10-second summary for personal use: 1. Install getmail 2. mkdir -m 700 ~/.getmail 3. Create ~/.getmail/getmailrc by following the instructions. 4. Run `getmail`. See docs/BUGS for instructions on reporting bugs in this software. getmail-4.43.0/getmail_fetch0000755000175000017500000002065111770242271017346 0ustar charlesccharlesc00000000000000#!/usr/bin/env python import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3 ' 'or later') import os import socket import poplib from optparse import OptionParser try: from getmailcore import __version__, retrievers, destinations, message, \ logging from getmailcore.exceptions import * except ImportError, o: sys.stderr.write('ImportError: %s\n' % o) sys.exit(127) log = logging.Logger() log.addhandler(sys.stderr, logging.WARNING) ####################################### class dummyInstance: pass ####################################### def blurb(): log.info('getmail_fetch version %s\n' % __version__) log.info('Copyright (C) 1998-2012 Charles Cazabon. Licensed under the ' 'GNU GPL version 2.\n') ####################################### def error_exit(v, s): sys.stderr.write(s + '\n') sys.stderr.flush() sys.exit(v) ####################################### def go(retriever, destination, options, startmsg): msgs_retrieved = 0 if options.verbose: log.addhandler(sys.stdout, logging.INFO, maxlevel=logging.INFO) blurb() try: if startmsg is not None: destination.deliver_message(startmsg, False, False) log.info('%s:\n' % retriever) retriever.initialize(options) destination.retriever_info(retriever) nummsgs = len(retriever) fmtlen = len(str(nummsgs)) for (msgnum, msgid) in enumerate(retriever): msgnum += 1 size = retriever.getmsgsize(msgid) log.info(' msg %*d/%*d (%d bytes) ...' % (fmtlen, msgnum, fmtlen, nummsgs, size)) try: msg = retriever.getmsg(msgid) msgs_retrieved += 1 destination.deliver_message(msg, False, False) log.info(' delivered') retriever.delivered(msgid) if options.delete: retriever.delmsg(msgid) log.info(', deleted') log.info('\n') except getmailDeliveryError, o: error_exit(7, 'Delivery error: %s' % o) try: retriever.quit() except getmailOperationError, o: log.warning('Operation error during quit (%s)\n' % o) except socket.timeout, o: error_exit(8, 'Timeout error: %s' % o) except socket.gaierror, o: error_exit(9, 'gaierror: %s' % o) except socket.error, o: error_exit(10, 'Socket error: %s' % o) except poplib.error_proto, o: error_exit(11, 'Protocol error: %s' % o) except getmailCredentialError, o: error_exit(13, 'Credential error: %s' % o) except getmailOperationError, o: error_exit(12, 'Operational error: %s' % o) log.info('%d messages retrieved\n' % msgs_retrieved) ####################################### def main(): try: parser = OptionParser( version='%%prog %s' % __version__, usage='%prog [options] server username password destination' ) parser.add_option('-q', '--quiet', action='store_false', dest='verbose', help='output only on error') parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=True, help='output informational messages ' '(default: verbose)') parser.add_option('-m', '--message', action='store', dest='message', help='deliver message from FILE first', metavar='FILE') parser.add_option('-p', '--port', action='store', type='int', dest='port', metavar='PORT', default=None, help='use server port PORT (default: POP: 110, ' 'POP3-over-SSL: 995)') parser.add_option('-d', '--delete', action='store_true', dest='delete', default=False, help='delete messages after retrieval (default: no)') parser.add_option('-t', '--timeout', action='store', type='int', dest='timeout', metavar='SECS', default=180, help='use timeout SECS (default: 180)') parser.add_option('-a', '--apop', action='store_true', dest='apop', default=False, help='use APOP authentication (default: no)') parser.add_option('-s', '--ssl', action='store_true', dest='ssl', default=False, help='use POP3-over-SSL (default: no)') (options, args) = parser.parse_args(sys.argv[1:]) if len(args) != 4: raise getmailOperationError('incorrect arguments; try --help' % args) msg = None if options.message is not None: try: f = open(options.message, 'rb') msg = message.Message(fromfile=f) except IOError, o: error_exit( 1, 'Error reading message file "%s": %s' % (options.message, o) ) instance = dummyInstance() # Retriever if options.ssl: retriever_func = retrievers.BrokenUIDLPOP3SSLRetriever port = options.port or 995 else: retriever_func = retrievers.BrokenUIDLPOP3Retriever port = options.port or 110 retriever_args = { 'getmaildir' : os.getcwd(), 'configparser' : instance, 'timeout' : options.timeout, 'server' : args[0], 'port' : port, 'username' : args[1], 'password' : args[2], 'use_apop' : options.apop } try: retriever = retriever_func(**retriever_args) retriever.checkconf() except getmailOperationError, o: error_exit(3, 'Error initializing retriever: %s' % o) # Destination destination = args[3].strip() if destination.startswith('.') or destination.startswith('/'): if destination.endswith('/'): destination_func = destinations.Maildir destination_args = { 'path' : destination, 'configparser' : instance, } else: destination_func = destinations.Mboxrd destination_args = { 'path' : destination, 'configparser' : instance, } elif destination.startswith('|'): destination_func = destinations.MDA_external parts = destination[1:].split() cmd = parts[0] arguments = str(tuple(parts[1:])) destination_args = { 'path' : cmd, 'arguments' : arguments, 'allow_root_commands' : False, 'configparser' : instance, } else: error_exit(4, 'Unknown destination type "%s"' % destination) try: destination = destination_func(**destination_args) except getmailOperationError, o: error_exit( 5, 'Error initializing destination "%s": %s' % (destination, o) ) go(retriever, destination, options, msg) except KeyboardInterrupt: error_exit(6, 'Operation aborted by user (keyboard interrupt)') except getmailOperationError, o: error_exit(7, 'Operation error: %s' % o) except getmailConfigurationError, o: error_exit(8, 'Configuration error: %s' % o) except StandardError, o: log.critical('\nException: please read docs/BUGS and include the ' 'following information in any bug report:\n\n') log.critical(' getmail_fetch version %s\n' % __version__) log.critical(' Python version %s\n\n' % sys.version) log.critical('Unhandled exception follows:\n') exc_type, value, tb = sys.exc_info() import traceback tblist = (traceback.format_tb(tb, None) + traceback.format_exception_only(exc_type, value)) if type(tblist) != list: tblist = [tblist] for line in tblist: log.critical(' %s\n' % line.rstrip()) sys.exit(9) ####################################### if __name__ == '__main__': main() getmail-4.43.0/getmail0000755000175000017500000011376612206541102016175 0ustar charlesccharlesc00000000000000#!/usr/bin/env python import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3 ' 'or later') import os.path import time import ConfigParser import poplib import imaplib import pprint from optparse import OptionParser, OptionGroup import socket import signal # Optional gnome-keyring integration try: import gnomekeyring import glib glib.set_application_name('getmail') # And test to see if it's actually available if not gnomekeyring.is_available(): gnomekeyring = None except ImportError: gnomekeyring = None options_bool = ( 'read_all', 'delete', 'delivered_to', 'received', 'message_log_verbose', 'message_log_syslog', ) options_int = ( 'delete_after', 'delete_bigger_than', 'max_message_size', 'max_messages_per_session', 'max_bytes_per_session', 'verbose', ) options_str = ( 'message_log', ) # Unix only try: import syslog except ImportError: pass try: from getmailcore import __version__, retrievers, destinations, filters, \ logging from getmailcore.exceptions import * from getmailcore.utilities import eval_bool, logfile, format_params, \ address_no_brackets, expand_user_vars, get_password except ImportError, o: sys.stderr.write('ImportError: %s\n' % o) sys.exit(127) log = logging.Logger() log.addhandler(sys.stdout, logging.INFO, maxlevel=logging.INFO) log.addhandler(sys.stderr, logging.WARNING) defaults = { 'getmaildir' : '~/.getmail/', 'rcfile' : 'getmailrc', 'verbose' : 1, 'read_all' : True, 'delete' : False, 'delete_after' : 0, 'delete_bigger_than' : 0, 'max_message_size' : 0, 'max_messages_per_session' : 0, 'max_bytes_per_session' : 0, 'delivered_to' : True, 'received' : True, 'message_log' : None, 'message_log_verbose' : False, 'message_log_syslog' : False, 'logfile' : None, } ####################################### def convert_to_sigint(unused1, unused2): """Catch a SIGTERM and raise a SIGINT so getmail exits normally and does cleanup if killed with default signal. """ raise KeyboardInterrupt('from signal') signal.signal(signal.SIGTERM, convert_to_sigint) ####################################### def blurb(): log.info('getmail version %s\n' % __version__) log.info('Copyright (C) 1998-2012 Charles Cazabon. Licensed under the ' 'GNU GPL version 2.\n') ####################################### def go(configs, idle): """Main code. Returns True if all goes well, False if any error condition occurs. """ blurb() summary = [] errorexit = False idling = False if len(configs) > 1 and idle: log.info('more than one config file given with --idle, ignoring\n') idle = False for (configfile, retriever, _filters, destination, options) in configs: if options['read_all'] and not options['delete']: if idle: # This is a nonsense combination of options; every time the # server returns from IDLE, all messages will be re-retrieved. log.error('%s: IDLE, read_all, and not delete - bad ' 'combination, skipping\n' % retriever) continue else: # Slightly less nonsensical, but still weird. log.warning('%s: read_all and not delete -- all messages will ' 'be retrieved each time getmail is run\n' % retriever) oplevel = options['verbose'] logverbose = options['message_log_verbose'] now = int(time.time()) msgs_retrieved = 0 bytes_retrieved = 0 msgs_skipped = 0 if options['message_log_syslog']: syslog.openlog('getmail', 0, syslog.LOG_MAIL) try: if not idling: log.info('%s:\n' % retriever) logline = 'Initializing %s:' % retriever if options['logfile'] and logverbose: options['logfile'].write(logline) if options['message_log_syslog'] and logverbose: syslog.syslog(syslog.LOG_INFO, logline) retriever.initialize(options) destination.retriever_info(retriever) for mailbox in retriever.mailboxes: if mailbox: # For POP this is None and uninteresting log.debug(' checking mailbox %s ...\n' % mailbox) try: retriever.select_mailbox(mailbox) except getmailMailboxSelectError, o: errorexit = True log.info(' mailbox %s not selectable (%s) - verify the ' 'mailbox exists and you have sufficient ' 'permissions\n' % (mailbox, o)) continue nummsgs = len(retriever) fmtlen = len(str(nummsgs)) for (msgnum, msgid) in enumerate(retriever): log.debug(' message %s ...\n' % msgid) msgnum += 1 retrieve = False reason = 'seen' delete = False timestamp = retriever.oldmail.get(msgid, None) size = retriever.getmsgsize(msgid) info = ('msg %*d/%*d (%d bytes)' % (fmtlen, msgnum, fmtlen, nummsgs, size)) logline = '%s msgid %s' % (info, msgid) if options['read_all'] or timestamp is None: retrieve = True if (options['max_message_size'] and size > options['max_message_size']): retrieve = False reason = 'oversized' if (options['max_bytes_per_session'] and (bytes_retrieved + size) > options['max_bytes_per_session']): retrieve = False reason = 'would surpass max_bytes_per_session' try: if retrieve: try: msg = retriever.getmsg(msgid) except getmailRetrievalError, o: errorexit = True log.error( 'Retrieval error: server for %s is broken; ' 'offered message %s but failed to provide it. ' 'Please notify the administrator of the ' 'server. Skipping message...\n' % (retriever, msgid) ) continue msgs_retrieved += 1 bytes_retrieved += size if oplevel > 1: info += (' from <%s>' % address_no_brackets(msg.sender)) if msg.recipient is not None: info += (' to <%s>' % address_no_brackets(msg.recipient)) logline += (' from <%s>' % address_no_brackets(msg.sender)) if msg.recipient is not None: logline += (' to <%s>' % address_no_brackets(msg.recipient)) for mail_filter in _filters: log.debug(' passing to filter %s\n' % mail_filter) msg = mail_filter.filter_message(msg, retriever) if msg is None: log.debug(' dropped by filter %s\n' % mail_filter) info += (' dropped by filter %s' % mail_filter) logline += (' dropped by filter %s' % mail_filter) retriever.delivered(msgid) break if msg is not None: r = destination.deliver_message(msg, options['delivered_to'], options['received']) log.debug(' delivered to %s\n' % r) info += ' delivered' if oplevel > 1: info += (' to %s' % r) logline += (' delivered to %s' % r) retriever.delivered(msgid) if options['delete']: delete = True else: logline += ' not retrieved (%s)' % reason msgs_skipped += 1 log.debug(' not retrieving (timestamp %s)\n' % timestamp) if oplevel > 1: info += ' not retrieved (%s)' % reason if (options['delete_after'] and timestamp and (now - timestamp) / 86400 >= options['delete_after']): log.debug( ' older than %d days (%s seconds), will delete\n' % (options['delete_after'], (now - timestamp)) ) delete = True if options['delete'] and timestamp: log.debug(' will delete\n') delete = True if (options['delete_bigger_than'] and size > options['delete_bigger_than']): log.debug(' bigger than %d, will delete\n' % options['delete_bigger_than']) delete = True if not retrieve and timestamp is None: # We haven't retrieved this message. Don't delete it. log.debug(' not yet retrieved, not deleting\n') delete = False if delete: retriever.delmsg(msgid) log.debug(' deleted\n') info += ', deleted' logline += ', deleted' except getmailDeliveryError, o: errorexit = True log.error('Delivery error (%s)\n' % o) info += ', delivery error (%s)' % o if options['logfile']: options['logfile'].write('Delivery error (%s)' % o) if options['message_log_syslog']: syslog.syslog(syslog.LOG_ERR, 'Delivery error (%s)' % o) except getmailFilterError, o: errorexit = True log.error('Filter error (%s)\n' % o) info += ', filter error (%s)' % o if options['logfile']: options['logfile'].write('Filter error (%s)' % o) if options['message_log_syslog']: syslog.syslog(syslog.LOG_ERR, 'Filter error (%s)' % o) if (retrieve or delete or oplevel > 1): log.info(' %s\n' % info) if options['logfile'] and (retrieve or delete or logverbose): options['logfile'].write(logline) if options['message_log_syslog'] and (retrieve or delete or logverbose): syslog.syslog(syslog.LOG_INFO, logline) if (options['max_messages_per_session'] and msgs_retrieved >= options['max_messages_per_session']): log.debug('hit max_messages_per_session (%d), breaking\n' % options['max_messages_per_session']) if oplevel > 1: log.info(' max messages per session (%d)\n' % options['max_messages_per_session']) raise StopIteration('max_messages_per_session %d' % options['max_messages_per_session']) except StopIteration: pass except KeyboardInterrupt, o: log.warning('%s: user aborted\n' % configfile) if options['logfile']: options['logfile'].write('user aborted') except socket.timeout, o: errorexit = True retriever.abort() if type(o) == tuple and len(o) > 1: o = o[1] log.error('%s: timeout (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('timeout error (%s)' % o) except (poplib.error_proto, imaplib.IMAP4.abort), o: errorexit = True retriever.abort() log.error('%s: protocol error (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('protocol error (%s)' % o) except socket.gaierror, o: errorexit = True retriever.abort() if type(o) == tuple and len(o) > 1: o = o[1] log.error('%s: error resolving name (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('gaierror error (%s)' % o) except socket.error, o: errorexit = True retriever.abort() if type(o) == tuple and len(o) > 1: o = o[1] log.error('%s: socket error (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('socket error (%s)' % o) except getmailCredentialError, o: errorexit = True retriever.abort() log.error('%s: credential/login error (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('credential/login error (%s)' % o) except getmailOperationError, o: errorexit = True retriever.abort() log.error('%s: operation error (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('getmailOperationError error (%s)' % o) if options['message_log_syslog']: syslog.syslog(syslog.LOG_ERR, 'getmailOperationError error (%s)' % o) summary.append( (retriever, msgs_retrieved, bytes_retrieved, msgs_skipped) ) log.info(' %d messages (%d bytes) retrieved, %d skipped\n' % (msgs_retrieved, bytes_retrieved, msgs_skipped)) if options['logfile'] and logverbose: options['logfile'].write( ' %d messages (%d bytes) retrieved, %d skipped\n' % (msgs_retrieved, bytes_retrieved, msgs_skipped) ) log.debug('retriever %s finished\n' % retriever) try: if idle and not retriever.supports_idle: log.info('--idle given, but chosen retriever does not support' 'IDLE\n') idle = False if not errorexit and idle: # TODO # Okay, so what should really happen here is that when go_idle # returns, getmail should use the *existing* connection to check # for new messages and then call go_idle again once that is # done. The current code layout doesn't lend itself very well to # that since the message download code is coupled with the # connection setup/teardown code. # # Therefore, we do a bit of a hack. # We add the current config back into configs, so that when the # main for loop over configs runs again, it will find the same # config again, and thus download the new messages and then go # back to IDLEing. Since the return value of go_idle changes the # value of idling, a failed connection will cause it to become # False, which will make the main go() loop reconnect, which is # what we want. try: idling = retriever.go_idle(idle) configs.append(configs[0]) continue except KeyboardInterrupt, o: # Because configs isn't appended to, this just means we'll # quit, which is presumably what the user wanted # The newline is to clear the ^C shown in terminal log.info('\n') pass retriever.quit() except getmailOperationError, o: errorexit = True log.debug('%s: operation error during quit (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('%s: operation error during quit (%s)' % (configfile, o)) if sum([i for (unused, i, unused, unused) in summary]) and oplevel > 1: log.info('Summary:\n') for (retriever, msgs_retrieved, bytes_retrieved, unused) in summary: log.info('Retrieved %d messages (%s bytes) from %s\n' % (msgs_retrieved, bytes_retrieved, retriever)) return (not errorexit) ####################################### def main(): try: parser = OptionParser(version='%%prog %s' % __version__) parser.add_option( '-g', '--getmaildir', dest='getmaildir', action='store', default=defaults['getmaildir'], help='look in DIR for config/data files', metavar='DIR' ) parser.add_option( '-r', '--rcfile', dest='rcfile', action='append', default=[], help='load configuration from FILE (may be given multiple times)', metavar='FILE' ) parser.add_option( '--dump', dest='dump_config', action='store_true', default=False, help='dump configuration and exit (debugging)' ) parser.add_option( '--trace', dest='trace', action='store_true', default=False, help='print extended trace information (extremely verbose)' ) parser.add_option( '-i', '--idle', dest='idle', action='store', default='', help='maintain connection and listen for new messages in FOLDER. ' 'Only applies if a single rc file is given with a connection ' 'to an IMAP server that supports the IDLE command', metavar='FOLDER' ) if gnomekeyring: parser.add_option( '--store-password-in-gnome-keyring', dest='store_gnome_keyring', action='store_true', default=False, help='store the POP/IMAP password in the Gnome keyring' ) overrides = OptionGroup( parser, 'Overrides', 'The following options override those specified in any ' 'getmailrc file.' ) overrides.add_option( '-v', '--verbose', dest='override_verbose', action='count', help='operate more verbosely (may be given multiple times)' ) overrides.add_option( '-q', '--quiet', dest='override_verbose', action='store_const', const=0, help='operate quietly (only report errors)' ) overrides.add_option( '-d', '--delete', dest='override_delete', action='store_true', help='delete messages from server after retrieving' ) overrides.add_option( '-l', '--dont-delete', dest='override_delete', action='store_false', help='do not delete messages from server after retrieving' ) overrides.add_option( '-a', '--all', dest='override_read_all', action='store_true', help='retrieve all messages' ) overrides.add_option( '-n', '--new', dest='override_read_all', action='store_false', help='retrieve only unread messages' ) parser.add_option_group(overrides) (options, args) = parser.parse_args(sys.argv[1:]) if args: raise getmailOperationError('unknown argument(s) %s ; try --help' % args) if options.trace: log.clearhandlers() if not options.rcfile: options.rcfile.append(defaults['rcfile']) s = '' for attr in dir(options): if attr.startswith('_'): continue if s: s += ',' s += '%s="%s"' % (attr, pprint.pformat(getattr(options, attr))) log.debug('parsed options: %s\n' % s) getmaildir_type = 'Default' if options.getmaildir != defaults['getmaildir']: getmaildir_type = 'Specified' getmaildir = expand_user_vars(options.getmaildir) if not os.path.exists(getmaildir): raise getmailOperationError( '%s config/data dir "%s" does not exist - create ' 'or specify alternate directory with --getmaildir option' % (getmaildir_type, getmaildir) ) if not os.path.isdir(getmaildir): raise getmailOperationError( '%s config/data dir "%s" is not a directory - fix ' 'or specify alternate directory with --getmaildir option' % (getmaildir_type, getmaildir) ) if not os.access(getmaildir, os.W_OK): raise getmailOperationError( '%s config/data dir "%s" is not writable - fix permissions ' 'or specify alternate directory with --getmaildir option' % (getmaildir_type, getmaildir) ) configs = [] for filename in options.rcfile: path = os.path.join(os.path.expanduser(options.getmaildir), filename) log.debug('processing rcfile %s\n' % path) if not os.path.exists(path): raise getmailOperationError('configuration file %s does ' 'not exist' % path) elif not os.path.isfile(path): raise getmailOperationError('%s is not a file' % path) f = open(path, 'rb') config = { 'verbose' : defaults['verbose'], 'read_all' : defaults['read_all'], 'delete' : defaults['delete'], 'delete_after' : defaults['delete_after'], 'delete_bigger_than' : defaults['delete_bigger_than'], 'max_message_size' : defaults['max_message_size'], 'max_messages_per_session' : defaults['max_messages_per_session'], 'max_bytes_per_session' : defaults['max_bytes_per_session'], 'delivered_to' : defaults['delivered_to'], 'received' : defaults['received'], 'logfile' : defaults['logfile'], 'message_log' : defaults['message_log'], 'message_log_verbose' : defaults['message_log_verbose'], 'message_log_syslog' : defaults['message_log_syslog'], } # Python's ConfigParser .getboolean() couldn't handle booleans in # the defaults. Submitted a patch; they fixed it a different way. # But for the extant, unfixed versions, an ugly hack.... parserdefaults = config.copy() for (key, value) in parserdefaults.items(): if type(value) == bool: parserdefaults[key] = str(value) try: configparser = ConfigParser.RawConfigParser(parserdefaults) configparser.readfp(f, path) for option in options_bool: log.debug(' looking for option %s ... ' % option) if configparser.has_option('options', option): log.debug('got "%s"' % configparser.get('options', option)) try: config[option] = configparser.getboolean( 'options', option ) log.debug('-> %s' % config[option]) except ValueError: raise getmailConfigurationError( 'configuration file %s incorrect (option %s ' 'must be boolean, not %s)' % (path, option, configparser.get('options', option)) ) else: log.debug('not found') log.debug('\n') for option in options_int: log.debug(' looking for option %s ... ' % option) if configparser.has_option('options', option): log.debug( 'got "%s"' % configparser.get('options', option) ) try: config[option] = configparser.getint('options', option) log.debug('-> %s' % config[option]) except ValueError: raise getmailConfigurationError( 'configuration file %s incorrect (option %s ' 'must be integer, not %s)' % (path, option, configparser.get('options', option)) ) else: log.debug('not found') log.debug('\n') # Message log file for option in options_str: log.debug(' looking for option %s ... ' % option) if configparser.has_option('options', option): log.debug('got "%s"' % configparser.get('options', option)) config[option] = configparser.get('options', option) log.debug('-> %s' % config[option]) else: log.debug('not found') log.debug('\n') if config['message_log']: try: config['logfile'] = logfile(config['message_log']) except IOError, o: raise getmailConfigurationError( 'error opening message_log file %s (%s)' % (config['message_log'], o) ) # Clear out the ConfigParser defaults before processing further # sections configparser._defaults = {} # Retriever log.debug(' getting retriever\n') retriever_type = configparser.get('retriever', 'type') log.debug(' type="%s"\n' % retriever_type) retriever_func = getattr(retrievers, retriever_type) if not callable(retriever_func): raise getmailConfigurationError( 'configuration file %s specifies incorrect ' 'retriever type (%s)' % (path, retriever_type) ) retriever_args = { 'getmaildir' : options.getmaildir, 'configparser' : configparser, } for (name, value) in configparser.items('retriever'): if name in ('type', 'configparser'): continue if name == 'password': log.debug(' parameter %s=*\n' % name) else: log.debug(' parameter %s="%s"\n' % (name, value)) retriever_args[name] = value log.debug(' instantiating retriever %s with args %s\n' % (retriever_type, format_params(retriever_args))) try: retriever = retriever_func(**retriever_args) log.debug(' checking retriever configuration for %s\n' % retriever) retriever.checkconf() except getmailOperationError, o: log.error('Error initializing retriever: %s\n' % o) continue # Retriever is okay. Check if user wants us to store the # password in a Gnome keyring for future use. if gnomekeyring and options.store_gnome_keyring: # Need to get the password first, if the user hasn't put # it in the rc file. if retriever.conf.get('password', None) is None: password = get_password( str(retriever), retriever.conf['username'], retriever.conf['server'], retriever.received_with, log ) else: password = retriever.conf['password'] gnomekeyring.set_network_password_sync( # keyring=None, user, domain=None, server, object=None, # protocol, authtype=None, port=0 None, retriever.conf['username'], None, retriever.conf['server'], None, retriever.received_with, None, 0, password ) log.info('Stored password in Gnome keyring. Exiting.\n') raise SystemExit() # Destination log.debug(' getting destination\n') destination_type = configparser.get('destination', 'type') log.debug(' type="%s"\n' % destination_type) destination_func = getattr(destinations, destination_type) if not callable(destination_func): raise getmailConfigurationError( 'configuration file %s specifies incorrect destination ' 'type (%s)' % (path, destination_type) ) destination_args = {'configparser' : configparser} for (name, value) in configparser.items('destination'): if name in ('type', 'configparser'): continue if name == 'password': log.debug(' parameter %s=*\n' % name) else: log.debug(' parameter %s="%s"\n' % (name, value)) destination_args[name] = value log.debug(' instantiating destination %s with args %s\n' % (destination_type, format_params(destination_args))) destination = destination_func(**destination_args) # Filters log.debug(' getting filters\n') _filters = [] filtersections = [ section.lower() for section in configparser.sections() if section.lower().startswith('filter') ] filtersections.sort() for section in filtersections: log.debug(' processing filter section %s\n' % section) filter_type = configparser.get(section, 'type') log.debug(' type="%s"\n' % filter_type) filter_func = getattr(filters, filter_type) if not callable(filter_func): raise getmailConfigurationError( 'configuration file %s specifies incorrect filter ' 'type (%s)' % (path, filter_type) ) filter_args = {'configparser' : configparser} for (name, value) in configparser.items(section): if name in ('type', 'configparser'): continue if name == 'password': log.debug(' parameter %s=*\n' % name) else: log.debug(' parameter %s="%s"\n' % (name, value)) filter_args[name] = value log.debug(' instantiating filter %s with args %s\n' % (filter_type, format_params(filter_args))) mail_filter = filter_func(**filter_args) _filters.append(mail_filter) except ConfigParser.NoSectionError, o: raise getmailConfigurationError( 'configuration file %s missing section (%s)' % (path, o) ) except ConfigParser.NoOptionError, o: raise getmailConfigurationError( 'configuration file %s missing option (%s)' % (path, o) ) except (ConfigParser.DuplicateSectionError, ConfigParser.InterpolationError, ConfigParser.MissingSectionHeaderError, ConfigParser.ParsingError), o: raise getmailConfigurationError( 'configuration file %s incorrect (%s)' % (path, o) ) except getmailConfigurationError, o: raise getmailConfigurationError( 'configuration file %s incorrect (%s)' % (path, o) ) # Apply overrides from commandline for option in ('read_all', 'delete', 'verbose'): val = getattr(options, 'override_%s' % option) if val is not None: log.debug('overriding option %s from commandline %s\n' % (option, val)) config[option] = val if config['verbose'] > 2: config['verbose'] = 2 if not options.trace and config['verbose'] == 0: log.clearhandlers() log.addhandler(sys.stderr, logging.WARNING) configs.append((os.path.basename(filename), retriever, _filters, destination, config.copy())) if options.dump_config: # Override any "verbose = 0" in the config file log.clearhandlers() log.addhandler(sys.stdout, logging.INFO, maxlevel=logging.INFO) log.addhandler(sys.stderr, logging.WARNING) blurb() for (filename, retriever, _filters, destination, config) in configs: log.info('getmail configuration:\n') log.info(' getmail version %s\n' % __version__) log.info(' Python version %s\n' % sys.version) log.info(' retriever: ') retriever.showconf() if _filters: for _filter in _filters: log.info(' filter: ') _filter.showconf() log.info(' destination: ') destination.showconf() log.info(' options:\n') names = config.keys() names.sort() for name in names: log.info(' %s : %s\n' % (name, config[name])) log.info('\n') sys.exit() # Go! success = go(configs, options.idle) if not success: raise SystemExit(127) except KeyboardInterrupt: log.warning('Operation aborted by user (keyboard interrupt)\n') sys.exit(0) except getmailConfigurationError, o: log.error('Configuration error: %s\n' % o) sys.exit(2) except getmailOperationError, o: log.error('Error: %s\n' % o) sys.exit(3) except StandardError, o: log.critical( '\nException: please read docs/BUGS and include the ' 'following information in any bug report:\n\n' ) log.critical(' getmail version %s\n' % __version__) log.critical(' Python version %s\n\n' % sys.version) log.critical('Unhandled exception follows:\n') (exc_type, value, tb) = sys.exc_info() import traceback tblist = (traceback.format_tb(tb, None) + traceback.format_exception_only(exc_type, value)) if type(tblist) != list: tblist = [tblist] for line in tblist: log.critical(' %s\n' % line.rstrip()) log.critical('\nPlease also include configuration information ' 'from running getmail\n') log.critical('with your normal options plus "--dump".\n') sys.exit(4) ####################################### if __name__ == '__main__': main() getmail-4.43.0/getmailcore/0000755000175000017500000000000012206542270017111 5ustar charlesccharlesc00000000000000getmail-4.43.0/getmailcore/logging.py0000755000175000017500000000674411131473115021124 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Logging support for getmail. The new standard Python libary module logging didn't cut it for me; it doesn't seem capable of handling some very simple requirements like logging messages of a certain level to one fd, and other messages of higher levels to a different fd (i.e. info to stdout, warnings to stderr). ''' __all__ = [ 'Logger', ] import sys import os.path import traceback from getmailcore.constants import * ####################################### class _Logger(object): '''Class for logging. Do not instantiate directly; use Logger() instead, to keep this a singleton. ''' def __init__(self): '''Create a logger.''' self.handlers = [] self.newline = False def __call__(self): return self def addhandler(self, stream, minlevel, maxlevel=CRITICAL): '''Add a handler for logged messages. Logged messages of at least level (and at most level , default CRITICAL) will be output to . If no handlers are specified, messages of all levels will be output to stdout. ''' self.handlers.append({'minlevel' : minlevel, 'stream' : stream, 'newline' : True, 'maxlevel' : maxlevel}) def clearhandlers(self): '''Clear the list of handlers. There should be a way to remove only one handler from a list. But that would require an easy way for the caller to distinguish between them. ''' self.handlers = [] def log(self, msglevel, msgtxt): '''Log a message of level containing text .''' for handler in self.handlers: if msglevel < handler['minlevel'] or msglevel > handler['maxlevel']: continue if not handler['newline'] and msglevel == DEBUG: handler['stream'].write('\n') handler['stream'].write(msgtxt) handler['stream'].flush() if msgtxt.endswith('\n'): handler['newline'] = True else: handler['newline'] = False if not self.handlers: if not self.newline and msglevel == DEBUG: sys.stdout.write('\n') sys.stdout.write(msgtxt) sys.stdout.flush() if msgtxt.endswith('\n'): self.newline = True else: self.newline = False def trace(self, msg='trace\n'): '''Log a message with level TRACE. The message will be prefixed with filename, line number, and function name of the calling code. ''' trace = traceback.extract_stack()[-2] msg = '%s [%s:%i] %s' % (trace[FUNCNAME] + '()', os.path.basename(trace[FILENAME]), trace[LINENO], msg ) self.log(TRACE, msg) def debug(self, msg): '''Log a message with level DEBUG.''' self.log(DEBUG, msg) def moreinfo(self, msg): '''Log a message with level MOREINFO.''' self.log(MOREINFO, msg) def info(self, msg): '''Log a message with level INFO.''' self.log(INFO, msg) def warning(self, msg): '''Log a message with level WARNING.''' self.log(WARNING, msg) def error(self, msg): '''Log a message with level ERROR.''' self.log(ERROR, msg) def critical(self, msg): '''Log a message with level CRITICAL.''' self.log(CRITICAL, msg) Logger = _Logger() getmail-4.43.0/getmailcore/_retrieverbases.py0000755000175000017500000015373412206537021022665 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Base and mix-in classes implementing retrievers (message sources getmail can retrieve mail from). None of these classes can be instantiated directly. In this module: Mix-in classes for SSL/non-SSL initialization: POP3initMixIn Py23POP3SSLinitMixIn Py24POP3SSLinitMixIn IMAPinitMixIn IMAPSSLinitMixIn Base classes: RetrieverSkeleton POP3RetrieverBase MultidropPOP3RetrieverBase IMAPRetrieverBase MultidropIMAPRetrieverBase ''' __all__ = [ 'IMAPinitMixIn', 'IMAPRetrieverBase', 'IMAPSSLinitMixIn', 'MultidropPOP3RetrieverBase', 'MultidropIMAPRetrieverBase', 'POP3_ssl_port', 'POP3initMixIn', 'POP3RetrieverBase', 'POP3SSLinitMixIn', 'RetrieverSkeleton', ] import sys import os import socket import time import email import poplib import imaplib import re import select try: # do we have a recent pykerberos? HAVE_KERBEROS_GSS = False import kerberos if 'authGSSClientWrap' in dir(kerberos): HAVE_KERBEROS_GSS = True except ImportError: pass from getmailcore.compatibility import * from getmailcore.exceptions import * from getmailcore.constants import * from getmailcore.message import * from getmailcore.utilities import * from getmailcore._pop3ssl import POP3SSL, POP3_ssl_port from getmailcore.baseclasses import * import getmailcore.imap_utf7 # registers imap4-utf-7 codec NOT_ENVELOPE_RECIPIENT_HEADERS = ( 'to', 'cc', 'bcc', 'received', 'resent-to', 'resent-cc', 'resent-bcc' ) # How long a vanished message is kept in the oldmail state file for IMAP # retrievers before we figure it's gone for good. This is to allow users # to only occasionally retrieve mail from certain IMAP folders without # losing their oldmail state for that folder. This is in seconds, so it's # 30 days. VANISHED_AGE = (60 * 60 * 24 * 30) # Regex used to remove problematic characters from oldmail filenames STRIP_CHAR_RE = r'[/\:;<>|]+' # Kerberos authentication state constants (GSS_STATE_STEP, GSS_STATE_WRAP) = (0, 1) # For matching imap LIST responses IMAP_LISTPARTS = re.compile( r'^\s*' r'\((?P[^)]*)\)' r'\s+' r'"(?P[^"]+)"' r'\s+' # I *think* this should actually be a double-quoted string "like/this" # but in testing we saw an MSexChange response that violated that # expectation: # (\HasNoChildren) "/" Calendar" # i.e. the leading quote on the mailbox name was missing. The following # works for both by treating the leading/trailing double-quote as optional, # even when mismatched. r'("?)(?P.+?)("?)' r'\s*$' ) # Constants used in socket module NO_OBJ = object() EAI_NONAME = getattr(socket, 'EAI_NONAME', NO_OBJ) EAI_NODATA = getattr(socket, 'EAI_NODATA', NO_OBJ) EAI_FAIL = getattr(socket, 'EAI_FAIL', NO_OBJ) # # Mix-in classes # ####################################### class POP3initMixIn(object): '''Mix-In class to do POP3 non-SSL initialization. ''' SSL = False def _connect(self): self.log.trace() try: self.conn = poplib.POP3(self.conf['server'], self.conf['port']) self.setup_received(self.conn.sock) except poplib.error_proto, o: raise getmailOperationError('POP error (%s)' % o) except socket.timeout: raise #raise getmailOperationError('timeout during connect') except socket.gaierror, o: raise getmailOperationError( 'error resolving name %s during connect (%s)' % (self.conf['server'], o) ) self.log.trace('POP3 connection %s established' % self.conn + os.linesep) ####################################### class Py24POP3SSLinitMixIn(object): '''Mix-In class to do POP3 over SSL initialization with Python 2.4's poplib.POP3_SSL class. ''' SSL = True def _connect(self): self.log.trace() if not hasattr(socket, 'ssl'): raise getmailConfigurationError( 'SSL not supported by this installation of Python' ) (keyfile, certfile) = check_ssl_key_and_cert(self.conf) try: if keyfile: self.log.trace( 'establishing POP3 SSL connection to %s:%d with ' 'keyfile %s, certfile %s' % (self.conf['server'], self.conf['port'], keyfile, certfile) + os.linesep ) self.conn = poplib.POP3_SSL( self.conf['server'], self.conf['port'], keyfile, certfile ) else: self.log.trace('establishing POP3 SSL connection to %s:%d' % (self.conf['server'], self.conf['port']) + os.linesep) self.conn = poplib.POP3_SSL(self.conf['server'], self.conf['port']) self.setup_received(self.conn.sock) except poplib.error_proto, o: raise getmailOperationError('POP error (%s)' % o) except socket.timeout: #raise getmailOperationError('timeout during connect') raise except socket.gaierror, o: raise getmailOperationError( 'error resolving name %s during connect (%s)' % (self.conf['server'], o) ) self.conn.sock.setblocking(1) self.log.trace('POP3 connection %s established' % self.conn + os.linesep) ####################################### class Py23POP3SSLinitMixIn(object): '''Mix-In class to do POP3 over SSL initialization with custom-implemented code to support SSL with Python 2.3's poplib.POP3 class. ''' SSL = True def _connect(self): self.log.trace() if not hasattr(socket, 'ssl'): raise getmailConfigurationError( 'SSL not supported by this installation of Python' ) (keyfile, certfile) = check_ssl_key_and_cert(self.conf) try: if keyfile: self.log.trace( 'establishing POP3 SSL connection to %s:%d with keyfile ' '%s, certfile %s' % (self.conf['server'], self.conf['port'], keyfile, certfile) + os.linesep ) self.conn = POP3SSL(self.conf['server'], self.conf['port'], keyfile, certfile) else: self.log.trace( 'establishing POP3 SSL connection to %s:%d' % (self.conf['server'], self.conf['port']) + os.linesep ) self.conn = POP3SSL(self.conf['server'], self.conf['port']) self.setup_received(self.conn.rawsock) except poplib.error_proto, o: raise getmailOperationError('POP error (%s)' % o) except socket.timeout: #raise getmailOperationError('timeout during connect') raise except socket.gaierror, o: raise getmailOperationError('socket error during connect (%s)' % o) except socket.sslerror, o: raise getmailOperationError( 'socket sslerror during connect (%s)' % o ) self.log.trace('POP3 SSL connection %s established' % self.conn + os.linesep) ####################################### class IMAPinitMixIn(object): '''Mix-In class to do IMAP non-SSL initialization. ''' SSL = False def _connect(self): self.log.trace() try: self.conn = imaplib.IMAP4(self.conf['server'], self.conf['port']) self.setup_received(self.conn.sock) except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) except socket.timeout: #raise getmailOperationError('timeout during connect') raise except socket.gaierror, o: raise getmailOperationError('socket error during connect (%s)' % o) self.log.trace('IMAP connection %s established' % self.conn + os.linesep) ####################################### class IMAPSSLinitMixIn(object): '''Mix-In class to do IMAP over SSL initialization. ''' SSL = True def _connect(self): self.log.trace() if not hasattr(socket, 'ssl'): raise getmailConfigurationError( 'SSL not supported by this installation of Python' ) (keyfile, certfile) = check_ssl_key_and_cert(self.conf) try: if keyfile: self.log.trace( 'establishing IMAP SSL connection to %s:%d with keyfile ' '%s, certfile %s' % (self.conf['server'], self.conf['port'], keyfile, certfile) + os.linesep ) self.conn = imaplib.IMAP4_SSL( self.conf['server'], self.conf['port'], keyfile, certfile ) else: self.log.trace( 'establishing IMAP SSL connection to %s:%d' % (self.conf['server'], self.conf['port']) + os.linesep ) self.conn = imaplib.IMAP4_SSL(self.conf['server'], self.conf['port']) self.setup_received(self.conn.sock) except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) except socket.timeout: #raise getmailOperationError('timeout during connect') raise except socket.gaierror, o: errcode = o[0] if errcode in (EAI_NONAME, EAI_NODATA): # No such DNS name raise getmailDnsLookupError( 'no address for %s (%s)' % (self.conf['server'], o) ) elif errcode == EAI_FAIL: # DNS server failure raise getmailDnsServerFailure( 'DNS server failure looking up address for %s (%s)' % (self.conf['server'], o) ) else: raise getmailOperationError('socket error during connect (%s)' % o) except socket.sslerror, o: raise getmailOperationError( 'socket sslerror during connect (%s)' % o ) self.log.trace('IMAP SSL connection %s established' % self.conn + os.linesep) # # Base classes # ####################################### class RetrieverSkeleton(ConfigurableBase): '''Base class for implementing message-retrieval classes. Sub-classes should provide the following data attributes and methods: _confitems - a tuple of dictionaries representing the parameters the class takes. Each dictionary should contain the following key, value pairs: - name - parameter name - type - a type function to compare the parameter value against (i.e. str, int, bool) - default - optional default value. If not present, the parameter is required. __str__(self) - return a simple string representing the class instance. _getmsglist(self) - retieve a list of all available messages, and store unique message identifiers in the dict self.msgnum_by_msgid. Message identifiers must be unique and persistent across instantiations. Also store message sizes (in octets) in a dictionary self.msgsizes, using the message identifiers as keys. _delmsgbyid(self, msgid) - delete a message from the message store based on its message identifier. _getmsgbyid(self, msgid) - retreive and return a message from the message store based on its message identifier. The message is returned as a Message() class object. The message will have additional data attributes "sender" and "recipient". sender should be present or "unknown". recipient should be non-None if (and only if) the protocol/method of message retrieval preserves the original message envelope. _getheaderbyid(self, msgid) - similar to _getmsgbyid() above, but only the message header should be retrieved, if possible. It should be returned in the same format. showconf(self) - should invoke self.log.info() to display the configuration of the class instance. Sub-classes may also wish to extend or over-ride the following base class methods: __init__(self, **args) __del__(self) initialize(self, options) checkconf(self) ''' def __init__(self, **args): self.headercache = {} self.deleted = {} self.timestamp = int(time.time()) self.__oldmail_written = False self.__initialized = False self.gotmsglist = False self._clear_state() self.conn = None self.supports_idle = False ConfigurableBase.__init__(self, **args) def _clear_state(self): self.msgnum_by_msgid = {} self.msgid_by_msgnum = {} self.sorted_msgnum_msgid = () self.msgsizes = {} self.oldmail = {} self.__delivered = {} self.mailbox_selected = False def setup_received(self, sock): serveraddr = sock.getpeername() if len(serveraddr) == 2: # IPv4 self.remoteaddr = '%s:%s' % serveraddr elif len(serveraddr) == 4: # IPv6 self.remoteaddr = '[%s]:%s' % serveraddr[:2] else: # Shouldn't happen self.log.warn('unexpected peer address format %s', str(serveraddr)) self.remoteaddr = str(serveraddr) self.received_from = '%s (%s)' % (self.conf['server'], self.remoteaddr) def __str__(self): self.log.trace() return str(self.conf) def list_mailboxes(self): raise NotImplementedError('virtual') def select_mailbox(self, mailbox): raise NotImplementedError('virtual') def __len__(self): self.log.trace() return len(self.msgnum_by_msgid) def __getitem__(self, i): self.log.trace('i == %d' % i) if not self.__initialized: raise getmailOperationError('not initialized') return self.sorted_msgnum_msgid[i][1] def _oldmail_filename(self, mailbox): assert (mailbox is None or (isinstance(mailbox, (str, unicode)) and mailbox)), ( 'bad mailbox %s (%s)' % (mailbox, type(mailbox)) ) filename = self.oldmail_filename if mailbox is not None: if isinstance(mailbox, str): mailbox = mailbox.decode('utf-8') mailbox = re.sub(STRIP_CHAR_RE, '.', mailbox) mailbox = mailbox.encode('utf-8') # Use oldmail file per IMAP folder filename += '-' + mailbox # else: # mailbox is None, is POP, just use filename return filename def oldmail_exists(self, mailbox): '''Test whether an oldmail file exists for a specified mailbox.''' return os.path.isfile(self._oldmail_filename(mailbox)) def read_oldmailfile(self, mailbox): '''Read contents of an oldmail file. For POP, mailbox must be explicitly None. ''' assert not self.oldmail, ( 'still have %d unflushed oldmail' % len(self.oldmail) ) self.log.trace('mailbox=%s' % mailbox) filename = self._oldmail_filename(mailbox) logname = '%s:%s' % (self, mailbox or '') try: f = open(filename, 'rb') except IOError: self.log.moreinfo('no oldmail file for %s%s' % (logname, os.linesep)) return for line in f: line = line.strip() if not line or not '\0' in line: # malformed continue try: (msgid, timestamp) = line.split('\0', 1) if msgid.count('/') == 2: # Was pre-4.22.0 file format, which includes the # mailbox name in the msgid, in the format # 'uidvalidity/mailbox/serveruid'. # Strip it out. fields = msgid.split('/') msgid = '/'.join([fields[0], fields[2]]) self.oldmail[msgid] = int(timestamp) except ValueError: # malformed self.log.info( 'skipped malformed line "%r" for %s%s' % (line, logname, os.linesep) ) self.log.moreinfo( 'read %i uids for %s%s' % (len(self.oldmail), logname, os.linesep) ) self.log.moreinfo('read %i uids in total for %s%s' % (len(self.oldmail), logname, os.linesep)) def write_oldmailfile(self, mailbox): '''Write oldmail info to oldmail file.''' self.log.trace('mailbox=%s' % mailbox) filename = self._oldmail_filename(mailbox) logname = '%s:%s' % (self, mailbox or '') oldmailfile = None wrote = 0 msgids = frozenset( self.__delivered.keys() ).union(frozenset(self.oldmail.keys())) try: oldmailfile = updatefile(filename) for msgid in msgids: self.log.debug('msgid %s ...' % msgid) t = self.oldmail.get(msgid, self.timestamp) self.log.debug(' timestamp %s' % t + os.linesep) oldmailfile.write('%s\0%i%s' % (msgid, t, os.linesep)) wrote += 1 oldmailfile.close() self.log.moreinfo('wrote %i uids for %s%s' % (wrote, logname, os.linesep)) except IOError, o: self.log.error('failed writing oldmail file for %s (%s)' % (logname, o) + os.linesep) if oldmailfile: oldmailfile.abort() self.__oldmail_written = True def initialize(self, options): # Options - dict of application-wide settings, including ones that # aren't used in initializing the retriever. self.log.trace() self.checkconf() # socket.ssl() and socket timeouts are incompatible in Python 2.3 if 'timeout' in self.conf: socket.setdefaulttimeout(self.conf['timeout']) else: # Explicitly set to None in case it was previously set socket.setdefaulttimeout(None) # Construct base filename for oldmail files. # strip problematic characters from oldmail filename. Mostly for # non-Unix systems; only / is illegal in a Unix path component oldmail_filename = re.sub( STRIP_CHAR_RE, '-', 'oldmail-%(server)s-%(port)i-%(username)s' % self.conf ) self.oldmail_filename = os.path.join(self.conf['getmaildir'], oldmail_filename) self.received_from = None self.app_options = options self.__initialized = True def quit(self): if self.mailbox_selected is not False: self.write_oldmailfile(self.mailbox_selected) self._clear_state() def abort(self): '''On error conditions where you do not want modified state to be saved, call this before .quit(). ''' self._clear_state() def delivered(self, msgid): self.__delivered[msgid] = None def getheader(self, msgid): if not self.__initialized: raise getmailOperationError('not initialized') if not msgid in self.headercache: self.headercache[msgid] = self._getheaderbyid(msgid) return self.headercache[msgid] def getmsg(self, msgid): if not self.__initialized: raise getmailOperationError('not initialized') return self._getmsgbyid(msgid) def getmsgsize(self, msgid): if not self.__initialized: raise getmailOperationError('not initialized') try: return self.msgsizes[msgid] except KeyError: raise getmailOperationError('no such message ID %s' % msgid) def delmsg(self, msgid): if not self.__initialized: raise getmailOperationError('not initialized') self._delmsgbyid(msgid) self.deleted[msgid] = True ####################################### class POP3RetrieverBase(RetrieverSkeleton): '''Base class for single-user POP3 mailboxes. ''' def __init__(self, **args): RetrieverSkeleton.__init__(self, **args) self.log.trace() def select_mailbox(self, mailbox): assert mailbox is None, ( 'POP does not support mailbox selection (%s)' % mailbox ) if self.mailbox_selected is not False: self.write_oldmailfile(self.mailbox_selected) self._clear_state() if self.oldmail_exists(mailbox): self.read_oldmailfile(mailbox) self.mailbox_selected = mailbox self._getmsglist() def _getmsgnumbyid(self, msgid): self.log.trace() if not msgid in self.msgnum_by_msgid: raise getmailOperationError('no such message ID %s' % msgid) return self.msgnum_by_msgid[msgid] def _getmsglist(self): self.log.trace() try: (response, msglist, octets) = self.conn.uidl() self.log.debug('UIDL response "%s", %d octets' % (response, octets) + os.linesep) for (i, line) in enumerate(msglist): try: (msgnum, msgid) = line.split(None, 1) # Don't allow / in UIDs we store, as we look for that to # detect old-style oldmail files. Shouldn't occur in POP3 # anyway. msgid = msgid.replace('/', '-') except ValueError: # Line didn't contain two tokens. Server is broken. raise getmailOperationError( '%s failed to identify message index %d in UIDL output' ' -- see documentation or use ' 'BrokenUIDLPOP3Retriever instead' % (self, i) ) msgnum = int(msgnum) if msgid in self.msgnum_by_msgid: # UIDL "unique" identifiers weren't unique. # Server is broken. if self.conf.get('delete_dup_msgids', False): self.log.debug('deleting message %s with duplicate ' 'msgid %s' % (msgnum, msgid) + os.linesep) self.conn.dele(msgnum) else: raise getmailOperationError( '%s does not uniquely identify messages ' '(got %s twice) -- see documentation or use ' 'BrokenUIDLPOP3Retriever instead' % (self, msgid) ) else: self.msgnum_by_msgid[msgid] = msgnum self.msgid_by_msgnum[msgnum] = msgid self.log.debug('Message IDs: %s' % sorted(self.msgnum_by_msgid.keys()) + os.linesep) self.sorted_msgnum_msgid = sorted(self.msgid_by_msgnum.items()) (response, msglist, octets) = self.conn.list() for line in msglist: msgnum = int(line.split()[0]) msgsize = int(line.split()[1]) msgid = self.msgid_by_msgnum.get(msgnum, None) # If no msgid found, it's a message that wasn't in the UIDL # response above. Ignore it and we'll get it next time. if msgid is not None: self.msgsizes[msgid] = msgsize # Remove messages from state file that are no longer in mailbox, # but only if the timestamp for them are old (30 days for now). # This is because IMAP users can have one state file but multiple # IMAP folders in different configuration rc files. for msgid in self.oldmail.keys(): timestamp = self.oldmail[msgid] age = self.timestamp - timestamp if not self.msgsizes.has_key(msgid) and age > VANISHED_AGE: self.log.debug('removing vanished old message id %s' % msgid + os.linesep) del self.oldmail[msgid] except poplib.error_proto, o: raise getmailOperationError( 'POP error (%s) - if your server does not support the UIDL ' 'command, use BrokenUIDLPOP3Retriever instead' % o ) self.gotmsglist = True def _delmsgbyid(self, msgid): self.log.trace() msgnum = self._getmsgnumbyid(msgid) self.conn.dele(msgnum) def _getmsgbyid(self, msgid): self.log.debug('msgid %s' % msgid + os.linesep) msgnum = self._getmsgnumbyid(msgid) self.log.debug('msgnum %i' % msgnum + os.linesep) try: response, lines, octets = self.conn.retr(msgnum) self.log.debug('RETR response "%s", %d octets' % (response, octets) + os.linesep) msg = Message(fromlines=lines+['']) return msg except poplib.error_proto, o: raise getmailRetrievalError( 'failed to retrieve msgid %s; server said %s' % (msgid, o) ) def _getheaderbyid(self, msgid): self.log.trace() msgnum = self._getmsgnumbyid(msgid) response, headerlist, octets = self.conn.top(msgnum, 0) parser = email.Parser.HeaderParser() return parser.parsestr(os.linesep.join(headerlist)) def initialize(self, options): self.log.trace() # POP doesn't support different mailboxes self.mailboxes = (None, ) # Handle password if self.conf.get('password', None) is None: self.conf['password'] = get_password( self, self.conf['username'], self.conf['server'], self.received_with, self.log ) RetrieverSkeleton.initialize(self, options) try: self._connect() if self.conf['use_apop']: self.conn.apop(self.conf['username'], self.conf['password']) else: self.conn.user(self.conf['username']) self.conn.pass_(self.conf['password']) self._getmsglist() self.log.debug('msgids: %s' % sorted(self.msgnum_by_msgid.keys()) + os.linesep) self.log.debug('msgsizes: %s' % self.msgsizes + os.linesep) # Remove messages from state file that are no longer in mailbox for msgid in self.oldmail.keys(): if not self.msgsizes.has_key(msgid): self.log.debug('removing vanished message id %s' % msgid + os.linesep) del self.oldmail[msgid] except poplib.error_proto, o: raise getmailOperationError('POP error (%s)' % o) def abort(self): self.log.trace() RetrieverSkeleton.abort(self) if not self.conn: return try: self.conn.rset() self.conn.quit() except (poplib.error_proto, socket.error), o: pass self.conn = None def quit(self): RetrieverSkeleton.quit(self) self.log.trace() if not self.conn: return try: self.conn.quit() except (poplib.error_proto, socket.error), o: raise getmailOperationError('POP error (%s)' % o) except AttributeError: pass self.conn = None ####################################### class MultidropPOP3RetrieverBase(POP3RetrieverBase): '''Base retriever class for multi-drop POP3 mailboxes. Envelope is reconstructed from Return-Path: (sender) and a header specified by the user (recipient). This header is specified with the "envelope_recipient" parameter, which takes the form [:]. field-number defaults to 1 and is counted from top to bottom in the message. For instance, if the envelope recipient is present in the second Delivered-To: header field of each message, envelope_recipient should be specified as "delivered-to:2". ''' def initialize(self, options): self.log.trace() POP3RetrieverBase.initialize(self, options) self.envrecipname = ( self.conf['envelope_recipient'].split(':')[0].lower() ) if self.envrecipname in NOT_ENVELOPE_RECIPIENT_HEADERS: raise getmailConfigurationError( 'the %s header field does not record the envelope ' 'recipient address' ) self.envrecipnum = 0 try: self.envrecipnum = int( self.conf['envelope_recipient'].split(':', 1)[1] ) - 1 if self.envrecipnum < 0: raise ValueError(self.conf['envelope_recipient']) except IndexError: pass except ValueError, o: raise getmailConfigurationError( 'invalid envelope_recipient specification format (%s)' % o ) def _getmsgbyid(self, msgid): self.log.trace() msg = POP3RetrieverBase._getmsgbyid(self, msgid) data = {} for (name, val) in msg.headers(): name = name.lower() val = val.strip() if name in data: data[name].append(val) else: data[name] = [val] try: line = data[self.envrecipname][self.envrecipnum] except (KeyError, IndexError), unused: raise getmailConfigurationError( 'envelope_recipient specified header missing (%s)' % self.conf['envelope_recipient'] ) msg.recipient = address_no_brackets(line.strip()) return msg ####################################### class IMAPRetrieverBase(RetrieverSkeleton): '''Base class for single-user IMAP mailboxes. ''' def __init__(self, **args): RetrieverSkeleton.__init__(self, **args) self.log.trace() self.gss_step = 0 self.gss_vc = None self.gssapi = False def _clear_state(self): RetrieverSkeleton._clear_state(self) self.mailbox = None self.uidvalidity = None self.msgnum_by_msgid = {} self.msgid_by_msgnum = {} self.sorted_msgnum_msgid = () self._mboxuids = {} self._mboxuidorder = [] self.msgsizes = {} self.oldmail = {} self.__delivered = {} def checkconf(self): RetrieverSkeleton.checkconf(self) if self.conf['use_kerberos'] and not HAVE_KERBEROS_GSS: raise getmailConfigurationError( 'cannot use kerberos authentication; Python kerberos support ' 'not installed or does not support GSS' ) def gssauth(self, response): if not HAVE_KERBEROS_GSS: # shouldn't get here raise ValueError('kerberos GSS support not available') data = ''.join(str(response).encode('base64').splitlines()) if self.gss_step == GSS_STATE_STEP: if not self.gss_vc: (rc, self.gss_vc) = kerberos.authGSSClientInit( 'imap@%s' % self.conf['server'] ) response = kerberos.authGSSClientResponse(self.gss_vc) rc = kerberos.authGSSClientStep(self.gss_vc, data) if rc != kerberos.AUTH_GSS_CONTINUE: self.gss_step = GSS_STATE_WRAP elif self.gss_step == GSS_STATE_WRAP: rc = kerberos.authGSSClientUnwrap(self.gss_vc, data) response = kerberos.authGSSClientResponse(self.gss_vc) rc = kerberos.authGSSClientWrap(self.gss_vc, response, self.conf['username']) response = kerberos.authGSSClientResponse(self.gss_vc) if not response: response = '' return response.decode('base64') def _getmboxuidbymsgid(self, msgid): self.log.trace() if not msgid in self.msgnum_by_msgid: raise getmailOperationError('no such message ID %s' % msgid) uid = self._mboxuids[msgid] return uid def _parse_imapcmdresponse(self, cmd, *args): self.log.trace() try: result, resplist = getattr(self.conn, cmd)(*args) except imaplib.IMAP4.error, o: if cmd == 'login': # Percolate up raise else: raise getmailOperationError('IMAP error (%s)' % o) if result != 'OK': raise getmailOperationError( 'IMAP error (command %s returned %s %s)' % ('%s %s' % (cmd, args), result, resplist) ) if cmd.lower().startswith('login'): self.log.debug('login command response %s' % resplist + os.linesep) else: self.log.debug( 'command %s response %s' % ('%s %s' % (cmd, args), resplist) + os.linesep ) return resplist def _parse_imapuidcmdresponse(self, cmd, *args): self.log.trace() try: result, resplist = self.conn.uid(cmd, *args) except imaplib.IMAP4.error, o: if cmd == 'login': # Percolate up raise else: raise getmailOperationError('IMAP error (%s)' % o) if result != 'OK': raise getmailOperationError( 'IMAP error (command %s returned %s %s)' % ('%s %s' % (cmd, args), result, resplist) ) self.log.debug('command uid %s response %s' % ('%s %s' % (cmd, args), resplist) + os.linesep) return resplist def _parse_imapattrresponse(self, line): self.log.trace('parsing attributes response line %s' % line + os.linesep) r = {} try: parts = line[line.index('(') + 1:line.rindex(')')].split() while parts: # Flags starts a parenthetical list of valueless flags if parts[0].lower() == 'flags' and parts[1].startswith('('): while parts and not parts[0].endswith(')'): del parts[0] if parts: # Last one, ends with ")" del parts[0] continue if len(parts) == 1: # Leftover part -- not name, value pair. raise ValueError name = parts.pop(0).lower() r[name] = parts.pop(0) except (ValueError, IndexError), o: raise getmailOperationError( 'IMAP error (failed to parse UID response line "%s")' % line ) self.log.trace('got %s' % r + os.linesep) return r def list_mailboxes(self): '''List (selectable) IMAP folders in account.''' mailboxes = [] cmd = ('LIST', ) resplist = self._parse_imapcmdresponse(*cmd) for item in resplist: m = IMAP_LISTPARTS.match(item) if not m: raise getmailOperationError( 'no match for list response "%s"' % item ) g = m.groupdict() attributes = g['attributes'].split() if r'\Noselect' in attributes: # Can't select this mailbox, don't include it in output continue try: mailbox = g['mailbox'].decode('imap4-utf-7') mailboxes.append(mailbox) #log.debug(u'%20s : delimiter %s, attributes: %s', # mailbox, g['delimiter'], ', '.join(attributes)) except Exception, o: raise getmailOperationError('error decoding mailbox "%s"' % g['mailbox']) return mailboxes def select_mailbox(self, mailbox): self.log.trace() assert mailbox in self.mailboxes, ( 'mailbox not in config (%s)' % mailbox ) if self.mailbox_selected is not False: # Close current mailbox so deleted mail is expunged. # Except one user reports that an explicit expunge is needed with # his server, or else the mail is never removed from the mailbox. self.conn.expunge() self.conn.close() self.write_oldmailfile(self.mailbox_selected) self._clear_state() if self.oldmail_exists(mailbox): self.read_oldmailfile(mailbox) self.log.debug('selecting mailbox "%s"' % mailbox + os.linesep) try: if (self.app_options['delete'] or self.app_options['delete_after'] or self.app_options['delete_bigger_than']): read_only = False else: read_only = True (status, count) = self.conn.select(mailbox.encode('imap4-utf-7'), read_only) if status == 'NO': # Specified mailbox doesn't exist, no permissions, etc. raise getmailMailboxSelectError(mailbox) self.mailbox_selected = mailbox # use *last* EXISTS returned count = int(count[-1]) uidvalidity = self.conn.response('UIDVALIDITY')[1][0] except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) except (IndexError, ValueError), o: raise getmailOperationError( 'IMAP server failed to return correct SELECT response (%s)' % o ) self.log.debug('select(%s) returned message count of %d' % (mailbox, count) + os.linesep) self.mailbox = mailbox self.uidvalidity = uidvalidity self._getmsglist(count) return count def _getmsglist(self, msgcount): self.log.trace() try: if msgcount: # Get UIDs and sizes for all messages in mailbox response = self._parse_imapcmdresponse( 'FETCH', '1:%d' % msgcount, '(UID RFC822.SIZE)' ) for line in response: r = self._parse_imapattrresponse(line) # Don't allow / in UIDs we store, as we look for that to # detect old-style oldmail files. Can occur with IMAP, at # least with some servers. uid = r['uid'].replace('/', '-') msgid = '%s/%s' % (self.uidvalidity, uid) self._mboxuids[msgid] = r['uid'] self._mboxuidorder.append(msgid) self.msgnum_by_msgid[msgid] = None self.msgsizes[msgid] = int(r['rfc822.size']) # Remove messages from state file that are no longer in mailbox, # but only if the timestamp for them are old (30 days for now). # This is because IMAP users can have one state file but multiple # IMAP folders in different configuration rc files. for msgid in self.oldmail.keys(): timestamp = self.oldmail[msgid] age = self.timestamp - timestamp if not self.msgsizes.has_key(msgid) and age > VANISHED_AGE: self.log.debug('removing vanished old message id %s' % msgid + os.linesep) del self.oldmail[msgid] except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) self.gotmsglist = True def __getitem__(self, i): return self._mboxuidorder[i] def _delmsgbyid(self, msgid): self.log.trace() try: uid = self._getmboxuidbymsgid(msgid) #self._selectmailbox(mailbox) # Delete message if self.conf['move_on_delete']: self.log.debug('copying message to folder "%s"' % self.conf['move_on_delete'] + os.linesep) response = self._parse_imapuidcmdresponse( 'COPY', uid, self.conf['move_on_delete'] ) self.log.debug('deleting message "%s"' % uid + os.linesep) response = self._parse_imapuidcmdresponse( 'STORE', uid, 'FLAGS', '(\Deleted)' ) except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) def _getmsgpartbyid(self, msgid, part): self.log.trace() try: uid = self._getmboxuidbymsgid(msgid) # Retrieve message self.log.debug('retrieving body for message "%s"' % uid + os.linesep) try: response = self._parse_imapuidcmdresponse('FETCH', uid, part) except (imaplib.IMAP4.error, getmailOperationError), o: # server gave a negative/NO response, most likely. Bad server, # no doughnut. raise getmailRetrievalError( 'failed to retrieve msgid %s; server said %s' % (msgid, o) ) # Response is really ugly: # # [ # ( # '1 (UID 1 RFC822 {704}', # 'message text here with CRLF EOL' # ), # ')', # # ] # MSExchange is broken -- if a message is badly formatted enough # (virus, spam, trojan), it can completely fail to return the # message when requested. try: try: sbody = response[0][1] except Exception, o: sbody = None if not sbody: self.log.error('bad message from server!') sbody = str(response) msg = Message(fromstring=sbody) except TypeError, o: # response[0] is None instead of a message tuple raise getmailRetrievalError('failed to retrieve msgid %s' % msgid) # record mailbox retrieved from in a header msg.add_header('X-getmail-retrieved-from-mailbox', self.mailbox_selected) # google extensions: apply labels, etc if 'X-GM-EXT-1' in self.conn.capabilities: metadata = self._getgmailmetadata(uid, msg) for (header, value) in metadata.items(): msg.add_header(header, value) return msg except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) def _getgmailmetadata(self, uid, msg): """ Add Gmail labels and other metadata which Google exposes through an IMAP extension to headers in the message. See https://developers.google.com/google-apps/gmail/imap_extensions """ try: # ['976 (X-GM-THRID 1410134259107225671 X-GM-MSGID ' # '1410134259107225671 X-GM-LABELS (labels space ' # 'separated) UID 167669)'] response = self._parse_imapuidcmdresponse('FETCH', uid, '(X-GM-LABELS X-GM-THRID X-GM-MSGID)') except imaplib.IMAP4.error, o: self.log.warning('Could not fetch google imap extensions: %s' % o) return {} if not response: return {} ext = re.search( 'X-GM-THRID (?P\d+) X-GM-MSGID (?P\d+)' ' X-GM-LABELS \((?P.*)\) UID', response[0] ) if not ext: self.log.warning( 'Could not parse google imap extensions. Server said: %s' % repr(response) ) return {} results = ext.groupdict() metadata = {} for item in ('LABELS', 'THRID', 'MSGID'): if item in results and results[item]: metadata['X-GMAIL-%s' % item] = results[item] return metadata def _getmsgbyid(self, msgid): self.log.trace() if self.conf.get('use_peek', True): part = '(BODY.PEEK[])' else: part = '(RFC822)' return self._getmsgpartbyid(msgid, part) def _getheaderbyid(self, msgid): self.log.trace() if self.conf.get('use_peek', True): part = '(BODY.PEEK[header])' else: part = '(RFC822[header])' return self._getmsgpartbyid(msgid, part) def initialize(self, options): self.log.trace() self.mailboxes = self.conf.get('mailboxes', ('INBOX', )) # Handle password if (self.conf.get('password', None) is None and not (HAVE_KERBEROS_GSS and self.conf['use_kerberos'])): self.conf['password'] = get_password( self, self.conf['username'], self.conf['server'], self.received_with, self.log ) RetrieverSkeleton.initialize(self, options) try: self.log.trace('trying self._connect()' + os.linesep) self._connect() try: self.log.trace('logging in' + os.linesep) if self.conf['use_kerberos'] and HAVE_KERBEROS_GSS: self.conn.authenticate('GSSAPI', self.gssauth) elif self.conf['use_cram_md5']: self._parse_imapcmdresponse( 'login_cram_md5', self.conf['username'], self.conf['password'] ) else: self._parse_imapcmdresponse('login', self.conf['username'], self.conf['password']) except imaplib.IMAP4.abort, o: raise getmailLoginRefusedError(o) except imaplib.IMAP4.error, o: raise getmailCredentialError(o) self.log.trace('logged in' + os.linesep) """ self.log.trace('logged in, getting message list' + os.linesep) self._getmsglist() self.log.debug('msgids: %s' % sorted(self.msgnum_by_msgid.keys()) + os.linesep) self.log.debug('msgsizes: %s' % self.msgsizes + os.linesep) # Remove messages from state file that are no longer in mailbox, # but only if the timestamp for them are old (30 days for now). # This is because IMAP users can have one state file but multiple # IMAP folders in different configuration rc files. for msgid in self.oldmail.keys(): timestamp = self.oldmail[msgid] age = self.timestamp - timestamp if not self.msgsizes.has_key(msgid) and age > VANISHED_AGE: self.log.debug('removing vanished old message id %s' % msgid + os.linesep) del self.oldmail[msgid] """ if 'IDLE' in self.conn.capabilities: self.supports_idle = True imaplib.Commands['IDLE'] = ('AUTH', 'SELECTED') if self.mailboxes == ('ALL', ): # Special value meaning all mailboxes in account self.mailboxes = tuple(self.list_mailboxes()) except imaplib.IMAP4.error, o: raise getmailOperationError('IMAP error (%s)' % o) def abort(self): self.log.trace() RetrieverSkeleton.abort(self) if not self.conn: return try: self.quit() except (imaplib.IMAP4.error, socket.error), o: pass self.conn = None def go_idle(self, folder, timeout=29*60): """Initiates IMAP's IDLE mode if the server supports it Waits until state of current mailbox changes, and then returns. Returns True if the connection still seems to be up, False otherwise. May throw getmailOperationError if the server refuses the IDLE setup (e.g. if the server does not support IDLE) Default timeout is the maximum possible idle time according to RFC 2177. """ if not self.supports_idle: self.log.warning('IDLE not supported, so not idling') raise getmailOperationError( 'IMAP4 IDLE requested, but not supported by server' ) if self.SSL: sock = self.conn.ssl() else: sock = self.conn.socket() # Based on current imaplib IDLE patch: http://bugs.python.org/issue11245 self.conn.untagged_responses = {} self.conn.select(folder) tag = self.conn._command('IDLE') data = self.conn._get_response() # read continuation response if data is not None: raise getmailOperationError( 'IMAP4 IDLE requested, but server refused IDLE request: %s' % data ) self.log.debug('Entering IDLE mode (server says "%s")\n' % self.conn.continuation_response) try: aborted = None (readable, unused, unused) = select.select([sock], [], [], timeout) except KeyboardInterrupt, o: # Delay raising this until we've stopped IDLE mode aborted = o if aborted is not None: self.log.debug('IDLE mode cancelled\n') elif readable: # The socket has data waiting; server has updated status self.log.info('IDLE message received\n') else: self.log.debug('IDLE timeout (%ds)\n' % timeout) try: self.conn.untagged_responses = {} self.conn.send('DONE\r\n') self.conn._command_complete('IDLE', tag) except imaplib.IMAP4.error, o: return False if aborted: raise aborted return True def quit(self): self.log.trace() if not self.conn: return try: if self.mailbox_selected is not False: # Close current mailbox so deleted mail is expunged. # Except one user reports that an explicit expunge is needed # with his server, or else the mail is never removed from the # mailbox. self.conn.expunge() self.conn.close() self.conn.logout() except imaplib.IMAP4.error, o: #raise getmailOperationError('IMAP error (%s)' % o) self.log.warning('IMAP error during logout (%s)' % o + os.linesep) RetrieverSkeleton.quit(self) self.conn = None ####################################### class MultidropIMAPRetrieverBase(IMAPRetrieverBase): '''Base retriever class for multi-drop IMAP mailboxes. Envelope is reconstructed from Return-Path: (sender) and a header specified by the user (recipient). This header is specified with the "envelope_recipient" parameter, which takes the form [:]. field-number defaults to 1 and is counted from top to bottom in the message. For instance, if the envelope recipient is present in the second Delivered-To: header field of each message, envelope_recipient should be specified as "delivered-to:2". ''' def initialize(self, options): self.log.trace() IMAPRetrieverBase.initialize(self, options) self.envrecipname = (self.conf['envelope_recipient'].split(':') [0].lower()) if self.envrecipname in NOT_ENVELOPE_RECIPIENT_HEADERS: raise getmailConfigurationError( 'the %s header field does not record the envelope recipient ' 'address' ) self.envrecipnum = 0 try: self.envrecipnum = int( self.conf['envelope_recipient'].split(':', 1)[1] ) - 1 if self.envrecipnum < 0: raise ValueError(self.conf['envelope_recipient']) except IndexError: pass except ValueError, o: raise getmailConfigurationError( 'invalid envelope_recipient specification format (%s)' % o ) def _getmsgbyid(self, msgid): self.log.trace() msg = IMAPRetrieverBase._getmsgbyid(self, msgid) data = {} for (name, val) in msg.headers(): name = name.lower() val = val.strip() if name in data: data[name].append(val) else: data[name] = [val] try: line = data[self.envrecipname][self.envrecipnum] except (KeyError, IndexError), unused: raise getmailConfigurationError( 'envelope_recipient specified header missing (%s)' % self.conf['envelope_recipient'] ) msg.recipient = address_no_brackets(line.strip()) return msg # Choose right POP-over-SSL mix-in based on Python version being used. if sys.hexversion >= 0x02040000: POP3SSLinitMixIn = Py24POP3SSLinitMixIn else: POP3SSLinitMixIn = Py23POP3SSLinitMixIn getmail-4.43.0/getmailcore/constants.py0000755000175000017500000000033611131473115021501 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 # Log levels (TRACE, DEBUG, MOREINFO, INFO, WARNING, ERROR, CRITICAL) = range(1, 8) # Components of stack trace (indices to tuple) FILENAME = 0 LINENO = 1 FUNCNAME = 2 #SOURCELINE = 3 ; not used getmail-4.43.0/getmailcore/filters.py0000755000175000017500000004632412177046065021157 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Classes implementing message filters. Currently implemented: ''' __all__ = [ 'FilterSkeleton', 'Filter_external', 'Filter_classifier', 'Filter_TMDA', ] import os import tempfile import types from getmailcore.exceptions import * from getmailcore.compatibility import * from getmailcore.message import * from getmailcore.utilities import * from getmailcore.baseclasses import * ####################################### class FilterSkeleton(ConfigurableBase): '''Base class for implementing message-filtering classes. Sub-classes should provide the following data attributes and methods: _confitems - a tuple of dictionaries representing the parameters the class takes. Each dictionary should contain the following key, value pairs: - name - parameter name - type - a type function to compare the parameter value against (i.e. str, int, bool) - default - optional default value. If not preseent, the parameter is required. __str__(self) - return a simple string representing the class instance. showconf(self) - log a message representing the instance and configuration from self._confstring(). initialize(self) - process instantiation parameters from self.conf. Raise getmailConfigurationError on errors. Do any other validation necessary, and set self.__initialized when done. _filter_message(self, msg) - accept the message and deliver it, returning a tuple (exitcode, newmsg, err). exitcode should be 0 for success, 99 or 100 for success but drop the message, anything else for error. err should be an empty string on success, or an error message otherwise. newmsg is an email.Message() object representing the message in filtered form, or None on error or when dropping the message. See the Filter_external class for a good (though not simple) example. ''' def __init__(self, **args): ConfigurableBase.__init__(self, **args) try: self.initialize() except KeyError, o: raise getmailConfigurationError( 'missing required configuration parameter %s' % o ) self.log.trace('done\n') def filter_message(self, msg, retriever): self.log.trace() msg.received_from = retriever.received_from msg.received_with = retriever.received_with msg.received_by = retriever.received_by exitcode, newmsg, err = self._filter_message(msg) if exitcode in self.exitcodes_drop: # Drop message self.log.debug('filter %s returned %d; dropping message\n' % (self, exitcode)) return None elif (exitcode not in self.exitcodes_keep): raise getmailFilterError('filter %s returned %d (%s)\n' % (self, exitcode, err)) elif err: if self.conf['ignore_stderr']: self.log.info('filter %s: %s\n' % (self, err)) else: raise getmailFilterError( 'filter %s returned %d but wrote to stderr: %s\n' % (self, exitcode, err) ) # Check the filter was sane if len(newmsg.headers()) < len(msg.headers()): # Warn user self.log.warning( 'Warning: filter %s returned fewer headers (%d) than supplied ' '(%d)\n' % (self, len(newmsg.headers()), len(msg.headers())) ) # Copy attributes from original message newmsg.copyattrs(msg) return newmsg ####################################### class Filter_external(FilterSkeleton, ForkingBase): '''Arbitrary external filter destination. Parameters: path - path to the external filter binary. unixfrom - (boolean) whether to include a Unix From_ line at the beginning of the message. Defaults to False. arguments - a valid Python tuple of strings to be passed as arguments to the command. The following replacements are available if supported by the retriever: %(sender) - envelope return path %(recipient) - recipient address %(domain) - domain-part of recipient address %(local) - local-part of recipient address Warning: the text of these replacements is taken from the message and is therefore under the control of a potential attacker. DO NOT PASS THESE VALUES TO A SHELL -- they may contain unsafe shell metacharacters or other hostile constructions. example: path = /path/to/myfilter arguments = ('--demime', '-f%(sender)', '--', '%(recipient)') exitcodes_keep - if provided, a tuple of integers representing filter exit codes that mean to pass the message to the next filter or destination. Default is (0, ). exitcodes_drop - if provided, a tuple of integers representing filter exit codes that mean to drop the message. Default is (99, 100). user (string, optional) - if provided, the external command will be run as the specified user. This requires that the main getmail process have permission to change the effective user ID. group (string, optional) - if provided, the external command will be run with the specified group ID. This requires that the main getmail process have permission to change the effective group ID. allow_root_commands (boolean, optional) - if set, external commands are allowed when running as root. The default is not to allow such behaviour. ignore_stderr (boolean, optional) - if set, getmail will not consider the program writing to stderr to be an error. The default is False. ''' _confitems = ( ConfFile(name='path'), ConfBool(name='unixfrom', required=False, default=False), ConfTupleOfStrings(name='arguments', required=False, default="()"), ConfTupleOfStrings(name='exitcodes_keep', required=False, default="(0, )"), ConfTupleOfStrings(name='exitcodes_drop', required=False, default="(99, 100)"), ConfString(name='user', required=False, default=None), ConfString(name='group', required=False, default=None), ConfBool(name='allow_root_commands', required=False, default=False), ConfBool(name='ignore_stderr', required=False, default=False), ConfInstance(name='configparser', required=False), ) def initialize(self): self.log.trace() self.conf['command'] = os.path.basename(self.conf['path']) if not os.access(self.conf['path'], os.X_OK): raise getmailConfigurationError( '%s not executable' % self.conf['path'] ) if type(self.conf['arguments']) != tuple: raise getmailConfigurationError( 'incorrect arguments format; see documentation (%s)' % self.conf['arguments'] ) try: self.exitcodes_keep = [int(i) for i in self.conf['exitcodes_keep'] if 0 <= int(i) <= 255] self.exitcodes_drop = [int(i) for i in self.conf['exitcodes_drop'] if 0 <= int(i) <= 255] if not self.exitcodes_keep: raise getmailConfigurationError('exitcodes_keep set empty') if frozenset(self.exitcodes_keep).intersection( frozenset(self.exitcodes_drop) ): raise getmailConfigurationError('exitcode sets intersect') except ValueError, o: raise getmailConfigurationError('invalid exit code specified (%s)' % o) def __str__(self): self.log.trace() return 'Filter_external %s (%s)' % (self.conf['command'], self._confstring()) def showconf(self): self.log.trace() self.log.info('Filter_external(%s)\n' % self._confstring()) def _filter_command(self, msg, msginfo, stdout, stderr): try: # Write out message with native EOL convention msgfile = tempfile.TemporaryFile() msgfile.write(msg.flatten(False, False, include_from=self.conf['unixfrom'])) msgfile.flush() os.fsync(msgfile.fileno()) # Rewind msgfile.seek(0) # Set stdin to read from this file os.dup2(msgfile.fileno(), 0) # Set stdout and stderr to write to files os.dup2(stdout.fileno(), 1) os.dup2(stderr.fileno(), 2) change_usergroup(None, self.conf['user'], self.conf['group']) args = [self.conf['path'], self.conf['path']] for arg in self.conf['arguments']: arg = expand_user_vars(arg) for (key, value) in msginfo.items(): arg = arg.replace('%%(%s)' % key, value) args.append(arg) # Can't log this; if --trace is on, it will be written to the # message passed to the filter. #self.log.debug('about to execl() with args %s\n' % str(args)) os.execl(*args) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it self.log.critical('exec of filter %s failed (%s)' % (self.conf['command'], o)) os._exit(127) def _filter_message(self, msg): self.log.trace() self._prepare_child() msginfo = {} msginfo['sender'] = msg.sender if msg.recipient != None: msginfo['recipient'] = msg.recipient msginfo['domain'] = msg.recipient.lower().split('@')[-1] msginfo['local'] = '@'.join(msg.recipient.split('@')[:-1]) self.log.debug('msginfo "%s"\n' % msginfo) # At least some security... if (os.geteuid() == 0 and not self.conf['allow_root_commands'] and self.conf['user'] == None): raise getmailConfigurationError( 'refuse to invoke external commands as root by default' ) stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self._filter_command(msg, msginfo, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) err = stderr.read().strip() self.log.debug('command %s %d exited %d\n' % (self.conf['command'], childpid, exitcode)) newmsg = Message(fromfile=stdout) return (exitcode, newmsg, err) ####################################### class Filter_classifier(Filter_external): '''Filter which runs the message through an external command, adding the command's output to the message header. Takes the same parameters as Filter_external. If the command prints nothing, no header fields are added. ''' def __str__(self): self.log.trace() return 'Filter_classifier %s (%s)' % (self.conf['command'], self._confstring()) def showconf(self): self.log.trace() self.log.info('Filter_classifier(%s)\n' % self._confstring()) def _filter_message(self, msg): self.log.trace() self._prepare_child() msginfo = {} msginfo['sender'] = msg.sender if msg.recipient != None: msginfo['recipient'] = msg.recipient msginfo['domain'] = msg.recipient.lower().split('@')[-1] msginfo['local'] = '@'.join(msg.recipient.split('@')[:-1]) self.log.debug('msginfo "%s"\n' % msginfo) # At least some security... if (os.geteuid() == 0 and not self.conf['allow_root_commands'] and self.conf['user'] == None): raise getmailConfigurationError( 'refuse to invoke external commands as root by default' ) stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self._filter_command(msg, msginfo, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) err = stderr.read().strip() self.log.debug('command %s %d exited %d\n' % (self.conf['command'], childpid, exitcode)) for line in [line.strip() for line in stdout.readlines() if line.strip()]: # Output from filter can be in any random text encoding and may # not even be valid, which causes problems when trying to stick # that text into message headers. Try to decode it to something # sane here first. line = decode_crappy_text(line) msg.add_header('X-getmail-filter-classifier', line) return (exitcode, msg, err) ####################################### class Filter_TMDA(FilterSkeleton, ForkingBase): '''Filter which runs the message through TMDA's tmda-filter program to handle confirmations, etc. Parameters: path - path to the external tmda-filter binary. user (string, optional) - if provided, the external command will be run as the specified user. This requires that the main getmail process have permission to change the effective user ID. group (string, optional) - if provided, the external command will be run with the specified group ID. This requires that the main getmail process have permission to change the effective group ID. allow_root_commands (boolean, optional) - if set, external commands are allowed when running as root. The default is not to allow such behaviour. ignore_stderr (boolean, optional) - if set, getmail will not consider the program writing to stderr to be an error. The default is False. conf-break - used to break envelope recipient to find EXT. Defaults to "-". ''' _confitems = ( ConfFile(name='path', default='/usr/local/bin/tmda-filter'), ConfString(name='user', required=False, default=None), ConfString(name='group', required=False, default=None), ConfBool(name='allow_root_commands', required=False, default=False), ConfBool(name='ignore_stderr', required=False, default=False), ConfString(name='conf-break', required=False, default='-'), ConfInstance(name='configparser', required=False), ) def initialize(self): self.log.trace() self.conf['command'] = os.path.basename(self.conf['path']) if not os.access(self.conf['path'], os.X_OK): raise getmailConfigurationError( '%s not executable' % self.conf['path'] ) self.exitcodes_keep = (0, ) self.exitcodes_drop = (99, ) def __str__(self): self.log.trace() return 'Filter_TMDA %s' % self.conf['command'] def showconf(self): self.log.trace() self.log.info('Filter_TMDA(%s)\n' % self._confstring()) def _filter_command(self, msg, stdout, stderr): try: # Write out message with native EOL convention msgfile = tempfile.TemporaryFile() msgfile.write(msg.flatten(True, True, include_from=True)) msgfile.flush() os.fsync(msgfile.fileno()) # Rewind msgfile.seek(0) # Set stdin to read from this file os.dup2(msgfile.fileno(), 0) # Set stdout and stderr to write to files os.dup2(stdout.fileno(), 1) os.dup2(stderr.fileno(), 2) change_usergroup(None, self.conf['user'], self.conf['group']) args = [self.conf['path'], self.conf['path']] # Set environment for TMDA os.environ['SENDER'] = msg.sender os.environ['RECIPIENT'] = msg.recipient os.environ['EXT'] = self.conf['conf-break'].join( '@'.join(msg.recipient.split('@')[:-1]).split( self.conf['conf-break'] )[1:] ) self.log.trace('SENDER="%(SENDER)s",RECIPIENT="%(RECIPIENT)s"' ',EXT="%(EXT)s"' % os.environ) self.log.debug('about to execl() with args %s\n' % str(args)) os.execl(*args) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it self.log.critical('exec of filter %s failed (%s)' % (self.conf['command'], o)) os._exit(127) def _filter_message(self, msg): self.log.trace() self._prepare_child() if msg.recipient == None or msg.sender == None: raise getmailConfigurationError( 'TMDA requires the message envelope and therefore a multidrop ' 'retriever' ) # At least some security... if (os.geteuid() == 0 and not self.conf['allow_root_commands'] and self.conf['user'] == None): raise getmailConfigurationError( 'refuse to invoke external commands as root by default' ) stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self._filter_command(msg, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stderr.seek(0) err = stderr.read().strip() self.log.debug('command %s %d exited %d\n' % (self.conf['command'], childpid, exitcode)) return (exitcode, msg, err) getmail-4.43.0/getmailcore/imap_utf7.py0000755000175000017500000000724411760212577021400 0ustar charlesccharlesc00000000000000# -*- coding: utf-8 -*- """ Modified utf-7 encoding as used in IMAP v4r1 for encoding mailbox names. Code from here; couldn't find a license statement: http://www.koders.com/python/fid744B4E448B1689C0963942A7928FA049084FAC86.aspx From the RFC: 5.1.3. Mailbox International Naming Convention By convention, international mailbox names are specified using a modified version of the UTF-7 encoding described in [UTF-7]. The purpose of these modifications is to correct the following problems with UTF-7: 1) UTF-7 uses the "+" character for shifting; this conflicts with the common use of "+" in mailbox names, in particular USENET newsgroup names. 2) UTF-7's encoding is BASE64 which uses the "/" character; this conflicts with the use of "/" as a popular hierarchy delimiter. 3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with the use of "\" as a popular hierarchy delimiter. 4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with the use of "~" in some servers as a home directory indicator. 5) UTF-7 permits multiple alternate forms to represent the same string; in particular, printable US-ASCII chararacters can be represented in encoded form. In modified UTF-7, printable US-ASCII characters except for "&" represent themselves; that is, characters with octet values 0x20-0x25 and 0x27-0x7e. The character "&" (0x26) is represented by the two- octet sequence "&-". All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all Unicode 16-bit octets) are represented in modified BASE64, with a further modification from [UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be used to represent any printing US-ASCII character which can represent itself. "&" is used to shift to modified BASE64 and "-" to shift back to US- ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that is, a name that ends with a Unicode 16-bit octet MUST end with a "- "). """ import binascii import codecs # # encoding # def modified_base64(s): s = s.encode('utf-16be') return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',') def doB64(_in, r): if _in: r.append('&%s-' % modified_base64(''.join(_in))) del _in[:] def encoder(s): r = [] _in = [] for c in s: ordC = ord(c) if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e: doB64(_in, r) r.append(c) elif c == '&': doB64(_in, r) r.append('&-') else: _in.append(c) doB64(_in, r) return (str(''.join(r)), len(s)) # # decoding # def modified_unbase64(s): b = binascii.a2b_base64(s.replace(',', '/') + '===') return unicode(b, 'utf-16be') def decoder(s): r = [] decode = [] for c in s: if c == '&' and not decode: decode.append('&') elif c == '-' and decode: if len(decode) == 1: r.append('&') else: r.append(modified_unbase64(''.join(decode[1:]))) decode = [] elif decode: decode.append(c) else: r.append(c) if decode: r.append(modified_unbase64(''.join(decode[1:]))) bin_str = ''.join(r) return (bin_str, len(s)) class StreamReader(codecs.StreamReader): def decode(self, s, errors='strict'): return decoder(s) class StreamWriter(codecs.StreamWriter): def decode(self, s, errors='strict'): return encoder(s) def imap4_utf_7(name): if name == 'imap4-utf-7': return (encoder, decoder, StreamReader, StreamWriter) codecs.register(imap4_utf_7) getmail-4.43.0/getmailcore/__init__.py0000755000175000017500000000163312206541204021224 0ustar charlesccharlesc00000000000000#!/usr/bin/env python '''A reliable mail-retriever toolkit. getmail is a reliable, modular, extensible mail retriever with support for simple and multidrop POP3 mailboxes, multidrop SDPS mailboxes, simple and multidrop IMAP mailboxes. Also supports POP3- and IMAP-over-SSL, message filtering, and other features. getmail is Copyright (C) 1998-2009 Charles Cazabon. All rights reserved. Distributed under the terms of the GNU General Public License version 2 (only). You should have received a copy of the license in the file COPYING. ''' import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3' ' or later') __version__ = '4.43.0' __all__ = [ 'baseclasses', 'compatibility', 'constants', 'destinations', 'exceptions', 'filters', 'imap_utf7', 'logging', 'message', 'retrievers', 'utilities', ] getmail-4.43.0/getmailcore/_pop3ssl.py0000755000175000017500000001346511156276142021246 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Provide an SSL-capable POP3 class. ''' __all__ = [ 'POP3_ssl_port', 'sslsocket', 'POP3SSL', ] import socket from poplib import POP3, CR, LF, CRLF, error_proto from getmailcore.exceptions import * import getmailcore.logging log = getmailcore.logging.Logger() POP3_ssl_port = 995 class sslsocket(object): '''The Python poplib.POP3() class mixes socket-like .sendall() and file-like .readline() for communications. That would be okay, except that the new socket.ssl objects provide only read() and write(), so they don't act like a socket /or/ like a file. Argh. This class takes a standard, connected socket.socket() object, sets it to blocking mode (required for socket.ssl() to work correctly, though apparently not documented), wraps .write() for .sendall() and implements .readline(). The modified POP3 class below can then use this to provide POP3-over-SSL. Thanks to Frank Benkstein for the inspiration. ''' def __init__(self, sock, keyfile=None, certfile=None): log.trace() self.sock = sock self.sock.setblocking(1) if keyfile and certfile: self.ssl = socket.ssl(self.sock, keyfile, certfile) else: self.ssl = socket.ssl(self.sock) self.buf = '' self.bufsize = 128 def _fillbuf(self): '''Fill an internal buffer for .readline() to use. ''' log.trace() want = self.bufsize - len(self.buf) log.trace('want %i bytes\n' % want) if want <= 0: return s = self.ssl.read(want) got = len(s) log.trace('got %i bytes\n' % got) self.buf += s def close(self): self.sock.close() self.ssl = None # self.sock.sendall def sendall(self, s): # Maybe only set blocking around this call? self.ssl.write(s) # self.file.readline def readline(self): '''Simple hack to implement .readline() on a non-file object that only supports .read(). ''' log.trace() line = '' try: if not self.buf: self._fillbuf() log.trace('checking self.buf\n') if self.buf: log.trace('self.buf = "%r", len %i\n' % (self.buf, len(self.buf))) while True: log.trace('looking for EOL\n') i = self.buf.find('\n') if i != -1: log.trace('EOL found at %d\n' % i) line += self.buf[:i + 1] self.buf = self.buf[i + 1:] break # else log.trace('EOL not found, trying to fill self.buf\n') line += self.buf self.buf = '' self._fillbuf() if not self.buf: log.trace('nothing read, exiting\n') break log.trace('end of loop\n') log.trace('returning line "%r"\n' % line) return line except (socket.sslerror, socket.error), o: raise getmailOperationError( 'socket/ssl error while reading from server (%s)' % o ) class POP3SSL(POP3): '''Thin subclass to add SSL functionality to the built-in POP3 class. Note that Python's socket module does not do certificate verification for SSL connections. This gets rid of the .file attribute from os.makefile(rawsock) and relies on sslsocket() above to provide .readline() instead. ''' def __init__(self, host, port=POP3_ssl_port, keyfile=None, certfile=None): if not ((certfile and keyfile) or (keyfile == certfile == None)): raise getmailConfigurationError('certfile requires keyfile') self.host = host self.port = port msg = "getaddrinfo returns an empty list" self.rawsock = None self.sock = None for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): (af, socktype, proto, canonname, sa) = res try: self.rawsock = socket.socket(af, socktype, proto) self.rawsock.connect(sa) if certfile and keyfile: self.sock = sslsocket(self.rawsock, keyfile, certfile) else: self.sock = sslsocket(self.rawsock) except socket.error, msg: if self.rawsock: self.rawsock.close() self.rawsock = None continue break if not self.sock: raise socket.error, msg self._debugging = 0 self.welcome = self._getresp() # Internal: return one line from the server, stripping CRLF. # This is where all the CPU time of this module is consumed. # Raise error_proto('-ERR EOF') if the connection is closed. def _getline(self): line = self.sock.readline() if self._debugging > 1: print '*get*', `line` if not line: raise error_proto('-ERR EOF') octets = len(line) # server can send any combination of CR & LF # however, 'readline()' returns lines ending in LF # so only possibilities are ...LF, ...CRLF, CR...LF if line[-2:] == CRLF: return line[:-2], octets if line[0] == CR: return line[1:-1], octets return line[:-1], octets def quit(self): """Signoff: commit changes on server, unlock mailbox, close connection. """ try: resp = self._shortcmd('QUIT') except (error_proto, socket.error), val: resp = val self.sock.close() del self.sock return resp getmail-4.43.0/getmailcore/message.py0000755000175000017500000001730512176300552021121 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''The getmail Message class. ''' __all__ = [ 'Message', ] import sys import os import time import cStringIO import re import email import email.Errors import email.Utils import email.Parser from email.Generator import Generator try: from email.header import Header except ImportError, o: try: from email.Header import Header except ImportError, o: # Python < 2.5 from email import Header from getmailcore.exceptions import * from getmailcore.utilities import mbox_from_escape, format_header, \ address_no_brackets import getmailcore.logging if sys.hexversion < 0x02040000: # email module in Python 2.3 uses more recursion to parse messages or # similar; a user reported recursion errors with a message with ~300 # MIME parts. # Hack around it by increasing the recursion limit. sys.setrecursionlimit(2000) message_attributes = ( 'sender', 'received_by', 'received_from', 'received_with', 'recipient' ) RE_FROMLINE = re.compile(r'^(>*From )', re.MULTILINE) ####################################### def corrupt_message(why, fromlines=None, fromstring=None): log = getmailcore.logging.Logger() log.error('failed to parse retrieved message; constructing container for ' 'contents\n') if fromlines == fromstring == None: raise SystemExit('corrupt_message() called with wrong arguments') msg = email.message_from_string('') msg['From'] = '"unknown sender" <>' msg['Subject'] = 'Corrupt message received' msg['Date'] = email.Utils.formatdate(localtime=True) body = [ 'A badly-corrupt message was retrieved and could not be parsed', 'for the following reason:', '', ' %s' % why, '', 'Below the following line is the original message contents.', '', '--------------------------------------------------------------', ] if fromlines: body.extend([line.rstrip() for line in fromlines]) elif fromstring: body.extend([line.rstrip() for line in fromstring.splitlines()]) msg.set_payload(os.linesep.join(body)) for attr in message_attributes: setattr(msg, attr, '') return msg ####################################### class Message(object): '''Message class for getmail. Does sanity-checking on attribute accesses and provides some convenient interfaces to an underlying email.Message() object. ''' __slots__ = ( '__msg', '__raw', #'log', 'sender', 'received_by', 'received_from', 'received_with', 'recipient', ) def __init__(self, fromlines=None, fromstring=None, fromfile=None): #self.log = Logger() self.recipient = None self.received_by = None self.received_from = None self.received_with = None self.__raw = None parser = email.Parser.Parser() # Message is instantiated with fromlines for POP3, fromstring for # IMAP (both of which can be badly-corrupted or invalid, i.e. spam, # MS worms, etc). It's instantiated with fromfile for the output # of filters, etc, which should be saner. if fromlines: try: self.__msg = parser.parsestr(os.linesep.join(fromlines)) except email.Errors.MessageError, o: self.__msg = corrupt_message(o, fromlines=fromlines) self.__raw = os.linesep.join(fromlines) elif fromstring: try: self.__msg = parser.parsestr(fromstring) except email.Errors.MessageError, o: self.__msg = corrupt_message(o, fromstring=fromstring) self.__raw = fromstring elif fromfile: try: self.__msg = parser.parse(fromfile) except email.Errors.MessageError, o: # Shouldn't happen self.__msg = corrupt_message(o, fromstring=fromfile.read()) # fromfile is only used by getmail_maildir, getmail_mbox, and # from reading the output of a filter. Ignore __raw here. else: # Can't happen? raise SystemExit('Message() called with wrong arguments') self.sender = address_no_brackets(self.__msg['return-path'] or 'unknown') def content(self): return self.__msg def copyattrs(self, othermsg): for attr in message_attributes: setattr(self, attr, getattr(othermsg, attr)) def flatten(self, delivered_to, received, mangle_from=False, include_from=False): '''Return a string with native EOL convention. The email module apparently doesn't always use native EOL, so we force it by writing out what we need, letting the generator write out the message, splitting it into lines, and joining them with the platform EOL. Note on mangle_from: the Python email.Generator class apparently only quotes "From ", not ">From " (i.e. it uses mboxo format instead of mboxrd). So we don't use its mangling, and do it by hand instead. ''' if include_from: # Mbox-style From line, not rfc822 From: header field. fromline = 'From %s %s' % (mbox_from_escape(self.sender), time.asctime()) + os.linesep else: fromline = '' # Write the Return-Path: header rpline = format_header('Return-Path', '<%s>' % self.sender) # Remove previous Return-Path: header fields. del self.__msg['Return-Path'] if delivered_to: dtline = format_header('Delivered-To', self.recipient or 'unknown') else: dtline = '' if received: content = 'from %s by %s with %s' % ( self.received_from, self.received_by, self.received_with ) if self.recipient is not None: content += ' for <%s>' % self.recipient content += '; ' + time.strftime('%d %b %Y %H:%M:%S -0000', time.gmtime()) receivedline = format_header('Received', content) else: receivedline = '' # From_ handled above, always tell the generator not to include it try: tmpf = cStringIO.StringIO() gen = Generator(tmpf, False, 0) gen.flatten(self.__msg, False) strmsg = tmpf.getvalue() if mangle_from: # do mboxrd-style "From " line quoting strmsg = RE_FROMLINE.sub(r'>\1', strmsg) return (fromline + rpline + dtline + receivedline + os.linesep.join(strmsg.splitlines() + [''])) except TypeError, o: # email module chokes on some badly-misformatted messages, even # late during flatten(). Hope this is fixed in Python 2.4. if self.__raw is None: # Argh -- a filter took a correctly-formatted message # and returned a badly-misformatted one? raise getmailDeliveryError('failed to parse retrieved message ' 'and could not recover (%s)' % o) self.__msg = corrupt_message(o, fromstring=self.__raw) return self.flatten(delivered_to, received, mangle_from, include_from) def add_header(self, name, content): self.__msg[name] = Header(content.rstrip(), 'utf-8') def remove_header(self, name): del self.__msg[name] def headers(self): return self.__msg._headers def get_all(self, name, failobj=None): return self.__msg.get_all(name, failobj) getmail-4.43.0/getmailcore/compatibility.py0000755000175000017500000000461411770671421022352 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Compatibility class declarations used elsewhere in the package. ''' __all__ = [ 'set', 'frozenset', ] import sys import imaplib import new if sys.version_info < (2, 4, 0): # set/frozenset not built-in until Python 2.4 import sets set = sets.Set frozenset = sets.ImmutableSet set = set frozenset = frozenset if sys.version_info < (2, 5, 0): # Python < 2.5.0 has a bug with the readonly flag on imaplib's select(). # Monkey-patch it in. def py25_select(self, mailbox='INBOX', readonly=False): """Select a mailbox. Flush all untagged responses. (typ, [data]) = .select(mailbox='INBOX', readonly=False) 'data' is count of messages in mailbox ('EXISTS' response). Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so other responses should be obtained via .response('FLAGS') etc. """ self.untagged_responses = {} # Flush old responses. self.is_readonly = readonly if readonly: name = 'EXAMINE' else: name = 'SELECT' typ, dat = self._simple_command(name, mailbox) if typ != 'OK': self.state = 'AUTH' # Might have been 'SELECTED' return typ, dat self.state = 'SELECTED' if 'READ-ONLY' in self.untagged_responses \ and not readonly: if __debug__: if self.debug >= 1: self._dump_ur(self.untagged_responses) raise self.readonly('%s is not writable' % mailbox) return typ, self.untagged_responses.get('EXISTS', [None]) imaplib.IMAP4.select = new.instancemethod(py25_select, None, imaplib.IMAP4) if sys.version_info < (2, 5, 3): # A serious imaplib bug (http://bugs.python.org/issue1389051) was # fixed in 2.5.3. Earlier Python releases need a work-around. # Monkey-patch it in. def fixed_read(self, size): """Read 'size' bytes from remote.""" # sslobj.read() sometimes returns < size bytes chunks = [] read = 0 while read < size: data = self.sslobj.read(min(size-read, 16384)) read += len(data) chunks.append(data) return ''.join(chunks) imaplib.IMAP4_SSL.read = new.instancemethod(fixed_read, None, imaplib.IMAP4_SSL) getmail-4.43.0/getmailcore/baseclasses.py0000755000175000017500000003452012101123062021746 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Base classes used elsewhere in the package. ''' __all__ = [ 'ConfigurableBase', 'ForkingBase', 'ConfInstance', 'ConfString', 'ConfBool', 'ConfInt', 'ConfTupleOfStrings', 'ConfTupleOfUnicode', 'ConfTupleOfTupleOfStrings', 'ConfPassword', 'ConfDirectory', 'ConfFile', 'ConfMaildirPath', 'ConfMboxPath', ] import sys import os import time import signal import types from getmailcore.exceptions import * from getmailcore.compatibility import * import getmailcore.logging from getmailcore.utilities import eval_bool, expand_user_vars # # Base classes # class ConfItem: securevalue = False def __init__(self, name, dtype, default=None, required=True): self.log = getmailcore.logging.Logger() self.name = name self.dtype = dtype self.default = default self.required = required def validate(self, configuration, val=None): if val is None: # If not passed in by subclass val = configuration.get(self.name, None) if val is None: # Not provided. if self.required: raise getmailConfigurationError( '%s: missing required configuration parameter' % self.name ) # Use default. return self.default if type(val) is not self.dtype and val != self.default: # Got value, but not of expected type. Try to convert. if self.securevalue: self.log.debug('converting %s to type %s\n' % (self.name, self.dtype)) else: self.log.debug('converting %s (%s) to type %s\n' % (self.name, val, self.dtype)) try: if self.dtype == bool: val = eval_bool(val) else: val = self.dtype(eval(val)) except (ValueError, SyntaxError, TypeError), o: raise getmailConfigurationError( '%s: configuration value (%s) not of required type %s (%s)' % (self.name, val, self.dtype, o) ) return val class ConfInstance(ConfItem): def __init__(self, name, default=None, required=True): ConfItem.__init__(self, name, types.InstanceType, default=default, required=required) class ConfString(ConfItem): def __init__(self, name, default=None, required=True): ConfItem.__init__(self, name, str, default=default, required=required) class ConfBool(ConfItem): def __init__(self, name, default=None, required=True): ConfItem.__init__(self, name, bool, default=default, required=required) class ConfInt(ConfItem): def __init__(self, name, default=None, required=True): ConfItem.__init__(self, name, int, default=default, required=required) class ConfTupleOfStrings(ConfString): def __init__(self, name, default=None, required=True): ConfString.__init__(self, name, default=default, required=required) def validate(self, configuration): val = ConfItem.validate(self, configuration) try: if not val: val = '()' tup = eval(val) if type(tup) != tuple: raise ValueError('not a tuple') val = tup except (ValueError, SyntaxError), o: raise getmailConfigurationError( '%s: incorrect format (%s)' % (self.name, o) ) result = [str(item) for item in val] return tuple(result) class ConfTupleOfUnicode(ConfString): def __init__(self, name, default=None, required=True, allow_specials=()): ConfString.__init__(self, name, default=default, required=required) self.specials = allow_specials def validate(self, configuration): _locals = dict([(v, v) for v in self.specials]) val = ConfItem.validate(self, configuration) try: if not val: val = '()' tup = eval(val, {}, _locals) if tup in self.specials: val = [tup] else: if type(tup) != tuple: raise ValueError('not a tuple') vals = [] for item in tup: item = str(item) try: vals.append(item.decode('ascii')) except UnicodeError, o: try: vals.append(item.decode('utf-8')) except UnicodeError, o: raise ValueError('not ascii or utf-8: %s' % item) val = vals except (ValueError, SyntaxError), o: raise getmailConfigurationError( '%s: incorrect format (%s)' % (self.name, o) ) return tuple(val) class ConfTupleOfTupleOfStrings(ConfString): def __init__(self, name, default=None, required=True): ConfString.__init__(self, name, default=default, required=required) def validate(self, configuration): val = ConfItem.validate(self, configuration) try: if not val: val = '()' tup = eval(val) if type(tup) != tuple: raise ValueError('not a tuple') val = tup except (ValueError, SyntaxError), o: raise getmailConfigurationError( '%s: incorrect format (%s)' % (self.name, o) ) for tup in val: if type(tup) != tuple: raise ValueError('contained value "%s" not a tuple' % tup) if len(tup) != 2: raise ValueError('contained value "%s" not length 2' % tup) for part in tup: if type(part) != str: raise ValueError('contained value "%s" has non-string part ' '"%s"' % (tup, part)) return val class ConfPassword(ConfString): securevalue = True class ConfDirectory(ConfString): def __init__(self, name, default=None, required=True): ConfString.__init__(self, name, default=default, required=required) def validate(self, configuration): val = ConfString.validate(self, configuration) if val is None: return None val = expand_user_vars(val) if not os.path.isdir(val): raise getmailConfigurationError( '%s: specified directory "%s" does not exist' % (self.name, val) ) return val class ConfFile(ConfString): def __init__(self, name, default=None, required=True): ConfString.__init__(self, name, default=default, required=required) def validate(self, configuration): val = ConfString.validate(self, configuration) if val is None: return None val = expand_user_vars(val) if not os.path.isfile(val): raise getmailConfigurationError( '%s: specified file "%s" does not exist' % (self.name, val) ) return val class ConfMaildirPath(ConfDirectory): def validate(self, configuration): val = ConfDirectory.validate(self, configuration) if val is None: return None if not val.endswith('/'): raise getmailConfigurationError( '%s: maildir must end with "/"' % self.name ) for subdir in ('cur', 'new', 'tmp'): subdirpath = os.path.join(val, subdir) if not os.path.isdir(subdirpath): raise getmailConfigurationError( '%s: maildir subdirectory "%s" does not exist' % (self.name, subdirpath) ) return val class ConfMboxPath(ConfString): def __init__(self, name, default=None, required=True): ConfString.__init__(self, name, default=default, required=required) def validate(self, configuration): val = ConfString.validate(self, configuration) if val is None: return None val = expand_user_vars(val) if not os.path.isfile(val): raise getmailConfigurationError( '%s: specified mbox file "%s" does not exist' % (self.name, val) ) fd = os.open(val, os.O_RDWR) status_old = os.fstat(fd) f = os.fdopen(fd, 'r+b') # Check if it _is_ an mbox file. mbox files must start with "From " # in their first line, or are 0-length files. f.seek(0, 0) first_line = f.readline() if first_line and first_line[:5] != 'From ': # Not an mbox file; abort here raise getmailConfigurationError('%s: not an mboxrd file' % val) # Reset atime and mtime try: os.utime(val, (status_old.st_atime, status_old.st_mtime)) except OSError, o: # Not root or owner; readers will not be able to reliably # detect new mail. But you shouldn't be delivering to # other peoples' mboxes unless you're root, anyways. pass return val ####################################### class ConfigurableBase(object): '''Base class for user-configurable classes. Sub-classes must provide the following data attributes and methods: _confitems - a tuple of dictionaries representing the parameters the class takes. Each dictionary should contain the following key, value pairs: - name - parameter name - type - a type function to compare the parameter value against (i.e. str, int, bool) - default - optional default value. If not preseent, the parameter is required. ''' def __init__(self, **args): self.log = getmailcore.logging.Logger() self.log.trace() self.conf = {} allowed_params = set([item.name for item in self._confitems]) for (name, value) in args.items(): if not name in allowed_params: self.log.warning('Warning: ignoring unknown parameter "%s" ' '(value: %s)\n' % (name, value)) continue if name.lower() == 'password': self.log.trace('setting %s to * (%s)\n' % (name, type(value))) else: self.log.trace('setting %s to "%s" (%s)\n' % (name, value, type(value))) self.conf[name] = value self.__confchecked = False self.checkconf() def checkconf(self): self.log.trace() if self.__confchecked: return for item in self._confitems: # New class-based configuration item self.log.trace('checking %s\n' % item.name) self.conf[item.name] = item.validate(self.conf) unknown_params = frozenset(self.conf.keys()).difference( frozenset([item.name for item in self._confitems]) ) for param in sorted(list(unknown_params), key=str.lower): self.log.warning('Warning: ignoring unknown parameter "%s" ' '(value: %s)\n' % (param, self.conf[param])) self.__confchecked = True self.log.trace('done\n') def _confstring(self): self.log.trace() confstring = '' names = self.conf.keys() names.sort() for name in names: if name.lower() == 'configparser': continue if confstring: confstring += ', ' if name.lower() == 'password': confstring += '%s="*"' % name else: confstring += '%s="%s"' % (name, self.conf[name]) return confstring ####################################### class ForkingBase(object): '''Base class for classes which fork children and wait for them to exit. Sub-classes must provide the following data attributes and methods: log - an object of type getmailcore.logging.Logger() ''' def _child_handler(self, sig, stackframe): self.log.trace('handler called for signal %s' % sig) try: pid, r = os.wait() except OSError, o: # No children on SIGCHLD. Can't happen? self.log.warning('handler called, but no children (%s)' % o) return signal.signal(signal.SIGCHLD, self.__orig_handler) self.__child_pid = pid self.__child_status = r self.log.trace('handler reaped child %s with status %s' % (pid, r)) self.__child_exited = True def _prepare_child(self): self.log.trace('') self.__child_exited = False self.__child_pid = None self.__child_status = None self.__orig_handler = signal.signal(signal.SIGCHLD, self._child_handler) def _wait_for_child(self, childpid): while not self.__child_exited: # Could implement a maximum wait time here self.log.trace('waiting for child %d' % childpid) time.sleep(1.0) #raise getmailDeliveryError('failed waiting for commands %s %d (%s)' # % (self.conf['command'], childpid, o)) if self.__child_pid != childpid: #self.log.error('got child pid %d, not %d' % (pid, childpid)) raise getmailOperationError( 'got child pid %d, not %d' % (self.__child_pid, childpid) ) if os.WIFSTOPPED(self.__child_status): raise getmailOperationError( 'child pid %d stopped by signal %d' % (self.__child_pid, os.WSTOPSIG(self.__child_status)) ) if os.WIFSIGNALED(self.__child_status): raise getmailOperationError( 'child pid %d killed by signal %d' % (self.__child_pid, os.WTERMSIG(self.__child_status)) ) if not os.WIFEXITED(self.__child_status): raise getmailOperationError('child pid %d failed to exit' % self.__child_pid) exitcode = os.WEXITSTATUS(self.__child_status) return exitcode # For Python 2.3, which lacks the sorted() builtin if sys.hexversion < 0x02040000: def sorted(l, key=lambda x: x): lst = [(key(item), item) for item in l] lst.sort() return [val for (unused, val) in lst] __all__.append('sorted') getmail-4.43.0/getmailcore/exceptions.py0000755000175000017500000000446411760255661021670 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Exceptions raised by getmail. ''' __all__ = [ 'getmailError', 'getmailConfigurationError', 'getmailDnsLookupError', 'getmailDnsServerFailure', 'getmailOperationError', 'getmailFilterError', 'getmailRetrievalError', 'getmailDeliveryError', 'getmailCredentialError', 'getmailLoginRefusedError', 'getmailMailboxSelectError', ] # Base class for all getmail exceptions class getmailError(StandardError): '''Base class for all getmail exceptions.''' pass # Specific exception classes class getmailConfigurationError(getmailError): '''Exception raised when a user configuration error is detected.''' pass class getmailOperationError(getmailError): '''Exception raised when a runtime error is detected.''' pass class getmailRetrievalError(getmailOperationError): '''Exception raised when a server (cough MSExchange cough) fails to hand over a message it claims to have.''' pass class getmailFilterError(getmailOperationError): '''Exception raised when problems occur during message filtering. Subclass of getmailOperationError. ''' pass class getmailDeliveryError(getmailOperationError): '''Exception raised when problems occur during message delivery. Subclass of getmailOperationError. ''' pass class getmailDnsError(getmailOperationError): '''Base class for errors looking up hosts in DNS to connect to.''' pass class getmailDnsLookupError(getmailDnsError): '''No such DNS name, or name found but no address records for it.''' pass class getmailDnsServerFailure(getmailDnsError): '''DNS server failed when trying to look up name.''' pass class getmailCredentialError(getmailOperationError): '''Error raised when server says "bad password", "no such user", etc (when that is possible to detect).''' pass class getmailLoginRefusedError(getmailOperationError): '''Error raised when the server is just refusing logins due to reasons other than credential problems (when that is possible to detect): server too busy, service shutting down, etc.''' pass class getmailMailboxSelectError(getmailOperationError): '''Error raised when the server responds NO to an (IMAP) select mailbox command -- no such mailbox, no permissions, etc. ''' pass getmail-4.43.0/getmailcore/utilities.py0000755000175000017500000004616312206535137021517 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Utility classes and functions for getmail. ''' __all__ = [ 'address_no_brackets', 'change_usergroup', 'change_uidgid', 'decode_crappy_text', 'format_header', 'check_ssl_key_and_cert', 'deliver_maildir', 'eval_bool', 'expand_user_vars', 'is_maildir', 'localhostname', 'lock_file', 'logfile', 'mbox_from_escape', 'safe_open', 'unlock_file', 'gid_of_uid', 'uid_of_user', 'updatefile', 'get_password', ] import os import os.path import socket import signal import stat import time import glob import re import fcntl import pwd import grp import getpass import commands # Optional gnome-keyring integration try: import gnomekeyring # And test to see if it's actually available if not gnomekeyring.is_available(): gnomekeyring = None except ImportError: gnomekeyring = None from getmailcore.exceptions import * logtimeformat = '%Y-%m-%d %H:%M:%S' _bool_values = { 'true' : True, 'yes' : True, 'on' : True, '1' : True, 'false' : False, 'no' : False, 'off' : False, '0' : False } osx_keychain_binary = '/usr/bin/security' ####################################### def lock_file(file, locktype): '''Do file locking.''' assert locktype in ('lockf', 'flock'), 'unknown lock type %s' % locktype if locktype == 'lockf': fcntl.lockf(file, fcntl.LOCK_EX) elif locktype == 'flock': fcntl.flock(file, fcntl.LOCK_EX) ####################################### def unlock_file(file, locktype): '''Do file unlocking.''' assert locktype in ('lockf', 'flock'), 'unknown lock type %s' % locktype if locktype == 'lockf': fcntl.lockf(file, fcntl.LOCK_UN) elif locktype == 'flock': fcntl.flock(file, fcntl.LOCK_UN) ####################################### def safe_open(path, mode, permissions=0600): '''Open a file path safely. ''' if os.name != 'posix': return open(path, mode) try: fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_EXCL, permissions) file = os.fdopen(fd, mode) except OSError, o: raise getmailDeliveryError('failure opening %s (%s)' % (path, o)) return file ####################################### class updatefile(object): '''A class for atomically updating files. A new, temporary file is created when this class is instantiated. When the object's close() method is called, the file is synced to disk and atomically renamed to replace the original file. close() is automatically called when the object is deleted. ''' def __init__(self, filename): self.closed = False self.filename = filename self.tmpname = filename + '.tmp.%d' % os.getpid() # If the target is a symlink, the rename-on-close semantics of this # class would break the symlink, replacing it with the new file. # Instead, follow the symlink here, and replace the target file on # close. while os.path.islink(filename): filename = os.path.join(os.path.dirname(filename), os.readlink(filename)) try: f = safe_open(self.tmpname, 'wb') except IOError, (code, msg): raise IOError('%s, opening output file "%s"' % (msg, self.tmpname)) self.file = f self.write = f.write self.flush = f.flush def __del__(self): self.close() def abort(self): try: if hasattr(self, 'file'): self.file.close() except IOError: pass self.closed = True def close(self): if self.closed or not hasattr(self, 'file'): return self.file.flush() os.fsync(self.file.fileno()) self.file.close() os.rename(self.tmpname, self.filename) self.closed = True ####################################### class logfile(object): '''A class for locking and appending timestamped data lines to a log file. ''' def __init__(self, filename): self.closed = False self.filename = filename try: self.file = open(expand_user_vars(self.filename), 'ab') except IOError, (code, msg): raise IOError('%s, opening file "%s"' % (msg, self.filename)) def __del__(self): self.close() def __str__(self): return 'logfile(filename="%s")' % self.filename def close(self): if self.closed: return self.file.flush() self.file.close() self.closed = True def write(self, s): try: lock_file(self.file, 'flock') # Seek to end self.file.seek(0, 2) self.file.write(time.strftime(logtimeformat, time.localtime()) + ' ' + s.rstrip() + os.linesep) self.file.flush() finally: unlock_file(self.file, 'flock') ####################################### def format_params(d, maskitems=('password', ), skipitems=()): '''Take a dictionary of parameters and return a string summary. ''' s = '' keys = d.keys() keys.sort() for key in keys: if key in skipitems: continue if s: s += ',' if key in maskitems: s += '%s=*' % key else: s += '%s="%s"' % (key, d[key]) return s ################################### def alarm_handler(*unused): '''Handle an alarm during maildir delivery. Should never happen. ''' raise getmailDeliveryError('Delivery timeout') ####################################### def is_maildir(d): '''Verify a path is a maildir. ''' dir_parent = os.path.dirname(d.endswith('/') and d[:-1] or d) if not os.access(dir_parent, os.X_OK): raise getmailConfigurationError( 'cannot read contents of parent directory of %s ' '- check permissions and ownership' % d ) if not os.path.isdir(d): return False if not os.access(d, os.X_OK): raise getmailConfigurationError( 'cannot read contents of directory %s ' '- check permissions and ownership' % d ) for sub in ('tmp', 'cur', 'new'): subdir = os.path.join(d, sub) if not os.path.isdir(subdir): return False if not os.access(subdir, os.W_OK): raise getmailConfigurationError( 'cannot write to maildir %s ' '- check permissions and ownership' % d ) return True ####################################### def deliver_maildir(maildirpath, data, hostname, dcount=None, filemode=0600): '''Reliably deliver a mail message into a Maildir. Uses Dan Bernstein's documented rules for maildir delivery, and the updated naming convention for new files (modern delivery identifiers). See http://cr.yp.to/proto/maildir.html and http://qmail.org/man/man5/maildir.html for details. ''' if not is_maildir(maildirpath): raise getmailDeliveryError('not a Maildir (%s)' % maildirpath) # Set a 24-hour alarm for this delivery signal.signal(signal.SIGALRM, alarm_handler) signal.alarm(24 * 60 * 60) info = { 'deliverycount' : dcount, 'hostname' : hostname.split('.')[0].replace('/', '\\057').replace( ':', '\\072'), 'pid' : os.getpid(), } dir_tmp = os.path.join(maildirpath, 'tmp') dir_new = os.path.join(maildirpath, 'new') for unused in range(3): t = time.time() info['secs'] = int(t) info['usecs'] = int((t - int(t)) * 1000000) info['unique'] = 'M%(usecs)dP%(pid)s' % info if info['deliverycount'] is not None: info['unique'] += 'Q%(deliverycount)s' % info try: info['unique'] += 'R%s' % ''.join( ['%02x' % ord(char) for char in open('/dev/urandom', 'rb').read(8)] ) except StandardError: pass filename = '%(secs)s.%(unique)s.%(hostname)s' % info fname_tmp = os.path.join(dir_tmp, filename) fname_new = os.path.join(dir_new, filename) # File must not already exist if os.path.exists(fname_tmp): # djb says sleep two seconds and try again time.sleep(2) continue # Be generous and check cur/file[:...] just in case some other, dumber # MDA is in use. We wouldn't want them to clobber us and have the user # blame us for their bugs. curpat = os.path.join(maildirpath, 'cur', filename) + ':*' collision = glob.glob(curpat) if collision: # There is a message in maildir/cur/ which could be clobbered by # a dumb MUA, and which shouldn't be there. Abort. raise getmailDeliveryError('collision with %s' % collision) # Found an unused filename break else: signal.alarm(0) raise getmailDeliveryError('failed to allocate file in maildir') # Get user & group of maildir s_maildir = os.stat(maildirpath) # Open file to write try: f = safe_open(fname_tmp, 'wb', filemode) f.write(data) f.flush() os.fsync(f.fileno()) f.close() except IOError, o: signal.alarm(0) raise getmailDeliveryError('failure writing file %s (%s)' % (fname_tmp, o)) # Move message file from Maildir/tmp to Maildir/new try: os.link(fname_tmp, fname_new) os.unlink(fname_tmp) except OSError: signal.alarm(0) try: os.unlink(fname_tmp) except KeyboardInterrupt: raise except StandardError: pass raise getmailDeliveryError('failure renaming "%s" to "%s"' % (fname_tmp, fname_new)) # Delivery done # Cancel alarm signal.alarm(0) signal.signal(signal.SIGALRM, signal.SIG_DFL) return filename ####################################### def mbox_from_escape(s): '''Escape spaces, tabs, and newlines in the envelope sender address.''' return ''.join([(c in (' ', '\t', '\n')) and '-' or c for c in s]) or '<>' ####################################### def address_no_brackets(addr): '''Strip surrounding <> on an email address, if present.''' if addr.startswith('<') and addr.endswith('>'): return addr[1:-1] else: return addr ####################################### def eval_bool(s): '''Handle boolean values intelligently. ''' try: return _bool_values[str(s).lower()] except KeyError: raise getmailConfigurationError( 'boolean parameter requires value to be one of true or false, ' 'not "%s"' % s ) ####################################### def gid_of_uid(uid): try: return pwd.getpwuid(uid).pw_gid except KeyError, o: raise getmailConfigurationError('no such specified uid (%s)' % o) ####################################### def uid_of_user(user): try: return pwd.getpwnam(user).pw_uid except KeyError, o: raise getmailConfigurationError('no such specified user (%s)' % o) ####################################### def change_usergroup(logger=None, user=None, _group=None): ''' Change the current effective GID and UID to those specified by user and _group. ''' uid = None gid = None if _group: if logger: logger.debug('Getting GID for specified group %s\n' % _group) try: gid = grp.getgrnam(_group).gr_gid except KeyError, o: raise getmailConfigurationError('no such specified group (%s)' % o) if user: if logger: logger.debug('Getting UID for specified user %s\n' % user) uid = uid_of_user(user) change_uidgid(logger, uid, gid) ####################################### def change_uidgid(logger=None, uid=None, gid=None): ''' Change the current effective GID and UID to those specified by uid and gid. ''' try: if gid: if os.getegid() != gid: if logger: logger.debug('Setting egid to %d\n' % gid) os.setregid(gid, gid) if uid: if os.geteuid() != uid: if logger: logger.debug('Setting euid to %d\n' % uid) os.setreuid(uid, uid) except OSError, o: raise getmailDeliveryError('change UID/GID to %s/%s failed (%s)' % (uid, gid, o)) ####################################### def decode_crappy_text(s): '''Take a line of text in arbitrary and possibly broken bytestring encoding and return an ASCII or unicode version of it. ''' # first, assume it was written in the encoding of the user's terminal lang = os.environ.get('LANG') if lang: try: (lang, encoding) = lang.split('.') return s.decode(encoding) except (UnicodeError, ValueError), o: pass # that failed; try well-formed in various common encodings next for encoding in ('ascii', 'utf-8', 'latin-1', 'utf-16'): try: return s.decode(encoding) except UnicodeError, o: continue # all failed - force it return s.decode('utf-8', 'replace') ####################################### def format_header(name, line): '''Take a long line and return rfc822-style multiline header. ''' header = '' line = (name.strip() + ': ' + ' '.join([part.strip() for part in line.splitlines()])) # Split into lines of maximum 78 characters long plus newline, if # possible. A long line may result if no space characters are present. while line and len(line) > 78: i = line.rfind(' ', 0, 78) if i == -1: # No space in first 78 characters, try a long line i = line.rfind(' ') if i == -1: # No space at all break if header: header += os.linesep + ' ' header += line[:i] line = line[i:].lstrip() if header: header += os.linesep + ' ' if line: header += line.strip() + os.linesep return header ####################################### def expand_user_vars(s): '''Return a string expanded for both leading "~/" or "~username/" and environment variables in the form "$varname" or "${varname}". ''' return os.path.expanduser(os.path.expandvars(s)) ####################################### def localhostname(): '''Return a name for localhost which is (hopefully) the "correct" FQDN. ''' n = socket.gethostname() if '.' in n: return n return socket.getfqdn() ####################################### def check_ssl_key_and_cert(conf): keyfile = conf['keyfile'] if keyfile is not None: keyfile = expand_user_vars(keyfile) certfile = conf['certfile'] if certfile is not None: certfile = expand_user_vars(certfile) if keyfile and not os.path.isfile(keyfile): raise getmailConfigurationError( 'optional keyfile must be path to a valid file' ) if certfile and not os.path.isfile(certfile): raise getmailConfigurationError( 'optional certfile must be path to a valid file' ) if (keyfile is None) ^ (certfile is None): raise getmailConfigurationError( 'optional certfile and keyfile must be supplied together' ) return (keyfile, certfile) ####################################### keychain_password = None if os.name == 'posix': if os.path.isfile(osx_keychain_binary): def keychain_password(user, server, protocol, logger): """Mac OSX: return a keychain password, if it exists. Otherwise, return None. """ # OSX protocol is not an arbitrary string; it's a code limited to # 4 case-sensitive chars, and only specific values. protocol = protocol.lower() if 'imap' in protocol: protocol = 'imap' elif 'pop' in protocol: protocol = 'pop3' else: # This will break. protocol = '????' # wish we could pass along a comment to this thing for the user prompt cmd = "%s find-internet-password -g -a '%s' -s '%s' -r '%s'" % ( osx_keychain_binary, user, server, protocol ) (status, output) = commands.getstatusoutput(cmd) if status != os.EX_OK or not output: logger.error('keychain command %s failed: %s %s' % (cmd, status, output)) return None password = None for line in output.split('\n'): match = re.match(r'password: "([^"]+)"', line) if match: password = match.group(1) if password is None: logger.debug('No keychain password found for %s %s %s' % (user, server, protocol)) return password elif gnomekeyring: def keychain_password(user, server, protocol, logger): """Gnome: return a keyring password, if it exists. Otherwise, return None. """ #logger.trace('trying Gnome keyring for user="%s", server="%s", protocol="%s"\n' # % (user, server, protocol)) try: # http://developer.gnome.org/gnome-keyring/3.5/gnome-keyring # -Network-Passwords.html#gnome-keyring-find-network-password-sync secret = gnomekeyring.find_network_password_sync( # user, domain=None, server, object=None, protocol, # authtype=None, port=0 user, None, server, None, protocol, None, 0 ) #logger.trace('got keyring result %s' % str(secret)) except gnomekeyring.NoMatchError: logger.debug('gnome-keyring does not know password for %s %s %s' % (user, server, protocol)) return None # secret looks like this: # [{'protocol': 'imap', 'keyring': 'Default', 'server': 'gmail.com', # 'user': 'hiciu', 'item_id': 1L, 'password': 'kielbasa'}] if secret and 'password' in secret[0]: return secret[0]['password'] return None #else: # Posix but no OSX keychain or Gnome keyring. # Fallthrough if keychain_password is None: def keychain_password(user, server, protocol, logger): """Neither Mac OSX keychain or Gnome keyring available: always return None. """ return None ####################################### def get_password(label, user, server, protocol, logger): # try keychain/keyrings first, where available password = keychain_password(user, server, protocol, logger) if password: logger.debug('using password from keychain/keyring') else: # no password found (or not on OSX), prompt in the usual way password = getpass.getpass('Enter password for %s: ' % label) return password getmail-4.43.0/getmailcore/destinations.py0000755000175000017500000012610012110012455022161 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Classes implementing destinations (files, directories, or programs getmail can deliver mail to). Currently implemented: Maildir Mboxrd MDA_qmaillocal (deliver though qmail-local as external MDA) MDA_external (deliver through an arbitrary external MDA) MultiSorter (deliver to a selection of maildirs/mbox files based on matching recipient address patterns) ''' __all__ = [ 'DeliverySkeleton', 'Maildir', 'Mboxrd', 'MDA_qmaillocal', 'MDA_external', 'MultiDestinationBase', 'MultiDestination', 'MultiSorterBase', 'MultiSorter', ] import os import re import tempfile import types import email.Utils import pwd from getmailcore.exceptions import * from getmailcore.utilities import * from getmailcore.baseclasses import * ####################################### class DeliverySkeleton(ConfigurableBase): '''Base class for implementing message-delivery classes. Sub-classes should provide the following data attributes and methods: _confitems - a tuple of dictionaries representing the parameters the class takes. Each dictionary should contain the following key, value pairs: - name - parameter name - type - a type function to compare the parameter value against (i.e. str, int, bool) - default - optional default value. If not preseent, the parameter is required. __str__(self) - return a simple string representing the class instance. showconf(self) - log a message representing the instance and configuration from self._confstring(). initialize(self) - process instantiation parameters from self.conf. Raise getmailConfigurationError on errors. Do any other validation necessary, and set self.__initialized when done. retriever_info(self, retriever) - extract information from retriever and store it for use in message deliveries. _deliver_message(self, msg, delivered_to, received) - accept the message and deliver it, returning a string describing the result. See the Maildir class for a good, simple example. ''' def __init__(self, **args): ConfigurableBase.__init__(self, **args) try: self.initialize() except KeyError, o: raise getmailConfigurationError( 'missing required configuration parameter %s' % o ) self.received_from = None self.received_with = None self.received_by = None self.retriever = None self.log.trace('done\n') def retriever_info(self, retriever): self.log.trace() self.received_from = retriever.received_from self.received_with = retriever.received_with self.received_by = retriever.received_by self.retriever = retriever def deliver_message(self, msg, delivered_to=True, received=True): self.log.trace() msg.received_from = self.received_from msg.received_with = self.received_with msg.received_by = self.received_by return self._deliver_message(msg, delivered_to, received) ####################################### class Maildir(DeliverySkeleton, ForkingBase): '''Maildir destination. Parameters: path - path to maildir, which will be expanded for leading '~/' or '~USER/', as well as environment variables. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfMaildirPath(name='path'), ConfString(name='user', required=False, default=None), ConfString(name='filemode', required=False, default='0600'), ) def initialize(self): self.log.trace() self.hostname = localhostname() self.dcount = 0 try: self.conf['filemode'] = int(self.conf['filemode'], 8) except ValueError, o: raise getmailConfigurationError('filemode %s not valid: %s' % (self.conf['filemode'], o)) def __str__(self): self.log.trace() return 'Maildir %s' % self.conf['path'] def showconf(self): self.log.info('Maildir(%s)\n' % self._confstring()) def __deliver_message_maildir(self, uid, gid, msg, delivered_to, received, stdout, stderr): '''Delivery method run in separate child process. ''' try: if os.name == 'posix': if uid: change_uidgid(None, uid, gid) if os.geteuid() == 0: raise getmailConfigurationError( 'refuse to deliver mail as root' ) if os.getegid() == 0: raise getmailConfigurationError( 'refuse to deliver mail as GID 0' ) f = deliver_maildir( self.conf['path'], msg.flatten(delivered_to, received), self.hostname, self.dcount, self.conf['filemode'] ) stdout.write(f) stdout.flush() os.fsync(stdout.fileno()) os._exit(0) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it stderr.write('maildir delivery process failed (%s)' % o) stderr.flush() os.fsync(stderr.fileno()) os._exit(127) def _deliver_message(self, msg, delivered_to, received): self.log.trace() uid = None gid = None user = self.conf['user'] if os.name == 'posix': if user and uid_of_user(user) != os.geteuid(): # Config specifies delivery as user other than current UID uid = uid_of_user(user) gid = gid_of_uid(uid) if uid == 0: raise getmailConfigurationError( 'refuse to deliver mail as root' ) if gid == 0: raise getmailConfigurationError( 'refuse to deliver mail as GID 0' ) self._prepare_child() stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self.__deliver_message_maildir(uid, gid, msg, delivered_to, received, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) out = stdout.read().strip() err = stderr.read().strip() self.log.debug('maildir delivery process %d exited %d\n' % (childpid, exitcode)) if exitcode or err: raise getmailDeliveryError('maildir delivery %d error (%d, %s)' % (childpid, exitcode, err)) self.dcount += 1 self.log.debug('maildir file %s' % out) return self ####################################### class Mboxrd(DeliverySkeleton, ForkingBase): '''mboxrd destination with fcntl-style locking. Parameters: path - path to mboxrd file, which will be expanded for leading '~/' or '~USER/', as well as environment variables. Note the differences between various subtypes of mbox format (mboxrd, mboxo, mboxcl, mboxcl2) and differences in locking; see the following for details: http://qmail.org/man/man5/mbox.html http://groups.google.com/groups?selm=4ivk9s%24bok%40hustle.rahul.net ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfMboxPath(name='path'), ConfString(name='locktype', required=False, default='lockf'), ConfString(name='user', required=False, default=None), ) def initialize(self): self.log.trace() if self.conf['locktype'] not in ('lockf', 'flock'): raise getmailConfigurationError('unknown mbox lock type: %s' % self.conf['locktype']) def __str__(self): self.log.trace() return 'Mboxrd %s' % self.conf['path'] def showconf(self): self.log.info('Mboxrd(%s)\n' % self._confstring()) def __deliver_message_mbox(self, uid, gid, msg, delivered_to, received, stdout, stderr): '''Delivery method run in separate child process. ''' try: if os.name == 'posix': if uid: change_uidgid(None, uid, gid) if os.geteuid() == 0: raise getmailConfigurationError( 'refuse to deliver mail as root' ) if os.getegid() == 0: raise getmailConfigurationError( 'refuse to deliver mail as GID 0' ) if not os.path.exists(self.conf['path']): raise getmailDeliveryError('mboxrd does not exist (%s)' % self.conf['path']) if not os.path.isfile(self.conf['path']): raise getmailDeliveryError('not an mboxrd file (%s)' % self.conf['path']) # Open mbox file, refusing to create it if it doesn't exist fd = os.open(self.conf['path'], os.O_RDWR) status_old = os.fstat(fd) f = os.fdopen(fd, 'r+b') lock_file(f, self.conf['locktype']) # Check if it _is_ an mbox file. mbox files must start with "From " # in their first line, or are 0-length files. f.seek(0, 0) first_line = f.readline() if first_line and not first_line.startswith('From '): # Not an mbox file; abort here unlock_file(f, self.conf['locktype']) raise getmailDeliveryError('not an mboxrd file (%s)' % self.conf['path']) # Seek to end f.seek(0, 2) try: # Write out message plus blank line with native EOL f.write(msg.flatten(delivered_to, received, include_from=True, mangle_from=True) + os.linesep) f.flush() os.fsync(fd) status_new = os.fstat(fd) # Reset atime try: os.utime(self.conf['path'], (status_old.st_atime, status_new.st_mtime)) except OSError, o: # Not root or owner; readers will not be able to reliably # detect new mail. But you shouldn't be delivering to # other peoples' mboxes unless you're root, anyways. stdout.write('failed to updated mtime/atime of mbox') stdout.flush() os.fsync(stdout.fileno()) unlock_file(f, self.conf['locktype']) except IOError, o: try: if not f.closed: # If the file was opened and we know how long it was, # try to truncate it back to that length # If it's already closed, or the error occurred at # close(), then there's not much we can do. f.truncate(status_old.st_size) except KeyboardInterrupt: raise except StandardError: pass raise getmailDeliveryError( 'failure writing message to mbox file "%s" (%s)' % (self.conf['path'], o) ) os._exit(0) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it stderr.write('mbox delivery process failed (%s)' % o) stderr.flush() os.fsync(stderr.fileno()) os._exit(127) def _deliver_message(self, msg, delivered_to, received): self.log.trace() uid = None gid = None # Get user & group of mbox file st_mbox = os.stat(self.conf['path']) user = self.conf['user'] if os.name == 'posix': if user and uid_of_user(user) != os.geteuid(): # Config specifies delivery as user other than current UID uid = uid_of_user(user) gid = gid_of_uid(uid) if uid == 0: raise getmailConfigurationError( 'refuse to deliver mail as root' ) if gid == 0: raise getmailConfigurationError( 'refuse to deliver mail as GID 0' ) self._prepare_child() stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self.__deliver_message_mbox(uid, gid, msg, delivered_to, received, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) out = stdout.read().strip() err = stderr.read().strip() self.log.debug('mboxrd delivery process %d exited %d\n' % (childpid, exitcode)) if exitcode or err: raise getmailDeliveryError('mboxrd delivery %d error (%d, %s)' % (childpid, exitcode, err)) if out: self.log.debug('mbox delivery: %s' % out) return self ####################################### class MDA_qmaillocal(DeliverySkeleton, ForkingBase): '''qmail-local MDA destination. Passes the message to qmail-local for delivery. qmail-local is invoked as: qmail-local -nN user homedir local dash ext domain sender defaultdelivery Parameters (all optional): qmaillocal - complete path to the qmail-local binary. Defaults to "/var/qmail/bin/qmail-local". user - username supplied to qmail-local as the "user" argument. Defaults to the login name of the current effective user ID. If supplied, getmail will also change the effective UID to that of the user before running qmail-local. group - If supplied, getmail will change the effective GID to that of the named group before running qmail-local. homedir - complete path to the directory supplied to qmail-local as the "homedir" argument. Defaults to the home directory of the current effective user ID. localdomain - supplied to qmail-local as the "domain" argument. Defaults to localhostname(). defaultdelivery - supplied to qmail-local as the "defaultdelivery" argument. Defaults to "./Maildir/". conf-break - supplied to qmail-local as the "dash" argument and used to calculate ext from local. Defaults to "-". localpart_translate - a string representing a Python 2-tuple of strings (i.e. "('foo', 'bar')"). If supplied, the retrieved message recipient address will have any leading instance of "foo" replaced with "bar" before being broken into "local" and "ext" for qmail- local (according to the values of "conf-break" and "user"). This can be used to add or remove a prefix of the address. strip_delivered_to - if set, existing Delivered-To: header fields will be removed from the message before processing by qmail-local. This may be necessary to prevent qmail-local falsely detecting a looping message if (for instance) the system retrieving messages otherwise believes it has the same domain name as the POP server. Inappropriate use, however, may cause message loops. allow_root_commands (boolean, optional) - if set, external commands are allowed when running as root. The default is not to allow such behaviour. For example, if getmail is run as user "exampledotorg", which has virtual domain "example.org" delegated to it with a virtualdomains entry of "example.org:exampledotorg", and messages are retrieved with envelope recipients like "trimtext-localpart@example.org", the messages could be properly passed to qmail-local with a localpart_translate value of "('trimtext-', '')" (and perhaps a defaultdelivery value of "./Maildirs/postmaster/" or similar). ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfFile(name='qmaillocal', required=False, default='/var/qmail/bin/qmail-local'), ConfString(name='user', required=False, default=pwd.getpwuid(os.geteuid()).pw_name), ConfString(name='group', required=False, default=None), ConfDirectory(name='homedir', required=False, default=pwd.getpwuid(os.geteuid()).pw_dir), ConfString(name='localdomain', required=False, default=localhostname()), ConfString(name='defaultdelivery', required=False, default='./Maildir/'), ConfString(name='conf-break', required=False, default='-'), ConfTupleOfStrings(name='localpart_translate', required=False, default="('', '')"), ConfBool(name='strip_delivered_to', required=False, default=False), ConfBool(name='allow_root_commands', required=False, default=False), ) def initialize(self): self.log.trace() def __str__(self): self.log.trace() return 'MDA_qmaillocal %s' % self._confstring() def showconf(self): self.log.info('MDA_qmaillocal(%s)\n' % self._confstring()) def _deliver_qmaillocal(self, msg, msginfo, delivered_to, received, stdout, stderr): try: args = ( self.conf['qmaillocal'], self.conf['qmaillocal'], '--', self.conf['user'], self.conf['homedir'], msginfo['local'], msginfo['dash'], msginfo['ext'], self.conf['localdomain'], msginfo['sender'], self.conf['defaultdelivery'] ) self.log.debug('about to execl() with args %s\n' % str(args)) # Modify message if self.conf['strip_delivered_to']: msg.remove_header('delivered-to') # Also don't insert a Delivered-To: header. delivered_to = None # Write out message msgfile = tempfile.TemporaryFile() msgfile.write(msg.flatten(delivered_to, received)) msgfile.flush() os.fsync(msgfile.fileno()) # Rewind msgfile.seek(0) # Set stdin to read from this file os.dup2(msgfile.fileno(), 0) # Set stdout and stderr to write to files os.dup2(stdout.fileno(), 1) os.dup2(stderr.fileno(), 2) change_usergroup(self.log, self.conf['user'], self.conf['group']) # At least some security... if ((os.geteuid() == 0 or os.getegid() == 0) and not self.conf['allow_root_commands']): raise getmailConfigurationError( 'refuse to invoke external commands as root ' 'or GID 0 by default' ) os.execl(*args) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it stderr.write('exec of qmail-local failed (%s)' % o) stderr.flush() os.fsync(stderr.fileno()) os._exit(127) def _deliver_message(self, msg, delivered_to, received): self.log.trace() self._prepare_child() if msg.recipient == None: raise getmailConfigurationError( 'MDA_qmaillocal destination requires a message source that ' 'preserves the message envelope' ) msginfo = { 'sender' : msg.sender, 'local' : '@'.join(msg.recipient.lower().split('@')[:-1]) } self.log.debug('recipient: extracted local-part "%s"\n' % msginfo['local']) xlate_from, xlate_to = self.conf['localpart_translate'] if xlate_from or xlate_to: if msginfo['local'].startswith(xlate_from): self.log.debug('recipient: translating "%s" to "%s"\n' % (xlate_from, xlate_to)) msginfo['local'] = xlate_to + msginfo['local'][len(xlate_from):] else: self.log.debug('recipient: does not start with xlate_from ' '"%s"\n' % xlate_from) self.log.debug('recipient: translated local-part "%s"\n' % msginfo['local']) if self.conf['conf-break'] in msginfo['local']: msginfo['dash'] = self.conf['conf-break'] msginfo['ext'] = self.conf['conf-break'].join( msginfo['local'].split(self.conf['conf-break'])[1:] ) else: msginfo['dash'] = '' msginfo['ext'] = '' self.log.debug('recipient: set dash to "%s", ext to "%s"\n' % (msginfo['dash'], msginfo['ext'])) stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self._deliver_qmaillocal(msg, msginfo, delivered_to, received, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) out = stdout.read().strip() err = stderr.read().strip() self.log.debug('qmail-local %d exited %d\n' % (childpid, exitcode)) if exitcode == 111: raise getmailDeliveryError('qmail-local %d temporary error (%s)' % (childpid, err)) elif exitcode: raise getmailDeliveryError('qmail-local %d error (%d, %s)' % (childpid, exitcode, err)) if out and err: info = '%s:%s' % (out, err) else: info = out or err return 'MDA_qmaillocal (%s)' % info ####################################### class MDA_external(DeliverySkeleton, ForkingBase): '''Arbitrary external MDA destination. Parameters: path - path to the external MDA binary. unixfrom - (boolean) whether to include a Unix From_ line at the beginning of the message. Defaults to False. arguments - a valid Python tuple of strings to be passed as arguments to the command. The following replacements are available if supported by the retriever: %(sender) - envelope return path %(recipient) - recipient address %(domain) - domain-part of recipient address %(local) - local-part of recipient address %(mailbox) - for IMAP retrievers, the name of the server-side mailbox/folder the message was retrieved from. Will be empty for POP. Warning: the text of these replacements is taken from the message and is therefore under the control of a potential attacker. DO NOT PASS THESE VALUES TO A SHELL -- they may contain unsafe shell metacharacters or other hostile constructions. example: path = /path/to/mymda arguments = ('--demime', '-f%(sender)', '--', '%(recipient)') user (string, optional) - if provided, the external command will be run as the specified user. This requires that the main getmail process have permission to change the effective user ID. group (string, optional) - if provided, the external command will be run with the specified group ID. This requires that the main getmail process have permission to change the effective group ID. allow_root_commands (boolean, optional) - if set, external commands are allowed when running as root. The default is not to allow such behaviour. ignore_stderr (boolean, optional) - if set, getmail will not consider the program writing to stderr to be an error. The default is False. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfFile(name='path'), ConfTupleOfStrings(name='arguments', required=False, default="()"), ConfString(name='user', required=False, default=None), ConfString(name='group', required=False, default=None), ConfBool(name='allow_root_commands', required=False, default=False), ConfBool(name='unixfrom', required=False, default=False), ConfBool(name='ignore_stderr', required=False, default=False), ) def initialize(self): self.log.trace() self.conf['command'] = os.path.basename(self.conf['path']) if not os.access(self.conf['path'], os.X_OK): raise getmailConfigurationError('%s not executable' % self.conf['path']) if type(self.conf['arguments']) != tuple: raise getmailConfigurationError( 'incorrect arguments format; see documentation (%s)' % self.conf['arguments'] ) def __str__(self): self.log.trace() return 'MDA_external %s (%s)' % (self.conf['command'], self._confstring()) def showconf(self): self.log.info('MDA_external(%s)\n' % self._confstring()) def _deliver_command(self, msg, msginfo, delivered_to, received, stdout, stderr): try: # Write out message with native EOL convention msgfile = tempfile.TemporaryFile() msgfile.write(msg.flatten(delivered_to, received, include_from=self.conf['unixfrom'])) msgfile.flush() os.fsync(msgfile.fileno()) # Rewind msgfile.seek(0) # Set stdin to read from this file os.dup2(msgfile.fileno(), 0) # Set stdout and stderr to write to files os.dup2(stdout.fileno(), 1) os.dup2(stderr.fileno(), 2) change_usergroup(self.log, self.conf['user'], self.conf['group']) # At least some security... if ((os.geteuid() == 0 or os.getegid() == 0) and not self.conf['allow_root_commands']): raise getmailConfigurationError( 'refuse to invoke external commands as root ' 'or GID 0 by default' ) args = [self.conf['path'], self.conf['path']] msginfo['mailbox'] = self.retriever.mailbox_selected or '' for arg in self.conf['arguments']: arg = expand_user_vars(arg) for (key, value) in msginfo.items(): arg = arg.replace('%%(%s)' % key, value) args.append(arg) self.log.debug('about to execl() with args %s\n' % str(args)) os.execl(*args) except StandardError, o: # Child process; any error must cause us to exit nonzero for parent # to detect it stderr.write('exec of command %s failed (%s)' % (self.conf['command'], o)) stderr.flush() os.fsync(stderr.fileno()) os._exit(127) def _deliver_message(self, msg, delivered_to, received): self.log.trace() self._prepare_child() msginfo = {} msginfo['sender'] = msg.sender if msg.recipient != None: msginfo['recipient'] = msg.recipient msginfo['domain'] = msg.recipient.lower().split('@')[-1] msginfo['local'] = '@'.join(msg.recipient.split('@')[:-1]) self.log.debug('msginfo "%s"\n' % msginfo) stdout = tempfile.TemporaryFile() stderr = tempfile.TemporaryFile() childpid = os.fork() if not childpid: # Child self._deliver_command(msg, msginfo, delivered_to, received, stdout, stderr) self.log.debug('spawned child %d\n' % childpid) # Parent exitcode = self._wait_for_child(childpid) stdout.seek(0) stderr.seek(0) out = stdout.read().strip() err = stderr.read().strip() self.log.debug('command %s %d exited %d\n' % (self.conf['command'], childpid, exitcode)) if exitcode: raise getmailDeliveryError( 'command %s %d error (%d, %s)' % (self.conf['command'], childpid, exitcode, err) ) elif err: if not self.conf['ignore_stderr']: raise getmailDeliveryError( 'command %s %d wrote to stderr: %s' % (self.conf['command'], childpid, err) ) #else: # User said to ignore stderr, just log it. self.log.info('command %s: %s' % (self, err)) return 'MDA_external command %s (%s)' % (self.conf['command'], out) ####################################### class MultiDestinationBase(DeliverySkeleton): '''Base class for destinations which hand messages off to other destinations. Sub-classes must provide the following attributes and methods: conf - standard ConfigurableBase configuration dictionary log - getmailcore.logging.Logger() instance In addition, sub-classes must populate the following list provided by this base class: _destinations - a list of all destination objects messages could be handed to by this class. ''' def _get_destination(self, path): p = expand_user_vars(path) if p.startswith('[') and p.endswith(']'): destsectionname = p[1:-1] if not destsectionname in self.conf['configparser'].sections(): raise getmailConfigurationError( 'destination specifies section name %s which does not exist' % path ) # Construct destination instance self.log.debug(' getting destination for %s\n' % path) destination_type = self.conf['configparser'].get(destsectionname, 'type') self.log.debug(' type="%s"\n' % destination_type) destination_func = globals().get(destination_type, None) if not callable(destination_func): raise getmailConfigurationError( 'configuration file section %s specifies incorrect ' 'destination type (%s)' % (destsectionname, destination_type) ) destination_args = {'configparser' : self.conf['configparser']} for (name, value) in self.conf['configparser'].items(destsectionname): if name in ('type', 'configparser'): continue self.log.debug(' parameter %s="%s"\n' % (name, value)) destination_args[name] = value self.log.debug(' instantiating destination %s with args %s\n' % (destination_type, destination_args)) dest = destination_func(**destination_args) elif (p.startswith('/') or p.startswith('.')) and p.endswith('/'): dest = Maildir(path=p) elif (p.startswith('/') or p.startswith('.')): dest = Mboxrd(path=p) else: raise getmailConfigurationError( 'specified destination %s not of recognized type' % p ) return dest def initialize(self): self.log.trace() self._destinations = [] def retriever_info(self, retriever): '''Override base class to pass this to the encapsulated destinations. ''' self.log.trace() DeliverySkeleton.retriever_info(self, retriever) # Pass down to all destinations for destination in self._destinations: destination.retriever_info(retriever) ####################################### class MultiDestination(MultiDestinationBase): '''Send messages to one or more other destination objects unconditionally. Parameters: destinations - a tuple of strings, each specifying a destination that messages should be delivered to. These strings will be expanded for leading "~/" or "~user/" and environment variables, then interpreted as maildir/mbox/other-destination-section. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfTupleOfStrings(name='destinations'), ) def initialize(self): self.log.trace() MultiDestinationBase.initialize(self) dests = [expand_user_vars(item) for item in self.conf['destinations']] for item in dests: try: dest = self._get_destination(item) except getmailConfigurationError, o: raise getmailConfigurationError('%s destination error %s' % (item, o)) self._destinations.append(dest) if not self._destinations: raise getmailConfigurationError('no destinations specified') def _confstring(self): '''Override the base class implementation. ''' self.log.trace() confstring = '' for dest in self._destinations: if confstring: confstring += ', ' confstring += '%s' % dest return confstring def __str__(self): self.log.trace() return 'MultiDestination (%s)' % self._confstring() def showconf(self): self.log.info('MultiDestination(%s)\n' % self._confstring()) def _deliver_message(self, msg, delivered_to, received): self.log.trace() for dest in self._destinations: dest.deliver_message(msg, delivered_to, received) return self ####################################### class MultiSorterBase(MultiDestinationBase): '''Base class for multiple destinations with address matching. ''' def initialize(self): self.log.trace() MultiDestinationBase.initialize(self) self.default = self._get_destination(self.conf['default']) self._destinations.append(self.default) self.targets = [] try: _locals = self.conf['locals'] # Special case for convenience if user supplied one base 2-tuple if (len(_locals) == 2 and type(_locals[0]) == str and type(_locals[1]) == str): _locals = (_locals, ) for item in _locals: if not (type(item) == tuple and len(item) == 2 and type(item[0]) == str and type(item[1]) == str): raise getmailConfigurationError( 'invalid syntax for locals; see documentation' ) for (pattern, path) in _locals: try: dest = self._get_destination(path) except getmailConfigurationError, o: raise getmailConfigurationError( 'pattern %s destination error %s' % (pattern, o) ) self.targets.append((re.compile(pattern, re.IGNORECASE), dest)) self._destinations.append(dest) except re.error, o: raise getmailConfigurationError('invalid regular expression %s' % o) def _confstring(self): ''' Override the base class implementation; locals isn't readable that way. ''' self.log.trace() confstring = 'default=%s' % self.default for (pattern, destination) in self.targets: confstring += ', %s->%s' % (pattern.pattern, destination) return confstring ####################################### class MultiSorter(MultiSorterBase): '''Multiple destination with envelope recipient address matching. Parameters: default - the default destination. Messages not matching any "local" patterns (see below) will be delivered here. locals - an optional tuple of items, each being a 2-tuple of quoted strings. Each quoted string pair is a regular expression and a maildir/mbox/other destination. In the general case, an email address is a valid regular expression. Each pair is on a separate line; the second and subsequent lines need to have leading whitespace to be considered a continuation of the "locals" configuration. If the recipient address matches a given pattern, it will be delivered to the corresponding destination. A destination is assumed to be a maildir if it starts with a dot or slash and ends with a slash. A destination is assumed to be an mboxrd file if it starts with a dot or a slash and does not end with a slash. A destination may also be specified by section name, i.e. "[othersectionname]". Multiple patterns may match a given recipient address; the message will be delivered to /all/ destinations with matching patterns. Patterns are matched case-insensitively. example: default = /home/kellyw/Mail/postmaster/ locals = ( ("jason@example.org", "/home/jasonk/Maildir/"), ("sales@example.org", "/home/karlyk/Mail/sales"), ("abuse@(example.org|example.net)", "/home/kellyw/Mail/abuse/"), ("^(jeff|jefferey)(\.s(mith)?)?@.*$", "[jeff-mail-delivery]"), ("^.*@(mail.)?rapinder.example.org$", "/home/rapinder/Maildir/") ) In it's simplest form, locals is merely a list of pairs of email addresses and corresponding maildir/mbox paths. Don't worry about the details of regular expressions if you aren't familiar with them. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfString(name='default'), ConfTupleOfTupleOfStrings(name='locals', required=False, default="()"), ) def __str__(self): self.log.trace() return 'MultiSorter (%s)' % self._confstring() def showconf(self): self.log.info('MultiSorter(%s)\n' % self._confstring()) def _deliver_message(self, msg, delivered_to, received): self.log.trace() matched = [] if msg.recipient == None and self.targets: raise getmailConfigurationError( 'MultiSorter recipient matching requires a retriever (message ' 'source) that preserves the message envelope' ) for (pattern, dest) in self.targets: self.log.debug('checking recipient %s against pattern %s\n' % (msg.recipient, pattern.pattern)) if pattern.search(msg.recipient): self.log.debug('recipient %s matched target %s\n' % (msg.recipient, dest)) dest.deliver_message(msg, delivered_to, received) matched.append(str(dest)) if not matched: if self.targets: self.log.debug('recipient %s not matched; using default %s\n' % (msg.recipient, self.default)) else: self.log.debug('using default %s\n' % self.default) return 'MultiSorter (default %s)' % self.default.deliver_message( msg, delivered_to, received ) return 'MultiSorter (%s)' % matched ####################################### class MultiGuesser(MultiSorterBase): '''Multiple destination with header field address matching. Parameters: default - see MultiSorter for definition. locals - see MultiSorter for definition. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfString(name='default'), ConfTupleOfTupleOfStrings(name='locals', required=False, default="()"), ) def __str__(self): self.log.trace() return 'MultiGuesser (%s)' % self._confstring() def showconf(self): self.log.info('MultiGuesser(%s)\n' % self._confstring()) def _deliver_message(self, msg, delivered_to, received): self.log.trace() matched = [] header_addrs = [] fieldnames = ( ('delivered-to', ), ('envelope-to', ), ('x-envelope-to', ), ('apparently-to', ), ('resent-to', 'resent-cc', 'resent-bcc'), ('to', 'cc', 'bcc'), ) for fields in fieldnames: for field in fields: self.log.debug( 'looking for addresses in %s header fields\n' % field ) header_addrs.extend( [addr for (name, addr) in email.Utils.getaddresses( msg.get_all(field, []) ) if addr] ) if header_addrs: # Got some addresses, quit here self.log.debug('found total of %d addresses (%s)\n' % (len(header_addrs), header_addrs)) break else: self.log.debug('no addresses found, continuing\n') for (pattern, dest) in self.targets: for addr in header_addrs: self.log.debug('checking address %s against pattern %s\n' % (addr, pattern.pattern)) if pattern.search(addr): self.log.debug('address %s matched target %s\n' % (addr, dest)) dest.deliver_message(msg, delivered_to, received) matched.append(str(dest)) # Only deliver once to each destination; this one matched, # so we don't need to check any remaining addresses against # this pattern break if not matched: if self.targets: self.log.debug('no addresses matched; using default %s\n' % self.default) else: self.log.debug('using default %s\n' % self.default) return 'MultiGuesser (default %s)' % self.default.deliver_message( msg, delivered_to, received ) return 'MultiGuesser (%s)' % matched getmail-4.43.0/getmailcore/retrievers.py0000755000175000017500000004640412206535137021674 0ustar charlesccharlesc00000000000000#!/usr/bin/env python2.3 '''Classes implementing retrievers (message sources getmail can retrieve mail from). Currently implemented: SimplePOP3Retriever SimplePOP3SSLRetriever BrokenUIDLPOP3Retriever BrokenUIDLPOP3SSLRetriever MultidropPOP3Retriever MultidropPOP3SSLRetriever MultidropSDPSRetriever SimpleIMAPRetriever -- IMAP, as a protocol, is a FPOS, and it shows. The Python standard library module imaplib leaves much up to the user because of this. SimpleIMAPSSLRetriever - the above, for IMAP-over-SSL. MultidropIMAPRetriever MultidropIMAPSSLRetriever ''' __all__ = [ 'SimplePOP3Retriever', 'SimplePOP3SSLRetriever', 'BrokenUIDLPOP3Retriever', 'BrokenUIDLPOP3SSLRetriever', 'MultidropPOP3Retriever', 'MultidropPOP3SSLRetriever', 'MultidropSDPSRetriever', 'SimpleIMAPRetriever', 'SimpleIMAPSSLRetriever', 'MultidropIMAPRetriever', 'MultidropIMAPSSLRetriever', ] import os import poplib import imaplib import types from getmailcore.exceptions import * from getmailcore.constants import * from getmailcore.utilities import * from getmailcore.baseclasses import * from getmailcore._retrieverbases import * # # Functional classes # ####################################### class SimplePOP3Retriever(POP3RetrieverBase, POP3initMixIn): '''Retriever class for single-user POP3 mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=110), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ConfBool(name='delete_dup_msgids', required=False, default=False), ) received_from = None received_with = 'POP3' received_by = localhostname() def __str__(self): self.log.trace() return 'SimplePOP3Retriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('SimplePOP3Retriever(%s)' % self._confstring() + os.linesep) ####################################### class SimplePOP3SSLRetriever(POP3RetrieverBase, POP3SSLinitMixIn): '''Retriever class for single-user POP3-over-SSL mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=POP3_ssl_port), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ConfBool(name='delete_dup_msgids', required=False, default=False), ConfFile(name='keyfile', required=False, default=None), ConfFile(name='certfile', required=False, default=None), ) received_from = None received_with = 'POP3-SSL' received_by = localhostname() def __str__(self): self.log.trace() return 'SimplePOP3SSLRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('SimplePOP3SSLRetriever(%s)' % self._confstring() + os.linesep) ####################################### class BrokenUIDLPOP3RetrieverBase(POP3RetrieverBase): '''Retriever base class for single-user POP3 mailboxes on servers that do not properly assign unique IDs to messages. Since with these broken servers we cannot rely on UIDL, we have to use message numbers, which are unique within a POP3 session, but which change across sessions. This class therefore can not be used to leave old mail on the server and download only new mail. ''' received_from = None received_by = localhostname() def _read_oldmailfile(self): '''Force list of old messages to be empty by making this a no-op, so duplicated IDs are always treated as new messages.''' self.log.trace() def write_oldmailfile(self, unused, **kwargs): '''Short-circuit writing the oldmail file.''' self.log.trace() def _getmsglist(self): '''Don't rely on UIDL; instead, use just the message number.''' self.log.trace() try: (response, msglist, octets) = self.conn.list() for line in msglist: msgnum = int(line.split()[0]) msgsize = int(line.split()[1]) self.msgnum_by_msgid[msgnum] = msgnum self.msgid_by_msgnum[msgnum] = msgnum self.msgsizes[msgnum] = msgsize self.sorted_msgnum_msgid = sorted(self.msgid_by_msgnum.items()) except poplib.error_proto, o: raise getmailOperationError('POP error (%s)' % o) self.gotmsglist = True ####################################### class BrokenUIDLPOP3Retriever(BrokenUIDLPOP3RetrieverBase, POP3initMixIn): '''For broken POP3 servers without SSL. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=110), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ) received_with = 'POP3' def __str__(self): self.log.trace() return 'BrokenUIDLPOP3Retriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('BrokenUIDLPOP3Retriever(%s)' % self._confstring() + os.linesep) ####################################### class BrokenUIDLPOP3SSLRetriever(BrokenUIDLPOP3RetrieverBase, POP3SSLinitMixIn): '''For broken POP3 servers with SSL. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=POP3_ssl_port), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ConfFile(name='keyfile', required=False, default=None), ConfFile(name='certfile', required=False, default=None), ) received_with = 'POP3-SSL' def __str__(self): self.log.trace() return 'BrokenUIDLPOP3SSLRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('BrokenUIDLPOP3SSLRetriever(%s)' % self._confstring() + os.linesep) ####################################### class MultidropPOP3Retriever(MultidropPOP3RetrieverBase, POP3initMixIn): '''Retriever class for multi-drop POP3 mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=110), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ConfString(name='envelope_recipient'), ) received_from = None received_with = 'POP3' received_by = localhostname() def __str__(self): self.log.trace() return 'MultidropPOP3Retriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('MultidropPOP3Retriever(%s)' % self._confstring() + os.linesep) ####################################### class MultidropPOP3SSLRetriever(MultidropPOP3RetrieverBase, POP3SSLinitMixIn): '''Retriever class for multi-drop POP3-over-SSL mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=POP3_ssl_port), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfBool(name='use_apop', required=False, default=False), ConfString(name='envelope_recipient'), ConfFile(name='keyfile', required=False, default=None), ConfFile(name='certfile', required=False, default=None), ) received_from = None received_with = 'POP3-SSL' received_by = localhostname() def __str__(self): self.log.trace() return 'MultidropPOP3SSLRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('MultidropPOP3SSLRetriever(%s)' % self._confstring() + os.linesep) ####################################### class MultidropSDPSRetriever(SimplePOP3Retriever, POP3initMixIn): '''Retriever class for multi-drop SDPS (demon.co.uk) mailboxes. Extend POP3 class to include support for Demon's protocol extensions, known as SDPS. A non-standard command (*ENV) is used to retrieve the message envelope. See http://www.demon.net/helpdesk/products/mail/sdps-tech.shtml for details. Support originally requested by Paul Clifford for getmail v.2/3. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=110), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), # Demon apparently doesn't support APOP ConfBool(name='use_apop', required=False, default=False), ) received_from = None received_with = 'SDPS' received_by = localhostname() def __str__(self): self.log.trace() return 'MultidropSDPSRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('MultidropSDPSRetriever(%s)' % self._confstring() + os.linesep) def _getmsgbyid(self, msgid): self.log.trace() msg = SimplePOP3Retriever._getmsgbyid(self, msgid) # The magic of SDPS is the "*ENV" command. Implement it: try: msgnum = self._getmsgnumbyid(msgid) resp, lines, octets = self.conn._longcmd('*ENV %i' % msgnum) except poplib.error_proto, o: raise getmailConfigurationError( 'server does not support *ENV (%s)' % o ) if len(lines) < 4: raise getmailOperationError('short *ENV response (%s)' % lines) msg.sender = lines[2] msg.recipient = lines[3] return msg ####################################### class SimpleIMAPRetriever(IMAPRetrieverBase, IMAPinitMixIn): '''Retriever class for single-user IMAPv4 mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=imaplib.IMAP4_PORT), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfTupleOfUnicode(name='mailboxes', required=False, default="('INBOX', )", allow_specials=('ALL',)), ConfBool(name='use_peek', required=False, default=True), ConfString(name='move_on_delete', required=False, default=None), # imaplib.IMAP4.login_cram_md5() requires the (unimplemented) # .authenticate(), so we can't do this yet (?). ConfBool(name='use_cram_md5', required=False, default=False), ConfBool(name='use_kerberos', required=False, default=False), ) received_from = None received_with = 'IMAP4' received_by = localhostname() def __str__(self): self.log.trace() return 'SimpleIMAPRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('SimpleIMAPRetriever(%s)' % self._confstring() + os.linesep) ####################################### class SimpleIMAPSSLRetriever(IMAPRetrieverBase, IMAPSSLinitMixIn): '''Retriever class for single-user IMAPv4-over-SSL mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), # socket.ssl() and socket timeouts are incompatible in Python 2.3 #ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=imaplib.IMAP4_SSL_PORT), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfTupleOfUnicode(name='mailboxes', required=False, default="('INBOX', )", allow_specials=('ALL',)), ConfBool(name='use_peek', required=False, default=True), ConfString(name='move_on_delete', required=False, default=None), ConfFile(name='keyfile', required=False, default=None), ConfFile(name='certfile', required=False, default=None), # imaplib.IMAP4.login_cram_md5() requires the (unimplemented) # .authenticate(), so we can't do this yet (?). ConfBool(name='use_cram_md5', required=False, default=False), ConfBool(name='use_kerberos', required=False, default=False), ) received_from = None received_with = 'IMAP4-SSL' received_by = localhostname() def __str__(self): self.log.trace() return 'SimpleIMAPSSLRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('SimpleIMAPSSLRetriever(%s)' % self._confstring() + os.linesep) ####################################### class MultidropIMAPRetriever(MultidropIMAPRetrieverBase, IMAPinitMixIn): '''Retriever class for multi-drop IMAPv4 mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=imaplib.IMAP4_PORT), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfTupleOfUnicode(name='mailboxes', required=False, default="('INBOX', )", allow_specials=('ALL',)), ConfBool(name='use_peek', required=False, default=True), ConfString(name='move_on_delete', required=False, default=None), # imaplib.IMAP4.login_cram_md5() requires the (unimplemented) # .authenticate(), so we can't do this yet (?). ConfBool(name='use_cram_md5', required=False, default=False), ConfBool(name='use_kerberos', required=False, default=False), ConfString(name='envelope_recipient'), ) received_from = None received_with = 'IMAP4' received_by = localhostname() def __str__(self): self.log.trace() return 'MultidropIMAPRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('MultidropIMAPRetriever(%s)' % self._confstring() + os.linesep) ####################################### class MultidropIMAPSSLRetriever(MultidropIMAPRetrieverBase, IMAPSSLinitMixIn): '''Retriever class for multi-drop IMAPv4-over-SSL mailboxes. ''' _confitems = ( ConfInstance(name='configparser', required=False), ConfDirectory(name='getmaildir', required=False, default='~/.getmail/'), # socket.ssl() and socket timeouts are incompatible in Python 2.3 #ConfInt(name='timeout', required=False, default=180), ConfString(name='server'), ConfInt(name='port', required=False, default=imaplib.IMAP4_SSL_PORT), ConfString(name='username'), ConfPassword(name='password', required=False, default=None), ConfTupleOfUnicode(name='mailboxes', required=False, default="('INBOX', )", allow_specials=('ALL',)), ConfBool(name='use_peek', required=False, default=True), ConfString(name='move_on_delete', required=False, default=None), ConfFile(name='keyfile', required=False, default=None), ConfFile(name='certfile', required=False, default=None), # imaplib.IMAP4.login_cram_md5() requires the (unimplemented) # .authenticate(), so we can't do this yet (?). ConfBool(name='use_cram_md5', required=False, default=False), ConfBool(name='use_kerberos', required=False, default=False), ConfString(name='envelope_recipient'), ) received_from = None received_with = 'IMAP4-SSL' received_by = localhostname() def __str__(self): self.log.trace() return 'MultidropIMAPSSLRetriever:%s@%s:%s' % ( self.conf.get('username', 'username'), self.conf.get('server', 'server'), self.conf.get('port', 'port') ) def showconf(self): self.log.trace() self.log.info('MultidropIMAPSSLRetriever(%s)' % self._confstring() + os.linesep) getmail-4.43.0/getmail_mbox0000755000175000017500000000551311770242167017226 0ustar charlesccharlesc00000000000000#!/usr/bin/env python '''getmail_mbox Reads a message from stdin and delivers it to an mbox file specified as a commandline argument. Expects the envelope sender address to be in the environment variable SENDER. Copyright (C) 2001-2012 Charles Cazabon This program is free software; you can redistribute it and/or modify it under the terms of version 2 (only) of the GNU General Public License as published by the Free Software Foundation. A copy of this license should be included in the file COPYING. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ''' import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3 ' 'or later') import os import email from getmailcore.exceptions import * from getmailcore.message import Message from getmailcore import logging, constants, destinations verbose = False path = None for arg in sys.argv[1:]: if arg in ('-h', '--help'): sys.stdout.write('Usage: %s mboxpath\n' % sys.argv[0]) raise SystemExit elif arg in ('-v', '--verbose'): verbose = True elif not path: path = arg else: raise SystemExit('Error: mbox path specified twice (was %s, now %s)' % (path, arg)) if os.name == 'posix' and (os.geteuid() == 0 or os.getegid() == 0): raise SystemExit('Error: do not run this program as user root') logger = logging.Logger() logger.addhandler(sys.stderr, constants.WARNING) if verbose: logger.addhandler(sys.stdout, constants.INFO, constants.INFO) if not (path and (path.startswith('.') or path.startswith('/')) and not path.endswith('/')): raise SystemExit('Error: mbox must start with . or / and not end with /') if os.path.exists(path) and not os.path.isfile(path): raise SystemExit('Error: %s is not an mbox' % path) msg = Message(fromfile=sys.stdin) if os.environ.has_key('SENDER'): msg.sender = os.environ['SENDER'] if os.environ.has_key('RECIPIENT'): msg.recipient = os.environ['RECIPIENT'] try: dest = destinations.Mboxrd(path=path) d = dest.deliver_message(msg, True, False) except getmailDeliveryError, o: raise SystemExit('Error: delivery error delivering to mboxrd %s (%s)' % (path, o)) except StandardError, o: raise SystemExit('Error: other error delivering to mboxrd %s (%s)' % (path, o)) if verbose: sys.stdout.write('Delivered to mboxrd %s\n' % path) getmail-4.43.0/docs/0000755000175000017500000000000012206542270015546 5ustar charlesccharlesc00000000000000getmail-4.43.0/docs/faq.html0000644000175000017500000021432512206542267017220 0ustar charlesccharlesc00000000000000 getmail frequently-asked questions (FAQs) (version 4)

getmail documentation

This is the documentation for getmail version 4. Version 4 includes numerous changes from version 3.x; if you are using getmail version 3, please refer to the documentation included with that version of the software.

getmail is Copyright © 1998-2009 Charles Cazabon.

getmail is licensed under the GNU General Public License version 2 (only). If you wish to obtain a license to distribute getmail under other terms, please contact me directly.

Table of Contents

Frequently-Asked Questions (FAQs)

The following questions about getmail are answered more-or-less frequently. Please also read the unexpected behaviour section of the troubleshooting document.

About getmail

What is getmail?

getmail is a mail retriever with support for POP3, POP3-over-SSL, IMAP4, IMAP4-over-SSL, and SDPS mail accounts. It supports normal single-user mail accounts and multidrop (domain) mailboxes. getmail is written in Python, and licensed under the GNU General Public License version 2.

What platforms/machines does getmail run on?

getmail runs on basically any platform. It's designed to, and written in a language that helps to maintain cross-platform compatibility. getmail is known to run on the following platforms:

  • Linux-based GNU systems (all distributions)
  • HURD-based GNU systems
  • FreeBSD
  • OpenBSD
  • NetBSD
  • HP/UX
  • Sun Solaris
  • IBM AIX
  • Digital/Compaq Tru64 (a.k.a OSF/1) UNIX
  • SGI Irix
  • other commercial Unices
  • Digital VMS / OpenVMS
  • BeOS
  • Amiga OS
  • OS/2
  • Cygwin on Windows
  • Macintosh OS X
  • Macintosh OS 9

But getmail will also run on other, less common platforms. The only real requirement is that Python run on that platform, and porting Python is generally very easy.

Does getmail run on MS Windows?

Yes, under the free Cygwin package. Running recent versions of Python under Cygwin requires a process known as "rebasing" your Cygwin installation; you can find details in this Python developers' mailing list message.

Does getmail run on Macintosh systems?

Yes.

Does getmail require Unix/Linux?

No.

How can I get support for getmail?

getmail is Free Software. As such, it comes with no warranty. However, I will do my best to support getmail on a voluntary basis through the getmail mailing list.

If you are using getmail in a commercial or other environment where problems cost money, consider contacting me privately for commercial support, by emailing <charlesc-getmail-support @ pyropus.ca>

If you have questions about getmail, the first step is to read the documentation, and the remainder of the Frequently Asked Questions. If your question isn't answered there, search the getmail mailing list archives.

If you still haven't found an answer to your question, please subscribe to the getmail users' mailing list by sending a blank email to <getmail-subscribe @ lists.pyropus.ca>. If you post your question there, I will see it. As an additional bonus, your question may be answered by another member of the list.

I think I found a bug! How do I report it?

First, make sure that you are running the latest version. You can always find what is the latest version by checking this page at the original web site:
http://pyropus.ca/software/getmail/.
If you running an older version of the software, chances are whatever bug you may have found has already been fixed.

Ideally, you should join the mailing list and send your bug report there. You should include the following information:

  • getmail version
  • Python version
  • any error message which getmail displayed
  • the output from running getmail with your normal options plus --dump
  • if your problem is getmail not determining the proper local recipient, please include the output of running getmail with your normal options plus --trace, showing the retrieval of one problematic message.

If you absolutely cannot sign up for the mailing list, send the report to me directly at <charlesc-getmail-bugs @ pyropus.ca>. I may not be able to respond to all reports privately, but I will try to address any bugs I find out about this way.

I have a neat idea for random feature "foo" … how do I get you to implement it?

Follow the same instructions as for reporting bugs above — yes, that means I would prefer you submit your idea to the getmail users' mailing list. That will lead to a useful discussion if your feature has not been proposed before.

Why won't you implement random feature "foo"?

Every line of code added to getmail has a certain cost. Every feature added requires code, documentation, and support. Adding features increases the complexity of the software, confuses users, and leads to higher support costs. I therefore weigh features very carefully as a cost-versus-benefit tradeoff before deciding whether to add them.

Some users are confused by this. They think that a feature you don't use has no cost, and therefore if it has any value to anyone, it should be added. That simply isn't the case; the costs of an unused feature are simply borne by others, including me.

If you have asked me to add some feature, and I've said no, this may be the reason. Other possibilities include me simply not having had sufficient time to implement it yet.

Does getmail support virus scanning of retrieved messages?

Yes. You can use getmail message filtering options to do this with an external virus scanning program, or invoke your virus scanning program during delivery with getmail's support for external MDAs.

Also see the FAQ about using getmail with the ClamAV program.

Does getmail support spam filtering of retrieved messages?

Yes. You can use getmail message filtering options to do this with an external spam filtering program, or invoke your spam filtering program during delivery with getmail's support for external MDAs.

Also see the FAQ about using getmail with the SpamAssassin program.

Does getmail support SSL?

Yes. getmail has built in support for POP3-over-SSL and IMAP4-over-SSL.

Does getmail rewrite mail headers when it retrieves mail?

No. Rewriting message header fields is bad for many reasons; the biggest problem is that it causes a loss of critical technical information necessary to track down many mail problems. getmail will add a new Received: header field and a new Delivered-To: header field, but does not rewrite existing headers. You can disable the creation of these header fields.

Can I upgrade from getmail 3 to getmail 4? What about my "oldmail" files?

Yes. getmail version 4 uses exactly the same oldmail-server-port-username naming convention for its oldmail files. The only difference is that version 4 escapes a couple of additional characters in this string so that it is truly cross-platform compatible. If you upgrade from version 3 to version 4, getmail will still remember which messages you've already retrieved.

To upgrade, do the following:

  1. Rename your old getmail rc file, creating a new file in ~/.getmail/getmailrc.
  2. Create a new [options] section, containing the appropriate values from your version 3 rc file [defaults] section.
  3. Create a new [retriever] section, using your previous server configuration values in a new type = SimplePOP3Retriever or type = MultidropPOP3Retriever as appropriate.
  4. Create a new [destination] section, using your previous destination path values in a new type = Maildir, type = Mboxrd, type = MDA_external, or type = MultiSorter destination as appropriate.
  5. If you were retrieving messages from multiple mail accounts in a single version 3 getmail rc file, split them up into one account per version 4 rc file.

That's it.

Why did you write getmail? Why not just use fetchmail?

Short answer: … well, the short answer is mostly unprintable. The long answer is … well, long:

I do not like some of the design choices which were made with fetchmail. getmail does things a little differently, and for my purposes, better. In addition, most people find getmail easier to configure and use than fetchmail. Perhaps most importantly, getmail goes to great lengths to ensure that mail is never lost, while fetchmail (in its default configuration) frequently loses mail, causes mail loops, bounces legitimate messages, and causes many other problems.

When people have pointed out problems in fetchmail's design and implementation, it's maintainer has frequently ignored them, or (worse yet) gone in the completely wrong direction in the name of "fixing" the problems. For instance, fetchmail's configuration file syntax has been criticized as being needlessly difficult to write; instead of cleaning up the syntax, the maintainer instead included a GUI configuration-file-writing program, leading to comments like:

The punchline is that fetchmail sucks, even if it does have giddily-engineered whizbang configurator apps.

As an example, Dan Bernstein, author of qmail and other software packages, once noted to the qmail list:

Last night, root@xxxxxxxxxxxxxxxxx reinjected thirty old messages from various authors to qmail@xxxxxxxxxxxxxx

This sort of idiocy happens much more often than most subscribers know, thanks to a broken piece of software by Eric Raymond called fetchmail. Fortunately, qmail and ezmlm have loop-prevention mechanisms that stop these messages before they are distributed to subscribers. The messages end up bouncing to the wrong place, thanks to another fetchmail bug, but at least the mailing list is protected.

--D. J. Bernstein

The maintainer also ignored dozens of complaints about fetchmail's behaviour, stating (by fiat) that fetchmail was bug-free and had entered "maintenance mode", allowing him to ignore further bug reports.

fetchmail's default configuration values frequently cause lost or misdirected mail, and seem to be chosen to cause maximum pain and inconvenience. From fetchmail's to-do file (emphasis mine):

Maybe refuse multidrop configuration unless "envelope" is _explicitly_ configured … This would prevent a significant class of shoot-self-in-foot problems.

perhaps treat a delivery as "temporarily failed" … This is so you don't lose mail if you configure the wrong envelope header.

fetchmail is famous for mangling messages it retrieves, rather than leaving them alone as a mail-handling program should. getmail will add trace information to messages (so you can see what happened, and when), but will otherwise leave message content alone.

In addition, fetchmail has a long history of security problems:

In July, 2004, it was noted that there may be at least 2 unfixed denial-of-service attacks, 2 unfixed remote-code-execution, 2 unfixed remote-user-access, and 3 unfixed remote-shell attacks against fetchmail. See http://www.mail-archive.com/euglug@euglug.org/msg00971.html for details

I've given up even trying to stay abreast of the various security holes in fetchmail, but others have noted continuing problems, including:

The fetchmail authors' boneheaded decision to create a configuration-file GUI editor (rather than actually giving fetchmail a sane configuration syntax) also came back to bite them in the ass: in October 2005, it became known that fetchmailconf created its files in such a way that users' passwords could be read during file creation.

Addendum, January 2007: since I wrote the above, the following new security problems have been discovered in fetchmail:

  • CVE-2005-4348 - anyone can crash fetchmail by sending messages without headers
  • CVE-2006-0321 - anyone can crash fetchmail by sending a message that fetchmail tries to bounce
  • CVE-2006-5867 - fetchmail can transmit passwords in plaintext even if the user has configured it not to
  • CVE-2006-5974 - anyone can cause fetchmail to crash by triggering certain error code paths

But don't just take my word for it; see http://docs.freebsd.org/cgi/mid.cgi?200102172349.QAA11724 and http://esr.1accesshost.com/ (note: went offline sometime in 2009 or 2010; the content is still available at http://web.archive.org/web/20080621090439/http://esr.1accesshost.com/ ).

getmail users have not had to worry about any of these security holes or design and implementation errors.

Configuring getmail

What is a "domain mailbox"?

A domain (or multidrop) mailbox is a POP3 mailbox which receives mail for all users in a given domain. Normal mailboxes contain mail for a single user (like jason@myisp.co.uk); some Internet Service Providers which provide webhosting or other services will provide a POP3 mailbox which receives mail for all addresses in a given domain (i.e. mail for service@smallcompany.net, sales@smallcompany.net, and indeed anything @smallcompany.net ends up in the same POP3 mailbox).

getmail provides a method of retrieving mail from a domain mailbox and distributing it among the various users automatically. The retriever classes MultidropPOP3Retriever, MultidropPOP3SSLRetriever, MultidropSDPSRetriever, MultidropIMAPRetriever, and MultidropIMAPSSLRetriever provide this capability.

See the documentation on the [retriever] section for details of what the requirements for a multidrop mailbox are. getmail user Matthias Andree also has a web page about multidrop mailboxes.

Do I have to run sendmail or another MTA to use getmail?

No. getmail delivers directly to maildirs, mboxrd files, or via arbitrary MDAs, and never injects mail via SMTP, so no MTA is necessary.

Will getmail deliver mail as root?

No. When run as the root user on a Unix-like system, getmail drops privileges (switches to an unprivileged group and user id) before delivering to maildirs or mboxrd files. You can specify the user explicitly, or let getmail use the owner of the maildir or mboxrd file.

If getmail attempts to deliver mail and finds it has UID 0 or GID 0, it will refuse the delivery and print an error message.

What's a maildir?

A maildir is a mail storage format invented by D. J. Bernstein (author of qmail) that requires no file locking to deliver to safely and reliably, even over NFS. getmail natively supports delivery to maildirs.

See http://qmail.org/man/man5/maildir.html and http://cr.yp.to/proto/maildir.html for details.

What's "mboxrd" format?

There are various sub-types of the mbox mail storage format. mboxrd is the most reliable of them, though (like all mbox types) it still relies on file locking and is therefore more easily corrupted than maildir format. In particular, using mbox files with multiple writers over NFS can be problematic.

For details on the differences between the various mbox sub-types, see http://qmail.org/man/man5/mbox.html.

What's this "envelope sender" and "envelope recipient" stuff?

The "envelope" of an email message is "message metadata"; that is, the message is information, and the envelope is information about the message (information about other information). Knowing this is critical to understanding what a domain or multidrop mailbox is, how it works, and what getmail can do for you.

Others have tried to explain this with varying degrees of success. I'll use the standard analogy of normal postal (i.e. non-electronic) mail:

Message header vs. message envelope

When you receive a letter (a reply from the customer-disservice department of your telephone company, say) it arrives in an envelope. You tear it open, remove the letter, and read it. At the top of the letter is the telephone company's return address, followed by the date the letter was written. Your name and mailing address follow that, and then the remainder of the letter.

The important thing to keep in mind is that the contents of the letter (including the addresses just discussed) are never looked at by the post office. If they can't deliver the letter (your mailing address on the envelope got smudged in the rain), they'll return it to the address listed in the top-left corner of the envelope. They don't check to make sure that the address listed there is the same as the one listed at the top of the letter. Similarly, when they can successfully deliver it, they don't check to make sure that the recipient name and address on the envelope matches the one listed on the letter between the date and the salutation.

The message header fields From: and Resent-from: are equivalent to the block of address information at the top of the letter; it usually contains the name and address of the sender of the message, but it is never actually used in the delivery of the message. Similarly, the To:, cc:, Resent-to:, and Resent-cc: header fields are the equivalent of the block of address information between the date and the salutation on the letter; they usually contain the names and addresses of the intended recipients of the message, but they too are not used in the delivery of the message.

Receiving messages without your address in the message header

You might open an envelope addressed to you and find that the letter inside makes no mention of your name. Your name and address don't appear anywhere in the letter, but it was still successfully delivered to you based on the envelope information. There's nothing strange about this. If someone else opens your mail for you, discards the envelopes, and places the contents in your in-basket, you might wonder how some of it ended up there, because there's nothing to connect you with the message contents.

Email is exactly like this. Each message has two parts, the message contents, and the message envelope. The message contents include the message header, and the message body. The message envelope is made up of exactly one envelope sender address (which can be empty) and one or more envelope recipient addresses. If the message cannot be delivered for any reason, and the envelope sender address is not empty, the message must be returned to the envelope sender address by the mail transfer agent (MTA) which last accepted responsibility for delivering the message. These notifications are known as "bounce messages" or sometimes as "non-delivery notifications". Bounce messages are sent using the empty envelope return path, to prevent mail loops from occurring when a bounce message itself cannot be delivered.

Confusion often arises among novice users about the difference between the message header and the message envelope; they seem to believe that they are not independant. This appears to be an artifact of their use of simple-minded GUI mail user agents (MUAs) that do not allow them to set the envelopes of their messages explicitly, but instead simply use the contents of the From: header field as the envelope sender address, and any addresses found in To:, cc:, and bcc: header fields as the envelope recipient addresses. While these are sensible as default values, more powerful MUAs allow the user to override this choice.

Responsibility for recording the message envelope

The last MTA to receive a message (usually the one running on the POP or IMAP server where you retrieve your mail from) essentially acts as your correspondence secretary, accepting your mail from the postman, opening it, and placing it into your in-basket. Note that this would normally destroy the important information contained in the message envelope. To prevent this loss of information, this MTA is supposed to copy the information from the envelope into new fields in the header of the message content, as if your secretrary copied the sender and recipient addresses onto the back of your letters in felt pen. Unfortunately, some MTAs do not always do this properly, and envelope information can then be lost. When this happens, it makes dealing with certain types of mail messages problematic:

  • bcc'd messages (bcc stands for blind carbon copy), where you are an envelope recipient, but your address does not appear in the message content (i.e., your address does not appear in a To:, cc:, or similar message header field). With bcc'd messages, the From: header field contains the name and address of the author of the message, and the To: and cc: header fields contain the names and addresses of the other, non-blind recipients of the message.
  • mailing list messages, where you are an envelope recipient, but your address does not appear in the message content (i.e., your address does not appear in a To:, cc:, or similar message header field). Mailing list messages have the envelope sender address set to the mailing list manager (so that it can monitor "bad" list addresses for bounces), while the From: header field contains the name and address of the author of the message. The envelope recipient addresses of mailing list messages are the addresses of the list subscribers, while the To: header field usually contains the address of the mailing list.
  • other, less common cases.

MTAs are supposed to record the envelope sender address by placing it into a new Return-Path: header field at the top of the message. They should then record the envelope recipient address(es) in another new header field; sometimes this header field is named Delivered-To:, but it can also be Envelope-To: or one of a few other names.

How this relates to domain or multidrop mailboxes

A domain or multidrop mailbox is one which receives mail for multiple email addresses (commonly all addresses in a given domain). If you do not want all of this mail to go to one person, you need to know who the messages were originally addressed to after retrieving them from the POP/IMAP multidrop mailbox. You cannot do this by looking at the To:, cc:, or other informational message header fields, because they do not actually reflect the message envelope at the time of delivery. Instead, you have to reconstruct the envelope information from the message header fields which the MTA on the server used to record it at the time of delivery.

If the final MTA does not record the message envelope (the envelope sender, and all envelope recipient addresses in the domain mailbox the message was sent to), then mail will be lost or misdirected regardless of which software you use to access the mailbox. The mailbox cannot actually be said to be a domain mailbox in this case; the defining characteristic of a domain mailbox is that it records the envelope correctly. The configuration of the MTA running on the server needs to be fixed so that the envelope is properly recorded for every message it receives.

This rc stuff seems complicated. Does it have to be?

The configuration file format is actually very simple; you don't need to worry about most of it if you're not interested in using those features. The simplest and most common getmail rc file configuration will be for users who want to retrieve all mail from a single-user POP3 mailbox, deliver those messages to a maildir or mbox file, and delete the mail from the server. For maildir, that configuration is:

[options]
delete = True

[retriever]
type = SimplePOP3Retriever
server = my-pop3-servername
username = my-pop3-username
password = my-pop3-password

[destination]
type = Maildir
path = ~/Maildir/

For an mbox file, that configuration is:

[options]
delete = True

[retriever]
type = SimplePOP3Retriever
server = my-pop3-servername
username = my-pop3-username
password = my-pop3-password

[destination]
type = Mboxrd
path = ~/inbox

How do I …

How do I retrieve mail from multiple accounts?

Create a separate getmail rc file for each account, and run getmail with multiple --rcfile options.

Of course, it's really easy to script this for a large number of rc-* files. You might create a script in $HOME/bin/run-getmail.sh containing:

#!/bin/sh
set -e
cd /path/to/my-rc-directory
rcfiles=""
for file in rc-* ; do
  rcfiles="$rcfiles --rcfile $file"
done
exec /path/to/getmail $rcfiles $@

See any beginner's tutorial on Unix shell scripting for details.

How do I get getmail to deliver messages to different mailboxes based on …

If you want getmail to sort messages based on who they're from, or what address appears in the To: or cc: header fields, or based on the Subject: field contents, or anything like that, pick a filtering MDA (like maildrop or procmail), and call it from a getmail MDA_external destination.

How do I stop getmail adding a Delivered-To: header to messages?

Use the delivered_to [options] parameter.

How do I stop getmail adding a Received: header to messages?

Use the received [options] parameter.

How do I make getmail deliver messages by re-injecting with SMTP?

You don't need to. getmail can deliver to maildirs, mboxrd files, or through arbitrary external MDAs.

If you still think you need to, you can use getmail's external MDA support to do so.

How do I create a maildir?

Use the maildirmake command, if you have it installed. Otherwise, run the following command from your shell:

$ mkdir -p /path/to/Maildir/{cur,new,tmp}

Some other maildir-aware programs ship with their own maildir-creation programs; you can use those, or make the above shell command a shellscript or alias if you like.

How do I create an mboxrd file?

Create a completely empty (i.e. zero bytes long) file via your favourite method. The standard utility touch is commonly used:

$ touch /path/to/mboxrd

How do I make getmail deliver messages to an mh folder?

mh clients (and nmh, or "new mh" clients) include a command for delivering a message into your mh folder. In nmh, this command is called rcvstore. You use it as an external message delivery agent (MDA) with getmail's MDA_external destination. Ensure your $HOME/.mh_profile file is configured properly; getmail user Frankye Fattarelli suggests a line like the following is necessary to indicate the path to your mh mail root:

Path: Mail

Then use MDA_external like this (which, after adjusting the path of the command to reflect your mh/nmh installation, should work with either mh or nmh):

[destination]
type = MDA_external
path = /usr/local/libexec/nmh/rcvstore
arguments = ("+inbox", )

Thanks to Frankye Fattarelli for contributing this answer.

How do I run getmail in "daemon" mode?

Use your system's cron utility to run getmail periodically if you wish to have mail retrieved automatically at intervals. This is precisely what cron is designed to do; there's no need to add special code to getmail to do this.

With a reasonably standard system cron utility, a crontab(5) entry like the following will make getmail retrieve mail every hour:

0 * * * * /usr/local/bin/getmail --quiet

How do I make getmail stop after retrieving X messages so that the server actually flushes deleted messages?

Use the max_messages_per_session option to limit the number of messages getmail will process in a single session. Some users with flaky servers use this option to reduce the chances of seeing messages more than once if the server dies in mid-session.

How do I make getmail retrieve mail from Hotmail?

Well, you could write a retriever that speaks Hotmail's proprietary, undocumented, and unsupported access protocol (or pay me to write one), or simply set up the POP3 proxy from the httpmail package, and have getmail retrieve mail from that POP3 proxy.

I'm using getmail. How do I make it …

These are supplementary questions I occasionally see about doing various things to enhance a getmail setup. The solution to many of them is to use a standard Unix technique of some sort to make the system behave in a certain manner, or otherwise change the behaviour of something that's actually outside of getmail proper.

I'm running getmail from cron. How do I temporarily stop it?

Some people ask about temporarily stopping getmail from running from a cron job, possibly because the mail server is down and they don't want to see the warnings cron mails them.

The easiest method is to comment out getmail from your crontab file:

  1. Run
    $ crontab -e
    to edit your crontab file.
  2. Place a # (pound) character at the start of the line containing the call to getmail.
  3. Save the changed file.

When you want to re-enable getmail, edit the file again and un-do the above change.

If you need to do this on a regular basis, you can instead use a "flag file" to tell the system whether or not to run getmail:

Change your cron job or shellscript that normally launches getmail to check for the presence of a certain file first, and have it not run getmail if that file is present. For example, your crontab entry could be changed to do this:

    [ -f ~/.getmail/do-not-run ] || /path/to/getmail

When you don't want getmail to run, touch that file:

    $ touch ~/.getmail/do-not-run

When you want getmail to run again, delete it:

    $ rm -f ~/.getmail/do-not-run

This is even safe for scripting, as creating and removing the file are atomic operations under Unix.

How do I stop multiple instances of getmail from running at the same time?

getmail has no problems running multiple instances in parallel, though you shouldn't attempt to use the same getmail rc file from two different instances at the same time. If you need to prevent two instances of getmail from running simultaneously, use any standard Unix method of providing a mutex for this purpose. One example would be to run getmail under a program like setlock (part of the daemontools package). Change your script or crontab file to invoke getmail like this:

/path/to/setlock -n /path/to/lockfile /path/to/getmail [getmail options]

There are other programs that provide functionality similar to setlock.

Using getmail with other software

getmail user Frankye Fattarelli contributed to the following questions about integrating getmail with SpamAssassin and ClamAV.

How do I use SpamAssassin with getmail?

SpamAssassin can be run in standalone mode or in a client/server configuration. In both configurations, SpamAssassin accepts a wide variety of arguments; please refer to SpamAssassin's manual pages or online documentation for details.

To filter messages through SpamAssassin in a client/server configuration (i.e. with the spamd daemon), use a configuration like this:

[filter]
type = Filter_external
path = /usr/local/bin/spamc
arguments = ("-s 10000", )

The value supplied to the -s option is the maximum message size accepted (in bytes). The default is 250k.

A similar configuration without the spamd daemon would be:

[filter]
type = Filter_external
path = /usr/local/bin/spamassassin
arguments = ("--report", )

The --report option sends the message to the various spam-blocker databases and tags it as spam in your bayesian database.

Note that if you are using Bayesian (learning) filtering, and you've put your SpamAssassin filter after any getmail Filter_classifier, you may have a problem with your learning filter learning getmail's header fields. That is, the headers added by the other filters may get learned, and affect your database. To prevent this, ensure that SpamAssassin ignores these fields by adding the following to your SpamAssassin configuration:

bayes_ignore_header X-getmail-filter-classifier

How do I use ClamAV with getmail?

You should also read this message in the getmail users' mailing list archives and the ClamAV documentation if you want to use ClamAV with getmail.

ClamAV, like SpamAssassin, can by used in standalone or client/server configurations. In either case, you need to add the StreamSaveToDisk option to your clamav.conf file to enable scanning from stdin.

To use ClamAV without the clamd daemon, use a filter configuration like this:

[filter]
type = Filter_classifier
path = /usr/local/bin/clamscan
arguments = ("--stdout", "--no-summary",
    "--mbox", "--infected", "-")
exitcodes_drop = (1,)

The above assumes you do not want the infected emails to be delivered. If you do want them delivered, you would use a slightly different configuration:

[filter]
type = Filter_classifier
path = /usr/local/bin/clamscan
arguments = ("--stdout", "--no-summary",
    "--mbox", "--infected", "-")
exitcodes_keep = (0,1)

To use ClamAV with the clamd daemon, use a filter configuration like this:

[filter]
type = Filter_classifier
path = /usr/local/bin/clamdscan
arguments = ("--stdout", "--disable-summary", "-")
exitcodes_drop = (1, )

As with Clamscan (above), if you do want the infected messages delivered instead of dropped, you should modify your configuration as follows:

[filter]
type = Filter_classifier
path = /usr/local/bin/clamdscan
arguments = ("--stdout", "--disable-summary", "-")
exitcodes_keep = (0,1)

You may find it necessary to specify the paths of some decompression utilities used by ClamAV with additional arguments like:

arguments = ( …,
    "--unzip=/usr/local/bin/unzip",
    "--unrar=/usr/local/bin/unrar",
    "--unarj=/usr/local/bin/unarj",
    "--lha=/usr/local/bin/lha",
    "--jar=/usr/local/bin/unzip",
    "--tar=/usr/bin/tar",
    "--tgz=/usr/bin/tar"

Note: if you want to use the daemonized (client/server) version of ClamAV, ensure that your clamav.conf file contains:

ScanMail

The paths to the various decompression utilities must be specified in this file as well.

See the following mailing list message from Frankye Fattarelli for additional notes on using ClamAV with getmail: http://marc.theaimsgroup.com/?l=getmail&m=109128345509273&w=2

Getting prettier output from ClamAV

Using getmail's Filter_classifier, the output of your filtering program (in this case ClamAV) is placed into a X-getmail-filter-classifier: header field in the message. This can make auditing the actions of filters difficult if you use multiple filters and cannot tell which filter added which line.

To correct this, you can use an additional filter to change the name of the added filter header lines immediately after each filter is run. For example, reformail, from the maildrop package (which is in turn part of the Courier MTA ) can be used in this fashion to rename the added header fields (say, to "X-mypersonalmailscan") with a filter configuration like this:

type = Filter_external
path = /usr/local/bin/reformail
arguments = ("-R", "X-getmail-filter-classifier:",
    "X-mypersonalmailscan:")

Simply ensure ClamAV is invoked as the first filter, and this is invoked as the second filter (or immediately after the ClamAV filter, if it is the second, third, etc. filter).

How do I use F-Prot with getmail?

getmail user Kai Raven reports that getmail and F-Prot work fine together with the following getmailrc filter configuration:

[filter]
type = Filter_external
path = /usr/local/bin/f-prot-wrapper.sh

The wrapper script f-prot-wrapper.sh is a small shellscript by Ali Onur Cinar, and can be downloaded from his website.

How do I use procmail with getmail?

Simply invoke procmail as an external MDA. procmail requires that one of the following be true:

  • that the message begin with a Unix "From " line (the mbox message delimiter)
  • that procmail is invoked with the -f option supplying the envelope sender, so that it may generate the "From " line

To have getmail generate and prepend the "From " line to the start of the message, set the MDA_external parameter unixfrom to True:

[destination]
type = MDA_external
path = /path/to/procmail
unixfrom = True

To supply the -f option to procmail, do something like this:

[destination]
type = MDA_external
path = /path/to/procmail
arguments = ("-f", "%(sender)")

How do I use maildrop with getmail?

Simply invoke maildrop as an external MDA. maildrop requires that the message begin with a Unix "From " line (the mbox message delimiter), so you'll need to either set the MDA_external parameter unixfrom to True, or supply arguments that tell maildrop to recreate this line. One of the following would be fine:

[destination]
type = MDA_external
path = /path/to/maildrop
arguments = ("-f", "%(sender)")

Or:

[destination]
type = MDA_external
path = /path/to/maildrop
unixfrom = True

If you want to specify a maildrop rc file as one of its arguments, that would be something like:

[destination]
type = MDA_external
path = /path/to/maildrop
arguments = ("-f", "%(sender)", "~/.maildroprc")

How do I use TMDA with getmail?

Simply use the Filter_TMDA module as a message filter:

[filter-X]
type = Filter_TMDA

See the documentation for details on optional parameters to the Filter_TMDA module.

How can I get Gmail labels with getmail?

As of getmail version 4.34.0, getmail retrieves the labels and other metadata that Gmail makes available via an IMAP extension, and records that information in the message headers X-GMAIL-LABELS:, X-GMAIL-THRID:, and X-GMAIL-MSGID:.

I think I found this bug in getmail …

I get frequent reports like the following, which aren't bugs in getmail. Please read them before reporting them to me.

getmail doesn't download all my mail from Gmail …

There's a couple of different problems here. One is that Google's Gmail service violates the POP3 protocol by removing messages from the POP3 view of the mailbox without the user issuing a DELE command. They do this as soon as an RETR command is given, so if getmail tries to download a message and it fails for any reason (delivery fails due to a full disk, or the Gmail server fails to respond, or the network connection dies before the transfer is complete, or the Gmail server fails to respond to the QUIT command, or …), the next time getmail connects to that Gmail account, Gmail will have "helpfully" deleted the message from the POP3 mailbox, even though getmail never issued a DELE command. So Gmail silently destroys mail, from a POP3 perspective. There's nothing getmail can do about this.

Note this feature of Gmail is not well-publicized. The only mention I can find of it is here: http://mail.google.com/support/bin/answer.py?answer=13291&topic=1555

The other issue here is that Google doesn't include mail from your trash or spam folders in the POP3 view, so getmail can't see those messages either. That's generally less of an issue, provided their spam filters never give false positive results (ha!).

FutureWarning: %u/%o/%x/%X of negative int will return a signed string in Python 2.4 and up

Various people have reported this "bug" in getmail, where they see the following warning when getmail is run:

/usr/lib/python2.3/optparse.py:668: FutureWarning: %u/%o/%x/%X of negative int will return a signed string in Python 2.4 and up
  return ("<%s at 0x%x: %r>"

This warning is a bug in Python 2.3.4's optparse.py module, not in getmail. Feel free to report it to the Python team if the most recent release of Python hasn't fixed it. I reported it in June, 2004.

AttributeError: 'module' object has no attribute 'fsync'

Various people have reported this "bug" in getmail — it's actually a bug in some versions of Python 2.3.X. It was fixed in Python version 2.3.3. getmail 4 detects problematic versions of Python and refuses to run, but you can still encounter this problem with getmail version 3 if you try to run it with a broken Python version.

operation error (SimplePOP3Retriever: [...] does not uniquely identify messages [...] see documentation or use BrokenUIDLPOP3Retriever instead

The server you're trying to use does not properly uniquely identify messages (getmail noticed when it saw the same "unique" identifier twice in the same mailbox at the same time). getmail needs these identifiers to be unique so that it can properly tell the difference between new and old messages.

If you see this error message, and you've configured getmail to retrieve and immediately delete all messages, just switch to using the BrokenUIDLPOP3Retriever class (or its SSL variant) -- it'll work fine.

If you see this error message, and you're trying to leave messages on the server after retrieval (permanently, or for a few days with delete_after), you have a few options to try to resolve it:

  • If your provider also offers IMAP access to your mailbox, try one of the IMAP retrievers instead.
  • Change your configuration so you're not leaving messages on the server, and use BrokenUIDLPOP3Retriever instead.
  • Talk to your mail hosting provider, and see if they can fix their POP3 software so that it doesn't have this problem any more.

MemoryError on OS X

If you see errors like this while running getmail on Macintosh OS X:

python2.5(27172) malloc: *** vm_allocate(size=15699968) failed (error code=3)
python2.5(27172) malloc: *** error: can't allocate region
python2.5(27172) malloc: *** set a breakpoint in szone_error to debug
[...]

... which then end with MemoryError, please report the problem to Apple. The OS X implementation of realloc() is broken, and there's nothing getmail can do about it.

MemoryError when using IMAP

If you see errors like this while running getmail configured to retrieve mail via IMAP or IMAP-over-SSL:

Python(24006) malloc: *** mmap(size=9875456) failed (error code=12)
[...]

... which then end with MemoryError, you've run into a bug in Python's IMAP library. It's fixed in the most recent versions of Python 2.5 and 2.6. This bug can cause getmail to grow to a huge size, vastly out of proportion to the size of the message it's retrieving, at which point Python may fail or be killed when the system runs out of memory.

Upgrade your Python installation to the newest Python 2.5 or 2.6 version to fix the problem, or use POP3 instead of IMAP to work around it.

getmail-4.43.0/docs/documentation.txt0000644000175000017500000000000012206542267021154 0ustar charlesccharlesc00000000000000getmail-4.43.0/docs/troubleshooting.html0000644000175000017500000006245612206542267021706 0ustar charlesccharlesc00000000000000 getmail troubleshooting (version 4)

getmail documentation

This is the documentation for getmail version 4. Version 4 includes numerous changes from version 3.x; if you are using getmail version 3, please refer to the documentation included with that version of the software.

getmail is Copyright © 1998-2009 Charles Cazabon.

getmail is licensed under the GNU General Public License version 2 (only). If you wish to obtain a license to distribute getmail under other terms, please contact me directly.

Table of Contents

Troubleshooting problems

This section of the documentation is to be added to as getmail version 4 progresses through beta testing to release state. If you have suggestions for additions or changes to this documentation, please send them to the mailing list or me.

Error messages

getmail may output various diagnostic error messages. The common ones and their meanings are given below.

ImportError: getmail version 4 requires Python version 2.3.3 or later

You tried to run getmail 4 with a version of Python prior to Python 2.3.3. This is unsupported. If you cannot install a newer Python alongside your current version, please use getmail version 3, which supports Python 1.5.2 and later.

Configuration error: …

getmail detected an error in your configuration. Check your getmail rc file(s). getmail will do its best to point out the exact cause of the error. Some of the specific errors it may find include the following.

Configuration error: missing required configuration parameter name

A class object in your getmail rc file requires the parameter name, but it was not found in the appropriate section of the file.

Configuration error: configuration value name (value) not of required type type (why)

The configuration parameter name must be of type type, but the supplied value value does not appear to be of that type. Further information may be present in why.

The getmail documentation contains descriptions of the syntax for each parameter type.

Configuration error: maildir path missing trailing /

Maildir paths must start with dot or slash and end with a slash.

Configuration error: not a maildir (path)

The specified maildir path path does not appear to be a valid maildir. Check to ensure that it is a valid maildir, and that getmail has permission to write to it.

Configuration error: ... (path: maildir subdirectory "path" does not exist)

The specified maildir path path does not appear to be a valid maildir, as it is missing one of the required subdirectories. Check to ensure that it is a valid maildir and that getmail has permission to write to it.

Configuration error: not an mboxrd file (path)

The specified mboxrd path path does not appear to be a valid mboxrd file. To avoid corrupting files in the event of a user typo, getmail will not deliver messages to files that do not appear to be valid mboxrd files.

Configuration error: mboxrd does not exist

The specified mboxrd does not exist. getmail will not create mbox files; ensure they exist before trying to deliver to them.

Configuration error: the fieldname header field does not record the envelope recipient address

In a multidrop retriever configuration, you specified that the envelope recipient was recorded in a header field that getmail knows does not actually record that information.

Configuration error: refuse to invoke external commands as root or GID 0 by default

By default, getmail will not invoke external commands (in destinations or filters) when it is running with root privileges, for security reasons. See the documenation for possible solutions.

Configuration error: no such command path

An external command was specified as being located at path path, but the command executable was not found at that location.

Configuration error: path not executable

A specified external command at path path was found to not be an executable file.

Configuration error: destination specifies section name section which does not exist

A destination in the getmail rc file refers to another rc file section named section, but that section was not found in the file.

Retrieval error …

getmail detected an error while trying to retrieve a message. Some of the specific errors it may find include the following.

Retrieval error: server … is broken; …

The server claimed to have a particular message, but when getmail tried to retrieve it, the server returned an empty response in violation of the POP or IMAP protocol. getmail will skip on to the next message.

This problem is almost always with an MSExchange server, and is due to bugs in MSExchange. Delete the offending message from the mailbox via the webmail interface if you don't want to see this error message, and report the bug to the server administrator or Microsoft.

Delivery error …

getmail detected an error after retrieving a message but before delivery was successfully completed. Some of the specific errors it may find include the following.

Delivery error: maildir delivery process failed (refuse to deliver mail as root)

getmail will not deliver to a maildir as the root user. You may specify an alternate user to deliver as with the user directive in the destination section of your getmailrc file, or let getmail default to the user who is the owner of the maildir.

Delivery error: mbox delivery process failed (refuse to deliver mail as root)

getmail will not deliver to an mbox file as the root user. You may specify an alternate user to deliver as with the user directive in the destination section of your getmailrc file, or let getmail default to the user who is the owner of the mbox file.

Delivery error: mbox delivery process failed (not an mboxrd file)

The specified mbox file does not appear to be a valid mbox file.

Error: …

getmail detected an operational error. getmail will do its best to point out the exact cause of the error. Common causes are failures to connect to a remote mail server, timeouts during network operations, and other transient failures.

Error: server-info does not uniquely identify messages …

The POP3 server for this particular account was not able to uniquely identify the messages in the mailstore. You can use the BrokenUIDLPOP3Retriever class with this server instead, but functionality is limited because of the inability to identify messages properly.

Error: server-info failed to identify message X in UIDL output …

The POP3 server for this particular account completely failed to identify one of the messages in the mailstore when the UIDL command was issued. The POP server is in violation of the POP3 protocol, and getmail has no way to identify the message in question.

The most common cause of this is servers that use the contents of the Message-ID: header field as the UIDL value for the message - some messages (usually spam) lack a Message-ID: header field entirely, causing these servers to emit blank UIDL values for these messages, which is not permitted by the POP3 protocol. You may be able to work around the problem by deleting the problematic message(s) from the mailstore by other means (such as a webmail interface provided by your POP3 mailhost).

Otherwise, you'll need to either ask the postmaster of the POP3 mail host to fix the POP3 software they're using. In the meantime, you can use the BrokenUIDLPOP3Retriever class with this server instead, but functionality is limited because of the inability to identify messages properly.

Python(…) malloc: *** mmap(…) failed (…)

… followed by an Unhandled exception block and then MemoryError.

getmail ran out of memory, usually due to a bug in Python's IMAP library which was fixed in early 2008.

If you are using IMAP and run into this problem retrieving messages that aren't hundreds of megabytes in size, you can almost certainly fix it by upgrading your Python installation to the latest 2.5.x or 2.6.x release. Note that if you switch from Python 2.5.x to Python 2.6.x, you'll need to re-install getmail with python setup.py install from the getmail source directory.

Warning messages

getmail may output various diagnostic warning messages. The common ones and their meanings are given below.

Warning: …

getmail detected a condition which might be problematic. Some of the specific warnings it may find include the following.

Warning: ignoring unknown parameter "parameter_name"

A getmail rc file contained an unknown parameter. This usually indicates that you've put the parameter in the wrong section of the file (such as putting the read_all parameter in the [retriever] section instead of in the [options] section).

Warning: filter filter returned fewer headers (X) than supplied (Y)

A message filter appeared to incorrectly remove header fields from the header of a message which it handled. getmail warns you about this so that you can check your filter for proper operation. Filters should add information to the message header or leave it alone; check the configuration for the filter program you are using.

Unexpected Behaviour

If getmail is behaving in a manner other that you expected, see the following sections.

getmail uses the wrong hostname for my machine

If getmail records a hostname other than the "right" one for your host (in its Received: trace header fields), check your /etc/hosts file and make sure the "right" name is the first hostname listed for any of the addresses of the machine.

getmail puts "unknown" in the Delivered-To: or Return-Path:header field

getmail records the envelope recipient address in the Delivered-To: header field, and the envelope sender in the Return-Path: header field. If this information is not available (because you're not using a multidrop retriever class, or the MTA on the POP/IMAP server is broken, for example), getmail uses the string "unknown". You can disable the creation of the Delivered-To: header field if you wish.

getmail isn't replacing my command argument "%(recipient)" with the message recipient address

The %(recipient), %(local), and %(domain) replacement strings are only replaced if the message is retrieved with a multidrop retriever class; otherwise, getmail does not "know" the recipient address of the message, and therefore cannot perform this replacement.

getmail seems to take longer than expected to initialize

If getmail takes more than a few seconds to initialize, run the following command to test:

python -c "import socket; print socket.getfqdn()"

If this seems to take a similarly long period of time to complete, the delay is in finding the fully-qualified hostname of your system. The fix is to ensure you have a valid mapping of address-to-hostname for all the addresses in your system. You can do this in your /etc/hosts file, in your authoritative content DNS server, or in another system-specific manner — please contact your OS vendor (or its public support mailing list) for assistance.

getmail-4.43.0/docs/CHANGELOG0000644000175000017500000014214712206541262016771 0ustar charlesccharlesc00000000000000Version 4.43.0 25 August 2013 -add IMAP IDLE support. Thanks: Jon Gjengset. Version 4.42.0 3 August 2013 -fix problem with non-ascii characters in newly-added message header fields with output from Filter_classifier. Thanks: "Gour". Version 4.41.0 26 May 2013 -messages retrieved with POP could have a blank trailing line removed when delivered; fixed. Thanks: Christoph Mitterer, Krzysztof Warzecha. -fix an ImportError when IMAP retriever used with getmail under Python 2.4. Version 4.40.3 10 May 2013 -increase system recursion limit when run with Python 2.3, to prevent recursion errors in parsing some pathologically complex MIME emails. Thanks: Kenneth Pronovici. Version 4.40.2 8 May 2013 -fix a backwards incompatibility with Python 2.3 and 2.4 introduced in getmail 4.38.0. Thanks: Massimo Zambelli, Krzysztof Warzecha. Version 4.40.1 22 April 2013 -again change protocol codes passed to OSX, as they're not only reserved codes, but also case-sensitive. Use the ones an OSX user reports work properly. Thanks: Tim Gray. Version 4.40.0 21 April 2013 -convert SIGTERM to SIGINT so getmail can cleanup and exit normally if killed with a default signal. Thanks: Carl Lei. -fix change which resulted in passing full protocol name to OSX keyring program, which can't handle it (restricted to 4-character code). Thanks: Tim Gray. Version 4.39.1 10 March 2013 -fix a bug that could crop up when retrieving mail via IMAP from Gmail, and the Gmail servers provided weird data in their header extensions. Thanks: Krzysztof Warzecha. Version 4.39.0 22 February 2013 -fix an oldmail filename issue that occurred with certain non-ASCII characters in an IMAP mailbox name. Thanks: Michael Kockmeyer. Version 4.38.0 16 February 2013 -fix retrieving mail from an IMAP mailbox with non-ASCII characters in its name causing difficult-to-understand unicode errors during delivery. Thanks: "fsLeg", Krzysztof Warzecha. Version 4.37.0 27 January 2013 -handle IMAP servers which include "/" in message IDs better, so those messages aren't always thought to be new. Thanks: Bradley Rintoul. -record (IMAP) mailbox retrieved from in X-getmail-retrieved-from-mailbox: header field, and make it available as %(mailbox) substitution in MDA_external arguments. Thanks: Les Barstow. -add delete_bigger_than option to allow removal of large messages after retrieval, even if not deleting messages otherwise. Thanks: Jan Vereecke. Version 4.36.0 15 December 2012 -add support for retrieving POP/IMAP passwords from, and storing them to, the Gnome keyring. Thanks: Krzysztof Warzecha, Scott Hepler. Version 4.35.0 24 October 2012 -fix From_ quoting in mbox delivery; use of the Python stdlib function meant getmail was incorrectly using mboxo-style quoting instead of mboxrd quoting, probably since early in the v4 series. Thanks: Christoph Mitterer. Version 4.34.0 8 September 2012 -retrieve Gmail metadata (labels, thread ID, message ID) via IMAP extension and record it in new message headers. Thanks: Krzysztof Warzecha. Version 4.33.0 7 August 2012 -allow normal exit on interrupt (ctrl-c) to allow the user to abort message retrieval while still remembering already-retrieved messages as successfully delivered. You may get a weird error message after you abort, since the server may be in the middle of delivering another message to getmail at the time. Thanks: Krzysztof Warzecha. Version 4.32.0 6 July 2012 -prevent some nuisance stack traces if getmail cannot connect to the POP/ IMAP server correctly. Thanks: Daniel Dumke. -restore use_peek IMAP retriever parameter which accidentally got removed in 4.30. Thanks: Andreas Amann. Version 4.31.0 5 July 2012 -improved backwards compatibility with pre-v.4.22.0 oldmail files, so IMAP mail is not re-retrieved if you upgrade from a 4.22 or earlier to this one; no user action necessary. Thanks: Osamu Aoki, Tim van der Molen. Version 4.30.2 27 June 2012 -fix a nuisance stack trace that would be dumped if a connection failed in particular states. Thanks: Gary Schmidt. Version 4.30.1 21 June 2012 -silence a nuisance deprecation warning about the sets module when running with Python >= 2.5 which was reintroduced in 4.29.0. Thanks: Stephan Schulz. Version 4.30.0 21 June 2012 -fix breakage introduced in 4.29.0 where BrokenUIDLPOP3Retriever would fail with a TypeError at logout time. Thanks: Scott Robbins, Stephan Schulz. -fix breakage introduced in 4.29.0 where deleted mail was not being expunged from the last (or only) folder retrieved from in an IMAP session. Thanks: Paul Howarth. Version 4.29.0 19 June 2012 -update old contact information for Free Software Foundation. Thanks: Ricky Zhou. -fix incorrect character encoding in plaintext documentation. Thanks: Ricky Zhou. -ensure getmail exits nonzero if a server refuses login due to a credential problem. Thanks: Stephan Schulz. Version 4.28.0 26 May 2012 -ensure getmail exits nonzero if various error conditions (like POP/IMAP authentication failure) occur. Thanks: Ryan J., Stephan Schulz. -python versions prior to 2.5.0 contain a bug when dealing with read-only IMAP mailboxes. Monkey-patch imaplib when running with Python<2.5.0. Thanks: Les Barstow. -do IMAP modified-utf7 conversion of mailbox names containing non-ASCII characters. Thanks: A. Lapraitis, Randall Mason. -add special ALL value for retrieving mail from all selectable IMAP mailboxes in the account. -change IMAP retrieval strategy to retrieve all messages from a mailbox, then move on to the next mailbox, etc. Should result in increased speed, but if you set `max_messages_per_session` too low, this could result in later mailboxes not being retrieved from. Version 4.27.0 20 May 2012 -make use of IMAP BODY.PEEK configurable; set the IMAP retriever parameter `use_peek` to False to disable use of PEEK to get getmail's historical IMAP behaviour. Version 4.26.0 14 April 2012 -switch to using BODY.PEEK in IMAP retrieval; I no longer see problems with this feature in my testing. If users experience incompatibility with any IMAP servers where 4.25.0 worked, please let me know. Version 4.25.0 1 February 2012 -add support for storing POP/IMAP password in OSX keychain. Thanks: Adam Lazur. Version 4.24.0 11 December 2011 -add an explicit expunge when closing an IMAP mailbox, for servers that incorrectly do not do this when the mailbox is closed. Thanks: Nicolas Pomarède. -fix incorrect section reference for `mailboxes` parameter in documentation. Thanks: Ross Boylan. -fix getmail_fetch broken in 4.21.0. Thanks: Chris Donoghue. Version 4.23.0 20 November 2011 -fix race if new message shows up in POP3 mailbox between running the UIDL and LIST commands. Thanks: Roland Koebler, Osamu Aoki. Version 4.22.2 12 November 2011 -fix an error when logging a bad response to an IMAP SELECT command, introduced in 4.21.0. Thanks: "kureshii". Version 4.22.1 30 September 2011 -fix BrokenUIDLPOP3Retriever breakage from 4.21.0. Thanks: Scott Robbins, "hgolden". Version 4.22.0 25 September 2011 -when retriever supports multiple mail folders (IMAP), store oldmail data for each folder separately, preventing problems in some cases when using multiple rc files with the same account. Thanks: Jesse Schobben. Version 4.21.0 23 September 2011 -automatically open IMAP folders read-only if neither the delete nor delete_after options are in use; necessary to access chatlogs via Gmail. Thanks: Daniel M. Drucker. -avoid sorting msgids on each retrieval, reducing overhead when dealing with folders containing thousands of messages. Thanks: Daniel Koukola. Version 4.20.4 16 July 2011 -include more info from server response in error message when IMAP command errors out. Thanks: W. Martin Borgert, Osamu Aoki. -switch to using Parser instead of HeaderParser to correct illegal formatting bogosities in the body parts of incoming messages. Thanks: Lauri Alanko. Version 4.20.3 30 May 2011 -fix logging errors in error paths. Thanks: Visa Putkinen. Version 4.20.2 9 Apr 2011 -further changes to the Received: header construction to handle IPv6 better. Thanks: Frédéric Perrin. Version 4.20.1 7 Apr 2011 -ensure correct remote address and port is included in the Received: trace headers generated by getmail. Previously the first address found for the server was used even if another address was actually used to connect to the server. Thanks: Frédéric Perrin. -add workaround for Python issue http://bugs.python.org/issue1389051 which affects Python < 2.5.3, causing message retrieval of large messages from IMAP4 SSL servers to consume pathologically large amounts of memory. Thanks: Bill Janssen. Version 4.20.0 29 June 2010 -fix crap code from bad svn merge that slipped into 4.18.0, triggering exceptions in MDA_external configs. Thanks: Paul Howarth. Version 4.19.0 29 June 2010 -fix missing import introduced in 4.18.0. Thanks: Paul Howarth. Version 4.18.0 26 June 2010 -update broken link in FAQ. Thanks: Stefan Kangas. -strip a few extra problematic (on non-Unix systems) characters when generating oldmail filenames; backslash was requested by Andy Ross. If upgrading and your current oldmail file contains any of these characters: \ ; < > | ... then rename it, replacing runs of one or more of those characters with a single "-". -improve clarity of message logged by getmail when an external program exits 0 but getmail considers it failed because it wrote to stderr. Thanks: Chris Dennis. Version 4.17.0 30 April 2010 -change to how getmail counts messages in an IMAP mailbox; prevents problems where getmail would only see the first 500 messages in a mailbox with some IMAP servers that return oddball responses to SELECT. Thanks: David Damerell. Version 4.16.0 5 January 2010 -additional fix for IMAP server that offers a message it then fails to return, to allow getmail to continue with the next message. Thanks: Yaw Anokwa. Version 4.15.0 2 December 2009 -default port for MultidropIMAPSSLRetriever was incorrect. Thanks: David Lehn. Version 4.14.0 23 November 2009 -handle additional cases where IMAP (and for good measure, POP3) server is broken and offers a message that it then fails to return. Thanks: Eric Waguespack. Version 4.13.0 13 October 2009 -fix new log message not having trailing newline.Thanks: Morty Abzug. Version 4.12.0 7 October 2009 -handle MSExchange failure to deliver message gracefully. Thanks: Morty Abzug. -documentation updates regarding Python IMAP memory bug. Thanks: "spig". -fix bad tag in documentation. Version 4.11.0 8 August 2009 -fix --dump not generating any output for rcfiles containing "verbose = 0" option. Thanks: Morty Abzug. -fix copy-and-paste error in documentation of MultiSorter destination. Thanks: Roland Hill. Version 4.10.0 6 August 2009 -add "message_log_verbose" option, defaulting to false. If true, the message_log file (and syslog, if used) will contain a little more information than the default of just messages retrieved. Thanks: Matthias Andree, Gregory Morozov. Version 4.9.2 15 July 2009 -use tempfile module function instead of os.tmpfile() so getmail obeys TMPDIR and similar environment variables. Thanks: Stefan Bähring. -fix encoding of CHANGELOG to utf-8. Version 4.9.1 1 June 2009 -if oldmail file is corrupted on disk (by system crash, perhaps), handle it gracefully. Some lines may be lost (and messages therefore re-retrieved), but operation will continue, with the corrupt lines logged. Thanks: Domen Puncer. Version 4.9.0 5 April 2009 -add Kerberos authentication support to IMAP retriever classes. Thanks: Guido Günther, Uli M. -clarify documentation regarding MDA_external. Thanks: AJ Weber. -eliminate deprecation warning for sets module with Python 2.6 by using a compatibility wrapper module. Version 4.8.4 26 September 2008 -Add missing fsync() in updatefile close method to ensure data actually hits disk before the rename takes place, to make writing of the oldmail file more resilient to system crashes. Thanks: Domen Puncer. Version 4.8.3 11 August 2008 -Improve information sent to logfile about messages not retrieved. Thanks: Scott. Version 4.8.2 2 August 2008 -for IMAP retrievers, keep message state around for 30 days even if the corresponding message isn't seen. This allows users to have multiple rc files with different IMAP folders to retrieve from without losing their "seen" mail state for the folders they're not currently retrieving from. You no longer need to work around this with different getmail state directories. Thanks: ?. Version 4.8.1 26 March 2008 -fix use of Python 2.4 set builtin in 4.8.0 preventing getmail from running on Python 2.3.x. Thanks: Björn Stenberg. -fix the RPM spec file not being included in the getmail distribution. Version 4.8.0 19 February 2008 -better diagnostic when user invalidly supplies timeout configuration parametmer for an IMAP-SSL retriever. Thanks: Dennis S. -code cleanups Version 4.7.8 5 February 2008 -explicitly close current IMAP mailbox when selecting a new one, so all servers expunge deleted mail. Thanks: Josh Triplett. -include experimental spec file for creating RPM with rpmbuild. Thanks: Dag Wieers, Rob Loos, Dries Verachtert. Version 4.7.7 8 November 2007 -convert changelog to utf-8 encoding. -update email addresses, etc. Domain for mailing lists has changed to lists.pyropus.ca. -add FAQ about memory errors on OS X. Thanks: Andres Gasson. -drop log message level for stderr output of destination if ignore_stderr is set, just like for filter. Thanks: Jeremy Chadwick. Version 4.7.6 8 August 2007 -fix exception when getmail is trying to report a POP3 server that completely missed generating a UIDL token for a message. Thanks: Hans Lammerts. Version 4.7.5 6 June 2007 -make updatefile honour symlinks, so users can alias one server's oldmail state file to another (for split-horizon DNS and other strange configurations). Thanks: Scott Hepler. Version 4.7.4 24 April 2007 -explicitly watch for broken POP3 servers that return a blank UIDL value (not permitted by the POP3 protocol) and print a fatal error message. Thanks: Florian Hackenberger and Cameron Rangeley. Version 4.7.3 18 March 2007 -fix wrong bytecount displayed in summary at end of run. Thanks: Andreas Jung. Version 4.7.2 20 February 2007 -add ignore_stderr option to filters, copying same from MDA_external destination. Thanks: Vittorio Beggi. Version 4.7.1 2 February 2007 -add max_bytes_per_session option. Thanks: Robert Spencer. -documentation additions. Version 4.7.0 24 January 2007 -make Mboxrd lock type configurable (lockf or flock). Change default to lockf. Thanks: Norman Carver. Version 4.6.7 3 January 2007 -better handling for some fatal violations of POP/IMAP protocols. Thanks: Paul ?. -formatting cleanups. Version 4.6.6 12 December 2006 -add ignore_stderr option to MDA_external destination. Thanks: Daniel Burrows. -documentation cleanups. Version 4.6.5 20 October 2006 -disable debug logging message which would show up in messages if --trace was used and the message was passed through an external filter. Thanks: Christian Bruemmer. -add additional sanity checks to ensure a recent-enough Python is used; trying to run getmail 4 with Python 2.2 would throw an exception. Thanks: fakhri ?. Version 4.6.4 28 September 2006 -add FAQ about BrokenUIDLPOP3 retrievers. -add better diagnostics for missing/invalid/unwritable state/data directory. Thanks: Christian Authmann. Version 4.6.3 22 June 2006 -fix regression: spurious errors thrown when required multidrop retriever parameter "envelope_recipient" not present in getmailrc file. Thanks: Elliot Schlegelmilch. -fix regression: getmail_fetch no longer properly supported external MDAs because of problems parsing the arguments to them. Thanks: ?. -change getmail's handling of regular expressions in the "locals" parameter of MultiSorter destination to allow it to handle backslashes correctly. Unfortunately this means complicating the configuration syntax slightly. Thanks: Tim van der Molen. -update filters to use new configuration code. Remove obsolete code. Version 4.6.2 8 June 2006 -fix regression introduced in 4.6.0 where atime on mbox files would get updated (i.e. not set back), making it difficult for other programs to tell if there was new mail or not without actually opening the mbox. Thanks: Tim van der Molen. Version 4.6.1 31 May 2006 -fix attribute deletion error with Filter_classifier when strip_delivered_to is set. Thanks: Darren Stevens. -do not consider qmail-local writing to stderr a delivery error. Thanks: Darren Stevens. Version 4.6.0 5 April 2006 -clean up logger API. -refactor configuration mechanism to use classes with embedded logic, rather than simply dictionaries. Version 4.5.4 14 February 2006 -added Maildir "filemode" parameter. Thanks: Martin Haag. Version 4.5.3 8 February 2006 -keyfile and certfile parameters to SSL retrievers are now expanded for leading "~/" and "~username/" as well as environment variables, just like other path parameters. Thanks: Fredrik Steen. Version 4.5.2 7 February 2006 -fix IMAP bug where no mail was retrieved introduced in 4.5.0. Thanks: Scott Robbins. Version 4.5.1 3 February 2006 -fix accidental use of sorted() builtin only present in Python 2.4 and up. Thanks: Jürgen Nagel. Version 4.5.0 1 February 2006 -add delete_dup_msgids option to non-multidrop POP3 retrievers. Thanks: Matthias Andree. -suppress stack trace in getmail_fetch error message when configuration error detected. Thanks: Dave Jarvis. -documentation updates. -build and development environment changes. The plaintext versions of the documentation are now generated with links instead of lynx. -explicitly log the reason for not retrieving a message (seen or oversized). Thanks: Payal Rathod. Version 4.4.4 3 January 2006 -improve parsing of flags in IMAP responses. Thanks: Gareth McCaughan. Version 4.4.3 1 December 2005 -fix harmless copy and paste error in IMAPRetrieverBase class. Thanks: Henry Miller. Version 4.4.2 13 November 2005 -fix logging import in corrupt messages with Python 2.3. Thanks: Marco Ferra. Version 4.4.1 10 November 2005 -work around bug in Python which meant POP3-over-SSL with Python >= 2.4 could "hang" (it's exactly the same bug that affected getmail's initial IMAP-over-SSL code). Version 4.4.0 9 November 2005 -add new POP3-over-SSL initialization class, which is automatically used when Python 2.4 or newer is in use. This reduces the overhead involved in retrieving messages from a POP3SSL server (reduces systime about 35%, user time about 50%, wall time 10-30%). Thanks: Jan Vereecke. -add getmail_fetch configuration-less commandline POP retriever, perfect for scripting use. Development of getmail_fetch was sponsored by Texas Communications. -future proofing: eliminate relative imports within getmailcore package. Version 4.3.14 2 November 2005 -Fix copy and paste error that caused BrokenUIDLPOP3SSLRetriever to default to port 110. Thanks: Daniel Burrows via Fredrik Steen. Version 4.3.13 15 October 2005 -add BrokenUIDLPOP3SSLRetriever as SSL version of BrokenUIDLPOP3Retriever, based on an idea by a user of the Debian package. Thanks: Daniel Burrows, Fredrik Steen. Version 4.3.12 13 October 2005 -remove deprecated "strict" argument from message/header parsing calls. While it was possible to suppress the warning that resulted when using Python 2.4, it appears many getmail users couldn't bother to do that and complained to me instead. Version 4.3.11 16 June 2005 -getmail could previously record an envelope return path header of "<<>>". Fixed. Thanks: Fredrik Steen. Version 4.3.10 19 May 2005 -bugfix for missing import. Thanks: Matthias Andree. Version 4.3.9 18 May 2005 -for multidrop retrievers, change the way the envelope recipient header field is parsed, to prevent odd values from being interpreted as multiple addresses when they look like an 822-style address group. Thanks: "aal". -try to avoid parsing message bodies, in case they're corrupt or invalid. Thanks: Michael Gold. Version 4.3.8 6 May 2005 -change (again) handling of deleted messages vs. connection that dies. Inability to send QUIT to POP3 server should be less problematic now. -improve housekeeping of old message state file. Thanks: Thomas Schwinge. Version 4.3.7 1 May 2005 -add error message for missing mboxrd file. Thanks: Marco Ferra. -change handling of connection that dies vs. deleted messages. Thanks: Thomas Schwinge. Version 4.3.6 8 April 2005 -broke BrokenUIDLPOP3Retriever when I added the forget_deleted parameter to the retrievers base class; now fixed. Thanks: Georg Neis. Version 4.3.5 3 April 2005 -make getmail less conservative about remembering messages as already-seen when unrelated errors occur after successfully delivering them. Thanks: Thomas Schwinge. Version 4.3.4 14 March 2005 -documentation changes; getmail v4 cannot run natively on Windows. Use Cygwin if you must run on a Windows platform; this works. -remove some Windows-specific code. Version 4.3.3 19 February 2005 -previously, for safety, getmail would re-retrieve messages after a session that encountered errors. However, getmail had enough information to safely remember those messages that had been successfully delivered. This behaviour has been changed, to avoid delivering duplicate messages where it isn't necessary. Thanks: Thomas Schwinge. -in output/log files, getmailrc files are now specified only by filename, instead of by complete paths. This will prevent some overly-long output lines. -add Windows versions of functions to lock/unlock files, so mbox delivery can be used on Windows. Thanks: Felix Schwarz. Version 4.3.2 5 February 2005 -previously, if an SSL POP3 or IMAP server abruptly closed the connection before getmail could finish logging in, getmail would exit instead of proceeding to the next configured mail account. Fixed. Thanks: Matthias Andree, Frank Benkstein, Thomas Schwinge. -eliminate duplicate Return-Path: header fields. Thanks: Angles Puglisi. Version 4.3.1 18 January 2005 -some IMAP errors would cause getmail to raise an exception, instead of gracefully proceeding with the next configured mail account. Fixed. Thanks: Matthias Andree. Version 4.3.0 10 January 2005 -add BrokenUIDLPOP3Retriever class to support servers that do not implement the UIDL command, or which fail to uniquely identify messages using it. Version 4.2.6 2 January 2005 -add new error message and documentation for POP3 servers that cannot uniquely identify messages in a mailbox. Thanks: Thomas Schwinge. Version 4.2.5 8 December 2004 -fix typo in getmailcore/exceptions.py that would raise a NameError exception in certain rare cases. Thanks: Gour ?. Version 4.2.4 22 November 2004 -one type of filter error would cause getmail to skip to the next configured mail account, rather than simply proceeding to the next message from the same account. Fixed. Thanks: Adrien Beau. -documentation updates. Version 4.2.3 18 November 2004 -documentation updates. -getmailrc examples file updated. Thanks: Scott Robbins. -clarify error message when user insecurely tries to deliver mail as GID 0. Version 4.2.2 11 October 2004 -in child delivery processes, change real as well as effective uid/gid. Thanks: David Watson. -handle corrupted oldmail file better. Thanks: Matthias Andree. Version 4.2.1 8 October 2004 -set message attributes on corrupt container objects to prevent problems with destinations that expect multidrop-retrieved messages. Thanks: Harry Wearne. -move tests for existence of file from mbox destination initialization to delivery method, and change error from configuration to delivery error. Thanks: David Watson. Version 4.2.0 18 September 2004 -SECURITY: previous versions of getmail contain a security vulnerability. A local attacker with a shell account could exploit a race condition (or a similar symlink attack) to cause getmail to create or overwrite files in a directory of the local user's choosing if the system administrator ran getmail as root and delivered messages to a maildir or mbox file under the control of the attacker, resulting in a local root exploit. Fixed in versions 4.2.0 and 3.2.5. This vulnerability is not exploitable if the administrator does not deliver mail to the maildirs/mbox files of untrusted local users, or if getmail is configured to use an external unprivileged MDA. This vulnerability is not remotely exploitable. Thanks: David Watson. My gratitude to David for his work on finding and analyzing this problem. -Now, on Unix-like systems when run as root, getmail forks a child process and drops privileges before delivering to maildirs or mbox files. getmail will absolutely refuse to deliver to such destinations as root; the uid to switch to must be configured in the getmailrc file. -revert behaviour regarding delivery to non-existent mbox files. Versions 4.0.0 through 4.1.5 would create the mbox file if it did not exist; in versions 4.2.0 and up, getmail reverts to the v.3 behaviour of refusing to do so. Version 4.1.5 13 September 2004 -getmail would not delete messages from the server if it was configured not to retrieve them and the delete_after directive was not in use (i.e. user normally left messages on server but occasionally wanted to force-delete them). Fixed. Thanks: Frankye Fattarelli. Version 4.1.4 1 September 2004 -change failure of a message filter to produce at least as many mail headers as it was provided from a non-fatal error to warning. If your filter strips headers, getmail will now warn you about it, but will not consider it an error. -documentation additions. Version 4.1.3 31 August 2004 -enhance warning diagnostics about non-accessible or non-writable maildirs. -change method of determining name of local host; only fall back to getfqdn() if the result of gethostname() does not contain a dot. -documentation enhancements. Version 4.1.2 28 August 2004 -dumping config would raise an exception since 4.1.0; fixed. Thanks: Ilya Krel. Version 4.1.1 27 August 2004 -getmail raised an exception after processing all accounts, while printing a summary, if verbose was set to 2. Fixed. Thanks: Matthias Andree. Version 4.1.0 24 August 2004 -biggest change is multiple verbosity levels. To support this from rc file, the verbose parameter had to change from a boolean to an integer. Update your getmail rc files: for quiet, set verbose to 0. For more output, set it to 1 or 2. 2 includes info about messages not retrieved, etc. -add rc filename to error messages -change fix from 4.0.11 for email module raising exception during .flatten(). Thanks: Jürgen Nagel. -some types of SSL error could cause getmail to exit instead of continuing to the next mail account; fixed. Thanks: Matthias Andree. Version 4.0.13 19 August 2004 -unrecognized parameters could trigger a traceback instead of a warning. Fixed. Thanks: Frankye Fattarelli. Version 4.0.12 19 August 2004 -forgot to add the new Message attribute (from 4.0.11) to its slots declaration. Fixed. Version 4.0.11 18 August 2004 -change oldmail file writing to save a few bytes of disk space -documentation additions. -Python's standard library email.Message().flatten() could barf on certain types of badly mis-formatted messages (instead of the during instantiation, like the /other/ buggy cases). Hope this is fixed in Python 2.4. Developed a work-around for getmail. Thanks: Jürgen Nagel. -changes to "normal" output of getmail to make it slightly less verbose. Version 4.0.10 12 August 2004 -cosmetic fix to output: add whitespace after timestamp when not retrieving message. Thanks: Matthias Andree. -include MANIFEST.in in source distribution, to assist users in building "built distributions" from it. -change a few output messages' level to make verbose mode slightly less verbose, and add --debug switch to get "more verbose" behaviour. Version 4.0.9 9 August 2004 -fix cosmetic error for IMAP mailboxes with 0 messages in them. -change method of obtaining uidvalidity from IMAP server and remove wrapper. -previously, connecting to an IMAP-SSL server could fail if a non-SSL IMAP connection were attempted immediately before. This was due to the Python bug discussed here: http://sourceforge.net/tracker/index.php?func=detail&aid=945642&group_id=5470&atid=305470 Implemented a workaround for getmail. Thanks: Payal Rathod. Version 4.0.8 6 August 2004 -add an extra error message if you ask getmail to deliver to a maildir, but getmail can't check the contents of it due to permissions. Thanks: Clemens Buschmann. -fix breakage introduced in 4.0.7: getmail would forget a message was "seen" after a cycle of not retrieving it. Thanks: Payal Rathod. Version 4.0.7 5 August 2004 -change failure of a message filter to produce at least as many mail headers as it was provided from a fatal to a non-fatal error. The message will be skipped. Thanks: Payal Rathod. -a few non-conformant IMAP4 servers don't implement SEARCH, so getmail couldn't get a list of UIDs. Changed to use FETCH instead. Thanks: Matthias Andree. -prevent traceback if IMAP SSL connection closed during connect(). Thanks: Payal Rathod. -add warning if unknown parameters are supplied to a retriever, filter, or destination. -write subclasses of Python imaplib classes to work around missing UIDVALIDITY select() response. Add it to the state getmail keeps. Thanks: Matthias Andree. -move message state saving to later, so getmail doesn't falsely remember having handled a message. Thanks: Matthias Andree. -change location of documentation/man pages to /share/{doc/getmail,man}/ to be more comformant with the FHS. -documentation updates Version 4.0.6 4 August 2004 -if the connection failed in a certain way, getmail could forget which messages it had already retrieved, and therefore retrieve them again when the next successful connection occurred. Fixed. Thanks: Wim Uyttebroek. -add win32 executable installer as third download option. Version 4.0.5 3 August 2004 -add additional error handler for certain network errors with POP3-over-SSL connections. Thanks: Frank Benkstein. -rename retriever class for Demon UK: it should have been SDPS; my transposition. Thanks: Paul Howarth. Version 4.0.4 2 August 2004 -corrupt-message handler introduced in 4.0.1 needed tweaking. Thanks: Bernhard Riedel. Version 4.0.3 2 August 2004 -some types of socket errors would raise an exception instead of letting getmail gracefully continue; fixed. Thanks: David. -documentatation updates; fix two typos and add to the section on using ClamAV with getmail. -changes to my release process; the current version's URL will not change when a new version is released. See, I try to help others, even when they don't do the same... Version 4.0.2 30 July 2004 -trying to use MDA_qmaillocal destination with a non-multidrop retriever would raise an exception instead of printing a configuration error message. Fixed. Thanks: Clemens Buschmann. Version 4.0.1 26 July 2004 -documentation fixes. Thanks: Roland Hill. -add handler for badly-misformatted messages as a workaround for Python 2.3.x, where the email module can raise exceptions while parsing messages. Thanks: Paul. Version 4.0.0 23 July 2004 -exit beta; final release of version 4.0.0, with approximately four thousand lines (~150 kbytes) of Python code and five thousand lines (~180 kbytes) of documentation. Version 4.0.0b10 21 July 2004 -documentation updates and cleanups -add MultiGuesser destination; it's like MultiSorter, but guesses at the message recipient based on addresses found in the message header, to be used for mail sorting if you don't have a proper domain/multidrop mailbox. -consolidate initialization code from retrievers to RetrieverSkeleton base class. -slight change to main script and retriever base classes to change the way the retriever objects are destroyed and garbage-collected -get rid of unneeded hostname variable in MultiDestinationBase -add "Summary:" header line to main script output in verbose mode -wrap code for 80 columns -fix error message when a filter doesn't return a message properly. Thanks: Shantanu Kulkarni. Version 4.0.0b9 19 July 2004 -change syntax of MultiSorter's locals parameter. The previous syntax was fragile and was mostly a holdover from previous versions; it is now a tuple of items, each of which is a 2-tuple of quoted strings. -add Python version to --dump output. -eliminate redundant _confstring() method in _retrieverbases.py. -small documentation updates. Version 4.0.0b8 18 July 2004 -documentation updates. -include RPM build. Rapidly approaching release state. Version 4.0.0b7 17 July 2004 -changes to an "impossible" error handler in _child_handler(), just in case. -move some code from destinations.MultiSorter() into new MultiDestinationBase base class. -fix MultiDestinationBase._get_destination() -add MultiDestination class to deliver messages to multiple destinations unconditionally. -cosmetic fixes to output Version 4.0.0b6 15 July 2004 -move common child-handling code out of filters and destinations into new baseclasses.ForkingBase() class. -add __all__ declarations to modules that were missing them, to help prevent namespace clutter for others Version 4.0.0b5 15 July 2004 -apply the child fix from b3 to Filter_classifier; I missed this in the update. Thanks: Dave Osbourn. -cosmetic fix for output describing filters and destination objects. Thanks: Dave Osbourn. -catch configuration error of non-multidrop retriever with multidrop-only destination. This broke when I cleaned up the message attribute interface in alpha 29. Thanks: Dominik Kacprzak. Version 4.0.0b4 14 July 2004 -remove unneeded code raising a ConfigurationError in Filter_TMDA -clean up output for Maildir, Mboxrd destinations and getmail_maildir script, messages dropped by filters, various other bits -add --show-default-install-dirs to setup.py -documentation updates, including more clarification and examples of installation options -tweak to getmail manpage. Thanks: Frankye Fattarelli. Version 4.0.0b3 13 July 2004 -different fix for the reaping child problem. This one tested by Paul and confirmed working on Mandrake 10's kernel. -add Filter_TMDA filter class. -changes to setup.py to account for --install-data specifying a non-default path for the documentation and man pages Version 4.0.0b2 13 July 2004 -documentation fixes regarding installation. Thanks: Emily Jackson. -do environment variable expansion in paths and arguments (before substitutions like %(sender), etc) in addition to expanding leading ~/ or ~user/ . Thanks: Paul. -try change to code which reaps child processes; the previous code failed on a few Linux systems (perhaps the ones where the kernel changed fork() behaviour to run the child first?) Reported by Paul. Version 4.0.0b1 10 July 2004 -first beta release -add missing os import to getmail_maildir -fix received_by in getmail_mbox and don't create Received: header -a handful of trivial changes to make the code idiomatically consistent -fix to oldmail file writing -documentation updates Version 4.0.0a30 8 July 2004 -documentation updates -change header-adding code from filters to a method in Message class. Thanks: Frankye Fattarelli. -make MultiSorter pass retriever info down to its sub-destinations. This fixes a problem reported by Frankye Fattarelli. Version 4.0.0a29 8 July 2004 -flesh out the getmailrc-examples file (note that it's been renamed from getmailrc-example) -clean up message attributes. Make Message() class. Move flatten_msg() out of utilities and make it into a Message method. Remove header-length checking when generating flattened representation of message. Change callers to use the new class. -move SENDER check from deliver_maildir() to getmail_maildir where it belongs. -update the miscellaneous documentation files -last alpha, I think Version 4.0.0a28 7 July 2004 -documentation additions and changes. -add manpages for getmail, getmail_maildir, getmail_box. These go in PREFIX/man/man1/. Version 4.0.0a27 4 July 2004 -documentation fixes and additions. Split documentation into reasonably-sized files. I need to add inter- and intra-document links to most of the documentation yet. -add auto-generated tables of contents to each doc file. -start flushing out the website with copies of the docs, download directories, etc. Version 4.0.0a26 4 July 2004 -add explicit checks for multidrop classes to prevent users from incorrectly specifying certain header fields as recording the envelope recipient address -documentation fixes and additions Version 4.0.0a25 2 July 2004 -add handler for KeyboardInterrupt. Thanks: Thomas Schwinge. -change setup script to not error if the specified directory prefix doesn't exist. Thanks: Thomas Schwinge. -pass retriever protocol info to filters. Thanks: Frankye Fattarelli. -first inclusion of "real" documentation. See docs/documentation.html or its plaintext counterpart docs/documentation.txt. Version 4.0.0a24 1 July 2004 -setup script/distutils changes. The setup.py script now looks for --prefix or --home specifying an alternate installation directory, and otherwise defaults to the hierarchy that Python was installed to. /usr/share/doc/ or similar is no longer hardcoded anywhere. Version 4.0.0a23 1 July 2004 -changes to MDA_qmaillocal: add "group" parameter, and make "user" parameter also have getmail change UID before invoking qmail-local. -change two instances of socket.gethostname() to socket.getfqdn() -change localparttranslate to localpart_translate -add explicit checks for socket.ssl(), which is optional. Raise getmailConfigurationError if they're not present and the user tries to use it. Thanks: Thomas Schwinge. -fix missing received_by in destinations. Thanks: Frankye Fattarelli and Andrew Raines. Version 4.0.0a22 30 June 2004 -MultiSorter default destination can now be a named destination section as well, so postmaster mail can go to an external MDA, etc. -deliver_maildir writes out a new Return-Path: header field if SENDER is set. -include getmail_maildir and getmail_mbox delivery scripts for use as MDAs with other programs. -add starting/finishing log lines for each retriever -change eval_bool to raise exception on unexpected values, rather than using Python's native idea of what is boolean True and False -ensure no message has partial final line regardless of delivery method. Thanks: Thomas Schwinge. -get rid of msg_lines(), mbox_timestamp() and their only callers. -add the Delivered-To: and Received: header creation like getmail v.3 had, and boolean options delivered_to and received to allow the user to disable them. This necessitates adding some data attributes to retriever classes and additional code to destination classes and utilities. Thanks: Thomas Schwinge. -drop .py suffix on the name of the main getmail script -declare the three scripts to be scripts instead of modules (for distutils) -some fixups to the distutils setup script. If files or directories are still installed with "wrong" permissions (like 0600/0700 instead of 0644/0755), please let me know. -a few miscellaneous fixups -heading for beta, working on documentation ... Version 4.0.0a21 27 June 2004 -leave Return-Path header field alone for program deliveries. Thanks: Andrew Raines. Version 4.0.0a20 25 June 2004 -fix typo in Filter_classifier -wrap additional exception check around executing subprocesses to ensure nonzero exit on error -finish MDA_qmaillocal address handling and add strip_delivered_to parameter. Document MDA_qmaillocal. -change email module import in utilities.py. Submit bug report to Python bug tracker. Thanks: Frankye Fattarelli. Version 4.0.0a19 24 June 2004 -bring some docstrings, comments, and documentation up to date. -add Filter_classifier filter module, to support adding information to the message header from programs that don't provide the original message back on stdout (like clamscan/clamdscan, apparently). Thanks: Frankye Fattarelli. -missed seek(0) in msg_flatten(). Add missing imports. Thanks: Andrew Raines. Version 4.0.0a18 24 June 2004 -add msg_flatten(), msg_lines() and use them exclusively to prevent .as_string() from changing header fields for spurious reasons. Thanks: Andrew Raines. -remove code doing From_-escaping in Mboxrd and let the email module do it. -fix copy & paste errors in change_uidgid(). Thanks: Andrew Raines. -catch timeouts Version 4.0.0a17 23 June 2004 -add exitcodes_keep and exitcodes_drop parameters to Filter_external. Thanks: Frankye Fattarelli. Version 4.0.0a16 23 June 2004 -add user and group, and allow_root_commands parameters to MDA_external destination and Filter_external filter classes. -make import pwd fail gracefully for Windows, etc. -add allow_root_commands parameter to MDA_external destination and Filter_external filter classes to force overriding of running external commands as root. -pass reference to configparser to retriever, destination, and filters -add MultiSorter destination of "[section]" which invokes another destination from the same getmail rc file. Use this to deliver to programs by including an MDA_external destination in a section named [foo] in the rc file, then specify that section name in the MultiSorter in the locals part, i.e. [destination] type = MultiSorter postmaster = /path/to/maildir/ locals = joe /path/to/maildir/ sam /path/to/mbox/ chris [myprogram] [myprogram] type = MDA_external path = /usr/local/bin/mymda arguments = ("--strip-attachments", "-f%(sender)", "--fast") Don't try to cause a loop using this to point to itself. You have been warned. -MultiSorter is now a little stricter about destination names. A maildir (after any tilde expansion, if applicable) must start with "." or "/" and end with "/". An mboxrd (after any tilde expansion, if applicable) must start with "." or "/". This means if you previous had something like: locals = sam@example.net subdir/maildir/ You'll need to change it to: locals = sam@example.net ./subdir/maildir/ Of course, locals = sam@example.net ~sam/maildir/ is already okay if sam is a valid user and has a valid home directory. -add message_log_syslog Version 4.0.0a15 22 June 2004 -add summary "Retrieved X messages for ..." for each rc file at program close. Thanks: Frankye Fattarelli. -add explicit delete of retriever after quit() to try to force writing oldmail file immediately instead of at garbage collection. Python makes no guarantees about when garbage collection takes place, so this won't be perfect. Thanks: Julian Gomez. -add message_log -add traceback formatting on exception -change --dump to include filters. Change order of operations. Version 4.0.0a14 22 June 2004 -fix IMAP-over-SSL. socket.ssl objects are apparently incompatible with socket timeouts, even in the IMAP_SSL class. -ensure --trace debugging output doesn't output passwords. Thanks: Julian Gomez. -IMAP classes would stop indexing mailboxes when they hit an empty one. Fixed. Thanks: Julian Gomez. Version 4.0.0a13 21 June 2004 -add missing default for max_messages_per_session. Thanks: Frankye Fattarelli. Version 4.0.0a12 21 June 2004 -slight change to quit() to ensure old connections are closed and garbage-collected before trying to make new ones. Thanks: Frankye Fattarelli. -move default documentation directory to /usr/local/share/doc/ . -filters.py now checks that the filtered message is at least basically sane in that it has a header no shorter than the message it was supplied. Let me know if this causes problems. -add and document max_messages_per_session Version 4.0.0a11 21 June 2004 -add MultidropIMAPRetriever and MultidropIMAPSSLRetriever -add move_on_delete parameter to all IMAP retrievers. -add and document delete_after and max_message_size options -document read_all and delete options -rename pop3ssl.py -fix --quiet. i.e., getmail should again operate truly silently if you want it to. -retrievers' quit() method wasn't getting called. Fix. Thanks: Frankye Fattarelli. Version 4.0.0a10 19 June 2004 -fix the retrieve-every-other-message bug when delete == True. -move base classes out of retrievers.py into _retrieverbases.py -fix exception when retrieving mail from an empty IMAP account Version 4.0.0a9 19 June 2004 -accidentally edited initialize() in the wrong class; it broke POP3. This update should fix it. Version 4.0.0a8 18 June 2004 -fix 6 instances of getmailConfirurationError typo in retrievers.py -finish basics of IMAP support -- i.e., it successfully retrieves mail. delete might not work yet. IMAP-over-SSL /might/, but the server I was testing against wasn't working with SSL (no connection). -handle email.Utils.getaddresses returning None. Thanks: Frankye Fattarelli. -fix default port for MultidropPOP3SSLRetriever. Version 4.0.0a7 17 June 2004 -debug and rewrite POP3-over-SSL functionality. Involved writing around some ugly limitations and kludges in Python's socket.ssl and poplib.POP3 code. But POP3-over-SSL actually works now, and is fairly clean. Let me know if it hangs; I might need to change the way blocking mode is handled on the underlying socket. Version 4.0.0a6 16 June 2004 -add pseudofile class to implement readline() and sslwrapper to implement sendall() on ssl objects. ssl objects in Python don't behave like a file, or like a socket; they're just ... broken. It's a deficiency in the Python standard library. This might make POP3-over-SSL work, or it might need more work yet. I've got it talking some SSL, but it hangs at the moment. Might be blocking in read(). -move Mboxrd file locking to _deliver_message(). Thanks: Frankye Fattarelli. -make --trace more useful and add extra debug info to main script. -eliminate noise from defaults in non-[options] section processing -found Python's ConfigParser .getboolean() method failed when the passed-in default was a non-string. Maybe I should have stayed with my own replacement configuration parser, as it properly handled this case ... submitted a patch to the Python bug tracker and added a workaround to getmail for the moment. -/really/ enable delete and read_all in [options] of rc file. Version 4.0.0a5 16 June 2004 -document tuple syntax. Thanks: Frankye Fattarelli. -fix "filters" name collision in script. Thanks: Frankye Fattarelli. Version 4.0.0a4 15 June 2004 -fix default values in three classes' parameters which previously relied on type conversion. Thanks: Andrew Raines. -fix default port for POP3-over-SSL and "username" parameter in documentation. Thanks: Frankye Fattarelli. -enable delete and read_all in [options] of rc file. Change handling of verbose. Version 4.0.0a3 15 June 2004 -fix typo in processing filters. Thanks: K. Shantanu. -fix typo in processing non-default parameters. Thanks: Andrew Raines. -document SSL retrievers -move retrievers.py configuration functionality into ConfigurableBase class -convert filters.py to use ConfigurableBase -convert destinations.py to use ConfigurableBase Version 4.0.0a2 14 June 2004 -fix default/'default' in filters module. Thanks: Andrew Raines. -add the mix-in classes for SSL support with POP3 classes. Meant small rewrites to the POP3 classes, but the design should be even cleaner now. Version 4.0.0a1 14 June 2004 -first alpha release of getmail version 4 Changes since getmail version 3 ------------------------------- -complete rewrite -switch to Python version 2.3.3: -increased code readability (augmented assignment, list comprehensions, string methods, etc) -eliminate external modules (ConfParser, timeoutsocket) -use standard library modules which have come up to reasonable levels of quality and functionality over old custom code (optparse, ConfigParser) -no longer require workarounds for older, broken standard library modules/ functions -modular, object-oriented framework for retrievers, destinations, filters allows extensibility -make more platform-agnostic (transparently support system EOL convention, etc) -support for multiple retriever/account types: -POP (single-user, multidrop, SDPS) -IMAPv4 (single-user, multidrop) -full native support for POP3-over-SSL and IMAP-over-SSL. -configuration (rc) file format changes: -support multiple rc files per invocation -one mail account per rc file getmail-4.43.0/docs/getmaildocs.css0000644000175000017500000000227211131473115020553 0ustar charlesccharlesc00000000000000body { background: white; color: black; font-family: sans-serif; font-size: 100%; padding-bottom: 20em; } p { margin: 0.1em; padding: 0.5em; } p.introduction { padding: 2em; color: black; background-color: #f0f0f0; } .about { font-size: 80%; } .title { font-weight: bold; text-decoration: underline; } .file { font-family: monospace; font-size: 130%; color: #700000; background-color: transparent; } pre.example { display : block; margin: 1em; padding: 0 1em 1em 1em; font-family: monospace; white-space: pre; color: black; background-color: #f0f0f0; } li pre.example { display : block; margin: 1em; padding: 1em; font-family: monospace; white-space: pre; color: black; background-color: #f0f0f0; } .meta { font-style: italic; color: #0000c0; background-color: transparent; } .meta:before { content: "<"; } .meta:after { content: ">"; } .sample { font-family: monospace; font-size: 130%; color: #000070; background-color: transparent; } .note { font-style: italic; } .warning { color: red; background-color: transparent; } .important { font-weight: bold; color: black; background-color: #e0e0ff; } .errmsg,.warnmsg { font-family: monospace; font-size: 150%; } hr.divider { margin-top: 5em; } getmail-4.43.0/docs/getmail_fetch.10000644000175000017500000000476011236652463020442 0ustar charlesccharlesc00000000000000.TH getmail_fetch "1" "November 2005" "getmail 4" "User Commands" .SH NAME getmail_fetch \- retrieve messages from one or more POP3 or POP3-over-SSL mailboxes and deliver to a maildir, mboxrd-format mbox file, or external MDA .SH SYNOPSIS .B getmail_fetch [\fIOPTIONS\fR] \fISERVER\fR \fIUSERNAME\fR \fIPASSWORD\fR \fIDESTINATION\fR .SH DESCRIPTION .\" Add any additional description here .PP getmail_fetch retrieves messages from POP3 or POP3-over-SSL mailboxes and delivers to a maildir, mboxrd, or external MDA. This command is intended primarily for scripting, and as such does not require a client-side configuration file, and does not record any state. .PP The \fIDESTINATION\fR argument is interpreted as follows: .PP If it begins with a dot or a slash and ends with a slash, it is assumed to be a path to a maildir. .PP If it begins with a dot or a slash and does not end with a slash, it is assumed to be a path to an mboxrd file. .PP If it begins with a pipe, it is assumed to be a path to an external MDA command. Note that arguments can be included in this string by separating them by whitespace; untrusted data \fBmust not\fR be used in constructing this string. .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show a short usage summary and exit .TP \fB\-v\fR, \fB\-\-verbose\fR output information messages while running .TP \fB\-q\fR, \fB\-\-quiet\fR output only on error .TP \fB\-m\fIFILE\fR, \fB\-\-message\fR=\fIFILE\fR read well-formatted RFC822 message from FILE and deliver prior to connecting to POP server .TP \fB\-p\fIPORT\fR, \fB\-\-port\fR=\fIPORT\fR use port PORT instead of default (POP3: 110, POP3-over-SSL: 995) .TP \fB\-d\fR, \fB\-\-delete\fR delete messages from server after delivery .TP \fB\-t\fITIMEOUT\fR, \fB\-\-timeout\fR=\fITIMEOUT\fR use timeout of TIMEOUT seconds instead of default 180 .TP \fB\-a\fR, \fB\-\-apop\fR use APOP authentication .TP \fB\-s\fR, \fB\-\-ssl\fR use POP3-over-SSL .SH AUTHOR Written by Charles Cazabon. .SH "REPORTING BUGS" Report bugs to . .PP \fBIMPORTANT:\fR include the output of .B getmail [options] --dump\fR with any bug report. .SH COPYRIGHT Copyright \(co 1998-2009 Charles Cazabon .br This is free software; see the file COPYING for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" The full documentation for .B getmail is maintained in HTML and plaintext formats. See the included files for details. getmail-4.43.0/docs/getmail_maildir.10000644000175000017500000000221411236652455020763 0ustar charlesccharlesc00000000000000.TH getmail_maildir "1" "July 2004" "getmail 4" "User Commands" .SH NAME getmail_maildir \- read a message from stdin and deliver to a named maildir .SH SYNOPSIS .B getmail_maildir [\fIOPTION\fR] \fIPATH\fR .SH DESCRIPTION .\" Add any additional description here .PP Deliver a mail message from standard input, to the maildir named PATH. PATH must start with a dot or a slash and end with a slash. .PP .B getmail_maildir uses the SENDER environment variable to construct a .B Return-Path: header field and the contents of the RECIPIENT environment variable to construct a .B Delivered-To: header field at the top of the message. .TP \fB\-\-verbose, \-v\fR print a status message on success\fR .SH AUTHOR Written by Charles Cazabon. .SH "REPORTING BUGS" Report bugs to . .SH COPYRIGHT Copyright \(co 1998-2009 Charles Cazabon .br This is free software; see the file COPYING for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" The full documentation for .B getmail_maildir is maintained in HTML and plaintext formats. See the included files for details. getmail-4.43.0/docs/COPYING0000644000175000017500000004325411770240033016606 0ustar charlesccharlesc00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. getmail-4.43.0/docs/getmail_mbox.10000644000175000017500000000264111236652445020312 0ustar charlesccharlesc00000000000000.TH getmail_mbox "1" "July 2004" "getmail 4" "User Commands" .SH NAME getmail_mbox \- read a message from stdin and deliver to a named mboxrd-format mbox file with fcntl-style locking. .SH SYNOPSIS .B getmail_mbox [\fIOPTION\fR] \fIPATH\fR .SH DESCRIPTION .\" Add any additional description here .PP Deliver a mail message from standard input, to the mboxrd-format mbox file named PATH. PATH must start with a dot or a slash and must not end with a slash. fcntl-type locking is used; if your system requires another type of locking (such as .B flock or .B dotlock ), use an MDA configured for that style of locking instead. .PP .B getmail_mbox uses the SENDER environment variable to construct a .B Return-Path: header field and the contents of the RECIPIENT environment variable to construct a .B Delivered-To: header field at the top of the message. SENDER is also used in creating the mbox .B From_ line. .TP \fB\-\-verbose, \-v\fR print a status message on success\fR .SH AUTHOR Written by Charles Cazabon. .SH "REPORTING BUGS" Report bugs to . .SH COPYRIGHT Copyright \(co 1998-2009 Charles Cazabon .br This is free software; see the file COPYING for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" The full documentation for .B getmail_mbox is maintained in HTML and plaintext formats. See the included files for details. getmail-4.43.0/docs/configuration.html0000644000175000017500000032112612206542267021316 0ustar charlesccharlesc00000000000000 getmail configuration (version 4)

getmail documentation

This is the documentation for getmail version 4. Version 4 includes numerous changes from version 3.x; if you are using getmail version 3, please refer to the documentation included with that version of the software.

getmail is Copyright © 1998-2009 Charles Cazabon.

getmail is licensed under the GNU General Public License version 2 (only). If you wish to obtain a license to distribute getmail under other terms, please contact me directly.

Table of Contents

Configuring getmail

Once getmail is installed, you need to configure it before you can retrieve mail with it. Follow these steps:

  1. Create a data/configuration directory. The default is $HOME/.getmail/. If you choose a different location, you will need to specify it on the getmail command line. In general, other users should not be able to read the contents of this directory, so you should set the permissions on it appropriately.
    mkdir -m 0700 $HOME/.getmail
            
  2. Create a configuration file in the configuration/data directory. The default name is getmailrc. If you choose a different filename, you will need to specify it on the getmail command line. If you want to retrieve mail from more than one mail account, you will need to create a separate rc file for each account getmail should retrieve mail from.

Creating a getmail rc file

The configuration file format is designed to be easy to understand (both for getmail, and for the user). It is broken down into small sections of related parameters by section headers which appear on lines by themselves, enclosed in square brackets, like this:

[section name]

Each section contains a series of parameters, declared as follows:

parameter_name = parameter_value

A parameter value, if necessary, can span multiple lines. To indicate that the second and subsequent lines form a continuation of the previous line, they need to begin with leading whitespace, like this:

first_parameter = value
    first parameter value continues here
second_parameter = value

You can annotate your configuration files with comments by putting them on lines which begin with a pound sign, like this:

first_parameter = value
# I chose this value because of etc.
second_parameter = value

Each rc file requires at least two specific sections. The first is retriever, which tells getmail about the mail account to retrieve messages from. The second is destination, which tells getmail what to do with the retrieved messages. There is also an optional section named options , which gives getmail general configuration information (such as whether to log its actions to a file), and other sections can be used to tell getmail to filter retrieved messages through other programs, or to deliver messages for particular users in a particular way.

Parameter types and formats

Several different types of parameters are used in getmail rc files:

Each parameter type has a specific format that must be used to represent it in the getmail rc file. They are explained below. Each parameter documented later specifies its type explicitly.

string

Specify a string parameter value with no special syntax:

parameter = my value

integer

Specify an integer parameter value with no special syntax:

parameter = 4150

boolean

A boolean parameter is true or false; you can specify its value with the (case-insensitive) words "true" and "false". The values "yes", "on" and 1 are accepted as equivalent to "true", while values "no", "off" and 0 are accepted as equivalent to "false". Some examples:

parameter = True
parameter = false
parameter = NO
parameter = 1

tuple of quoted strings

A tuple of quoted strings is essentially a list of strings, with each string surrounded by matching double- or single-quote characters to indicate where it begins and ends. The list must be surrounded by open- and close-parenthesis characters. A tuple may have to be a specific number of strings; for instance, a "2-tuple" must consist of two quoted strings, while a "4-tuple" must have exactly four. In most cases, the number of strings is not required to be a specific number, and it will not be specified in this fashion.

In general, a tuple of quoted strings parameter values should look like this:

parameter = ('first string', 'second string',
    "third string that contains a ' character")

However, tuples of 0 or 1 strings require special treatment. The empty tuple is specified with just the open- and close-parenthesis characters:

parameter = ()

A tuple containing a single quoted string requires a comma to indicate it is a tuple:

parameter = ("single string", )

tuple of integers

This is very similar to a tuple of quoted strings, above, minus the quotes. Some examples:

parameter = (1, 2, 3, 4, 5)
parameter = (37, )
parameter = ()

tuple of 2-tuples

This is a tuple of items, each of which is a 2-tuple of quoted strings. You can think of this as a list of pairs of quoted strings.

# Three pairs
parameter = (
    ("first-a", "first-b"),
    ("second-a", "second-b"),
    ("third-a", "third-b"),
    )
# One pair
parameter = (
    ("lone-a", "lone-b"),
    )

Creating the [retriever] section

The retriever section of the rc file tells getmail what mail account to retrieve mail from, and how to access that account. Begin with the section header line as follows:

[retriever]

Then, include a type string parameter to tell getmail what type of mail retriever to use to retrieve mail from this account. The possible values are:

What is a "multidrop" mailbox? How do I know if I have one?

Some ISPs, mailhosts, and other service providers provide a mail service they refer to as a "domain mailbox" or "multidrop mailbox". This is where they register a domain for you, and mail addressed to any local-part in that domain ends up in a single mailbox accessible via POP3, with the message envelope (envelope sender address and envelope recipient address) recorded properly in the message header, so that it can be re-constructed after you retrieve the messages with POP3 or IMAP. The primary benefit of this is that you can run your own MTA (qmail, Postfix, sendmail, Exchange, etc.) for your domain without having to have an SMTP daemon listening at a static IP address.

Unfortunately, a lot of what is advertised and sold as multidrop service really isn't. In many cases, the envelope recipient address of the message is not properly recorded, so the envelope information is lost and cannot be reconstructed. If the envelope isn't properly preserved, it isn't a domain mailbox, and you therefore can't use a multidrop retriever with that mailbox.

To determine if you have a multidrop mailbox, check the following list: if any of these items are not true, you do not have a multidrop mailbox.

  • the mailbox must receive one copy of the message for each envelope recipient in the domain; if the message was addressed to three local-parts in the domain, the mailbox must receive three separate copies of the message.
  • the envelope sender address must be recorded in a header field named Return-Path at the top of the message. If the message (incorrectly) already contained such a header field, it must be deleted before the envelope sender address is recorded.
  • the envelope recipient address must be recorded in a new header field. These may be named various things, but are commonly Delivered-To, X-Envelope-To, and similar values. In the case of messages which had multiple recipients in the domain, this must be a single address, reflecting the particular recipient of this copy of the message. Note that this field (and the envelope recipient address) are not related to informational header fields created by the originating MUA, like To or cc.

If you're not sure whether you have a multidrop mailbox, you probably don't. You probably want to use SimplePOP3Retriever (for POP3 mail accounts) or SimpleIMAPRetriever (for IMAP mail accounts) retrievers.

Specify the mail account type with one of the above values, like this:

type = typename

Then, include lines for any parameters and their values which are required by the retriever. The parameters and their types are documented below.

Common retriever parameters

All retriever types take several common required parameters:

  • server (string) — the name or IP address of the server to retrieve mail from
  • username (string) — username to provide when logging in to the mail server

All retriever types also take several optional parameters:

  • port (integer) — the TCP port number to connect to. If not provided, the default is a port appropriate for the protocol (110 for POP3, etc.)
  • password (string) — password to use when logging in to the mail server. If not using Kerberos authentication -- see below -- getmail gets the password credential for the POP/IMAP server in one of the following ways:
    1. from the password configuration item in the getmailrc file
    2. on Mac OS X only, from the OS X keychain
    3. on systems with Gnome keyring support, from the default Gnome keyring
    4. if not found via any of the above methods, getmail will prompt for the password when run
    To store your POP/IMAP account password into the Gnome keyring, ensure the password is not provided in the getmailrc file, and run getmail with the special option --store-password-in-gnome-keyring; getmail will run, prompt you for the password, store it in the Gnome keyring, and exit without retrieving mail. If this option is not recognized, your Python installation does not have Gnome keyring integration support, or Gnome indicates that the keyring is not available.

All IMAP retriever types also take the following optional parameters:

  • mailboxes (tuple of quoted strings) — a list of mailbox paths to retrieve mail from, expressed as a Python tuple. If not specified, the default is to retrieve mail from the mail folder named INBOX. You might want to retrieve messages from several different mail folders, using a configuration like this:
    mailboxes = ("INBOX", "INBOX.spam",
        "mailing-lists.model-railroading")
            
    Note that the format for hierarchical folder names is determined by the IMAP server, not by getmail. Consult your server's documentation or postmaster if you're unsure what form your server uses. If your mailbox names contain non-ASCII characters, ensure that your getmailrc file is stored with UTF-8 encoding so that getmail can correctly determine the unicode character names that need to be quoted in IMAP's modified UTF-7 encoding; if you do not do this, the mailbox names will not match what the server expects them to be, or will cause UnicodeErrors when attempting to load your getmailrc file. As a special case, in getmail version 4.29.0 and later, the unquoted base (non-tuple) value ALL (case-sensitive) means to retrieve mail from all selectable IMAP mailboxes in the account. To retrieve messages from all mailboxes, you would use:
    mailboxes = ALL
            
  • use_peek (boolean) — whether to use PEEK to retrieve the message; the default is True. IMAP servers typically mark a message as seen if PEEK is not used to retrieve the message content. Versions of getmail prior to 4.26.0 did not use PEEK to retrieve messages.
  • move_on_delete (string) — if set, messages are moved to the named mail folder before being deleted from their original location. Note that if you configure getmail not to delete retrieved messages (the default behaviour), they will not be moved at all.
  • use_kerberos (boolean) — whether to use Kerberos authentication with the IMAP server. If not set, normal password-based authenticaion is used. Note that when you use Kerberos authentication, it is up to you to ensure you have a valid Kerberos ticket (perhaps by running a ticket-renewing agent such as kstart or similar). This feature requires that a recent version of pykerberos with GSS support is installed; check your OS distribution or see http://honk.sigxcpu.org/projects/pykerberos/" for details.

SimplePOP3Retriever

The SimplePOP3Retriever class takes the common retriever parameters above, plus the following optional parameters:

  • use_apop (boolean) — if set to True, getmail will use APOP-style authentication to log in to the server instead of normal USER/PASS authentication. This is not supported by many POP3 servers. Note that APOP adds much less security than might be supposed; weaknesses in its hashing algorithm mean that an attacker can recover the first three characters of the password after snooping on only a few hundred authentications between a client and server — see http://www.securityfocus.com/archive/1/464477/30/0/threaded for details. The default is False.
  • timeout (integer) — how long (in seconds) to wait for socket operations to complete before considering them failed. If not specified, the default is 180 seconds. You may need to increase this value in particularly poor networking conditions.
  • delete_dup_msgids (boolean) — if set to True, and the POP3 server identifies multiple messages as having the same "unique" identifier, all but the first will be deleted without retrieving them.

BrokenUIDLPOP3Retriever

This retriever class is intended only for use with broken POP3 servers that either do not implement the UIDL command, or which do not properly assign unique identifiers to messages (preventing getmail from determining which messages it has seen before). It will identify every message in the mailbox as a new message, and therefore if you use this retriever class and opt not to delete messages after retrieval, it will retrieve those messages again the next time getmail is run. Use this retriever class only if your mailbox is hosted on such a broken POP3 server, and the server does not provide another means of getmail accessing it (i.e., IMAP).

The BrokenUIDLPOP3Retriever class takes the common retriever parameters above, plus the following optional parameters:

SimpleIMAPRetriever

The SimpleIMAPRetriever class takes the common retriever parameters above, plus the following optional parameters:

SimplePOP3SSLRetriever

The SimplePOP3SSLRetriever class takes the common retriever parameters above, plus the following optional parameters:

  • use_apop (boolean) — see SimplePOP3Retriever for definition.
  • delete_dup_msgids (boolean) — see SimplePOP3Retriever for definition.
  • keyfile (string) — use the specified PEM-formatted key file in the SSL negotiation. Note that no certificate or key validation is done.
  • certfile (string) — use the specified PEM-formatted certificate file in the SSL negotiation. Note that no certificate or key validation is done.

BrokenUIDLPOP3SSLRetriever

The BrokenUIDLPOP3SSLRetriever class takes the common retriever parameters above, plus the following optional parameters:

SimpleIMAPSSLRetriever

The SimpleIMAPSSLRetriever class takes the common retriever parameters above, plus the following optional parameters:

MultidropPOP3Retriever

The MultidropPOP3Retriever class takes the common retriever parameters above, plus the following required parameter:

  • envelope_recipient (string) — the name and position of the header field which records the envelope recipient address. This is set to a value of the form field_name : field_position . The first (topmost) Delivered-To: header field would be specified as:
    envelope_recipient = delivered-to:1
            

The MultidropPOP3Retriever also takes the following optional parameters:

MultidropPOP3SSLRetriever

The MultidropPOP3SSLRetriever class takes the common retriever parameters above, plus the following required parameter:

The MultidropPOP3SSLRetriever class alo takes the following optional parameters:

MultidropSDPSRetriever

The MultidropSDPSRetriever class takes the common retriever parameters above, plus the following optional parameters:

MultidropIMAPRetriever

The MultidropIMAPRetriever class takes the common retriever parameters above, plus the following required parameter:

The MultidropIMAPRetriever class also takes the following optional parameters:

MultidropIMAPSSLRetriever

The MultidropIMAPSSLRetriever class takes the common retriever parameters above, plus the following required parameter:

The MultidropIMAPSSLRetriever class also takes following optional parameters:

Retriever examples

A typical POP3 mail account (the basic kind of mailbox provided by most internet service providers (ISPs)) would use a retriever configuration like this:

[retriever]
type = SimplePOP3Retriever
server = popmail.isp.example.net
username = account_name
password = my_mail_password

If your ISP provides POP3 access on a non-standard port number, you would need to include the port parameter:

[retriever]
type = SimplePOP3Retriever
server = popmail.isp.example.net
port = 8110
username = account_name
password = my_mail_password

If your ISP provides POP3-over-SSL and you wanted to use that, your retriever configuration might look like this:

[retriever]
type = SimplePOP3SSLRetriever
server = popmail.isp.example.net
username = account_name
password = my_mail_password

If you have an IMAP mail account and want to retrieve messages from several mail folders under that account, and you want to move messages to a special folder when deleting them, you would use a retriever configuration like this:

[retriever]
type = SimpleIMAPRetriever
server = imapmail.isp.example.net
username = account_name
password = my_mail_password
mailboxes = ("INBOX", "lists.unix", "lists.getmail")
move_on_delete = mail.deleted

If you are retrieving your company's mail from a domain POP3 mailbox for delivery to multiple local users, you might use a retriever configuration like this:

[retriever]
type = MultidropPOP3Retriever
server = imapmail.isp.example.net
username = account_name
password = company_maildrop_password
envelope_recipient = delivered-to:1

Creating the [destination] section

The destination section of the rc file tells getmail what to do with retrieved messages. Begin with the section header line as follows:

[destination]

Then, include a type string parameter to tell getmail what type of mail destination this is. The possible values are:

  • Maildir — deliver all messages to a local qmail-style maildir
  • Mboxrd — deliver all messages to a local mboxrd-format mbox file with fcntl-type locking.
  • MDA_external — use an external message delivery agent (MDA) to deliver messages. Typical MDAs include maildrop, procmail, and others.
  • MultiDestination — unconditionally deliver messages to multiple destinations (maildirs, mbox files, external MDAs, or other destinations).
  • MultiSorter — sort messages according to the envelope recipient (requires a domain mailbox retriever) and deliver to a variety of maildirs, mbox files, external MDAs, or other destinations based on regular expressions matching the recipient address of each message. Messages not matching any of the regular expressions are delivered to a default "postmaster" destination.
  • MultiGuesser — sort messages according to getmail's best guess at what the envelope recipient of the message might have been, and deliver to a variety of maildirs, mbox files, external MDAs, or other destinations based on regular expressions matching those addresses. Messages not matching any of the regular expressions are delivered to a default "postmaster" destination.
  • MDA_qmaillocal — use qmail-local to deliver messages according to instructions in a .qmail file.

Maildir

The Maildir destination delivers to a qmail-style maildir. The maildir must already exist, and must contain all of the subdirectories required by the maildir format. getmail will not create the maildir if it does not exist. If you're not familiar with the maildir format, the requirements in a nutshell are: it must be a directory containing three writable subdirectories cur, new, and tmp, and they must all reside on the same filesystem.

The Maildir destination takes one required parameter:

  • path (string) — the path to the maildir, ending in slash (/). This value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}. You might want to deliver messages to a maildir named Maildir in your home directory; you could do this with a configuration like this:
    [destination]
    type = Maildir
    path = ~/Maildir/
            

The Maildir destination also takes two optional parameters:

  • user (string) — on Unix-like systems, if supplied, getmail will change the effective UID to that of the named user before delivering messages to the maildir. Note that this typically requires root privileges. getmail will not deliver to maildirs as root, so this "optional" parameter is required in that situation.
  • filemode (string) — if supplied, getmail will cause the delivered message files in the maildir to have at most these permissions (given in standard Unix octal notation). Note that the current umask is masked out of the given value at file creation time. The default value, which should be appropriate for most users, is "0600".

Mboxrd

The Mboxrd destination delivers to an mboxrd-format mbox file with either fcntl-type (lockf) or flock-type file locking. The file must already exist and appear to be a valid mboxrd file before getmail will try to deliver to it — getmail will not create the file if it does not exist. If you want to create a new mboxrd file for getmail to use, simply create a completely empty (0-byte) file.

You must ensure that all other programs accessing any the mbox file expect mboxrd-format mbox files and the same type of file locking that you configure getmail to use; failure to do so can cause mbox corruption. If you do not know what type of file locking your system expects, ask your system administrator. If you are the system administrator and don't know what type of file locking your system expects, do not use Mboxrd files; use Maildirs instead. Note that delivering to mbox files over NFS can be unreliable and should be avoided; this is the case with any MDA.

The Mboxrd destination takes one required parameter:

  • path (string) — the path to the mbox file. This value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}. You might want to deliver messages to an mbox file named inbox in your home directory; you could do this with a configuration like this:
    [destination]
    type = Mboxrd
    path = ~/inbox
            

The Mboxrd destination also takes two optional parameters:

  • user (string) — on Unix-like systems, if supplied, getmail will change the effective UID to that of the named user before delivering messages to the mboxrd file. Note that this typically requires root privileges. getmail will not deliver to mbox files as root, so this "optional" parameter is required in that situation.
  • locktype (string) — which type of file locking to use; may be "lockf" (for fcntl locking) or "flock". The default in getmail 4.7.0 and later is lockf.

MDA_external

MDA_external delivers messages by running an external program (known as a message delivery agent, or MDA) and feeding it the message on its standard input. Some typical MDAs include maildrop and procmail.

The MDA_external destination takes one required parameter:

  • path (string) — the path to the command to run. This value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}.

The MDA_external destination also takes several optional parameters:

  • arguments (tuple of quoted strings) — arguments to be supplied to the command. The following substrings will be substituted with the equivalent values from the message:
    • %(sender) — envelope return-path address
    If the message is retrieved with a multidrop retriever class, the message recipient (and parts of it) are also available with the following replacement substrings:
    • %(recipient) — envelope recipient address
    • %(local) — local-part of the envelope recipient address
    • %(domain) — domain-part of the envelope recipient address
    • %(mailbox) — the IMAP mailbox name the message was retrieved from; for POP, this will be empty
    The default value of the arguments parameter is (), so no arguments are supplied to the command.
  • unixfrom (boolean) — whether to include a Unix-style mbox From_ line at the beginning of the message supplied to the command. Defaults to false. Some MDAs expect such a line to be present and will fail to operate if it is missing.
  • user (string) — if supplied, getmail will change the effective UID to that of the named user. Note that this typically requires root privileges.
  • group (string) — if supplied, getmail will change the effective GID to that of the named group. Note that this typically requires root privileges.
  • allow_root_commands (boolean) — if set, getmail will run external commands even if it is currently running with root privileges. The default is false, which causes getmail to raise an exception if it is asked to run an external command as root. Note that setting this option has serious security implications. Don't use it if you don't know what you're doing. I strongly recommend against running external processes as root.
  • ignore_stderr (boolean) — if set, getmail will not consider it an error if the program writes to stderr. The default is false, which causes getmail to consider the delivery failed and leave the message on the server, proceeding to the next message. This prevents loss of mail if the MDA writes to stderr but fails to exit nonzero when it encounters an error. Note that setting this option has serious implications; some MDAs can fail to deliver a message but still exit 0, which can cause loss of mail if this option is set. Only change this setting if you are confident your MDA always exits nonzero on error.

A basic invocation of an external MDA might look like this:

[destination]
type = MDA_external
path = /path/to/mymda
arguments = ("--log-errors", )

Something more complex might look like this:

[destination]
type = MDA_external
path = /path/to/mymda
# Switch to fred's UID and the mail group GID before delivering his mail
user = fred
group = mail
arguments = ("--strip-forbidden-attachments", "--recipient=%(recipient)")

MultiDestination

MultiDestination doesn't do any message deliveries itself; instead, it lets you specify a list of one or more other destinations which it will pass each message to. You can use this to deliver each message to several different destinations.

The MultiDestination destination takes one required parameter:

  • destinations (tuple of quoted strings) — the destinations which the messages will be passed to. A destination is a string that refers to another configuration file section by name (shortcuts for maildirs and mboxrd files are also provided; see below), like this:

    destinations = ('[other-destination-1]', '[other-destination-2]')
    
    [other-destination-1]
    type = Mboxrd
    path = /var/spool/mail/alice
    user = alice
    
    [other-destination-2]
    type = Maildir
    path = /home/joe/Maildir/
    user = joe
    

    Because Maildir and Mboxrd destinations are common, you can specify them directly as a shortcut if they do not require a user parameter. If the string (after expansion; see below) starts with a dot or slash and ends with a slash, it specifies the path of a Maildir destination, while if it starts with a dot or a slash and does not end with a slash, it specifies the path of a Mboxrd destination.

    For instance, you can deliver mail to two maildirs with the following:

    destinations = ('~/Mail/inbox/', '~/Mail/archive/current/')
    

    Each destination string is first expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}.

Some examples:

  • To deliver to a maildir named Maildir in the home directory of user jeff, when getmail is run as that user:
    [destination]
    type = MultiDestination
    destinations = ("~jeff/Maildir/", )
    
  • To deliver to an mboxrd file:
    [destination]
    type = MultiDestination
    destinations = ("/var/spool/mail/alice", )
    
  • To deliver with an external MDA:
    [destination]
    type = MultiDestination
    destinations = ("[procmail-as-bob]", )
    
    [procmail-as-bob]
    type = MDA_external
    path = /path/to/procmail
    arguments = ('~bob/.procmailrc', '-f', '%(sender)')
    user = bob
    

Of course, the whole point of MultiDestination is to allow you to specify multiple destinations, like this:

[destination]
type = MultiDestination
destinations = (
    "~jeff/Mail/inbox",
    "[procmail-as-jeff]",
    "/var/mail-archive/incoming"
    )

[procmail-as-jeff]
type = MDA_external
path = /path/to/procmail
arguments = ('~jeff/.procmailrc', '-f', '%(sender)')
user = jeff

MultiSorter

MultiSorter compares the envelope recipient address of messages against a list of user-supplied regular expressions and delivers the message to the destination (maildir, mboxrd file, or other) associated with any matching patterns. A message can match multiple patterns and therefore be delivered to multiple matching destinations. Any message which matches none of the patterns is delivered to a default destination for the postmaster.

Because MultiSorter requires the envelope recipient to operate, it must be used with a domain mailbox retriever. If you instead want to do some basic message sorting based on getmail's best guess as to the envelope recipient of the message, see the MultiGuesser destination class below.

The MultiSorter destination takes one required parameter:

  • default (string) — the destination for messages which aren't matched by any of the "locals" regular expressions. The destination can be a maildir, mboxrd file, or other destination. See MultiDestination for an explanation of how the type of destination is interpreted from this value.

The MultiSorter destination also takes one optional parameter:

  • locals (tuple of 2-tuples) — zero or more regular expression – destination pairs. Messages will be delivered to each destination for which the envelope recipient matches the given regular expression. The regular expression and destination are supplied as two quoted strings in a tuple; locals is then a tuple of such pairs of strings. Destinations are specified in the same manner as with the "default" parameter, above.

Important note: if your regular expression contains backslashes (by themselves, or as part of an escaped character or symbol like \n or \W ), you need to tell the parser that this expression must be parsed "raw" by prepending the string with an "r":

locals = (
    (r'jeff\?\?\?@.*', '[jeff]'),
    ('alice@', '[alice]')
    )

locals = (
    ('jeff@.*', '[jeff]'),
    (r'alice\D+@', '[alice]')
    )

Note that if you don't understand regular expressions, you don't need to worry about it. In general, an email address is a regular expression that matches itself. The only significant times this isn't the case is when the address contains odd punctuation characters like ^, $, \, or [. Handy hints:

  • the regular expression . (dot) matches anything
  • matches can occur anywhere in the address. If you want to only match at the beginning, start your expression with the ^ character. If you only want to match the whole address, also end your expression with a dollar sign $.

Using regular expressions:

  • The regular expression joe@example.org matches the addresses joe@example.org, joe@example.org.net, and heyjoe@example.org.
  • The regular expression ^jeff@ matches the addresses jeff@example.org and jeff@example.net, but not otherjeff@example.org.
  • The regular expression sam matches the addresses sam@example.org, samantha@example.org, asam@example.org, and chris@isam.example.net.

Some examples:

    • Deliver mail matching jeff@example.net to ~jeff/Maildir/
    • Deliver mail matching alice@anything to ~alice/inbox
    • Deliver all other mail to ~bob/Maildir/
    [destination]
    type = MultiSorter
    default = [bob-default]
    locals = (
        ('jeff@example.net', '[jeff]'),
        ('alice@', '[alice]')
        )
    
    [jeff]
    type = Maildir
    path = ~jeff/Maildir/
    user = jeff
    
    [alice]
    type = Mboxrd
    path = ~alice/inbox
    user = alice
    
    [bob-default]
    type = Maildir
    path = ~bob/Maildir/
    user = bob
    
    • Deliver mail for jeff, bob, and alice to maildirs in their home directories
    • Deliver copies of all messages to samantha's mail archive
    • Deliver copies of all messages to a program that logs certain information. This program should run as the user log, and command arguments should tell it to record the info to /var/log/mail/info
    [destination]
    type = MultiSorter
    default = doesn't matter, this won't be used, as locals will always match
    locals = (
        ('^jeff@', '[jeff]'),
        ('^bob@', '[bob]'),
        ('^alice@', '[alice]'),
        ('.', '[copies]'),
        ('.', '[info]')
        )
    
    [alice]
    type = Maildir
    path = ~alice/Maildir/
    user = alice
    
    [bob]
    type = Maildir
    path = ~bob/Maildir/
    user = bob
    
    [jeff]
    type = Maildir
    path = ~jeff/Maildir/
    user = jeff
    
    [copies]
    type = Maildir
    path = ~samantha/Mail/archive/copies/
    user = samantha
    
    [info]
    type = MDA_external
    path = /path/to/infologger
    arguments = ('--log=/var/log/mail/info', '--sender=%(sender)', '--recipient=%(recipient))
    user = log
    

MultiGuesser

MultiGuesser tries to guess what the envelope recipient address of the message might have been, by comparing addresses found in the message header against a list of user-supplied regular expressions, and delivers the message to the destination (maildir, mboxrd file, or other) associated with any matching patterns. A message can match multiple patterns and therefore be delivered to multiple matching destinations. Any message which matches none of the patterns is delivered to a default destination for the postmaster. In this fashion, you can do basic mail filtering and sorting with getmail without using an external filtering message delivery agent (MDA) (such as maildrop or procmail), if and only if the message recipient is the criteria you want to filter on.

If you want to filter based on arbitrary message critera, like "What address is in the To: header field?" or "Who is the message from?", then use the filtering MDA of your choice, called from a getmail MDA_external destination.

MultiGuesser is similar to MultiSorter, except that it does not operate on the true envelope recipient address, and therefore does not require a domain mailbox retriever. Because it is "guessing" at the intended recipient of the message based on the contents of the message header, it is fallible — for instance, the address of a recipient of a mailing list message may not appear in the header of the message at all. If your locals regular expression patterns are only looking for that address, MultiGuesser will then have to deliver it to the destination specified as the default recipient.

This functionality is very similar to the guessing functionality of getmail version 2, which was removed in version 3. MultiGuesser extracts a list of addresses from the message header like this:

  1. it looks for addresses in any Delivered-To: header fields.
  2. if no addresses have been found, it looks for addresses in any Envelope-To: header fields.
  3. if no addresses have been found, it looks for addresses in any X-Envelope-To: header fields.
  4. if no addresses have been found, it looks for addresses in any Apparently-To: header fields.
  5. if no addresses have been found, it looks for addresses in any Resent-to: or Resent-cc: header fields (or Resent-bcc:, which shouldn't be present).
  6. if no addresses have been found, it looks for addresses in any To: or cc: header fields (or bcc:, which shouldn't be present).

The MultiGuesser destination takes one required parameter:

The MultiGuesser destination also takes one optional parameter:

Examples:

If you have a simple POP3 account (i.e. it's not a multidrop mailbox) and you want to deliver your personal mail to your regular maildir, but deliver mail from a couple of mailing lists (identified by the list address appearing in the message header) to separate maildirs, you could use a MultiGuesser configuration like this:

[destination]
type = MultiGuesser
default = ~/Maildir/
locals = (
    ("list-address-1@list-domain-1", "~/Mail/mailing-lists/list-1/"),
    ("list-address-2@list-domain-2", "~/Mail/mailing-lists/list-2/"),
    )

See MultiSorter above for other examples of getmail rc usage; the only difference is the type parameter specifying the MultiGuesser destination.

MDA_qmaillocal

MDA_qmaillocal delivers messages by running the qmail-local program as an external MDA. qmail-local uses .qmail files to tell it what to do with messages. If you're not already familiar with qmail, you don't need to use this destination class.

The MDA_qmaillocal destination takes several optional parameters:

  • qmaillocal (string) — path to the qmail-local program. The default value is /var/qmail/bin/qmail-local.
  • user (string) — supplied to qmail-local, and also tells getmail to change the current effective UID to that of the named user before running qmail-local. Note that this typically requires root privileges. The default value is the account name of the current effective UID.
  • group (string) — if supplied, getmail will change the effective GID to that of the named group before running qmail-local. Note that this typically requires root privileges.
  • homedir (string) — supplied to qmail-local. The default value is the home directory of the account with the current effective UID.
  • localdomain (string) — supplied to qmail-local as its domain argument. The default value is the fully-qualified domain name of the local host.
  • defaultdelivery (string) — supplied to qmail-local as its defaultdelivery argument. The default value is ./Maildir/.
  • conf-break (string) — supplied to qmail-local as its dash argument. The default value is -.
  • localpart_translate (2-tuple of quoted strings) — if supplied, the recipient address of the message (which is used to construct the local argument (among others) to qmail-local) will have any leading instance of the first string replaced with the second string. This can be used to remap recipient addresses, trim extraneous prefixes (such as the qmail virtualdomain prepend value), or perform other tasks. The default value is ('', '') (i.e., no translation).
  • strip_delivered_to (boolean) — if set, Delivered-To: header fields will be removed from the message before handing it to qmail-local. This may be necessary to prevent qmail-local falsely detecting a looping message if (for instance) the system retrieving messages otherwise believes it has the same domain name as the retrieval server. Inappropriate use of this option may cause message loops. The default value is False.
  • allow_root_commands (boolean) — if set, getmail will run qmail-local even if it is currently running with root privileges. The default is false, which causes getmail to raise an exception if it is asked to run an external command as root. Note that setting this option has serious security implications. Don't use it if you don't know what you're doing. I strongly recommend against running external processes as root.

A basic invocation of qmail-local might look like this:

[destination]
type = MDA_qmaillocal
user = joyce

Something more complex might look like this:

[destination]
type = MDA_qmaillocal
user = joyce
# The mail domain isn't the normal FQDN of the server running getmail
localdomain = host.example.net
# Trim the server's virtualdomain prepend value from message recipient before
# sending it to qmail-local
localpart_translate = ('mailhostaccount-', '')

Creating the [options] section

The optional options section of the rc file can be used to alter getmail's default behaviour. The parameters supported in this section are as follows:

  • verbose (integer) — controls getmail's verbosity. If set to 2, getmail prints messages about each of its actions. If set to 1, it prints messages about retrieving and deleting messages (only). If set to 0, getmail will only print warnings and errors. Default: 1.
  • read_all (boolean) — if set, getmail retrieves all available messages. If unset, getmail only retrieves messages it has not seen before. Default: True.
  • delete (boolean) — if set, getmail will delete messages after retrieving and successfully delivering them. If unset, getmail will leave messages on the server after retrieving them. Default: False.
  • delete_after (integer) — if set, getmail will delete messages this number of days after first seeing them, if they have been retrieved and delivered. This, in effect, leaves messages on the server for a configurable number of days after retrieving them. Note that the delete parameter has higher priority; if both are set, the messages will be deleted immediately. Default: 0, which means not to enable this feature.
  • delete_bigger_than (integer) — if set, getmail will delete messages larger than this number of bytes after retrieving them, even if the delete and delete_after options are disabled. The purpose of this feature is to allow deleting only large messages, to help keep a mailbox under quota. Has no effect if delete is set, as that will unconditionally remove messages. If delete_after is also set, the message will be deleted immediately after retrieval if it is over this size, and otherwise will be deleted according to the setting of delete_after. Default: 0, which means not to enable this feature.
  • max_bytes_per_session (integer) — if set, getmail will retrieve messages totalling up to this number of bytes before closing the session with the server. This can be useful if you do not want large messages causing large bursts of network traffic. Default: 0, which means not to enable this feature. Note that message sizes reported by the server are used, and therefore may vary slightly from the actual size on disk after message retrieval.
  • max_message_size (integer) — if set, getmail will not retrieve messages larger than this number of bytes. Default: 0, which means not to enable this feature.
  • max_messages_per_session (integer) — if set, getmail will process a maximum of this number of messages before closing the session with the server. This can be useful if your network or the server is particuarly unreliable. Default: 0, which means not to enable this feature.
  • delivered_to (boolean) — if set, getmail adds a Delivered-To: header field to the message. If unset, it will not do so. Default: True. Note that this field will contain the envelope recipient of the message if the retriever in use is a multidrop retriever; otherwise it will contain the string "unknown".
  • received (boolean) — if set, getmail adds a Received: header field to the message. If unset, it will not do so. Default: True.
  • message_log (string) — if set, getmail will record a log of its actions to the named file. The value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}. Default: '' (the empty string), which means not to enable this feature.
  • message_log_syslog (boolean) — if set, getmail will record a log of its actions using the system logger. Note that syslog is inherently unreliable and can lose log messages. Default: False.
  • message_log_verbose (boolean) — if set, getmail will log to the message log file (or syslog) information about messages not retrieved and the reason for not retrieving them, as well as starting and ending information lines. By default, it will log only about messages actually retrieved, and about error conditions. Note that this has no effect if neither message_log nor message_log_syslog is in use. Default: False.

Most users will want to either enable the delete option (to delete mail after retrieving it), or disable the read_all option (to only retrieve previously-unread mail).

The verbose, read_all, and delete parameters can be overridden at run time with commandline options.

[options] example

To configure getmail to operate quietly, to retrieve only new mail, to delete messages after retrieving them, and to log its actions to a file, you could provide the following in your getmail rc file(s):

[options]
verbose = 0
read_all = false
delete = true
message_log = ~/.getmail/log

Creating the [filter-something] sections

The filter-something section(s) of the rc file (which are not required) tell getmail to process messages in some way after retrieving them, but before delivering them to your destinations. Filters can tell getmail to drop a message (i.e. not deliver it at all), add information to the message header (i.e. for a spam- classification system or similar), or modify message content (like an antivirus system stripping suspected MIME parts from messages).

You can specify any number of filters; provide a separate rc file section for each, naming each of them filter-something. They will be run in collated order, so it's likely simplest to name them like this:

  • [filter-1]
  • [filter-2]
  • [filter-3]

Begin with the section header line as follows:

[filter-something]

Then, include a type string parameter to tell getmail what type of filter. The possible values are:

  • Filter_classifier — run the message through an external program, and insert the output of the program into X-getmail-filter-classifier: header fields in the message. Messages can be dropped by having the filter return specific exit codes.
  • Filter_external — supply the message to an external program, which can then modify the message in any fashion. The program must print the modified message to stdout. getmail reads the modified message from the program in this fasion before proceeding to the next filter or destination. Messages can be dropped by having the filter return specific exit codes.
  • Filter_TMDA — run the message through the tmda-filter program for use with the Tagged Message Delivery Agent (TMDA) package. If tmda-filter returns 0, the message will be passed to the next filter (or destination). If it returns 99, the message will be dropped, and TMDA is responsible for sending a challenge message, queuing the original, etc., as with normal TMDA operation in a .qmail, .courier, or .forward file.

By default, if a filter writes anything to stderr, getmail will consider the delivery to have encountered an error. getmail will leave the message on the server and proceed to the next message. You must configure any filter you use not to emit messages to stderr except on errors — please see the documentation for your filter program for details. Optionally, if you know your filter can emit warnings on stderr under non-error conditions, you can set the ignore_stderr option.

Filter_classifier

Filter_classifier runs the message through an external program, placing the output of that program into X-getmail-filter-classifier: header fields. It can also cause messages to be dropped by exiting with a return code listed in the exitcodes_drop parameter.

Filter_classifier has one required parameter:

  • path (string) — the path to the command to run. This value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}.

In addition, Filter_classifier takes the following optional parameters:

  • arguments (tuple of quoted strings) — arguments to be supplied to the command. The following substrings will be substituted with the equivalent values from the message:
    • %(sender) — envelope return-path address
    If the message is retrieved with a multidrop retriever class, the message recipient (and parts of it) are also available with the following replacement substrings:
    • %(recipient) — envelope recipient address
    • %(local) — local-part of the envelope recipient address
    • %(domain) — domain-part of the envelope recipient address
    The default value of the arguments parameter is (), so no arguments are supplied to the command.
  • unixfrom (boolean) — whether to include a Unix-style mbox From_ line at the beginning of the message supplied to the command. Default: False.
  • user (string) — if supplied, getmail will change the effective UID to that of the named user. Note that this typically requires root privileges.
  • group (string) — if supplied, getmail will change the effective GID to that of the named group. Note that this typically requires root privileges.
  • allow_root_commands (boolean) — if set, getmail will run external commands even if it is currently running with root privileges. The default is false, which causes getmail to raise an exception if it is asked to run an external command as root. Note that setting this option has serious security implications. Don't use it if you don't know what you're doing. I strongly recommend against running external processes as root.
  • ignore_stderr (boolean) — if set, getmail will not consider it an error if the filter writes to stderr. The default is false, which causes getmail to consider the delivery failed and leave the message on the server, proceeding to the next message. This prevents loss of mail if the filter writes to stderr but fails to exit nonzero when it encounters an error. Note that setting this option has serious implications; some poorly-written programs commonly used as mail filters can can mangle or drop mail but still exit 0, their only clue to failure being warnings emitted on stderr. Only change this setting if you are confident your filter always exits nonzero on error.
  • exitcodes_drop (tuple of integers) — if the filter returns an exit code in this list, the message will be dropped. The default is (99, 100).
  • exitcodes_keep (tuple of integers) — if the filter returns an exit code other than those in exitcodes_drop and exitcodes_keep, getmail assumes the filter encountered an error. getmail will then not proceed, so that the message is not lost. The default is (0, ).

Filter_external

Filter_external runs the message through an external program, and replaces the message with the output of that program, allowing the filter to make arbitrary changes to messages. It can also cause messages to be dropped by exiting with a return code listed in the exitcodes_drop parameter.

Filter_external has one required parameter:

In addition, Filter_external takes the following optional parameters:

Filter_TMDA

Filter_external runs the message through the external program tmda-filter, allowing the use of the Tagged Message Delivery Agent (TMDA) package. As TMDA relies on the message envelope, this filter requires the use of a multidrop retriever class to function. It sets the three environment variables SENDER, RECIPIENT, and EXT prior to running tmda-filter.

I've tested this filter, and it Works For Me™, but I'm not a regular TMDA user. I would appreciate any feedback about its use from TMDA users.

Filter_TMDA has no required parameters. It has the following optional parameters:

  • path (string) — the path to the tmda-filter binary. Default: /usr/local/bin/tmda-filter. This value will be expanded for leading ~ or ~USER and environment variables in the form $VARNAME or ${VARNAME}.
  • user (string) — see Filter_classifier for definition.
  • group (string) — see Filter_classifier for definition.
  • allow_root_commands (boolean) — see Filter_classifier for definition.
  • ignore_stderr (boolean) — see Filter_classifier for definition.
  • conf-break (string) — this value will be used to split the local-part of the envelope recipient address to determine the value of the EXT environment variable. For example, if the envelope sender address is sender-something@host.example.org, and the envelope recipient address is user-ext-ext2@host.example.net, and conf-break is set to -, getmail will set the environment variables SENDER to "sender-something@host.example.org", RECIPIENT to "user-ext-ext2@host.example.net", and EXT to "ext-ext2". Default: "-".

[filter-something] examples

You might filter spam messages in your MUA based on information added to the message header by a spam-classification program. You could have that information added to the message header with a filter configuration like this:

[filter-3]
type = Filter_classifier
path = /path/to/my-classifier
arguments = ('--message-from-stdin', '--report-to-stdout')
user = nobody

You might use a program to prevent users from accidentally destroying their data by stripping suspected attachments from messages. You could have that information added to the message header with a filter configuration like this:

[filter-3]
type = Filter_external
path = /path/to/my-mime-filter
arguments = ('--message-from-stdin', '--remove-all-but-attachment-types=text/plain,text/rfc822')
user = nobody

You might use TMDA to challenge messages from unknown senders. If the default parameters are fine for your configuration, this is as simple as:

[filter-3]
type = Filter_TMDA

getmail rc file examples

Several examples of different getmail rc configuration are available in the included file getmailrc-examples.

Running getmail

To use getmail, simply run the script getmail, which is typically installed in /usr/local/bin/ by default. getmail will read the default getmail rc file (getmailrc) from the default configuration/data directory (~/.getmail/) and begin operating.

You can modify this behaviour by supplying commandline options to getmail.

Commandline options

getmail understands the following options:

  • --version — show getmail's version number and exit
  • --help or -h — show a brief usage summary and exit
  • --getmaildir=DIR or -gDIR — use DIR for configuration and data files
  • --rcfile=FILE or -rFILE — read getmail rc file FILE instead of the default. The file path is assumed to be relative to the getmaildir directory unless this value starts with a slash (/). This option can be given multiple times to have getmail retrieve mail from multiple accounts.
  • --dump — read rc files, dump configuration, and exit (debugging)
  • --trace — print extended debugging information

If you are using a single getmailrc file with an IMAP server that understands the IDLE extension from RFC 2177, you can use the --rcfile=MAILBOX option to specify that getmail should wait on the server to notify getmail of new mail in the specified mailbox after getmail is finished retrieving mail.

In addition, the following commandline options can be used to override any values specified in the [options] section of the getmail rc files:

  • --verbose or -v — operate more verbosely. Can be given multiple times.
  • --quiet or -q — print only warnings or errors while running
  • --delete or -d — delete messages after retrieving
  • --dont-delete or -l — do not delete messages after retrieving
  • --all or -a — retrieve all messages
  • --new or -n — retrieve only new (unseen) messages

For instance, if you want to retrieve mail from two different mail accounts, create a getmail rc file for each of them (named, say, getmailrc-account1 and getmailrc-account2) and put them in ~/.getmail/ . Then run getmail as follows:

$ getmail --rcfile getmailrc-account1 --rcfile getmailrc-account2

If those files were located in a directory other than the default, and you wanted to use that directory for storing the data files as well, you could run getmail as follows:

$ getmail --getmaildir /path/to/otherdir --rcfile getmailrc-account1 --rcfile getmailrc-account2

Using getmail as an MDA

getmail includes helper scripts which allow you to use it to deliver mail from other programs to maildirs or mboxrd files.

Using the getmail_maildir MDA

The getmail_maildir script can be used as an MDA from other programs to deliver mail to maildirs. It reads the mail message from stdin, and delivers it to a maildir path provided as an argument on the commandline. This path must (after expansion by the shell, if applicable) start with a dot or slash and end with a slash.

getmail_maildir uses the contents of the SENDER environment variable to construct a Return-Path: header field and the contents of the RECIPIENT environment variable to construct a Delivered-To: header field at the top of the message.

getmail_maildir also accepts the options --verbose or -v which tell it to print a status message on success. The default is to operate silently unless an error occurs.

Example

You could deliver a message to a maildir named Maildir located in your home directory by running the following command with the message on stdin:

$ getmail_maildir $HOME/Maildir/

Using the getmail_mbox MDA

The getmail_mbox script can be used as an MDA from other programs to deliver mail to mboxrd-format mbox files. It reads the mail message from stdin, and delivers it to an mbox path provided as an argument on the commandline. This path must (after expansion by the shell, if applicable) start with a dot or slash and not end with a slash.

getmail_maildir uses the contents of the SENDER environment variable to construct a Return-Path: header field and mbox From_ line and the contents of the RECIPIENT environment variable to construct a Delivered-To: header field at the top of the message.

getmail_mbox also accepts the options --verbose or -v which tell it to print a status message on success. The default is to operate silently unless an error occurs.

Example

You could deliver a message to an mboxrd-format mbox file named inbox located in a directory named mail in your home directory by running the following command with the message on stdin:

$ getmail_mbox $HOME/mail/inbox

Using getmail_fetch to retrieve mail from scripts

getmail includes the getmail_fetch helper script, which allows you to retrieve mail from a POP3 server without the use of a configuration file. It is primarily intended for use in automated or scripted environments, but can be used to retrieve mail normally.

See the getmail_fetch manual page for details on the use of getmail_fetch.

getmail-4.43.0/docs/getmail.10000644000175000017500000000452212206535266017264 0ustar charlesccharlesc00000000000000.TH getmail "1" "August 2004" "getmail 4" "User Commands" .SH NAME getmail \- retrieve messages from one or more POP3, IMAP4, or SDPS mailboxes and deliver to a maildir, mboxrd-format mbox file, or external MDA .SH SYNOPSIS .B getmail [\fIOPTION\fR] ... .SH DESCRIPTION .\" Add any additional description here .PP getmail can retrieve messages from POP3, IMAP4, and SDPS mailboxes. SSL-wrapped POP3 and IMAP are also supported. Single-user and domain (multi\-drop) mailboxes are supported. .PP getmail has built-in support for delivering to maildirs, mboxrd-format mbox files, and external MDAs. .SH OPTIONS .TP \fB\-\-version\fR show program's version number and exit .TP \fB\-h\fR, \fB\-\-help\fR show a short usage summary and exit .TP \fB\-g\fIDIR\fR, \fB\-\-getmaildir\fR=\fIDIR\fR look in DIR for config/data files .TP \fB\-r\fIFILE\fR, \fB\-\-rcfile\fR=\fIFILE\fR load configuration from FILE (may be given multiple times) .TP \fB\-\-dump\fR dump configuration and exit (debugging) .TP \fB\-\-trace\fR print extended trace information (extremely verbose) .TP \fB\-i\fIFOLDER\fR, \fB\-\-idle\fR=\fIFOLDER\fR maintain connection and listen for new messages in \fR\fIFOLDER\fI\fR. This flag will only work if a single rc file is given, and will only work on IMAP connections where the server supports IMAP4 IDLE (RFC 2177). .PP The following options override any in the configuration file(s). .TP \fB\-v\fR, \fB\-\-verbose\fR operate more verbosely (can be given multiple times) .TP \fB\-q\fR, \fB\-\-quiet\fR do not print informational messages .TP \fB\-d\fR, \fB\-\-delete\fR delete messages from server after retrieving .TP \fB\-l\fR, \fB\-\-dont\-delete\fR do not delete messages from server after retrieving .TP \fB\-a\fR, \fB\-\-all\fR retrieve all messages .TP \fB\-n\fR, \fB\-\-new\fR retrieve only unread messages .SH AUTHOR Written by Charles Cazabon. .SH "REPORTING BUGS" Report bugs to . .PP \fBIMPORTANT:\fR include the output of .B getmail [options] --dump\fR with any bug report. .SH COPYRIGHT Copyright \(co 1998-2009 Charles Cazabon .br This is free software; see the file COPYING for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. .SH "SEE ALSO" The full documentation for .B getmail is maintained in HTML and plaintext formats. See the included files for details. getmail-4.43.0/docs/troubleshooting.txt0000644000175000017500000000000012206542267021532 0ustar charlesccharlesc00000000000000getmail-4.43.0/docs/documentation.html0000644000175000017500000010147712206542267021325 0ustar charlesccharlesc00000000000000 getmail documentation (version 4)

getmail documentation

This is the documentation for getmail version 4. Version 4 includes numerous changes from version 3.x; if you are using getmail version 3, please refer to the documentation included with that version of the software.

getmail is Copyright © 1998-2009 Charles Cazabon. <charlesc-getmail @ pyropus.ca>

getmail is licensed under the GNU General Public License version 2 (only). If you wish to obtain a license to distribute getmail under other terms, please contact me directly.

Table of Contents

Features

getmail is a mail retriever designed to allow you to get your mail from one or more mail accounts on various mail servers to your local machine for reading with a minimum of fuss. getmail is designed to be secure, flexible, reliable, and easy-to-use. getmail is designed to replace other mail retrievers such as fetchmail.

getmail version 4 includes the following features:

  • simple to install, configure, and use
  • retrieve virtually any mail
    • support for accessing mailboxes with the following protocols:
      • POP3
      • POP3-over-SSL
      • IMAP4
      • IMAP4-over-SSL
      • SDPS (Demon UK's extensions to POP3)
    • support for single-user and domain mailboxes
    • retrieve mail from an unlimited number of mailboxes and servers
    • can remember which mail it has already retrieved, and can be set to only download new messages
  • support for message filtering, classification, and annotation by external programs like spam filters and anti-virus programs
  • support for delivering messages to different destinations based on the message recipient
  • reliability
    • native safe and reliable delivery support for maildirs and mboxrd files, in addition to delivery through arbitrary external message delivery agents (MDAs)
    • does not destroy information by rewriting mail headers
    • does not cause mail loops by doing SMTP injection, and therefore does not require that you run an MTA (like qmail or sendmail) on your host
  • written in Python, and therefore easy to extend or customize
    • a flexible, extensible architecture so that support for new mail access protocols, message filtering operations, or destination types can be easily added
    • cross-platform operation; getmail 4 should work on Unix/Linux, Macintosh, and other platforms. Windows support available under the free Cygwin package.
  • winner of various software awards, including DaveCentral's "Best of Linux"

Differences from previous versions

getmail version 4 has been completely rewritten. It is designed to closely mimic the interface and user experience of getmail version 3, but the new architecture necessitates some differences you will notice:

  • the getmail rc file (configuration file) format has changed. If you are upgrading from version 3, you will need to write a new configuration file based on the contents of your old one. The new file format resembles the old in many ways. Each account you retrieve mail from will require a separate rc file, but getmail can operate with multiple rc files simultaneously if you wish to retrieve mail from multiple accounts.
  • support for protocols other than POP3/SDPS. IMAP support is now included, and other protocols can be added with relative ease.
  • support for SSL-encrypted protocols. The included POP3 and IMAP retriever classes are complemented by SSL-enabled counterparts.
  • messages can be filtered or annotated by external programs like spam filters and anti-Microsoft-worm programs. Filters can cause messages to be dropped completely.
  • a flexible, extensible architecture. Additional classes for handling new mail protocols, filter types, or destination mailstores can be added without needing to modify the main script at all. Feel free to contact me if you need a custom retriever, filter, or destination class written, or if you want commercial support for getmail.

Requirements

getmail version 4 requires Python version 2.3.3 or later. If you have only an earlier version of Python available, you can install the latest version without disturbing your current version, or use getmail version 3, which requires only Python version 1.5.2 or later.

At the time of this writing, the current stable version of Python is 2.3.4. You can download that version from the page at http://www.python.org/2.3.4/ . Binary packages are available for RPM-based Linux systems, or building Python from source is typically as easy as unpacking the source tarball, and running the following commands:

./configure
make
make install

Since the above was written, Python 2.4 has been released. getmail 4 will work with that version of Python as well.

getmail 4 also requires that servers uniquely identify the messages they provide (via the UIDL command) to getmail for full functionality. Certain very old or broken POP3 servers may not be capable of this (I have had only one report of such problems from among the tens of thousands of people who have downloaded getmail 4 from my website and from other archives), or may not implement the UIDL command at all, and limited support is available for such servers via the BrokenUIDLPOP3Retriever and BrokenUIDLPOP3SSLRetriever retriever classes.

Obtaining getmail

Download getmail 4 from the official website main page at http://pyropus.ca/software/getmail/ .

Installing getmail

For the impatient

Installing getmail is very easy; just download the tarball distribution, unpack it, change into the directory it unpacks into, and run this command:

$ python setup.py install

That's all there is to it. 99.9% of users don't need a special package/port/etc. If you'd like more details on install options, keep reading.

Full installation instructions

Once you have downloaded or otherwise obtained getmail, unpack it. On GNU-ish Unix-like systems, this means:

$ tar xzf getmail-version.tar.gz

On Macintosh systems, use a Zip-type archiver program to unpack the tarball.

On SystemV-like Unix systems, you may instead need to break this down into two steps:

$ gunzip getmail-version.tar.gz
$ tar xf getmail-version.tar

Then, change into the extracted getmail directory and start the build process. The easiest installation method is to use the included setup.py Python distutils script to build and install getmail directly. Alternatively, you can build a binary package (i.e., an RPM or similar managed software package) for your system from the source package and install the resulting package, but the Python distutils support for this is spotty at present.

Installing from the RPM

If you downloaded the RPM, you should be able to install it with the following command:

$ rpm -ihv getmail-version-release.noarch.rpm

Installing directly from the source

To build and install directly from the included source, follow these steps.

$ cd getmail-version
$ python setup.py build

When that completes in a few seconds, become root and then install the software. You can install in the default location, or specify an alternate location to install the software, or specify alternate directories for only part of the package.

Installing in the default location

To install in the default location, become user root and install with the following commands:

$ su
enter root password
# python setup.py install

This will, by default, install files into subdirectories under the directory prefix, which is the directory that your Python installation was configured to install under (typically /usr/local/ or /usr/, but other values are sometimes used):

  • the scripts getmail, getmail_fetch, getmail_maildir, and getmail_mbox will be installed under prefix/bin/
  • the Python package getmailcore (which implements all the protocol–, filter–, and destination-specific code for getmail, plus various other bits) will be installed under the site-specific packages directory of your Python library directory. This directory is prefix/lib/python-python-version/site-packages/.
  • The documentation directory getmail-getmail-version will be installed under prefix/doc/
  • The manual pages for the four scripts will be installed under prefix/man/

You can see a list of the default installation locations by running:

# python setup.py install --show-default-install-dirs

Installing under an alternate prefix directory

You can specify an alternate prefix directory by supplying the --prefix option to the install command, like this:

# python setup.py install --prefix=path

This will install the various parts of the package in subdirectories like in the default installation (see the section Installing in the default location above), but under your specified prefix directory. These alternate installations allow you to install the software without root privileges (say, by installing under $HOME/). Note, however, that the getmailcore package will not be in the default Python module search path if you do this; see the section Installing the getmailcore package in a non-standard location if you use this option.

Installing parts of the package to alternate directories

If you only want to change the directory for some of the components, use the following options:

  • --install-lib=path specifies the directory the getmailcore package is installed under (i.e., it will be installed as path/getmailcore ). See the section Installing the getmailcore package in a non-standard location if you use this option.
  • --install-scripts=path specifies the directory the four scripts are installed under (i.e., they will be installed directly in path/ ).
  • --install-data=path specifies the directory the documentation is installed under (i.e., the HTML and plaintext documentation will be installed in the directory path/doc/getmail-getmail-version/, and the man(1) pages will be installed in path/man/man1/.

For example, if your Python installation is located under /usr/ because it was installed as part of your OS, but you would like the getmail scripts installed into /usr/local/bin/ instead of /usr/bin/, while still letting the getmailcore package be installed under /usr/lib/python-python-version/site-packages/, and the documentation and man pages under /usr/doc/ and /usr/man/ you could use this command to install:

# python setup.py --install-scripts=/usr/local/bin/

If you also wanted to locate the documentation and man pages under /usr/local/ but still install the getmailcore package in the default /usr/lib/python-python-version/site-packages/, you would instead use this command to install:

# python setup.py --install-scripts=/usr/local/bin/ --install-data=/usr/local/

Installing the getmailcore package in a non-standard location

Note: if you use one of the above methods to install the getmailcore package into a directory other than the default, the four scripts (getmail, getmail_fetch, getmail_maildir, and getmail_mbox) will almost certainly be unable to locate the required files from the getmailcore package, because they will not be in a directory in the standard Python module search path. You will need to do one of the following to make those files available to the scripts:

  • set the environment variable PYTHONPATH to tell Python where to find the appropriate modules. See the documentation at the Python.org website for details.

    Note that setting PYTHONPATH in $HOME/.profile (or equivalent) is not sufficient -- for instance, cron runs jobs in a simpler environment, ignoring $HOME/.profile, and getmail would therefore fail when run as a user cron job. It is strongly recommended that you install the Python library files in the site-packages directory which Python provides for exactly this reason.

  • modify the scripts to explicitly tell Python where you've installed them. Insert a line like this:
    sys.path.append('/path/to/installation-directory')
    
    containing the path to the directory you installed the getmailcore directory in, somewhere below the line which reads
    import sys
    
    and before the first line which references getmailcore .

Building a binary package from the source

To build a binary package from the included source, run the following command from inside the unpacked getmail source.

$ cd getmail-version
$ python setup.py bdist --format=package-format

The useful allowed values for package-format are:

  • rpm — build a .noarch.rpm file which can then be installed with the rpm package manager.
  • pkgtool — build a package for the Sun Solaris pkgtool package manager.
  • sdux — build a package for the HP/UX swinstall software installer.

Ideally, if you use this method, it will result in a "built distribution" binary package in a subdirectory named dist which can then be installed using the appropriate system-specific tool. If you have problems with this process, please do not ask me for assistance; ask your OS vendor or the comp.lang.python newsgroup. The install-directory-from-source process above is the only one I can support, and it should work on all platforms.

You can discuss issues with building binary packages on the getmail users' mailing list.

getmail mailing lists

getmail-users' mailing list

A mailing list has been set up to discuss getmail. Only subscribers may post to the list.

The list is available for free getmail support from me and other users, for discussions of bugs, configuration issues, documentation, and other technical issues related to getmail.

How to subscribe

To subscribe to the list, send a blank email to <getmail-subscribe @ lists.pyropus.ca> and follow the instructions in the message you receive. Read and save the "welcome" message you receive when you subscribe; it contains valuable instructions about how to use the list.

How to unsubscribe

To un-subscribe from the list, send a blank email from the same address you subscribed with to <getmail-unsubscribe @ lists.pyropus.ca> and follow the instructions in the message you receive.

How to post

Once you have subscribed to the list, you may post messages to the list by sending them to <getmail @ lists.pyropus.ca>. Complete instructions for using the list are sent to you when you subscribe.

The list allows plaintext message bodies and plaintext attachments. Do not attempt to send binary files (gzip, etc), HTML, or other types, as they will be stripped from your message.

Note: please ensure you have read the documentation and Frequently Asked Questions, and browsed/searched the mailing list archives before posting a question to the mailing list.

Archives of the getmail-users' mailing list

There are browsable archives of the list at http://marc.theaimsgroup.com/?l=getmail&r=1&w=2 and http://news.gmane.org/gmane.mail.getmail.user . The GMANE getmail users' archive is also available via NNTP if you prefer to read it with a newsreader, rather than a web browser.

Notes on the getmail-users' mailing list

When subscribing to the getmail users' mailing list, please note the following:

  • The mailing list software does not, and will not munge the Reply-To: header of list messages. I encourage you to read and post to the list using a good MUA that properly supports reply-to-list and reply-to-author functionality. If your MUA lacks a reply-to-list function, you'll need to manually ensure your followup messages to the the list are actually directed to the list submission address.
  • The mailing list software does not munge the Subject: header of list messages, so don't look for "[getmail-users]" or anything like that. If you want your MUA to recognize list messages, there are a number of header fields added to allow it to do so.
  • Subscribing and unsubscribing from the list are both secure and completely automatic. When you try to do either, the list manager software will send you a special message you have to reply to to finish the operation; this prevents others from subscribing you to or unsubscribing you from the list without your permission.
  • You must be a list subscriber to post messages to the list.

Announcements List

If you only want to be notified of new releases of getmail, an announce-only list has been set up. The list is very low-volume; you can expect to receive only a small number of messages per month.

All announcements are sent to both lists, so there is no need to subscribe to the announcements list if you are on the discussion list.

How to subscribe

To subscribe to the list, send a blank email to <getmail-announce-subscribe @ lists.pyropus.ca> and follow the instructions in the message you receive. Read and save the "welcome" message you receive when you subscribe; it contains valuable instructions about how to use the list.

How to unsubscribe

To un-subscribe from the list, send a blank email from the same address you subscribed with to <getmail-announce-unsubscribe @ lists.pyropus.ca> and follow the instructions in the message you receive.

How to post

You cannot post messages directly to the announcements list. If you feel you have an announcement regarding getmail which should be distributed, send it to me and request that I send it to the announcements list.

Archives of the getmail announcements mailing list

There is an archive of the announcements list at http://news.gmane.org/gmane.mail.getmail.announce . The GMANE getmail announcements archive is also available via NNTP if you prefer to read it with a newsreader, rather than a web browser.

getmail-4.43.0/docs/getmailrc-examples0000644000175000017500000001422611236651513021264 0ustar charlesccharlesc00000000000000# # This file contains various examples of configuration sections to use # in your getmail rc file. You need one file for each mail account you # want to retrieve mail from. These files should be placed in your # getmail configuration/data directory (default: $HOME/.getmail/). # If you only need one rc file, name it getmailrc in that directory, # and you won't need to supply any commandline options to run getmail. # # # Example 1: simplest case of retrieving mail from one POP3 server and # storing all messages in a maildir. # [retriever] type = SimplePOP3Retriever server = pop.example.net username = jeff.plotzky password = mailpassword [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 2: same as (1), but operate quietly, delete messages from # the server after retrieving them, and log getmail's actions (in detail) # to a file. # [options] verbose = 0 delete = true message_log = ~/.getmail/log message_log_verbose = true [retriever] type = SimplePOP3Retriever server = pop.example.net username = jeff.plotzky password = mailpassword [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 3: same as (1), but the mail account is accessed via IMAP4 instead # of POP3. # [retriever] type = SimpleIMAPRetriever server = mail.example.net username = jeff.plotzky password = mailpassword [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 4: same as (3), but retrieve mail from the INBOX, INBOX.spam, and # mailing-lists.getmail-users mail folders. # [retriever] type = SimpleIMAPRetriever server = mail.example.net username = jeff.plotzky password = mailpassword mailboxes = ("INBOX", "INBOX.spam", "mailing-lists.getmail-users") [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 5: same as (3), but move messages to the mail folder "sent-mail" # after retrieving them. Note that you do this by setting delete and # move_on_delete options. # [options] delete = true [retriever] type = SimpleIMAPRetriever server = mail.example.net username = jeff.plotzky password = mailpassword move_on_delete = sent-mail [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 6: same as (1), but deliver the messages to an mboxrd-format mbox # file as user "jeffp". # [retriever] type = SimplePOP3Retriever server = pop.example.net username = jeff.plotzky password = mailpassword [destination] type = Mboxrd path = ~jeffp/Mail/inbox user = jeffp # # Example 7: same as (1), but deliver the messages through an external MDA # which takes several arguments. # [retriever] type = SimplePOP3Retriever server = pop.example.net username = jeff.plotzky password = mailpassword [destination] type = MDA_external path = /usr/local/bin/my-mda arguments = ("--message-from-stdin", "--scan-message", "--to-maildir", "~jeffp/Maildir/") # # Example 8: retrieve mail from a corporate POP3-SSL domain mailbox, # sort messages for several local users and deliver to maildirs in their # home directories (except Sam, who likes mbox files, and Christina, who # uses procmail for further sorting), and deliver all other mail to # Joe, who serves as postmaster for the company. Sam also needs # to receive mail for "sam1", "sam23", etc, so we use a regular expression # matching "sam" plus zero or more decimal digits. # [retriever] type = MultidropPOP3SSLRetriever server = pop.example.net username = companylogin password = mailpassword # Our domain mailbox mailhost records the envelope recipient address in a # new Delivered-To: header field at the top of the message. envelope_recipient = delivered-to:1 [destination] type = MultiSorter default = [postmaster] locals = ( ("jeffk@company.example.net", "[jeff]"), ("martinh@company.example.net", "[martin]"), (r"sam\D*@company.example.net", "[sam]"), ("c.fellowes@company.example.net", "[christina-procmail]") ) [postmaster] type = Maildir path = ~joe/Mail/postmaster/ user = joe [jeff] type = Maildir path = ~jeffp/Maildir/ user = jeffp [martin] type = Maildir path = ~martinh/Maildir/ user = martinh [sam] type = Mboxrd path = ~sam/Mail/inbox user = sam [christina-procmail] type = MDA_external path = /usr/local/bin/procmail # procmail requires either that the message starts with an mboxrd-style # "From " line (which getmail can generate by setting "unixfrom" to True), or # that the -f option is provided as below. arguments = ("-f", "%(sender)", "-m", "/home/christina/.procmailrc") user = christina # # Example 9: same as (3), but use SpamAssassin to filter out spam, # and ClamAV to filter out MS worms. # [retriever] type = SimpleIMAPRetriever server = mail.example.net username = jeff.plotzky password = mailpassword [filter-1] type = Filter_external path = /usr/local/bin/spamc [filter-2] type = Filter_classifier path = /usr/local/bin/clamscan arguments = ("--stdout", "--no-summary", "--mbox", "--infected", "-") exitcodes_drop = (1,) [destination] type = Maildir path = ~jeffp/Maildir/ # # Example 10: same as (3), but deliver all mail to two different local # mailboxes. # [retriever] type = SimpleIMAPRetriever server = mail.example.net username = jeff.plotzky password = mailpassword [destination] type = MultiDestination destinations = ( "~jeff/Maildir/", "/var/log/mail-archive/current", ) # # Example 11: retrieve mail from a simple (non-multidrop) POP3 mailbox. # Then extract addresses from the message header (see documentation for which # fields are examined), and deliver mail containing the address # to ~/Mail/lists/list1/, mail containing the # address to ~/Mail/lists/list2/, # mail containing the address to ~/Mail/other/, # and all other mail gets delivered through the external MDA program # "my-mda" with some default arguments. # [retriever] type = SimplePOP3Retriever server = pop.example.net username = jeff.plotzky password = mailpassword [destination] type = MultiGuesser default = [my-mda] locals = ( ("list1@domain.example.net", "~/Mail/lists/list1/"), ("list2@otherdomain.example.com", "~/Mail/lists/list2/"), ("othername@example.org", "~/Mail/other/"), ) [my-mda] type = MDA_external path = /path/to/my-mda arguments = ("-f", "%(sender)", "${HOME}/.mymdarc") getmail-4.43.0/docs/THANKS0000644000175000017500000000127211131473115016460 0ustar charlesccharlesc00000000000000I would like to thank the following individuals and organizations for their gracious contributions to the development of getmail version 4. getmail 4 sponsors ------------------ Texas Communications getmail version 4 alpha testers ------------------------------- Andrew A. Raines Julian Gomez Frankye Fattarelli Thomas Schwinge Jody J. Hietpas Frank Benkstein Shantanu Kulkarni and others ... other thanks ------------ Clemens Hermann, for ... well, lots Fredrik Steen, maintainer of the Debian packages of getmail Earlier versions of getmail also benefitted from the contributions of others, including the following: getmail sponsors ---------------- Klinikum Landsberg of Germany getmail-4.43.0/docs/faq.txt0000644000175000017500000000000012206542267017052 0ustar charlesccharlesc00000000000000getmail-4.43.0/docs/BUGS0000644000175000017500000000407211131473115016231 0ustar charlesccharlesc00000000000000Known bugs ========== None. Reporting bugs ============== Regarding packages/ports/emerges/etc ------------------------------------ CRITICAL: if you are using a "package"/"emerge"/"port" of getmail, DO NOT REPORT PROBLEMS TO ME if that package makes changes to the original getmail code; if the package includes patches or other changes to the getmail code, report the problem to your OS vendor or packager -- only they can help you with problems introduced by their packaging of getmail. If you're not sure whether the package you're using is an unmodified version of getmail, I can help you determine that. Make sure to include a URL to the package you installed in your email. To packagers: feel free to contact me regarding your packaging of getmail, and any issues you might have with getmail as I distribute it. If you think changes to getmail are necessary for your package, let's discuss it -- maybe the changes you want would be good for all getmail users, and I can integrate them into the trunk. If you *do* distribute a modified version of getmail, please make a good-faith effort at ensuring that anyone using your package knows that the version you distribute differs from mine, and the reasons for the changes. Before reporting a problem -------------------------- If you want to report a getmail problem to me, please ensure -you are running the latest version of getmail from the website at http://pyropus.ca/software/getmail/ -you have installed getmail from the .tar.gz file I provide Then follow the rest of the instructions below. How to report a problem with getmail ------------------------------------ IMPORTANT: Please include the output of getmail [your usual options] --dump and a copy of your getmail rc file(s) (with password masked) with any bug report. If you find a bug in getmail, please either join the getmail users' mailing list and report it there (preferred), or send it to me at . You can join the getmail users' mailing list by sending a (blank) message to . getmail-4.43.0/docs/TODO0000644000175000017500000000240111131473115016230 0ustar charlesccharlesc00000000000000TODO ==== -additional documentation on extending getmail's capabilities DONE ==== -distutils setup script -convert POP3-based classes to use the MixIn classes for SSL and non-SSL connections. -fix every-other-message problem with delete -break retrievers.py into smaller units -Multidrop IMAP support -make delete work with previously retrieved messages, and add delete_after -max_message_size -fix --quiet (first two lines output) -max_messages_per_session -make output cleaner when hitting max_messages_per_session -message_log -message_log_syslog -add "user" and "group" parameters to destinations and filters that run in separate processes. -add allow_root_commands -allow command delivery in MultiSorter -add specified-section for program destinations in MultiSorter -port getmail_maildir and getmail_mbox wrappers -finish/clean up qmail-local destination -do something about the #! line in the scripts? distutils now does this. -finish documentation -man pages -flush out getmailrc-example -Filter_TMDA filter class -RPM package -rewrite the distutils setup script to properly allow the user to specify alternate installation directories for types of files -remove the raise statements in the outermost exception handlers from the main script (when leaving beta) getmail-4.43.0/docs/configuration.txt0000644000175000017500000000000012206542267021152 0ustar charlesccharlesc00000000000000getmail-4.43.0/MANIFEST.in0000644000175000017500000000057011131473115016353 0ustar charlesccharlesc00000000000000include README include MANIFEST.in include getmail.spec include docs/*.txt include docs/*.html include docs/BUGS include docs/CHANGELOG include docs/COPYING include docs/THANKS include docs/TODO include docs/getmaildocs.css include docs/getmailrc-examples include docs/*.1 include getmail getmail_fetch getmail_maildir getmail_mbox setup.py setup.cfg include getmailcore/*.py getmail-4.43.0/getmail.spec0000644000175000017500000002164612206542270017125 0ustar charlesccharlesc00000000000000%define python_sitelib %(%{__python} -c 'from distutils import sysconfig; print sysconfig.get_python_lib()') Summary: POP3 mail retriever with reliable Maildir delivery Name: getmail Version: 4.43.0 Release: 1 License: GPL Group: Applications/Internet URL: http://pyropus.ca/software/getmail/ Source: http://pyropus.ca/software/getmail/old-versions/getmail-%{version}.tar.gz Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildArch: noarch BuildRequires: python-devel >= 2.3.3 Requires: python >= 2.3.3 %description getmail is intended as a simple replacement for fetchmail for those people who do not need its various and sundry configuration options, complexities, and bugs. It retrieves mail from one or more POP3 servers for one or more email accounts, and reliably delivers into a Maildir specified on a per-account basis. It can also deliver into mbox files, although this should not be attempted over NFS. getmail is written entirely in python. %prep %setup -q %build %install %{__rm} -rf %{buildroot} %{__python} setup.py install --root="%{buildroot}" %clean %{__rm} -rf %{buildroot} %files %defattr(-, root, root, 0755) %doc docs/BUGS docs/CHANGELOG docs/COPYING docs/THANKS docs/TODO %doc docs/configuration.html docs/configuration.txt docs/documentation.html %doc docs/documentation.txt docs/faq.html docs/faq.txt docs/getmaildocs.css %doc docs/getmailrc-examples docs/troubleshooting.html docs/troubleshooting.txt %doc %{_mandir}/man1/getmail.1* %doc %{_mandir}/man1/getmail_fetch.1* %doc %{_mandir}/man1/getmail_maildir.1* %doc %{_mandir}/man1/getmail_mbox.1* %{_bindir}/getmail %{_bindir}/getmail_fetch %{_bindir}/getmail_maildir %{_bindir}/getmail_mbox %{python_sitelib}/getmailcore/ %changelog * Sun Aug 25 2013 Charles Cazabon -update to version 4.43.0 * Sun Aug 25 2013 Charles Cazabon -update to version 4.43.0 * Sat Aug 03 2013 Charles Cazabon -update to version 4.42.0 * Sat Aug 03 2013 Charles Cazabon -update to version 4.42.0 * Sat Aug 03 2013 Charles Cazabon -update to version 4.42.0 * Sat Aug 03 2013 Charles Cazabon -update to version 4.42.0 * Sat Aug 03 2013 Charles Cazabon -update to version 4.42.0 * Sun May 26 2013 Charles Cazabon -update to version 4.41.0 * Fri May 10 2013 Charles Cazabon -update to version 4.40.3 * Wed May 08 2013 Charles Cazabon -update to version 4.40.2 * Mon Apr 22 2013 Charles Cazabon -update to version 4.40.1 * Sun Apr 21 2013 Charles Cazabon -update to version 4.40.0 * Sun Mar 10 2013 Charles Cazabon -update to version 4.39.1 * Fri Feb 22 2013 Charles Cazabon -update to version 4.39.0 * Fri Feb 22 2013 Charles Cazabon -update to version 4.39.0 * Fri Feb 22 2013 Charles Cazabon -update to version 4.39.0 * Sat Feb 16 2013 Charles Cazabon -update to version 4.38.0 * Sat Feb 16 2013 Charles Cazabon -update to version 4.38.0 * Sun Jan 27 2013 Charles Cazabon -update to version 4.37.0 * Sun Jan 27 2013 Charles Cazabon -update to version 4.37.0 * Sat Dec 15 2012 Charles Cazabon -update to version 4.36.0 * Wed Oct 24 2012 Charles Cazabon -update to version 4.35.0 * Sat Sep 08 2012 Charles Cazabon -update to version 4.34.0 * Tue Aug 07 2012 Charles Cazabon -update to version 4.33.0 * Fri Jul 06 2012 Charles Cazabon -update to version 4.32.0 * Thu Jul 05 2012 Charles Cazabon -update to version 4.31.0 * Wed Jun 27 2012 Charles Cazabon -update to version 4.30.2 * Thu Jun 21 2012 Charles Cazabon -update to version 4.30.1 * Thu Jun 21 2012 Charles Cazabon -update to version 4.30.1 * Thu Jun 21 2012 Charles Cazabon -update to version 4.30.0 * Thu Jun 21 2012 Charles Cazabon -update to version 4.30.0 * Thu Jun 21 2012 Charles Cazabon -update to version 4.30.0 * Tue Jun 19 2012 Charles Cazabon -update to version 4.29.0 * Tue Jun 19 2012 Charles Cazabon -update to version 4.29.0 * Sun May 20 2012 Charles Cazabon -update to version 4.27.0 * Sun May 20 2012 Charles Cazabon -update to version 4.27.0 * Sat Apr 14 2012 Charles Cazabon -update to version 4.26.0 * Wed Feb 01 2012 Charles Cazabon -update to version 4.25.0 * Tue Jan 03 2012 Charles Cazabon -update to version 4.25.0 * Tue Jan 03 2012 Charles Cazabon -update to version 4.25.0 * Sun Dec 11 2011 Charles Cazabon -update to version 4.24.0 * Sun Nov 20 2011 Charles Cazabon -update to version 4.23.0 * Sat Nov 12 2011 Charles Cazabon -update to version 4.22.2 * Fri Sep 30 2011 Charles Cazabon -update to version 4.22.1 * Sun Sep 25 2011 Charles Cazabon -update to version 4.22.0 * Fri Sep 23 2011 Charles Cazabon -update to version 4.21.0 * Sat Jul 16 2011 Charles Cazabon -update to version 4.20.4 * Mon May 30 2011 Charles Cazabon -update to version 4.20.3 * Mon May 30 2011 Charles Cazabon -update to version 4.20.3 * Sat Apr 09 2011 Charles Cazabon -update to version 4.20.2 * Thu Apr 07 2011 Charles Cazabon -update to version 4.20.1 * Tue Jun 29 2010 Charles Cazabon -update to version 4.20.0 * Tue Jun 29 2010 Charles Cazabon -update to version 4.19.0 * Sat Jun 26 2010 Charles Cazabon -update to version 4.18.0 * Sat Jun 26 2010 Charles Cazabon -update to version 4.18.0 * Fri Apr 30 2010 Charles Cazabon -update to version 4.17.0 * Tue Jan 05 2010 Charles Cazabon -update to version 4.16.0 * Wed Dec 02 2009 Charles Cazabon -update to version 4.15.0 * Mon Nov 23 2009 Charles Cazabon -update to version 4.14.0 * Tue Oct 13 2009 Charles Cazabon -update to version 4.13.0 * Tue Oct 13 2009 Charles Cazabon -update to version 4.12.0 * Wed Oct 07 2009 Charles Cazabon -update to version 4.12.0 * Sat Aug 08 2009 Charles Cazabon -update to version 4.11.0 * Thu Aug 06 2009 Charles Cazabon -update to version 4.10.0 * Thu Jul 16 2009 Charles Cazabon -update to version 4.9.2 * Mon Jun 01 2009 Charles Cazabon -update to version 4.9.1 * Sun Apr 05 2009 Charles Cazabon -update to version 4.9.0 * Fri Sep 26 2008 Charles Cazabon -update to version 4.8.4 * Mon Aug 11 2008 Charles Cazabon -update to version 4.8.3 * Sat Aug 02 2008 Charles Cazabon -update to version 4.8.2 * Wed Mar 26 2008 Charles Cazabon -update to version 4.8.1 * Tue Feb 19 2008 Charles Cazabon -update to version 4.8.0 * Tue Feb 05 2008 Charles Cazabon -update to version 4.7.8 * Mon Aug 13 2007 Dries Verachtert - 4.7.6-1 - Updated to release 4.7.6. * Thu Jun 07 2007 Dries Verachtert - 4.7.5-1 - Updated to release 4.7.5. * Wed May 09 2007 Dries Verachtert - 4.7.4-1 - Updated to release 4.7.4. * Mon Mar 19 2007 Dries Verachtert - 4.7.3-1 - Updated to release 4.7.3. * Sun Mar 04 2007 Dag Wieers - 4.7.2-1 - Initial package. (using DAR) getmail-4.43.0/setup.cfg0000644000175000017500000000112711131473115016435 0ustar charlesccharlesc00000000000000[bdist_rpm] release = 1 vendor = Charles Cazabon packager = Charles Cazabon #doc_files = # docs/BUGS # docs/CHANGELOG # docs/configuration.html # docs/configuration.txt # docs/COPYING # docs/documentation.html # docs/documentation.txt # docs/faq.html # docs/faq.txt # docs/getmaildocs.css # docs/getmailrc-examples # README # docs/THANKS # docs/TODO # docs/troubleshooting.html # docs/troubleshooting.txt binary_only = True group = Applications/Email release = 1 requires = python >= 2.3.3 getmail-4.43.0/setup.py0000644000175000017500000001014011131473115016321 0ustar charlesccharlesc00000000000000#!/usr/bin/env python import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 4 requires Python version 2.3.3 or later') import os.path from distutils.core import setup import distutils.sysconfig from getmailcore import __version__ # # distutils doesn't seem to handle documentation files specially; they're # just "data" files. The problem is, there's no easy way to say "install # the doc files under /doc/-/ (obeying any # --home= or --prefix=, which would be "normal". # This hacks around this limitation. # prefix = distutils.sysconfig.get_config_var('prefix') datadir = None args = sys.argv[1:] for (pos, arg) in enumerate(args): # hack hack hack if arg.startswith('--prefix='): # hack hack hack hack hack prefix = arg.split('=', 1)[1] elif arg == '--prefix': # hack hack hack hack hack hack hack prefix = args[pos + 1] elif arg.startswith('--install-data='): # hack hack hack hack hack datadir = arg.split('=', 1)[1] elif arg == '--install-data': # hack hack hack hack hack hack hack datadir = args[pos + 1] GETMAILDOCDIR = os.path.join( datadir or prefix, 'share', 'doc', 'getmail-%s' % __version__ ) GETMAILMANDIR = os.path.join( datadir or prefix, 'share', 'man', 'man1' ) if '--show-default-install-dirs' in args: print 'Default installation directories:' print ' scripts : %s' % distutils.sysconfig.get_config_var('BINDIR') print ' Python modules : %s' % os.path.join(distutils.sysconfig.get_config_var('LIBP'), 'site-packages') print ' documentation : %s' % GETMAILDOCDIR print ' man(1) pages : %s' % GETMAILMANDIR raise SystemExit() setup( name='getmail', version=__version__, description='a mail retrieval, sorting, and delivering system', long_description=('getmail is a multi-protocol mail retrieval system with' 'support for simple and domain POP3 and IMAP4 mailboxes, domain SDPS ' 'mailboxes, POP3-over-SSL and IMAP-over-SSL, mail sorting, message ' 'filtering, and delivery to Maildirs, Mboxrd files, external MDAs, and ' 'other advanced features.'), author='Charles Cazabon', author_email='charlesc-getmail@pyropus.ca', license='GNU GPL version 2', url='http://pyropus.ca/software/getmail/', download_url='http://pyropus.ca/software/getmail/#download', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Operating System :: POSIX', 'Programming Language :: Python', 'Topic :: Communications :: Email', 'Topic :: Communications :: Email :: Filters', 'Topic :: Communications :: Email :: Post-Office :: IMAP', 'Topic :: Communications :: Email :: Post-Office :: POP3', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities', ], packages=[ 'getmailcore' ], scripts=[ 'getmail', 'getmail_fetch', 'getmail_maildir', 'getmail_mbox' ], data_files=[ (GETMAILDOCDIR, [ './README', './getmail.spec', 'docs/BUGS', 'docs/COPYING', 'docs/CHANGELOG', 'docs/TODO', 'docs/THANKS', 'docs/configuration.html', 'docs/configuration.txt', 'docs/documentation.html', 'docs/documentation.txt', 'docs/faq.html', 'docs/faq.txt', 'docs/getmaildocs.css', 'docs/getmailrc-examples', 'docs/troubleshooting.html', 'docs/troubleshooting.txt', ]), (GETMAILMANDIR, [ 'docs/getmail.1', 'docs/getmail_fetch.1', 'docs/getmail_maildir.1', 'docs/getmail_mbox.1', ]), ], )