/usr/lib/python2.7/dist-packages/mercurial_keyring.py is in mercurial-keyring 1.1.7-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 | # -*- coding: utf-8 -*-
#
# mercurial_keyring: save passwords in password database
#
# Copyright (c) 2009 Marcin Kasperski <Marcin.Kasperski@mekk.waw.pl>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# See README.txt for more details.
'''securely save HTTP and SMTP passwords to encrypted storage
mercurial_keyring securely saves HTTP and SMTP passwords in password
databases (Gnome Keyring, KDE KWallet, OSXKeyChain, Win32 crypto
services).
The process is automatic. Whenever bare Mercurial just prompts for
the password, Mercurial with mercurial_keyring enabled checks whether
saved password is available first. If so, it is used. If not, you
will be prompted for the password, but entered password will be
saved for the future use.
In case saved password turns out to be invalid (HTTP or SMTP login
fails) it is dropped, and you are asked for current password.
Actual password storage is implemented by Python keyring library, this
extension glues those services to Mercurial. Consult keyring
documentation for information how to configure actual password
backend (by default keyring guesses, usually correctly, for example
you get KDE Wallet under KDE, and Gnome Keyring under Gnome or Unity).
'''
import urllib2
import smtplib
import socket
import os
import sys
import re
from mercurial import util, sslutil
from mercurial.i18n import _
from mercurial.url import passwordmgr
from mercurial import mail
from mercurial.mail import SMTPS, STARTTLS
from mercurial import encoding
# pylint: disable=invalid-name, line-too-long, protected-access, too-many-arguments
###########################################################################
# Specific import trickery
###########################################################################
def import_meu():
"""
Convoluted import of mercurial_extension_utils, which helps
TortoiseHg/Win setups. This routine and it's use below
performs equivalent of
from mercurial_extension_utils import monkeypatch_method
but looks for some non-path directories.
"""
try:
import mercurial_extension_utils
except ImportError:
my_dir = os.path.dirname(__file__)
sys.path.extend([
# In the same dir (manual or site-packages after pip)
my_dir,
# Developer clone
os.path.join(os.path.dirname(my_dir), "extension_utils"),
# Side clone
os.path.join(os.path.dirname(my_dir), "mercurial-extension_utils"),
])
try:
import mercurial_extension_utils
except ImportError:
raise util.Abort(_("""Can not import mercurial_extension_utils.
Please install this module in Python path.
See Installation chapter in https://bitbucket.org/Mekk/mercurial_keyring/ for details
(and for info about TortoiseHG on Windows, or other bundled Python)."""))
return mercurial_extension_utils
meu = import_meu()
monkeypatch_method = meu.monkeypatch_method
def import_keyring():
"""
Importing keyring happens to be costly if wallet is slow, so we delay it
until it is really needed. The routine below also works around various
demandimport-related problems.
"""
# mercurial.demandimport incompatibility workaround.
# various keyring backends fail as they can't properly import helper
# modules (as demandimport modifies python import behaviour).
# If you get import errors with demandimport in backtrace, try
# guessing what to block and extending the list below.
mod, was_imported_now = meu.direct_import_ext(
"keyring", [
"gobject._gobject",
"configparser",
"json",
"abc",
"io",
"keyring",
"gdata.docs.service",
"gdata.service",
"types",
"atom.http",
"atom.http_interface",
"atom.service",
"atom.token_store",
"ctypes",
"secretstorage.exceptions",
"fs.opener",
])
if was_imported_now:
# Shut up warning about uninitialized logging by keyring
meu.disable_logging("keyring")
return mod
#################################################################
# Actual implementation
#################################################################
KEYRING_SERVICE = "Mercurial"
class PasswordStore(object):
"""
Helper object handling keyring usage (password save&restore,
the way passwords are keyed in the keyring).
"""
def __init__(self):
self.cache = dict()
def get_http_password(self, url, username):
"""
Checks whether password of username for url is available,
returns it or None
"""
return self._read_password_from_keyring(
self._format_http_key(url, username))
def set_http_password(self, url, username, password):
"""Saves password to keyring"""
self._save_password_to_keyring(
self._format_http_key(url, username),
password)
def clear_http_password(self, url, username):
"""Drops saved password"""
self.set_http_password(url, username, "")
@staticmethod
def _format_http_key(url, username):
"""Construct actual key for password identification"""
return "%s@@%s" % (username, url)
def get_smtp_password(self, machine, port, username):
"""Checks for SMTP password in keyring, returns
password or None"""
return self._read_password_from_keyring(
self._format_smtp_key(machine, port, username))
def set_smtp_password(self, machine, port, username, password):
"""Saves SMTP password to keyring"""
self._save_password_to_keyring(
self._format_smtp_key(machine, port, username),
password)
def clear_smtp_password(self, machine, port, username):
"""Drops saved SMTP password"""
self.set_smtp_password(machine, port, username, "")
@staticmethod
def _format_smtp_key(machine, port, username):
"""Construct key for SMTP password identification"""
return "%s@@%s:%s" % (username, machine, str(port))
@staticmethod
def _read_password_from_keyring(pwdkey):
"""Physically read from keyring"""
keyring = import_keyring()
password = keyring.get_password(KEYRING_SERVICE, pwdkey)
# Reverse recoding from next routine
if isinstance(password, unicode):
return encoding.tolocal(password.encode('utf-8'))
return password
@staticmethod
def _save_password_to_keyring(pwdkey, password):
"""Physically write to keyring"""
keyring = import_keyring()
# keyring in general expects unicode.
# Mercurial provides "local" encoding. See #33
password = encoding.fromlocal(password).decode('utf-8')
keyring.set_password(
KEYRING_SERVICE, pwdkey, password)
password_store = PasswordStore()
############################################################
# Various utils
############################################################
def _debug(ui, msg):
"""Generic debug message"""
ui.debug("keyring: " + msg + "\n")
class PwdCache(object):
"""Short term cache, used to preserve passwords
if they are used twice during a command"""
def __init__(self):
self._cache = {}
def store(self, realm, url, user, pwd):
"""Saves password"""
cache_key = (realm, url, user)
self._cache[cache_key] = pwd
def check(self, realm, url, user):
"""Checks for cached password"""
cache_key = (realm, url, user)
return self._cache.get(cache_key)
_re_http_url = re.compile(r'^https?://')
def is_http_path(url):
return bool(_re_http_url.search(url))
def make_passwordmgr(ui):
"""Constructing passwordmgr in a way compatible with various mercurials"""
if hasattr(ui, 'httppasswordmgrdb'):
return passwordmgr(ui, ui.httppasswordmgrdb)
else:
return passwordmgr(ui)
############################################################
# HTTP password management
############################################################
class HTTPPasswordHandler(object):
"""
Actual implementation of password handling (user prompting,
configuration file searching, keyring save&restore).
Object of this class is bound as passwordmgr attribute.
"""
def __init__(self):
self.pwd_cache = PwdCache()
self.last_reply = None
# Markers and also names used in debug notes. Password source
SRC_URL = "repository URL"
SRC_CFGAUTH = "hgrc"
SRC_MEMCACHE = "temporary cache"
SRC_URLCACHE = "urllib temporary cache"
SRC_KEYRING = "keyring"
def get_credentials(self, pwmgr, realm, authuri, skip_caches=False):
"""
Looks up for user credentials in various places, returns them
and information about their source.
Used internally inside find_auth and inside informative
commands (thiis method doesn't cache, doesn't detect bad
passwords etc, doesn't prompt interactively, doesn't store
password in keyring).
Returns: user, password, SRC_*, actual_url
If not found, password and SRC is None, user can be given or
not, url is always set
"""
ui = pwmgr.ui
parsed_url, url_user, url_passwd = self.unpack_url(authuri)
base_url = str(parsed_url)
ui.debug(_('keyring: base url: %s, url user: %s, url pwd: %s\n') %
(base_url, url_user or '', url_passwd and '******' or ''))
# Extract username (or password) stored directly in url
if url_user and url_passwd:
return url_user, url_passwd, self.SRC_URL, base_url
# Extract data from urllib (in case it was already stored)
if isinstance(pwmgr, urllib2.HTTPPasswordMgrWithDefaultRealm):
urllib_user, urllib_pwd = \
urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
pwmgr, realm, authuri)
else:
urllib_user, urllib_pwd = pwmgr.passwddb.find_user_password(
realm, authuri)
if urllib_user and urllib_pwd:
return urllib_user, urllib_pwd, self.SRC_URLCACHE, base_url
actual_user = url_user or urllib_user
# Consult configuration to normalize url to prefix, and find username
# (and maybe password)
auth_user, auth_pwd, keyring_url = self.get_url_config(
ui, parsed_url, actual_user)
if auth_user and actual_user and (actual_user != auth_user):
raise util.Abort(_('keyring: username for %s specified both in repository path (%s) and in .hg/hgrc/[auth] (%s). Please, leave only one of those' % (base_url, actual_user, auth_user)))
if auth_user and auth_pwd:
return auth_user, auth_pwd, self.SRC_CFGAUTH, keyring_url
actual_user = actual_user or auth_user
if skip_caches:
return actual_user, None, None, keyring_url
# Check memory cache (reuse )
# Checking the memory cache (there may be many http calls per command)
cached_pwd = self.pwd_cache.check(realm, keyring_url, actual_user)
if cached_pwd:
return actual_user, cached_pwd, self.SRC_MEMCACHE, keyring_url
# Load from keyring.
if actual_user:
ui.debug(_("keyring: looking for password (user %s, url %s)\n") % (actual_user, keyring_url))
keyring_pwd = password_store.get_http_password(keyring_url, actual_user)
if keyring_pwd:
return actual_user, keyring_pwd, self.SRC_KEYRING, keyring_url
return actual_user, None, None, keyring_url
@staticmethod
def prompt_interactively(ui, user, realm, url):
"""Actual interactive prompt"""
if not ui.interactive():
raise util.Abort(_('keyring: http authorization required but program used in non-interactive mode'))
if not user:
ui.status(_("keyring: username not specified in hgrc (or in url). Password will not be saved.\n"))
ui.write(_("http authorization required\n"))
ui.status(_("realm: %s\n") % realm)
ui.status(_("url: %s\n") % url)
if user:
ui.write(_("user: %s (fixed in hgrc or url)\n" % user))
else:
user = ui.prompt(_("user:"), default=None)
pwd = ui.getpass(_("password: "))
return user, pwd
def find_auth(self, pwmgr, realm, authuri, req):
"""
Actual implementation of find_user_password - different
ways of obtaining the username and password.
Returns pair username, password
"""
ui = pwmgr.ui
after_bad_auth = self._after_bad_auth(ui, realm, authuri, req)
# Look in url, cache, etc
user, pwd, src, final_url = self.get_credentials(
pwmgr, realm, authuri, skip_caches=after_bad_auth)
if pwd:
if src != self.SRC_MEMCACHE:
self.pwd_cache.store(realm, final_url, user, pwd)
self._note_last_reply(realm, authuri, user, req)
_debug(ui, _("Password found in " + src))
return user, pwd
# Last resort: interactive prompt
user, pwd = self.prompt_interactively(ui, user, realm, final_url)
if user:
# Saving password to the keyring.
# It is done only if username is permanently set.
# Otherwise we won't be able to find the password so it
# does not make much sense to preserve it
_debug(ui, _("Saving password for %s to keyring") % user)
try:
password_store.set_http_password(final_url, user, pwd)
except Exception, e:
keyring = import_keyring()
if isinstance(e, keyring.errors.PasswordSetError):
ui.traceback()
ui.warn(_("warning: failed to save password in keyring\n"))
else:
raise e
# Saving password to the memory cache
self.pwd_cache.store(realm, final_url, user, pwd)
self._note_last_reply(realm, authuri, user, req)
_debug(ui, _("Manually entered password"))
return user, pwd
def get_url_config(self, ui, parsed_url, user):
"""
Checks configuration to decide whether/which username, prefix,
and password are configured for given url. Consults [auth] section.
Returns tuple (username, password, prefix) containing elements
found. username and password can be None (if unset), if prefix
is not found, url itself is returned.
"""
base_url = str(parsed_url)
from mercurial.httpconnection import readauthforuri
_debug(ui, _("Checking for hgrc info about url %s, user %s") % (base_url, user))
res = readauthforuri(ui, base_url, user)
# If it user-less version not work, let's try with added username to handle
# both config conventions
if (not res) and user:
parsed_url.user = user
res = readauthforuri(ui, str(parsed_url), user)
parsed_url.user = None
if res:
group, auth_token = res
else:
auth_token = None
if auth_token:
username = auth_token.get('username')
password = auth_token.get('password')
prefix = auth_token.get('prefix')
else:
username = user
password = None
prefix = None
password_url = self.password_url(base_url, prefix)
_debug(ui, _("Password url: %s, user: %s, password: %s (prefix: %s)") % (
password_url, username, '********' if password else '', prefix))
return username, password, password_url
def _note_last_reply(self, realm, authuri, user, req):
"""
Internal helper. Saves info about auth-data obtained,
preserves them in last_reply, and returns pair user, pwd
"""
self.last_reply = dict(realm=realm, authuri=authuri,
user=user, req=req)
def _after_bad_auth(self, ui, realm, authuri, req):
"""
If we are called again just after identical previous
request, then the previously returned auth must have been
wrong. So we note this to force password prompt (and avoid
reusing bad password indefinitely).
This routine checks for this condition.
"""
if self.last_reply:
if (self.last_reply['realm'] == realm) \
and (self.last_reply['authuri'] == authuri) \
and (self.last_reply['req'] == req):
_debug(ui, _("Working after bad authentication, cached passwords not used %s") % str(self.last_reply))
return True
return False
@staticmethod
def password_url(base_url, prefix):
"""Calculates actual url identifying the password. Takes
configured prefix under consideration (so can be shorter
than repo url)"""
if not prefix or prefix == '*':
return base_url
scheme, hostpath = base_url.split('://', 1)
p = prefix.split('://', 1)
if len(p) > 1:
prefix_host_path = p[1]
else:
prefix_host_path = prefix
password_url = scheme + '://' + prefix_host_path
return password_url
@staticmethod
def unpack_url(authuri):
"""
Takes original url for which authentication is attempted and:
- Strips query params from url. Used to convert urls like
https://repo.machine.com/repos/apps/module?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between
to
https://repo.machine.com/repos/apps/module
- Extracts username and password, if present, and removes them from url
(so prefix matching works properly)
Returns url, user, password
where url is mercurial.util.url object already stripped of all those
params.
"""
# mercurial.util.url, rather handy url parser
parsed_url = util.url(authuri)
parsed_url.query = ''
parsed_url.fragment = None
# Strip arguments to get actual remote repository url.
# base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.netloc,
# parsed_url.path)
user = parsed_url.user
passwd = parsed_url.passwd
parsed_url.user = None
parsed_url.passwd = None
return parsed_url, user, passwd
############################################################
# Mercurial monkey-patching
############################################################
@monkeypatch_method(passwordmgr)
def find_user_password(self, realm, authuri):
"""
keyring-based implementation of username/password query
for HTTP(S) connections
Passwords are saved in gnome keyring, OSX/Chain or other platform
specific storage and keyed by the repository url
"""
# Extend object attributes
if not hasattr(self, '_pwd_handler'):
self._pwd_handler = HTTPPasswordHandler()
if hasattr(self, '_http_req'):
req = self._http_req
else:
req = None
return self._pwd_handler.find_auth(self, realm, authuri, req)
@monkeypatch_method(urllib2.AbstractBasicAuthHandler, "http_error_auth_reqed")
def basic_http_error_auth_reqed(self, authreq, host, req, headers):
"""Preserves current HTTP request so it can be consulted
in find_user_password above"""
self.passwd._http_req = req
try:
return basic_http_error_auth_reqed.orig(self, authreq, host, req, headers)
finally:
self.passwd._http_req = None
@monkeypatch_method(urllib2.AbstractDigestAuthHandler, "http_error_auth_reqed")
def digest_http_error_auth_reqed(self, authreq, host, req, headers):
"""Preserves current HTTP request so it can be consulted
in find_user_password above"""
self.passwd._http_req = req
try:
return digest_http_error_auth_reqed.orig(self, authreq, host, req, headers)
finally:
self.passwd._http_req = None
############################################################
# SMTP support
############################################################
def try_smtp_login(ui, smtp_obj, username, password):
"""
Attempts smtp login on smtp_obj (smtplib.SMTP) using username and
password.
Returns:
- True if login succeeded
- False if login failed due to the wrong credentials
Throws Abort exception if login failed for any other reason.
Immediately returns False if password is empty
"""
if not password:
return False
try:
ui.note(_('(authenticating to mail server as %s)\n') %
(username))
smtp_obj.login(username, password)
return True
except smtplib.SMTPException, inst:
if inst.smtp_code == 535:
ui.status(_("SMTP login failed: %s\n\n") % inst.smtp_error)
return False
else:
raise util.Abort(inst)
def keyring_supported_smtp(ui, username):
"""
keyring-integrated replacement for mercurial.mail._smtp
Used only when configuration file contains username, but
does not contain the password.
Most of the routine below is copied as-is from
mercurial.mail._smtp. The only changed part is
marked with # >>>>> and # <<<<< markers
"""
local_hostname = ui.config('smtp', 'local_hostname')
tls = ui.config('smtp', 'tls', 'none')
# backward compatible: when tls = true, we use starttls.
starttls = tls == 'starttls' or util.parsebool(tls)
smtps = tls == 'smtps'
if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
raise util.Abort(_("can't use TLS: Python SSL support not installed"))
mailhost = ui.config('smtp', 'host')
if not mailhost:
raise util.Abort(_('smtp.host not configured - cannot send mail'))
verifycert = ui.config('smtp', 'verifycert', 'strict')
if verifycert not in ['strict', 'loose']:
if util.parsebool(verifycert) is not False:
raise util.Abort(_('invalid smtp.verifycert configuration: %s')
% (verifycert))
verifycert = False
if getattr(sslutil, 'sslkwargs', None) is None:
sslkwargs = None
elif (starttls or smtps) and verifycert:
sslkwargs = sslutil.sslkwargs(ui, mailhost)
else:
sslkwargs = {}
if smtps:
ui.note(_('(using smtps)\n'))
# mercurial 3.8 added a mandatory host arg
if not sslkwargs:
s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
elif 'host' in SMTPS.__init__.__code__.co_varnames:
s = SMTPS(sslkwargs, local_hostname=local_hostname, host=mailhost)
else:
s = SMTPS(sslkwargs, local_hostname=local_hostname)
elif starttls:
if not sslkwargs:
s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
elif 'host' in STARTTLS.__init__.__code__.co_varnames:
s = STARTTLS(sslkwargs, local_hostname=local_hostname, host=mailhost)
else:
s = STARTTLS(sslkwargs, local_hostname=local_hostname)
else:
s = smtplib.SMTP(local_hostname=local_hostname)
if smtps:
defaultport = 465
else:
defaultport = 25
mailport = util.getport(ui.config('smtp', 'port', defaultport))
ui.note(_('sending mail: smtp host %s, port %s\n') %
(mailhost, mailport))
s.connect(host=mailhost, port=mailport)
if starttls:
ui.note(_('(using starttls)\n'))
s.ehlo()
s.starttls()
s.ehlo()
if (starttls or smtps) and verifycert:
ui.note(_('(verifying remote certificate)\n'))
if getattr(sslutil, 'validatesocket', None):
sslutil.validatesocket(s.sock)
else:
validator(ui, mailhost)(s.sock, verifycert == 'strict')
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
stored = password = password_store.get_smtp_password(
mailhost, mailport, username)
# No need to check whether password was found as try_smtp_login
# just returns False if it is absent.
while not try_smtp_login(ui, s, username, password):
password = ui.getpass(_("Password for %s on %s:%d: ") % (username, mailhost, mailport))
if stored != password:
password_store.set_smtp_password(
mailhost, mailport, username, password)
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
def send(sender, recipients, msg):
try:
return s.sendmail(sender, recipients, msg)
except smtplib.SMTPRecipientsRefused, inst:
recipients = [r[1] for r in inst.recipients.values()]
raise util.Abort('\n' + '\n'.join(recipients))
except smtplib.SMTPException, inst:
raise util.Abort(inst)
return send
############################################################
# SMTP monkeypatching
############################################################
@monkeypatch_method(mail)
def _smtp(ui):
"""
build an smtp connection and return a function to send email
This is the monkeypatched version of _smtp(ui) function from
mercurial/mail.py. It calls the original unless username
without password is given in the configuration.
"""
username = ui.config('smtp', 'username')
password = ui.config('smtp', 'password')
if username and not password:
return keyring_supported_smtp(ui, username)
else:
return _smtp.orig(ui)
############################################################
# Custom commands
############################################################
cmdtable = {}
command = meu.command(cmdtable)
@command('keyring_check',
[],
_("keyring_check [PATH]"),
optionalrepo=True)
def cmd_keyring_check(ui, repo, *path_args, **opts): # pylint: disable=unused-argument
"""
Prints basic info (whether password is currently saved, and how is
it identified) for given path.
Can be run without parameters to show status for all (current repository) paths which
are HTTP-like.
"""
defined_paths = [(name, url)
for name, url in ui.configitems('paths')]
if path_args:
# Maybe parameter is an alias
defined_paths_dic = dict(defined_paths)
paths = [(path_arg, defined_paths_dic.get(path_arg, path_arg))
for path_arg in path_args]
else:
if not repo:
ui.status(_("Url to check not specified. Either run ``hg keyring_check https://...``, or run the command inside some repository (to test all defined paths).\n"))
return
paths = [(name, url) for name, url in defined_paths]
if not paths:
ui.status(_("keyring_check: no paths defined\n"))
return
handler = HTTPPasswordHandler()
ui.status(_("keyring password save status:\n"))
for name, url in paths:
if not is_http_path(url):
if path_args:
ui.status(_(" %s: non-http path (%s)\n") % (name, url))
continue
user, pwd, source, final_url = handler.get_credentials(
make_passwordmgr(ui), name, url)
if pwd:
ui.status(_(" %s: password available, source: %s, bound to user %s, url %s\n") % (
name, source, user, final_url))
elif user:
ui.status(_(" %s: password not available, once entered, will be bound to user %s, url %s\n") % (
name, user, final_url))
else:
ui.status(_(" %s: password not available, user unknown, url %s\n") % (
name, final_url))
@command('keyring_clear',
[],
_('hg keyring_clear PATH-OR-ALIAS'),
optionalrepo=True)
def cmd_keyring_clear(ui, repo, path, **opts): # pylint: disable=unused-argument
"""
Drops password bound to given path (if any is saved).
Parameter can be given as full url (``https://John@bitbucket.org``) or as the name
of path alias (``bitbucket``).
"""
path_url = path
for name, url in ui.configitems('paths'):
if name == path:
path_url = url
break
if not is_http_path(path_url):
ui.status(_("%s is not a http path (and %s can't be resolved as path alias)\n") % (path, path_url))
return
handler = HTTPPasswordHandler()
user, pwd, source, final_url = handler.get_credentials(
make_passwordmgr(ui), path, path_url)
if not user:
ui.status(_("Username not configured for url %s\n") % final_url)
return
if not pwd:
ui.status(_("No password is saved for user %s, url %s\n") % (
user, final_url))
return
if source != handler.SRC_KEYRING:
ui.status(_("Password for user %s, url %s is saved in %s, not in keyring\n") % (
user, final_url, source))
password_store.clear_http_password(final_url, user)
ui.status(_("Password removed for user %s, url %s\n") % (
user, final_url))
buglink = 'https://bitbucket.org/Mekk/mercurial_keyring/issues'
|