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