#!/usr/local/bin/python2.3
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
options_bool = (
'read_all',
'delete',
'delivered_to',
'received',
'message_log_syslog',
)
options_int = (
'delete_after',
'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
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,
'max_message_size' : 0,
'max_messages_per_session' : 0,
'max_bytes_per_session' : 0,
'delivered_to' : True,
'received' : True,
'message_log' : None,
'message_log_syslog' : False,
'logfile' : None,
}
#######################################
def blurb():
log.info('getmail version %s\n' % __version__)
log.info('Copyright (C) 1998-2007 Charles Cazabon. Licensed under the'
' GNU GPL version 2.\n')
#######################################
def go(configs):
blurb()
summary = []
for (configfile, retriever, _filters, destination, options) in configs:
oplevel = options['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:
log.info('%s:\n' % retriever)
logline = 'Initializing %s:' % retriever
if options['logfile']:
options['logfile'].write(logline)
if options['message_log_syslog']:
syslog.syslog(syslog.LOG_INFO, logline)
retriever.initialize()
destination.retriever_info(retriever)
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:
msg = retriever.getmsg(msgid)
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:
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 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:
log.error('Delivery error (%s)\n' % o)
info += ', delivery error (%s)' % o
if options['logfile']:
options['logfile'].write('Delivery error (%s)' % o)
except getmailFilterError, o:
log.error('Filter error (%s)\n' % o)
info += ', filter error (%s)' % o
if options['logfile']:
options['logfile'].write('Filter error (%s)' % o)
if (retrieve or delete or oplevel > 1):
log.info(' %s\n' % info)
if retrieve or delete:
if options['logfile']:
options['logfile'].write(logline)
if options['message_log_syslog']:
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 socket.timeout, o:
retriever.write_oldmailfile(forget_deleted=False)
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:
retriever.write_oldmailfile(forget_deleted=False)
log.error('%s: protocol error (%s)\n' % (configfile, o))
if options['logfile']:
options['logfile'].write('protocol error (%s)' % o)
except socket.gaierror, o:
retriever.write_oldmailfile(forget_deleted=False)
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:
retriever.write_oldmailfile(forget_deleted=False)
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 getmailOperationError, o:
retriever.write_oldmailfile(forget_deleted=False)
log.error('%s: operation error (%s)\n' % (configfile, o))
if options['logfile']:
options['logfile'].write('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))
log.debug('retriever %s finished\n' % retriever)
try:
retriever.quit()
except getmailOperationError, o:
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))
#######################################
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)')
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'],
'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_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
# 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:
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!
go(configs)
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()
syntax highlighted by Code2HTML, v. 0.9.1