ntlmpool.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """
  2. NTLM authenticating pool, contributed by erikcederstran
  3. Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
  4. """
  5. from __future__ import absolute_import
  6. from logging import getLogger
  7. from ntlm import ntlm
  8. from .. import HTTPSConnectionPool
  9. from ..packages.six.moves.http_client import HTTPSConnection
  10. log = getLogger(__name__)
  11. class NTLMConnectionPool(HTTPSConnectionPool):
  12. """
  13. Implements an NTLM authentication version of an urllib3 connection pool
  14. """
  15. scheme = "https"
  16. def __init__(self, user, pw, authurl, *args, **kwargs):
  17. """
  18. authurl is a random URL on the server that is protected by NTLM.
  19. user is the Windows user, probably in the DOMAIN\\username format.
  20. pw is the password for the user.
  21. """
  22. super(NTLMConnectionPool, self).__init__(*args, **kwargs)
  23. self.authurl = authurl
  24. self.rawuser = user
  25. user_parts = user.split("\\", 1)
  26. self.domain = user_parts[0].upper()
  27. self.user = user_parts[1]
  28. self.pw = pw
  29. def _new_conn(self):
  30. # Performs the NTLM handshake that secures the connection. The socket
  31. # must be kept open while requests are performed.
  32. self.num_connections += 1
  33. log.debug(
  34. "Starting NTLM HTTPS connection no. %d: https://%s%s",
  35. self.num_connections,
  36. self.host,
  37. self.authurl,
  38. )
  39. headers = {"Connection": "Keep-Alive"}
  40. req_header = "Authorization"
  41. resp_header = "www-authenticate"
  42. conn = HTTPSConnection(host=self.host, port=self.port)
  43. # Send negotiation message
  44. headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE(
  45. self.rawuser
  46. )
  47. log.debug("Request headers: %s", headers)
  48. conn.request("GET", self.authurl, None, headers)
  49. res = conn.getresponse()
  50. reshdr = dict(res.getheaders())
  51. log.debug("Response status: %s %s", res.status, res.reason)
  52. log.debug("Response headers: %s", reshdr)
  53. log.debug("Response data: %s [...]", res.read(100))
  54. # Remove the reference to the socket, so that it can not be closed by
  55. # the response object (we want to keep the socket open)
  56. res.fp = None
  57. # Server should respond with a challenge message
  58. auth_header_values = reshdr[resp_header].split(", ")
  59. auth_header_value = None
  60. for s in auth_header_values:
  61. if s[:5] == "NTLM ":
  62. auth_header_value = s[5:]
  63. if auth_header_value is None:
  64. raise Exception(
  65. "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header])
  66. )
  67. # Send authentication message
  68. ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(
  69. auth_header_value
  70. )
  71. auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(
  72. ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags
  73. )
  74. headers[req_header] = "NTLM %s" % auth_msg
  75. log.debug("Request headers: %s", headers)
  76. conn.request("GET", self.authurl, None, headers)
  77. res = conn.getresponse()
  78. log.debug("Response status: %s %s", res.status, res.reason)
  79. log.debug("Response headers: %s", dict(res.getheaders()))
  80. log.debug("Response data: %s [...]", res.read()[:100])
  81. if res.status != 200:
  82. if res.status == 401:
  83. raise Exception("Server rejected request: wrong username or password")
  84. raise Exception("Wrong server response: %s %s" % (res.status, res.reason))
  85. res.fp = None
  86. log.debug("Connection established")
  87. return conn
  88. def urlopen(
  89. self,
  90. method,
  91. url,
  92. body=None,
  93. headers=None,
  94. retries=3,
  95. redirect=True,
  96. assert_same_host=True,
  97. ):
  98. if headers is None:
  99. headers = {}
  100. headers["Connection"] = "Keep-Alive"
  101. return super(NTLMConnectionPool, self).urlopen(
  102. method, url, body, headers, retries, redirect, assert_same_host
  103. )