/offlineimap/head: changeset 278

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.
This commit is contained in:
jgoerzen 2002-11-05 00:15:42 +01:00
parent d64138c228
commit 4527b82221
7 changed files with 95 additions and 64 deletions

View File

@ -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 <jgoerzen@complete.org> Mon, 4 Nov 2002 11:15:11 -0600
offlineimap (3.99.2) unstable; urgency=low offlineimap (3.99.2) unstable; urgency=low
* Further attempts to fix imapsplit problems. * Further attempts to fix imapsplit problems.

View File

@ -48,11 +48,15 @@ class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
class IMAPServer: 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, port = None, ssl = 1, maxconnections = 1, tunnel = None,
reference = '""'): reference = '""'):
self.account = accountname
self.config = config
self.username = username self.username = username
self.password = password self.password = password
self.passworderror = None
self.hostname = hostname self.hostname = hostname
self.tunnel = tunnel self.tunnel = tunnel
self.port = port self.port = port
@ -72,6 +76,16 @@ class IMAPServer:
self.connectionlock = Lock() self.connectionlock = Lock()
self.reference = reference 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): def getdelim(self):
"""Returns this server's folder delimiter. Can only be called """Returns this server's folder delimiter. Can only be called
after one or more calls to acquireconnection.""" after one or more calls to acquireconnection."""
@ -92,7 +106,7 @@ class IMAPServer:
def md5handler(self, response): def md5handler(self, response):
challenge = response.strip() challenge = response.strip()
msg = self.password msg = self.getpassword()
while len(msg) < 64: while len(msg) < 64:
msg += "\0" msg += "\0"
@ -135,23 +149,31 @@ class IMAPServer:
UIBase.getglobalui().connecting(self.hostname, self.port) UIBase.getglobalui().connecting(self.hostname, self.port)
# Generate a new connection. success = 0
if self.tunnel: while not success:
imapobj = UsefulIMAP4_Tunnel(self.tunnel) # Generate a new connection.
elif self.usessl: if self.tunnel:
imapobj = UsefulIMAP4_SSL(self.hostname, self.port) imapobj = UsefulIMAP4_Tunnel(self.tunnel)
else: elif self.usessl:
imapobj = UsefulIMAP4(self.hostname, self.port) imapobj = UsefulIMAP4_SSL(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)
else: else:
UIBase.getglobalui().debug('imap', imapobj = UsefulIMAP4(self.hostname, self.port)
'Attempting plain authentication')
imapobj.login(self.username, self.password) 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: if self.delim == None:
listres = imapobj.list(self.reference, '""')[1] listres = imapobj.list(self.reference, '""')[1]
@ -249,13 +271,19 @@ class ConfigedIMAPServer(IMAPServer):
# Connect to the remote server. # Connect to the remote server.
if usetunnel: if usetunnel:
IMAPServer.__init__(self, IMAPServer.__init__(self, config, accountname,
tunnel = config.get(accountname, "preauthtunnel"), tunnel = config.get(accountname, "preauthtunnel"),
reference = reference, reference = reference,
maxconnections = config.getint(accountname, "maxconnections")) maxconnections = config.getint(accountname, "maxconnections"))
else: else:
if not password: if not password:
password = config.get(accountname, 'remotepass') if config.has_option(accountname, 'remotepass'):
IMAPServer.__init__(self, user, password, host, port, ssl, 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"), config.getint(accountname, "maxconnections"),
reference = reference) reference = reference)

View File

@ -90,8 +90,6 @@ def startup(versionno):
server = None server = None
remoterepos = None remoterepos = None
localrepos = None localrepos = None
passwords = {}
tunnels = {}
if '-1' in options: if '-1' in options:
threadutil.initInstanceLimit("ACCOUNTLIMIT", 1) threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
@ -99,23 +97,7 @@ def startup(versionno):
threadutil.initInstanceLimit("ACCOUNTLIMIT", threadutil.initInstanceLimit("ACCOUNTLIMIT",
config.getint("general", "maxsyncaccounts")) 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: 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]: for instancename in ["FOLDER_" + account, "MSGCOPY_" + account]:
if '-1' in options: if '-1' in options:
threadutil.initInstanceLimit(instancename, 1) threadutil.initInstanceLimit(instancename, 1)
@ -133,7 +115,6 @@ def startup(versionno):
'metadatadir': metadatadir, 'metadatadir': metadatadir,
'servers': servers, 'servers': servers,
'config': config, 'config': config,
'passwords': passwords,
'localeval': localeval}) 'localeval': localeval})
t.setDaemon(1) t.setDaemon(1)
t.start() t.start()

View File

