Mercurial > p > roundup > code
changeset 3203:eddcfee2cc19
reformat
| author | Alexander Smishlajev <a1s@users.sourceforge.net> |
|---|---|
| date | Fri, 18 Feb 2005 12:31:47 +0000 |
| parents | d2b1a946fdf4 |
| children | dbd6ba721943 |
| files | scripts/imapServer.py |
| diffstat | 1 files changed, 324 insertions(+), 305 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/imapServer.py Wed Feb 16 22:07:33 2005 +0000 +++ b/scripts/imapServer.py Fri Feb 18 12:31:47 2005 +0000 @@ -1,27 +1,27 @@ -#!/usr/bin/env python2.3 -# arch-tag: f2d1fd6e-df72-4188-a3b4-a9dbbb0807b9 -# vim: filetype=python ts=4 sw=4 noexpandtab si +#!/usr/bin/env python """\ This script is a wrapper around the mailgw.py script that exists in roundup. It runs as service instead of running as a one-time shot. It also connects to a secure IMAP server. The main reasons for this script are: -1) The roundup-mailgw script isn't designed to run as a server. It expects that you - either run it by hand, and enter the password each time, or you supply the - password on the command line. I prefer to run a server that I initialize with - the password, and then it just runs. I don't want to have to pass it on the - command line, so running through crontab isn't a possibility. (This wouldn't - be a problem on a local machine running through a mailspool.) -2) mailgw.py somehow screws up SSL support so IMAP4_SSL doesn't work. So hopefully - running that work outside of the mailgw will allow it to work. -3) I wanted to be able to check multiple projects at the same time. roundup-mailgw is - only for 1 mailbox and 1 project. +1) The roundup-mailgw script isn't designed to run as a server. It + expects that you either run it by hand, and enter the password each + time, or you supply the password on the command line. I prefer to + run a server that I initialize with the password, and then it just + runs. I don't want to have to pass it on the command line, so + running through crontab isn't a possibility. (This wouldn't be a + problem on a local machine running through a mailspool.) +2) mailgw.py somehow screws up SSL support so IMAP4_SSL doesn't work. So + hopefully running that work outside of the mailgw will allow it to work. +3) I wanted to be able to check multiple projects at the same time. + roundup-mailgw is only for 1 mailbox and 1 project. *TODO*: - For the first round, the program spawns a new roundup-mailgw for each imap message - that it finds and pipes the result in. In the future it might be more practical to - actually include the roundup files and run the appropriate commands using python. + For the first round, the program spawns a new roundup-mailgw for + each imap message that it finds and pipes the result in. In the + future it might be more practical to actually include the roundup + files and run the appropriate commands using python. *TODO*: Look into supporting a logfile instead of using 2>/logfile @@ -30,338 +30,357 @@ Add an option for changing the uid/gid of the running process. """ +import getpass import logging +import imaplib +import optparse +import os +import re +import time + logging.basicConfig() log = logging.getLogger('IMAPServer') version = '0.1.2' class RoundupMailbox: - """This contains all the info about each mailbox. - Username, Password, server, security, roundup database - """ - def __init__(self, dbhome='', username=None, password=None, mailbox=None - , server=None, protocol='imaps'): - self.username = username - self.password = password - self.mailbox = mailbox - self.server = server - self.protocol = protocol - self.dbhome = dbhome + """This contains all the info about each mailbox. + Username, Password, server, security, roundup database + """ + def __init__(self, dbhome='', username=None, password=None, mailbox=None + , server=None, protocol='imaps'): + self.username = username + self.password = password + self.mailbox = mailbox + self.server = server + self.protocol = protocol + self.dbhome = dbhome + + try: + if not self.dbhome: + self.dbhome = raw_input('Tracker home: ') + if not os.path.exists(self.dbhome): + raise ValueError, 'Invalid home address: ' \ + 'directory "%s" does not exist.' % self.dbhome - try: - if not self.dbhome: - import os - self.dbhome = raw_input('Tracker home: ') - if not os.path.exists(self.dbhome): - raise ValueError, 'Invalid home address directory does not exist. %s' % self.dbhome - - if not self.server: - self.server = raw_input('Server: ') - if not self.server: - raise ValueError, 'No Servername supplied' - protocol = raw_input('protocol [imaps]? ') - self.protocol = protocol + if not self.server: + self.server = raw_input('Server: ') + if not self.server: + raise ValueError, 'No Servername supplied' + protocol = raw_input('protocol [imaps]? ') + self.protocol = protocol + + if not self.username: + self.username = raw_input('Username: ') + if not self.username: + raise ValueError, 'Invalid Username' - if not self.username: - self.username = raw_input('Username: ') - if not self.username: - raise ValueError, 'Invalid Username' - - if not self.password: - import getpass - print 'For server %s, user %s' % (self.server, self.username) - self.password = getpass.getpass() - # password can be empty because it could be superceeded by a later - # entry - - #if self.mailbox is None: - # self.mailbox = raw_input('Mailbox [INBOX]: ') - # # We allow an empty mailbox because that will - # # select the INBOX, whatever it is called + if not self.password: + print 'For server %s, user %s' % (self.server, self.username) + self.password = getpass.getpass() + # password can be empty because it could be superceeded + # by a later entry - except (KeyboardInterrupt, EOFError): - raise ValueError, 'Canceled by User' + #if self.mailbox is None: + # self.mailbox = raw_input('Mailbox [INBOX]: ') + # # We allow an empty mailbox because that will + # # select the INBOX, whatever it is called - def __str__(self): - return """Mailbox{ server:%(server)s, protocol:%(protocol)s, username:%(username)s, mailbox:%(mailbox)s, dbhome:%(dbhome)s }""" % self.__dict__ - + except (KeyboardInterrupt, EOFError): + raise ValueError, 'Canceled by User' + + def __str__(self): + return 'Mailbox{ server:%(server)s, protocol:%(protocol)s, ' \ + 'username:%(username)s, mailbox:%(mailbox)s, ' \ + 'dbhome:%(dbhome)s }' % self.__dict__ +# [als] class name is misleading. this is imap client, not imap server class IMAPServer: - """This class runs as a server process. It is configured with a list of - mailboxes to connect to, along with the roundup database directories that correspond - with each email address. - It then connects to each mailbox at a specified interval, and if there are new messages - it reads them, and sends the result to the roundup.mailgw. + + """IMAP mail gatherer. - *TODO*: - Try to be smart about how you access the mailboxes so that you can connect once, and - access multiple mailboxes and possibly multiple usernames. + This class runs as a server process. It is configured with a list of + mailboxes to connect to, along with the roundup database directories + that correspond with each email address. It then connects to each + mailbox at a specified interval, and if there are new messages it + reads them, and sends the result to the roundup.mailgw. - *NOTE*: - This assumes that if you are using the same user on the same server, you are using - the same password. (the last one supplied is used.) Empty passwords are ignored. - Only the last protocol supplied is used. - """ + *TODO*: + Try to be smart about how you access the mailboxes so that you can + connect once, and access multiple mailboxes and possibly multiple + usernames. - def __init__(self, pidfile=None, delay=5, daemon=False): - #This is sorted by servername, then username, then mailboxes - self.mailboxes = {} - self.delay = float(delay) - self.pidfile = pidfile - self.daemon = daemon + *NOTE*: + This assumes that if you are using the same user on the same + server, you are using the same password. (the last one supplied is + used.) Empty passwords are ignored. Only the last protocol + supplied is used. + """ - def setDelay(self, delay): - self.delay = delay + def __init__(self, pidfile=None, delay=5, daemon=False): + #This is sorted by servername, then username, then mailboxes + self.mailboxes = {} + self.delay = float(delay) + self.pidfile = pidfile + self.daemon = daemon - def addMailbox(self, mailbox): - """ The linkage is as follows: - servers -- users - mailbox:dbhome - So there can be multiple servers, each with multiple users. - Each username can be associated with multiple mailboxes. - each mailbox is associated with 1 database home - """ - log.info('Adding mailbox %s', mailbox) - if not self.mailboxes.has_key(mailbox.server): - self.mailboxes[mailbox.server] = {'protocol':'imaps', 'users':{}} - server = self.mailboxes[mailbox.server] - if mailbox.protocol: - server['protocol'] = mailbox.protocol + def setDelay(self, delay): + self.delay = delay + + def addMailbox(self, mailbox): + """ The linkage is as follows: + servers -- users - mailbox:dbhome + So there can be multiple servers, each with multiple users. + Each username can be associated with multiple mailboxes. + each mailbox is associated with 1 database home + """ + log.info('Adding mailbox %s', mailbox) + if not self.mailboxes.has_key(mailbox.server): + self.mailboxes[mailbox.server] = {'protocol':'imaps', 'users':{}} + server = self.mailboxes[mailbox.server] + if mailbox.protocol: + server['protocol'] = mailbox.protocol - if not server['users'].has_key(mailbox.username): - server['users'][mailbox.username] = {'password':'', 'mailboxes':{}} - user = server['users'][mailbox.username] - if mailbox.password: - user['password'] = mailbox.password + if not server['users'].has_key(mailbox.username): + server['users'][mailbox.username] = {'password':'', 'mailboxes':{}} + user = server['users'][mailbox.username] + if mailbox.password: + user['password'] = mailbox.password - if user['mailboxes'].has_key(mailbox.mailbox): - raise ValueError, 'Mailbox is already defined' + if user['mailboxes'].has_key(mailbox.mailbox): + raise ValueError, 'Mailbox is already defined' - user['mailboxes'][mailbox.mailbox] = mailbox.dbhome + user['mailboxes'][mailbox.mailbox] = mailbox.dbhome - def _process(self, message, dbhome): - """Actually process one of the email messages""" - import os, sys - child = os.popen('roundup-mailgw %s' % dbhome, 'wb') - child.write(message) - child.close() - #print message + def _process(self, message, dbhome): + """Actually process one of the email messages""" + child = os.popen('roundup-mailgw %s' % dbhome, 'wb') + child.write(message) + child.close() + #print message - def _getMessages(self, serv, count, dbhome): - """This assumes that you currently have a mailbox open, and want to - process all messages that are inside. - """ - for n in range(1, count+1): - (t, data) = serv.fetch(n, '(RFC822)') - if t == 'OK': - self._process(data[0][1], dbhome) - serv.store(n, '+FLAGS', r'(\Deleted)') + def _getMessages(self, serv, count, dbhome): + """This assumes that you currently have a mailbox open, and want to + process all messages that are inside. + """ + for n in range(1, count+1): + (t, data) = serv.fetch(n, '(RFC822)') + if t == 'OK': + self._process(data[0][1], dbhome) + serv.store(n, '+FLAGS', r'(\Deleted)') - def checkBoxes(self): - """This actually goes out and does all the checking. - Returns False if there were any errors, otherwise returns true. - """ - import imaplib - noErrors = True - for server in self.mailboxes: - log.info('Connecting to server: %s', server) - s_vals = self.mailboxes[server] + def checkBoxes(self): + """This actually goes out and does all the checking. + Returns False if there were any errors, otherwise returns true. + """ + noErrors = True + for server in self.mailboxes: + log.info('Connecting to server: %s', server) + s_vals = self.mailboxes[server] + + try: + for user in s_vals['users']: + u_vals = s_vals['users'][user] + # TODO: As near as I can tell, you can only + # login with 1 username for each connection to a server. + protocol = s_vals['protocol'].lower() + if protocol == 'imaps': + serv = imaplib.IMAP4_SSL(server) + elif protocol == 'imap': + serv = imaplib.IMAP4(server) + else: + raise ValueError, 'Unknown protocol %s' % protocol + + password = u_vals['password'] - try: - for user in s_vals['users']: - u_vals = s_vals['users'][user] - # TODO: As near as I can tell, you can only - # login with 1 username for each connection to a server. - protocol = s_vals['protocol'].lower() - if protocol == 'imaps': - serv = imaplib.IMAP4_SSL(server) - elif protocol == 'imap': - serv = imaplib.IMAP4(server) - else: - raise ValueError, 'Unknown protocol %s' % protocol - - password = u_vals['password'] - - try: - log.info('Connecting as user: %s', user) - serv.login(user, password) - - for mbox in u_vals['mailboxes']: - dbhome = u_vals['mailboxes'][mbox] - log.info('Using mailbox: %s, home: %s', mbox, dbhome) - #access a specific mailbox - if mbox: - (t, data) = serv.select(mbox) - else: - # Select the default mailbox (INBOX) - (t, data) = serv.select() - try: - nMessages = int(data[0]) - except ValueError: - nMessages = 0 + try: + log.info('Connecting as user: %s', user) + serv.login(user, password) + + for mbox in u_vals['mailboxes']: + dbhome = u_vals['mailboxes'][mbox] + log.info('Using mailbox: %s, home: %s', + mbox, dbhome) + #access a specific mailbox + if mbox: + (t, data) = serv.select(mbox) + else: + # Select the default mailbox (INBOX) + (t, data) = serv.select() + try: + nMessages = int(data[0]) + except ValueError: + nMessages = 0 - log.info('Found %s messages', nMessages) - - if nMessages: - self._getMessages(serv, nMessages, dbhome) - serv.expunge() - - # We are done with this mailbox - serv.close() - except: - log.exception('Exception with server %s user %s', server, user) - noErrors = False + log.info('Found %s messages', nMessages) + + if nMessages: + self._getMessages(serv, nMessages, dbhome) + serv.expunge() - serv.logout() - serv.shutdown() - del serv - except: - log.exception('Exception while connecting to %s', server) - noErrors = False - return noErrors + # We are done with this mailbox + serv.close() + except: + log.exception('Exception with server %s user %s', + server, user) + noErrors = False + + serv.logout() + serv.shutdown() + del serv + except: + log.exception('Exception while connecting to %s', server) + noErrors = False + return noErrors - def makeDaemon(self): - """This forks a couple of times, and otherwise makes this run as a daemon.""" - ''' Turn this process into a daemon. - - make our parent PID 1 - - Write our new PID to the pidfile. - - From A.M. Kuuchling (possibly originally Greg Ward) with - modification from Oren Tirosh, and finally a small mod from me. - Originally taken from roundup.scripts.roundup_server.py - ''' - log.info('Running as Daemon') - import os - # Fork once - if os.fork() != 0: - os._exit(0) - - # Create new session - os.setsid() - - # Second fork to force PPID=1 - pid = os.fork() - if pid: - if self.pidfile: - pidfile = open(self.pidfile, 'w') - pidfile.write(str(pid)) - pidfile.close() - os._exit(0) - - #os.chdir("/") - #os.umask(0) + def makeDaemon(self): + """Turn this process into a daemon. + + - make our parent PID 1 + + Write our new PID to the pidfile. + + From A.M. Kuuchling (possibly originally Greg Ward) with + modification from Oren Tirosh, and finally a small mod from me. + Originally taken from roundup.scripts.roundup_server.py + """ + log.info('Running as Daemon') + # Fork once + if os.fork() != 0: + os._exit(0) + + # Create new session + os.setsid() - def run(self): - """This spawns itself as a daemon, and then runs continually, just sleeping inbetween checks. - It is recommended that you run checkBoxes once first before you select run. That way you can - know if there were any failures. - """ - import time - if self.daemon: - self.makeDaemon() - while True: - - time.sleep(self.delay * 60.0) - log.info('Time: %s', time.strftime('%Y-%m-%d %H:%M:%S')) - self.checkBoxes() + # Second fork to force PPID=1 + pid = os.fork() + if pid: + if self.pidfile: + pidfile = open(self.pidfile, 'w') + pidfile.write(str(pid)) + pidfile.close() + os._exit(0) + + #os.chdir("/") + #os.umask(0) + + def run(self): + """Run email gathering daemon. + + This spawns itself as a daemon, and then runs continually, just + sleeping inbetween checks. It is recommended that you run + checkBoxes once first before you select run. That way you can + know if there were any failures. + """ + if self.daemon: + self.makeDaemon() + while True: + + time.sleep(self.delay * 60.0) + log.info('Time: %s', time.strftime('%Y-%m-%d %H:%M:%S')) + self.checkBoxes() def getItems(s): - """Parse a string looking for userame@server""" - import re - myRE = re.compile( - r'((?P<proto>[^:]+)://)?'#You can supply a protocol if you like - r'(' #The username part is optional - r'(?P<user>[^:]+)' #You can supply the password as - r'(:(?P<pass>.+))?' #username:password@server - r'@)?' - r'(?P<server>[^/]+)' - r'(/(?P<mailbox>.+))?$' - ) - m = myRE.match(s) - if m: - return {'username':m.group('user'), 'password':m.group('pass') - , 'server':m.group('server'), 'protocol':m.group('proto') - , 'mailbox':m.group('mailbox') - } + """Parse a string looking for userame@server""" + myRE = re.compile( + r'((?P<protocol>[^:]+)://)?'#You can supply a protocol if you like + r'(' #The username part is optional + r'(?P<username>[^:]+)' #You can supply the password as + r'(:(?P<password>.+))?' #username:password@server + r'@)?' + r'(?P<server>[^/]+)' + r'(/(?P<mailbox>.+))?$' + ) + m = myRE.match(s) + if m: + return m.groupdict() + else: + return None def main(): - """This is what is called if run at the prompt""" - import optparse, os - parser = optparse.OptionParser( - version=('%prog ' + version) - , usage="""usage: %prog [options] (home server)... -So each entry has a home, and then the server configuration. home is just a path to the -roundup issue tracker. The server is something of the form: - imaps://user:password@server/mailbox -If you don't supply the protocol, imaps is assumed. Without user or password, you will be -prompted for them. The server must be supplied. Without mailbox the INBOX is used. + """This is what is called if run at the prompt""" + parser = optparse.OptionParser( + version=('%prog ' + version), + usage="""usage: %prog [options] (home server)... + +So each entry has a home, and then the server configuration. Home is just +a path to the roundup issue tracker. The server is something of the form: + + imaps://user:password@server/mailbox + +If you don't supply the protocol, imaps is assumed. Without user or +password, you will be prompted for them. The server must be supplied. +Without mailbox the INBOX is used. Examples: %prog /home/roundup/trackers/test imaps://test@imap.example.com/test - %prog /home/roundup/trackers/test imap.example.com /home/roundup/trackers/test2 imap.example.com/test2 + %prog /home/roundup/trackers/test imap.example.com \ +/home/roundup/trackers/test2 imap.example.com/test2 """ - ) - parser.add_option('-d', '--delay', dest='delay', type='float', metavar='<sec>' - , default=5 - , help="Set the delay between checks in minutes. (default 5)" - ) - parser.add_option('-p', '--pid-file', dest='pidfile', metavar='<file>' - , default=None - , help="The pid of the server process will be written to <file>" - ) - parser.add_option('-n', '--no-daemon', dest='daemon', action='store_false' - , default=True - , help="Do not fork into the background after running the first check." - ) - parser.add_option('-v', '--verbose', dest='verbose', action='store_const' - , const=logging.INFO - , help="Be more verbose in letting you know what is going on." - " Enables informational messages." - ) - parser.add_option('-V', '--very-verbose', dest='verbose', action='store_const' - , const=logging.DEBUG - , help="Be very verbose in letting you know what is going on." - " Enables debugging messages." - ) - parser.add_option('-q', '--quiet', dest='verbose', action='store_const' - , const=logging.ERROR - , help="Be less verbose. Ignores warnings, only prints errors." - ) - parser.add_option('-Q', '--very-quiet', dest='verbose', action='store_const' - , const=logging.CRITICAL - , help="Be much less verbose. Ignores warnings and errors." - " Only print CRITICAL messages." - ) + ) + parser.add_option('-d', '--delay', dest='delay', type='float', + metavar='<sec>', default=5, + help="Set the delay between checks in minutes. (default 5)" + ) + parser.add_option('-p', '--pid-file', dest='pidfile', + metavar='<file>', default=None, + help="The pid of the server process will be written to <file>" + ) + parser.add_option('-n', '--no-daemon', dest='daemon', + action='store_false', default=True, + help="Do not fork into the background after running the first check." + ) + parser.add_option('-v', '--verbose', dest='verbose', + action='store_const', const=logging.INFO, + help="Be more verbose in letting you know what is going on." + " Enables informational messages." + ) + parser.add_option('-V', '--very-verbose', dest='verbose', + action='store_const', const=logging.DEBUG, + help="Be very verbose in letting you know what is going on." + " Enables debugging messages." + ) + parser.add_option('-q', '--quiet', dest='verbose', + action='store_const', const=logging.ERROR, + help="Be less verbose. Ignores warnings, only prints errors." + ) + parser.add_option('-Q', '--very-quiet', dest='verbose', + action='store_const', const=logging.CRITICAL, + help="Be much less verbose. Ignores warnings and errors." + " Only print CRITICAL messages." + ) - (opts, args) = parser.parse_args() - if (len(args) == 0) or (len(args) % 2 == 1): - parser.error('Invalid number of arguments. Each site needs a home and a server.') + (opts, args) = parser.parse_args() + if (len(args) == 0) or (len(args) % 2 == 1): + parser.error('Invalid number of arguments. ' + 'Each site needs a home and a server.') - log.setLevel(opts.verbose) - myServer = IMAPServer(delay=opts.delay, pidfile=opts.pidfile, daemon=opts.daemon) - for i in range(0,len(args),2): - home = args[i] - server = args[i+1] - if not os.path.exists(home): - parser.error('Home: "%s" does not exist' % home) + log.setLevel(opts.verbose) + myServer = IMAPServer(delay=opts.delay, pidfile=opts.pidfile, + daemon=opts.daemon) + for i in range(0,len(args),2): + home = args[i] + server = args[i+1] + if not os.path.exists(home): + parser.error('Home: "%s" does not exist' % home) - info = getItems(server) - if not info: - parser.error('Invalid server string: "%s"' % server) + info = getItems(server) + if not info: + parser.error('Invalid server string: "%s"' % server) - myServer.addMailbox( - RoundupMailbox(dbhome=home, mailbox=info['mailbox'] - , username=info['username'], password=info['password'] - , server=info['server'], protocol=info['protocol'] - ) - ) - - if myServer.checkBoxes(): - myServer.run() + myServer.addMailbox( + RoundupMailbox(dbhome=home, mailbox=info['mailbox'] + , username=info['username'], password=info['password'] + , server=info['server'], protocol=info['protocol'] + ) + ) + + if myServer.checkBoxes(): + myServer.run() if __name__ == '__main__': - main() + main() +# vim: et ft=python si sts=4 sw=4
