##// END OF EJS Templates
Handle httpconnection.readauthforuri changes (hg.0593e8f81c71)
Handle httpconnection.readauthforuri changes (hg.0593e8f81c71)

File last commit:

r81:049fac04 default
r81:049fac04 default
Show More
mercurial_keyring.py
406 lines | 15.1 KiB | text/x-python | PythonLexer
/ mercurial_keyring.py
Marcin Kasperski
Zaczątki
r0 # -*- coding: utf-8 -*-
Marcin Kasperski
Docs and comments
r13 #
# mercurial_keyring: save passwords in password database
#
# Copyright 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
Marcin Kasperski
Docs moved to README.txt
r35 #
# See README.txt for more details.
Marcin Kasperski
Zaczątki
r0
from mercurial import hg, repo, util
from mercurial.i18n import _
try:
Marcin Kasperski
Fixed some bad indentation
r19 from mercurial.url import passwordmgr
Marcin Kasperski
Zaczątki
r0 except:
Marcin Kasperski
Fixed some bad indentation
r19 from mercurial.httprepo import passwordmgr
Marcin Kasperski
Some diagnostics. Still - the way to detect good/bad password is needed
r9 from mercurial.httprepo import httprepository
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 from mercurial import mail
Marcin Kasperski
Zaczątki
r0
Alan Franzoni
Workaround for gnomekeyring mercurial.demandimport incompatibility...
r64 # mercurial.demandimport incompatibility workaround,
# would cause gnomekeyring, one of the possible
# keyring backends, not to work.
from mercurial.demandimport import ignore
if "gobject._gobject" not in ignore:
ignore.append("gobject._gobject")
Marcin Kasperski
Zaczątki
r0 import keyring
from urlparse import urlparse
Marcin Kasperski
Mystery. Why .hg/hgrc items are missing?
r3 import urllib2
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 import smtplib, socket
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 import os
Marcin Kasperski
Zaczątki
r0
Marcin Kasperski
Temporary working version
r1 KEYRING_SERVICE = "Mercurial"
Marcin Kasperski
Zaczątki
r0
Marcin Kasperski
Working in-process cache
r2 ############################################################
Marcin Kasperski
Zaczątki
r0 def monkeypatch_method(cls):
def decorator(func):
setattr(cls, func.__name__, func)
return func
return decorator
Marcin Kasperski
Working in-process cache
r2 ############################################################
class PasswordStore(object):
"""
Marcin Kasperski
Docs and comments
r13 Helper object handling keyring usage (password save&restore,
the way passwords are keyed in the keyring).
Marcin Kasperski
Working in-process cache
r2 """
def __init__(self):
self.cache = dict()
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 def get_http_password(self, url, username):
Marcin Kasperski
Working version. Password is actually saved and restored,...
r7 return keyring.get_password(KEYRING_SERVICE,
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 self._format_http_key(url, username))
def set_http_password(self, url, username, password):
Marcin Kasperski
Working version. Password is actually saved and restored,...
r7 keyring.set_password(KEYRING_SERVICE,
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 self._format_http_key(url, username),
Marcin Kasperski
Working version. Password is actually saved and restored,...
r7 password)
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 def clear_http_password(self, url, username):
self.set_http_password(url, username, "")
def _format_http_key(self, url, username):
Marcin Kasperski
Working version. Password is actually saved and restored,...
r7 return "%s@@%s" % (username, url)
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 def get_smtp_password(self, machine, port, username):
return keyring.get_password(
KEYRING_SERVICE,
self._format_smtp_key(machine, port, username))
def set_smtp_password(self, machine, port, username, password):
keyring.set_password(
KEYRING_SERVICE,
self._format_smtp_key(machine, port, username),
password)
def clear_smtp_password(self, machine, port, username):
self.set_smtp_password(url, username, "")
def _format_smtp_key(self, machine, port, username):
return "%s@@%s:%s" % (username, machine, str(port))
Marcin Kasperski
Working in-process cache
r2
password_store = PasswordStore()
############################################################
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 class HTTPPasswordHandler(object):
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 """
Actual implementation of password handling (user prompting,
configuration file searching, keyring save&restore).
Object of this class is bound as passwordmgr attribute.
"""
def __init__(self):
self.pwd_cache = {}
self.last_reply = None
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 def find_auth(self, pwmgr, realm, authuri):
"""
Marcin Kasperski
Docs and comments
r13 Actual implementation of find_user_password - different
ways of obtaining the username and password.
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 """
ui = pwmgr.ui
Marcin Kasperski
Docs and comments
r13 # If we are called again just after identical previous
# request, then the previously returned auth must have been
# wrong. So we note this to force password prompt (and avoid
# reusing bad password indifinitely).
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 after_bad_auth = (self.last_reply \
Marcin Kasperski
Fixed some bad indentation
r19 and (self.last_reply['realm'] == realm) \
and (self.last_reply['authuri'] == authuri))
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Docs and comments
r13 # Strip arguments to get actual remote repository url.
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 base_url = self.canonical_url(authuri)
# Extracting possible username (or password)
Marcin Kasperski
Docs and comments
r13 # stored directly in repository url
Marcin Kasperski
Some reformatting (shortening long lines, slightly reorganised docs
r18 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
Marcin Kasperski
Fixed some bad indentation
r19 pwmgr, realm, authuri)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 if user and pwd:
Steve Borho
remove trailing whitespace
r68 self._debug_reply(ui, _("Auth data found in repository URL"),
Marcin Kasperski
Fixed some bad indentation
r19 base_url, user, pwd)
self.last_reply = dict(realm=realm,authuri=authuri,user=user)
return user, pwd
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Steve Borho
remove trailing whitespace
r68 # Loading .hg/hgrc [auth] section contents. If prefix is given,
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 # it will be used as a key to lookup password in the keyring.
Patrick Mezard
Handle httpconnection.readauthforuri changes (hg.0593e8f81c71)
r81 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url, user)
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 if prefix_url:
keyring_url = prefix_url
else:
keyring_url = base_url
ui.debug("keyring URL: %s\n" % keyring_url)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 # Checking the memory cache (there may be many http calls per command)
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 cache_key = (realm, keyring_url)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 if not after_bad_auth:
Marcin Kasperski
Fixed some bad indentation
r19 cached_auth = self.pwd_cache.get(cache_key)
if cached_auth:
user, pwd = cached_auth
Steve Borho
remove trailing whitespace
r68 self._debug_reply(ui, _("Cached auth data found"),
Marcin Kasperski
Fixed some bad indentation
r19 base_url, user, pwd)
self.last_reply = dict(realm=realm,authuri=authuri,user=user)
return user, pwd
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 if auth_user:
Marcin Kasperski
Stopped raising error when username is specified both in ~/.hgrc and...
r59 if user and (user != auth_user):
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 raise util.Abort(_('mercurial_keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, user, auth_user)))
user = auth_user
Marcin Kasperski
Fixed some bad indentation
r19 if pwd:
self.pwd_cache[cache_key] = user, pwd
Steve Borho
remove trailing whitespace
r68 self._debug_reply(ui, _("Auth data set in .hg/hgrc"),
Marcin Kasperski
Fixed some bad indentation
r19 base_url, user, pwd)
self.last_reply = dict(realm=realm,authuri=authuri,user=user)
return user, pwd
else:
ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Steve Borho
remove trailing whitespace
r68 # Loading password from keyring.
Marcin Kasperski
Some reformatting (shortening long lines, slightly reorganised docs
r18 # Only if username is known (so we know the key) and we are
# not after failure (so we don't reuse the bad password).
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 if user and not after_bad_auth:
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 pwd = password_store.get_http_password(keyring_url, user)
Marcin Kasperski
Fixed some bad indentation
r19 if pwd:
self.pwd_cache[cache_key] = user, pwd
Steve Borho
remove trailing whitespace
r68 self._debug_reply(ui, _("Keyring password found"),
Marcin Kasperski
Fixed some bad indentation
r19 base_url, user, pwd)
self.last_reply = dict(realm=realm,authuri=authuri,user=user)
return user, pwd
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Docs and comments
r13 # Is the username permanently set?
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 fixed_user = (user and True or False)
# Last resort: interactive prompt
if not ui.interactive():
Marcin Kasperski
Better error message
r40 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
Marcin Kasperski
Explicit information that keyring is not used due to lack of username
r52
if not fixed_user:
ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 ui.write(_("http authorization required\n"))
ui.status(_("realm: %s\n") % realm)
if fixed_user:
Marcin Kasperski
Fixed some bad indentation
r19 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 else:
Marcin Kasperski
Fixed some bad indentation
r19 user = ui.prompt(_("user:"), default=None)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 pwd = ui.getpass(_("password: "))
if fixed_user:
Marcin Kasperski
Fixed some bad indentation
r19 # Saving password to the keyring.
# It is done only if username is permanently set.
# Otherwise we won't be able to find the password so it
# does not make much sense to preserve it
ui.debug("Saving password for %s to keyring\n" % user)
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 password_store.set_http_password(keyring_url, user, pwd)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Marcin Kasperski
Docs and comments
r13 # Saving password to the memory cache
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 self.pwd_cache[cache_key] = user, pwd
Marcin Kasperski
Docs and comments
r13
Steve Borho
remove trailing whitespace
r68 self._debug_reply(ui, _("Manually entered password"),
Marcin Kasperski
Some reformatting (shortening long lines, slightly reorganised docs
r18 base_url, user, pwd)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
return user, pwd
Patrick Mezard
Handle httpconnection.readauthforuri changes (hg.0593e8f81c71)
r81 def load_hgrc_auth(self, ui, base_url, user):
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 """
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 Loading [auth] section contents from local .hgrc
Returns (username, password, prefix) tuple (every
element can be None)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 """
Marcin Kasperski
Fixed some bad indentation
r19 # Theoretically 3 lines below should do:
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Fixed some bad indentation
r19 #auth_token = self.readauthtoken(base_url)
#if auth_token:
# user, pwd = auth.get('username'), auth.get('password')
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Docs and comments
r13 # Unfortunately they do not work, readauthtoken always return
# None. Why? Because ui (self.ui of passwordmgr) describes the
# *remote* repository, so does *not* contain any option from
# local .hg/hgrc.
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Marcin Kasperski
TODO note about removing workaround
r50 # TODO: mercurial 1.4.2 is claimed to resolve this problem
Steve Borho
remove trailing whitespace
r68 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
Marcin Kasperski
TODO note about removing workaround
r50 # so since this version workaround implemented below should
# not be necessary. As it will take some time until people
# migrate to >= 1.4.2, it would be best to implement
# workaround conditionally.
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 # Workaround: we recreate the repository object
repo_root = ui.config("bundle", "mainreporoot")
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44
from mercurial.ui import ui as _ui
local_ui = _ui(ui)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 if repo_root:
Marcin Kasperski
Fixed some bad indentation
r19 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
Steve Borho
passwordmgr.readauthtoken() has been moved into a utility function
r69 try:
local_passwordmgr = passwordmgr(local_ui)
auth_token = local_passwordmgr.readauthtoken(base_url)
except AttributeError:
Steve Borho
readauthforuri has been moved into new httpconnection module
r72 try:
# hg 1.8
Patrick Mezard
Fix module imports with demandimport...
r80 import mercurial.url
readauthforuri = mercurial.url.readauthforuri
except (ImportError, AttributeError):
Steve Borho
readauthforuri has been moved into new httpconnection module
r72 # hg 1.9
Patrick Mezard
Fix module imports with demandimport...
r80 import mercurial.httpconnection
readauthforuri = mercurial.httpconnection.readauthforuri
Patrick Mezard
Handle httpconnection.readauthforuri changes (hg.0593e8f81c71)
r81 if readauthforuri.func_code.co_argcount == 3:
# Since hg.0593e8f81c71
res = readauthforuri(local_ui, base_url, user)
else:
res = readauthforuri(local_ui, base_url)
Steve Borho
passwordmgr.readauthtoken() has been moved into a utility function
r69 if res:
group, auth_token = res
else:
auth_token = None
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 if auth_token:
username = auth_token.get('username')
password = auth_token.get('password')
prefix = auth_token.get('prefix')
shortest_url = self.shortest_url(base_url, prefix)
return username, password, shortest_url
Marcin Kasperski
Slightly polishing the prefix patch:...
r46 return None, None, None
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
Dave Dribin
Store and lookup prefix from [auth] so that password is shared amongst shared auth entries
r44 def shortest_url(self, base_url, prefix):
if not prefix or prefix == '*':
return base_url
scheme, hostpath = base_url.split('://', 1)
p = prefix.split('://', 1)
if len(p) > 1:
prefix_host_path = p[1]
else:
prefix_host_path = prefix
shortest_url = scheme + '://' + prefix_host_path
return shortest_url
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 def canonical_url(self, authuri):
"""
Marcin Kasperski
Some reformatting (shortening long lines, slightly reorganised docs
r18 Strips query params from url. Used to convert urls like
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
to
https://repo.machine.com/repos/apps/module
"""
parsed_url = urlparse(authuri)
Marcin Kasperski
Most of lines longer than 80 chars fixed
r21 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
parsed_url.path)
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
def _debug_reply(self, ui, msg, url, user, pwd):
Marcin Kasperski
Some reformatting (shortening long lines, slightly reorganised docs
r18 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
msg, url, user, pwd and '*' * len(pwd) or 'not set'))
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10
############################################################
Marcin Kasperski
Zaczątki
r0 @monkeypatch_method(passwordmgr)
def find_user_password(self, realm, authuri):
"""
keyring-based implementation of username/password query
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 for HTTP(S) connections
Marcin Kasperski
Zaczątki
r0
Passwords are saved in gnome keyring, OSX/Chain or other platform
specific storage and keyed by the repository url
"""
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 # Extend object attributes
if not hasattr(self, '_pwd_handler'):
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 self._pwd_handler = HTTPPasswordHandler()
Marcin Kasperski
Working .hgrc read
r6
Marcin Kasperski
Mostly working, heavily refactored (separate class). Decision: keyring save only when username is fixed
r10 return self._pwd_handler.find_auth(self, realm, authuri)
Marcin Kasperski
Temporary working version
r1
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 ############################################################
def try_smtp_login(ui, smtp_obj, username, password):
"""
Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
Steve Borho
remove trailing whitespace
r68 password.
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24
Returns:
- True if login succeeded
- False if login failed due to the wrong credentials
Throws Abort exception if login failed for any other reason.
Immediately returns False if password is empty
"""
if not password:
return False
try:
ui.note(_('(authenticating to mail server as %s)\n') %
(username))
smtp_obj.login(username, password)
return True
Marcin Kasperski
Reverting previous change as it just makes extension incompatible with 2.4 and 2.5 without gaining anything as neither mercurial, nor keyring are 3k compatible
r63 except smtplib.SMTPException, inst:
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 if inst.smtp_code == 535:
ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
return False
else:
raise util.Abort(inst)
def keyring_supported_smtp(ui, username):
"""
keyring-integrated replacement for mercurial.mail._smtp
Used only when configuration file contains username, but
does not contain the password.
Most of the routine below is copied as-is from
mercurial.mail._smtp. The only changed part is
marked with #>>>>> and #<<<<< markers
"""
local_hostname = ui.config('smtp', 'local_hostname')
s = smtplib.SMTP(local_hostname=local_hostname)
mailhost = ui.config('smtp', 'host')
if not mailhost:
raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
mailport = int(ui.config('smtp', 'port', 25))
ui.note(_('sending mail: smtp host %s, port %s\n') %
(mailhost, mailport))
s.connect(host=mailhost, port=mailport)
if ui.configbool('smtp', 'tls'):
if not hasattr(socket, 'ssl'):
raise util.Abort(_("can't use TLS: Python SSL support "
"not installed"))
ui.note(_('(using tls)\n'))
s.ehlo()
s.starttls()
s.ehlo()
Steve Borho
remove trailing whitespace
r68
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
stored = password = password_store.get_smtp_password(
mailhost, mailport, username)
# No need to check whether password was found as try_smtp_login
# just returns False if it is absent.
while not try_smtp_login(ui, s, username, password):
password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
if stored != password:
password_store.set_smtp_password(
mailhost, mailport, username, password)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
def send(sender, recipients, msg):
try:
return s.sendmail(sender, recipients, msg)
Marcin Kasperski
Reverting previous change as it just makes extension incompatible with 2.4 and 2.5 without gaining anything as neither mercurial, nor keyring are 3k compatible
r63 except smtplib.SMTPRecipientsRefused, inst:
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 recipients = [r[1] for r in inst.recipients.values()]
raise util.Abort('\n' + '\n'.join(recipients))
Marcin Kasperski
Reverting previous change as it just makes extension incompatible with 2.4 and 2.5 without gaining anything as neither mercurial, nor keyring are 3k compatible
r63 except smtplib.SMTPException, inst:
Marcin Kasperski
SMTP passwords are also handled (tested on patchbomb extension...
r24 raise util.Abort(inst)
return send
############################################################
orig_smtp = mail._smtp
@monkeypatch_method(mail)
def _smtp(ui):
"""
build an smtp connection and return a function to send email
This is the monkeypatched version of _smtp(ui) function from
mercurial/mail.py. It calls the original unless username
without password is given in the configuration.
"""
username = ui.config('smtp', 'username')
password = ui.config('smtp', 'password')
if username and not password:
return keyring_supported_smtp(ui, username)
else:
return orig_smtp(ui)