@ -23,7 +23,7 @@ import re, os, os.path, offlineimap, sys
from ConfigParser import ConfigParser from ConfigParser import ConfigParser
from threading import * from threading import *
def syncaccount(accountname, metadatadir, servers, config, passwords, def syncaccount(accountname, metadatadir, servers, config,
localeval, *args): localeval, *args):
ui = UIBase.getglobalui() ui = UIBase.getglobalui()
# We don't need an account lock because syncitall() goes through # 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: if accountname in servers:
server = servers[accountname] server = servers[accountname]
else: else:
server = imapserver.ConfigedIMAPServer(config, accountname, passwords) server = imapserver.ConfigedIMAPServer(config, accountname)
servers[accountname] = server servers[accountname] = server
remoterepos = repository.IMAP.IMAPRepository(config, localeval, 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() ui = UIBase.getglobalui()
global mailboxes global mailboxes
mailboxes = [] # Reset. mailboxes = [] # Reset.
@ -157,7 +157,7 @@ def syncitall(accounts, metadatadir, servers, config, passwords, localeval):
target = syncaccount, target = syncaccount,
name = "Account sync %s" % accountname, name = "Account sync %s" % accountname,
args = (accountname, metadatadir, args = (accountname, metadatadir,
servers, config, passwords, servers, config,
localeval)) localeval))
thread.setDaemon(1) thread.setDaemon(1)
thread.start() thread.start()
@ -166,11 +166,11 @@ def syncitall(accounts, metadatadir, servers, config, passwords, localeval):
threadutil.threadsreset(threads) threadutil.threadsreset(threads)
mbnames.genmbnames(config, localeval, mailboxes) mbnames.genmbnames(config, localeval, mailboxes)
def sync_with_timer(accounts, metadatadir, servers, config, passwords, def sync_with_timer(accounts, metadatadir, servers, config,
localeval): localeval):
ui = UIBase.getglobalui() ui = UIBase.getglobalui()
currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE') 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'): if config.has_option('general', 'autorefresh'):
refreshperiod = config.getint('general', 'autorefresh') * 60 refreshperiod = config.getint('general', 'autorefresh') * 60
while 1: while 1:
@ -200,5 +200,5 @@ def sync_with_timer(accounts, metadatadir, servers, config, passwords,
event.set() event.set()
for thread in kathreads.values(): for thread in kathreads.values():
thread.join() thread.join()
syncitall(accounts, metadatadir, servers, config, passwords, syncitall(accounts, metadatadir, servers, config,
localeval) localeval)

View File

@ -25,21 +25,32 @@ class TTYUI(UIBase):
def __init__(s, config, verbose = 0): def __init__(s, config, verbose = 0):
UIBase.__init__(s, config, verbose) UIBase.__init__(s, config, verbose)
s.iswaiting = 0 s.iswaiting = 0
s.outputlock = Lock()
def isusable(s): def isusable(s):
return sys.stdout.isatty() and sys.stdin.isatty() return sys.stdout.isatty() and sys.stdin.isatty()
def _msg(s, msg): def _msg(s, msg):
if (currentThread().getName() == 'MainThread'): s.outputlock.acquire()
print msg try:
else: if (currentThread().getName() == 'MainThread'):
print "%s:\n %s" % (currentThread().getName(), msg) print msg
sys.stdout.flush() else:
print "%s:\n %s" % (currentThread().getName(), msg)
sys.stdout.flush()
finally:
s.outputlock.release()
def getpass(s, accountname, config): def getpass(s, accountname, config, errmsg = None):
return getpass("%s: Enter password for %s on %s: " % if errmsg:
(accountname, config.get(accountname, "remoteuser"), s._msg("%s: %s" % (accountname, errmsg))
config.get(accountname, "remotehost"))) 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): def sleep(s, sleepsecs):
s.iswaiting = 1 s.iswaiting = 1

View File

@ -27,13 +27,16 @@ from Queue import Queue
from UIBase import UIBase from UIBase import UIBase
class PasswordDialog: class PasswordDialog:
def __init__(self, accountname, config, master=None): def __init__(self, accountname, config, master=None, errmsg = None):
self.top = Toplevel(master) self.top = Toplevel(master)
self.top.title(version.productname + " Password Entry") self.top.title(version.productname + " Password Entry")
self.label = Label(self.top, text = ''
text = "%s: Enter password for %s on %s: " % \ if errmsg:
(accountname, config.get(accountname, "remoteuser"), text = '%s: %s\n' % (accountname, errmsg)
config.get(accountname, "remotehost"))) 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.label.pack()
self.entry = Entry(self.top, show='*') self.entry = Entry(self.top, show='*')
@ -171,8 +174,8 @@ class VerboseUI(UIBase):
s.top.mainloop() s.top.mainloop()
s.notdeleted = 0 s.notdeleted = 0
def getpass(s, accountname, config): def getpass(s, accountname, config, errmsg = None):
pd = PasswordDialog(accountname, config) pd = PasswordDialog(accountname, config, errmsg = errmsg)
return pd.getpassword() return pd.getpassword()
def gettf(s, newtype=ThreadFrame, master = None): def gettf(s, newtype=ThreadFrame, master = None):

View File

@ -92,7 +92,7 @@ class UIBase:
################################################## INPUT ################################################## INPUT
def getpass(s, accountname, config): def getpass(s, accountname, config, errmsg = None):
raise NotImplementedError raise NotImplementedError
def folderlist(s, list): def folderlist(s, list):