]>
Commit | Line | Data |
---|---|---|
3292c4a9 JM |
1 | #!/usr/bin/python |
2 | ||
adfe6ac1 JM |
3 | ''' |
4 | A WebSocket to TCP socket proxy with support for "wss://" encryption. | |
af6b17ce JM |
5 | Copyright 2010 Joel Martin |
6 | Licensed under LGPL version 3 (see LICENSE.LGPL-3) | |
adfe6ac1 JM |
7 | |
8 | You can make a cert/key with openssl using: | |
9 | openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem | |
10 | as taken from http://docs.python.org/dev/library/ssl.html#certificates | |
11 | ||
12 | ''' | |
13 | ||
325d9eb7 | 14 | import sys, socket, ssl, optparse |
3292c4a9 | 15 | from select import select |
95ef30a1 | 16 | from websocket import * |
3292c4a9 | 17 | |
64ab5c4d | 18 | buffer_size = 65536 |
325d9eb7 | 19 | rec = None |
5d2c3864 | 20 | |
5d8e7ec0 JM |
21 | traffic_legend = """ |
22 | Traffic Legend: | |
23 | } - Client receive | |
24 | }. - Client receive partial | |
25 | { - Target receive | |
26 | ||
27 | > - Target send | |
28 | >. - Target send partial | |
29 | < - Client send | |
30 | <. - Client send partial | |
31 | """ | |
32 | ||
95ef30a1 | 33 | def do_proxy(client, target): |
5d2c3864 | 34 | """ Proxy WebSocket to normal socket. """ |
325d9eb7 | 35 | global rec |
ce0e28c7 | 36 | cqueue = [] |
5d2c3864 | 37 | cpartial = "" |
ce0e28c7 | 38 | tqueue = [] |
92f572a2 | 39 | rlist = [client, target] |
3292c4a9 | 40 | |
ce0e28c7 | 41 | while True: |
92f572a2 JM |
42 | wlist = [] |
43 | if tqueue: wlist.append(target) | |
44 | if cqueue: wlist.append(client) | |
45 | ins, outs, excepts = select(rlist, wlist, [], 1) | |
ce0e28c7 JM |
46 | if excepts: raise Exception("Socket exception") |
47 | ||
92f572a2 | 48 | if target in outs: |
5d8e7ec0 JM |
49 | dat = tqueue.pop(0) |
50 | sent = target.send(dat) | |
51 | if sent == len(dat): | |
52 | traffic(">") | |
53 | else: | |
54 | tqueue.insert(0, dat[sent:]) | |
95ef30a1 | 55 | traffic(".>") |
325d9eb7 | 56 | ##if rec: rec.write("Target send: %s\n" % map(ord, dat)) |
5d8e7ec0 | 57 | |
92f572a2 | 58 | if client in outs: |
5d8e7ec0 JM |
59 | dat = cqueue.pop(0) |
60 | sent = client.send(dat) | |
61 | if sent == len(dat): | |
62 | traffic("<") | |
325d9eb7 JM |
63 | ##if rec: rec.write("Client send: %s ...\n" % repr(dat[0:80])) |
64 | if rec: rec.write("%s,\n" % repr(">" + dat[1:-1])) | |
5d8e7ec0 JM |
65 | else: |
66 | cqueue.insert(0, dat[sent:]) | |
67 | traffic("<.") | |
325d9eb7 | 68 | ##if rec: rec.write("Client send partial: %s\n" % repr(dat[0:send])) |
5d8e7ec0 JM |
69 | |
70 | ||
71 | if target in ins: | |
72 | buf = target.recv(buffer_size) | |
73 | if len(buf) == 0: raise Exception("Target closed") | |
74 | ||
95ef30a1 | 75 | cqueue.append(encode(buf)) |
5d8e7ec0 | 76 | traffic("{") |
325d9eb7 | 77 | ##if rec: rec.write("Target recv (%d): %s\n" % (len(buf), map(ord, buf))) |
5d8e7ec0 | 78 | |
ce0e28c7 | 79 | if client in ins: |
64ab5c4d | 80 | buf = client.recv(buffer_size) |
ce0e28c7 | 81 | if len(buf) == 0: raise Exception("Client closed") |
5d2c3864 | 82 | |
95ef30a1 JM |
83 | if buf[-1] == '\xff': |
84 | if buf.count('\xff') > 1: | |
85 | traffic(str(buf.count('\xff'))) | |
5d8e7ec0 | 86 | traffic("}") |
325d9eb7 JM |
87 | ##if rec: rec.write("Client recv (%d): %s\n" % (len(buf), repr(buf))) |
88 | if rec: rec.write("%s,\n" % repr(buf[1:-1])) | |
5d2c3864 JM |
89 | if cpartial: |
90 | tqueue.extend(decode(cpartial + buf)) | |
91 | cpartial = "" | |
92 | else: | |
93 | tqueue.extend(decode(buf)) | |
5d2c3864 | 94 | else: |
95ef30a1 | 95 | traffic(".}") |
325d9eb7 | 96 | ##if rec: rec.write("Client recv partial (%d): %s\n" % (len(buf), repr(buf))) |
5d2c3864 JM |
97 | cpartial = cpartial + buf |
98 | ||
95ef30a1 | 99 | def proxy_handler(client): |
325d9eb7 | 100 | global target_host, target_port, options, rec |
95ef30a1 | 101 | |
6ee61a4c JM |
102 | if settings['record']: |
103 | print "Opening record file: %s" % settings['record'] | |
104 | rec = open(settings['record'], 'w') | |
105 | ||
95ef30a1 JM |
106 | print "Connecting to: %s:%s" % (target_host, target_port) |
107 | tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
108 | tsock.connect((target_host, target_port)) | |
ce0e28c7 | 109 | |
5d8e7ec0 | 110 | print traffic_legend |
95ef30a1 JM |
111 | |
112 | try: | |
113 | do_proxy(client, tsock) | |
114 | except: | |
115 | if tsock: tsock.close() | |
325d9eb7 | 116 | if rec: rec.close() |
95ef30a1 | 117 | raise |
3292c4a9 JM |
118 | |
119 | if __name__ == '__main__': | |
f2898eab JM |
120 | usage = "%prog [--record FILE]" |
121 | usage += " [source_addr:]source_port target_addr:target_port" | |
122 | parser = optparse.OptionParser(usage=usage) | |
459b2578 | 123 | parser.add_option("--record", |
325d9eb7 | 124 | help="record session to a file", metavar="FILE") |
6ee61a4c JM |
125 | parser.add_option("--foreground", "-f", |
126 | dest="daemon", default=True, action="store_false", | |
127 | help="stay in foreground, do not daemonize") | |
459b2578 JM |
128 | parser.add_option("--ssl-only", action="store_true", |
129 | help="disallow non-encrypted connections") | |
6ee61a4c JM |
130 | parser.add_option("--cert", default="self.pem", |
131 | help="SSL certificate") | |
325d9eb7 JM |
132 | (options, args) = parser.parse_args() |
133 | ||
f2898eab JM |
134 | if len(args) > 2: parser.error("Too many arguments") |
135 | if len(args) < 2: parser.error("Too few arguments") | |
136 | if args[0].count(':') > 0: | |
6ee61a4c | 137 | host,port = args[0].split(':') |
f2898eab | 138 | else: |
6ee61a4c | 139 | host,port = '',args[0] |
f2898eab JM |
140 | if args[1].count(':') > 0: |
141 | target_host,target_port = args[1].split(':') | |
142 | else: | |
143 | parser.error("Error parsing target") | |
6ee61a4c | 144 | try: port = int(port) |
325d9eb7 | 145 | except: parser.error("Error parsing listen port") |
f2898eab | 146 | try: target_port = int(target_port) |
325d9eb7 JM |
147 | except: parser.error("Error parsing target port") |
148 | ||
6ee61a4c JM |
149 | settings['listen_host'] = host |
150 | settings['listen_port'] = port | |
151 | settings['handler'] = proxy_handler | |
152 | settings['cert'] = os.path.abspath(options.cert) | |
153 | settings['ssl_only'] = options.ssl_only | |
154 | settings['daemon'] = options.daemon | |
155 | settings['record'] = os.path.abspath(options.record) | |
156 | start_server() |