11import logging
22import re
3+ import socket
34from base64 import b64decode , b64encode
45
56import gssapi
@@ -128,6 +129,47 @@ def __init__(
128129 self .creds = creds
129130 self .mech = mech if mech else SPNEGO
130131 self .sanitize_mutual_error_response = sanitize_mutual_error_response
132+ self ._dns_canonicalize_hostname = False
133+ self ._use_reverse_dns = False
134+
135+ def dns_canonicalize_hostname (self , value = None ):
136+ """
137+ Enables canonical hostname resolution via CNAME records.
138+
139+ >>> import requests
140+ >>> from requests_gssapi import HTTPSPNEGOAuth
141+ >>> gssapi_auth = HTTPSPNEGOAuth()
142+ >>> gssapi_auth.dns_canonicalize_hostname(True)
143+ >>> gssapi_auth.use_reverse_dns(True)
144+ >>> r = requests.get("http://example.org", auth=gssapi_auth)
145+
146+ .. warning:::
147+ Using an insecure DNS queries for principal name
148+ canonicalization can result in risc of a man-in-the-middle
149+ attack. Strictly speaking such queries are in violation of
150+ RFC 4120. Alas misconfigured realms exist and client libraries
151+ like MIT Kerberos provide means to canonicalize principal
152+ names via DNS queries. Be very careful when using thi option.
153+
154+ .. seealso:::
155+ `RFC 4120 <https://datatracker.ietf.org/doc/html/rfc4120>`
156+ `RFC 6808 <https://datatracker.ietf.org/doc/html/rfc6806>`
157+ """
158+ if isinstance (value , bool ):
159+ self ._dns_canonicalize_hostname = value
160+ return self ._dns_canonicalize_hostname
161+
162+ def use_reverse_dns (self , value = None ):
163+ """
164+ Use rev-DNS query to resolve canonical host name when DNS
165+ canonicalization is enabled.
166+
167+ .. seealso::
168+ See `dns_canonicalize_hostname` for further details and warnings.
169+ """
170+ if isinstance (value , bool ):
171+ self ._use_reverse_dns = value
172+ return self ._use_reverse_dns
131173
132174 def generate_request_header (self , response , host , is_preemptive = False ):
133175 """
@@ -144,12 +186,26 @@ def generate_request_header(self, response, host, is_preemptive=False):
144186 if self .mutual_authentication != DISABLED :
145187 gssflags .append (gssapi .RequirementFlag .mutual_authentication )
146188
189+ canonhost = host
190+ if self ._dns_canonicalize_hostname and type (self .target_name ) != gssapi .Name :
191+ try :
192+ ai = socket .getaddrinfo (host , 0 , flags = socket .AI_CANONNAME )
193+ canonhost = ai [0 ][3 ]
194+
195+ if self ._use_reverse_dns :
196+ ni = socket .getnameinfo (ai [0 ][4 ], socket .NI_NAMEREQD )
197+ canonhost = ni [0 ]
198+
199+ except socket .gaierror as e :
200+ if e .errno == socket .EAI_MEMORY :
201+ raise e
202+
147203 try :
148204 gss_stage = "initiating context"
149205 name = self .target_name
150206 if type (name ) != gssapi .Name :
151207 if "@" not in name :
152- name = "%s@%s" % (name , host )
208+ name = "%s@%s" % (name , canonhost )
153209
154210 name = gssapi .Name (name , gssapi .NameType .hostbased_service )
155211 self .context [host ] = gssapi .SecurityContext (
0 commit comments