forked from COUR4G3/proxy-negotiate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproxy_negotiate.py
217 lines (176 loc) · 6.84 KB
/
proxy_negotiate.py
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
"""
Initial version based on https://github.com/cour4g3/proxy-negotiate
See also https://pypi.python.org/pypi/Proxy-Negotiate/1.0.0
Modified by Patrick Spielmann, [email protected]
See https://github.com/spielmap/proxy-negotiate
Modifications incorporate ideas from https://github.com/gevent/gevent/blob/master/examples/portforwarder.py
2018-02-09
-Added exception handling for socket errors
-Added retry loop for acquiring the SPNEGO token (certain GSS-APIs causing issues here)
"""
import base64
import fcntl
import datetime
import gevent
import gssapi
import os
import socket
import sys
from logging import getLogger
from gevent.server import StreamServer
from gevent.socket import create_connection, wait_read
logger = getLogger(__name__)
__version__ = '1.0.1'
GET_TOKEN_NUM_RETRIES = 3
def log(msg):
# TODO: Clarify, if "print" is blocking or could be used here
sys.stderr.write(str(datetime.datetime.now()) + " " + msg + '\n')
#print str(datetime.datetime.now()), msg
def forward(src, dst):
try:
while True:
try:
data = src.recv(1024)
except socket.error:
log(" ==> Could not read data from socket [%s], closing socket!" % src)
if not data:
break
try:
dst.sendall(data)
except socket.error:
log(" ==> Could not send data to socket [%s], closing socket!" % dst)
break
finally:
src.close()
def forward_stdin(sock):
# set stdin to non-blocking so we can read available bytes
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
try:
while True:
try:
wait_read(sys.stdin.fileno())
data = sys.stdin.read()
except:
log(" ==> Could not read data from socket [%s], closing socket!" % sock)
if not data:
break
try:
sock.sendall(data)
except socket.error:
log(" ==> Could not send data to socket [%s], closing socket!" % sock)
finally:
sock.close()
def forward_stdout(sock):
try:
while True:
try:
data = sock.recv(1024)
except socket.error:
log(" ==> Could not read data from socket [%s], closing socket!" % sock)
if not data:
break
sys.stdout.write(data)
sys.stdout.flush()
finally:
sock.close()
class NegotiateProxy(StreamServer):
def __init__(self, listener, upstream, **kwargs):
super(NegotiateProxy, self).__init__(listener, **kwargs)
self.upstream = upstream
def handle(self, src, addr):
data = b''
while True:
try:
data += src.recv(1024)
except socket.error:
log(" ==> Could not read data from socket [%s], closing socket!" % src)
return
if not data:
log(" ==> Received no data from socket [%s], closing socket!" % src)
return
if b'\r\n\r\n' in data:
break
service = gssapi.Name('HTTP@%s' % self.upstream[0], gssapi.NameType.hostbased_service)
ctx = gssapi.SecurityContext(name=service, usage='initiate')
retry_counter = 0
while True:
try:
token = ctx.step()
break
except:
retry_counter += 1
log(" ==> Exception while getting Kerberos token! Number of retries [%s]" % retry_counter)
if retry_counter > GET_TOKEN_NUM_RETRIES:
log(" ==> Retry limit reached while trying to get a Kerberos token - giving up!")
return
b64token = base64.b64encode(token).encode('ascii')
# TODO: Check if Header handling can/should be moved to the forwarder function
headers, data = data.split(b'\r\n\r\n', 1)
headers = headers.split('\r\n')
replaced = False
for i, header in enumerate(headers):
if header.startswith('Proxy-Authorization:'):
headers[i] = b'Proxy-Authorization: Negotiate %s' % b64token
replaced = True
break
if not replaced:
headers.append(b'Proxy-Authorization: Negotiate %s' % b64token)
try:
dst = create_connection(self.upstream)
except:
log(" ==> Could not connect to upstream proxy [%s:%s]!" % self.upstream[:2])
return
try:
dst.sendall(b'\r\n'.join(headers) + b'\r\n\r\n' + data)
except:
log(" ==> Could not send data to upstream proxy [%s:%s]!" % self.upstream[:2])
return
forwarders = (gevent.spawn(forward, src, dst),
gevent.spawn(forward, dst, src))
gevent.joinall(forwarders)
def netcat(host, port, proxy_host, proxy_port):
request = [('CONNECT %s:%d HTTP/1.1' % (host, port)).encode('ascii')]
request.append(('Host: %s:%d' % (host, port)).encode('ascii'))
request.append(('Proxy-Connection: Keep-Alive').encode('ascii'))
request.append(('\r\n').encode('ascii'))
dst = create_connection((proxy_host, proxy_port))
dst.sendall(b'\r\n'.join(request))
data = b''
while True:
data += dst.recv(1024)
if b'\r\n\r\n' in data:
break
if b'200 Connection established' not in data and b'407' in data:
service = gssapi.Name('HTTP@%s' % proxy_host, gssapi.NameType.hostbased_service)
ctx = gssapi.SecurityContext(name=service, usage='initiate')
token = ctx.step()
b64token = base64.b64encode(token).encode('ascii')
request[-1] = ('Proxy-Authorization: Negotiate %s' % b64token).encode('ascii')
request.append(('\r\n').encode('ascii'))
try:
dst.sendall(b'\r\n'.join(request))
except:
# if proxy does not support Keep-Alive
dst.close()
dst = create_connection((proxy_host, proxy_port))
dst.sendall(b'\r\n'.join(request))
data = b''
while True:
data += dst.recv(1024)
if b'\r\n\r\n' in data:
break
if b'200 Connection established' in data:
sys.stderr.write('Proxy connection established\n')
data = data.split(b'\r\n\r\n', 1)[1]
if data:
dst.sendall(data)
forwarders = (gevent.spawn(forward_stdin, dst),
gevent.spawn(forward_stdout, dst))
gevent.joinall(forwarders)
elif b'407' in data:
sys.stderr.write('Proxy authentication failed\n')
else:
version, status_code, status_message = data.split(b'\r\n', 1)[0].split(b' ', 2)
sys.stderr.write('Proxy returned %s %s\n' % (status_code, status_message))