##// END OF EJS Templates
Explicit information that keyring is not used due to lack of username
Marcin Kasperski -
r52:9cb5c985 default
parent child Browse files
Show More
@@ -1,376 +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:
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
166 if not fixed_user:
167 ui.status(_("Username not specified in .hg/hgrc. Keyring will not be used.\n"))
168
165 ui.write(_("http authorization required\n"))
169 ui.write(_("http authorization required\n"))
166 ui.status(_("realm: %s\n") % realm)
170 ui.status(_("realm: %s\n") % realm)
167 if fixed_user:
171 if fixed_user:
168 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
172 ui.write(_("user: %s (fixed in .hg/hgrc)\n" % user))
169 else:
173 else:
170 user = ui.prompt(_("user:"), default=None)
174 user = ui.prompt(_("user:"), default=None)
171 pwd = ui.getpass(_("password: "))
175 pwd = ui.getpass(_("password: "))
172
176
173 if fixed_user:
177 if fixed_user:
174 # Saving password to the keyring.
178 # Saving password to the keyring.
175 # It is done only if username is permanently set.
179 # It is done only if username is permanently set.
176 # 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
177 # does not make much sense to preserve it
181 # does not make much sense to preserve it
178 ui.debug("Saving password for %s to keyring\n" % user)
182 ui.debug("Saving password for %s to keyring\n" % user)
179 password_store.set_http_password(keyring_url, user, pwd)
183 password_store.set_http_password(keyring_url, user, pwd)
180
184
181 # Saving password to the memory cache
185 # Saving password to the memory cache
182 self.pwd_cache[cache_key] = user, pwd
186 self.pwd_cache[cache_key] = user, pwd
183
187
184 self._debug_reply(ui, _("Manually entered password"),
188 self._debug_reply(ui, _("Manually entered password"),
185 base_url, user, pwd)
189 base_url, user, pwd)
186 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
190 self.last_reply = dict(realm=realm,authuri=authuri,user=user)
187 return user, pwd
191 return user, pwd
188
192
189 def load_hgrc_auth(self, ui, base_url):
193 def load_hgrc_auth(self, ui, base_url):
190 """
194 """
191 Loading [auth] section contents from local .hgrc
195 Loading [auth] section contents from local .hgrc
192
196
193 Returns (username, password, prefix) tuple (every
197 Returns (username, password, prefix) tuple (every
194 element can be None)
198 element can be None)
195 """
199 """
196 # Theoretically 3 lines below should do:
200 # Theoretically 3 lines below should do:
197
201
198 #auth_token = self.readauthtoken(base_url)
202 #auth_token = self.readauthtoken(base_url)
199 #if auth_token:
203 #if auth_token:
200 # user, pwd = auth.get('username'), auth.get('password')
204 # user, pwd = auth.get('username'), auth.get('password')
201
205
202 # Unfortunately they do not work, readauthtoken always return
206 # Unfortunately they do not work, readauthtoken always return
203 # None. Why? Because ui (self.ui of passwordmgr) describes the
207 # None. Why? Because ui (self.ui of passwordmgr) describes the
204 # *remote* repository, so does *not* contain any option from
208 # *remote* repository, so does *not* contain any option from
205 # local .hg/hgrc.
209 # local .hg/hgrc.
206
210
207 # TODO: mercurial 1.4.2 is claimed to resolve this problem
211 # TODO: mercurial 1.4.2 is claimed to resolve this problem
208 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
212 # (thanks to: http://hg.xavamedia.nl/mercurial/crew/rev/fb45c1e4396f)
209 # so since this version workaround implemented below should
213 # so since this version workaround implemented below should
210 # not be necessary. As it will take some time until people
214 # not be necessary. As it will take some time until people
211 # migrate to >= 1.4.2, it would be best to implement
215 # migrate to >= 1.4.2, it would be best to implement
212 # workaround conditionally.
216 # workaround conditionally.
213
217
214 # Workaround: we recreate the repository object
218 # Workaround: we recreate the repository object
215 repo_root = ui.config("bundle", "mainreporoot")
219 repo_root = ui.config("bundle", "mainreporoot")
216
220
217 from mercurial.ui import ui as _ui
221 from mercurial.ui import ui as _ui
218 local_ui = _ui(ui)
222 local_ui = _ui(ui)
219 if repo_root:
223 if repo_root:
220 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
224 local_ui.readconfig(os.path.join(repo_root, ".hg", "hgrc"))
221 local_passwordmgr = passwordmgr(local_ui)
225 local_passwordmgr = passwordmgr(local_ui)
222 auth_token = local_passwordmgr.readauthtoken(base_url)
226 auth_token = local_passwordmgr.readauthtoken(base_url)
223 if auth_token:
227 if auth_token:
224 username = auth_token.get('username')
228 username = auth_token.get('username')
225 password = auth_token.get('password')
229 password = auth_token.get('password')
226 prefix = auth_token.get('prefix')
230 prefix = auth_token.get('prefix')
227 shortest_url = self.shortest_url(base_url, prefix)
231 shortest_url = self.shortest_url(base_url, prefix)
228 return username, password, shortest_url
232 return username, password, shortest_url
229
233
230 return None, None, None
234 return None, None, None
231
235
232 def shortest_url(self, base_url, prefix):
236 def shortest_url(self, base_url, prefix):
233 if not prefix or prefix == '*':
237 if not prefix or prefix == '*':
234 return base_url
238 return base_url
235 scheme, hostpath = base_url.split('://', 1)
239 scheme, hostpath = base_url.split('://', 1)
236 p = prefix.split('://', 1)
240 p = prefix.split('://', 1)
237 if len(p) > 1:
241 if len(p) > 1:
238 prefix_host_path = p[1]
242 prefix_host_path = p[1]
239 else:
243 else:
240 prefix_host_path = prefix
244 prefix_host_path = prefix
241 shortest_url = scheme + '://' + prefix_host_path
245 shortest_url = scheme + '://' + prefix_host_path
242 return shortest_url
246 return shortest_url
243
247
244 def canonical_url(self, authuri):
248 def canonical_url(self, authuri):
245 """
249 """
246 Strips query params from url. Used to convert urls like
250 Strips query params from url. Used to convert urls like
247 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
248 to
252 to
249 https://repo.machine.com/repos/apps/module
253 https://repo.machine.com/repos/apps/module
250 """
254 """
251 parsed_url = urlparse(authuri)
255 parsed_url = urlparse(authuri)
252 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
256 return "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
253 parsed_url.path)
257 parsed_url.path)
254
258
255 def _debug_reply(self, ui, msg, url, user, pwd):
259 def _debug_reply(self, ui, msg, url, user, pwd):
256 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
260 ui.debug("%s. Url: %s, user: %s, passwd: %s\n" % (
257 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
261 msg, url, user, pwd and '*' * len(pwd) or 'not set'))
258
262
259 ############################################################
263 ############################################################
260
264
261 @monkeypatch_method(passwordmgr)
265 @monkeypatch_method(passwordmgr)
262 def find_user_password(self, realm, authuri):
266 def find_user_password(self, realm, authuri):
263 """
267 """
264 keyring-based implementation of username/password query
268 keyring-based implementation of username/password query
265 for HTTP(S) connections
269 for HTTP(S) connections
266
270
267 Passwords are saved in gnome keyring, OSX/Chain or other platform
271 Passwords are saved in gnome keyring, OSX/Chain or other platform
268 specific storage and keyed by the repository url
272 specific storage and keyed by the repository url
269 """
273 """
270 # Extend object attributes
274 # Extend object attributes
271 if not hasattr(self, '_pwd_handler'):
275 if not hasattr(self, '_pwd_handler'):
272 self._pwd_handler = HTTPPasswordHandler()
276 self._pwd_handler = HTTPPasswordHandler()
273
277
274 return self._pwd_handler.find_auth(self, realm, authuri)
278 return self._pwd_handler.find_auth(self, realm, authuri)
275
279
276 ############################################################
280 ############################################################
277
281
278 def try_smtp_login(ui, smtp_obj, username, password):
282 def try_smtp_login(ui, smtp_obj, username, password):
279 """
283 """
280 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
284 Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
281 password.
285 password.
282
286
283 Returns:
287 Returns:
284 - True if login succeeded
288 - True if login succeeded
285 - False if login failed due to the wrong credentials
289 - False if login failed due to the wrong credentials
286
290
287 Throws Abort exception if login failed for any other reason.
291 Throws Abort exception if login failed for any other reason.
288
292
289 Immediately returns False if password is empty
293 Immediately returns False if password is empty
290 """
294 """
291 if not password:
295 if not password:
292 return False
296 return False
293 try:
297 try:
294 ui.note(_('(authenticating to mail server as %s)\n') %
298 ui.note(_('(authenticating to mail server as %s)\n') %
295 (username))
299 (username))
296 smtp_obj.login(username, password)
300 smtp_obj.login(username, password)
297 return True
301 return True
298 except smtplib.SMTPException, inst:
302 except smtplib.SMTPException, inst:
299 if inst.smtp_code == 535:
303 if inst.smtp_code == 535:
300 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
304 ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
301 return False
305 return False
302 else:
306 else:
303 raise util.Abort(inst)
307 raise util.Abort(inst)
304
308
305 def keyring_supported_smtp(ui, username):
309 def keyring_supported_smtp(ui, username):
306 """
310 """
307 keyring-integrated replacement for mercurial.mail._smtp
311 keyring-integrated replacement for mercurial.mail._smtp
308 Used only when configuration file contains username, but
312 Used only when configuration file contains username, but
309 does not contain the password.
313 does not contain the password.
310
314
311 Most of the routine below is copied as-is from
315 Most of the routine below is copied as-is from
312 mercurial.mail._smtp. The only changed part is
316 mercurial.mail._smtp. The only changed part is
313 marked with #>>>>> and #<<<<< markers
317 marked with #>>>>> and #<<<<< markers
314 """
318 """
315 local_hostname = ui.config('smtp', 'local_hostname')
319 local_hostname = ui.config('smtp', 'local_hostname')
316 s = smtplib.SMTP(local_hostname=local_hostname)
320 s = smtplib.SMTP(local_hostname=local_hostname)
317 mailhost = ui.config('smtp', 'host')
321 mailhost = ui.config('smtp', 'host')
318 if not mailhost:
322 if not mailhost:
319 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
323 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
320 mailport = int(ui.config('smtp', 'port', 25))
324 mailport = int(ui.config('smtp', 'port', 25))
321 ui.note(_('sending mail: smtp host %s, port %s\n') %
325 ui.note(_('sending mail: smtp host %s, port %s\n') %
322 (mailhost, mailport))
326 (mailhost, mailport))
323 s.connect(host=mailhost, port=mailport)
327 s.connect(host=mailhost, port=mailport)
324 if ui.configbool('smtp', 'tls'):
328 if ui.configbool('smtp', 'tls'):
325 if not hasattr(socket, 'ssl'):
329 if not hasattr(socket, 'ssl'):
326 raise util.Abort(_("can't use TLS: Python SSL support "
330 raise util.Abort(_("can't use TLS: Python SSL support "
327 "not installed"))
331 "not installed"))
328 ui.note(_('(using tls)\n'))
332 ui.note(_('(using tls)\n'))
329 s.ehlo()
333 s.ehlo()
330 s.starttls()
334 s.starttls()
331 s.ehlo()
335 s.ehlo()
332
336
333 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
337 #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
334 stored = password = password_store.get_smtp_password(
338 stored = password = password_store.get_smtp_password(
335 mailhost, mailport, username)
339 mailhost, mailport, username)
336 # 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
337 # just returns False if it is absent.
341 # just returns False if it is absent.
338 while not try_smtp_login(ui, s, username, password):
342 while not try_smtp_login(ui, s, username, password):
339 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))
340
344
341 if stored != password:
345 if stored != password:
342 password_store.set_smtp_password(
346 password_store.set_smtp_password(
343 mailhost, mailport, username, password)
347 mailhost, mailport, username, password)
344 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
348 #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
345
349
346 def send(sender, recipients, msg):
350 def send(sender, recipients, msg):
347 try:
351 try:
348 return s.sendmail(sender, recipients, msg)
352 return s.sendmail(sender, recipients, msg)
349 except smtplib.SMTPRecipientsRefused, inst:
353 except smtplib.SMTPRecipientsRefused, inst:
350 recipients = [r[1] for r in inst.recipients.values()]
354 recipients = [r[1] for r in inst.recipients.values()]
351 raise util.Abort('\n' + '\n'.join(recipients))
355 raise util.Abort('\n' + '\n'.join(recipients))
352 except smtplib.SMTPException, inst:
356 except smtplib.SMTPException, inst:
353 raise util.Abort(inst)
357 raise util.Abort(inst)
354
358
355 return send
359 return send
356
360
357 ############################################################
361 ############################################################
358
362
359 orig_smtp = mail._smtp
363 orig_smtp = mail._smtp
360
364
361 @monkeypatch_method(mail)
365 @monkeypatch_method(mail)
362 def _smtp(ui):
366 def _smtp(ui):
363 """
367 """
364 build an smtp connection and return a function to send email
368 build an smtp connection and return a function to send email
365
369
366 This is the monkeypatched version of _smtp(ui) function from
370 This is the monkeypatched version of _smtp(ui) function from
367 mercurial/mail.py. It calls the original unless username
371 mercurial/mail.py. It calls the original unless username
368 without password is given in the configuration.
372 without password is given in the configuration.
369 """
373 """
370 username = ui.config('smtp', 'username')
374 username = ui.config('smtp', 'username')
371 password = ui.config('smtp', 'password')
375 password = ui.config('smtp', 'password')
372
376
373 if username and not password:
377 if username and not password:
374 return keyring_supported_smtp(ui, username)
378 return keyring_supported_smtp(ui, username)
375 else:
379 else:
376 return orig_smtp(ui)
380 return orig_smtp(ui)
General Comments 0
You need to be logged in to leave comments. Login now