PageRenderTime 47ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/socksify.rb

https://github.com/prettynatty/socksify-a
Ruby | 314 lines | 271 code | 23 blank | 20 comment | 39 complexity | cd030d6ae3f873333d3bf73560ae3796 MD5 | raw file
Possible License(s): GPL-3.0
  1. =begin
  2. Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. =end
  14. require 'socket'
  15. require 'resolv'
  16. require 'socksify/debug'
  17. class SOCKSError < RuntimeError
  18. def initialize(msg)
  19. Socksify::debug_error("#{self.class}: #{msg}")
  20. super
  21. end
  22. class ServerFailure < SOCKSError
  23. def initialize
  24. super("general SOCKS server failure")
  25. end
  26. end
  27. class NotAllowed < SOCKSError
  28. def initialize
  29. super("connection not allowed by ruleset")
  30. end
  31. end
  32. class NetworkUnreachable < SOCKSError
  33. def initialize
  34. super("Network unreachable")
  35. end
  36. end
  37. class HostUnreachable < SOCKSError
  38. def initialize
  39. super("Host unreachable")
  40. end
  41. end
  42. class ConnectionRefused < SOCKSError
  43. def initialize
  44. super("Connection refused")
  45. end
  46. end
  47. class TTLExpired < SOCKSError
  48. def initialize
  49. super("TTL expired")
  50. end
  51. end
  52. class CommandNotSupported < SOCKSError
  53. def initialize
  54. super("Command not supported")
  55. end
  56. end
  57. class AddressTypeNotSupported < SOCKSError
  58. def initialize
  59. super("Address type not supported")
  60. end
  61. end
  62. def self.for_response_code(code)
  63. case code
  64. when 1
  65. ServerFailure
  66. when 2
  67. NotAllowed
  68. when 3
  69. NetworkUnreachable
  70. when 4
  71. HostUnreachable
  72. when 5
  73. ConnectionRefused
  74. when 6
  75. TTLExpired
  76. when 7
  77. CommandNotSupported
  78. when 8
  79. AddressTypeNotSupported
  80. else
  81. self
  82. end
  83. end
  84. end
  85. class TCPSocket
  86. @@socks_version ||= "5"
  87. def self.socks_version
  88. (@@socks_version == "4a" or @@socks_version == "4") ? "\004" : "\005"
  89. end
  90. def self.socks_version=(version)
  91. @@socks_version = version.to_s
  92. end
  93. def self.socks_server
  94. @@socks_server ||= nil
  95. end
  96. def self.socks_server=(host)
  97. @@socks_server = host
  98. end
  99. def self.socks_port
  100. @@socks_port ||= nil
  101. end
  102. def self.socks_port=(port)
  103. @@socks_port = port
  104. end
  105. def self.socks_ignores
  106. @@socks_ignores ||= %w(localhost)
  107. end
  108. def self.socks_ignores=(ignores)
  109. @@socks_ignores = ignores
  110. end
  111. class SOCKSConnectionPeerAddress
  112. attr_reader :socks_server, :socks_port, :peer_host
  113. def initialize(socks_server, socks_port, peer_host)
  114. @socks_server, @socks_port, @peer_host = socks_server, socks_port, peer_host
  115. end
  116. def to_s
  117. "#{@peer_host} (via #{@socks_server}:#{@socks_port})"
  118. end
  119. alias_method :to_str, :to_s
  120. end
  121. alias :initialize_tcp :initialize
  122. # See http://tools.ietf.org/html/rfc1928
  123. def initialize(host=nil, port=0, local_host="0.0.0.0", local_port=0)
  124. if host.is_a?(SOCKSConnectionPeerAddress)
  125. socks_peer = host
  126. socks_server = socks_peer.socks_server
  127. socks_port = socks_peer.socks_port
  128. socks_ignores = []
  129. host = socks_peer.peer_host
  130. else
  131. socks_server = self.class.socks_server
  132. socks_port = self.class.socks_port
  133. socks_ignores = self.class.socks_ignores
  134. end
  135. if socks_server and socks_port and not socks_ignores.include?(host)
  136. Socksify::debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
  137. initialize_tcp socks_server, socks_port
  138. socks_authenticate unless @@socks_version =~ /^4/
  139. if host
  140. socks_connect(host, port)
  141. end
  142. else
  143. Socksify::debug_notice "Connecting directly to #{host}:#{port}"
  144. initialize_tcp host, port, local_host, local_port
  145. Socksify::debug_debug "Connected to #{host}:#{port}"
  146. end
  147. end
  148. # Authentication
  149. def socks_authenticate
  150. Socksify::debug_debug "Sending no authentication"
  151. write "\005\001\000"
  152. Socksify::debug_debug "Waiting for authentication reply"
  153. auth_reply = recv(2)
  154. if auth_reply[0..0] != "\004" and auth_reply[0..0] != "\005"
  155. raise SOCKSError.new("SOCKS version #{auth_reply[0..0]} not supported")
  156. end
  157. if auth_reply[1..1] != "\000"
  158. raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
  159. end
  160. end
  161. # Connect
  162. def socks_connect(host, port)
  163. Socksify::debug_debug "Sending destination address"
  164. write TCPSocket.socks_version
  165. Socksify::debug_debug TCPSocket.socks_version.unpack "H*"
  166. write "\001"
  167. write "\000" if @@socks_version == "5"
  168. write [port].pack('n') if @@socks_version =~ /^4/
  169. if @@socks_version == "4"
  170. host = Resolv::DNS.new.getaddress(host).to_s
  171. end
  172. Socksify::debug_debug host
  173. if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
  174. write "\001" if @@socks_version == "5"
  175. _ip = [$1.to_i,
  176. $2.to_i,
  177. $3.to_i,
  178. $4.to_i
  179. ].pack('CCCC')
  180. write _ip
  181. elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
  182. raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
  183. write "\004"
  184. else # to hostname
  185. if @@socks_version == "5"
  186. write "\003" + [host.size].pack('C') + host
  187. else
  188. write "\000\000\000\001"
  189. write "\007\000"
  190. Socksify::debug_notice host
  191. write host
  192. write "\000"
  193. end
  194. end
  195. write [port].pack('n') if @@socks_version == "5"
  196. socks_receive_reply
  197. Socksify::debug_notice "Connected to #{host}:#{port} over SOCKS"
  198. end
  199. # returns [bind_addr: String, bind_port: Fixnum]
  200. def socks_receive_reply
  201. Socksify::debug_debug "Waiting for SOCKS reply"
  202. if @@socks_version == "5"
  203. connect_reply = recv(4)
  204. Socksify::debug_debug connect_reply.unpack "H*"
  205. if connect_reply[0..0] != "\005"
  206. raise SOCKSError.new("SOCKS version #{connect_reply[0..0]} is not 5")
  207. end
  208. if connect_reply[1..1] != "\000"
  209. raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1])
  210. end
  211. Socksify::debug_debug "Waiting for bind_addr"
  212. bind_addr_len = case connect_reply[3..3]
  213. when "\001"
  214. 4
  215. when "\003"
  216. recv(1).bytes.first
  217. when "\004"
  218. 16
  219. else
  220. raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
  221. end
  222. bind_addr_s = recv(bind_addr_len)
  223. bind_addr = case connect_reply[3..3]
  224. when "\001"
  225. bind_addr_s.bytes.to_a.join('.')
  226. when "\003"
  227. bind_addr_s
  228. when "\004" # Untested!
  229. i = 0
  230. ip6 = ""
  231. bind_addr_s.each_byte do |b|
  232. if i > 0 and i % 2 == 0
  233. ip6 += ":"
  234. end
  235. i += 1
  236. ip6 += b.to_s(16).rjust(2, '0')
  237. end
  238. end
  239. bind_port = recv(bind_addr_len + 2)
  240. [bind_addr, bind_port.unpack('n')]
  241. else
  242. connect_reply = recv(8)
  243. unless connect_reply[0] == "\000" and connect_reply[1] == "\x5A"
  244. Socksify::debug_debug connect_reply.unpack 'H'
  245. raise SOCKSError.new("Failed while connecting througth socks")
  246. end
  247. end
  248. end
  249. end
  250. module Socksify
  251. def self.resolve(host)
  252. s = TCPSocket.new
  253. begin
  254. Socksify::debug_debug "Sending hostname to resolve: #{host}"
  255. s.write "\005"
  256. if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
  257. s.write "\xF1\000\001" + [$1.to_i,
  258. $2.to_i,
  259. $3.to_i,
  260. $4.to_i
  261. ].pack('CCCC')
  262. elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
  263. raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
  264. s.write "\004"
  265. else # to hostname
  266. s.write "\xF0\000\003" + [host.size].pack('C') + host
  267. end
  268. s.write [0].pack('n') # Port
  269. addr, port = s.socks_receive_reply
  270. Socksify::debug_notice "Resolved #{host} as #{addr} over SOCKS"
  271. addr
  272. ensure
  273. s.close
  274. end
  275. end
  276. def self.proxy(server, port)
  277. default_server = TCPSocket::socks_server
  278. default_port = TCPSocket::socks_port
  279. begin
  280. TCPSocket::socks_server = server
  281. TCPSocket::socks_port = port
  282. yield
  283. ensure
  284. TCPSocket::socks_server = default_server
  285. TCPSocket::socks_port = default_port
  286. end
  287. end
  288. end