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