##// END OF EJS Templates
Stopped raising error when username is specified both in ~/.hgrc and...
Marcin Kasperski -
r59:abed620e default
parent child Browse files
Show More
@@ -1,380 +1,380 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 #
2 #
3 # mercurial_keyring: save passwords in password database
3 # mercurial_keyring: save passwords in password database
4 #
4 #
5 # Copyright 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
5 # Copyright 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
6 #
6 #
7 # This software may be used and distributed according to the terms
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9 #
9 #
10 # See README.txt for more details.
10 # See README.txt for more details.
11
11
12 from mercurial import hg, repo, util
12 from mercurial import hg, repo, util
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 try:
14 try:
15 from mercurial.url import passwordmgr
15 from mercurial.url import passwordmgr
16 except:
16 except:
17 from mercurial.httprepo import passwordmgr
17 from mercurial.httprepo import passwordmgr
18 from mercurial.httprepo import httprepository
18 from mercurial.httprepo import httprepository
19 from mercurial import mail
19 from mercurial import mail
20
20
21 import keyring
21 import keyring
22 from urlparse import urlparse
22 from urlparse import urlparse
23 import urllib2
23 import urllib2
24 import smtplib, socket
24 import smtplib, socket
25 import os
25 import os
26
26
27 KEYRING_SERVICE = "Mercurial"
27 KEYRING_SERVICE = "Mercurial"
28
28
29 ############################################################
29 ############################################################
30
30
31 def monkeypatch_method(cls):
31 def monkeypatch_method(cls):
32 def decorator(func):
32 def decorator(func):
33 setattr(cls, func.__name__, func)
33 setattr(cls, func.__name__, func)
34 return func
34 return func
35 return decorator
35 return decorator
36
36
37 ############################################################
37 ############################################################
38
38
39 class PasswordStore(object):
39 class PasswordStore(object):
40 """
40 """
41 Helper object handling keyring usage (password save&restore,
41 Helper object handling keyring usage (password save&restore,
42 the way passwords are keyed in the keyring).
42 the way passwords are keyed in the keyring).
43 """
43 """
44 def __init__(self):
44 def __init__(self):
45 self.cache = dict()
45 self.cache = dict()
46 def get_http_password(self, url, username):
46 def get_http_password(self, url, username):
47 return keyring.get_password(KEYRING_SERVICE,
47 return keyring.get_password(KEYRING_SERVICE,
48 self._format_http_key(url, username))
48 self._format_http_key(url, username))
49 def set_http_password(self, url, username, password):
49 def set_http_password(self, url, username, password):
50 keyring.set_password(KEYRING_SERVICE,
50 keyring.set_password(KEYRING_SERVICE,
51 self._format_http_key(url, username),
51 self._format_http_key(url, username),
52 password)
52 password)
53 def clear_http_password(self, url, username):
53 def clear_http_password(self, url, username):
54 self.set_http_password(url, username, "")
54 self.set_http_password(url, username, "")
55 def _format_http_key(self, url, username):
55 def _format_http_key(self, url, username):
56 return "%s@@%s" % (username, url)
56 return "%s@@%s" % (username, url)
57 def get_smtp_password(self, machine, port, username):
57 def get_smtp_password(self, machine, port, username):
58 return keyring.get_password(
58 return keyring.get_password(
59 KEYRING_SERVICE,
59 KEYRING_SERVICE,
60 self._format_smtp_key(machine, port, username))
60 self._format_smtp_key(machine, port, username))
61 def set_smtp_password(self, machine, port, username, password):
61 def set_smtp_password(self, machine, port, username, password):
62 keyring.set_password(
62 keyring.set_password(
63 KEYRING_SERVICE,
63 KEYRING_SERVICE,
64 self._format_smtp_key(machine, port, username),
64 self._format_smtp_key(machine, port, username),
65 password)
65 password)
66 def clear_smtp_password(self, machine, port, username):
66 def clear_smtp_password(self, machine, port, username):
67 self.set_smtp_password(url, username, "")
67 self.set_smtp_password(url, username, "")
68 def _format_smtp_key(self, machine, port, username):
68 def _format_smtp_key(self, machine, port, username):
69 return "%s@@%s:%s" % (username, machine, str(port))
69 return "%s@@%s:%s" % (username, machine, str(port))
70
70
71 password_store = PasswordStore()
71 password_store = PasswordStore()
72
72
73 ############################################################
73 ############################################################
74
74
75 class HTTPPasswordHandler(object):
75 class HTTPPasswordHandler(object):
76 """
76 """
77 Actual implementation of password handling (user prompting,
77 Actual implementation of password handling (user prompting,
78 configuration file searching, keyring save&restore).
78 configuration file searching, keyring save&restore).
79
79
80 Object of this class is bound as passwordmgr attribute.
80 Object of this class is bound as passwordmgr attribute.
81 """
81 """
82 def __init__(self):
82 def __init__(self):
83 self.pwd_cache = {}
83 self.pwd_cache = {}
84 self.last_reply = None
84 self.last_reply = None
85
85
86 def find_auth(self, pwmgr, realm, authuri):
86 def find_auth(self, pwmgr, realm, authuri):
87 """
87 """
88 Actual implementation of find_user_password - different
88 Actual implementation of find_user_password - different
89 ways of obtaining the username and password.
89 ways of obtaining the username and password.
90 """
90 """
91 ui = pwmgr.ui
91 ui = pwmgr.ui
92
92
93 # If we are called again just after identical previous
93 # If we are called again just after identical previous
94 # request, then the previously returned auth must have been
94 # request, then the previously returned auth must have been
95 # wrong. So we note this to force password prompt (and avoid
95 # wrong. So we note this to force password prompt (and avoid
96 # reusing bad password indifinitely).
96 # reusing bad password indifinitely).
97 after_bad_auth = (self.last_reply \
97 after_bad_auth = (self.last_reply \
98 and (self.last_reply['realm'] == realm) \
98 and (self.last_reply['realm'] == realm) \
99 and (self.last_reply['authuri'] == authuri))
99 and (self.last_reply['authuri'] == authuri))
100
100
101 # Strip arguments to get actual remote repository url.
101 # Strip arguments to get actual remote repository url.
102 base_url = self.canonical_url(authuri)
102 base_url = self.canonical_url(authuri)
103
103
104 # Extracting possible username (or password)
104 # Extracting possible username (or password)
105 # stored directly in repository url
105 # stored directly in repository url
106 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
106 user, pwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
107 pwmgr, realm, authuri)
107 pwmgr, realm, authuri)
108 if user and pwd:
108 if user and pwd:
109 self._debug_reply(ui, _("Auth data found in repository URL"),
109 self._debug_reply(ui, _("Auth data found in repository URL"),
110 base_url, user, pwd)
110 base_url, user, pwd)
111 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
111 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
112 return user, pwd
112 return user, pwd
113
113
114 # Loading .hg/hgrc [auth] section contents. If prefix is given,
114 # Loading .hg/hgrc [auth] section contents. If prefix is given,
115 # it will be used as a key to lookup password in the keyring.
115 # it will be used as a key to lookup password in the keyring.
116 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url)
116 auth_user, pwd, prefix_url = self.load_hgrc_auth(ui, base_url)
117 if prefix_url:
117 if prefix_url:
118 keyring_url = prefix_url
118 keyring_url = prefix_url
119 else:
119 else:
120 keyring_url = base_url
120 keyring_url = base_url
121 ui.debug("keyring URL: %s\n" % keyring_url)
121 ui.debug("keyring URL: %s\n" % keyring_url)
122
122
123 # Checking the memory cache (there may be many http calls per command)
123 # Checking the memory cache (there may be many http calls per command)
124 cache_key = (realm, keyring_url)
124 cache_key = (realm, keyring_url)
125 if not after_bad_auth:
125 if not after_bad_auth:
126 cached_auth = self.pwd_cache.get(cache_key)
126 cached_auth = self.pwd_cache.get(cache_key)
127 if cached_auth:
127 if cached_auth:
128 user, pwd = cached_auth
128 user, pwd = cached_auth
129 self._debug_reply(ui, _("Cached auth data found"),
129 self._debug_reply(ui, _("Cached auth data found"),
130 base_url, user, pwd)
130 base_url, user, pwd)
131 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
131 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
132 return user, pwd
132 return user, pwd
133
133
134 if auth_user:
134 if auth_user:
135 if user:
135 if user and (user != auth_user):
136 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)))
136 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)))
137 user = auth_user
137 user = auth_user
138 if pwd:
138 if pwd:
139 self.pwd_cache[cache_key] = user, pwd
139 self.pwd_cache[cache_key] = user, pwd
140 self._debug_reply(ui, _("Auth data set in .hg/hgrc"),
140 self._debug_reply(ui, _("Auth data set in .hg/hgrc"),
141 base_url, user, pwd)
141 base_url, user, pwd)
142 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
142 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
143 return user, pwd
143 return user, pwd
144 else:
144 else:
145 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
145 ui.debug(_("Username found in .hg/hgrc: %s\n" % user))
146
146
147 # Loading password from keyring.
147 # Loading password from keyring.
148 # Only if username is known (so we know the key) and we are
148 # Only if username is known (so we know the key) and we are
149 # not after failure (so we don't reuse the bad password).
149 # not after failure (so we don't reuse the bad password).
150 if user and not after_bad_auth:
150 if user and not after_bad_auth:
151 pwd = password_store.get_http_password(keyring_url, user)
151 pwd = password_store.get_http_password(keyring_url, user)
152 if pwd:
152 if pwd:
153 self.pwd_cache[cache_key] = user, pwd
153 self.pwd_cache[cache_key] = user, pwd
154 self._debug_reply(ui, _("Keyring password found"),
154 self._debug_reply(ui, _("Keyring password found"),
155 base_url, user, pwd)
155 base_url, user, pwd)
156 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
156 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
157 return user, pwd
157 return user, pwd
158
158
159 # Is the username permanently set?
159 # Is the username permanently set?
160 fixed_user = (user and True or False)
160 fixed_user = (user and True or False)
161
161
162 # Last resort: interactive prompt
162 # Last resort: interactive prompt
163 if not ui.interactive():
163 if not ui.interactive():
164 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
164 raise util.Abort(_('mercurial_keyring: http authorization required but program used in non-interactive mode'))
165
165
166 if not fixed_user:
166 if not fixed_user:
167 ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
167 ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
168
168
169 ui.write(_("http authorization required\n"))
169 ui.write(_("http authorization required\n"))
170 ui.status(_("realm: %s\n") % realm)
170 ui.status(_("realm: %s\n") % realm)
171 if fixed_user:
171 if fixed_user:
172 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
172 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
173 else:
173 else:
174 user = ui.prompt(_("user:"), default=None)
174 user = ui.prompt(_("user:"), default=None)
175 pwd = ui.getpass(_("password: "))
175 pwd = ui.getpass(_("password: "))
176
176
177 if fixed_user:
177 if fixed_user:
178 # Saving password to the keyring.
178 # Saving password to the keyring.
179 # It is done only if username is permanently set.
179 # It is done only if username is permanently set.
180 # Otherwise we won't be able to find the password so it
180 # Otherwise we won't be able to find the password so it
181 # does not make much sense to preserve it
181 # does not make much sense to preserve it
182 ui.debug("Saving password for %s to keyring\n" % user)
182 ui.debug("Saving password for %s to keyring\n" % user)
183 password_store.set_http_password(keyring_url, user, pwd)
183 password_store.set_http_password(keyring_url, user, pwd)
184
184
185 # Saving password to the memory cache
185 # Saving password to the memory cache
186 self.pwd_cache[cache_key] = user, pwd
186 self.pwd_cache[cache_key] = user, pwd
187
187
188 self._debug_reply(ui, _("Manually entered password"),
188 self._debug_reply(ui, _("Manually entered password"),
189 base_url, user, pwd)
189 base_url, user, pwd)
190 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
190 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
191 return user, pwd
191 return user, pwd
192
192
193 def load_hgrc_auth(self, ui, base_url):
193 def load_hgrc_auth(self, ui, base_url):
194 """
194 """
195 Loading [auth] section contents from local .hgrc
195 Loading [auth] section contents from local .hgrc
196
196
197 Returns (username, password, prefix) tuple (every
197 Returns (username, password, prefix) tuple (every
198 element can be None)
198 element can be None)
199 """
199 """
200 # Theoretically 3 lines below should do:
200 # Theoretically 3 lines below should do:
201
201
202 #auth_token = self.readauthtoken(base_url)
202 #auth_token = self.readauthtoken(base_url)
203 #if auth_token:
203 #if auth_token:
204 # user, pwd = auth.get('username'), auth.get('password')
204 # user, pwd = auth.get('username'), auth.get('password')
205
205
206 # Unfortunately they do not work, readauthtoken always return
206 # Unfortunately they do not work, readauthtoken always return
207 # None. Why? Because ui (self.ui of passwordmgr) describes the
207 # None. Why? Because ui (self.ui of passwordmgr) describes the
208 # *remote* repository, so does *not* contain any option from
208 # *remote* repository, so does *not* contain any option from
209 # local .hg/hgrc.
209 # local .hg/hgrc.
210
210
211 # TODO: mercurial 1.4.2 is claimed to resolve this problem
211 # TODO: mercurial 1.4.2 is claimed to resolve this problem
212 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
212 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
213 # so since this version workaround implemented below should
213 # so since this version workaround implemented below should
214 # not be necessary. As it will take some time until people
214 # not be necessary. As it will take some time until people
215 # migrate to >= 1.4.2, it would be best to implement
215 # migrate to >= 1.4.2, it would be best to implement
216 # workaround conditionally.
216 # workaround conditionally.
217
217
218 # Workaround: we recreate the repository object
218 # Workaround: we recreate the repository object
219 repo_root = ui.config("bundle", "mainreporoot")
219 repo_root = ui.config("bundle", "mainreporoot")
220
220
221 from mercurial.ui import ui as _ui
221 from mercurial.ui import ui as _ui
222 local_ui = _ui(ui)
222 local_ui = _ui(ui)
223 if repo_root:
223 if repo_root:
224 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
224 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
225 local_passwordmgr = passwordmgr(local_ui)
225 local_passwordmgr = passwordmgr(local_ui)
226 auth_token = local_passwordmgr.readauthtoken(base_url)
226 auth_token = local_passwordmgr.readauthtoken(base_url)
227 if auth_token:
227 if auth_token:
228 username = auth_token.get('username')
228 username = auth_token.get('username')
229 password = auth_token.get('password')
229 password = auth_token.get('password')
230 prefix = auth_token.get('prefix')
230 prefix = auth_token.get('prefix')
231 shortest_url = self.shortest_url(base_url, prefix)
231 shortest_url = self.shortest_url(base_url, prefix)
232 return username, password, shortest_url
232 return username, password, shortest_url
233
233
234 return None, None, None
234 return None, None, None
235
235
236 def shortest_url(self, base_url, prefix):
236 def shortest_url(self, base_url, prefix):
237 if not prefix or prefix == '*':
237 if not prefix or prefix == '*':
238 return base_url
238 return base_url
239 scheme, hostpath = base_url.split('://', 1)
239 scheme, hostpath = base_url.split('://', 1)
240 p = prefix.split('://', 1)
240 p = prefix.split('://', 1)
241 if len(p) > 1:
241 if len(p) > 1:
242 prefix_host_path = p[1]
242 prefix_host_path = p[1]
243 else:
243 else:
244 prefix_host_path = prefix
244 prefix_host_path = prefix
245 shortest_url = scheme + '://' + prefix_host_path
245 shortest_url = scheme + '://' + prefix_host_path
246 return shortest_url
246 return shortest_url
247
247
248 def canonical_url(self, authuri):
248 def canonical_url(self, authuri):
249 """
249 """
250 Strips query params from url. Used to convert urls like
250 Strips query params from url. Used to convert urls like
251 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
251 https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
252 to
252 to
253 https://repo.machine.com/repos/apps/module
253 https://repo.machine.com/repos/apps/module
254 """
254 """
255 parsed_url = urlparse(authuri)
255 parsed_url = urlparse(authuri)
256 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
256 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
257 parsed_url.path)
257 parsed_url.path)
258
258
259 def _debug_reply(self, ui, msg, url, user, pwd):
259 def _debug_reply(self, ui, msg, url, user, pwd):
260 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
260 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
261 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
261 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
262
262
263 ############################################################
263 ############################################################
264
264
265 @monkeypatch_method(passwordmgr)
265 @monkeypatch_method(passwordmgr)
266 def find_user_password(self, realm, authuri):
266 def find_user_password(self, realm, authuri):
267 """
267 """
268 keyring-based implementation of username/password query
268 keyring-based implementation of username/password query
269 for HTTP(S) connections
269 for HTTP(S) connections
270
270
271 Passwords are saved in gnome keyring, OSX/Chain or other platform
271 Passwords are saved in gnome keyring, OSX/Chain or other platform
272 specific storage and keyed by the repository url
272 specific storage and keyed by the repository url
273 """
273 """
274 # Extend object attributes
274 # Extend object attributes
275 if not hasattr(self, '_pwd_handler'):
275 if not hasattr(self, '_pwd_handler'):
276 self._pwd_handler = HTTPPasswordHandler()
276 self._pwd_handler = HTTPPasswordHandler()
277
277
278 return self._pwd_handler.find_auth(self, realm, authuri)
278 return self._pwd_handler.find_auth(self, realm, authuri)
279
279
280 ############################################################
280 ############################################################
281
281
282 def try_smtp_login(ui, smtp_obj, username, password):
282 def try_smtp_login(ui, smtp_obj, username, password):
283 """
283 """
284 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
284 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
285 password.
285 password.
286
286
287 Returns:
287 Returns:
288 - True if login succeeded
288 - True if login succeeded
289 - False if login failed due to the wrong credentials
289 - False if login failed due to the wrong credentials
290
290
291 Throws Abort exception if login failed for any other reason.
291 Throws Abort exception if login failed for any other reason.
292
292
293 Immediately returns False if password is empty
293 Immediately returns False if password is empty
294 """
294 """
295 if not password:
295 if not password:
296 return False
296 return False
297 try:
297 try:
298 ui.note(_('(authenticating to mail server as %s)\n') %
298 ui.note(_('(authenticating to mail server as %s)\n') %
299 (username))
299 (username))
300 smtp_obj.login(username, password)
300 smtp_obj.login(username, password)
301 return True
301 return True
302 except smtplib.SMTPException, inst:
302 except smtplib.SMTPException, inst:
303 if inst.smtp_code == 535:
303 if inst.smtp_code == 535:
304 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
304 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
305 return False
305 return False
306 else:
306 else:
307 raise util.Abort(inst)
307 raise util.Abort(inst)
308
308
309 def keyring_supported_smtp(ui, username):
309 def keyring_supported_smtp(ui, username):
310 """
310 """
311 keyring-integrated replacement for mercurial.mail._smtp
311 keyring-integrated replacement for mercurial.mail._smtp
312 Used only when configuration file contains username, but
312 Used only when configuration file contains username, but
313 does not contain the password.
313 does not contain the password.
314
314
315 Most of the routine below is copied as-is from
315 Most of the routine below is copied as-is from
316 mercurial.mail._smtp. The only changed part is
316 mercurial.mail._smtp. The only changed part is
317 marked with #>>>>> and #<<<<< markers
317 marked with #>>>>> and #<<<<< markers
318 """
318 """
319 local_hostname = ui.config('smtp', 'local_hostname')
319 local_hostname = ui.config('smtp', 'local_hostname')
320 s = smtplib.SMTP(local_hostname=local_hostname)
320 s = smtplib.SMTP(local_hostname=local_hostname)
321 mailhost = ui.config('smtp', 'host')
321 mailhost = ui.config('smtp', 'host')
322 if not mailhost:
322 if not mailhost:
323 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
323 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
324 mailport = int(ui.config('smtp', 'port', 25))
324 mailport = int(ui.config('smtp', 'port', 25))
325 ui.note(_('sending mail: smtp host %s, port %s\n') %
325 ui.note(_('sending mail: smtp host %s, port %s\n') %
326 (mailhost, mailport))
326 (mailhost, mailport))
327 s.connect(host=mailhost, port=mailport)
327 s.connect(host=mailhost, port=mailport)
328 if ui.configbool('smtp', 'tls'):
328 if ui.configbool('smtp', 'tls'):
329 if not hasattr(socket, 'ssl'):
329 if not hasattr(socket, 'ssl'):
330 raise util.Abort(_("can't use TLS: Python SSL support "
330 raise util.Abort(_("can't use TLS: Python SSL support "
331 "not installed"))
331 "not installed"))
332 ui.note(_('(using tls)\n'))
332 ui.note(_('(using tls)\n'))
333 s.ehlo()
333 s.ehlo()
334 s.starttls()
334 s.starttls()
335 s.ehlo()
335 s.ehlo()
336
336
337 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
337 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
338 stored = password = password_store.get_smtp_password(
338 stored = password = password_store.get_smtp_password(
339 mailhost, mailport, username)
339 mailhost, mailport, username)
340 # No need to check whether password was found as try_smtp_login
340 # No need to check whether password was found as try_smtp_login
341 # just returns False if it is absent.
341 # just returns False if it is absent.
342 while not try_smtp_login(ui, s, username, password):
342 while not try_smtp_login(ui, s, username, password):
343 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
343 password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
344
344
345 if stored != password:
345 if stored != password:
346 password_store.set_smtp_password(
346 password_store.set_smtp_password(
347 mailhost, mailport, username, password)
347 mailhost, mailport, username, password)
348 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
348 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
349
349
350 def send(sender, recipients, msg):
350 def send(sender, recipients, msg):
351 try:
351 try:
352 return s.sendmail(sender, recipients, msg)
352 return s.sendmail(sender, recipients, msg)
353 except smtplib.SMTPRecipientsRefused, inst:
353 except smtplib.SMTPRecipientsRefused, inst:
354 recipients = [r[1] for r in inst.recipients.values()]
354 recipients = [r[1] for r in inst.recipients.values()]
355 raise util.Abort('\n' + '\n'.join(recipients))
355 raise util.Abort('\n' + '\n'.join(recipients))
356 except smtplib.SMTPException, inst:
356 except smtplib.SMTPException, inst:
357 raise util.Abort(inst)
357 raise util.Abort(inst)
358
358
359 return send
359 return send
360
360
361 ############################################################
361 ############################################################
362
362
363 orig_smtp = mail._smtp
363 orig_smtp = mail._smtp
364
364
365 @monkeypatch_method(mail)
365 @monkeypatch_method(mail)
366 def _smtp(ui):
366 def _smtp(ui):
367 """
367 """
368 build an smtp connection and return a function to send email
368 build an smtp connection and return a function to send email
369
369
370 This is the monkeypatched version of _smtp(ui) function from
370 This is the monkeypatched version of _smtp(ui) function from
371 mercurial/mail.py. It calls the original unless username
371 mercurial/mail.py. It calls the original unless username
372 without password is given in the configuration.
372 without password is given in the configuration.
373 """
373 """
374 username = ui.config('smtp', 'username')
374 username = ui.config('smtp', 'username')
375 password = ui.config('smtp', 'password')
375 password = ui.config('smtp', 'password')
376
376
377 if username and not password:
377 if username and not password:
378 return keyring_supported_smtp(ui, username)
378 return keyring_supported_smtp(ui, username)
379 else:
379 else:
380 return orig_smtp(ui)
380 return orig_smtp(ui)
General Comments 0
You need to be logged in to leave comments. Login now