EVOLUTION-MANAGER
Edit File: getmail
#!/usr/bin/python2 import sys if sys.hexversion < 0x2030300: raise ImportError('getmail version 5 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 import errno # 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', 'fingerprint', ) 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, run_command 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, 'fingerprint' : False, } ####################################### 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-2019 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.encode('utf-8')) 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.encode('utf-8'), 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 getmailLoginRefusedError, o: retriever.abort() log.error('%s: login refused error (%s)\n' % (configfile, o)) if options['logfile']: options['logfile'].write('login refused 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 retriever does not support IDLE\n') idle = False if idle and sys.version_info < (2, 5, 0): log.info('--idle requires Python 2.5 or higher\n') idle = False if idle and not errorexit: # 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. # Expunge and close the mailbox to prevent the same messages # being pulled again in some configurations. retriever.close_mailbox() try: idling = retriever.go_idle(idle) # Returned from idle retriever.set_new_timestamp() 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 except socket.error, o: if o.errno != errno.ECONNRESET: # Something unexpected happened raise #pass # Just exit after a reset connection. 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( '--fingerprint', dest='override_fingerprint', action='store_true', help='show SSL/TLS fingerprint and connection information' ) 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'], 'fingerprint' : defaults['fingerprint'], } # 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 not None: password = retriever.conf['password'] elif retriever.conf.get('password_command', None): # Retrieve from an arbitrary external command command = retriever.conf['password_command'][0] args = retriever.conf['password_command'][1:] (rc, stdout, stderr) = run_command(command, args) if stderr: log.warn( 'External password program "%s" wrote to stderr: %s', command, stderr ) if rc: # program exited nonzero raise getmailOperationError( 'External password program error (exited %d)' % rc ) else: password = stdout else: password = get_password( str(retriever), retriever.conf['username'], retriever.conf['server'], retriever.received_with, log ) 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', 'fingerprint'): 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()