Merge branch 'fix-changelogs' into 'main'
[tor.git] / contrib / client-tools / tor-resolve.py
blob85e7d2d8b4dbf1d855df8eb4ed3885d8fe3ddb2e
1 #!/usr/bin/env python
3 # Future imports for Python 2.7, mandatory in 3.0
4 from __future__ import division
5 from __future__ import print_function
6 from __future__ import unicode_literals
8 import socket
9 import struct
10 import sys
12 def socks4AResolveRequest(hostname):
13 version = 4
14 command = 0xF0
15 port = 0
16 addr = 0x0000001
17 username = ""
18 reqheader = struct.pack("!BBHL", version, command, port, addr)
19 return "%s%s\x00%s\x00"%(reqheader,username,hostname)
21 def socks4AParseResponse(response):
22 RESPONSE_LEN = 8
23 if len(response) < RESPONSE_LEN:
24 return None
25 assert len(response) >= RESPONSE_LEN
26 version,status,port = struct.unpack("!BBH",response[:4])
27 assert version == 0
28 assert port == 0
29 if status == 90:
30 return "%d.%d.%d.%d"%tuple(map(ord, response[4:]))
31 else:
32 return "ERROR (status %d)"%status
34 def socks5Hello():
35 return "\x05\x01\x00"
36 def socks5ParseHello(response):
37 if response != "\x05\x00":
38 raise ValueError("Bizarre socks5 response")
39 def socks5ResolveRequest(hostname, atype=0x03, command=0xF0):
40 version = 5
41 rsv = 0
42 port = 0
43 reqheader = struct.pack("!BBBB",version, command, rsv, atype)
44 if atype == 0x03:
45 reqheader += struct.pack("!B", len(hostname))
46 portstr = struct.pack("!H",port)
47 return "%s%s%s"%(reqheader,hostname,portstr)
49 def socks5ParseResponse(r):
50 if len(r)<8:
51 return None
52 version, reply, rsv, atype = struct.unpack("!BBBB",r[:4])
53 assert version==5
54 assert rsv==0
55 if reply != 0x00:
56 return "ERROR",reply
57 assert atype in (0x01,0x03,0x04)
58 if atype != 0x03:
59 expected_len = 4 + ({1:4,4:16}[atype]) + 2
60 if len(r) < expected_len:
61 return None
62 elif len(r) > expected_len:
63 raise ValueError("Overlong socks5 reply!")
64 addr = r[4:-2]
65 if atype == 0x01:
66 return "%d.%d.%d.%d"%tuple(map(ord,addr))
67 else:
68 # not really the right way to format IPv6
69 return "IPv6: %s"%(":".join([hex(ord(c)) for c in addr]))
70 else:
71 hlen, = struct.unpack("!B", r[4])
72 expected_len = 5 + hlen + 2
73 if len(r) < expected_len:
74 return None
75 return r[5:-2]
77 def socks5ResolvePTRRequest(hostname):
78 return socks5ResolveRequest(socket.inet_aton(hostname),
79 atype=1, command = 0xF1)
82 def parseHostAndPort(h):
83 host, port = "localhost", 9050
84 if ":" in h:
85 i = h.index(":")
86 host = h[:i]
87 try:
88 port = int(h[i+1:])
89 except ValueError:
90 print("Bad hostname %r"%h)
91 sys.exit(1)
92 elif h:
93 try:
94 port = int(h)
95 except ValueError:
96 host = h
98 return host, port
100 def resolve(hostname, sockshost, socksport, socksver=4, reverse=0):
101 assert socksver in (4,5)
102 if socksver == 4:
103 fmt = socks4AResolveRequest
104 parse = socks4AParseResponse
105 elif not reverse:
106 fmt = socks5ResolveRequest
107 parse = socks5ParseResponse
108 else:
109 fmt = socks5ResolvePTRRequest
110 parse = socks5ParseResponse
112 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113 s.connect((sockshost,socksport))
114 if socksver == 5:
115 s.send(socks5Hello())
116 socks5ParseHello(s.recv(2))
117 s.send(fmt(hostname))
118 answer = s.recv(6)
119 result = parse(answer)
120 while result is None:
121 more = s.recv(1)
122 if not more:
123 return None
124 answer += more
125 result = parse(answer)
126 print("Got answer",result)
127 m = s.recv(1)
128 if m:
129 print("Got extra data too: %r"%m)
130 return result
132 if __name__ == '__main__':
133 if len(sys.argv) not in (2,3,4):
134 print("Syntax: resolve.py [-4|-5] hostname [sockshost:socksport]")
135 sys.exit(0)
136 socksver = 4
137 reverse = 0
138 while sys.argv[1][0] == '-':
139 if sys.argv[1] in ("-4", "-5"):
140 socksver = int(sys.argv[1][1])
141 del sys.argv[1]
142 elif sys.argv[1] == '-x':
143 reverse = 1
144 del sys.argv[1]
145 elif sys.argv[1] == '--':
146 break
148 if len(sys.argv) >= 4:
149 print("Syntax: resolve.py [-x] [-4|-5] hostname [sockshost:socksport]")
150 sys.exit(0)
151 if len(sys.argv) == 3:
152 sh,sp = parseHostAndPort(sys.argv[2])
153 else:
154 sh,sp = parseHostAndPort("")
156 if reverse and socksver == 4:
157 socksver = 5
158 resolve(sys.argv[1], sh, sp, socksver, reverse)