diff --git a/offlineimap/head/debian/changelog b/offlineimap/head/debian/changelog index 6701715..48d3dbc 100644 --- a/offlineimap/head/debian/changelog +++ b/offlineimap/head/debian/changelog @@ -1,3 +1,11 @@ +offlineimap (3.99.3) unstable; urgency=low + + * Moved password promting into imapserver.py. Passwords are now asked + for on-demand and typos will no longer crash the program (the user + will be re-prompted). Closes: #162672. + + -- John Goerzen Mon, 4 Nov 2002 11:15:11 -0600 + offlineimap (3.99.2) unstable; urgency=low * Further attempts to fix imapsplit problems. diff --git a/offlineimap/head/offlineimap/imapserver.py b/offlineimap/head/offlineimap/imapserver.py index 7843057..8be3c12 100644 --- a/offlineimap/head/offlineimap/imapserver.py +++ b/offlineimap/head/offlineimap/imapserver.py @@ -48,11 +48,15 @@ class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass class IMAPServer: - def __init__(self, username = None, password = None, hostname = None, + def __init__(self, config, accountname, + username = None, password = None, hostname = None, port = None, ssl = 1, maxconnections = 1, tunnel = None, reference = '""'): + self.account = accountname + self.config = config self.username = username self.password = password + self.passworderror = None self.hostname = hostname self.tunnel = tunnel self.port = port @@ -72,6 +76,16 @@ class IMAPServer: self.connectionlock = Lock() self.reference = reference + def getpassword(self): + if self.password != None and self.passworderror == None: + return self.password + + self.password = UIBase.getglobalui().getpass(self.account, self.config, + self.passworderror) + self.passworderror = None + + return self.password + def getdelim(self): """Returns this server's folder delimiter. Can only be called after one or more calls to acquireconnection.""" @@ -92,7 +106,7 @@ class IMAPServer: def md5handler(self, response): challenge = response.strip() - msg = self.password + msg = self.getpassword() while len(msg) < 64: msg += "\0" @@ -135,23 +149,31 @@ class IMAPServer: UIBase.getglobalui().connecting(self.hostname, self.port) - # Generate a new connection. - if self.tunnel: - imapobj = UsefulIMAP4_Tunnel(self.tunnel) - elif self.usessl: - imapobj = UsefulIMAP4_SSL(self.hostname, self.port) - else: - imapobj = UsefulIMAP4(self.hostname, self.port) - - if not self.tunnel: - if 'AUTH=CRAM-MD5' in imapobj.capabilities: - UIBase.getglobalui().debug('imap', - 'Attempting CRAM-MD5 authentication') - imapobj.authenticate('CRAM-MD5', self.md5handler) + success = 0 + while not success: + # Generate a new connection. + if self.tunnel: + imapobj = UsefulIMAP4_Tunnel(self.tunnel) + elif self.usessl: + imapobj = UsefulIMAP4_SSL(self.hostname, self.port) else: - UIBase.getglobalui().debug('imap', - 'Attempting plain authentication') - imapobj.login(self.username, self.password) + imapobj = UsefulIMAP4(self.hostname, self.port) + + if not self.tunnel: + try: + if 'AUTH=CRAM-MD5' in imapobj.capabilities: + UIBase.getglobalui().debug('imap', + 'Attempting CRAM-MD5 authentication') + imapobj.authenticate('CRAM-MD5', self.md5handler) + else: + UIBase.getglobalui().debug('imap', + 'Attempting plain authentication') + imapobj.login(self.username, self.getpassword()) + # Would bail by here if there was a failure. + success = 1 + except imapobj.error, val: + self.passworderror = str(val) + self.password = None if self.delim == None: listres = imapobj.list(self.reference, '""')[1] @@ -249,13 +271,19 @@ class ConfigedIMAPServer(IMAPServer): # Connect to the remote server. if usetunnel: - IMAPServer.__init__(self, + IMAPServer.__init__(self, config, accountname, tunnel = config.get(accountname, "preauthtunnel"), reference = reference, maxconnections = config.getint(accountname, "maxconnections")) else: if not password: - password = config.get(accountname, 'remotepass') - IMAPServer.__init__(self, user, password, host, port, ssl, + if config.has_option(accountname, 'remotepass'): + password = config.get(accountname, 'remotepass') + elif config.has_option(accountname, 'remotepassfile'): + passfile = open(os.path.expanduser(config.get(accountname, "remotepassfile"))) + password = passfile.readline().strip() + passfile.close() + IMAPServer.__init__(self, config, accountname, + user, password, host, port, ssl, config.getint(accountname, "maxconnections"), reference = reference) diff --git a/offlineimap/head/offlineimap/init.py b/offlineimap/head/offlineimap/init.py index 485811a..7e4d18f 100644 --- a/offlineimap/head/offlineimap/init.py +++ b/offlineimap/head/offlineimap/init.py @@ -90,8 +90,6 @@ def startup(versionno): server = None remoterepos = None localrepos = None - passwords = {} - tunnels = {} if '-1' in options: threadutil.initInstanceLimit("ACCOUNTLIMIT", 1) @@ -99,23 +97,7 @@ def startup(versionno): threadutil.initInstanceLimit("ACCOUNTLIMIT", config.getint("general", "maxsyncaccounts")) - # We have to gather passwords here -- don't want to have two threads - # asking for passwords simultaneously. - for account in accounts: - #if '.' in account: - # raise ValueError, "Account '%s' contains a dot; dots are not " \ - # "allowed in account names." % account - if config.has_option(account, "preauthtunnel"): - tunnels[account] = config.get(account, "preauthtunnel") - elif config.has_option(account, "remotepass"): - passwords[account] = config.get(account, "remotepass") - elif config.has_option(account, "remotepassfile"): - passfile = open(os.path.expanduser(config.get(account, "remotepassfile"))) - passwords[account] = passfile.readline().strip() - passfile.close() - else: - passwords[account] = ui.getpass(account, config) for instancename in ["FOLDER_" + account, "MSGCOPY_" + account]: if '-1' in options: threadutil.initInstanceLimit(instancename, 1) @@ -133,7 +115,6 @@ def startup(versionno): 'metadatadir': metadatadir, 'servers': servers, 'config': config, - 'passwords': passwords, 'localeval': localeval}) t.setDaemon(1) t.start() diff --git a/offlineimap/head/offlineimap/syncmaster.py b/offlineimap/head/offlineimap/syncmaster.py index 61d721d..7911977 100644 --- a/offlineimap/head/offlineimap/syncmaster.py +++ b/offlineimap/head/offlineimap/syncmaster.py @@ -23,7 +23,7 @@ import re, os, os.path, offlineimap, sys from ConfigParser import ConfigParser from threading import * -def syncaccount(accountname, metadatadir, servers, config, passwords, +def syncaccount(accountname, metadatadir, servers, config, localeval, *args): ui = UIBase.getglobalui() # We don't need an account lock because syncitall() goes through @@ -38,7 +38,7 @@ def syncaccount(accountname, metadatadir, servers, config, passwords, if accountname in servers: server = servers[accountname] else: - server = imapserver.ConfigedIMAPServer(config, accountname, passwords) + server = imapserver.ConfigedIMAPServer(config, accountname) servers[accountname] = server remoterepos = repository.IMAP.IMAPRepository(config, localeval, accountname, server) @@ -147,7 +147,7 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos, -def syncitall(accounts, metadatadir, servers, config, passwords, localeval): +def syncitall(accounts, metadatadir, servers, config, localeval): ui = UIBase.getglobalui() global mailboxes mailboxes = [] # Reset. @@ -157,7 +157,7 @@ def syncitall(accounts, metadatadir, servers, config, passwords, localeval): target = syncaccount, name = "Account sync %s" % accountname, args = (accountname, metadatadir, - servers, config, passwords, + servers, config, localeval)) thread.setDaemon(1) thread.start() @@ -166,11 +166,11 @@ def syncitall(accounts, metadatadir, servers, config, passwords, localeval): threadutil.threadsreset(threads) mbnames.genmbnames(config, localeval, mailboxes) -def sync_with_timer(accounts, metadatadir, servers, config, passwords, +def sync_with_timer(accounts, metadatadir, servers, config, localeval): ui = UIBase.getglobalui() currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE') - syncitall(accounts, metadatadir, servers, config, passwords, localeval) + syncitall(accounts, metadatadir, servers, config, localeval) if config.has_option('general', 'autorefresh'): refreshperiod = config.getint('general', 'autorefresh') * 60 while 1: @@ -200,5 +200,5 @@ def sync_with_timer(accounts, metadatadir, servers, config, passwords, event.set() for thread in kathreads.values(): thread.join() - syncitall(accounts, metadatadir, servers, config, passwords, + syncitall(accounts, metadatadir, servers, config, localeval) diff --git a/offlineimap/head/offlineimap/ui/TTY.py b/offlineimap/head/offlineimap/ui/TTY.py index e88991c..8d952eb 100644 --- a/offlineimap/head/offlineimap/ui/TTY.py +++ b/offlineimap/head/offlineimap/ui/TTY.py @@ -25,21 +25,32 @@ class TTYUI(UIBase): def __init__(s, config, verbose = 0): UIBase.__init__(s, config, verbose) s.iswaiting = 0 + s.outputlock = Lock() def isusable(s): return sys.stdout.isatty() and sys.stdin.isatty() def _msg(s, msg): - if (currentThread().getName() == 'MainThread'): - print msg - else: - print "%s:\n %s" % (currentThread().getName(), msg) - sys.stdout.flush() + s.outputlock.acquire() + try: + if (currentThread().getName() == 'MainThread'): + print msg + else: + print "%s:\n %s" % (currentThread().getName(), msg) + sys.stdout.flush() + finally: + s.outputlock.release() - def getpass(s, accountname, config): - return getpass("%s: Enter password for %s on %s: " % - (accountname, config.get(accountname, "remoteuser"), - config.get(accountname, "remotehost"))) + def getpass(s, accountname, config, errmsg = None): + if errmsg: + s._msg("%s: %s" % (accountname, errmsg)) + s.outputlock.acquire() + try: + return getpass("%s: Enter password for %s on %s: " % + (accountname, config.get(accountname, "remoteuser"), + config.get(accountname, "remotehost"))) + finally: + s.outputlock.release() def sleep(s, sleepsecs): s.iswaiting = 1 diff --git a/offlineimap/head/offlineimap/ui/Tk.py b/offlineimap/head/offlineimap/ui/Tk.py index 85582b6..9e8a345 100644 --- a/offlineimap/head/offlineimap/ui/Tk.py +++ b/offlineimap/head/offlineimap/ui/Tk.py @@ -27,13 +27,16 @@ from Queue import Queue from UIBase import UIBase class PasswordDialog: - def __init__(self, accountname, config, master=None): + def __init__(self, accountname, config, master=None, errmsg = None): self.top = Toplevel(master) self.top.title(version.productname + " Password Entry") - self.label = Label(self.top, - text = "%s: Enter password for %s on %s: " % \ - (accountname, config.get(accountname, "remoteuser"), - config.get(accountname, "remotehost"))) + text = '' + if errmsg: + text = '%s: %s\n' % (accountname, errmsg) + text += "%s: Enter password for %s on %s: " % \ + (accountname, config.get(accountname, "remoteuser"), + config.get(accountname, "remotehost")) + self.label = Label(self.top, text = text) self.label.pack() self.entry = Entry(self.top, show='*') @@ -171,8 +174,8 @@ class VerboseUI(UIBase): s.top.mainloop() s.notdeleted = 0 - def getpass(s, accountname, config): - pd = PasswordDialog(accountname, config) + def getpass(s, accountname, config, errmsg = None): + pd = PasswordDialog(accountname, config, errmsg = errmsg) return pd.getpassword() def gettf(s, newtype=ThreadFrame, master = None): diff --git a/offlineimap/head/offlineimap/ui/UIBase.py b/offlineimap/head/offlineimap/ui/UIBase.py index d9678c4..d48b162 100644 --- a/offlineimap/head/offlineimap/ui/UIBase.py +++ b/offlineimap/head/offlineimap/ui/UIBase.py @@ -92,7 +92,7 @@ class UIBase: ################################################## INPUT - def getpass(s, accountname, config): + def getpass(s, accountname, config, errmsg = None): raise NotImplementedError def folderlist(s, list):