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