PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/service/socksify/socksify.rb

https://github.com/bartt/bp-jsonrequest
Ruby | 237 lines | 198 code | 18 blank | 21 comment | 20 complexity | 842c2f1821eb508bc13b8f9e12cddd07 MD5 | raw file
  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 'socksify_debug'
  16. class SOCKSError < RuntimeError
  17. def initialize(msg)
  18. Socksify::debug_error("#{self.class}: #{msg}")
  19. super
  20. end
  21. class ServerFailure < SOCKSError
  22. def initialize
  23. super("general SOCKS server failure")
  24. end
  25. end
  26. class NotAllowed < SOCKSError
  27. def initialize
  28. super("connection not allowed by ruleset")
  29. end
  30. end
  31. class NetworkUnreachable < SOCKSError
  32. def initialize
  33. super("Network unreachable")
  34. end
  35. end
  36. class HostUnreachable < SOCKSError
  37. def initialize
  38. super("Host unreachable")
  39. end
  40. end
  41. class ConnectionRefused < SOCKSError
  42. def initialize
  43. super("Connection refused")
  44. end
  45. end
  46. class TTLExpired < SOCKSError
  47. def initialize
  48. super("TTL expired")
  49. end
  50. end
  51. class CommandNotSupported < SOCKSError
  52. def initialize
  53. super("Command not supported")
  54. end
  55. end
  56. class AddressTypeNotSupported < SOCKSError
  57. def initialize
  58. super("Address type not supported")
  59. end
  60. end
  61. def self.for_response_code(code)
  62. case code
  63. when 1
  64. ServerFailure
  65. when 2
  66. NotAllowed
  67. when 3
  68. NetworkUnreachable
  69. when 4
  70. HostUnreachable
  71. when 5
  72. ConnectionRefused
  73. when 6
  74. TTLExpired
  75. when 7
  76. CommandNotSupported
  77. when 8
  78. AddressTypeNotSupported
  79. else
  80. self
  81. end
  82. end
  83. end
  84. class TCPSocket
  85. def self.socks_server
  86. @@socks_server
  87. end
  88. def self.socks_server=(host)
  89. @@socks_server = host
  90. end
  91. def self.socks_port
  92. @@socks_port
  93. end
  94. def self.socks_port=(port)
  95. @@socks_port = port
  96. end
  97. alias :initialize_tcp :initialize
  98. # See http://tools.ietf.org/html/rfc1928
  99. def initialize(host=nil, port=0, local_host="0.0.0.0", local_port=0)
  100. socks_server = self.class.socks_server
  101. socks_port = self.class.socks_port
  102. if socks_server and socks_port
  103. Socksify::debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
  104. initialize_tcp socks_server, socks_port
  105. socks_authenticate
  106. if host
  107. socks_connect(host, port)
  108. end
  109. else
  110. Socksify::debug_notice "Connecting directly to #{host}:#{port}"
  111. initialize_tcp host, port, local_host, local_port
  112. Socksify::debug_debug "Connected to #{host}:#{port}"
  113. end
  114. end
  115. # Authentication
  116. def socks_authenticate
  117. Socksify::debug_debug "Sending no authentication"
  118. write "\005\001\000"
  119. Socksify::debug_debug "Waiting for authentication reply"
  120. auth_reply = recv(2)
  121. if auth_reply[0] != 4 and auth_reply[0] != 5
  122. raise SOCKSError.new("SOCKS version #{auth_reply[0]} not supported")
  123. end
  124. if auth_reply[1] != 0
  125. raise SOCKSError.new("SOCKS authentication method #{auth_reply[1]} neither requested nor supported")
  126. end
  127. end
  128. # Connect
  129. def socks_connect(host, port)
  130. Socksify::debug_debug "Sending destination address"
  131. write "\005\001\000"
  132. if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
  133. write "\001" + [$1.to_i,
  134. $2.to_i,
  135. $3.to_i,
  136. $4.to_i
  137. ].pack('CCCC')
  138. elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
  139. raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
  140. write "\004"
  141. else # to hostname
  142. write "\003" + [host.size].pack('C') + host
  143. end
  144. write [port].pack('n')
  145. socks_receive_reply
  146. Socksify::debug_notice "Connected to #{host}:#{port} over SOCKS"
  147. end
  148. # returns [bind_addr: String, bind_port: Fixnum]
  149. def socks_receive_reply
  150. Socksify::debug_debug "Waiting for SOCKS reply"
  151. connect_reply = recv(4)
  152. if connect_reply[0] != 5
  153. raise SOCKSError.new("SOCKS version #{connect_reply[0]} is not 5")
  154. end
  155. if connect_reply[1] != 0
  156. raise SOCKSError.for_response_code(connect_reply[1])
  157. end
  158. Socksify::debug_debug "Waiting for bind_addr"
  159. bind_addr_len = case connect_reply[3]
  160. when 1
  161. 4
  162. when 3
  163. recv(1)[0]
  164. when 4
  165. 16
  166. else
  167. raise SOCKSError.for_response_code(connect_reply[3])
  168. end
  169. bind_addr_s = recv(bind_addr_len)
  170. bind_addr = case connect_reply[3]
  171. when 1
  172. "#{bind_addr_s[0]}.#{bind_addr_s[1]}.#{bind_addr_s[2]}.#{bind_addr_s[3]}"
  173. when 3
  174. bind_addr_s
  175. when 4 # Untested!
  176. i = 0
  177. ip6 = ""
  178. bind_addr_s.each_byte do |b|
  179. if i > 0 and i % 2 == 0
  180. ip6 += ":"
  181. end
  182. i += 1
  183. ip6 += b.to_s(16).rjust(2, '0')
  184. end
  185. end
  186. bind_port = recv(bind_addr_len + 2)
  187. [bind_addr, bind_port.unpack('n')]
  188. end
  189. end
  190. module Socksify
  191. def self.resolve(host)
  192. s = TCPSocket.new
  193. begin
  194. Socksify::debug_debug "Sending hostname to resolve: #{host}"
  195. s.write "\005"
  196. if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
  197. s.write "\xF1\000\001" + [$1.to_i,
  198. $2.to_i,
  199. $3.to_i,
  200. $4.to_i
  201. ].pack('CCCC')
  202. elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
  203. raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
  204. s.write "\004"
  205. else # to hostname
  206. s.write "\xF0\000\003" + [host.size].pack('C') + host
  207. end
  208. s.write [0].pack('n') # Port
  209. addr, port = s.socks_receive_reply
  210. Socksify::debug_notice "Resolved #{host} as #{addr} over SOCKS"
  211. addr
  212. ensure
  213. s.close
  214. end
  215. end
  216. end