PageRenderTime 36ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/socksify.rb

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