1
1
import logging
2
2
import re
3
+ import socket
3
4
from base64 import b64decode , b64encode
4
5
5
6
import gssapi
@@ -128,6 +129,47 @@ def __init__(
128
129
self .creds = creds
129
130
self .mech = mech if mech else SPNEGO
130
131
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
131
173
132
174
def generate_request_header (self , response , host , is_preemptive = False ):
133
175
"""
@@ -144,12 +186,26 @@ def generate_request_header(self, response, host, is_preemptive=False):
144
186
if self .mutual_authentication != DISABLED :
145
187
gssflags .append (gssapi .RequirementFlag .mutual_authentication )
146
188
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
+
147
203
try :
148
204
gss_stage = "initiating context"
149
205
name = self .target_name
150
206
if type (name ) != gssapi .Name :
151
207
if "@" not in name :
152
- name = "%s@%s" % (name , host )
208
+ name = "%s@%s" % (name , canonhost )
153
209
154
210
name = gssapi .Name (name , gssapi .NameType .hostbased_service )
155
211
self .context [host ] = gssapi .SecurityContext (
0 commit comments