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