mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-05-04 15:44:59 -04:00
add tor sub projects as modules
This commit is contained in:
parent
d33ebbed27
commit
8347b9f572
100 changed files with 121050 additions and 55 deletions
26
jsocks/README.txt
Normal file
26
jsocks/README.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
Module cloned from: https://github.com/ravn/jsocks
|
||||
|
||||
|
||||
This project provides the revised source of the
|
||||
http://sourceforge.net/projects/jsocks/ project as
|
||||
required by the LGPL.
|
||||
|
||||
The git repository was started on a blank sheet,
|
||||
and then importing the sources, so all the
|
||||
work done is in the git history. The sf repository
|
||||
does not contain any versioned files, so there
|
||||
was no history to import.
|
||||
|
||||
A source tree with proper packages has been
|
||||
set up and cleaned up by Eclipse.
|
||||
|
||||
Logging has been migrated to slf4j and a
|
||||
launch configuration using slf4j-simple has
|
||||
been included, which also allows for creating
|
||||
runnable jars from inside Eclipse 3.5+
|
||||
|
||||
/ravn - 2009-09-07<30>
|
||||
|
||||
Mavenized.
|
||||
|
||||
/ravn - 2012-11-07
|
503
jsocks/docs/SOCKS - Wikipedia, the free encyclopedia.mht
Normal file
503
jsocks/docs/SOCKS - Wikipedia, the free encyclopedia.mht
Normal file
|
@ -0,0 +1,503 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="generator" content="MediaWiki 1.16alpha-wmf" />
|
||||
<meta name="keywords" content="SOCKS,Wikipedia articles that are too technical,ASCII,Blue Coat Systems,Client-server,DNS lookup,Delegate (networking),Domain name,File Transfer Protocol,GSSAPI,HTTP" />
|
||||
<link rel="alternate" type="application/x-wiki" title="Edit this page" href="/w/index.php?title=SOCKS&action=edit" />
|
||||
<link rel="edit" title="Edit this page" href="/w/index.php?title=SOCKS&action=edit" />
|
||||
<link rel="apple-touch-icon" href="http://en.wikipedia.org/apple-touch-icon.png" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="/w/opensearch_desc.php" title="Wikipedia (en)" />
|
||||
<link rel="copyright" href="http://creativecommons.org/licenses/by-sa/3.0/" />
|
||||
<link rel="alternate" type="application/rss+xml" title="Wikipedia RSS Feed" href="/w/index.php?title=Special:RecentChanges&feed=rss" />
|
||||
<link rel="alternate" type="application/atom+xml" title="Wikipedia Atom Feed" href="/w/index.php?title=Special:RecentChanges&feed=atom" />
|
||||
<title>SOCKS - Wikipedia, the free encyclopedia</title>
|
||||
<link rel="stylesheet" href="/skins-1.5/common/shared.css?233zz2" type="text/css" media="screen" />
|
||||
<link rel="stylesheet" href="/skins-1.5/common/commonPrint.css?233zz2" type="text/css" media="print" />
|
||||
<link rel="stylesheet" href="/skins-1.5/monobook/main.css?233zz2" type="text/css" media="screen" />
|
||||
<link rel="stylesheet" href="/skins-1.5/chick/main.css?233zz2" type="text/css" media="handheld" />
|
||||
<!--[if lt IE 5.5000]><link rel="stylesheet" href="/skins-1.5/monobook/IE50Fixes.css?233zz2" type="text/css" media="screen" /><![endif]-->
|
||||
<!--[if IE 5.5000]><link rel="stylesheet" href="/skins-1.5/monobook/IE55Fixes.css?233zz2" type="text/css" media="screen" /><![endif]-->
|
||||
<!--[if IE 6]><link rel="stylesheet" href="/skins-1.5/monobook/IE60Fixes.css?233zz2" type="text/css" media="screen" /><![endif]-->
|
||||
<!--[if IE 7]><link rel="stylesheet" href="/skins-1.5/monobook/IE70Fixes.css?233zz2" type="text/css" media="screen" /><![endif]-->
|
||||
<link rel="stylesheet" href="/w/index.php?title=MediaWiki:Common.css&usemsgcache=yes&ctype=text%2Fcss&smaxage=2678400&action=raw&maxage=2678400" type="text/css" />
|
||||
<link rel="stylesheet" href="/w/index.php?title=MediaWiki:Print.css&usemsgcache=yes&ctype=text%2Fcss&smaxage=2678400&action=raw&maxage=2678400" type="text/css" media="print" />
|
||||
<link rel="stylesheet" href="/w/index.php?title=MediaWiki:Handheld.css&usemsgcache=yes&ctype=text%2Fcss&smaxage=2678400&action=raw&maxage=2678400" type="text/css" media="handheld" />
|
||||
<link rel="stylesheet" href="/w/index.php?title=MediaWiki:Monobook.css&usemsgcache=yes&ctype=text%2Fcss&smaxage=2678400&action=raw&maxage=2678400" type="text/css" />
|
||||
<link rel="stylesheet" href="/w/index.php?title=-&action=raw&maxage=2678400&gen=css" type="text/css" />
|
||||
<!--[if lt IE 7]><script type="text/javascript" src="/skins-1.5/common/IEFixes.js?233zz2"></script>
|
||||
<meta http-equiv="imagetoolbar" content="no" /><![endif]-->
|
||||
|
||||
<script type="text/javascript">/*<![CDATA[*/
|
||||
var skin = "monobook";
|
||||
var stylepath = "/skins-1.5";
|
||||
var wgArticlePath = "/wiki/$1";
|
||||
var wgScriptPath = "/w";
|
||||
var wgScript = "/w/index.php";
|
||||
var wgVariantArticlePath = false;
|
||||
var wgActionPaths = {};
|
||||
var wgServer = "http://en.wikipedia.org";
|
||||
var wgCanonicalNamespace = "";
|
||||
var wgCanonicalSpecialPageName = false;
|
||||
var wgNamespaceNumber = 0;
|
||||
var wgPageName = "SOCKS";
|
||||
var wgTitle = "SOCKS";
|
||||
var wgAction = "view";
|
||||
var wgArticleId = "244490";
|
||||
var wgIsArticle = true;
|
||||
var wgUserName = null;
|
||||
var wgUserGroups = null;
|
||||
var wgUserLanguage = "en";
|
||||
var wgContentLanguage = "en";
|
||||
var wgBreakFrames = false;
|
||||
var wgCurRevisionId = 309764390;
|
||||
var wgVersion = "1.16alpha-wmf";
|
||||
var wgEnableAPI = true;
|
||||
var wgEnableWriteAPI = true;
|
||||
var wgSeparatorTransformTable = ["", ""];
|
||||
var wgDigitTransformTable = ["", ""];
|
||||
var wgMainPageTitle = "Main Page";
|
||||
var wgMWSuggestTemplate = "http://en.wikipedia.org/w/api.php?action=opensearch\x26search={searchTerms}\x26namespace={namespaces}\x26suggest";
|
||||
var wgDBname = "enwiki";
|
||||
var wgSearchNamespaces = [0];
|
||||
var wgMWSuggestMessages = ["with suggestions", "no suggestions"];
|
||||
var wgRestrictionEdit = [];
|
||||
var wgRestrictionMove = [];
|
||||
/*]]>*/</script>
|
||||
|
||||
<script type="text/javascript" src="/skins-1.5/common/wikibits.js?233zz2"><!-- wikibits js --></script>
|
||||
<!-- Head Scripts -->
|
||||
<script type="text/javascript" src="/skins-1.5/common/ajax.js?233zz2"></script>
|
||||
<script type="text/javascript" src="/skins-1.5/common/mwsuggest.js?233zz2"></script>
|
||||
<script type="text/javascript">/*<![CDATA[*/
|
||||
var wgNotice='';var wgNoticeLocal='';
|
||||
/*]]>*/</script>
|
||||
<script type="text/javascript" src="http://upload.wikimedia.org/centralnotice/wikipedia/en/centralnotice.js?233zz2"></script>
|
||||
|
||||
<script type="text/javascript" src="/w/index.php?title=-&action=raw&gen=js&useskin=monobook"><!-- site js --></script>
|
||||
</head>
|
||||
<body class="mediawiki ltr ns-0 ns-subject page-SOCKS skin-monobook">
|
||||
<div id="globalWrapper">
|
||||
<div id="column-content">
|
||||
<div id="content">
|
||||
<a name="top" id="top"></a>
|
||||
<div id="siteNotice"><script type='text/javascript'>if (wgNotice != '') document.writeln(wgNotice);</script></div> <h1 id="firstHeading" class="firstHeading">SOCKS</h1>
|
||||
<div id="bodyContent">
|
||||
<h3 id="siteSub">From Wikipedia, the free encyclopedia</h3>
|
||||
<div id="contentSub"></div>
|
||||
<div id="jump-to-nav">Jump to: <a href="#column-one">navigation</a>, <a href="#searchInput">search</a></div> <!-- start content -->
|
||||
<div class="dablink">This article is about an internet protocol. For other uses, see <a href="/wiki/Socks_(disambiguation)" title="Socks (disambiguation)">Socks (disambiguation)</a>.</div>
|
||||
<table class="metadata plainlinks ambox ambox-style" style="">
|
||||
<tr>
|
||||
<td class="mbox-image">
|
||||
<div style="width: 52px;"><a href="/wiki/File:Ambox_style.png" class="image" title="Ambox style.png"><img alt="" src="http://upload.wikimedia.org/wikipedia/commons/d/d6/Ambox_style.png" width="40" height="40" /></a></div>
|
||||
</td>
|
||||
<td class="mbox-text" style="">This article <b>may be too technical for most readers to understand</b>. Please <a href="http://en.wikipedia.org/w/index.php?title=SOCKS&action=edit" class="external text" title="http://en.wikipedia.org/w/index.php?title=SOCKS&action=edit" rel="nofollow">expand</a> it to <a href="/wiki/Wikipedia:Make_technical_articles_accessible" title="Wikipedia:Make technical articles accessible">make it accessible to non-experts</a>, without removing the technical details.</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p><b>SOCKS</b> is an <a href="/wiki/Internet" title="Internet">Internet</a> <a href="/wiki/Protocol_(computing)" title="Protocol (computing)">protocol</a> that facilitates the routing of <a href="/wiki/Packet_(information_technology)" title="Packet (information technology)">network packets</a> between <a href="/wiki/Client-server" title="Client-server">client-server</a> applications via a <a href="/wiki/Proxy_server" title="Proxy server">proxy server</a>. SOCKS performs at Layer 5 of the <a href="/wiki/OSI_model" title="OSI model">OSI model</a>—the <a href="/wiki/Session_layer" title="Session layer" class="mw-redirect">Session Layer</a> (an intermediate layer between the <a href="/wiki/Presentation_layer" title="Presentation layer" class="mw-redirect">presentation layer</a> and the <a href="/wiki/Transport_layer" title="Transport layer" class="mw-redirect">transport layer</a>).</p>
|
||||
<table id="toc" class="toc" summary="Contents">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="toctitle">
|
||||
<h2>Contents</h2>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="toclevel-1"><a href="#History"><span class="tocnumber">1</span> <span class="toctext">History</span></a></li>
|
||||
<li class="toclevel-1"><a href="#Comparison_between_SOCKS_and_HTTP_proxies"><span class="tocnumber">2</span> <span class="toctext">Comparison between SOCKS and HTTP proxies</span></a>
|
||||
<ul>
|
||||
<li class="toclevel-2"><a href="#SOCKS"><span class="tocnumber">2.1</span> <span class="toctext">SOCKS</span></a></li>
|
||||
<li class="toclevel-2"><a href="#HTTP"><span class="tocnumber">2.2</span> <span class="toctext">HTTP</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toclevel-1"><a href="#SOCKS_4_protocol"><span class="tocnumber">3</span> <span class="toctext">SOCKS 4 protocol</span></a>
|
||||
<ul>
|
||||
<li class="toclevel-2"><a href="#SOCKS_4a_protocol"><span class="tocnumber">3.1</span> <span class="toctext">SOCKS 4a protocol</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toclevel-1"><a href="#SOCKS_5_protocol"><span class="tocnumber">4</span> <span class="toctext">SOCKS 5 protocol</span></a></li>
|
||||
<li class="toclevel-1"><a href="#Related_software"><span class="tocnumber">5</span> <span class="toctext">Related software</span></a></li>
|
||||
<li class="toclevel-1"><a href="#References"><span class="tocnumber">6</span> <span class="toctext">References</span></a></li>
|
||||
<li class="toclevel-1"><a href="#External_links"><span class="tocnumber">7</span> <span class="toctext">External links</span></a></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); }
|
||||
//]]>
|
||||
</script>
|
||||
<p><a name="History" id="History"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=1" title="Edit section: History">edit</a>]</span> <span class="mw-headline">History</span></h2>
|
||||
<p>The protocol was originally developed by David Koblas, a system administrator of <a href="/wiki/MIPS_Computer_Systems" title="MIPS Computer Systems" class="mw-redirect">MIPS Computer Systems</a>. After MIPS was taken over by <a href="/wiki/Silicon_Graphics" title="Silicon Graphics">Silicon Graphics</a> in 1992, Koblas presented a paper on SOCKS at that year's Usenix Security Symposium and SOCKS became publicly available.<sup id="cite_ref-0" class="reference"><a href="#cite_note-0"><span>[</span>1<span>]</span></a></sup> The protocol was extended to version 4 by Ying-Da Lee of <a href="/wiki/NEC" title="NEC">NEC</a>.</p>
|
||||
<p>The SOCKS reference architecture and client are owned by <a href="/w/index.php?title=Permeo_Technologies&action=edit&redlink=1" class="new" title="Permeo Technologies (page does not exist)">Permeo Technologies</a><sup id="cite_ref-1" class="reference"><a href="#cite_note-1"><span>[</span>2<span>]</span></a></sup> a spin-off from <a href="/wiki/NEC" title="NEC">NEC</a>. (Note that Permeo Technologies has been bought out by <a href="/wiki/Blue_Coat_Systems" title="Blue Coat Systems">Blue Coat Systems</a>.<sup id="cite_ref-2" class="reference"><a href="#cite_note-2"><span>[</span>3<span>]</span></a></sup><sup id="cite_ref-3" class="reference"><a href="#cite_note-3"><span>[</span>4<span>]</span></a></sup>)</p>
|
||||
<p><a name="Comparison_between_SOCKS_and_HTTP_proxies" id="Comparison_between_SOCKS_and_HTTP_proxies"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=2" title="Edit section: Comparison between SOCKS and HTTP proxies">edit</a>]</span> <span class="mw-headline">Comparison between SOCKS and HTTP proxies</span></h2>
|
||||
<p>SOCKS uses a handshake protocol to inform the proxy software about the connection that the client is trying to make and may be used for any form of <a href="/wiki/Transmission_Control_Protocol" title="Transmission Control Protocol">TCP</a> or <a href="/wiki/User_Datagram_Protocol" title="User Datagram Protocol">UDP</a> socket connection, whereas a <a href="/wiki/HTTP" title="HTTP" class="mw-redirect">HTTP</a> proxy analyses the <a href="/wiki/List_of_HTTP_headers" title="List of HTTP headers">HTTP headers</a> sent through it in order to deduce the address of the server and therefore may only be used for HTTP traffic. The following examples demonstrate the difference between the SOCKS and HTTP proxy protocols:</p>
|
||||
<p><a name="SOCKS" id="SOCKS"></a></p>
|
||||
<h3><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=3" title="Edit section: SOCKS">edit</a>]</span> <span class="mw-headline">SOCKS</span></h3>
|
||||
<p>Bill wishes to communicate with Jane over the internet, but a firewall exists on his network between them and Bill is not authorised to communicate through it himself. Therefore, he connects to the SOCKS proxy on his network and sends information about the connection he wishes to make to Jane. The SOCKS proxy opens a connection through the firewall and facilitates the communication between Bill and Jane. For more information on the technical specifics of the SOCKS protocol, see the sections below.</p>
|
||||
<p><a name="HTTP" id="HTTP"></a></p>
|
||||
<h3><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=4" title="Edit section: HTTP">edit</a>]</span> <span class="mw-headline">HTTP</span></h3>
|
||||
<p>Bill wishes to download a web page from Jane, who runs a web server. Bill cannot directly connect to Jane's server, as a firewall has been put in place on his network. In order to communicate with the server, Bill connects to his network's HTTP proxy. His internet browser communicates with the proxy in exactly the same way it would the target server—it sends a standard HTTP request header. The HTTP proxy reads the request and looks for the Host header. It then connects to the server specified in the header and transmits any data the server replies with back to Bill.</p>
|
||||
<p><a name="SOCKS_4_protocol" id="SOCKS_4_protocol"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=5" title="Edit section: SOCKS 4 protocol">edit</a>]</span> <span class="mw-headline">SOCKS 4 protocol</span></h2>
|
||||
<p>A typical SOCKS 4 connection request looks like this (one byte each):</p>
|
||||
<p>Client to SOCKS Server:</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS version number, 1 byte, must be 0x04 for this version</li>
|
||||
<li>field 2: command code, 1 byte:
|
||||
<ul>
|
||||
<li>0x01 = establish a TCP/IP stream connection</li>
|
||||
<li>0x02 = establish a TCP/IP port binding</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: network byte order port number, 2 bytes</li>
|
||||
<li>field 4: network byte order IP address, 4 bytes</li>
|
||||
<li>field 5: the user ID string, variable length, terminated with a null (0x00)</li>
|
||||
</ul>
|
||||
<p>Server to SOCKS client:</p>
|
||||
<ul>
|
||||
<li>field 1: null byte</li>
|
||||
<li>field 2: status, 1 byte:
|
||||
<ul>
|
||||
<li>0x5a = request granted</li>
|
||||
<li>0x5b = request rejected or failed</li>
|
||||
<li>0x5c = request failed because client is not running <a href="/wiki/Identd" title="Identd" class="mw-redirect">identd</a> (or not reachable from the server)</li>
|
||||
<li>0x5d = request failed because client's identd could not confirm the user ID string in the request</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: 2 arbitrary bytes, that should be ignored</li>
|
||||
<li>field 4: 4 arbitrary bytes, that should be ignored</li>
|
||||
</ul>
|
||||
<p>This is a SOCKS 4 request to connect Fred to 66.102.7.99:80, the server replies with an "OK".</p>
|
||||
<ul>
|
||||
<li>Client: 0x04 | 0x01 | 0x00 0x50 | 0x42 0x66 0x07 0x63 | 0x46 0x72 0x65 0x64 0x00
|
||||
<ul>
|
||||
<li>The last field is 'Fred' in <a href="/wiki/ASCII" title="ASCII">ASCII</a>, followed by a null byte.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Server: 0x00 | 0x5a | 0xXX 0xXX | 0xXX 0xXX 0xXX 0xXX
|
||||
<ul>
|
||||
<li>0xXX can be any byte value. The Socks 4 protocol specifies the values of these bytes should be ignored.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>From this point on any data sent from the SOCKS client to the SOCKS server will be relayed to 66.102.7.99 and vice versa.</p>
|
||||
<p>The command field can be 0x01 for "connect" or 0x02 for "bind". "bind" allows incoming connections for protocols like active <a href="/wiki/File_Transfer_Protocol" title="File Transfer Protocol">FTP</a>.</p>
|
||||
<p><a name="SOCKS_4a_protocol" id="SOCKS_4a_protocol"></a></p>
|
||||
<h3><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=6" title="Edit section: SOCKS 4a protocol">edit</a>]</span> <span class="mw-headline">SOCKS 4a protocol</span></h3>
|
||||
<p><b>SOCKS 4a</b> is a simple extension to SOCKS 4 protocol that allows a client that cannot resolve the destination host's domain name to specify it.</p>
|
||||
<p>The client should set the first three bytes of DSTIP to NULL and the last byte to a non-zero value. (This corresponds to IP address 0.0.0.x, with x nonzero, an inadmissible destination address and thus should never occur if the client can resolve the domain name.) Following the NULL byte terminating USERID, the client must send the destination domain name and terminate it with another NULL byte. This is used for both "connect" and "bind" requests.</p>
|
||||
<p>Client to SOCKS server:</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS version number, 1 byte, must be 0x04 for this version</li>
|
||||
<li>field 2: command code, 1 byte:
|
||||
<ul>
|
||||
<li>0x01 = establish a TCP/IP stream connection</li>
|
||||
<li>0x02 = establish a TCP/IP port binding</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: network byte order port number, 2 bytes</li>
|
||||
<li>field 4: deliberate invalid IP address, 4 bytes, first three must be 0x00 and the last one must not be 0x00</li>
|
||||
<li>field 5: the user ID string, variable length, terminated with a null (0x00)</li>
|
||||
<li>field 6: the domain name of the host we want to contact, variable length, terminated with a null (0x00)</li>
|
||||
</ul>
|
||||
<p>Server to SOCKS client:</p>
|
||||
<ul>
|
||||
<li>field 1: null byte</li>
|
||||
<li>field 2: status, 1 byte:
|
||||
<ul>
|
||||
<li>0x5a = request granted</li>
|
||||
<li>0x5b = request rejected or failed</li>
|
||||
<li>0x5c = request failed because client is not running identd (or not reachable from the server)</li>
|
||||
<li>0x5d = request failed because client's identd could not confirm the user ID string in the request</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: network byte order port number, 2 bytes</li>
|
||||
<li>field 4: network byte order IP address, 4 bytes</li>
|
||||
</ul>
|
||||
<p>A server using protocol 4A must check the DSTIP in the request <a href="/wiki/Packet_(information_technology)" title="Packet (information technology)">packet</a>. If it represents address 0.0.0.x with nonzero x, the server must read in the domain name that the client sends in the packet. The server should resolve the <a href="/wiki/Domain_name" title="Domain name">domain name</a> and make connection to the destination host if it can.</p>
|
||||
<p><a name="SOCKS_5_protocol" id="SOCKS_5_protocol"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=7" title="Edit section: SOCKS 5 protocol">edit</a>]</span> <span class="mw-headline">SOCKS 5 protocol</span></h2>
|
||||
<p>The SOCKS 5 protocol is an extension of the SOCKS 4 protocol that is defined in <a href="http://www.rfc-editor.org/rfc/rfc1928.txt" class="external text" title="http://www.rfc-editor.org/rfc/rfc1928.txt" rel="nofollow">RFC 1928</a>. It offers more choices of authentication, adds support for <a href="/wiki/IPv6" title="IPv6">IPv6</a> and <a href="/wiki/User_Datagram_Protocol" title="User Datagram Protocol">UDP</a> that can be used for <a href="/wiki/DNS_lookup" title="DNS lookup" class="mw-redirect">DNS lookups</a>. The initial handshake now consists of the following:</p>
|
||||
<ul>
|
||||
<li>Client connects and sends a greeting which includes a list of authentication methods supported.</li>
|
||||
<li>Server chooses one (or sends a failure response if none of the offered methods are acceptable).</li>
|
||||
<li>Several messages may now pass between the client and the server depending on the authentication method chosen.</li>
|
||||
<li>Client sends a connection request similar to SOCKS 4.</li>
|
||||
<li>Server responds similar to SOCKS 4.</li>
|
||||
</ul>
|
||||
<p>The authentication methods supported are numbered as follows:</p>
|
||||
<ul>
|
||||
<li>0x00: No authentication</li>
|
||||
<li>0x01: <a href="/wiki/GSSAPI" title="GSSAPI" class="mw-redirect">GSSAPI</a> <sup id="cite_ref-4" class="reference"><a href="#cite_note-4"><span>[</span>5<span>]</span></a></sup></li>
|
||||
<li>0x02: Username/Password <sup id="cite_ref-5" class="reference"><a href="#cite_note-5"><span>[</span>6<span>]</span></a></sup></li>
|
||||
<li>0x03-0x7F: methods assigned by <a href="/wiki/Internet_Assigned_Numbers_Authority" title="Internet Assigned Numbers Authority">IANA</a> <sup id="cite_ref-6" class="reference"><a href="#cite_note-6"><span>[</span>7<span>]</span></a></sup></li>
|
||||
<li>0x80-0xFE: methods reserved for private use</li>
|
||||
</ul>
|
||||
<p>The initial greeting from the client is</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS version number (must be 0x05 for this version)</li>
|
||||
<li>field 2: number of authentication methods supported, 1 byte</li>
|
||||
<li>field 3: authentication methods, variable length, 1 byte per method supported</li>
|
||||
</ul>
|
||||
<p>The server's choice is communicated:</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS version, 1 byte (0x05 for this version)</li>
|
||||
<li>field 2: chosen authentication method, 1 byte, or 0xFF if no acceptable methods were offered</li>
|
||||
</ul>
|
||||
<p>The subsequent authentication is method-dependent and described in <a href="http://tools.ietf.org/html/rfc1929" class="external text" title="http://tools.ietf.org/html/rfc1929" rel="nofollow">RFC 1929</a>:</p>
|
||||
<p>The client's authentication request is</p>
|
||||
<ul>
|
||||
<li>field 1: version number, 1 byte (must be 0x01)</li>
|
||||
<li>field 2: username length, 1 byte</li>
|
||||
<li>field 3: username</li>
|
||||
<li>field 4: password length, 1 byte</li>
|
||||
<li>field 5: password</li>
|
||||
</ul>
|
||||
<p>Server response for authentication:</p>
|
||||
<ul>
|
||||
<li>field 1: version, 1 byte</li>
|
||||
<li>field 2: status code, 1 byte.
|
||||
<ul>
|
||||
<li>0x00 = success</li>
|
||||
<li>any other value = failure, connection must be closed</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>The client's connection request is</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS version number, 1 byte (must be 0x05 for this version)</li>
|
||||
<li>field 2: command code, 1 byte:
|
||||
<ul>
|
||||
<li>0x01 = establish a TCP/IP stream connection</li>
|
||||
<li>0x02 = establish a TCP/IP port binding</li>
|
||||
<li>0x03 = associate a UDP port</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: reserved, must be 0x00</li>
|
||||
<li>field 4: address type, 1 byte:
|
||||
<ul>
|
||||
<li>0x01 = IPv4 address</li>
|
||||
<li>0x03 = Domain name</li>
|
||||
<li>0x04 = IPv6 address</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 5: destination address of
|
||||
<ul>
|
||||
<li>4 bytes for IPv4 address</li>
|
||||
<li>1 byte of name length followed by the name for Domain name</li>
|
||||
<li>16 bytes for IPv6 address</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 6: port number in a <a href="/wiki/Network_byte_order" title="Network byte order" class="mw-redirect">network byte order</a>, 2 bytes</li>
|
||||
</ul>
|
||||
<p>Server response:</p>
|
||||
<ul>
|
||||
<li>field 1: SOCKS protocol version, 1 byte (0x05 for this version)</li>
|
||||
<li>field 2: status, 1 byte:
|
||||
<ul>
|
||||
<li>0x00 = request granted</li>
|
||||
<li>0x01 = general failure</li>
|
||||
<li>0x02 = connection not allowed by ruleset</li>
|
||||
<li>0x03 = network unreachable</li>
|
||||
<li>0x04 = host unreachable</li>
|
||||
<li>0x05 = connection refused by destination host</li>
|
||||
<li>0x06 = <a href="/wiki/Time_to_live" title="Time to live">TTL</a> expired</li>
|
||||
<li>0x07 = command not supported / protocol error</li>
|
||||
<li>0x08 = address type not supported</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 3: reserved, must be 0x00</li>
|
||||
<li>field 4: address type, 1 byte:
|
||||
<ul>
|
||||
<li>0x01 = IPv4 address</li>
|
||||
<li>0x03 = Domain name</li>
|
||||
<li>0x04 = IPv6 address</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 5: destination address of
|
||||
<ul>
|
||||
<li>4 bytes for IPv4 address</li>
|
||||
<li>1 byte of name length followed by the name for Domain name</li>
|
||||
<li>16 bytes for IPv6 address</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>field 6: network byte order port number, 2 bytes</li>
|
||||
</ul>
|
||||
<p>There are client programs that "socksify",<sup id="cite_ref-7" class="reference"><a href="#cite_note-7"><span>[</span>8<span>]</span></a></sup> which allows adaptation of any networked software to connect to external networks via SOCKS.</p>
|
||||
<p><a name="Related_software" id="Related_software"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=8" title="Edit section: Related software">edit</a>]</span> <span class="mw-headline">Related software</span></h2>
|
||||
<ul>
|
||||
<li><a href="http://ksb.sourceforge.net" class="external text" title="http://ksb.sourceforge.net" rel="nofollow">Kernel SOCKS Bouncer</a> ksb26 (Kernel Socks Bouncer) is a Linux Kernel 2.6.x Loadable Kernel Module that redirects TCP connection (to user-defined target hosts) through socks 4/5 chains.</li>
|
||||
<li><a href="http://ss5.sourceforge.net/" class="external text" title="http://ss5.sourceforge.net/" rel="nofollow">SS5 Socks Server</a> is an open-source SOCKS4/SOCKS5 server.</li>
|
||||
<li><a href="http://www.inet.no/dante/" class="external text" title="http://www.inet.no/dante/" rel="nofollow">Dante</a> is an open-source SOCKS4/SOCKS5 implementation with commercial support developed by <a href="http://www.inet.no/english/index.html" class="external text" title="http://www.inet.no/english/index.html" rel="nofollow">Inferno Nettverk A/S</a>.</li>
|
||||
<li><a href="/wiki/OpenSSH" title="OpenSSH">OpenSSH</a> allows dynamic creation of tunnels, specified via a subset of the SOCKS protocol, supporting the CONNECT command.</li>
|
||||
<li><a href="/wiki/PuTTY" title="PuTTY">PuTTY</a> is a Win32 SSH client that supports local creation of SOCKS (dynamic) tunnels through remote SSH servers.</li>
|
||||
<li><a href="/w/index.php?title=WinSocks&action=edit&redlink=1" class="new" title="WinSocks (page does not exist)">WinSocks</a> is a light-weight SOCKS4/SOCKS5 server developed by <a href="http://www.proxylabs.com/" class="external text" title="http://www.proxylabs.com/" rel="nofollow">Proxy Labs</a>.</li>
|
||||
<li><a href="http://www.dest-unreach.org/socat/" class="external text" title="http://www.dest-unreach.org/socat/" rel="nofollow">SOcat (SOcket CAT)</a> is a multipurpose relay program : includes socks4, and socks4a functionality for Linux and Mac.</li>
|
||||
<li><a href="http://www.freecap.ru/eng/" class="external text" title="http://www.freecap.ru/eng/" rel="nofollow">FreeCap</a> Socksifyer for Windows, any App can run its network traffic transparently via a SOCKS or HTTP proxy.</li>
|
||||
<li><a href="http://sourceforge.net/projects/ssspl" class="external text" title="http://sourceforge.net/projects/ssspl" rel="nofollow">Simple Socks Server for Perl</a>: SSS is a Simple SOCKS Server written in perl that implements the SOCKS v5 protocol.</li>
|
||||
<li><a href="/wiki/Sun_Java_System_Web_Proxy_Server" title="Sun Java System Web Proxy Server">Sun Java System Web Proxy Server</a> is a caching proxy server running on Solaris, Linux and Windows servers that supports HTtp://S, NSAPI I/O filters, dynamic reconfiguration, SOCKSv5 and <a href="/wiki/Reverse_proxy" title="Reverse proxy">reverse proxy</a>.</li>
|
||||
<li><a href="http://barracudaserver.com/products/BarracudaDrive/" class="external text" title="http://barracudaserver.com/products/BarracudaDrive/" rel="nofollow">BarracudaDrive Web Server</a>, commercial SOCKS HTTP HTTPS tunnel/server, available for Windows, embedded Linux and Mac OS X.</li>
|
||||
<li><a href="/wiki/Delegate_(networking)" title="Delegate (networking)" class="mw-redirect">DeleGate</a> is a multi-purpose application level gateway and proxy server which runs on multiple platforms. Beside SOCKS it also supports HTTP(S), FTP, NNTP, SMTP, POP, IMAP, LDAP, Telnet, DNS and many more.</li>
|
||||
<li><a href="http://www.handcraftedsoftware.org/index.php?page=5/" class="external text" title="http://www.handcraftedsoftware.org/index.php?page=5/" rel="nofollow">Freeproxy</a> Is free proxy server software. It supports HTTP, SOCKS, and many other protocols.</li>
|
||||
<li><a href="/wiki/WinGate" title="WinGate">WinGate</a> is a multi-protocol proxy server and SOCKS server for Microsoft Windows.</li>
|
||||
</ul>
|
||||
<p><a name="References" id="References"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=9" title="Edit section: References">edit</a>]</span> <span class="mw-headline">References</span></h2>
|
||||
<div class="references-small references-column-count references-column-count-2" style="-moz-column-count:2; column-count:2;">
|
||||
<ol class="references">
|
||||
<li id="cite_note-0"><b><a href="#cite_ref-0">^</a></b> Darmohray, Tina. "<a href="http://www.usenix.org/publications/login/2005-02/pdfs/firewalls.pdf" class="external text" title="http://www.usenix.org/publications/login/2005-02/pdfs/firewalls.pdf" rel="nofollow">Firewalls and fairy tales</a>". ;LOGIN:. Vol 30, no. 1.</li>
|
||||
<li id="cite_note-1"><b><a href="#cite_ref-1">^</a></b> <a href="http://www.socks.permeo.com/" class="external free" title="http://www.socks.permeo.com/" rel="nofollow">http://www.socks.permeo.com/</a> (broken link as of July 2008)</li>
|
||||
<li id="cite_note-2"><b><a href="#cite_ref-2">^</a></b> <cite style="font-style:normal" class="web">"<a href="http://www.bluecoat.com/news/releases/2006/010306_permeo.html" class="external text" title="http://www.bluecoat.com/news/releases/2006/010306_permeo.html" rel="nofollow">News Release from</a>". Bluecoat. 2009-06-14<span class="printonly">. <a href="http://www.bluecoat.com/news/releases/2006/010306_permeo.html" class="external free" title="http://www.bluecoat.com/news/releases/2006/010306_permeo.html" rel="nofollow">http://www.bluecoat.com/news/releases/2006/010306_permeo.html</a></span><span class="reference-accessdate">. Retrieved 2009-06-19</span>.</cite><span class="Z3988" title="ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=bookitem&rft.btitle=News+Release+from&rft.atitle=&rft.date=2009-06-14&rft.pub=Bluecoat&rft_id=http%3A%2F%2Fwww.bluecoat.com%2Fnews%2Freleases%2F2006%2F010306_permeo.html&rfr_id=info:sid/en.wikipedia.org:SOCKS"><span style="display: none;"> </span></span></li>
|
||||
<li id="cite_note-3"><b><a href="#cite_ref-3">^</a></b> <a href="http://www.infosecurityproductsguide.com/hot2006/PermeoTechnologies.html" class="external text" title="http://www.infosecurityproductsguide.com/hot2006/PermeoTechnologies.html" rel="nofollow">Article from</a> infosecurityproductsguide.com</li>
|
||||
<li id="cite_note-4"><b><a href="#cite_ref-4">^</a></b> <cite style="font-style:normal" class="web">"<a href="http://tools.ietf.org/html/rfc1961" class="external text" title="http://tools.ietf.org/html/rfc1961" rel="nofollow">RFC 1961</a>". Tools.ietf.org<span class="printonly">. <a href="http://tools.ietf.org/html/rfc1961" class="external free" title="http://tools.ietf.org/html/rfc1961" rel="nofollow">http://tools.ietf.org/html/rfc1961</a></span><span class="reference-accessdate">. Retrieved 2009-06-19</span>.</cite><span class="Z3988" title="ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=bookitem&rft.btitle=RFC+1961&rft.atitle=&rft.pub=Tools.ietf.org&rft_id=http%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc1961&rfr_id=info:sid/en.wikipedia.org:SOCKS"><span style="display: none;"> </span></span></li>
|
||||
<li id="cite_note-5"><b><a href="#cite_ref-5">^</a></b> <cite style="font-style:normal" class="web">"<a href="http://tools.ietf.org/html/rfc1929" class="external text" title="http://tools.ietf.org/html/rfc1929" rel="nofollow">RFC 1929</a>". Tools.ietf.org<span class="printonly">. <a href="http://tools.ietf.org/html/rfc1929" class="external free" title="http://tools.ietf.org/html/rfc1929" rel="nofollow">http://tools.ietf.org/html/rfc1929</a></span><span class="reference-accessdate">. Retrieved 2009-06-19</span>.</cite><span class="Z3988" title="ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=bookitem&rft.btitle=RFC+1929&rft.atitle=&rft.pub=Tools.ietf.org&rft_id=http%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc1929&rfr_id=info:sid/en.wikipedia.org:SOCKS"><span style="display: none;"> </span></span></li>
|
||||
<li id="cite_note-6"><b><a href="#cite_ref-6">^</a></b> <a href="http://www.iana.org/assignments/socks-methods" class="external free" title="http://www.iana.org/assignments/socks-methods" rel="nofollow">http://www.iana.org/assignments/socks-methods</a></li>
|
||||
<li id="cite_note-7"><b><a href="#cite_ref-7">^</a></b> <cite style="font-style:normal" class="web">"<a href="http://mindprod.com/jgloss/socksify.html" class="external text" title="http://mindprod.com/jgloss/socksify.html" rel="nofollow">SOCKSIFY : Java Glossary</a>". Mindprod.com. 1996-2008<span class="printonly">. <a href="http://mindprod.com/jgloss/socksify.html" class="external free" title="http://mindprod.com/jgloss/socksify.html" rel="nofollow">http://mindprod.com/jgloss/socksify.html</a></span><span class="reference-accessdate">. Retrieved 2008-10-23</span>.</cite><span class="Z3988" title="ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&rft.genre=bookitem&rft.btitle=SOCKSIFY+%3A+Java+Glossary&rft.atitle=&rft.date=1996-2008&rft.pub=Mindprod.com&rft_id=http%3A%2F%2Fmindprod.com%2Fjgloss%2Fsocksify.html&rfr_id=info:sid/en.wikipedia.org:SOCKS"><span style="display: none;"> </span></span></li>
|
||||
</ol>
|
||||
</div>
|
||||
<p><a name="External_links" id="External_links"></a></p>
|
||||
<h2><span class="editsection">[<a href="/w/index.php?title=SOCKS&action=edit&section=10" title="Edit section: External links">edit</a>]</span> <span class="mw-headline">External links</span></h2>
|
||||
<ul>
|
||||
<li><a href="http://www.tools.ietf.org/html/draft-ietf-aft-socks-chap" class="external text" title="http://www.tools.ietf.org/html/draft-ietf-aft-socks-chap" rel="nofollow">draft-ietf-aft-socks-chap</a>, Challenge-Handshake Authentication Protocol for SOCKS V5</li>
|
||||
<li><a href="http://tools.ietf.org/html/rfc3089" class="external" title="http://tools.ietf.org/html/rfc3089">RFC 3089</a>: A SOCKS-based IPv6/IPv4 Gateway Mechanism</li>
|
||||
<li><a href="http://tools.ietf.org/html/rfc1961" class="external" title="http://tools.ietf.org/html/rfc1961">RFC 1961</a>: GSS-API Authentication Method for SOCKS Version 5</li>
|
||||
<li><a href="http://tools.ietf.org/html/rfc1929" class="external" title="http://tools.ietf.org/html/rfc1929">RFC 1929</a>: Username/Password Authentication for SOCKS V5</li>
|
||||
<li><a href="http://tools.ietf.org/html/rfc1928" class="external" title="http://tools.ietf.org/html/rfc1928">RFC 1928</a>: SOCKS Protocol Version 5</li>
|
||||
<li><a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol" class="external text" title="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol" rel="nofollow">SOCKS: A protocol for TCP proxy across firewalls</a>, SOCKS Protocol Version 4 (<a href="/wiki/NEC" title="NEC">NEC</a>)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<!--
|
||||
NewPP limit report
|
||||
Preprocessor node count: 2044/1000000
|
||||
Post-expand include size: 13333/2048000 bytes
|
||||
Template argument size: 3818/2048000 bytes
|
||||
Expensive parser function count: 1/500
|
||||
-->
|
||||
|
||||
<!-- Saved in parser cache with key enwiki:pcache:idhash:244490-0!1!0!default!!en!2 and timestamp 20090903094641 -->
|
||||
<div class="printfooter">
|
||||
Retrieved from "<a href="http://en.wikipedia.org/wiki/SOCKS">http://en.wikipedia.org/wiki/SOCKS</a>"</div>
|
||||
<div id='catlinks' class='catlinks'><div id="mw-normal-catlinks"><a href="/wiki/Special:Categories" title="Special:Categories">Categories</a>: <span dir='ltr'><a href="/wiki/Category:Internet_protocols" title="Category:Internet protocols">Internet protocols</a></span> | <span dir='ltr'><a href="/wiki/Category:Internet_privacy" title="Category:Internet privacy">Internet privacy</a></span> | <span dir='ltr'><a href="/wiki/Category:Session_layer_protocols" title="Category:Session layer protocols">Session layer protocols</a></span></div><div id="mw-hidden-catlinks" class="mw-hidden-cats-hidden">Hidden categories: <span dir='ltr'><a href="/wiki/Category:Wikipedia_articles_that_are_too_technical" title="Category:Wikipedia articles that are too technical">Wikipedia articles that are too technical</a></span> | <span dir='ltr'><a href="/wiki/Category:Articles_needing_expert_attention" title="Category:Articles needing expert attention">Articles needing expert attention</a></span></div></div> <!-- end content -->
|
||||
<div class="visualClear"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="column-one">
|
||||
<div id="p-cactions" class="portlet">
|
||||
<h5>Views</h5>
|
||||
<div class="pBody">
|
||||
<ul lang="en" xml:lang="en">
|
||||
|
||||
<li id="ca-nstab-main" class="selected"><a href="/wiki/SOCKS" title="View the content page [c]" accesskey="c">Article</a></li>
|
||||
<li id="ca-talk"><a href="/wiki/Talk:SOCKS" title="Discussion about the content page [t]" accesskey="t">Discussion</a></li>
|
||||
<li id="ca-edit"><a href="/w/index.php?title=SOCKS&action=edit" title="You can edit this page. Please use the preview button before saving. [e]" accesskey="e">Edit this page</a></li>
|
||||
<li id="ca-history"><a href="/w/index.php?title=SOCKS&action=history" title="Past versions of this page [h]" accesskey="h">History</a></li> </ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet" id="p-personal">
|
||||
<h5>Personal tools</h5>
|
||||
<div class="pBody">
|
||||
<ul lang="en" xml:lang="en">
|
||||
<li id="pt-acaibeta"><a href="http://en.wikipedia.org/w/index.php?title=Special:UsabilityInitiativeOptIn&from=SOCKS" class="no-text-transform">Try Beta</a></li>
|
||||
<li id="pt-login"><a href="/w/index.php?title=Special:UserLogin&returnto=SOCKS" title="You are encouraged to log in; however, it is not mandatory. [o]" accesskey="o">Log in / create account</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet" id="p-logo">
|
||||
<a style="background-image: url(http://upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png);" href="/wiki/Main_Page" title="Visit the main page"></a>
|
||||
</div>
|
||||
<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
|
||||
<div class='generated-sidebar portlet' id='p-navigation'>
|
||||
<h5 lang="en" xml:lang="en">Navigation</h5>
|
||||
<div class='pBody'>
|
||||
<ul>
|
||||
<li id="n-mainpage-description"><a href="/wiki/Main_Page" title="Visit the main page [z]" accesskey="z">Main page</a></li>
|
||||
<li id="n-contents"><a href="/wiki/Portal:Contents" title="Guides to browsing Wikipedia">Contents</a></li>
|
||||
<li id="n-featuredcontent"><a href="/wiki/Portal:Featured_content" title="Featured content — the best of Wikipedia">Featured content</a></li>
|
||||
<li id="n-currentevents"><a href="/wiki/Portal:Current_events" title="Find background information on current events">Current events</a></li>
|
||||
<li id="n-randompage"><a href="/wiki/Special:Random" title="Load a random article [x]" accesskey="x">Random article</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="p-search" class="portlet">
|
||||
<h5 lang="en" xml:lang="en"><label for="searchInput">Search</label></h5>
|
||||
<div id="searchBody" class="pBody">
|
||||
<form action="/w/index.php" id="searchform"><div>
|
||||
<input type='hidden' name="title" value="Special:Search"/>
|
||||
<input id="searchInput" name="search" type="text" title="Search Wikipedia [f]" accesskey="f" value="" />
|
||||
<input type='submit' name="go" class="searchButton" id="searchGoButton" value="Go" title="Go to a page with this exact name if one exists" />
|
||||
<input type='submit' name="fulltext" class="searchButton" id="mw-searchButton" value="Search" title="Search Wikipedia for this text" />
|
||||
</div></form>
|
||||
</div>
|
||||
</div>
|
||||
<div class='generated-sidebar portlet' id='p-interaction'>
|
||||
<h5 lang="en" xml:lang="en">Interaction</h5>
|
||||
<div class='pBody'>
|
||||
<ul>
|
||||
<li id="n-aboutsite"><a href="/wiki/Wikipedia:About" title="Find out about Wikipedia">About Wikipedia</a></li>
|
||||
<li id="n-portal"><a href="/wiki/Wikipedia:Community_portal" title="About the project, what you can do, where to find things">Community portal</a></li>
|
||||
<li id="n-recentchanges"><a href="/wiki/Special:RecentChanges" title="The list of recent changes in the wiki [r]" accesskey="r">Recent changes</a></li>
|
||||
<li id="n-contact"><a href="/wiki/Wikipedia:Contact_us" title="How to contact Wikipedia">Contact Wikipedia</a></li>
|
||||
<li id="n-sitesupport"><a href="http://wikimediafoundation.org/wiki/Donate/Now/en?utm_source=donate&utm_medium=sidebar&utm_campaign=spontaneous_donation" title="Support us">Donate to Wikipedia</a></li>
|
||||
<li id="n-help"><a href="/wiki/Help:Contents" title="Guidance on how to use and edit Wikipedia">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet" id="p-tb">
|
||||
<h5 lang="en" xml:lang="en">Toolbox</h5>
|
||||
<div class="pBody">
|
||||
<ul>
|
||||
<li id="t-whatlinkshere"><a href="/wiki/Special:WhatLinksHere/SOCKS" title="List of all English Wikipedia pages containing links to this page [j]" accesskey="j">What links here</a></li>
|
||||
<li id="t-recentchangeslinked"><a href="/wiki/Special:RecentChangesLinked/SOCKS" title="Recent changes in pages linked from this page [k]" accesskey="k">Related changes</a></li>
|
||||
<li id="t-upload"><a href="/wiki/Wikipedia:Upload" title="Upload files [u]" accesskey="u">Upload file</a></li>
|
||||
<li id="t-specialpages"><a href="/wiki/Special:SpecialPages" title="List of all special pages [q]" accesskey="q">Special pages</a></li>
|
||||
<li id="t-print"><a href="/w/index.php?title=SOCKS&printable=yes" rel="alternate" title="Printable version of this page [p]" accesskey="p">Printable version</a></li> <li id="t-permalink"><a href="/w/index.php?title=SOCKS&oldid=309764390" title="Permanent link to this revision of the page">Permanent link</a></li><li id="t-cite"><a href="/w/index.php?title=Special:Cite&page=SOCKS&id=309764390">Cite this page</a></li> </ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="p-lang" class="portlet">
|
||||
<h5 lang="en" xml:lang="en">Languages</h5>
|
||||
<div class="pBody">
|
||||
<ul>
|
||||
<li class="interwiki-de"><a href="http://de.wikipedia.org/wiki/SOCKS">Deutsch</a></li>
|
||||
<li class="interwiki-es"><a href="http://es.wikipedia.org/wiki/SOCKS">Español</a></li>
|
||||
<li class="interwiki-fr"><a href="http://fr.wikipedia.org/wiki/SOCKS">Français</a></li>
|
||||
<li class="interwiki-it"><a href="http://it.wikipedia.org/wiki/SOCKS">Italiano</a></li>
|
||||
<li class="interwiki-lv"><a href="http://lv.wikipedia.org/wiki/SOCKS">Latviešu</a></li>
|
||||
<li class="interwiki-nl"><a href="http://nl.wikipedia.org/wiki/SOCKS">Nederlands</a></li>
|
||||
<li class="interwiki-ja"><a href="http://ja.wikipedia.org/wiki/SOCKS">日本語</a></li>
|
||||
<li class="interwiki-pl"><a href="http://pl.wikipedia.org/wiki/SOCKS">Polski</a></li>
|
||||
<li class="interwiki-pt"><a href="http://pt.wikipedia.org/wiki/SOCKS">Português</a></li>
|
||||
<li class="interwiki-ru"><a href="http://ru.wikipedia.org/wiki/SOCKS">Русский</a></li>
|
||||
<li class="interwiki-simple"><a href="http://simple.wikipedia.org/wiki/SOCKS">Simple English</a></li>
|
||||
<li class="interwiki-zh"><a href="http://zh.wikipedia.org/wiki/SOCKS">中文</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end of the left (by default at least) column -->
|
||||
<div class="visualClear"></div>
|
||||
<div id="footer">
|
||||
<div id="f-poweredbyico"><a href="http://www.mediawiki.org/"><img src="/skins-1.5/common/images/poweredby_mediawiki_88x31.png" alt="Powered by MediaWiki" /></a></div>
|
||||
<div id="f-copyrightico"><a href="http://wikimediafoundation.org/"><img src="/images/wikimedia-button.png" width="88" height="31" alt="Wikimedia Foundation"/></a></div>
|
||||
<ul id="f-list">
|
||||
<li id="lastmod"> This page was last modified on 24 August 2009 at 11:21.</li>
|
||||
<li id="copyright">Text is available under the <a rel="license" href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License">Creative Commons Attribution-ShareAlike License</a><a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/" style="display:none;"></a>;
|
||||
additional terms may apply.
|
||||
See <a href="http://wikimediafoundation.org/wiki/Terms_of_Use">Terms of Use</a> for details.<br/>
|
||||
Wikipedia® is a registered trademark of the <a href="http://www.wikimediafoundation.org/">Wikimedia Foundation, Inc.</a>, a non-profit organization.</li>
|
||||
<li id="privacy"><a href="http://wikimediafoundation.org/wiki/Privacy_policy" class="extiw" title="wikimedia:Privacy policy">Privacy policy</a></li>
|
||||
<li id="about"><a href="/wiki/Wikipedia:About" title="Wikipedia:About">About Wikipedia</a></li>
|
||||
<li id="disclaimer"><a href="/wiki/Wikipedia:General_disclaimer" title="Wikipedia:General disclaimer">Disclaimers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">if (window.runOnloadHook) runOnloadHook();</script>
|
||||
<!-- Served by srv206 in 0.048 secs. --></body></html>
|
507
jsocks/docs/rfc1928.txt
Normal file
507
jsocks/docs/rfc1928.txt
Normal file
|
@ -0,0 +1,507 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Network Working Group M. Leech
|
||||
Request for Comments: 1928 Bell-Northern Research Ltd
|
||||
Category: Standards Track M. Ganis
|
||||
International Business Machines
|
||||
Y. Lee
|
||||
NEC Systems Laboratory
|
||||
R. Kuris
|
||||
Unify Corporation
|
||||
D. Koblas
|
||||
Independent Consultant
|
||||
L. Jones
|
||||
Hewlett-Packard Company
|
||||
March 1996
|
||||
|
||||
|
||||
SOCKS Protocol Version 5
|
||||
|
||||
Status of this Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
Acknowledgments
|
||||
|
||||
This memo describes a protocol that is an evolution of the previous
|
||||
version of the protocol, version 4 [1]. This new protocol stems from
|
||||
active discussions and prototype implementations. The key
|
||||
contributors are: Marcus Leech: Bell-Northern Research, David Koblas:
|
||||
Independent Consultant, Ying-Da Lee: NEC Systems Laboratory, LaMont
|
||||
Jones: Hewlett-Packard Company, Ron Kuris: Unify Corporation, Matt
|
||||
Ganis: International Business Machines.
|
||||
|
||||
1. Introduction
|
||||
|
||||
The use of network firewalls, systems that effectively isolate an
|
||||
organizations internal network structure from an exterior network,
|
||||
such as the INTERNET is becoming increasingly popular. These
|
||||
firewall systems typically act as application-layer gateways between
|
||||
networks, usually offering controlled TELNET, FTP, and SMTP access.
|
||||
With the emergence of more sophisticated application layer protocols
|
||||
designed to facilitate global information discovery, there exists a
|
||||
need to provide a general framework for these protocols to
|
||||
transparently and securely traverse a firewall.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 1]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
There exists, also, a need for strong authentication of such
|
||||
traversal in as fine-grained a manner as is practical. This
|
||||
requirement stems from the realization that client-server
|
||||
relationships emerge between the networks of various organizations,
|
||||
and that such relationships need to be controlled and often strongly
|
||||
authenticated.
|
||||
|
||||
The protocol described here is designed to provide a framework for
|
||||
client-server applications in both the TCP and UDP domains to
|
||||
conveniently and securely use the services of a network firewall.
|
||||
The protocol is conceptually a "shim-layer" between the application
|
||||
layer and the transport layer, and as such does not provide network-
|
||||
layer gateway services, such as forwarding of ICMP messages.
|
||||
|
||||
2. Existing practice
|
||||
|
||||
There currently exists a protocol, SOCKS Version 4, that provides for
|
||||
unsecured firewall traversal for TCP-based client-server
|
||||
applications, including TELNET, FTP and the popular information-
|
||||
discovery protocols such as HTTP, WAIS and GOPHER.
|
||||
|
||||
This new protocol extends the SOCKS Version 4 model to include UDP,
|
||||
and extends the framework to include provisions for generalized
|
||||
strong authentication schemes, and extends the addressing scheme to
|
||||
encompass domain-name and V6 IP addresses.
|
||||
|
||||
The implementation of the SOCKS protocol typically involves the
|
||||
recompilation or relinking of TCP-based client applications to use
|
||||
the appropriate encapsulation routines in the SOCKS library.
|
||||
|
||||
Note:
|
||||
|
||||
Unless otherwise noted, the decimal numbers appearing in packet-
|
||||
format diagrams represent the length of the corresponding field, in
|
||||
octets. Where a given octet must take on a specific value, the
|
||||
syntax X'hh' is used to denote the value of the single octet in that
|
||||
field. When the word 'Variable' is used, it indicates that the
|
||||
corresponding field has a variable length defined either by an
|
||||
associated (one or two octet) length field, or by a data type field.
|
||||
|
||||
3. Procedure for TCP-based clients
|
||||
|
||||
When a TCP-based client wishes to establish a connection to an object
|
||||
that is reachable only via a firewall (such determination is left up
|
||||
to the implementation), it must open a TCP connection to the
|
||||
appropriate SOCKS port on the SOCKS server system. The SOCKS service
|
||||
is conventionally located on TCP port 1080. If the connection
|
||||
request succeeds, the client enters a negotiation for the
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 2]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
authentication method to be used, authenticates with the chosen
|
||||
method, then sends a relay request. The SOCKS server evaluates the
|
||||
request, and either establishes the appropriate connection or denies
|
||||
it.
|
||||
|
||||
Unless otherwise noted, the decimal numbers appearing in packet-
|
||||
format diagrams represent the length of the corresponding field, in
|
||||
octets. Where a given octet must take on a specific value, the
|
||||
syntax X'hh' is used to denote the value of the single octet in that
|
||||
field. When the word 'Variable' is used, it indicates that the
|
||||
corresponding field has a variable length defined either by an
|
||||
associated (one or two octet) length field, or by a data type field.
|
||||
|
||||
The client connects to the server, and sends a version
|
||||
identifier/method selection message:
|
||||
|
||||
+----+----------+----------+
|
||||
|VER | NMETHODS | METHODS |
|
||||
+----+----------+----------+
|
||||
| 1 | 1 | 1 to 255 |
|
||||
+----+----------+----------+
|
||||
|
||||
The VER field is set to X'05' for this version of the protocol. The
|
||||
NMETHODS field contains the number of method identifier octets that
|
||||
appear in the METHODS field.
|
||||
|
||||
The server selects from one of the methods given in METHODS, and
|
||||
sends a METHOD selection message:
|
||||
|
||||
+----+--------+
|
||||
|VER | METHOD |
|
||||
+----+--------+
|
||||
| 1 | 1 |
|
||||
+----+--------+
|
||||
|
||||
If the selected METHOD is X'FF', none of the methods listed by the
|
||||
client are acceptable, and the client MUST close the connection.
|
||||
|
||||
The values currently defined for METHOD are:
|
||||
|
||||
o X'00' NO AUTHENTICATION REQUIRED
|
||||
o X'01' GSSAPI
|
||||
o X'02' USERNAME/PASSWORD
|
||||
o X'03' to X'7F' IANA ASSIGNED
|
||||
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
|
||||
o X'FF' NO ACCEPTABLE METHODS
|
||||
|
||||
The client and server then enter a method-specific sub-negotiation.
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 3]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
Descriptions of the method-dependent sub-negotiations appear in
|
||||
separate memos.
|
||||
|
||||
Developers of new METHOD support for this protocol should contact
|
||||
IANA for a METHOD number. The ASSIGNED NUMBERS document should be
|
||||
referred to for a current list of METHOD numbers and their
|
||||
corresponding protocols.
|
||||
|
||||
Compliant implementations MUST support GSSAPI and SHOULD support
|
||||
USERNAME/PASSWORD authentication methods.
|
||||
|
||||
4. Requests
|
||||
|
||||
Once the method-dependent subnegotiation has completed, the client
|
||||
sends the request details. If the negotiated method includes
|
||||
encapsulation for purposes of integrity checking and/or
|
||||
confidentiality, these requests MUST be encapsulated in the method-
|
||||
dependent encapsulation.
|
||||
|
||||
The SOCKS request is formed as follows:
|
||||
|
||||
+----+-----+-------+------+----------+----------+
|
||||
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
|
||||
Where:
|
||||
|
||||
o VER protocol version: X'05'
|
||||
o CMD
|
||||
o CONNECT X'01'
|
||||
o BIND X'02'
|
||||
o UDP ASSOCIATE X'03'
|
||||
o RSV RESERVED
|
||||
o ATYP address type of following address
|
||||
o IP V4 address: X'01'
|
||||
o DOMAINNAME: X'03'
|
||||
o IP V6 address: X'04'
|
||||
o DST.ADDR desired destination address
|
||||
o DST.PORT desired destination port in network octet
|
||||
order
|
||||
|
||||
The SOCKS server will typically evaluate the request based on source
|
||||
and destination addresses, and return one or more reply messages, as
|
||||
appropriate for the request type.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 4]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
5. Addressing
|
||||
|
||||
In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies
|
||||
the type of address contained within the field:
|
||||
|
||||
o X'01'
|
||||
|
||||
the address is a version-4 IP address, with a length of 4 octets
|
||||
|
||||
o X'03'
|
||||
|
||||
the address field contains a fully-qualified domain name. The first
|
||||
octet of the address field contains the number of octets of name that
|
||||
follow, there is no terminating NUL octet.
|
||||
|
||||
o X'04'
|
||||
|
||||
the address is a version-6 IP address, with a length of 16 octets.
|
||||
|
||||
6. Replies
|
||||
|
||||
The SOCKS request information is sent by the client as soon as it has
|
||||
established a connection to the SOCKS server, and completed the
|
||||
authentication negotiations. The server evaluates the request, and
|
||||
returns a reply formed as follows:
|
||||
|
||||
+----+-----+-------+------+----------+----------+
|
||||
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
+----+-----+-------+------+----------+----------+
|
||||
|
||||
Where:
|
||||
|
||||
o VER protocol version: X'05'
|
||||
o REP Reply field:
|
||||
o X'00' succeeded
|
||||
o X'01' general SOCKS server failure
|
||||
o X'02' connection not allowed by ruleset
|
||||
o X'03' Network unreachable
|
||||
o X'04' Host unreachable
|
||||
o X'05' Connection refused
|
||||
o X'06' TTL expired
|
||||
o X'07' Command not supported
|
||||
o X'08' Address type not supported
|
||||
o X'09' to X'FF' unassigned
|
||||
o RSV RESERVED
|
||||
o ATYP address type of following address
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 5]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
o IP V4 address: X'01'
|
||||
o DOMAINNAME: X'03'
|
||||
o IP V6 address: X'04'
|
||||
o BND.ADDR server bound address
|
||||
o BND.PORT server bound port in network octet order
|
||||
|
||||
Fields marked RESERVED (RSV) must be set to X'00'.
|
||||
|
||||
If the chosen method includes encapsulation for purposes of
|
||||
authentication, integrity and/or confidentiality, the replies are
|
||||
encapsulated in the method-dependent encapsulation.
|
||||
|
||||
CONNECT
|
||||
|
||||
In the reply to a CONNECT, BND.PORT contains the port number that the
|
||||
server assigned to connect to the target host, while BND.ADDR
|
||||
contains the associated IP address. The supplied BND.ADDR is often
|
||||
different from the IP address that the client uses to reach the SOCKS
|
||||
server, since such servers are often multi-homed. It is expected
|
||||
that the SOCKS server will use DST.ADDR and DST.PORT, and the
|
||||
client-side source address and port in evaluating the CONNECT
|
||||
request.
|
||||
|
||||
BIND
|
||||
|
||||
The BIND request is used in protocols which require the client to
|
||||
accept connections from the server. FTP is a well-known example,
|
||||
which uses the primary client-to-server connection for commands and
|
||||
status reports, but may use a server-to-client connection for
|
||||
transferring data on demand (e.g. LS, GET, PUT).
|
||||
|
||||
It is expected that the client side of an application protocol will
|
||||
use the BIND request only to establish secondary connections after a
|
||||
primary connection is established using CONNECT. In is expected that
|
||||
a SOCKS server will use DST.ADDR and DST.PORT in evaluating the BIND
|
||||
request.
|
||||
|
||||
Two replies are sent from the SOCKS server to the client during a
|
||||
BIND operation. The first is sent after the server creates and binds
|
||||
a new socket. The BND.PORT field contains the port number that the
|
||||
SOCKS server assigned to listen for an incoming connection. The
|
||||
BND.ADDR field contains the associated IP address. The client will
|
||||
typically use these pieces of information to notify (via the primary
|
||||
or control connection) the application server of the rendezvous
|
||||
address. The second reply occurs only after the anticipated incoming
|
||||
connection succeeds or fails.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 6]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
In the second reply, the BND.PORT and BND.ADDR fields contain the
|
||||
address and port number of the connecting host.
|
||||
|
||||
UDP ASSOCIATE
|
||||
|
||||
The UDP ASSOCIATE request is used to establish an association within
|
||||
the UDP relay process to handle UDP datagrams. The DST.ADDR and
|
||||
DST.PORT fields contain the address and port that the client expects
|
||||
to use to send UDP datagrams on for the association. The server MAY
|
||||
use this information to limit access to the association. If the
|
||||
client is not in possesion of the information at the time of the UDP
|
||||
ASSOCIATE, the client MUST use a port number and address of all
|
||||
zeros.
|
||||
|
||||
A UDP association terminates when the TCP connection that the UDP
|
||||
ASSOCIATE request arrived on terminates.
|
||||
|
||||
In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR
|
||||
fields indicate the port number/address where the client MUST send
|
||||
UDP request messages to be relayed.
|
||||
|
||||
Reply Processing
|
||||
|
||||
When a reply (REP value other than X'00') indicates a failure, the
|
||||
SOCKS server MUST terminate the TCP connection shortly after sending
|
||||
the reply. This must be no more than 10 seconds after detecting the
|
||||
condition that caused a failure.
|
||||
|
||||
If the reply code (REP value of X'00') indicates a success, and the
|
||||
request was either a BIND or a CONNECT, the client may now start
|
||||
passing data. If the selected authentication method supports
|
||||
encapsulation for the purposes of integrity, authentication and/or
|
||||
confidentiality, the data are encapsulated using the method-dependent
|
||||
encapsulation. Similarly, when data arrives at the SOCKS server for
|
||||
the client, the server MUST encapsulate the data as appropriate for
|
||||
the authentication method in use.
|
||||
|
||||
7. Procedure for UDP-based clients
|
||||
|
||||
A UDP-based client MUST send its datagrams to the UDP relay server at
|
||||
the UDP port indicated by BND.PORT in the reply to the UDP ASSOCIATE
|
||||
request. If the selected authentication method provides
|
||||
encapsulation for the purposes of authenticity, integrity, and/or
|
||||
confidentiality, the datagram MUST be encapsulated using the
|
||||
appropriate encapsulation. Each UDP datagram carries a UDP request
|
||||
header with it:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 7]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
+----+------+------+----------+----------+----------+
|
||||
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||
+----+------+------+----------+----------+----------+
|
||||
| 2 | 1 | 1 | Variable | 2 | Variable |
|
||||
+----+------+------+----------+----------+----------+
|
||||
|
||||
The fields in the UDP request header are:
|
||||
|
||||
o RSV Reserved X'0000'
|
||||
o FRAG Current fragment number
|
||||
o ATYP address type of following addresses:
|
||||
o IP V4 address: X'01'
|
||||
o DOMAINNAME: X'03'
|
||||
o IP V6 address: X'04'
|
||||
o DST.ADDR desired destination address
|
||||
o DST.PORT desired destination port
|
||||
o DATA user data
|
||||
|
||||
When a UDP relay server decides to relay a UDP datagram, it does so
|
||||
silently, without any notification to the requesting client.
|
||||
Similarly, it will drop datagrams it cannot or will not relay. When
|
||||
a UDP relay server receives a reply datagram from a remote host, it
|
||||
MUST encapsulate that datagram using the above UDP request header,
|
||||
and any authentication-method-dependent encapsulation.
|
||||
|
||||
The UDP relay server MUST acquire from the SOCKS server the expected
|
||||
IP address of the client that will send datagrams to the BND.PORT
|
||||
given in the reply to UDP ASSOCIATE. It MUST drop any datagrams
|
||||
arriving from any source IP address other than the one recorded for
|
||||
the particular association.
|
||||
|
||||
The FRAG field indicates whether or not this datagram is one of a
|
||||
number of fragments. If implemented, the high-order bit indicates
|
||||
end-of-fragment sequence, while a value of X'00' indicates that this
|
||||
datagram is standalone. Values between 1 and 127 indicate the
|
||||
fragment position within a fragment sequence. Each receiver will
|
||||
have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these
|
||||
fragments. The reassembly queue must be reinitialized and the
|
||||
associated fragments abandoned whenever the REASSEMBLY TIMER expires,
|
||||
or a new datagram arrives carrying a FRAG field whose value is less
|
||||
than the highest FRAG value processed for this fragment sequence.
|
||||
The reassembly timer MUST be no less than 5 seconds. It is
|
||||
recommended that fragmentation be avoided by applications wherever
|
||||
possible.
|
||||
|
||||
Implementation of fragmentation is optional; an implementation that
|
||||
does not support fragmentation MUST drop any datagram whose FRAG
|
||||
field is other than X'00'.
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 8]
|
||||
|
||||
RFC 1928 SOCKS Protocol Version 5 March 1996
|
||||
|
||||
|
||||
The programming interface for a SOCKS-aware UDP MUST report an
|
||||
available buffer space for UDP datagrams that is smaller than the
|
||||
actual space provided by the operating system:
|
||||
|
||||
o if ATYP is X'01' - 10+method_dependent octets smaller
|
||||
o if ATYP is X'03' - 262+method_dependent octets smaller
|
||||
o if ATYP is X'04' - 20+method_dependent octets smaller
|
||||
|
||||
8. Security Considerations
|
||||
|
||||
This document describes a protocol for the application-layer
|
||||
traversal of IP network firewalls. The security of such traversal is
|
||||
highly dependent on the particular authentication and encapsulation
|
||||
methods provided in a particular implementation, and selected during
|
||||
negotiation between SOCKS client and SOCKS server.
|
||||
|
||||
Careful consideration should be given by the administrator to the
|
||||
selection of authentication methods.
|
||||
|
||||
9. References
|
||||
|
||||
[1] Koblas, D., "SOCKS", Proceedings: 1992 Usenix Security Symposium.
|
||||
|
||||
Author's Address
|
||||
|
||||
Marcus Leech
|
||||
Bell-Northern Research Ltd
|
||||
P.O. Box 3511, Stn. C,
|
||||
Ottawa, ON
|
||||
CANADA K1Y 4H7
|
||||
|
||||
Phone: (613) 763-9145
|
||||
EMail: mleech@bnr.ca
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech, et al Standards Track [Page 9]
|
||||
|
115
jsocks/docs/rfc1929.txt
Normal file
115
jsocks/docs/rfc1929.txt
Normal file
|
@ -0,0 +1,115 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Network Working Group M. Leech
|
||||
Request for Comments: 1929 Bell-Northern Research Ltd
|
||||
Category: Standards Track March 1996
|
||||
|
||||
|
||||
Username/Password Authentication for SOCKS V5
|
||||
|
||||
Status of this Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
1. Introduction
|
||||
|
||||
The protocol specification for SOCKS Version 5 specifies a
|
||||
generalized framework for the use of arbitrary authentication
|
||||
protocols in the initial socks connection setup. This document
|
||||
describes one of those protocols, as it fits into the SOCKS Version 5
|
||||
authentication "subnegotiation".
|
||||
|
||||
Note:
|
||||
|
||||
Unless otherwise noted, the decimal numbers appearing in packet-
|
||||
format diagrams represent the length of the corresponding field, in
|
||||
octets. Where a given octet must take on a specific value, the
|
||||
syntax X'hh' is used to denote the value of the single octet in that
|
||||
field. When the word 'Variable' is used, it indicates that the
|
||||
corresponding field has a variable length defined either by an
|
||||
associated (one or two octet) length field, or by a data type field.
|
||||
|
||||
2. Initial negotiation
|
||||
|
||||
Once the SOCKS V5 server has started, and the client has selected the
|
||||
Username/Password Authentication protocol, the Username/Password
|
||||
subnegotiation begins. This begins with the client producing a
|
||||
Username/Password request:
|
||||
|
||||
+----+------+----------+------+----------+
|
||||
|VER | ULEN | UNAME | PLEN | PASSWD |
|
||||
+----+------+----------+------+----------+
|
||||
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
|
||||
+----+------+----------+------+----------+
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech Standards Track [Page 1]
|
||||
|
||||
RFC 1929 Username Authentication for SOCKS V5 March 1996
|
||||
|
||||
|
||||
The VER field contains the current version of the subnegotiation,
|
||||
which is X'01'. The ULEN field contains the length of the UNAME field
|
||||
that follows. The UNAME field contains the username as known to the
|
||||
source operating system. The PLEN field contains the length of the
|
||||
PASSWD field that follows. The PASSWD field contains the password
|
||||
association with the given UNAME.
|
||||
|
||||
The server verifies the supplied UNAME and PASSWD, and sends the
|
||||
following response:
|
||||
|
||||
+----+--------+
|
||||
|VER | STATUS |
|
||||
+----+--------+
|
||||
| 1 | 1 |
|
||||
+----+--------+
|
||||
|
||||
A STATUS field of X'00' indicates success. If the server returns a
|
||||
`failure' (STATUS value other than X'00') status, it MUST close the
|
||||
connection.
|
||||
|
||||
3. Security Considerations
|
||||
|
||||
This document describes a subnegotiation that provides authentication
|
||||
services to the SOCKS protocol. Since the request carries the
|
||||
password in cleartext, this subnegotiation is not recommended for
|
||||
environments where "sniffing" is possible and practical.
|
||||
|
||||
4. Author's Address
|
||||
|
||||
Marcus Leech
|
||||
Bell-Northern Research Ltd
|
||||
P.O. Box 3511, Station C
|
||||
Ottawa, ON
|
||||
CANADA K1Y 4H7
|
||||
|
||||
Phone: +1 613 763 9145
|
||||
EMail: mleech@bnr.ca
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech Standards Track [Page 2]
|
||||
|
60
jsocks/pom.xml
Normal file
60
jsocks/pom.xml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<version>0.3.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<description>Cloned from http://sourceforge.net/projects/jsocks/</description>
|
||||
|
||||
<artifactId>jsocks</artifactId>
|
||||
|
||||
|
||||
<!-- <properties>
|
||||
<project.build.sourceEncoding>ASCII</project.build.sourceEncoding>
|
||||
</properties>-->
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.5.11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<!-- <build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<version>2.7.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>-->
|
||||
|
||||
</project>
|
|
@ -0,0 +1,268 @@
|
|||
package com.runjva.sourceforge.jsocks.main;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.InetRange;
|
||||
import com.runjva.sourceforge.jsocks.protocol.ProxyServer;
|
||||
import com.runjva.sourceforge.jsocks.protocol.SocksProxyBase;
|
||||
import com.runjva.sourceforge.jsocks.server.IdentAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class SOCKS {
|
||||
|
||||
private static final int DEFAULT_LISTENING_PORT = 1080;
|
||||
final private static Logger log = LoggerFactory.getLogger(SOCKS.class);
|
||||
|
||||
static public void usage() {
|
||||
System.out.println("Usage: java SOCKS [inifile1 inifile2 ...]\n"
|
||||
+ "If none inifile is given, uses socks.properties.\n");
|
||||
}
|
||||
|
||||
static public void main(String[] args) {
|
||||
|
||||
String[] file_names;
|
||||
int port = DEFAULT_LISTENING_PORT;
|
||||
String logFile = null;
|
||||
String host = null;
|
||||
|
||||
final IdentAuthenticator auth = new IdentAuthenticator();
|
||||
|
||||
InetAddress localIP = null;
|
||||
|
||||
if (args.length == 0) {
|
||||
file_names = new String[]{"socks.properties"};
|
||||
} else {
|
||||
file_names = args;
|
||||
}
|
||||
|
||||
inform("Loading properties");
|
||||
for (int i = 0; i < file_names.length; ++i) {
|
||||
|
||||
inform("Reading file " + file_names[i]);
|
||||
|
||||
final Properties pr = loadProperties(file_names[i]);
|
||||
if (pr == null) {
|
||||
System.err.println("Loading of properties from "
|
||||
+ file_names[i] + "failed.");
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
if (!addAuth(auth, pr)) {
|
||||
System.err.println("Error in file " + file_names[i] + ".");
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
// First file should contain all global settings,
|
||||
// like port and host and log.
|
||||
if (i == 0) {
|
||||
final String port_s = (String) pr.get("port");
|
||||
if (port_s != null) {
|
||||
try {
|
||||
port = Integer.parseInt(port_s);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
System.err.println("Can't parse port: " + port_s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
serverInit(pr);
|
||||
logFile = (String) pr.get("log");
|
||||
host = (String) pr.get("host");
|
||||
}
|
||||
|
||||
// inform("Props:"+pr);
|
||||
}
|
||||
|
||||
if (logFile != null) {
|
||||
System.err.println("log property not supported anymore.");
|
||||
}
|
||||
if (host != null) {
|
||||
try {
|
||||
localIP = InetAddress.getByName(host);
|
||||
} catch (final UnknownHostException uhe) {
|
||||
System.err.println("Can't resolve local ip: " + host);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
inform("Using Ident Authentication scheme: " + auth);
|
||||
final ProxyServer server = new ProxyServer(auth);
|
||||
server.start(port, 5, localIP);
|
||||
}
|
||||
|
||||
static Properties loadProperties(String file_name) {
|
||||
|
||||
final Properties pr = new Properties();
|
||||
|
||||
try {
|
||||
final InputStream fin = new FileInputStream(file_name);
|
||||
pr.load(fin);
|
||||
fin.close();
|
||||
} catch (final IOException ioe) {
|
||||
return null;
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
static boolean addAuth(IdentAuthenticator ident, Properties pr) {
|
||||
|
||||
InetRange irange;
|
||||
|
||||
final String range = (String) pr.get("range");
|
||||
if (range == null) {
|
||||
return false;
|
||||
}
|
||||
irange = parseInetRange(range);
|
||||
|
||||
final String users = (String) pr.get("users");
|
||||
|
||||
if (users == null) {
|
||||
ident.add(irange, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
final Hashtable<String, String> uhash = new Hashtable<String, String>();
|
||||
|
||||
final StringTokenizer st = new StringTokenizer(users, ";");
|
||||
while (st.hasMoreTokens()) {
|
||||
uhash.put(st.nextToken(), "");
|
||||
}
|
||||
|
||||
ident.add(irange, uhash);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does server initialisation.
|
||||
*/
|
||||
static void serverInit(Properties props) {
|
||||
int val;
|
||||
val = readInt(props, "iddleTimeout");
|
||||
if (val >= 0) {
|
||||
ProxyServer.setIddleTimeout(val);
|
||||
inform("Setting iddle timeout to " + val + " ms.");
|
||||
}
|
||||
val = readInt(props, "acceptTimeout");
|
||||
if (val >= 0) {
|
||||
ProxyServer.setAcceptTimeout(val);
|
||||
inform("Setting accept timeout to " + val + " ms.");
|
||||
}
|
||||
val = readInt(props, "udpTimeout");
|
||||
if (val >= 0) {
|
||||
ProxyServer.setUDPTimeout(val);
|
||||
inform("Setting udp timeout to " + val + " ms.");
|
||||
}
|
||||
|
||||
val = readInt(props, "datagramSize");
|
||||
if (val >= 0) {
|
||||
ProxyServer.setDatagramSize(val);
|
||||
inform("Setting datagram size to " + val + " bytes.");
|
||||
}
|
||||
|
||||
proxyInit(props);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises proxy, if any specified.
|
||||
*/
|
||||
static void proxyInit(Properties props) {
|
||||
String proxy_list;
|
||||
SocksProxyBase proxy = null;
|
||||
StringTokenizer st;
|
||||
|
||||
proxy_list = (String) props.get("proxy");
|
||||
if (proxy_list == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
st = new StringTokenizer(proxy_list, ";");
|
||||
while (st.hasMoreTokens()) {
|
||||
final String proxy_entry = st.nextToken();
|
||||
|
||||
final SocksProxyBase p = SocksProxyBase.parseProxy(proxy_entry);
|
||||
|
||||
if (p == null) {
|
||||
exit("Can't parse proxy entry:" + proxy_entry);
|
||||
}
|
||||
|
||||
inform("Adding Proxy:" + p);
|
||||
|
||||
if (proxy != null) {
|
||||
p.setChainProxy(proxy);
|
||||
}
|
||||
|
||||
proxy = p;
|
||||
|
||||
}
|
||||
if (proxy == null) {
|
||||
return; // Empty list
|
||||
}
|
||||
|
||||
final String direct_hosts = (String) props.get("directHosts");
|
||||
if (direct_hosts != null) {
|
||||
final InetRange ir = parseInetRange(direct_hosts);
|
||||
inform("Setting direct hosts:" + ir);
|
||||
proxy.setDirect(ir);
|
||||
}
|
||||
|
||||
ProxyServer.setProxy(proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits range from the string of semicolon separated ranges.
|
||||
*/
|
||||
static InetRange parseInetRange(String source) {
|
||||
final InetRange irange = new InetRange();
|
||||
|
||||
final StringTokenizer st = new StringTokenizer(source, ";");
|
||||
while (st.hasMoreTokens()) {
|
||||
irange.add(st.nextToken());
|
||||
}
|
||||
|
||||
return irange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Integer representaion of the property named name, or -1 if one is not
|
||||
* found.
|
||||
*/
|
||||
static int readInt(Properties props, String name) {
|
||||
int result = -1;
|
||||
final String val = (String) props.get(name);
|
||||
if (val == null) {
|
||||
return -1;
|
||||
}
|
||||
final StringTokenizer st = new StringTokenizer(val);
|
||||
if (!st.hasMoreElements()) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
result = Integer.parseInt(st.nextToken());
|
||||
} catch (final NumberFormatException nfe) {
|
||||
inform("Bad value for " + name + ":" + val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Display functions
|
||||
// /////////////////
|
||||
|
||||
static void inform(String s) {
|
||||
log.info(s);
|
||||
}
|
||||
|
||||
static void exit(String msg) {
|
||||
System.err.println("Error:" + msg);
|
||||
System.err.println("Aborting operation");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 926 B |
|
@ -0,0 +1,674 @@
|
|||
package com.runjva.sourceforge.jsocks.main;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.*;
|
||||
|
||||
public class SocksEcho extends Frame implements ActionListener, Runnable,
|
||||
WindowListener {
|
||||
|
||||
Logger log = LoggerFactory.getLogger(SocksEcho.class);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
// GUI components
|
||||
TextField host_text, port_text, input_text;
|
||||
Button proxy_button, accept_button, clear_button, connect_button,
|
||||
udp_button, quit_button;
|
||||
TextArea output_textarea;
|
||||
Label status_label;
|
||||
|
||||
SocksDialog socks_dialog;
|
||||
|
||||
// Network related members
|
||||
SocksProxyBase proxy = null;
|
||||
int port;
|
||||
String host;
|
||||
Thread net_thread = null;
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
Socket sock = null;
|
||||
ServerSocket server_sock = null;
|
||||
Socks5DatagramSocket udp_sock;
|
||||
|
||||
Object net_lock = new Object();
|
||||
int mode = COMMAND_MODE;
|
||||
|
||||
// Possible mode states.
|
||||
static final int LISTEN_MODE = 0;
|
||||
static final int CONNECT_MODE = 1;
|
||||
static final int UDP_MODE = 2;
|
||||
static final int COMMAND_MODE = 3;
|
||||
static final int ABORT_MODE = 4;
|
||||
|
||||
// Maximum datagram size
|
||||
static final int MAX_DATAGRAM_SIZE = 1024;
|
||||
|
||||
// Constructors
|
||||
// //////////////////////////////////
|
||||
public SocksEcho() {
|
||||
super("SocksEcho");
|
||||
guiInit();
|
||||
socks_dialog = new SocksDialog(this);
|
||||
SocksDialog.useThreads = false;
|
||||
|
||||
final URL icon_url = SocksEcho.class.getResource("SocksEcho.gif");
|
||||
if (icon_url != null) {
|
||||
try {
|
||||
final Object content = icon_url.getContent();
|
||||
if (content instanceof java.awt.image.ImageProducer) {
|
||||
setIconImage(createImage((java.awt.image.ImageProducer) content));
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
log.debug("Could not getContent() for {}", icon_url, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
addWindowListener(this);
|
||||
final Component component[] = getComponents();
|
||||
for (int i = 0; i < component.length; ++i) {
|
||||
if (component[i] instanceof Button) {
|
||||
((Button) component[i]).addActionListener(this);
|
||||
} else if (component[i] instanceof TextField) {
|
||||
((TextField) component[i]).addActionListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ActionListener interface
|
||||
// /////////////////////////
|
||||
public void actionPerformed(ActionEvent ae) {
|
||||
final Object source = ae.getSource();
|
||||
|
||||
if (source == proxy_button) {
|
||||
onProxy();
|
||||
} else if (source == quit_button) {
|
||||
onQuit();
|
||||
} else if ((source == connect_button) || (source == port_text)
|
||||
|| (source == host_text)) {
|
||||
onConnect();
|
||||
} else if (source == input_text) {
|
||||
onInput();
|
||||
} else if (source == accept_button) {
|
||||
onAccept();
|
||||
} else if (source == udp_button) {
|
||||
onUDP();
|
||||
} else if (source == clear_button) {
|
||||
onClear();
|
||||
}
|
||||
}
|
||||
|
||||
// Runnable interface
|
||||
// /////////////////////////////
|
||||
|
||||
public void run() {
|
||||
boolean finished_OK = true;
|
||||
try {
|
||||
switch (mode) {
|
||||
case UDP_MODE:
|
||||
startUDP();
|
||||
doUDPPipe();
|
||||
break;
|
||||
case LISTEN_MODE:
|
||||
doAccept();
|
||||
doPipe();
|
||||
break;
|
||||
case CONNECT_MODE:
|
||||
doConnect();
|
||||
doPipe();
|
||||
break;
|
||||
default:
|
||||
warn("Unexpected mode in run() method");
|
||||
}
|
||||
|
||||
} catch (final UnknownHostException uh_ex) {
|
||||
if (mode != ABORT_MODE) {
|
||||
finished_OK = false;
|
||||
status("Host " + host + " has no DNS entry.");
|
||||
uh_ex.printStackTrace();
|
||||
}
|
||||
} catch (final IOException io_ex) {
|
||||
if (mode != ABORT_MODE) {
|
||||
finished_OK = false;
|
||||
status("" + io_ex);
|
||||
io_ex.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
if (mode == ABORT_MODE) {
|
||||
status("Connection closed");
|
||||
} else if (finished_OK) {
|
||||
status("Connection closed by foreign host.");
|
||||
}
|
||||
|
||||
onDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GUI event handlers.
|
||||
// ////////////////////////
|
||||
|
||||
private void onConnect() {
|
||||
if (mode == CONNECT_MODE) {
|
||||
status("Diconnecting...");
|
||||
abort_connection();
|
||||
return;
|
||||
} else if (mode != COMMAND_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readHost()) {
|
||||
return;
|
||||
}
|
||||
if (!readPort()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy == null) {
|
||||
warn("Proxy is not set");
|
||||
onProxy();
|
||||
return;
|
||||
}
|
||||
|
||||
startNetThread(CONNECT_MODE);
|
||||
status("Connecting to " + host + ":" + port + " ...");
|
||||
|
||||
connect_button.setLabel("Disconnect");
|
||||
connect_button.invalidate();
|
||||
accept_button.setEnabled(false);
|
||||
udp_button.setEnabled(false);
|
||||
doLayout();
|
||||
input_text.requestFocus();
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
synchronized (net_lock) {
|
||||
mode = COMMAND_MODE;
|
||||
connect_button.setLabel("Connect");
|
||||
accept_button.setLabel("Accept");
|
||||
udp_button.setLabel("UDP");
|
||||
accept_button.setEnabled(true);
|
||||
connect_button.setEnabled(true);
|
||||
udp_button.setEnabled(true);
|
||||
server_sock = null;
|
||||
sock = null;
|
||||
out = null;
|
||||
in = null;
|
||||
net_thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onAccept() {
|
||||
if (mode == LISTEN_MODE) {
|
||||
abort_connection();
|
||||
return;
|
||||
} else if (mode != COMMAND_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readHost()) {
|
||||
return;
|
||||
}
|
||||
if (!readPort()) {
|
||||
port = 0;
|
||||
}
|
||||
|
||||
if (proxy == null) {
|
||||
warn("Proxy is not set");
|
||||
onProxy();
|
||||
return;
|
||||
}
|
||||
|
||||
startNetThread(LISTEN_MODE);
|
||||
|
||||
accept_button.setLabel("Abort");
|
||||
connect_button.setEnabled(false);
|
||||
udp_button.setEnabled(false);
|
||||
input_text.requestFocus();
|
||||
}
|
||||
|
||||
private void onUDP() {
|
||||
if (mode == UDP_MODE) {
|
||||
abort_connection();
|
||||
return;
|
||||
} else if (mode == ABORT_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy == null) {
|
||||
warn("Proxy is not set");
|
||||
onProxy();
|
||||
return;
|
||||
}
|
||||
|
||||
startNetThread(UDP_MODE);
|
||||
udp_button.setLabel("Abort");
|
||||
connect_button.setEnabled(false);
|
||||
accept_button.setEnabled(false);
|
||||
udp_button.invalidate();
|
||||
doLayout();
|
||||
input_text.requestFocus();
|
||||
}
|
||||
|
||||
private void onInput() {
|
||||
final String send_string = input_text.getText() + "\n";
|
||||
switch (mode) {
|
||||
case ABORT_MODE: // Fall through
|
||||
case COMMAND_MODE:
|
||||
return;
|
||||
case CONNECT_MODE:// Fall through
|
||||
case LISTEN_MODE:
|
||||
synchronized (net_lock) {
|
||||
if (out == null) {
|
||||
return;
|
||||
}
|
||||
send(send_string);
|
||||
}
|
||||
break;
|
||||
case UDP_MODE:
|
||||
if (!readHost()) {
|
||||
return;
|
||||
}
|
||||
if (!readPort()) {
|
||||
return;
|
||||
}
|
||||
sendUDP(send_string, host, port);
|
||||
break;
|
||||
default:
|
||||
print("Unknown mode in onInput():" + mode);
|
||||
|
||||
}
|
||||
input_text.setText("");
|
||||
print(send_string);
|
||||
}
|
||||
|
||||
private void onClear() {
|
||||
output_textarea.setText("");
|
||||
}
|
||||
|
||||
private void onProxy() {
|
||||
SocksProxyBase p;
|
||||
p = socks_dialog.getProxy(proxy);
|
||||
if (p != null) {
|
||||
proxy = p;
|
||||
}
|
||||
if ((proxy != null) && (proxy instanceof Socks5Proxy)) {
|
||||
((Socks5Proxy) proxy).resolveAddrLocally(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void onQuit() {
|
||||
dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
// Data retrieval functions
|
||||
// ////////////////////////
|
||||
|
||||
/**
|
||||
* Reads the port field, returns false if parsing fails.
|
||||
*/
|
||||
private boolean readPort() {
|
||||
try {
|
||||
port = Integer.parseInt(port_text.getText());
|
||||
} catch (final NumberFormatException nfe) {
|
||||
warn("Port invalid!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean readHost() {
|
||||
host = host_text.getText();
|
||||
host.trim();
|
||||
if (host.length() < 1) {
|
||||
warn("Host is not set");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Display functions
|
||||
// /////////////////
|
||||
|
||||
private void status(String s) {
|
||||
status_label.setText(s);
|
||||
}
|
||||
|
||||
private void println(String s) {
|
||||
output_textarea.append(s + "\n");
|
||||
}
|
||||
|
||||
private void print(String s) {
|
||||
output_textarea.append(s);
|
||||
}
|
||||
|
||||
private void warn(String s) {
|
||||
status(s);
|
||||
// System.err.println(s);
|
||||
}
|
||||
|
||||
// Network related functions
|
||||
// //////////////////////////
|
||||
|
||||
private void startNetThread(int m) {
|
||||
mode = m;
|
||||
net_thread = new Thread(this);
|
||||
net_thread.start();
|
||||
}
|
||||
|
||||
private void abort_connection() {
|
||||
synchronized (net_lock) {
|
||||
if (mode == COMMAND_MODE) {
|
||||
return;
|
||||
}
|
||||
mode = ABORT_MODE;
|
||||
if (net_thread != null) {
|
||||
try {
|
||||
if (sock != null) {
|
||||
sock.close();
|
||||
}
|
||||
if (server_sock != null) {
|
||||
server_sock.close();
|
||||
}
|
||||
if (udp_sock != null) {
|
||||
udp_sock.close();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("abort_connection(): could not close socket", ioe);
|
||||
}
|
||||
net_thread.interrupt();
|
||||
net_thread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doAccept() throws IOException {
|
||||
|
||||
println("Trying to accept from " + host);
|
||||
status("Trying to accept from " + host);
|
||||
println("Using proxy:" + proxy);
|
||||
server_sock = new SocksServerSocket(proxy, host, port);
|
||||
|
||||
// server_sock.setSoTimeout(30000);
|
||||
|
||||
println("Listenning on: " + server_sock.getInetAddress() + ":"
|
||||
+ server_sock.getLocalPort());
|
||||
sock = server_sock.accept();
|
||||
println("Accepted from:" + sock.getInetAddress() + ":" + sock.getPort());
|
||||
|
||||
status("Accepted from:" + sock.getInetAddress().getHostAddress() + ":"
|
||||
+ sock.getPort());
|
||||
|
||||
server_sock.close(); // Even though this doesn't do anything
|
||||
}
|
||||
|
||||
private void doConnect() throws IOException {
|
||||
println("Trying to connect to:" + host + ":" + port);
|
||||
println("Using proxy:" + proxy);
|
||||
sock = new SocksSocket(proxy, host, port);
|
||||
println("Connected to:" + sock.getInetAddress() + ":" + port);
|
||||
status("Connected to: " + sock.getInetAddress().getHostAddress() + ":"
|
||||
+ port);
|
||||
println("Via-Proxy:" + sock.getLocalAddress() + ":"
|
||||
+ sock.getLocalPort());
|
||||
|
||||
}
|
||||
|
||||
private void doPipe() throws IOException {
|
||||
out = sock.getOutputStream();
|
||||
in = sock.getInputStream();
|
||||
|
||||
final byte[] buf = new byte[1024];
|
||||
int bytes_read;
|
||||
while ((bytes_read = in.read(buf)) > 0) {
|
||||
print(new String(buf, 0, bytes_read));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void startUDP() throws IOException {
|
||||
udp_sock = new Socks5DatagramSocket(proxy, 0, null);
|
||||
println("UDP started on " + udp_sock.getLocalAddress() + ":"
|
||||
+ udp_sock.getLocalPort());
|
||||
status("UDP:" + udp_sock.getLocalAddress().getHostAddress() + ":"
|
||||
+ udp_sock.getLocalPort());
|
||||
}
|
||||
|
||||
private void doUDPPipe() throws IOException {
|
||||
final DatagramPacket dp = new DatagramPacket(
|
||||
new byte[MAX_DATAGRAM_SIZE], MAX_DATAGRAM_SIZE);
|
||||
while (true) {
|
||||
udp_sock.receive(dp);
|
||||
print("UDP\n" + "From:" + dp.getAddress() + ":" + dp.getPort()
|
||||
+ "\n" + "\n" +
|
||||
// Java 1.2
|
||||
// new
|
||||
// String(dp.getData(),dp.getOffset(),dp.getLength())+"\n"
|
||||
// Java 1.1
|
||||
new String(dp.getData(), 0, dp.getLength()) + "\n");
|
||||
dp.setLength(MAX_DATAGRAM_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUDP(String message, String host, int port) {
|
||||
if (!udp_sock.isProxyAlive(100)) {
|
||||
status("Proxy closed connection");
|
||||
abort_connection();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final byte[] data = message.getBytes();
|
||||
final DatagramPacket dp = new DatagramPacket(data, data.length,
|
||||
null, port);
|
||||
udp_sock.send(dp, host);
|
||||
} catch (final UnknownHostException uhe) {
|
||||
status("Host " + host + " has no DNS entry.");
|
||||
} catch (final IOException ioe) {
|
||||
status("IOException:" + ioe);
|
||||
abort_connection();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void send(String s) {
|
||||
try {
|
||||
out.write(s.getBytes());
|
||||
} catch (final IOException io_ex) {
|
||||
println("IOException:" + io_ex);
|
||||
abort_connection();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================================================================
|
||||
* Form: Table: +---+---------------+ | | | +---+---+---+---+---+ | | | | |
|
||||
* | +---+---+---+---+---+ | | +-------------------+ | |
|
||||
* +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ | |
|
||||
* +-------------------+
|
||||
*/
|
||||
|
||||
void guiInit() {
|
||||
// Some default names used
|
||||
Label label;
|
||||
Container container;
|
||||
|
||||
final GridBagConstraints c = new GridBagConstraints();
|
||||
|
||||
container = this;
|
||||
// container = new Panel();
|
||||
container.setLayout(new GridBagLayout());
|
||||
container.setBackground(SystemColor.menu);
|
||||
c.insets = new Insets(3, 3, 3, 3);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
label = new Label("Host:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
label = new Label("Port:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 5;
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
c.gridheight = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.insets = new Insets(0, 0, 0, 0);
|
||||
status_label = new Label("");
|
||||
container.add(status_label, c);
|
||||
c.insets = new Insets(3, 3, 3, 3);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
host_text = new TextField("");
|
||||
container.add(host_text, c);
|
||||
|
||||
c.weightx = 1.0;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.gridx = 1;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
port_text = new TextField("", 5);
|
||||
container.add(port_text, c);
|
||||
|
||||
c.weightx = 0.0;
|
||||
c.gridx = 0;
|
||||
c.gridy = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
input_text = new TextField("");
|
||||
container.add(input_text, c);
|
||||
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.gridx = 2;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
connect_button = new Button("Connect");
|
||||
container.add(connect_button, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
accept_button = new Button("Accept");
|
||||
container.add(accept_button, c);
|
||||
|
||||
c.gridx = 4;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
udp_button = new Button("UDP");
|
||||
container.add(udp_button, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 4;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
proxy_button = new Button("Proxy...");
|
||||
container.add(proxy_button, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 4;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
clear_button = new Button("Clear");
|
||||
container.add(clear_button, c);
|
||||
|
||||
c.gridx = 4;
|
||||
c.gridy = 4;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
quit_button = new Button("Quit");
|
||||
container.add(quit_button, c);
|
||||
|
||||
c.weightx = 1.0;
|
||||
c.weighty = 1.0;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
output_textarea = new TextArea("", 10, 50);
|
||||
output_textarea.setFont(new Font("Monospaced", Font.PLAIN, 11));
|
||||
output_textarea.setEditable(false);
|
||||
// output_textarea.setEnabled(false);
|
||||
container.add(output_textarea, c);
|
||||
|
||||
}// end guiInit
|
||||
|
||||
// WindowListener Interface
|
||||
// ///////////////////////////////
|
||||
public void windowActivated(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowDeactivated(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowOpened(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowClosing(java.awt.event.WindowEvent e) {
|
||||
if (e.getWindow() == this) {
|
||||
onQuit();
|
||||
} else {
|
||||
e.getWindow().dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void windowClosed(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowIconified(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowDeiconified(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
// Main
|
||||
// //////////////////////////////////
|
||||
public static void main(String[] args) {
|
||||
final SocksEcho socksecho = new SocksEcho();
|
||||
socksecho.pack();
|
||||
socksecho.setVisible(true);
|
||||
}
|
||||
}// end class
|
|
@ -0,0 +1,33 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
/**
|
||||
* The Authentication interface provides for performing method specific
|
||||
* authentication for SOCKS5 connections.
|
||||
*/
|
||||
public interface Authentication {
|
||||
/**
|
||||
* This method is called when SOCKS5 server have selected a particular
|
||||
* authentication method, for whch an implementaion have been registered.
|
||||
* <p>
|
||||
* <p>
|
||||
* This method should return an array {inputstream,outputstream
|
||||
* [,UDPEncapsulation]}. The reason for that is that SOCKS5 protocol allows
|
||||
* to have method specific encapsulation of data on the socket for purposes
|
||||
* of integrity or security. And this encapsulation should be performed by
|
||||
* those streams returned from the method. It is also possible to
|
||||
* encapsulate datagrams. If authentication method supports such
|
||||
* encapsulation an instance of the UDPEncapsulation interface should be
|
||||
* returned as third element of the array, otherwise either null should be
|
||||
* returned as third element, or array should contain only 2 elements.
|
||||
*
|
||||
* @param methodId Authentication method selected by the server.
|
||||
* @param proxySocket Socket used to conect to the proxy.
|
||||
* @return Two or three element array containing Input/Output streams which
|
||||
* should be used on this connection. Third argument is optional and
|
||||
* should contain an instance of UDPEncapsulation. It should be
|
||||
* provided if the authentication method used requires any
|
||||
* encapsulation to be done on the datagrams.
|
||||
*/
|
||||
Object[] doSocksAuthentication(int methodId, java.net.Socket proxySocket)
|
||||
throws java.io.IOException;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* SOCKS5 none authentication. Dummy class does almost nothing.
|
||||
*/
|
||||
public class AuthenticationNone implements Authentication {
|
||||
|
||||
public Object[] doSocksAuthentication(final int methodId,
|
||||
final java.net.Socket proxySocket) throws java.io.IOException {
|
||||
|
||||
if (methodId != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InputStream in = proxySocket.getInputStream();
|
||||
OutputStream out = proxySocket.getOutputStream();
|
||||
return new Object[]{in, out};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,489 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Class InetRange provides the means of defining the range of inetaddresses.
|
||||
* It's used by Proxy class to store and look up addresses of machines, that
|
||||
* should be contacted directly rather then through the proxy.
|
||||
* <p>
|
||||
* InetRange provides several methods to add either standalone addresses, or
|
||||
* ranges (e.g. 100.200.300.0:100.200.300.255, which covers all addresses on on
|
||||
* someones local network). It also provides methods for checking wether given
|
||||
* address is in this range. Any number of ranges and standalone addresses can
|
||||
* be added to the range.
|
||||
*/
|
||||
public class InetRange implements Cloneable {
|
||||
|
||||
Hashtable<String, Object[]> host_names;
|
||||
Vector<Object[]> all;
|
||||
Vector<String> end_names;
|
||||
|
||||
boolean useSeparateThread = true;
|
||||
|
||||
/**
|
||||
* Creates the empty range.
|
||||
*/
|
||||
public InetRange() {
|
||||
all = new Vector<Object[]>();
|
||||
host_names = new Hashtable<String, Object[]>();
|
||||
end_names = new Vector<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another host or range to this range. The String can be one of those:
|
||||
* <UL>
|
||||
* <li>Host name. eg.(Athena.myhost.com or 45.54.56.65)
|
||||
* <p>
|
||||
* <li>Range in the form .myhost.net.au <BR>
|
||||
* In which case anything that ends with .myhost.net.au will be considered
|
||||
* in the range.
|
||||
* <p>
|
||||
* <li>Range in the form ddd.ddd.ddd. <BR>
|
||||
* This will be treated as range ddd.ddd.ddd.0 to ddd.ddd.ddd.255. It is not
|
||||
* necessary to specify 3 first bytes you can use just one or two. For
|
||||
* example 130. will cover address between 130.0.0.0 and 13.255.255.255.
|
||||
* <p>
|
||||
* <li>Range in the form host_from[: \t\n\r\f]host_to. <br>
|
||||
* That is two hostnames or ips separated by either whitespace or colon.
|
||||
* </UL>
|
||||
*/
|
||||
public synchronized boolean add(final String s0) {
|
||||
if (s0 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String s = s0.trim();
|
||||
if (s.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object[] entry;
|
||||
|
||||
if (s.endsWith(".")) {
|
||||
// thing like: 111.222.33.
|
||||
// it is being treated as range 111.222.33.000 - 111.222.33.255
|
||||
|
||||
final int[] addr = ip2intarray(s);
|
||||
long from, to;
|
||||
from = to = 0;
|
||||
|
||||
if (addr == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (addr[i] >= 0) {
|
||||
from += (((long) addr[i]) << 8 * (3 - i));
|
||||
} else {
|
||||
to = from;
|
||||
while (i < 4) {
|
||||
to += 255l << 8 * (3 - i++);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry = new Object[]{s, null, new Long(from), new Long(to)};
|
||||
all.addElement(entry);
|
||||
|
||||
} else if (s.startsWith(".")) {
|
||||
// Thing like: .myhost.com
|
||||
|
||||
end_names.addElement(s);
|
||||
all.addElement(new Object[]{s, null, null, null});
|
||||
} else {
|
||||
final StringTokenizer tokens = new StringTokenizer(s, " \t\r\n\f:");
|
||||
if (tokens.countTokens() > 1) {
|
||||
entry = new Object[]{s, null, null, null};
|
||||
resolve(entry, tokens.nextToken(), tokens.nextToken());
|
||||
all.addElement(entry);
|
||||
} else {
|
||||
entry = new Object[]{s, null, null, null};
|
||||
all.addElement(entry);
|
||||
host_names.put(s, entry);
|
||||
resolve(entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another ip for this range.
|
||||
*
|
||||
* @param ip IP os the host which should be added to this range.
|
||||
*/
|
||||
public synchronized void add(final InetAddress ip) {
|
||||
long from, to;
|
||||
from = to = ip2long(ip);
|
||||
all.addElement(new Object[]{ip.getHostName(), ip, new Long(from),
|
||||
new Long(to)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another range of ips for this range.Any host with ip address greater
|
||||
* than or equal to the address of from and smaller than or equal to the
|
||||
* address of to will be included in the range.
|
||||
*
|
||||
* @param from IP from where range starts(including).
|
||||
* @param to IP where range ends(including).
|
||||
*/
|
||||
public synchronized void add(final InetAddress from, final InetAddress to) {
|
||||
all.addElement(new Object[]{
|
||||
from.getHostAddress() + ":" + to.getHostAddress(), null,
|
||||
new Long(ip2long(from)), new Long(ip2long(to))});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the givan host is in the range. Attempts to resolve host
|
||||
* name if required.
|
||||
*
|
||||
* @param host Host name to check.
|
||||
* @return true If host is in the range, false otherwise.
|
||||
* @see InetRange#contains(String, boolean)
|
||||
*/
|
||||
public synchronized boolean contains(final String host) {
|
||||
return contains(host, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the given host is in the range.
|
||||
* <p>
|
||||
* Algorithm: <BR>
|
||||
* <ol>
|
||||
* <li>Look up if the hostname is in the range (in the Hashtable).
|
||||
* <li>Check if it ends with one of the speciefied endings.
|
||||
* <li>Check if it is ip(eg.130.220.35.98). If it is check if it is in the
|
||||
* range.
|
||||
* <li>If attemptResolve is true, host is name, rather than ip, and all
|
||||
* previous attempts failed, try to resolve the hostname, and check wether
|
||||
* the ip associated with the host is in the range.It also repeats all
|
||||
* previos steps with the hostname obtained from InetAddress, but the name
|
||||
* is not allways the full name,it is quite likely to be the same. Well it
|
||||
* was on my machine.
|
||||
* </ol>
|
||||
*
|
||||
* @param host Host name to check.
|
||||
* @param attemptResolve Wether to lookup ip address which corresponds to the host,if
|
||||
* required.
|
||||
* @return true If host is in the range, false otherwise.
|
||||
*/
|
||||
public synchronized boolean contains(final String host0,
|
||||
final boolean attemptResolve) {
|
||||
if (all.size() == 0) {
|
||||
return false; // Empty range
|
||||
}
|
||||
|
||||
String host = host0.trim();
|
||||
if (host.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkHost(host)) {
|
||||
return true;
|
||||
}
|
||||
if (checkHostEnding(host)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final long l = host2long(host);
|
||||
if (l >= 0) {
|
||||
return contains(l);
|
||||
}
|
||||
|
||||
if (!attemptResolve) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final InetAddress ip = InetAddress.getByName(host);
|
||||
return contains(ip);
|
||||
} catch (final UnknownHostException uhe) {
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the given ip is in the range.
|
||||
*
|
||||
* @param ip Address of the host to check.
|
||||
* @return true If host is in the range, false otherwise.
|
||||
*/
|
||||
public synchronized boolean contains(final InetAddress ip) {
|
||||
if (checkHostEnding(ip.getHostName())) {
|
||||
return true;
|
||||
}
|
||||
if (checkHost(ip.getHostName())) {
|
||||
return true;
|
||||
}
|
||||
return contains(ip2long(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries in the range as strings. <BR>
|
||||
* These strings can be used to delete entries from the range with remove
|
||||
* function.
|
||||
*
|
||||
* @return Array of entries as strings.
|
||||
* @see InetRange#remove(String)
|
||||
*/
|
||||
public synchronized String[] getAll() {
|
||||
final int size = all.size();
|
||||
Object entry[];
|
||||
final String all_names[] = new String[size];
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
entry = all.elementAt(i);
|
||||
all_names[i] = (String) entry[0];
|
||||
}
|
||||
return all_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entry from this range.<BR>
|
||||
*
|
||||
* @param s Entry to remove.
|
||||
* @return true if successfull.
|
||||
*/
|
||||
public synchronized boolean remove(final String s) {
|
||||
final Enumeration<Object[]> enumx = all.elements();
|
||||
while (enumx.hasMoreElements()) {
|
||||
final Object[] entry = enumx.nextElement();
|
||||
if (s.equals(entry[0])) {
|
||||
all.removeElement(entry);
|
||||
end_names.removeElement(s);
|
||||
host_names.remove(s);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representaion of this Range.
|
||||
*/
|
||||
public String toString() {
|
||||
final String all[] = getAll();
|
||||
if (all.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String s = all[0];
|
||||
for (int i = 1; i < all.length; ++i) {
|
||||
s += "; " + all[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of this Object
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object clone() {
|
||||
final InetRange new_range = new InetRange();
|
||||
new_range.all = (Vector<Object[]>) all.clone();
|
||||
new_range.end_names = (Vector<String>) end_names.clone();
|
||||
new_range.host_names = (Hashtable<String, Object[]>) host_names.clone();
|
||||
return new_range;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ///////////////
|
||||
|
||||
/**
|
||||
* Same as previous but used internally, to avoid unnecessary convertion of
|
||||
* IPs, when checking subranges
|
||||
*/
|
||||
private synchronized boolean contains(final long ip) {
|
||||
final Enumeration<Object[]> enumx = all.elements();
|
||||
while (enumx.hasMoreElements()) {
|
||||
final Object[] obj = enumx.nextElement();
|
||||
final Long from = obj[2] == null ? null : (Long) obj[2];
|
||||
final Long to = obj[3] == null ? null : (Long) obj[3];
|
||||
if ((from != null) && (from.longValue() <= ip)
|
||||
&& (to.longValue() >= ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkHost(final String host) {
|
||||
return host_names.containsKey(host);
|
||||
}
|
||||
|
||||
private boolean checkHostEnding(final String host) {
|
||||
final Enumeration<String> enumx = end_names.elements();
|
||||
while (enumx.hasMoreElements()) {
|
||||
if (host.endsWith(enumx.nextElement())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void resolve(final Object[] entry) {
|
||||
// First check if it's in the form ddd.ddd.ddd.ddd.
|
||||
final long ip = host2long((String) entry[0]);
|
||||
if (ip >= 0) {
|
||||
entry[2] = entry[3] = new Long(ip);
|
||||
} else {
|
||||
final InetRangeResolver res = new InetRangeResolver(entry);
|
||||
res.resolve(useSeparateThread);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolve(final Object[] entry, final String from,
|
||||
final String to) {
|
||||
long f, t;
|
||||
if (((f = host2long(from)) >= 0) && ((t = host2long(to)) >= 0)) {
|
||||
entry[2] = new Long(f);
|
||||
entry[3] = new Long(t);
|
||||
} else {
|
||||
final InetRangeResolver res = new InetRangeResolver(entry, from, to);
|
||||
res.resolve(useSeparateThread);
|
||||
}
|
||||
}
|
||||
|
||||
// Class methods
|
||||
// /////////////
|
||||
|
||||
// Converts ipv4 to long value(unsigned int)
|
||||
// /////////////////////////////////////////
|
||||
static long ip2long(final InetAddress ip) {
|
||||
long l = 0;
|
||||
final byte[] addr = ip.getAddress();
|
||||
|
||||
if (addr.length == 4) { // IPV4
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
l += (((long) addr[i] & 0xFF) << 8 * (3 - i));
|
||||
}
|
||||
} else { // IPV6
|
||||
return 0; // Have no idea how to deal with those
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
long host2long(final String host) {
|
||||
long ip = 0;
|
||||
|
||||
// check if it's ddd.ddd.ddd.ddd
|
||||
if (!Character.isDigit(host.charAt(0))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final int[] addr = ip2intarray(host);
|
||||
if (addr == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < addr.length; ++i) {
|
||||
ip += ((long) (addr[i] >= 0 ? addr[i] : 0)) << 8 * (3 - i);
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
static int[] ip2intarray(final String host) {
|
||||
final int[] address = {-1, -1, -1, -1};
|
||||
int i = 0;
|
||||
final StringTokenizer tokens = new StringTokenizer(host, ".");
|
||||
if (tokens.countTokens() > 4) {
|
||||
return null;
|
||||
}
|
||||
while (tokens.hasMoreTokens()) {
|
||||
try {
|
||||
address[i++] = Integer.parseInt(tokens.nextToken()) & 0xFF;
|
||||
} catch (final NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* //* This was the test main function //**********************************
|
||||
*
|
||||
* public static void main(String args[])throws UnknownHostException{ int i;
|
||||
*
|
||||
* InetRange ir = new InetRange();
|
||||
*
|
||||
*
|
||||
* for(i=0;i<args.length;++i){ System.out.println("Adding:" + args[i]);
|
||||
* ir.add(args[i]); }
|
||||
*
|
||||
* String host; java.io.DataInputStream din = new
|
||||
* java.io.DataInputStream(System.in); try{ host = din.readLine();
|
||||
* while(host!=null){ if(ir.contains(host)){
|
||||
* System.out.println("Range contains ip:"+host); }else{
|
||||
* System.out.println(host+" is not in the range"); } host = din.readLine();
|
||||
* } }catch(java.io.IOException io_ex){ io_ex.printStackTrace(); } }
|
||||
* ******************
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
class InetRangeResolver implements Runnable {
|
||||
|
||||
Object[] entry;
|
||||
|
||||
String from;
|
||||
String to;
|
||||
|
||||
InetRangeResolver(final Object[] entry) {
|
||||
this.entry = entry;
|
||||
from = null;
|
||||
to = null;
|
||||
}
|
||||
|
||||
InetRangeResolver(final Object[] entry, final String from, final String to) {
|
||||
this.entry = entry;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public final void resolve() {
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
public final void resolve(final boolean inSeparateThread) {
|
||||
if (inSeparateThread) {
|
||||
final Thread t = new Thread(this);
|
||||
t.start();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (from == null) {
|
||||
final InetAddress ip = InetAddress.getByName((String) entry[0]);
|
||||
entry[1] = ip;
|
||||
final Long l = new Long(InetRange.ip2long(ip));
|
||||
entry[2] = l;
|
||||
entry[3] = l;
|
||||
} else {
|
||||
final InetAddress f = InetAddress.getByName(from);
|
||||
final InetAddress t = InetAddress.getByName(to);
|
||||
entry[2] = new Long(InetRange.ip2long(f));
|
||||
entry[3] = new Long(InetRange.ip2long(t));
|
||||
|
||||
}
|
||||
} catch (final UnknownHostException uhe) {
|
||||
// System.err.println("Resolve failed for "+from+','+to+','+entry[0]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Abstract class which describes SOCKS4/5 response/request.
|
||||
*/
|
||||
public abstract class ProxyMessage {
|
||||
|
||||
/**
|
||||
* Host as an IP address
|
||||
*/
|
||||
public InetAddress ip = null;
|
||||
|
||||
/**
|
||||
* SOCKS version, or version of the response for SOCKS4
|
||||
*/
|
||||
public int version;
|
||||
|
||||
/**
|
||||
* Port field of the request/response
|
||||
*/
|
||||
public int port;
|
||||
|
||||
/**
|
||||
* Request/response code as an int
|
||||
*/
|
||||
public int command;
|
||||
|
||||
/**
|
||||
* Host as string.
|
||||
*/
|
||||
public String host = null;
|
||||
|
||||
/**
|
||||
* User field for SOCKS4 request messages
|
||||
*/
|
||||
public String user = null;
|
||||
|
||||
ProxyMessage(int command, InetAddress ip, int port) {
|
||||
this.command = command;
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
ProxyMessage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response from given
|
||||
* stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0), or if any
|
||||
* error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public abstract void read(InputStream in) throws SocksException,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response or client
|
||||
* request from given stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @param clinetMode If true read server response, else read client request.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0) and reading
|
||||
* in client mode, or if any error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public abstract void read(InputStream in, boolean client_mode)
|
||||
throws SocksException, IOException;
|
||||
|
||||
/**
|
||||
* Writes the message to the stream.
|
||||
*
|
||||
* @param out Output stream to which message should be written.
|
||||
*/
|
||||
public abstract void write(OutputStream out) throws SocksException,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Get the Address field of this message as InetAddress object.
|
||||
*
|
||||
* @return Host address or null, if one can't be determined.
|
||||
*/
|
||||
public InetAddress getInetAddress() throws UnknownHostException {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representaion of this message.
|
||||
*
|
||||
* @return string representation of this message.
|
||||
*/
|
||||
public String toString() {
|
||||
return "Proxy Message:\n" + "Version:" + version + "\n" + "Command:"
|
||||
+ command + "\n" + "IP: " + ip + "\n" + "Port: " + port
|
||||
+ "\n" + "User: " + user + "\n";
|
||||
}
|
||||
|
||||
// Package methods
|
||||
// ////////////////
|
||||
|
||||
static final String bytes2IPV4(byte[] addr, int offset) {
|
||||
String hostName = "" + (addr[offset] & 0xFF);
|
||||
for (int i = offset + 1; i < offset + 4; i++) {
|
||||
hostName += "." + (addr[i] & 0xFF);
|
||||
}
|
||||
return hostName;
|
||||
}
|
||||
|
||||
static final String bytes2IPV6(byte[] addr, int offset) {
|
||||
// Have no idea how they look like!
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,636 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.server.ServerAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously. Implements
|
||||
* all SOCKS commands, including UDP relaying.
|
||||
* <p>
|
||||
* In order to use it you will need to implement ServerAuthenticator interface.
|
||||
* There is an implementation of this interface which does no authentication
|
||||
* ServerAuthenticatorNone, but it is very dangerous to use, as it will give
|
||||
* access to your local network to anybody in the world. One should never use
|
||||
* this authentication scheme unless one have pretty good reason to do so. There
|
||||
* is a couple of other authentication schemes in socks.server package.
|
||||
*
|
||||
* @see socks.server.ServerAuthenticator
|
||||
*/
|
||||
public class ProxyServer implements Runnable {
|
||||
|
||||
ServerAuthenticator auth;
|
||||
ProxyMessage msg = null;
|
||||
|
||||
Socket sock = null, remote_sock = null;
|
||||
ServerSocket ss = null;
|
||||
UDPRelayServer relayServer = null;
|
||||
InputStream in, remote_in;
|
||||
OutputStream out, remote_out;
|
||||
|
||||
int mode;
|
||||
static final int START_MODE = 0;
|
||||
static final int ACCEPT_MODE = 1;
|
||||
static final int PIPE_MODE = 2;
|
||||
static final int ABORT_MODE = 3;
|
||||
|
||||
static final int BUF_SIZE = 8192;
|
||||
|
||||
Thread pipe_thread1, pipe_thread2;
|
||||
long lastReadTime;
|
||||
|
||||
static int iddleTimeout = 180000; // 3 minutes
|
||||
static int acceptTimeout = 180000; // 3 minutes
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(ProxyServer.class);
|
||||
static SocksProxyBase proxy;
|
||||
|
||||
// Public Constructors
|
||||
// ///////////////////
|
||||
|
||||
/**
|
||||
* Creates a proxy server with given Authentication scheme.
|
||||
*
|
||||
* @param auth Authentication scheme to be used.
|
||||
*/
|
||||
public ProxyServer(final ServerAuthenticator auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
// Other constructors
|
||||
// //////////////////
|
||||
|
||||
ProxyServer(final ServerAuthenticator auth, final Socket s) {
|
||||
this.auth = auth;
|
||||
this.sock = s;
|
||||
this.mode = START_MODE;
|
||||
}
|
||||
|
||||
// Public methods
|
||||
// ///////////////
|
||||
|
||||
/**
|
||||
* Set proxy.
|
||||
* <p>
|
||||
* Allows Proxy chaining so that one Proxy server is connected to another
|
||||
* and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests can
|
||||
* be handled, UDP would not work, however CONNECT and BIND will be
|
||||
* translated.
|
||||
*
|
||||
* @param p Proxy which should be used to handle user requests.
|
||||
*/
|
||||
public static void setProxy(final SocksProxyBase p) {
|
||||
proxy = p;
|
||||
// FIXME: Side effect.
|
||||
UDPRelayServer.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy.
|
||||
*
|
||||
* @return Proxy wich is used to handle user requests.
|
||||
*/
|
||||
public static SocksProxyBase getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for connections, how long shoud server wait for data to
|
||||
* arrive before dropping the connection.<br>
|
||||
* Zero timeout implies infinity.<br>
|
||||
* Default timeout is 3 minutes.
|
||||
*/
|
||||
public static void setIddleTimeout(final int timeout) {
|
||||
iddleTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for BIND command, how long the server should wait for
|
||||
* the incoming connection.<br>
|
||||
* Zero timeout implies infinity.<br>
|
||||
* Default timeout is 3 minutes.
|
||||
*/
|
||||
public static void setAcceptTimeout(final int timeout) {
|
||||
acceptTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for UDPRelay server.<br>
|
||||
* Zero timeout implies infinity.<br>
|
||||
* Default timeout is 3 minutes.
|
||||
*/
|
||||
public static void setUDPTimeout(final int timeout) {
|
||||
UDPRelayServer.setTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the datagrams used in the UDPRelayServer.<br>
|
||||
* Default size is 64K, a bit more than maximum possible size of the
|
||||
* datagram.
|
||||
*/
|
||||
public static void setDatagramSize(final int size) {
|
||||
UDPRelayServer.setDatagramSize(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Proxy server at given port.<br>
|
||||
* This methods blocks.
|
||||
*/
|
||||
public void start(final int port) {
|
||||
start(port, 5, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a server with the specified port, listen backlog, and local IP
|
||||
* address to bind to. The localIP argument can be used on a multi-homed
|
||||
* host for a ServerSocket that will only accept connect requests to one of
|
||||
* its addresses. If localIP is null, it will default accepting connections
|
||||
* on any/all local addresses. The port must be between 0 and 65535,
|
||||
* inclusive. <br>
|
||||
* This methods blocks.
|
||||
*/
|
||||
public void start(final int port, final int backlog,
|
||||
final InetAddress localIP) {
|
||||
try {
|
||||
ss = new ServerSocket(port, backlog, localIP);
|
||||
final String address = ss.getInetAddress().getHostAddress();
|
||||
final int localPort = ss.getLocalPort();
|
||||
log.info("Starting SOCKS Proxy on: {}:{}", address, localPort);
|
||||
|
||||
while (true) {
|
||||
final Socket s = ss.accept();
|
||||
final String hostName = s.getInetAddress().getHostName();
|
||||
final int port2 = s.getPort();
|
||||
log.info("Accepted from:{}:{}", hostName, port2);
|
||||
|
||||
final ProxyServer ps = new ProxyServer(auth, s);
|
||||
(new Thread(ps)).start();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop server operation.It would be wise to interrupt thread running the
|
||||
* server afterwards.
|
||||
*/
|
||||
public void stop() {
|
||||
try {
|
||||
if (ss != null) {
|
||||
ss.close();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
// Runnable interface
|
||||
// //////////////////
|
||||
public void run() {
|
||||
switch (mode) {
|
||||
case START_MODE:
|
||||
try {
|
||||
startSession();
|
||||
} catch (final IOException ioe) {
|
||||
handleException(ioe);
|
||||
// ioe.printStackTrace();
|
||||
} finally {
|
||||
abort();
|
||||
if (auth != null) {
|
||||
auth.endSession();
|
||||
}
|
||||
log.info("Main thread(client->remote)stopped.");
|
||||
}
|
||||
break;
|
||||
case ACCEPT_MODE:
|
||||
try {
|
||||
doAccept();
|
||||
mode = PIPE_MODE;
|
||||
pipe_thread1.interrupt(); // Tell other thread that connection
|
||||
// have
|
||||
// been accepted.
|
||||
pipe(remote_in, out);
|
||||
} catch (final IOException ioe) {
|
||||
// log("Accept exception:"+ioe);
|
||||
handleException(ioe);
|
||||
} finally {
|
||||
abort();
|
||||
log.info("Accept thread(remote->client) stopped");
|
||||
}
|
||||
break;
|
||||
case PIPE_MODE:
|
||||
try {
|
||||
pipe(remote_in, out);
|
||||
} catch (final IOException ioe) {
|
||||
} finally {
|
||||
abort();
|
||||
log.info("Support thread(remote->client) stopped");
|
||||
}
|
||||
break;
|
||||
case ABORT_MODE:
|
||||
break;
|
||||
default:
|
||||
log.warn("Unexpected MODE " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ///////////////
|
||||
private void startSession() throws IOException {
|
||||
sock.setSoTimeout(iddleTimeout);
|
||||
|
||||
try {
|
||||
auth = auth.startSession(sock);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Auth throwed exception:", ioe);
|
||||
auth = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (auth == null) { // Authentication failed
|
||||
log.info("Authentication failed");
|
||||
return;
|
||||
}
|
||||
|
||||
in = auth.getInputStream();
|
||||
out = auth.getOutputStream();
|
||||
|
||||
msg = readMsg(in);
|
||||
handleRequest(msg);
|
||||
}
|
||||
|
||||
private void handleRequest(final ProxyMessage msg) throws IOException {
|
||||
if (!auth.checkRequest(msg)) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
|
||||
}
|
||||
|
||||
if (msg.ip == null) {
|
||||
if (msg instanceof Socks5Message) {
|
||||
msg.ip = InetAddress.getByName(msg.host);
|
||||
} else {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
|
||||
}
|
||||
}
|
||||
log(msg);
|
||||
|
||||
switch (msg.command) {
|
||||
case SocksProxyBase.SOCKS_CMD_CONNECT:
|
||||
onConnect(msg);
|
||||
break;
|
||||
case SocksProxyBase.SOCKS_CMD_BIND:
|
||||
onBind(msg);
|
||||
break;
|
||||
case SocksProxyBase.SOCKS_CMD_UDP_ASSOCIATE:
|
||||
onUDP(msg);
|
||||
break;
|
||||
default:
|
||||
throw new SocksException(SocksProxyBase.SOCKS_CMD_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleException(final IOException ioe) {
|
||||
// If we couldn't read the request, return;
|
||||
if (msg == null) {
|
||||
return;
|
||||
}
|
||||
// If have been aborted by other thread
|
||||
if (mode == ABORT_MODE) {
|
||||
return;
|
||||
}
|
||||
// If the request was successfully completed, but exception happened
|
||||
// later
|
||||
if (mode == PIPE_MODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
int error_code = SocksProxyBase.SOCKS_FAILURE;
|
||||
|
||||
if (ioe instanceof SocksException) {
|
||||
error_code = ((SocksException) ioe).errCode;
|
||||
} else if (ioe instanceof NoRouteToHostException) {
|
||||
error_code = SocksProxyBase.SOCKS_HOST_UNREACHABLE;
|
||||
} else if (ioe instanceof ConnectException) {
|
||||
error_code = SocksProxyBase.SOCKS_CONNECTION_REFUSED;
|
||||
} else if (ioe instanceof InterruptedIOException) {
|
||||
error_code = SocksProxyBase.SOCKS_TTL_EXPIRE;
|
||||
}
|
||||
|
||||
if ((error_code > SocksProxyBase.SOCKS_ADDR_NOT_SUPPORTED)
|
||||
|| (error_code < 0)) {
|
||||
error_code = SocksProxyBase.SOCKS_FAILURE;
|
||||
}
|
||||
|
||||
sendErrorMessage(error_code);
|
||||
}
|
||||
|
||||
private void onConnect(final ProxyMessage msg) throws IOException {
|
||||
Socket s;
|
||||
|
||||
if (proxy == null) {
|
||||
s = new Socket(msg.ip, msg.port);
|
||||
} else {
|
||||
s = new SocksSocket(proxy, msg.ip, msg.port);
|
||||
}
|
||||
|
||||
log.info("Connected to " + s.getInetAddress() + ":" + s.getPort());
|
||||
|
||||
ProxyMessage response = null;
|
||||
final InetAddress localAddress = s.getLocalAddress();
|
||||
final int localPort = s.getLocalPort();
|
||||
|
||||
if (msg instanceof Socks5Message) {
|
||||
final int cmd = SocksProxyBase.SOCKS_SUCCESS;
|
||||
response = new Socks5Message(cmd, localAddress, localPort);
|
||||
} else {
|
||||
final int cmd = Socks4Message.REPLY_OK;
|
||||
response = new Socks4Message(cmd, localAddress, localPort);
|
||||
|
||||
}
|
||||
response.write(out);
|
||||
startPipe(s);
|
||||
}
|
||||
|
||||
private void onBind(final ProxyMessage msg) throws IOException {
|
||||
ProxyMessage response = null;
|
||||
|
||||
if (proxy == null) {
|
||||
ss = new ServerSocket(0);
|
||||
} else {
|
||||
ss = new SocksServerSocket(proxy, msg.ip, msg.port);
|
||||
}
|
||||
|
||||
ss.setSoTimeout(acceptTimeout);
|
||||
|
||||
final InetAddress inetAddress = ss.getInetAddress();
|
||||
final int localPort = ss.getLocalPort();
|
||||
log.info("Trying accept on {}:{}", inetAddress, localPort);
|
||||
|
||||
if (msg.version == 5) {
|
||||
final int cmd = SocksProxyBase.SOCKS_SUCCESS;
|
||||
response = new Socks5Message(cmd, inetAddress, localPort);
|
||||
} else {
|
||||
final int cmd = Socks4Message.REPLY_OK;
|
||||
response = new Socks4Message(cmd, inetAddress, localPort);
|
||||
}
|
||||
response.write(out);
|
||||
|
||||
mode = ACCEPT_MODE;
|
||||
|
||||
pipe_thread1 = Thread.currentThread();
|
||||
pipe_thread2 = new Thread(this);
|
||||
pipe_thread2.start();
|
||||
|
||||
// Make timeout infinit.
|
||||
sock.setSoTimeout(0);
|
||||
int eof = 0;
|
||||
|
||||
try {
|
||||
while ((eof = in.read()) >= 0) {
|
||||
if (mode != ACCEPT_MODE) {
|
||||
if (mode != PIPE_MODE) {
|
||||
return;// Accept failed
|
||||
}
|
||||
|
||||
remote_out.write(eof);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (final EOFException e) {
|
||||
log.debug("Connection closed while we were trying to accept", e);
|
||||
return;
|
||||
} catch (final InterruptedIOException e) {
|
||||
log.debug("Interrupted by unsucessful accept thread", e);
|
||||
if (mode != PIPE_MODE) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
// System.out.println("Finnaly!");
|
||||
}
|
||||
|
||||
if (eof < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not restore timeout, instead timeout is set on the
|
||||
// remote socket. It does not make any difference.
|
||||
|
||||
pipe(in, remote_out);
|
||||
}
|
||||
|
||||
private void onUDP(final ProxyMessage msg) throws IOException {
|
||||
if (msg.ip.getHostAddress().equals("0.0.0.0")) {
|
||||
msg.ip = sock.getInetAddress();
|
||||
}
|
||||
log.info("Creating UDP relay server for {}:{}", msg.ip, msg.port);
|
||||
|
||||
relayServer = new UDPRelayServer(msg.ip, msg.port,
|
||||
Thread.currentThread(), sock, auth);
|
||||
|
||||
ProxyMessage response;
|
||||
|
||||
response = new Socks5Message(SocksProxyBase.SOCKS_SUCCESS,
|
||||
relayServer.relayIP, relayServer.relayPort);
|
||||
|
||||
response.write(out);
|
||||
|
||||
relayServer.start();
|
||||
|
||||
// Make timeout infinit.
|
||||
sock.setSoTimeout(0);
|
||||
try {
|
||||
while (in.read() >= 0) {
|
||||
/* do nothing */
|
||||
;
|
||||
// FIXME: Consider a slight delay here?
|
||||
}
|
||||
} catch (final EOFException eofe) {
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ////////////////
|
||||
|
||||
private void doAccept() throws IOException {
|
||||
Socket s = null;
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
while (true) {
|
||||
s = ss.accept();
|
||||
if (s.getInetAddress().equals(msg.ip)) {
|
||||
// got the connection from the right host
|
||||
// Close listenning socket.
|
||||
ss.close();
|
||||
break;
|
||||
} else if (ss instanceof SocksServerSocket) {
|
||||
// We can't accept more then one connection
|
||||
s.close();
|
||||
ss.close();
|
||||
throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
|
||||
} else {
|
||||
if (acceptTimeout != 0) { // If timeout is not infinit
|
||||
final long passed = System.currentTimeMillis() - startTime;
|
||||
final int newTimeout = acceptTimeout - (int) passed;
|
||||
|
||||
if (newTimeout <= 0) {
|
||||
throw new InterruptedIOException("newTimeout <= 0");
|
||||
}
|
||||
ss.setSoTimeout(newTimeout);
|
||||
}
|
||||
s.close(); // Drop all connections from other hosts
|
||||
}
|
||||
}
|
||||
|
||||
// Accepted connection
|
||||
remote_sock = s;
|
||||
remote_in = s.getInputStream();
|
||||
remote_out = s.getOutputStream();
|
||||
|
||||
// Set timeout
|
||||
remote_sock.setSoTimeout(iddleTimeout);
|
||||
|
||||
final InetAddress inetAddress = s.getInetAddress();
|
||||
final int port = s.getPort();
|
||||
log.info("Accepted from {}:{}", s.getInetAddress(), port);
|
||||
|
||||
ProxyMessage response;
|
||||
|
||||
if (msg.version == 5) {
|
||||
final int cmd = SocksProxyBase.SOCKS_SUCCESS;
|
||||
response = new Socks5Message(cmd, inetAddress, port);
|
||||
} else {
|
||||
final int cmd = Socks4Message.REPLY_OK;
|
||||
response = new Socks4Message(cmd, inetAddress, port);
|
||||
}
|
||||
response.write(out);
|
||||
}
|
||||
|
||||
private ProxyMessage readMsg(final InputStream in) throws IOException {
|
||||
PushbackInputStream push_in;
|
||||
if (in instanceof PushbackInputStream) {
|
||||
push_in = (PushbackInputStream) in;
|
||||
} else {
|
||||
push_in = new PushbackInputStream(in);
|
||||
}
|
||||
|
||||
final int version = push_in.read();
|
||||
push_in.unread(version);
|
||||
|
||||
ProxyMessage msg;
|
||||
|
||||
if (version == 5) {
|
||||
msg = new Socks5Message(push_in, false);
|
||||
} else if (version == 4) {
|
||||
msg = new Socks4Message(push_in, false);
|
||||
} else {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_FAILURE);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void startPipe(final Socket s) {
|
||||
mode = PIPE_MODE;
|
||||
remote_sock = s;
|
||||
try {
|
||||
remote_in = s.getInputStream();
|
||||
remote_out = s.getOutputStream();
|
||||
pipe_thread1 = Thread.currentThread();
|
||||
pipe_thread2 = new Thread(this);
|
||||
pipe_thread2.start();
|
||||
pipe(in, remote_out);
|
||||
} catch (final IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendErrorMessage(final int error_code) {
|
||||
ProxyMessage err_msg;
|
||||
if (msg instanceof Socks4Message) {
|
||||
err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
|
||||
} else {
|
||||
err_msg = new Socks5Message(error_code);
|
||||
}
|
||||
try {
|
||||
err_msg.write(out);
|
||||
} catch (final IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void abort() {
|
||||
if (mode == ABORT_MODE) {
|
||||
return;
|
||||
}
|
||||
mode = ABORT_MODE;
|
||||
try {
|
||||
log.info("Aborting operation");
|
||||
if (remote_sock != null) {
|
||||
remote_sock.close();
|
||||
}
|
||||
if (sock != null) {
|
||||
sock.close();
|
||||
}
|
||||
if (relayServer != null) {
|
||||
relayServer.stop();
|
||||
}
|
||||
if (ss != null) {
|
||||
ss.close();
|
||||
}
|
||||
if (pipe_thread1 != null) {
|
||||
pipe_thread1.interrupt();
|
||||
}
|
||||
if (pipe_thread2 != null) {
|
||||
pipe_thread2.interrupt();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
static final void log(final ProxyMessage msg) {
|
||||
log.debug("Request version: {}, Command: ", msg.version,
|
||||
command2String(msg.command));
|
||||
|
||||
final String user = msg.version == 4 ? ", User:" + msg.user : "";
|
||||
log.debug("IP:" + msg.ip + ", Port:" + msg.port + user);
|
||||
}
|
||||
|
||||
private void pipe(final InputStream in, final OutputStream out)
|
||||
throws IOException {
|
||||
lastReadTime = System.currentTimeMillis();
|
||||
final byte[] buf = new byte[BUF_SIZE];
|
||||
int len = 0;
|
||||
while (len >= 0) {
|
||||
try {
|
||||
if (len != 0) {
|
||||
out.write(buf, 0, len);
|
||||
out.flush();
|
||||
}
|
||||
len = in.read(buf);
|
||||
lastReadTime = System.currentTimeMillis();
|
||||
} catch (final InterruptedIOException iioe) {
|
||||
if (iddleTimeout == 0) {
|
||||
return;// Other thread interrupted us.
|
||||
}
|
||||
final long timeSinceRead = System.currentTimeMillis()
|
||||
- lastReadTime;
|
||||
|
||||
if (timeSinceRead >= iddleTimeout - 1000) {
|
||||
return;
|
||||
}
|
||||
len = 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final String command_names[] = {"CONNECT", "BIND", "UDP_ASSOCIATE"};
|
||||
|
||||
static final String command2String(int cmd) {
|
||||
if ((cmd > 0) && (cmd < 4)) {
|
||||
return command_names[cmd - 1];
|
||||
} else {
|
||||
return "Unknown Command " + cmd;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* SOCKS4 Reply/Request message.
|
||||
*/
|
||||
|
||||
class Socks4Message extends ProxyMessage {
|
||||
|
||||
private byte[] msgBytes;
|
||||
private int msgLength;
|
||||
|
||||
/**
|
||||
* Server failed reply, cmd command for failed request
|
||||
*/
|
||||
public Socks4Message(final int cmd) {
|
||||
super(cmd, null, 0);
|
||||
this.user = null;
|
||||
|
||||
msgLength = 2;
|
||||
msgBytes = new byte[2];
|
||||
|
||||
msgBytes[0] = (byte) 0;
|
||||
msgBytes[1] = (byte) command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server successfull reply
|
||||
*/
|
||||
public Socks4Message(final int cmd, final InetAddress ip, final int port) {
|
||||
this(0, cmd, ip, port, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client request
|
||||
*/
|
||||
public Socks4Message(final int cmd, final InetAddress ip, final int port,
|
||||
final String user) {
|
||||
this(SOCKS_VERSION, cmd, ip, port, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Most general constructor
|
||||
*/
|
||||
public Socks4Message(final int version, final int cmd,
|
||||
final InetAddress ip, final int port, final String user) {
|
||||
|
||||
super(cmd, ip, port);
|
||||
this.user = user;
|
||||
this.version = version;
|
||||
|
||||
msgLength = user == null ? 8 : 9 + user.length();
|
||||
msgBytes = new byte[msgLength];
|
||||
|
||||
msgBytes[0] = (byte) version;
|
||||
msgBytes[1] = (byte) command;
|
||||
msgBytes[2] = (byte) (port >> 8);
|
||||
msgBytes[3] = (byte) port;
|
||||
|
||||
byte[] addr;
|
||||
|
||||
if (ip != null) {
|
||||
addr = ip.getAddress();
|
||||
} else {
|
||||
addr = new byte[4];
|
||||
addr[0] = addr[1] = addr[2] = addr[3] = 0;
|
||||
}
|
||||
System.arraycopy(addr, 0, msgBytes, 4, 4);
|
||||
|
||||
if (user != null) {
|
||||
final byte[] buf = user.getBytes();
|
||||
System.arraycopy(buf, 0, msgBytes, 8, buf.length);
|
||||
msgBytes[msgBytes.length - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise from the stream If clientMode is true attempts to read a
|
||||
* server response otherwise reads a client request see read for more detail
|
||||
*/
|
||||
public Socks4Message(final InputStream in, final boolean clientMode)
|
||||
throws IOException {
|
||||
msgBytes = null;
|
||||
read(in, clientMode);
|
||||
}
|
||||
|
||||
public void read(final InputStream in) throws IOException {
|
||||
read(in, true);
|
||||
}
|
||||
|
||||
public void read(final InputStream in, final boolean clientMode)
|
||||
throws IOException {
|
||||
final DataInputStream d_in = new DataInputStream(in);
|
||||
version = d_in.readUnsignedByte();
|
||||
command = d_in.readUnsignedByte();
|
||||
if (clientMode && (command != REPLY_OK)) {
|
||||
String errMsg;
|
||||
// FIXME: Range should be replaced with cases.
|
||||
if ((command > REPLY_OK) && (command < REPLY_BAD_IDENTD)) {
|
||||
errMsg = replyMessage[command - REPLY_OK];
|
||||
} else {
|
||||
errMsg = "Unknown Reply Code";
|
||||
}
|
||||
throw new SocksException(command, errMsg);
|
||||
}
|
||||
port = d_in.readUnsignedShort();
|
||||
final byte[] addr = new byte[4];
|
||||
d_in.readFully(addr);
|
||||
ip = bytes2IP(addr);
|
||||
host = ip.getHostName();
|
||||
if (!clientMode) {
|
||||
int b = in.read();
|
||||
// FIXME: Hope there are no idiots with user name bigger than this
|
||||
final byte[] userBytes = new byte[256];
|
||||
int i = 0;
|
||||
for (i = 0; (i < userBytes.length) && (b > 0); ++i) {
|
||||
userBytes[i] = (byte) b;
|
||||
b = in.read();
|
||||
}
|
||||
user = new String(userBytes, 0, i);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final OutputStream out) throws IOException {
|
||||
if (msgBytes == null) {
|
||||
final Socks4Message msg;
|
||||
msg = new Socks4Message(version, command, ip, port, user);
|
||||
msgBytes = msg.msgBytes;
|
||||
msgLength = msg.msgLength;
|
||||
}
|
||||
out.write(msgBytes);
|
||||
}
|
||||
|
||||
// Class methods
|
||||
static InetAddress bytes2IP(final byte[] addr) {
|
||||
final String s = bytes2IPV4(addr, 0);
|
||||
try {
|
||||
return InetAddress.getByName(s);
|
||||
} catch (final UnknownHostException uh_ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
|
||||
static final String[] replyMessage = {"Request Granted",
|
||||
"Request Rejected or Failed",
|
||||
"Failed request, can't connect to Identd",
|
||||
"Failed request, bad user name"};
|
||||
|
||||
static final int SOCKS_VERSION = 4;
|
||||
|
||||
public final static int REQUEST_CONNECT = 1;
|
||||
public final static int REQUEST_BIND = 2;
|
||||
|
||||
public final static int REPLY_OK = 90;
|
||||
public final static int REPLY_REJECTED = 91;
|
||||
public final static int REPLY_NO_CONNECT = 92;
|
||||
public final static int REPLY_BAD_IDENTD = 93;
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Proxy which describes SOCKS4 proxy.
|
||||
*/
|
||||
|
||||
public class Socks4Proxy extends SocksProxyBase implements Cloneable {
|
||||
|
||||
// Data members
|
||||
String user;
|
||||
|
||||
// Public Constructors
|
||||
// ====================
|
||||
|
||||
/**
|
||||
* Creates the SOCKS4 proxy
|
||||
*
|
||||
* @param p Proxy to use to connect to this proxy, allows proxy chaining.
|
||||
* @param proxyHost Address of the proxy server.
|
||||
* @param proxyPort Port of the proxy server
|
||||
* @param user User name to use for identification purposes.
|
||||
* @throws UnknownHostException If proxyHost can't be resolved.
|
||||
*/
|
||||
public Socks4Proxy(SocksProxyBase p, String proxyHost, int proxyPort,
|
||||
String user) throws UnknownHostException {
|
||||
super(p, proxyHost, proxyPort);
|
||||
this.user = new String(user);
|
||||
version = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the SOCKS4 proxy
|
||||
*
|
||||
* @param proxyHost Address of the proxy server.
|
||||
* @param proxyPort Port of the proxy server
|
||||
* @param user User name to use for identification purposes.
|
||||
* @throws UnknownHostException If proxyHost can't be resolved.
|
||||
*/
|
||||
public Socks4Proxy(String proxyHost, int proxyPort, String user)
|
||||
throws UnknownHostException {
|
||||
this(null, proxyHost, proxyPort, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the SOCKS4 proxy
|
||||
*
|
||||
* @param p Proxy to use to connect to this proxy, allows proxy chaining.
|
||||
* @param proxyIP Address of the proxy server.
|
||||
* @param proxyPort Port of the proxy server
|
||||
* @param user User name to use for identification purposes.
|
||||
*/
|
||||
public Socks4Proxy(SocksProxyBase p, InetAddress proxyIP, int proxyPort,
|
||||
String user) {
|
||||
super(p, proxyIP, proxyPort);
|
||||
this.user = new String(user);
|
||||
version = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the SOCKS4 proxy
|
||||
*
|
||||
* @param proxyIP Address of the proxy server.
|
||||
* @param proxyPort Port of the proxy server
|
||||
* @param user User name to use for identification purposes.
|
||||
*/
|
||||
public Socks4Proxy(InetAddress proxyIP, int proxyPort, String user) {
|
||||
this(null, proxyIP, proxyPort, user);
|
||||
}
|
||||
|
||||
// Public instance methods
|
||||
// ========================
|
||||
|
||||
/**
|
||||
* Creates a clone of this proxy. Changes made to the clone should not
|
||||
* affect this object.
|
||||
*/
|
||||
public Object clone() {
|
||||
final Socks4Proxy newProxy = new Socks4Proxy(proxyIP, proxyPort, user);
|
||||
newProxy.directHosts = (InetRange) directHosts.clone();
|
||||
newProxy.chainProxy = chainProxy;
|
||||
return newProxy;
|
||||
}
|
||||
|
||||
// Public Static(Class) Methods
|
||||
// ==============================
|
||||
|
||||
// Protected Methods
|
||||
// =================
|
||||
|
||||
protected SocksProxyBase copy() {
|
||||
final Socks4Proxy copy = new Socks4Proxy(proxyIP, proxyPort, user);
|
||||
copy.directHosts = this.directHosts;
|
||||
copy.chainProxy = chainProxy;
|
||||
return copy;
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(int cmd, InetAddress ip, int port) {
|
||||
switch (cmd) {
|
||||
case SOCKS_CMD_CONNECT:
|
||||
cmd = Socks4Message.REQUEST_CONNECT;
|
||||
break;
|
||||
case SOCKS_CMD_BIND:
|
||||
cmd = Socks4Message.REQUEST_BIND;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return new Socks4Message(cmd, ip, port, user);
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(int cmd, String host, int port)
|
||||
throws UnknownHostException {
|
||||
|
||||
return formMessage(cmd, InetAddress.getByName(host), port);
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(InputStream in) throws SocksException,
|
||||
IOException {
|
||||
|
||||
return new Socks4Message(in, true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,478 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* Datagram socket to interract through the firewall.<BR>
|
||||
* Can be used same way as the normal DatagramSocket. One should be carefull
|
||||
* though with the datagram sizes used, as additional data is present in both
|
||||
* incomming and outgoing datagrams.
|
||||
* <p>
|
||||
* SOCKS5 protocol allows to send host address as either:
|
||||
* <ul>
|
||||
* <li>IPV4, normal 4 byte address. (10 bytes header size)
|
||||
* <li>IPV6, version 6 ip address (not supported by Java as for now). 22 bytes
|
||||
* header size.
|
||||
* <li>Host name,(7+length of the host name bytes header size).
|
||||
* </ul>
|
||||
* As with other Socks equivalents, direct addresses are handled transparently,
|
||||
* that is data will be send directly when required by the proxy settings.
|
||||
* <p>
|
||||
* <b>NOTE:</b><br>
|
||||
* Unlike other SOCKS Sockets, it <b>does not</b> support proxy chaining, and
|
||||
* will throw an exception if proxy has a chain proxy attached. The reason for
|
||||
* that is not my laziness, but rather the restrictions of the SOCKSv5 protocol.
|
||||
* Basicaly SOCKSv5 proxy server, needs to know from which host:port datagrams
|
||||
* will be send for association, and returns address to which datagrams should
|
||||
* be send by the client, but it does not inform client from which host:port it
|
||||
* is going to send datagrams, in fact there is even no guarantee they will be
|
||||
* send at all and from the same address each time.
|
||||
*/
|
||||
public class Socks5DatagramSocket extends DatagramSocket {
|
||||
|
||||
InetAddress relayIP;
|
||||
int relayPort;
|
||||
Socks5Proxy proxy;
|
||||
private boolean server_mode = false;
|
||||
UDPEncapsulation encapsulation;
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(Socks5DatagramSocket.class);
|
||||
|
||||
/**
|
||||
* Construct Datagram socket for communication over SOCKS5 proxy server.
|
||||
* This constructor uses default proxy, the one set with
|
||||
* Proxy.setDefaultProxy() method. If default proxy is not set or it is set
|
||||
* to version4 proxy, which does not support datagram forwarding, throws
|
||||
* SocksException.
|
||||
*/
|
||||
public Socks5DatagramSocket() throws SocksException, IOException {
|
||||
this(SocksProxyBase.defaultProxy, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct Datagram socket for communication over SOCKS5 proxy server. And
|
||||
* binds it to the specified local port. This constructor uses default
|
||||
* proxy, the one set with Proxy.setDefaultProxy() method. If default proxy
|
||||
* is not set or it is set to version4 proxy, which does not support
|
||||
* datagram forwarding, throws SocksException.
|
||||
*/
|
||||
public Socks5DatagramSocket(int port) throws SocksException, IOException {
|
||||
this(SocksProxyBase.defaultProxy, port, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct Datagram socket for communication over SOCKS5 proxy server. And
|
||||
* binds it to the specified local port and address. This constructor uses
|
||||
* default proxy, the one set with Proxy.setDefaultProxy() method. If
|
||||
* default proxy is not set or it is set to version4 proxy, which does not
|
||||
* support datagram forwarding, throws SocksException.
|
||||
*/
|
||||
public Socks5DatagramSocket(int port, InetAddress ip)
|
||||
throws SocksException, IOException {
|
||||
this(SocksProxyBase.defaultProxy, port, ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs datagram socket for communication over specified proxy. And
|
||||
* binds it to the given local address and port. Address of null and port of
|
||||
* 0, signify any availabale port/address. Might throw SocksException, if:
|
||||
* <ol>
|
||||
* <li>Given version of proxy does not support UDP_ASSOCIATE.
|
||||
* <li>Proxy can't be reached.
|
||||
* <li>Authorization fails.
|
||||
* <li>Proxy does not want to perform udp forwarding, for any reason.
|
||||
* </ol>
|
||||
* Might throw IOException if binding datagram socket to given address/port
|
||||
* fails. See java.net.DatagramSocket for more details.
|
||||
*/
|
||||
public Socks5DatagramSocket(SocksProxyBase p, int port, InetAddress ip)
|
||||
throws SocksException, IOException {
|
||||
|
||||
super(port, ip);
|
||||
|
||||
if (p == null) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
|
||||
}
|
||||
|
||||
if (!(p instanceof Socks5Proxy)) {
|
||||
final String s = "Datagram Socket needs Proxy version 5";
|
||||
throw new SocksException(-1, s);
|
||||
}
|
||||
|
||||
if (p.chainProxy != null) {
|
||||
final String s = "Datagram Sockets do not support proxy chaining.";
|
||||
throw new SocksException(SocksProxyBase.SOCKS_JUST_ERROR, s);
|
||||
}
|
||||
|
||||
proxy = (Socks5Proxy) p.copy();
|
||||
|
||||
final ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(),
|
||||
super.getLocalPort());
|
||||
|
||||
relayIP = msg.ip;
|
||||
if (relayIP.getHostAddress().equals("0.0.0.0")) {
|
||||
// FIXME: What happens here?
|
||||
relayIP = proxy.proxyIP;
|
||||
}
|
||||
relayPort = msg.port;
|
||||
|
||||
encapsulation = proxy.udp_encapsulation;
|
||||
|
||||
log.debug("Datagram Socket:{}:{}", getLocalAddress(), getLocalPort());
|
||||
log.debug("Socks5Datagram: {}:{}", relayIP, relayPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by UDPRelayServer.
|
||||
*/
|
||||
Socks5DatagramSocket(boolean server_mode, UDPEncapsulation encapsulation,
|
||||
InetAddress relayIP, int relayPort) throws IOException {
|
||||
super();
|
||||
this.server_mode = server_mode;
|
||||
this.relayIP = relayIP;
|
||||
this.relayPort = relayPort;
|
||||
this.encapsulation = encapsulation;
|
||||
this.proxy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Datagram either through the proxy or directly depending on
|
||||
* current proxy settings and destination address. <BR>
|
||||
* <p>
|
||||
* <B> NOTE: </B> DatagramPacket size should be at least 10 bytes less than
|
||||
* the systems limit.
|
||||
* <p>
|
||||
* <p>
|
||||
* See documentation on java.net.DatagramSocket for full details on how to
|
||||
* use this method.
|
||||
*
|
||||
* @param dp Datagram to send.
|
||||
* @throws IOException If error happens with I/O.
|
||||
*/
|
||||
public void send(DatagramPacket dp) throws IOException {
|
||||
// If the host should be accessed directly, send it as is.
|
||||
if (!server_mode && proxy.isDirect(dp.getAddress())) {
|
||||
super.send(dp);
|
||||
log.debug("Sending datagram packet directly:");
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] head = formHeader(dp.getAddress(), dp.getPort());
|
||||
byte[] buf = new byte[head.length + dp.getLength()];
|
||||
final byte[] data = dp.getData();
|
||||
|
||||
// Merge head and data
|
||||
System.arraycopy(head, 0, buf, 0, head.length);
|
||||
// System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength());
|
||||
System.arraycopy(data, 0, buf, head.length, dp.getLength());
|
||||
|
||||
if (encapsulation != null) {
|
||||
buf = encapsulation.udpEncapsulate(buf, true);
|
||||
}
|
||||
|
||||
super.send(new DatagramPacket(buf, buf.length, relayIP, relayPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows to send datagram packets with address type DOMAINNAME.
|
||||
* SOCKS5 allows to specify host as names rather than ip addresses.Using
|
||||
* this method one can send udp datagrams through the proxy, without having
|
||||
* to know the ip address of the destination host.
|
||||
* <p>
|
||||
* If proxy specified for that socket has an option resolveAddrLocally set
|
||||
* to true host will be resolved, and the datagram will be send with address
|
||||
* type IPV4, if resolve fails, UnknownHostException is thrown.
|
||||
*
|
||||
* @param dp Datagram to send, it should contain valid port and data
|
||||
* @param host Host name to which datagram should be send.
|
||||
* @throws IOException If error happens with I/O, or the host can't be resolved when
|
||||
* proxy settings say that hosts should be resolved locally.
|
||||
* @see Socks5Proxy#resolveAddrLocally(boolean)
|
||||
*/
|
||||
public void send(DatagramPacket dp, String host) throws IOException {
|
||||
if (proxy.isDirect(host)) {
|
||||
dp.setAddress(InetAddress.getByName(host));
|
||||
super.send(dp);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((proxy).resolveAddrLocally) {
|
||||
dp.setAddress(InetAddress.getByName(host));
|
||||
}
|
||||
|
||||
final byte[] head = formHeader(host, dp.getPort());
|
||||
byte[] buf = new byte[head.length + dp.getLength()];
|
||||
final byte[] data = dp.getData();
|
||||
// Merge head and data
|
||||
System.arraycopy(head, 0, buf, 0, head.length);
|
||||
// System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength());
|
||||
System.arraycopy(data, 0, buf, head.length, dp.getLength());
|
||||
|
||||
if (encapsulation != null) {
|
||||
buf = encapsulation.udpEncapsulate(buf, true);
|
||||
}
|
||||
|
||||
super.send(new DatagramPacket(buf, buf.length, relayIP, relayPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives udp packet. If packet have arrived from the proxy relay server,
|
||||
* it is processed and address and port of the packet are set to the address
|
||||
* and port of sending host.<BR>
|
||||
* If the packet arrived from anywhere else it is not changed.<br>
|
||||
* <B> NOTE: </B> DatagramPacket size should be at least 10 bytes bigger
|
||||
* than the largest packet you expect (this is for IPV4 addresses). For
|
||||
* hostnames and IPV6 it is even more.
|
||||
*
|
||||
* @param dp Datagram in which all relevent information will be copied.
|
||||
*/
|
||||
public void receive(DatagramPacket dp) throws IOException {
|
||||
super.receive(dp);
|
||||
|
||||
if (server_mode) {
|
||||
// Drop all datagrams not from relayIP/relayPort
|
||||
final int init_length = dp.getLength();
|
||||
final int initTimeout = getSoTimeout();
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
while (!relayIP.equals(dp.getAddress())
|
||||
|| (relayPort != dp.getPort())) {
|
||||
|
||||
// Restore datagram size
|
||||
dp.setLength(init_length);
|
||||
|
||||
// If there is a non-infinit timeout on this socket
|
||||
// Make sure that it happens no matter how often unexpected
|
||||
// packets arrive.
|
||||
if (initTimeout != 0) {
|
||||
final long passed = System.currentTimeMillis() - startTime;
|
||||
final int newTimeout = initTimeout - (int) passed;
|
||||
|
||||
if (newTimeout <= 0) {
|
||||
throw new InterruptedIOException(
|
||||
"In Socks5DatagramSocket->receive()");
|
||||
}
|
||||
setSoTimeout(newTimeout);
|
||||
}
|
||||
|
||||
super.receive(dp);
|
||||
}
|
||||
|
||||
// Restore timeout settings
|
||||
if (initTimeout != 0) {
|
||||
setSoTimeout(initTimeout);
|
||||
}
|
||||
|
||||
} else if (!relayIP.equals(dp.getAddress())
|
||||
|| (relayPort != dp.getPort())) {
|
||||
return; // Recieved direct packet
|
||||
// If the datagram is not from the relay server, return it it as is.
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
data = dp.getData();
|
||||
|
||||
if (encapsulation != null) {
|
||||
data = encapsulation.udpEncapsulate(data, false);
|
||||
}
|
||||
|
||||
// FIXME: What is this?
|
||||
final int offset = 0; // Java 1.1
|
||||
// int offset = dp.getOffset(); //Java 1.2
|
||||
|
||||
final ByteArrayInputStream bIn = new ByteArrayInputStream(data, offset,
|
||||
dp.getLength());
|
||||
|
||||
final ProxyMessage msg = new Socks5Message(bIn);
|
||||
dp.setPort(msg.port);
|
||||
dp.setAddress(msg.getInetAddress());
|
||||
|
||||
// what wasn't read by the Message is the data
|
||||
final int data_length = bIn.available();
|
||||
// Shift data to the left
|
||||
System.arraycopy(data, offset + dp.getLength() - data_length, data,
|
||||
offset, data_length);
|
||||
|
||||
dp.setLength(data_length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns port assigned by the proxy, to which datagrams are relayed. It is
|
||||
* not the same port to which other party should send datagrams.
|
||||
*
|
||||
* @return Port assigned by socks server to which datagrams are send for
|
||||
* association.
|
||||
*/
|
||||
public int getLocalPort() {
|
||||
if (server_mode) {
|
||||
return super.getLocalPort();
|
||||
}
|
||||
return relayPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address assigned by the proxy, to which datagrams are send for relay. It
|
||||
* is not necesseraly the same address, to which other party should send
|
||||
* datagrams.
|
||||
*
|
||||
* @return Address to which datagrams are send for association.
|
||||
*/
|
||||
public InetAddress getLocalAddress() {
|
||||
if (server_mode) {
|
||||
return super.getLocalAddress();
|
||||
}
|
||||
return relayIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes datagram socket, and proxy connection.
|
||||
*/
|
||||
public void close() {
|
||||
if (!server_mode) {
|
||||
proxy.endSession();
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks wether proxy still runs udp forwarding service for
|
||||
* this socket.
|
||||
* <p>
|
||||
* This methods checks wether the primary connection to proxy server is
|
||||
* active. If it is, chances are that proxy continues to forward datagrams
|
||||
* being send from this socket. If it was closed, most likely datagrams are
|
||||
* no longer being forwarded by the server.
|
||||
* <p>
|
||||
* Proxy might decide to stop forwarding datagrams, in which case it should
|
||||
* close primary connection. This method allows to check, wether this have
|
||||
* been done.
|
||||
* <p>
|
||||
* You can specify timeout for which we should be checking EOF condition on
|
||||
* the primary connection. Timeout is in milliseconds. Specifying 0 as
|
||||
* timeout implies infinity, in which case method will block, until
|
||||
* connection to proxy is closed or an error happens, and then return false.
|
||||
* <p>
|
||||
* One possible scenario is to call isProxyactive(0) in separate thread, and
|
||||
* once it returned notify other threads about this event.
|
||||
*
|
||||
* @param timeout For how long this method should block, before returning.
|
||||
* @return true if connection to proxy is active, false if eof or error
|
||||
* condition have been encountered on the connection.
|
||||
*/
|
||||
public boolean isProxyAlive(int timeout) {
|
||||
if (server_mode) {
|
||||
return false;
|
||||
}
|
||||
if (proxy != null) {
|
||||
try {
|
||||
proxy.proxySocket.setSoTimeout(timeout);
|
||||
|
||||
final int eof = proxy.in.read();
|
||||
if (eof < 0) {
|
||||
return false; // EOF encountered.
|
||||
} else {
|
||||
log.warn("This really should not happen");
|
||||
return true; // This really should not happen
|
||||
}
|
||||
|
||||
} catch (final InterruptedIOException iioe) {
|
||||
return true; // read timed out.
|
||||
} catch (final IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
// ////////////////
|
||||
|
||||
private byte[] formHeader(InetAddress ip, int port) {
|
||||
final Socks5Message request = new Socks5Message(0, ip, port);
|
||||
request.data[0] = 0;
|
||||
return request.data;
|
||||
}
|
||||
|
||||
private byte[] formHeader(String host, int port) {
|
||||
final Socks5Message request = new Socks5Message(0, host, port);
|
||||
request.data[0] = 0;
|
||||
return request.data;
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================================================================
|
||||
*
|
||||
* //Mainly Test functions //////////////////////
|
||||
*
|
||||
* private String bytes2String(byte[] b){ String s=""; char[] hex_digit = {
|
||||
* '0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F'};
|
||||
* for(int i=0;i<b.length;++i){ int i1 = (b[i] & 0xF0) >> 4; int i2 = b[i] &
|
||||
* 0xF; s+=hex_digit[i1]; s+=hex_digit[i2]; s+=" "; } return s; } private
|
||||
* static final void debug(String s){ if(DEBUG) System.out.print(s); }
|
||||
*
|
||||
* private static final boolean DEBUG = true;
|
||||
*
|
||||
*
|
||||
* public static void usage(){ System.err.print(
|
||||
* "Usage: java Socks.SocksDatagramSocket host port [socksHost socksPort]\n"
|
||||
* ); }
|
||||
*
|
||||
* static final int defaultProxyPort = 1080; //Default Port static final
|
||||
* String defaultProxyHost = "www-proxy"; //Default proxy
|
||||
*
|
||||
* public static void main(String args[]){ int port; String host; int
|
||||
* proxyPort; String proxyHost; InetAddress ip;
|
||||
*
|
||||
* if(args.length > 1 && args.length < 5){ try{
|
||||
*
|
||||
* host = args[0]; port = Integer.parseInt(args[1]);
|
||||
*
|
||||
* proxyPort =(args.length > 3)? Integer.parseInt(args[3]) :
|
||||
* defaultProxyPort;
|
||||
*
|
||||
* host = args[0]; ip = InetAddress.getByName(host);
|
||||
*
|
||||
* proxyHost =(args.length > 2)? args[2] : defaultProxyHost;
|
||||
*
|
||||
* Proxy.setDefaultProxy(proxyHost,proxyPort); Proxy p =
|
||||
* Proxy.getDefaultProxy(); p.addDirect("lux");
|
||||
*
|
||||
*
|
||||
* DatagramSocket ds = new Socks5DatagramSocket();
|
||||
*
|
||||
*
|
||||
* BufferedReader in = new BufferedReader( new
|
||||
* InputStreamReader(System.in)); String s;
|
||||
*
|
||||
* System.out.print("Enter line:"); s = in.readLine();
|
||||
*
|
||||
* while(s != null){ byte[] data = (s+"\r\n").getBytes(); DatagramPacket dp
|
||||
* = new DatagramPacket(data,0,data.length, ip,port);
|
||||
* System.out.println("Sending to: "+ip+":"+port); ds.send(dp); dp = new
|
||||
* DatagramPacket(new byte[1024],1024);
|
||||
*
|
||||
* System.out.println("Trying to recieve on port:"+ ds.getLocalPort());
|
||||
* ds.receive(dp); System.out.print("Recieved:\n"+
|
||||
* "From:"+dp.getAddress()+":"+dp.getPort()+ "\n\n"+ new
|
||||
* String(dp.getData(),dp.getOffset(),dp.getLength())+"\n" );
|
||||
* System.out.print("Enter line:"); s = in.readLine();
|
||||
*
|
||||
* } ds.close(); System.exit(1);
|
||||
*
|
||||
* }catch(SocksException s_ex){ System.err.println("SocksException:"+s_ex);
|
||||
* s_ex.printStackTrace(); System.exit(1); }catch(IOException io_ex){
|
||||
* io_ex.printStackTrace(); System.exit(1); }catch(NumberFormatException
|
||||
* num_ex){ usage(); num_ex.printStackTrace(); System.exit(1); }
|
||||
*
|
||||
* }else{ usage(); } }
|
||||
*/
|
||||
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* SOCKS5 request/response message.
|
||||
*/
|
||||
|
||||
class Socks5Message extends ProxyMessage {
|
||||
/**
|
||||
* Address type of given message
|
||||
*/
|
||||
public int addrType;
|
||||
|
||||
byte[] data;
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(Socks5Message.class);
|
||||
|
||||
/**
|
||||
* Server error response.
|
||||
*
|
||||
* @param cmd Error code.
|
||||
*/
|
||||
public Socks5Message(int cmd) {
|
||||
super(cmd, null, 0);
|
||||
data = new byte[3];
|
||||
data[0] = SOCKS_VERSION; // Version.
|
||||
data[1] = (byte) cmd; // Reply code for some kind of failure.
|
||||
data[2] = 0; // Reserved byte.
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct client request or server response.
|
||||
*
|
||||
* @param cmd - Request/Response code.
|
||||
* @param ip - IP field.
|
||||
* @paarm port - port field.
|
||||
*/
|
||||
public Socks5Message(int cmd, InetAddress ip, int port) {
|
||||
super(cmd, ip, port);
|
||||
|
||||
if (ip == null) {
|
||||
this.host = "0.0.0.0";
|
||||
} else {
|
||||
this.host = ip.getHostName();
|
||||
}
|
||||
|
||||
this.version = SOCKS_VERSION;
|
||||
|
||||
byte[] addr;
|
||||
|
||||
if (ip == null) {
|
||||
addr = new byte[4];
|
||||
addr[0] = addr[1] = addr[2] = addr[3] = 0;
|
||||
} else {
|
||||
addr = ip.getAddress();
|
||||
}
|
||||
|
||||
if (addr.length == 4) {
|
||||
addrType = SOCKS_ATYP_IPV4;
|
||||
} else {
|
||||
addrType = SOCKS_ATYP_IPV6;
|
||||
}
|
||||
|
||||
data = new byte[6 + addr.length];
|
||||
data[0] = (byte) SOCKS_VERSION; // Version
|
||||
data[1] = (byte) command; // Command
|
||||
data[2] = (byte) 0; // Reserved byte
|
||||
data[3] = (byte) addrType; // Address type
|
||||
|
||||
// Put Address
|
||||
System.arraycopy(addr, 0, data, 4, addr.length);
|
||||
// Put port
|
||||
data[data.length - 2] = (byte) (port >> 8);
|
||||
data[data.length - 1] = (byte) (port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct client request or server response.
|
||||
*
|
||||
* @param cmd - Request/Response code.
|
||||
* @param hostName - IP field as hostName, uses ADDR_TYPE of HOSTNAME.
|
||||
* @paarm port - port field.
|
||||
*/
|
||||
public Socks5Message(int cmd, String hostName, int port) {
|
||||
super(cmd, null, port);
|
||||
this.host = hostName;
|
||||
this.version = SOCKS_VERSION;
|
||||
|
||||
log.debug("Doing ATYP_DOMAINNAME");
|
||||
|
||||
addrType = SOCKS_ATYP_DOMAINNAME;
|
||||
final byte addr[] = hostName.getBytes();
|
||||
|
||||
data = new byte[7 + addr.length];
|
||||
data[0] = (byte) SOCKS_VERSION; // Version
|
||||
data[1] = (byte) command; // Command
|
||||
data[2] = (byte) 0; // Reserved byte
|
||||
data[3] = (byte) SOCKS_ATYP_DOMAINNAME; // Address type
|
||||
data[4] = (byte) addr.length; // Length of the address
|
||||
|
||||
// Put Address
|
||||
System.arraycopy(addr, 0, data, 5, addr.length);
|
||||
// Put port
|
||||
data[data.length - 2] = (byte) (port >> 8);
|
||||
data[data.length - 1] = (byte) (port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response from given
|
||||
* stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0), or if any
|
||||
* error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public Socks5Message(InputStream in) throws SocksException, IOException {
|
||||
this(in, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response or client
|
||||
* request from given stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @param clinetMode If true read server response, else read client request.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0) and reading
|
||||
* in client mode, or if any error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public Socks5Message(InputStream in, boolean clientMode)
|
||||
throws SocksException, IOException {
|
||||
|
||||
read(in, clientMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response from given
|
||||
* stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0), or if any
|
||||
* error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public void read(InputStream in) throws SocksException, IOException {
|
||||
read(in, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises Message from the stream. Reads server response or client
|
||||
* request from given stream.
|
||||
*
|
||||
* @param in Input stream to read response from.
|
||||
* @param clinetMode If true read server response, else read client request.
|
||||
* @throws SocksException If server response code is not SOCKS_SUCCESS(0) and reading
|
||||
* in client mode, or if any error with protocol occurs.
|
||||
* @throws IOException If any error happens with I/O.
|
||||
*/
|
||||
public void read(InputStream in, boolean clientMode) throws SocksException,
|
||||
IOException {
|
||||
|
||||
data = null;
|
||||
ip = null;
|
||||
|
||||
final DataInputStream di = new DataInputStream(in);
|
||||
|
||||
version = di.readUnsignedByte();
|
||||
command = di.readUnsignedByte();
|
||||
|
||||
if (clientMode && (command != 0)) {
|
||||
throw new SocksException(command);
|
||||
}
|
||||
|
||||
di.readUnsignedByte();
|
||||
addrType = di.readUnsignedByte();
|
||||
|
||||
byte addr[];
|
||||
|
||||
switch (addrType) {
|
||||
case SOCKS_ATYP_IPV4:
|
||||
addr = new byte[4];
|
||||
di.readFully(addr);
|
||||
host = bytes2IPV4(addr, 0);
|
||||
break;
|
||||
case SOCKS_ATYP_IPV6:
|
||||
addr = new byte[SOCKS_IPV6_LENGTH];// I believe it is 16 bytes,huge!
|
||||
di.readFully(addr);
|
||||
host = bytes2IPV6(addr, 0);
|
||||
break;
|
||||
case SOCKS_ATYP_DOMAINNAME:
|
||||
log.debug("Reading ATYP_DOMAINNAME");
|
||||
addr = new byte[di.readUnsignedByte()];// Next byte shows the length
|
||||
di.readFully(addr);
|
||||
host = new String(addr);
|
||||
break;
|
||||
default:
|
||||
throw (new SocksException(SocksProxyBase.SOCKS_JUST_ERROR));
|
||||
}
|
||||
|
||||
port = di.readUnsignedShort();
|
||||
|
||||
if ((addrType != SOCKS_ATYP_DOMAINNAME) && doResolveIP) {
|
||||
try {
|
||||
ip = InetAddress.getByName(host);
|
||||
} catch (final UnknownHostException uh_ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the message to the stream.
|
||||
*
|
||||
* @param out Output stream to which message should be written.
|
||||
*/
|
||||
public void write(OutputStream out) throws SocksException, IOException {
|
||||
if (data == null) {
|
||||
Socks5Message msg;
|
||||
|
||||
if (addrType == SOCKS_ATYP_DOMAINNAME) {
|
||||
msg = new Socks5Message(command, host, port);
|
||||
} else {
|
||||
if (ip == null) {
|
||||
try {
|
||||
ip = InetAddress.getByName(host);
|
||||
} catch (final UnknownHostException uh_ex) {
|
||||
throw new SocksException(
|
||||
SocksProxyBase.SOCKS_JUST_ERROR);
|
||||
}
|
||||
}
|
||||
msg = new Socks5Message(command, ip, port);
|
||||
}
|
||||
data = msg.data;
|
||||
}
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns IP field of the message as IP, if the message was created with
|
||||
* ATYP of HOSTNAME, it will attempt to resolve the hostname, which might
|
||||
* fail.
|
||||
*
|
||||
* @throws UnknownHostException if host can't be resolved.
|
||||
*/
|
||||
public InetAddress getInetAddress() throws UnknownHostException {
|
||||
if (ip != null) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
return (ip = InetAddress.getByName(host));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string representation of the message.
|
||||
*/
|
||||
public String toString() {
|
||||
// FIXME: Single line version, please.
|
||||
final String s = "Socks5Message:" + "\n" + "VN " + version + "\n"
|
||||
+ "CMD " + command + "\n" + "ATYP " + addrType + "\n"
|
||||
+ "ADDR " + host + "\n" + "PORT " + port + "\n";
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether to resolve hostIP returned from SOCKS server that is wether to
|
||||
* create InetAddress object from the hostName string
|
||||
*/
|
||||
static public boolean resolveIP() {
|
||||
return doResolveIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether to resolve hostIP returned from SOCKS server that is wether to
|
||||
* create InetAddress object from the hostName string
|
||||
*
|
||||
* @param doResolve Wether to resolve hostIP from SOCKS server.
|
||||
* @return Previous value.
|
||||
*/
|
||||
static public boolean resolveIP(boolean doResolve) {
|
||||
final boolean old = doResolveIP;
|
||||
doResolveIP = doResolve;
|
||||
return old;
|
||||
}
|
||||
|
||||
/*
|
||||
* private static final void debug(String s){ if(DEBUG) System.out.print(s);
|
||||
* } private static final boolean DEBUG = false;
|
||||
*/
|
||||
|
||||
// SOCKS5 constants
|
||||
public static final int SOCKS_VERSION = 5;
|
||||
|
||||
public static final int SOCKS_ATYP_IPV4 = 0x1; // Where is 2??
|
||||
public static final int SOCKS_ATYP_DOMAINNAME = 0x3; // !!!!rfc1928
|
||||
public static final int SOCKS_ATYP_IPV6 = 0x4;
|
||||
|
||||
public static final int SOCKS_IPV6_LENGTH = 16;
|
||||
|
||||
static boolean doResolveIP = true;
|
||||
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* SOCKS5 Proxy.
|
||||
*/
|
||||
|
||||
public class Socks5Proxy extends SocksProxyBase implements Cloneable {
|
||||
|
||||
// Data members
|
||||
private Hashtable<Integer, Authentication> authMethods = new Hashtable<Integer, Authentication>();
|
||||
private int selectedMethod;
|
||||
|
||||
boolean resolveAddrLocally = true;
|
||||
UDPEncapsulation udp_encapsulation = null;
|
||||
|
||||
// Public Constructors
|
||||
// ====================
|
||||
|
||||
/**
|
||||
* Creates SOCKS5 proxy.
|
||||
*
|
||||
* @param p Proxy to use to connect to this proxy, allows proxy chaining.
|
||||
* @param proxyHost Host on which a Proxy server runs.
|
||||
* @param proxyPort Port on which a Proxy server listens for connections.
|
||||
* @throws UnknownHostException If proxyHost can't be resolved.
|
||||
*/
|
||||
public Socks5Proxy(SocksProxyBase p, String proxyHost, int proxyPort)
|
||||
throws UnknownHostException {
|
||||
|
||||
super(p, proxyHost, proxyPort);
|
||||
version = 5;
|
||||
setAuthenticationMethod(0, new AuthenticationNone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SOCKS5 proxy.
|
||||
*
|
||||
* @param proxyHost Host on which a Proxy server runs.
|
||||
* @param proxyPort Port on which a Proxy server listens for connections.
|
||||
* @throws UnknownHostException If proxyHost can't be resolved.
|
||||
*/
|
||||
public Socks5Proxy(String proxyHost, int proxyPort)
|
||||
throws UnknownHostException {
|
||||
this(null, proxyHost, proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SOCKS5 proxy.
|
||||
*
|
||||
* @param p Proxy to use to connect to this proxy, allows proxy chaining.
|
||||
* @param proxyIP Host on which a Proxy server runs.
|
||||
* @param proxyPort Port on which a Proxy server listens for connections.
|
||||
*/
|
||||
public Socks5Proxy(SocksProxyBase p, InetAddress proxyIP, int proxyPort) {
|
||||
super(p, proxyIP, proxyPort);
|
||||
version = 5;
|
||||
setAuthenticationMethod(0, new AuthenticationNone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SOCKS5 proxy.
|
||||
*
|
||||
* @param proxyIP Host on which a Proxy server runs.
|
||||
* @param proxyPort Port on which a Proxy server listens for connections.
|
||||
*/
|
||||
public Socks5Proxy(InetAddress proxyIP, int proxyPort) {
|
||||
this(null, proxyIP, proxyPort);
|
||||
}
|
||||
|
||||
// Public instance methods
|
||||
// ========================
|
||||
|
||||
/**
|
||||
* Wether to resolve address locally or to let proxy do so.
|
||||
* <p>
|
||||
* SOCKS5 protocol allows to send host names rather then IPs in the
|
||||
* requests, this option controls wether the hostnames should be send to the
|
||||
* proxy server as names, or should they be resolved locally.
|
||||
*
|
||||
* @param doResolve Wether to perform resolution locally.
|
||||
* @return Previous settings.
|
||||
*/
|
||||
public boolean resolveAddrLocally(boolean doResolve) {
|
||||
final boolean old = resolveAddrLocally;
|
||||
resolveAddrLocally = doResolve;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current setting on how the addresses should be handled.
|
||||
*
|
||||
* @return Current setting for address resolution.
|
||||
* @see Socks5Proxy#resolveAddrLocally(boolean doResolve)
|
||||
*/
|
||||
public boolean resolveAddrLocally() {
|
||||
return resolveAddrLocally;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another authentication method.
|
||||
*
|
||||
* @param methodId Authentication method id, see rfc1928
|
||||
* @param method Implementation of Authentication
|
||||
* @see Authentication
|
||||
*/
|
||||
public boolean setAuthenticationMethod(int methodId, Authentication method) {
|
||||
if ((methodId < 0) || (methodId > 255)) {
|
||||
return false;
|
||||
}
|
||||
if (method == null) {
|
||||
// Want to remove a particular method
|
||||
return (authMethods.remove(new Integer(methodId)) != null);
|
||||
} else {// Add the method, or rewrite old one
|
||||
authMethods.put(new Integer(methodId), method);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication method, which corresponds to given method id
|
||||
*
|
||||
* @param methodId Authentication method id.
|
||||
* @return Implementation for given method or null, if one was not set.
|
||||
*/
|
||||
public Authentication getAuthenticationMethod(int methodId) {
|
||||
final Object method = authMethods.get(new Integer(methodId));
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
return (Authentication) method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of this Proxy. clone() returns an
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object clone() {
|
||||
final Socks5Proxy newProxy = new Socks5Proxy(proxyIP, proxyPort);
|
||||
|
||||
final Object o = this.authMethods.clone();
|
||||
newProxy.authMethods = (Hashtable<Integer, Authentication>) o;
|
||||
|
||||
newProxy.directHosts = (InetRange) directHosts.clone();
|
||||
newProxy.resolveAddrLocally = resolveAddrLocally;
|
||||
newProxy.chainProxy = chainProxy;
|
||||
return newProxy;
|
||||
}
|
||||
|
||||
// Public Static(Class) Methods
|
||||
// ==============================
|
||||
|
||||
// Protected Methods
|
||||
// =================
|
||||
|
||||
protected SocksProxyBase copy() {
|
||||
final Socks5Proxy copy = new Socks5Proxy(proxyIP, proxyPort);
|
||||
|
||||
copy.authMethods = this.authMethods; // same Hash, no copy
|
||||
copy.directHosts = this.directHosts;
|
||||
copy.chainProxy = this.chainProxy;
|
||||
copy.resolveAddrLocally = this.resolveAddrLocally;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
protected void startSession() throws SocksException {
|
||||
super.startSession();
|
||||
Authentication auth;
|
||||
final Socket ps = proxySocket; // The name is too long
|
||||
|
||||
try {
|
||||
|
||||
final byte nMethods = (byte) authMethods.size(); // Number of
|
||||
// methods
|
||||
|
||||
final byte[] buf = new byte[2 + nMethods]; // 2 is for VER,NMETHODS
|
||||
buf[0] = (byte) version;
|
||||
buf[1] = nMethods; // Number of methods
|
||||
int i = 2;
|
||||
|
||||
final Enumeration<Integer> ids = authMethods.keys();
|
||||
while (ids.hasMoreElements()) {
|
||||
buf[i++] = (byte) ids.nextElement().intValue();
|
||||
}
|
||||
|
||||
out.write(buf);
|
||||
out.flush();
|
||||
|
||||
final int versionNumber = in.read();
|
||||
selectedMethod = in.read();
|
||||
|
||||
if ((versionNumber < 0) || (selectedMethod < 0)) {
|
||||
// EOF condition was reached
|
||||
endSession();
|
||||
final String s = "Connection to proxy lost.";
|
||||
throw new SocksException(SOCKS_PROXY_IO_ERROR, s);
|
||||
}
|
||||
|
||||
if (versionNumber < version) {
|
||||
// What should we do??
|
||||
}
|
||||
|
||||
if (selectedMethod == 0xFF) { // No method selected
|
||||
ps.close();
|
||||
throw (new SocksException(SOCKS_AUTH_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
auth = getAuthenticationMethod(selectedMethod);
|
||||
if (auth == null) {
|
||||
// This shouldn't happen, unless method was removed by other
|
||||
// thread, or the server stuffed up
|
||||
final String s = "Specified Authentication not found!";
|
||||
throw new SocksException(SOCKS_JUST_ERROR, s);
|
||||
}
|
||||
|
||||
final Object[] in_out;
|
||||
in_out = auth.doSocksAuthentication(selectedMethod, ps);
|
||||
|
||||
if (in_out == null) {
|
||||
// Authentication failed by some reason
|
||||
throw (new SocksException(SOCKS_AUTH_FAILURE));
|
||||
}
|
||||
|
||||
/*
|
||||
* Most authentication methods are expected to return simply the
|
||||
* input/output streams associated with the socket. However if the
|
||||
* auth. method requires some kind of encryption/decryption being
|
||||
* done on the connection it should provide classes to handle I/O.
|
||||
*/
|
||||
|
||||
in = (InputStream) in_out[0];
|
||||
out = (OutputStream) in_out[1];
|
||||
if (in_out.length > 2) {
|
||||
udp_encapsulation = (UDPEncapsulation) in_out[2];
|
||||
}
|
||||
|
||||
} catch (final SocksException s_ex) {
|
||||
throw s_ex;
|
||||
} catch (final UnknownHostException uh_ex) {
|
||||
throw new SocksException(SOCKS_PROXY_NO_CONNECT, uh_ex);
|
||||
} catch (final SocketException so_ex) {
|
||||
throw new SocksException(SOCKS_PROXY_NO_CONNECT, so_ex);
|
||||
} catch (final IOException io_ex) {
|
||||
throw new SocksException(SOCKS_PROXY_IO_ERROR, io_ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(int cmd, InetAddress ip, int port) {
|
||||
return new Socks5Message(cmd, ip, port);
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(int cmd, String host, int port)
|
||||
throws UnknownHostException {
|
||||
if (resolveAddrLocally) {
|
||||
return formMessage(cmd, InetAddress.getByName(host), port);
|
||||
} else {
|
||||
return new Socks5Message(cmd, host, port);
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage formMessage(InputStream in) throws SocksException,
|
||||
IOException {
|
||||
return new Socks5Message(in);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,681 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* Socks configuration dialog.<br>
|
||||
* Class which provides GUI means of getting Proxy configuration from the user.
|
||||
*/
|
||||
// FIXME: Only used by SocksEcho class
|
||||
|
||||
public class SocksDialog extends Dialog implements WindowListener,
|
||||
ItemListener, ActionListener, Runnable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
// GUI components
|
||||
TextField host_text, port_text, user_text, password_text, direct_text;
|
||||
Button add_button, remove_button, cancel_button, ok_button, dismiss_button;
|
||||
List direct_list;
|
||||
Checkbox socks4radio, socks5radio, none_check, up_check, gssapi_check;
|
||||
|
||||
Dialog warning_dialog;
|
||||
Label warning_label;
|
||||
|
||||
String host, user, password;
|
||||
int port;
|
||||
Thread net_thread = null;
|
||||
|
||||
// CheckboxGroups
|
||||
CheckboxGroup socks_group = new CheckboxGroup();
|
||||
|
||||
SocksProxyBase proxy;
|
||||
InetRange ir;
|
||||
|
||||
final static int COMMAND_MODE = 0;
|
||||
final static int OK_MODE = 1;
|
||||
|
||||
int mode;
|
||||
|
||||
/**
|
||||
* Wether to resolve addresses in separate thread.
|
||||
* <p>
|
||||
* Default value is true, however on some JVMs, namely one from the
|
||||
* Microsoft, it doesn't want to work properly, separate thread can't close
|
||||
* the dialog opened in GUI thread, and everuthing else is then crashes.
|
||||
* <p>
|
||||
* When setting this variable to false, SocksDialog will block while trying
|
||||
* to look up proxy host, and if this takes considerable amount of time it
|
||||
* might be annoying to user.
|
||||
*/
|
||||
public static boolean useThreads = true;
|
||||
|
||||
// Constructors
|
||||
// //////////////////////////////////
|
||||
|
||||
/**
|
||||
* Creates SOCKS configuration dialog.<br>
|
||||
* Uses default initialisation:<br>
|
||||
* Proxy host: socks-proxy <br>
|
||||
* Proxy port: 1080 <br>
|
||||
* Version: 5<br>
|
||||
*/
|
||||
public SocksDialog(Frame parent) {
|
||||
this(parent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SOCKS configuration dialog and initialises it to given proxy.
|
||||
*/
|
||||
public SocksDialog(Frame parent, SocksProxyBase init_proxy) {
|
||||
super(parent, "Proxy Configuration", true);
|
||||
warning_dialog = new Dialog(parent, "Warning", true);
|
||||
|
||||
guiInit();
|
||||
setResizable(false);
|
||||
addWindowListener(this);
|
||||
final Component[] comps = getComponents();
|
||||
for (int i = 0; i < comps.length; ++i) {
|
||||
if (comps[i] instanceof Button) {
|
||||
((Button) comps[i]).addActionListener(this);
|
||||
} else if (comps[i] instanceof TextField) {
|
||||
((TextField) comps[i]).addActionListener(this);
|
||||
} else if (comps[i] instanceof Checkbox) {
|
||||
((Checkbox) comps[i]).addItemListener(this);
|
||||
}
|
||||
}
|
||||
proxy = init_proxy;
|
||||
if (proxy != null) {
|
||||
doInit(proxy);
|
||||
} else {
|
||||
ir = new InetRange();
|
||||
}
|
||||
|
||||
dismiss_button.addActionListener(this);
|
||||
warning_dialog.addWindowListener(this);
|
||||
}
|
||||
|
||||
// Public Methods
|
||||
// //////////////
|
||||
|
||||
/**
|
||||
* Displays SOCKS configuartion dialog.
|
||||
* <p>
|
||||
* Returns initialised proxy object, or null if user cancels dialog by
|
||||
* either pressing Cancel or closing the dialog window.
|
||||
*/
|
||||
public SocksProxyBase getProxy() {
|
||||
mode = COMMAND_MODE;
|
||||
pack();
|
||||
setVisible(true);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises dialog to given proxy and displays SOCKS configuartion
|
||||
* dialog.
|
||||
* <p>
|
||||
* Returns initialised proxy object, or null if user cancels dialog by
|
||||
* either pressing Cancel or closing the dialog window.
|
||||
*/
|
||||
public SocksProxyBase getProxy(SocksProxyBase p) {
|
||||
if (p != null) {
|
||||
doInit(p);
|
||||
}
|
||||
mode = COMMAND_MODE;
|
||||
pack();
|
||||
setVisible(true);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
// WindowListener Interface
|
||||
// ///////////////////////////////
|
||||
public void windowActivated(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowDeactivated(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowOpened(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowClosing(java.awt.event.WindowEvent e) {
|
||||
final Window source = e.getWindow();
|
||||
if (source == this) {
|
||||
onCancel();
|
||||
} else if (source == warning_dialog) {
|
||||
onDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public void windowClosed(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowIconified(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
public void windowDeiconified(java.awt.event.WindowEvent e) {
|
||||
}
|
||||
|
||||
// ActionListener interface
|
||||
// /////////////////////////
|
||||
public void actionPerformed(ActionEvent ae) {
|
||||
|
||||
final Object source = ae.getSource();
|
||||
|
||||
if (source == cancel_button) {
|
||||
onCancel();
|
||||
} else if ((source == add_button) || (source == direct_text)) {
|
||||
onAdd();
|
||||
} else if (source == remove_button) {
|
||||
onRemove();
|
||||
} else if (source == dismiss_button) {
|
||||
onDismiss();
|
||||
} else if ((source == ok_button) || (source instanceof TextField)) {
|
||||
onOK();
|
||||
}
|
||||
}
|
||||
|
||||
// ItemListener interface
|
||||
// //////////////////////
|
||||
public void itemStateChanged(ItemEvent ie) {
|
||||
final Object source = ie.getSource();
|
||||
// System.out.println("ItemEvent:"+source);
|
||||
if ((source == socks5radio) || (source == socks4radio)) {
|
||||
onSocksChange();
|
||||
} else if (source == up_check) {
|
||||
onUPChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Runnable interface
|
||||
// //////////////////
|
||||
|
||||
/**
|
||||
* Resolves proxy address in other thread, to avoid annoying blocking in GUI
|
||||
* thread.
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
if (!initProxy()) {
|
||||
// Check if we have been aborted
|
||||
if (mode != OK_MODE) {
|
||||
return;
|
||||
}
|
||||
if (net_thread != Thread.currentThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mode = COMMAND_MODE;
|
||||
warning_label.setText("Look up failed.");
|
||||
warning_label.invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("Done!");
|
||||
while (!warning_dialog.isShowing()) {
|
||||
; /* do nothing */
|
||||
}
|
||||
;
|
||||
|
||||
warning_dialog.dispose();
|
||||
// dispose(); //End Dialog
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
// /////////////////
|
||||
private void onOK() {
|
||||
host = host_text.getText().trim();
|
||||
user = user_text.getText();
|
||||
password = password_text.getText();
|
||||
|
||||
if (host.length() == 0) {
|
||||
warn("Proxy host is not set!");
|
||||
return;
|
||||
}
|
||||
if (socks_group.getSelectedCheckbox() == socks4radio) {
|
||||
if (user.length() == 0) {
|
||||
warn("User name is not set");
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (up_check.getState()) {
|
||||
if (user.length() == 0) {
|
||||
warn("User name is not set.");
|
||||
return;
|
||||
}
|
||||
if (password.length() == 0) {
|
||||
warn("Password is not set.");
|
||||
return;
|
||||
}
|
||||
} else if (!none_check.getState()) {
|
||||
warn("Please select at least one Authentication Method.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
port = Integer.parseInt(port_text.getText());
|
||||
} catch (final NumberFormatException nfe) {
|
||||
warn("Proxy port is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
mode = OK_MODE;
|
||||
|
||||
if (useThreads) {
|
||||
net_thread = new Thread(this);
|
||||
net_thread.start();
|
||||
|
||||
info("Looking up host: " + host);
|
||||
// System.out.println("Info returned.");
|
||||
} else if (!initProxy()) {
|
||||
warn("Proxy host is invalid.");
|
||||
mode = COMMAND_MODE;
|
||||
}
|
||||
|
||||
if (mode == OK_MODE) {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
// System.out.println("Cancel");
|
||||
proxy = null;
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void onAdd() {
|
||||
final String s = direct_text.getText();
|
||||
s.trim();
|
||||
if (s.length() == 0) {
|
||||
return;
|
||||
}
|
||||
// Check for Duplicate
|
||||
final String[] direct_hosts = direct_list.getItems();
|
||||
for (int i = 0; i < direct_hosts.length; ++i) {
|
||||
if (direct_hosts[i].equals(s)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
direct_list.add(s);
|
||||
ir.add(s);
|
||||
}
|
||||
|
||||
private void onRemove() {
|
||||
final int index = direct_list.getSelectedIndex();
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
ir.remove(direct_list.getItem(index));
|
||||
direct_list.remove(index);
|
||||
direct_list.select(index);
|
||||
}
|
||||
|
||||
private void onSocksChange() {
|
||||
final Object selected = socks_group.getSelectedCheckbox();
|
||||
if (selected == socks4radio) {
|
||||
user_text.setEnabled(true);
|
||||
password_text.setEnabled(false);
|
||||
none_check.setEnabled(false);
|
||||
up_check.setEnabled(false);
|
||||
} else {
|
||||
if (up_check.getState()) {
|
||||
user_text.setEnabled(true);
|
||||
password_text.setEnabled(true);
|
||||
} else {
|
||||
user_text.setEnabled(false);
|
||||
password_text.setEnabled(false);
|
||||
}
|
||||
none_check.setEnabled(true);
|
||||
up_check.setEnabled(true);
|
||||
}
|
||||
// System.out.println("onSocksChange:"+selected);
|
||||
}
|
||||
|
||||
private void onUPChange() {
|
||||
// System.out.println("onUPChange");
|
||||
if (up_check.getState()) {
|
||||
user_text.setEnabled(true);
|
||||
password_text.setEnabled(true);
|
||||
} else {
|
||||
user_text.setEnabled(false);
|
||||
password_text.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDismiss() {
|
||||
warning_dialog.dispose();
|
||||
if (mode == OK_MODE) {
|
||||
mode = COMMAND_MODE;
|
||||
if (net_thread != null) {
|
||||
net_thread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doInit(SocksProxyBase p) {
|
||||
if (p.version == 5) {
|
||||
socks_group.setSelectedCheckbox(socks5radio);
|
||||
onSocksChange();
|
||||
if (((Socks5Proxy) p).getAuthenticationMethod(0) != null) {
|
||||
none_check.setState(true);
|
||||
}
|
||||
final UserPasswordAuthentication auth = (UserPasswordAuthentication) ((Socks5Proxy) p)
|
||||
.getAuthenticationMethod(2);
|
||||
if (auth != null) {
|
||||
user_text.setText(auth.getUser());
|
||||
password_text.setText(auth.getPassword());
|
||||
up_check.setState(true);
|
||||
onUPChange();
|
||||
}
|
||||
} else {
|
||||
socks_group.setSelectedCheckbox(socks4radio);
|
||||
onSocksChange();
|
||||
user_text.setText(((Socks4Proxy) p).user);
|
||||
}
|
||||
ir = (InetRange) (p.directHosts.clone());
|
||||
final String[] direct_hosts = ir.getAll();
|
||||
direct_list.removeAll();
|
||||
for (int i = 0; i < direct_hosts.length; ++i) {
|
||||
direct_list.add(direct_hosts[i]);
|
||||
}
|
||||
|
||||
host_text.setText(p.proxyIP.getHostName());
|
||||
port_text.setText("" + p.proxyPort);
|
||||
|
||||
}
|
||||
|
||||
private boolean initProxy() {
|
||||
try {
|
||||
if (socks_group.getSelectedCheckbox() == socks5radio) {
|
||||
proxy = new Socks5Proxy(host, port);
|
||||
if (up_check.getState()) {
|
||||
((Socks5Proxy) proxy).setAuthenticationMethod(2,
|
||||
new UserPasswordAuthentication(user, password));
|
||||
}
|
||||
if (!none_check.getState()) {
|
||||
((Socks5Proxy) proxy).setAuthenticationMethod(0, null);
|
||||
}
|
||||
} else {
|
||||
proxy = new Socks4Proxy(host, port, user);
|
||||
}
|
||||
} catch (final java.net.UnknownHostException uhe) {
|
||||
return false;
|
||||
}
|
||||
proxy.directHosts = ir;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void info(String s) {
|
||||
msgBox("Info", s);
|
||||
}
|
||||
|
||||
private void warn(String s) {
|
||||
msgBox("Warning", s);
|
||||
}
|
||||
|
||||
private void msgBox(String title, String message) {
|
||||
warning_label.setText(message);
|
||||
warning_label.invalidate();
|
||||
warning_dialog.setTitle(title);
|
||||
warning_dialog.pack();
|
||||
warning_dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* ======================================================================
|
||||
* Form: Table:
|
||||
*/
|
||||
// +---+-------+---+---+
|
||||
// |...|.......|...|...|
|
||||
// +---+-------+---+---+
|
||||
// |...........|.......|
|
||||
// +---+-------+-------+
|
||||
// |...|.......|.......|
|
||||
// +---+-------+-------+
|
||||
// |...|.......|.......|
|
||||
// +---+-------+-------+
|
||||
// |...........|.......|
|
||||
// +-----------+-------+
|
||||
// |...........|.......|
|
||||
// |...........+---+---+
|
||||
// |...........|...|...|
|
||||
// +-----------+---+---+
|
||||
// |...........|...|...|
|
||||
// +---+---+---+---+---+
|
||||
// |...|...|...|...|...|
|
||||
// +---+---+---+---+---+
|
||||
//
|
||||
|
||||
void guiInit() {
|
||||
// Some default names used
|
||||
Label label;
|
||||
Container container;
|
||||
Font font;
|
||||
|
||||
final GridBagConstraints c = new GridBagConstraints();
|
||||
|
||||
font = new Font("Dialog", Font.PLAIN, 12);
|
||||
|
||||
container = this;
|
||||
// container = new Panel();
|
||||
container.setLayout(new GridBagLayout());
|
||||
container.setFont(font);
|
||||
container.setBackground(SystemColor.menu);
|
||||
c.insets = new Insets(3, 3, 3, 3);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
label = new Label("Host:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
host_text = new TextField("socks-proxy", 15);
|
||||
container.add(host_text, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
label = new Label("Port:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 4;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
port_text = new TextField("1080", 5);
|
||||
container.add(port_text, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 3;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
socks4radio = new Checkbox("Socks4", socks_group, false);
|
||||
// 1.0 compatible code
|
||||
// socks4radio = new Checkbox("Socks4",false);
|
||||
// socks4radio.setCheckboxGroup(socks_group);
|
||||
socks4radio.setFont(new Font(font.getName(), Font.BOLD, 14));
|
||||
container.add(socks4radio, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
socks5radio = new Checkbox("Socks5", socks_group, true);
|
||||
// 1.0 compatible code
|
||||
// socks5radio = new Checkbox("Socks5",true);
|
||||
// socks5radio.setCheckboxGroup(socks_group);
|
||||
socks5radio.setFont(new Font(font.getName(), Font.BOLD, 14));
|
||||
container.add(socks5radio, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
label = new Label("User Id:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
user_text = new TextField("", 15);
|
||||
user_text.setEnabled(false);
|
||||
container.add(user_text, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
label = new Label("Authentication");
|
||||
label.setFont(new Font(font.getName(), Font.BOLD, 14));
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 3;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
label = new Label("Password:");
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 3;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
password_text = new TextField("", 15);
|
||||
password_text.setEchoChar('*');
|
||||
password_text.setEnabled(false);
|
||||
// password_text.setEchoCharacter('*');//1.0
|
||||
container.add(password_text, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 3;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
none_check = new Checkbox("None", true);
|
||||
container.add(none_check, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 4;
|
||||
c.gridwidth = 3;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
label = new Label("Direct Hosts");
|
||||
label.setFont(new Font(font.getName(), Font.BOLD, 14));
|
||||
container.add(label, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 4;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
up_check = new Checkbox("User/Password", false);
|
||||
container.add(up_check, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 5;
|
||||
c.gridwidth = 3;
|
||||
c.gridheight = 2;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
direct_list = new List(3);
|
||||
container.add(direct_list, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 5;
|
||||
c.gridwidth = 2;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
gssapi_check = new Checkbox("GSSAPI", false);
|
||||
gssapi_check.setEnabled(false);
|
||||
container.add(gssapi_check, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 7;
|
||||
c.gridwidth = 3;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
direct_text = new TextField("", 25);
|
||||
container.add(direct_text, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 7;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
add_button = new Button("Add");
|
||||
container.add(add_button, c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 6;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
remove_button = new Button("Remove");
|
||||
container.add(remove_button, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 8;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
cancel_button = new Button("Cancel");
|
||||
container.add(cancel_button, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 8;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = 1;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
ok_button = new Button("OK");
|
||||
container.add(ok_button, c);
|
||||
|
||||
// up_check.setEnabled(false);
|
||||
|
||||
// Warning Dialog
|
||||
dismiss_button = new Button("Dismiss");
|
||||
warning_label = new Label("", Label.CENTER);
|
||||
warning_label.setFont(new Font("Dialog", Font.BOLD, 15));
|
||||
|
||||
final Panel p = new Panel();
|
||||
p.add(dismiss_button);
|
||||
warning_dialog.add(p, BorderLayout.SOUTH);
|
||||
warning_dialog.add(warning_label, BorderLayout.CENTER);
|
||||
warning_dialog.setResizable(false);
|
||||
}// end guiInit
|
||||
|
||||
/*
|
||||
* // Main //////////////////////////////////// public static void
|
||||
* main(String[] args) throws Exception{ Frame f = new
|
||||
* Frame("Test for SocksDialog"); f.add("Center", new
|
||||
* Label("Fill the Dialog")); SocksDialog socksdialog = new SocksDialog(f);
|
||||
* f.pack(); f.show(); f.addWindowListener(socksdialog); Proxy p =
|
||||
* socksdialog.getProxy(); System.out.println("Selected: "+p); }
|
||||
*/
|
||||
|
||||
}// end class
|
|
@ -0,0 +1,107 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
/**
|
||||
* Exception thrown by various socks classes to indicate errors with protocol or
|
||||
* unsuccessfull server responses.
|
||||
*/
|
||||
public class SocksException extends java.io.IOException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Construct a SocksException with given errorcode.
|
||||
* <p>
|
||||
* Tries to look up message which corresponds to this error code.
|
||||
*
|
||||
* @param errCode Error code for this exception.
|
||||
*/
|
||||
public SocksException(int errCode) {
|
||||
this.errCode = errCode;
|
||||
lookupErrorString(errCode);
|
||||
}
|
||||
|
||||
private void lookupErrorString(int errCode) {
|
||||
if ((errCode >> 16) == 0) {
|
||||
if (errCode <= serverReplyMessage.length) {
|
||||
errString = serverReplyMessage[errCode];
|
||||
} else {
|
||||
errString = UNASSIGNED_ERROR_MESSAGE;
|
||||
}
|
||||
} else {
|
||||
// Local error
|
||||
errCode = (errCode >> 16) - 1;
|
||||
if (errCode <= localErrorMessage.length) {
|
||||
errString = localErrorMessage[errCode];
|
||||
} else {
|
||||
errString = UNASSIGNED_ERROR_MESSAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SocksException with given error code, and a Throwable cause
|
||||
*
|
||||
* @param errCode
|
||||
* @param t Nested exception for debugging purposes.
|
||||
*/
|
||||
public SocksException(int errCode, Throwable t) {
|
||||
super(t); // Java 1.6+
|
||||
this.errCode = errCode;
|
||||
lookupErrorString(errCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SocksException with given error code and message.
|
||||
*
|
||||
* @param errCode Error code.
|
||||
* @param errString Error Message.
|
||||
*/
|
||||
public SocksException(int errCode, String errString) {
|
||||
this.errCode = errCode;
|
||||
this.errString = errString;
|
||||
}
|
||||
|
||||
public SocksException(int errCode, String string, Throwable t) {
|
||||
super(string, t); // Java 1.6+
|
||||
this.errCode = errCode;
|
||||
this.errString = string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code associated with this exception.
|
||||
*
|
||||
* @return Error code associated with this exception.
|
||||
*/
|
||||
public int getErrorCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable representation of this exception.
|
||||
*
|
||||
* @return String represntation of this exception.
|
||||
*/
|
||||
public String toString() {
|
||||
return errString;
|
||||
}
|
||||
|
||||
static final String UNASSIGNED_ERROR_MESSAGE = "Unknown error message";
|
||||
|
||||
static final String serverReplyMessage[] = {"Succeeded",
|
||||
"General SOCKS server failure",
|
||||
"Connection not allowed by ruleset", "Network unreachable",
|
||||
"Host unreachable", "Connection refused", "TTL expired",
|
||||
"Command not supported", "Address type not supported"};
|
||||
|
||||
static final String localErrorMessage[] = {"SOCKS server not specified",
|
||||
"Unable to contact SOCKS server", "IO error",
|
||||
"None of Authentication methods are supported",
|
||||
"Authentication failed", "General SOCKS fault"};
|
||||
|
||||
String errString;
|
||||
int errCode;
|
||||
|
||||
}// End of SocksException class
|
||||
|
|
@ -0,0 +1,527 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Abstract class Proxy, base for classes Socks4Proxy and Socks5Proxy. Defines
|
||||
* methods for specifying default proxy, to be used by all classes of this
|
||||
* package.
|
||||
*/
|
||||
|
||||
public abstract class SocksProxyBase {
|
||||
|
||||
// Data members
|
||||
protected InetRange directHosts = new InetRange();
|
||||
|
||||
protected InetAddress proxyIP = null;
|
||||
protected String proxyHost = null;
|
||||
protected int proxyPort;
|
||||
protected Socket proxySocket = null;
|
||||
|
||||
protected InputStream in;
|
||||
protected OutputStream out;
|
||||
|
||||
protected int version;
|
||||
|
||||
protected SocksProxyBase chainProxy = null;
|
||||
|
||||
// Protected static/class variables
|
||||
protected static SocksProxyBase defaultProxy = null;
|
||||
|
||||
// Constructors
|
||||
// ====================
|
||||
SocksProxyBase(SocksProxyBase chainProxy, String proxyHost, int proxyPort)
|
||||
throws UnknownHostException {
|
||||
this.chainProxy = chainProxy;
|
||||
this.proxyHost = proxyHost;
|
||||
|
||||
if (chainProxy == null) {
|
||||
this.proxyIP = InetAddress.getByName(proxyHost);
|
||||
}
|
||||
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
SocksProxyBase(String proxyHost, int proxyPort) throws UnknownHostException {
|
||||
this(null, proxyHost, proxyPort);
|
||||
}
|
||||
|
||||
SocksProxyBase(SocksProxyBase chainProxy, InetAddress proxyIP, int proxyPort) {
|
||||
this.chainProxy = chainProxy;
|
||||
this.proxyIP = proxyIP;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
SocksProxyBase(InetAddress proxyIP, int proxyPort) {
|
||||
this(null, proxyIP, proxyPort);
|
||||
}
|
||||
|
||||
SocksProxyBase(SocksProxyBase p) {
|
||||
this.proxyIP = p.proxyIP;
|
||||
this.proxyPort = p.proxyPort;
|
||||
this.version = p.version;
|
||||
this.directHosts = p.directHosts;
|
||||
}
|
||||
|
||||
// Public instance methods
|
||||
// ========================
|
||||
|
||||
/**
|
||||
* Get the port on which proxy server is running.
|
||||
*
|
||||
* @return Proxy port.
|
||||
*/
|
||||
public int getPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ip address of the proxy server host.
|
||||
*
|
||||
* @return Proxy InetAddress.
|
||||
*/
|
||||
public InetAddress getInetAddress() {
|
||||
return proxyIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds given ip to the list of direct addresses. This machine will be
|
||||
* accessed without using proxy.
|
||||
*/
|
||||
public void addDirect(InetAddress ip) {
|
||||
directHosts.add(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds host to the list of direct addresses. This machine will be accessed
|
||||
* without using proxy.
|
||||
*/
|
||||
public boolean addDirect(String host) {
|
||||
return directHosts.add(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds given range of addresses to the lsit of direct addresses, machines
|
||||
* within this range will be accessed without using proxy.
|
||||
*/
|
||||
public void addDirect(InetAddress from, InetAddress to) {
|
||||
directHosts.add(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets given InetRange as the list of direct address, previous list will be
|
||||
* discarded, any changes done previously with addDirect(Inetaddress) will
|
||||
* be lost. The machines in this range will be accessed without using proxy.
|
||||
*
|
||||
* @param ir InetRange which should be used to look up direct addresses.
|
||||
* @see InetRange
|
||||
*/
|
||||
public void setDirect(InetRange ir) {
|
||||
directHosts = ir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of direct hosts.
|
||||
*
|
||||
* @return Current range of direct address as InetRange object.
|
||||
* @see InetRange
|
||||
*/
|
||||
public InetRange getDirect() {
|
||||
return directHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether the given host is on the list of direct address.
|
||||
*
|
||||
* @param host Host name to check.
|
||||
* @return true if the given host is specified as the direct addresses.
|
||||
*/
|
||||
public boolean isDirect(String host) {
|
||||
return directHosts.contains(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether the given host is on the list of direct addresses.
|
||||
*
|
||||
* @param host Host address to check.
|
||||
* @return true if the given host is specified as the direct address.
|
||||
*/
|
||||
public boolean isDirect(InetAddress host) {
|
||||
return directHosts.contains(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proxy which should be used to connect to given proxy.
|
||||
*
|
||||
* @param chainProxy Proxy to use to connect to this proxy.
|
||||
*/
|
||||
public void setChainProxy(SocksProxyBase chainProxy) {
|
||||
this.chainProxy = chainProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy which is used to connect to this proxy.
|
||||
*
|
||||
* @return Proxy which is used to connect to this proxy, or null if proxy is
|
||||
* to be contacted directly.
|
||||
*/
|
||||
public SocksProxyBase getChainProxy() {
|
||||
return chainProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representation of this proxy.
|
||||
*
|
||||
* @returns string in the form:proxyHost:proxyPort \t Version versionNumber
|
||||
*/
|
||||
public String toString() {
|
||||
return ("" + proxyIP.getHostName() + ":" + proxyPort + "\tVersion " + version);
|
||||
}
|
||||
|
||||
// Public Static(Class) Methods
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* Sets SOCKS4 proxy as default.
|
||||
*
|
||||
* @param hostName Host name on which SOCKS4 server is running.
|
||||
* @param port Port on which SOCKS4 server is running.
|
||||
* @param user Username to use for communications with proxy.
|
||||
*/
|
||||
public static void setDefaultProxy(String hostName, int port, String user)
|
||||
throws UnknownHostException {
|
||||
defaultProxy = new Socks4Proxy(hostName, port, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets SOCKS4 proxy as default.
|
||||
*
|
||||
* @param ipAddress Host address on which SOCKS4 server is running.
|
||||
* @param port Port on which SOCKS4 server is running.
|
||||
* @param user Username to use for communications with proxy.
|
||||
*/
|
||||
public static void setDefaultProxy(InetAddress ipAddress, int port,
|
||||
String user) {
|
||||
defaultProxy = new Socks4Proxy(ipAddress, port, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets SOCKS5 proxy as default. Default proxy only supports
|
||||
* no-authentication.
|
||||
*
|
||||
* @param hostName Host name on which SOCKS5 server is running.
|
||||
* @param port Port on which SOCKS5 server is running.
|
||||
*/
|
||||
public static void setDefaultProxy(String hostName, int port)
|
||||
throws UnknownHostException {
|
||||
defaultProxy = new Socks5Proxy(hostName, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets SOCKS5 proxy as default. Default proxy only supports
|
||||
* no-authentication.
|
||||
*
|
||||
* @param ipAddress Host address on which SOCKS5 server is running.
|
||||
* @param port Port on which SOCKS5 server is running.
|
||||
*/
|
||||
public static void setDefaultProxy(InetAddress ipAddress, int port) {
|
||||
defaultProxy = new Socks5Proxy(ipAddress, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default proxy.
|
||||
*
|
||||
* @param p Proxy to use as default proxy.
|
||||
*/
|
||||
public static void setDefaultProxy(SocksProxyBase p) {
|
||||
defaultProxy = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current default proxy.
|
||||
*
|
||||
* @return Current default proxy, or null if none is set.
|
||||
*/
|
||||
public static SocksProxyBase getDefaultProxy() {
|
||||
return defaultProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses strings in the form: host[:port:user:password], and creates proxy
|
||||
* from information obtained from parsing.
|
||||
* <p>
|
||||
* Defaults: port = 1080.<br>
|
||||
* If user specified but not password, creates Socks4Proxy, if user not
|
||||
* specified creates Socks5Proxy, if both user and password are speciefied
|
||||
* creates Socks5Proxy with user/password authentication.
|
||||
*
|
||||
* @param proxy_entry String in the form host[:port:user:password]
|
||||
* @return Proxy created from the string, null if entry was somehow
|
||||
* invalid(host unknown for example, or empty string)
|
||||
*/
|
||||
public static SocksProxyBase parseProxy(String proxy_entry) {
|
||||
|
||||
String proxy_host;
|
||||
int proxy_port = 1080;
|
||||
String proxy_user = null;
|
||||
String proxy_password = null;
|
||||
SocksProxyBase proxy;
|
||||
|
||||
final java.util.StringTokenizer st = new java.util.StringTokenizer(
|
||||
proxy_entry, ":");
|
||||
if (st.countTokens() < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
proxy_host = st.nextToken();
|
||||
if (st.hasMoreTokens()) {
|
||||
try {
|
||||
proxy_port = Integer.parseInt(st.nextToken().trim());
|
||||
} catch (final NumberFormatException nfe) {
|
||||
}
|
||||
}
|
||||
|
||||
if (st.hasMoreTokens()) {
|
||||
proxy_user = st.nextToken();
|
||||
}
|
||||
|
||||
if (st.hasMoreTokens()) {
|
||||
proxy_password = st.nextToken();
|
||||
}
|
||||
|
||||
try {
|
||||
if (proxy_user == null) {
|
||||
proxy = new Socks5Proxy(proxy_host, proxy_port);
|
||||
} else if (proxy_password == null) {
|
||||
proxy = new Socks4Proxy(proxy_host, proxy_port, proxy_user);
|
||||
} else {
|
||||
proxy = new Socks5Proxy(proxy_host, proxy_port);
|
||||
final UserPasswordAuthentication upa = new UserPasswordAuthentication(
|
||||
proxy_user, proxy_password);
|
||||
|
||||
((Socks5Proxy) proxy).setAuthenticationMethod(
|
||||
UserPasswordAuthentication.METHOD_ID, upa);
|
||||
}
|
||||
} catch (final UnknownHostException uhe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
// Protected Methods
|
||||
// =================
|
||||
|
||||
protected void startSession() throws SocksException {
|
||||
try {
|
||||
if (chainProxy == null) {
|
||||
proxySocket = new Socket(proxyIP, proxyPort);
|
||||
} else if (proxyIP != null) {
|
||||
proxySocket = new SocksSocket(chainProxy, proxyIP, proxyPort);
|
||||
} else {
|
||||
proxySocket = new SocksSocket(chainProxy, proxyHost, proxyPort);
|
||||
}
|
||||
|
||||
in = proxySocket.getInputStream();
|
||||
out = proxySocket.getOutputStream();
|
||||
} catch (final SocksException se) {
|
||||
throw se;
|
||||
} catch (final IOException io_ex) {
|
||||
throw new SocksException(SOCKS_PROXY_IO_ERROR, "" + io_ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this proxy for use by individual threads.
|
||||
*
|
||||
* @return proxy
|
||||
*/
|
||||
protected abstract SocksProxyBase copy();
|
||||
|
||||
protected abstract ProxyMessage formMessage(int cmd, InetAddress ip,
|
||||
int port);
|
||||
|
||||
protected abstract ProxyMessage formMessage(int cmd, String host, int port)
|
||||
throws UnknownHostException;
|
||||
|
||||
protected abstract ProxyMessage formMessage(InputStream in)
|
||||
throws SocksException, IOException;
|
||||
|
||||
protected ProxyMessage connect(InetAddress ip, int port)
|
||||
throws SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, ip,
|
||||
port);
|
||||
return exchange(request);
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage connect(String host, int port)
|
||||
throws UnknownHostException, SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_CONNECT, host,
|
||||
port);
|
||||
return exchange(request);
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage bind(InetAddress ip, int port) throws SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_BIND, ip, port);
|
||||
return exchange(request);
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage bind(String host, int port)
|
||||
throws UnknownHostException, SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_BIND, host, port);
|
||||
return exchange(request);
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
protected ProxyMessage accept() throws IOException, SocksException {
|
||||
ProxyMessage msg;
|
||||
try {
|
||||
msg = formMessage(in);
|
||||
} catch (final InterruptedIOException iioe) {
|
||||
throw iioe;
|
||||
} catch (final IOException io_ex) {
|
||||
endSession();
|
||||
throw new SocksException(SOCKS_PROXY_IO_ERROR,
|
||||
"While Trying accept:" + io_ex);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected ProxyMessage udpAssociate(InetAddress ip, int port)
|
||||
throws SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
|
||||
ip, port);
|
||||
if (request != null) {
|
||||
return exchange(request);
|
||||
}
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
// Only get here if request was null
|
||||
endSession();
|
||||
throw new SocksException(SOCKS_METHOD_NOTSUPPORTED,
|
||||
"This version of proxy does not support UDP associate, use version 5");
|
||||
}
|
||||
|
||||
protected ProxyMessage udpAssociate(String host, int port)
|
||||
throws UnknownHostException, SocksException {
|
||||
try {
|
||||
startSession();
|
||||
final ProxyMessage request = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
|
||||
host, port);
|
||||
if (request != null) {
|
||||
return exchange(request);
|
||||
}
|
||||
} catch (final SocksException se) {
|
||||
endSession();
|
||||
throw se;
|
||||
}
|
||||
// Only get here if request was null
|
||||
endSession();
|
||||
throw new SocksException(SOCKS_METHOD_NOTSUPPORTED,
|
||||
"This version of proxy does not support UDP associate, use version 5");
|
||||
}
|
||||
|
||||
protected void endSession() {
|
||||
try {
|
||||
if (proxySocket != null) {
|
||||
proxySocket.close();
|
||||
}
|
||||
proxySocket = null;
|
||||
} catch (final IOException io_ex) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request to SOCKS server
|
||||
*/
|
||||
protected void sendMsg(ProxyMessage msg) throws SocksException, IOException {
|
||||
msg.write(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the reply from the SOCKS server
|
||||
*/
|
||||
protected ProxyMessage readMsg() throws SocksException, IOException {
|
||||
return formMessage(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request reads reply and returns it throws exception if
|
||||
* something wrong with IO or the reply code is not zero
|
||||
*/
|
||||
protected ProxyMessage exchange(ProxyMessage request) throws SocksException {
|
||||
ProxyMessage reply;
|
||||
try {
|
||||
request.write(out);
|
||||
reply = formMessage(in);
|
||||
} catch (final SocksException s_ex) {
|
||||
throw s_ex;
|
||||
} catch (final IOException ioe) {
|
||||
throw (new SocksException(SOCKS_PROXY_IO_ERROR, "" + ioe));
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ===============
|
||||
|
||||
// Constants
|
||||
|
||||
public static final int SOCKS_SUCCESS = 0;
|
||||
public static final int SOCKS_FAILURE = 1;
|
||||
public static final int SOCKS_BADCONNECT = 2;
|
||||
public static final int SOCKS_BADNETWORK = 3;
|
||||
public static final int SOCKS_HOST_UNREACHABLE = 4;
|
||||
public static final int SOCKS_CONNECTION_REFUSED = 5;
|
||||
public static final int SOCKS_TTL_EXPIRE = 6;
|
||||
public static final int SOCKS_CMD_NOT_SUPPORTED = 7;
|
||||
public static final int SOCKS_ADDR_NOT_SUPPORTED = 8;
|
||||
|
||||
public static final int SOCKS_NO_PROXY = 1 << 16;
|
||||
public static final int SOCKS_PROXY_NO_CONNECT = 2 << 16;
|
||||
public static final int SOCKS_PROXY_IO_ERROR = 3 << 16;
|
||||
public static final int SOCKS_AUTH_NOT_SUPPORTED = 4 << 16;
|
||||
public static final int SOCKS_AUTH_FAILURE = 5 << 16;
|
||||
public static final int SOCKS_JUST_ERROR = 6 << 16;
|
||||
|
||||
public static final int SOCKS_DIRECT_FAILED = 7 << 16;
|
||||
public static final int SOCKS_METHOD_NOTSUPPORTED = 8 << 16;
|
||||
|
||||
static final int SOCKS_CMD_CONNECT = 0x1;
|
||||
static final int SOCKS_CMD_BIND = 0x2;
|
||||
static final int SOCKS_CMD_UDP_ASSOCIATE = 0x3;
|
||||
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* SocksServerSocket allows to accept connections from one particular host
|
||||
* through the SOCKS4 or SOCKS5 proxy.
|
||||
*/
|
||||
public class SocksServerSocket extends ServerSocket {
|
||||
// Data members
|
||||
protected SocksProxyBase proxy;
|
||||
protected String localHost;
|
||||
protected InetAddress localIP;
|
||||
protected int localPort;
|
||||
|
||||
boolean doing_direct = false;
|
||||
InetAddress remoteAddr;
|
||||
|
||||
/**
|
||||
* Creates ServerSocket capable of accepting one connection through the
|
||||
* firewall, uses default Proxy.
|
||||
*
|
||||
* @param host Host from which the connection should be recieved.
|
||||
* @param port Port number of the primary connection.
|
||||
*/
|
||||
public SocksServerSocket(String host, int port) throws SocksException,
|
||||
UnknownHostException, IOException {
|
||||
this(SocksProxyBase.defaultProxy, host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ServerSocket capable of accepting one connection through the
|
||||
* firewall, uses given proxy.
|
||||
*
|
||||
* @param p Proxy object to use.
|
||||
* @param host Host from which the connection should be recieved.
|
||||
* @param port Port number of the primary connection.
|
||||
*/
|
||||
public SocksServerSocket(SocksProxyBase p, String host, int port)
|
||||
throws SocksException, UnknownHostException, IOException {
|
||||
|
||||
super(0);
|
||||
if (p == null) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
|
||||
}
|
||||
// proxy=p;
|
||||
proxy = p.copy();
|
||||
if (proxy.isDirect(host)) {
|
||||
remoteAddr = InetAddress.getByName(host);
|
||||
proxy = null;
|
||||
doDirect();
|
||||
} else {
|
||||
processReply(proxy.bind(host, port));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ServerSocket capable of accepting one connection through the
|
||||
* firewall, uses default Proxy.
|
||||
*
|
||||
* @param ip Host from which the connection should be recieved.
|
||||
* @param port Port number of the primary connection.
|
||||
*/
|
||||
public SocksServerSocket(InetAddress ip, int port) throws SocksException,
|
||||
IOException {
|
||||
this(SocksProxyBase.defaultProxy, ip, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ServerSocket capable of accepting one connection through the
|
||||
* firewall, uses given proxy.
|
||||
*
|
||||
* @param p Proxy object to use.
|
||||
* @param ip Host from which the connection should be recieved.
|
||||
* @param port Port number of the primary connection.
|
||||
*/
|
||||
public SocksServerSocket(SocksProxyBase p, InetAddress ip, int port)
|
||||
throws SocksException, IOException {
|
||||
super(0);
|
||||
|
||||
if (p == null) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
|
||||
}
|
||||
this.proxy = p.copy();
|
||||
|
||||
if (proxy.isDirect(ip)) {
|
||||
remoteAddr = ip;
|
||||
doDirect();
|
||||
} else {
|
||||
processReply(proxy.bind(ip, port));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the incoming connection.
|
||||
*/
|
||||
public Socket accept() throws IOException {
|
||||
Socket s;
|
||||
|
||||
if (!doing_direct) {
|
||||
if (proxy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ProxyMessage msg = proxy.accept();
|
||||
s = msg.ip == null ? new SocksSocket(msg.host, msg.port, proxy)
|
||||
: new SocksSocket(msg.ip, msg.port, proxy);
|
||||
// Set timeout back to 0
|
||||
proxy.proxySocket.setSoTimeout(0);
|
||||
} else { // Direct Connection
|
||||
|
||||
// Mimic the proxy behaviour,
|
||||
// only accept connections from the speciefed host.
|
||||
while (true) {
|
||||
s = super.accept();
|
||||
if (s.getInetAddress().equals(remoteAddr)) {
|
||||
// got the connection from the right host
|
||||
// Close listenning socket.
|
||||
break;
|
||||
} else {
|
||||
s.close(); // Drop all connections from other hosts
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
proxy = null;
|
||||
// Return accepted socket
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to proxy if socket have not been accepted, if the
|
||||
* direct connection is used, closes direct ServerSocket. If the client
|
||||
* socket have been allready accepted, does nothing.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
if (proxy != null) {
|
||||
proxy.endSession();
|
||||
}
|
||||
proxy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the host proxy is using to listen for incoming
|
||||
* connection.
|
||||
* <p>
|
||||
* Usefull when address is returned by proxy as the hostname.
|
||||
*
|
||||
* @return the hostname of the address proxy is using to listen for incoming
|
||||
* connection.
|
||||
*/
|
||||
public String getHost() {
|
||||
return localHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address assigned by proxy to listen for incomming connections, or the
|
||||
* local machine address if doing direct connection.
|
||||
*/
|
||||
public InetAddress getInetAddress() {
|
||||
if (localIP == null) {
|
||||
try {
|
||||
localIP = InetAddress.getByName(localHost);
|
||||
} catch (final UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return localIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get port assigned by proxy to listen for incoming connections, or the
|
||||
* port chosen by local system, if accepting directly.
|
||||
*/
|
||||
public int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Timeout.
|
||||
*
|
||||
* @param timeout Amount of time in milliseconds, accept should wait for
|
||||
* incoming connection before failing with exception. Zero
|
||||
* timeout implies infinity.
|
||||
*/
|
||||
public void setSoTimeout(int timeout) throws SocketException {
|
||||
super.setSoTimeout(timeout);
|
||||
if (!doing_direct) {
|
||||
proxy.proxySocket.setSoTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
// ////////////////
|
||||
|
||||
private void processReply(ProxyMessage reply) throws SocksException {
|
||||
localPort = reply.port;
|
||||
/*
|
||||
* If the server have assigned same host as it was contacted on it might
|
||||
* return an address of all zeros
|
||||
*/
|
||||
if (reply.host.equals("0.0.0.0")) {
|
||||
localIP = proxy.proxyIP;
|
||||
localHost = localIP.getHostName();
|
||||
} else {
|
||||
localHost = reply.host;
|
||||
localIP = reply.ip;
|
||||
}
|
||||
}
|
||||
|
||||
private void doDirect() {
|
||||
doing_direct = true;
|
||||
localPort = super.getLocalPort();
|
||||
localIP = super.getInetAddress();
|
||||
localHost = localIP.getHostName();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* SocksSocket tryies to look very similar to normal Socket, while allowing
|
||||
* connections through the SOCKS4 or 5 proxy. To use this class you will have to
|
||||
* identify proxy you need to use, Proxy class allows you to set default proxy,
|
||||
* which will be used by all Socks aware sockets. You can also create either
|
||||
* Socks4Proxy or Socks5Proxy, and use them by passing to the appropriate
|
||||
* constructors.
|
||||
* <p>
|
||||
* Using Socks package can be as easy as that:
|
||||
* <p>
|
||||
* <pre>
|
||||
* <tt>
|
||||
*
|
||||
* import Socks.*;
|
||||
* ....
|
||||
*
|
||||
* try{
|
||||
* //Specify SOCKS5 proxy
|
||||
* Proxy.setDefaultProxy("socks-proxy",1080);
|
||||
*
|
||||
* //OR you still use SOCKS4
|
||||
* //Code below uses SOCKS4 proxy
|
||||
* //Proxy.setDefaultProxy("socks-proxy",1080,userName);
|
||||
*
|
||||
* Socket s = SocksSocket("some.host.of.mine",13);
|
||||
* readTimeFromSock(s);
|
||||
* }catch(SocksException sock_ex){
|
||||
* //Usually it will turn in more or less meaningfull message
|
||||
* System.err.println("SocksException:"+sock_ex);
|
||||
* }
|
||||
*
|
||||
* </tt>
|
||||
* </pre>
|
||||
* <p>
|
||||
* However if the need exist for more control, like resolving addresses
|
||||
* remotely, or using some non-trivial authentication schemes, it can be done.
|
||||
*/
|
||||
|
||||
public class SocksSocket extends Socket {
|
||||
// Data members
|
||||
protected SocksProxyBase proxy;
|
||||
protected String localHost, remoteHost;
|
||||
protected InetAddress localIP, remoteIP;
|
||||
protected int localPort, remotePort;
|
||||
|
||||
private Socket directSock = null;
|
||||
private Logger log = LoggerFactory.getLogger(SocksSocket.class);
|
||||
|
||||
/**
|
||||
* Tryies to connect to given host and port using default proxy. If no
|
||||
* default proxy speciefied it throws SocksException with error code
|
||||
* SOCKS_NO_PROXY.
|
||||
*
|
||||
* @param host Machine to connect to.
|
||||
* @param port Port to which to connect.
|
||||
* @see SocksSocket#SocksSocket(SocksProxyBase, String, int)
|
||||
* @see Socks5Proxy#resolveAddrLocally
|
||||
*/
|
||||
public SocksSocket(String host, int port) throws SocksException,
|
||||
UnknownHostException {
|
||||
this(SocksProxyBase.defaultProxy, host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to host port using given proxy server.
|
||||
*
|
||||
* @param p Proxy to use.
|
||||
* @param host Machine to connect to.
|
||||
* @param port Port to which to connect.
|
||||
* @throws UnknownHostException If one of the following happens:
|
||||
* <ol>
|
||||
* <p>
|
||||
* <li>Proxy settings say that address should be resolved
|
||||
* locally, but this fails.
|
||||
* <li>Proxy settings say that the host should be contacted
|
||||
* directly but host name can't be resolved.
|
||||
* </ol>
|
||||
* @throws SocksException If one of the following happens:
|
||||
* <ul>
|
||||
* <li>Proxy is is null.
|
||||
* <li>Proxy settings say that the host should be contacted
|
||||
* directly but this fails.
|
||||
* <li>Socks Server can't be contacted.
|
||||
* <li>Authentication fails.
|
||||
* <li>Connection is not allowed by the SOCKS proxy.
|
||||
* <li>SOCKS proxy can't establish the connection.
|
||||
* <li>Any IO error occured.
|
||||
* <li>Any protocol error occured.
|
||||
* </ul>
|
||||
* @throws IOexception if anything is wrong with I/O.
|
||||
* @see Socks5Proxy#resolveAddrLocally
|
||||
*/
|
||||
public SocksSocket(SocksProxyBase p, String host, int port)
|
||||
throws SocksException, UnknownHostException {
|
||||
|
||||
if (p == null) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
|
||||
}
|
||||
// proxy=p;
|
||||
proxy = p.copy();
|
||||
remoteHost = host;
|
||||
remotePort = port;
|
||||
if (proxy.isDirect(host)) {
|
||||
remoteIP = InetAddress.getByName(host);
|
||||
doDirect();
|
||||
} else {
|
||||
processReply(proxy.connect(host, port));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tryies to connect to given ip and port using default proxy. If no default
|
||||
* proxy speciefied it throws SocksException with error code SOCKS_NO_PROXY.
|
||||
*
|
||||
* @param ip Machine to connect to.
|
||||
* @param port Port to which to connect.
|
||||
* @see SocksSocket#SocksSocket(SocksProxyBase, String, int)
|
||||
*/
|
||||
public SocksSocket(InetAddress ip, int port) throws SocksException {
|
||||
this(SocksProxyBase.defaultProxy, ip, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to given ip and port using given Proxy server.
|
||||
*
|
||||
* @param p Proxy to use.
|
||||
* @param ip Machine to connect to.
|
||||
* @param port Port to which to connect.
|
||||
*/
|
||||
public SocksSocket(SocksProxyBase p, InetAddress ip, int port)
|
||||
throws SocksException {
|
||||
if (p == null) {
|
||||
throw new SocksException(SocksProxyBase.SOCKS_NO_PROXY);
|
||||
}
|
||||
this.proxy = p.copy();
|
||||
this.remoteIP = ip;
|
||||
this.remotePort = port;
|
||||
this.remoteHost = ip.getHostName();
|
||||
if (proxy.isDirect(remoteIP)) {
|
||||
doDirect();
|
||||
} else {
|
||||
processReply(proxy.connect(ip, port));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These 2 constructors are used by the SocksServerSocket. This socket
|
||||
* simply overrides remoteHost, remotePort
|
||||
*/
|
||||
protected SocksSocket(String host, int port, SocksProxyBase proxy) {
|
||||
this.remotePort = port;
|
||||
this.proxy = proxy;
|
||||
this.localIP = proxy.proxySocket.getLocalAddress();
|
||||
this.localPort = proxy.proxySocket.getLocalPort();
|
||||
this.remoteHost = host;
|
||||
}
|
||||
|
||||
protected SocksSocket(InetAddress ip, int port, SocksProxyBase proxy) {
|
||||
remoteIP = ip;
|
||||
remotePort = port;
|
||||
this.proxy = proxy;
|
||||
this.localIP = proxy.proxySocket.getLocalAddress();
|
||||
this.localPort = proxy.proxySocket.getLocalPort();
|
||||
remoteHost = remoteIP.getHostName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as Socket
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (proxy != null) {
|
||||
proxy.endSession();
|
||||
}
|
||||
proxy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as Socket
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return proxy.in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as Socket
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
return proxy.out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as Socket
|
||||
*/
|
||||
public int getPort() {
|
||||
return remotePort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns remote host name, it is usefull in cases when addresses are
|
||||
* resolved by proxy, and we can't create InetAddress object.
|
||||
*
|
||||
* @return The name of the host this socket is connected to.
|
||||
*/
|
||||
public String getHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote host as InetAddress object, might return null if addresses are
|
||||
* resolved by proxy, and it is not possible to resolve it locally
|
||||
*
|
||||
* @return Ip address of the host this socket is connected to, or null if
|
||||
* address was returned by the proxy as DOMAINNAME and can't be
|
||||
* resolved locally.
|
||||
*/
|
||||
public InetAddress getInetAddress() {
|
||||
if (remoteIP == null) {
|
||||
try {
|
||||
remoteIP = InetAddress.getByName(remoteHost);
|
||||
} catch (final UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return remoteIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port assigned by the proxy for the socket, not the port on locall
|
||||
* machine as in Socket.
|
||||
*
|
||||
* @return Port of the socket used on the proxy server.
|
||||
*/
|
||||
public int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address assigned by proxy to make a remote connection, it might be
|
||||
* different from the host specified for the proxy. Can return null if socks
|
||||
* server returned this address as hostname and it can't be resolved
|
||||
* locally, use getLocalHost() then.
|
||||
*
|
||||
* @return Address proxy is using to make a connection.
|
||||
*/
|
||||
public InetAddress getLocalAddress() {
|
||||
if (localIP == null) {
|
||||
try {
|
||||
localIP = InetAddress.getByName(localHost);
|
||||
} catch (final UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return localIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of the host, proxy has assigned to make a remote connection for
|
||||
* this socket. This method is usefull when proxy have returned address as
|
||||
* hostname, and we can't resolve it on this machine.
|
||||
*
|
||||
* @return The name of the host proxy is using to make a connection.
|
||||
*/
|
||||
public String getLocalHost() {
|
||||
return localHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public void setSoLinger(boolean on, int val) throws SocketException {
|
||||
proxy.proxySocket.setSoLinger(on, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public int getSoLinger(int timeout) throws SocketException {
|
||||
return proxy.proxySocket.getSoLinger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public void setSoTimeout(int timeout) throws SocketException {
|
||||
proxy.proxySocket.setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public int getSoTimeout(int timeout) throws SocketException {
|
||||
return proxy.proxySocket.getSoTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||
proxy.proxySocket.setTcpNoDelay(on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as socket.
|
||||
*/
|
||||
public boolean getTcpNoDelay() throws SocketException {
|
||||
return proxy.proxySocket.getTcpNoDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representation of the socket.
|
||||
*/
|
||||
public String toString() {
|
||||
if (directSock != null) {
|
||||
return "Direct connection:" + directSock;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("Proxy:");
|
||||
sb.append(proxy);
|
||||
sb.append(";");
|
||||
sb.append("addr:");
|
||||
sb.append(remoteHost);
|
||||
sb.append(",port:");
|
||||
sb.append(remotePort);
|
||||
sb.append(",localport:");
|
||||
sb.append(localPort);
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
// ////////////////
|
||||
|
||||
private void processReply(ProxyMessage reply) throws SocksException {
|
||||
localPort = reply.port;
|
||||
/*
|
||||
* If the server have assigned same host as it was contacted on it might
|
||||
* return an address of all zeros
|
||||
*/
|
||||
if (reply.host.equals("0.0.0.0")) {
|
||||
localIP = proxy.proxyIP;
|
||||
localHost = localIP.getHostName();
|
||||
} else {
|
||||
localHost = reply.host;
|
||||
localIP = reply.ip;
|
||||
}
|
||||
}
|
||||
|
||||
private void doDirect() throws SocksException {
|
||||
try {
|
||||
log.debug("IP: {}_{}", remoteIP, remotePort);
|
||||
directSock = new Socket(remoteIP, remotePort);
|
||||
proxy.out = directSock.getOutputStream();
|
||||
proxy.in = directSock.getInputStream();
|
||||
proxy.proxySocket = directSock;
|
||||
localIP = directSock.getLocalAddress();
|
||||
localPort = directSock.getLocalPort();
|
||||
} catch (final IOException io_ex) {
|
||||
final int errCode = SocksProxyBase.SOCKS_DIRECT_FAILED;
|
||||
throw new SocksException(errCode, "Direct connect failed:", io_ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
/**
|
||||
* This interface provides for datagram encapsulation for SOCKSv5 protocol.
|
||||
* <p>
|
||||
* SOCKSv5 allows for datagrams to be encapsulated for purposes of integrity
|
||||
* and/or authenticity. How it should be done is aggreed during the
|
||||
* authentication stage, and is authentication dependent. This interface is
|
||||
* provided to allow this encapsulation.
|
||||
*
|
||||
* @see Authentication
|
||||
*/
|
||||
public interface UDPEncapsulation {
|
||||
|
||||
/**
|
||||
* This method should provide any authentication depended transformation on
|
||||
* datagrams being send from/to the client.
|
||||
*
|
||||
* @param data Datagram data (including any SOCKS related bytes), to be
|
||||
* encapsulated/decapsulated.
|
||||
* @param out Wether the data is being send out. If true method should
|
||||
* encapsulate/encrypt data, otherwise it should decapsulate/
|
||||
* decrypt data.
|
||||
* @return Should return byte array containing data after transformation. It
|
||||
* is possible to return same array as input, if transformation only
|
||||
* involves bit mangling, and no additional data is being added or
|
||||
* removed.
|
||||
* @throw IOException if for some reason data can be transformed correctly.
|
||||
*/
|
||||
byte[] udpEncapsulate(byte[] data, boolean out) throws java.io.IOException;
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.server.ServerAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* UDP Relay server, used by ProxyServer to perform udp forwarding.
|
||||
*/
|
||||
class UDPRelayServer implements Runnable {
|
||||
|
||||
DatagramSocket client_sock;
|
||||
DatagramSocket remote_sock;
|
||||
|
||||
Socket controlConnection;
|
||||
|
||||
int relayPort;
|
||||
InetAddress relayIP;
|
||||
|
||||
Thread pipe_thread1, pipe_thread2;
|
||||
Thread master_thread;
|
||||
|
||||
ServerAuthenticator auth;
|
||||
|
||||
long lastReadTime;
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(UDPRelayServer.class);
|
||||
static SocksProxyBase proxy = null;
|
||||
static int datagramSize = 0xFFFF;// 64K, a bit more than max udp size
|
||||
static int iddleTimeout = 180000;// 3 minutes
|
||||
|
||||
/**
|
||||
* Constructs UDP relay server to communicate with client on given ip and
|
||||
* port.
|
||||
*
|
||||
* @param clientIP Address of the client from whom datagrams will be recieved and
|
||||
* to whom they will be forwarded.
|
||||
* @param clientPort Clients port.
|
||||
* @param master_thread Thread which will be interrupted, when UDP relay server
|
||||
* stoppes for some reason.
|
||||
* @param controlConnection Socket which will be closed, before interrupting the master
|
||||
* thread, it is introduced due to a bug in windows JVM which
|
||||
* does not throw InterruptedIOException in threads which block
|
||||
* in I/O operation.
|
||||
*/
|
||||
public UDPRelayServer(InetAddress clientIP, int clientPort,
|
||||
Thread master_thread, Socket controlConnection,
|
||||
ServerAuthenticator auth) throws IOException {
|
||||
|
||||
this.master_thread = master_thread;
|
||||
this.controlConnection = controlConnection;
|
||||
this.auth = auth;
|
||||
|
||||
client_sock = new Socks5DatagramSocket(true,
|
||||
auth.getUdpEncapsulation(), clientIP, clientPort);
|
||||
|
||||
relayPort = client_sock.getLocalPort();
|
||||
relayIP = client_sock.getLocalAddress();
|
||||
|
||||
if (relayIP.getHostAddress().equals("0.0.0.0")) {
|
||||
relayIP = InetAddress.getLocalHost();
|
||||
}
|
||||
|
||||
if (proxy == null) {
|
||||
remote_sock = new DatagramSocket();
|
||||
} else {
|
||||
remote_sock = new Socks5DatagramSocket(proxy, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods
|
||||
// ///////////////
|
||||
|
||||
/**
|
||||
* Sets the timeout for UDPRelay server.<br>
|
||||
* Zero timeout implies infinity.<br>
|
||||
* Default timeout is 3 minutes.
|
||||
*/
|
||||
|
||||
static public void setTimeout(int timeout) {
|
||||
iddleTimeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the datagrams used in the UDPRelayServer.<br>
|
||||
* Default size is 64K, a bit more than maximum possible size of the
|
||||
* datagram.
|
||||
*/
|
||||
static public void setDatagramSize(int size) {
|
||||
datagramSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Port to which client should send datagram for association.
|
||||
*/
|
||||
public int getRelayPort() {
|
||||
return relayPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* IP address to which client should send datagrams for association.
|
||||
*/
|
||||
public InetAddress getRelayIP() {
|
||||
return relayIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts udp relay server. Spawns two threads of execution and returns.
|
||||
*/
|
||||
public void start() throws IOException {
|
||||
remote_sock.setSoTimeout(iddleTimeout);
|
||||
client_sock.setSoTimeout(iddleTimeout);
|
||||
|
||||
log.info("Starting UDP relay server on {}:{}", relayIP, relayPort);
|
||||
log.info("Remote socket {}:{}", remote_sock.getLocalAddress(),
|
||||
remote_sock.getLocalPort());
|
||||
|
||||
pipe_thread1 = new Thread(this, "pipe1");
|
||||
pipe_thread2 = new Thread(this, "pipe2");
|
||||
|
||||
lastReadTime = System.currentTimeMillis();
|
||||
|
||||
pipe_thread1.start();
|
||||
pipe_thread2.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops Relay server.
|
||||
* <p>
|
||||
* Does not close control connection, does not interrupt master_thread.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
master_thread = null;
|
||||
controlConnection = null;
|
||||
abort();
|
||||
}
|
||||
|
||||
// Runnable interface
|
||||
// //////////////////
|
||||
public void run() {
|
||||
try {
|
||||
if (Thread.currentThread().getName().equals("pipe1")) {
|
||||
pipe(remote_sock, client_sock, false);
|
||||
} else {
|
||||
pipe(client_sock, remote_sock, true);
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
} finally {
|
||||
abort();
|
||||
log.info("UDP Pipe thread " + Thread.currentThread().getName()
|
||||
+ " stopped.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ///////////////
|
||||
private synchronized void abort() {
|
||||
if (pipe_thread1 == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Aborting UDP Relay Server");
|
||||
|
||||
remote_sock.close();
|
||||
client_sock.close();
|
||||
|
||||
if (controlConnection != null) {
|
||||
try {
|
||||
controlConnection.close();
|
||||
} catch (final IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
if (master_thread != null) {
|
||||
master_thread.interrupt();
|
||||
}
|
||||
|
||||
pipe_thread1.interrupt();
|
||||
pipe_thread2.interrupt();
|
||||
|
||||
pipe_thread1 = null;
|
||||
}
|
||||
|
||||
private void pipe(DatagramSocket from, DatagramSocket to, boolean out)
|
||||
throws IOException {
|
||||
final byte[] data = new byte[datagramSize];
|
||||
final DatagramPacket dp = new DatagramPacket(data, data.length);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
from.receive(dp);
|
||||
lastReadTime = System.currentTimeMillis();
|
||||
|
||||
if (auth.checkRequest(dp, out)) {
|
||||
to.send(dp);
|
||||
}
|
||||
|
||||
} catch (final UnknownHostException uhe) {
|
||||
log.info("Dropping datagram for unknown host");
|
||||
} catch (final InterruptedIOException iioe) {
|
||||
// log("Interrupted: "+iioe);
|
||||
// If we were interrupted by other thread.
|
||||
if (iddleTimeout == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If last datagram was received, long time ago, return.
|
||||
final long timeSinceRead = System.currentTimeMillis()
|
||||
- lastReadTime;
|
||||
if (timeSinceRead >= iddleTimeout - 100) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dp.setLength(data.length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package com.runjva.sourceforge.jsocks.protocol;
|
||||
|
||||
/**
|
||||
* SOCKS5 User Password authentication scheme.
|
||||
*/
|
||||
public class UserPasswordAuthentication implements Authentication {
|
||||
|
||||
/**
|
||||
* SOCKS ID for User/Password authentication method
|
||||
*/
|
||||
public final static int METHOD_ID = 2;
|
||||
|
||||
String userName, password;
|
||||
byte[] request;
|
||||
|
||||
/**
|
||||
* Create an instance of UserPasswordAuthentication.
|
||||
*
|
||||
* @param userName User Name to send to SOCKS server.
|
||||
* @param password Password to send to SOCKS server.
|
||||
*/
|
||||
public UserPasswordAuthentication(String userName, String password) {
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
formRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user name.
|
||||
*
|
||||
* @return User name.
|
||||
*/
|
||||
public String getUser() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password
|
||||
*
|
||||
* @return Password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does User/Password authentication as defined in rfc1929.
|
||||
*
|
||||
* @return An array containnig in, out streams, or null if authentication
|
||||
* fails.
|
||||
*/
|
||||
public Object[] doSocksAuthentication(int methodId,
|
||||
java.net.Socket proxySocket) throws java.io.IOException {
|
||||
|
||||
if (methodId != METHOD_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final java.io.InputStream in = proxySocket.getInputStream();
|
||||
final java.io.OutputStream out = proxySocket.getOutputStream();
|
||||
|
||||
out.write(request);
|
||||
final int version = in.read();
|
||||
if (version < 0) {
|
||||
return null; // Server closed connection
|
||||
}
|
||||
final int status = in.read();
|
||||
if (status != 0) {
|
||||
return null; // Server closed connection, or auth failed.
|
||||
}
|
||||
|
||||
return new Object[]{in, out};
|
||||
}
|
||||
|
||||
// Private methods
|
||||
// ////////////////
|
||||
|
||||
/**
|
||||
* Convert UserName password in to binary form, ready to be send to server
|
||||
*/
|
||||
private void formRequest() {
|
||||
final byte[] user_bytes = userName.getBytes();
|
||||
final byte[] password_bytes = password.getBytes();
|
||||
|
||||
request = new byte[3 + user_bytes.length + password_bytes.length];
|
||||
request[0] = (byte) 1;
|
||||
request[1] = (byte) user_bytes.length;
|
||||
System.arraycopy(user_bytes, 0, request, 2, user_bytes.length);
|
||||
request[2 + user_bytes.length] = (byte) password_bytes.length;
|
||||
System.arraycopy(password_bytes, 0, request, 3 + user_bytes.length,
|
||||
password_bytes.length);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Socket;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Class Ident provides means to obtain user name of the owner of the socket on
|
||||
* remote machine, providing remote machine runs identd daemon.
|
||||
* <p>
|
||||
* To use it: <tt><pre>
|
||||
* Socket s = ss.accept();
|
||||
* Ident id = new Ident(s);
|
||||
* if(id.successful) goUseUser(id.userName);
|
||||
* else handleIdentError(id.errorCode,id.errorMessage)
|
||||
* </pre></tt>
|
||||
*/
|
||||
public class Ident {
|
||||
|
||||
Logger log = LoggerFactory.getLogger(Ident.class);
|
||||
|
||||
/**
|
||||
* Error Message can be null.
|
||||
*/
|
||||
public String errorMessage;
|
||||
|
||||
/**
|
||||
* Host type as returned by daemon, can be null, if error happened
|
||||
*/
|
||||
public String hostType;
|
||||
|
||||
/**
|
||||
* User name as returned by the identd daemon, or null, if it failed
|
||||
*/
|
||||
public String userName;
|
||||
|
||||
/**
|
||||
* If this is true then userName and hostType contain valid values. Else
|
||||
* errorCode contain the error code, and errorMessage contains the
|
||||
* corresponding message.
|
||||
*/
|
||||
public boolean successful;
|
||||
|
||||
/**
|
||||
* Error code
|
||||
*/
|
||||
public int errorCode;
|
||||
|
||||
/**
|
||||
* Identd on port 113 can't be contacted
|
||||
*/
|
||||
public static final int ERR_NO_CONNECT = 1;
|
||||
|
||||
/**
|
||||
* Connection timed out
|
||||
*/
|
||||
public static final int ERR_TIMEOUT = 2;
|
||||
|
||||
/**
|
||||
* Identd daemon responded with ERROR, in this case errorMessage contains
|
||||
* the string explanation, as send by the daemon.
|
||||
*/
|
||||
public static final int ERR_PROTOCOL = 3;
|
||||
|
||||
/**
|
||||
* When parsing server response protocol error happened.
|
||||
*/
|
||||
public static final int ERR_PROTOCOL_INCORRECT = 4;
|
||||
|
||||
/**
|
||||
* Maximum amount of time we should wait before dropping the connection to
|
||||
* identd server.Setting it to 0 implies infinit timeout.
|
||||
*/
|
||||
public static final int connectionTimeout = 10000;
|
||||
|
||||
/**
|
||||
* Constructor tries to connect to Identd daemon on the host of the given
|
||||
* socket, and retrieve user name of the owner of given socket connection on
|
||||
* remote machine. After constructor returns public fields are initialised
|
||||
* to whatever the server returned.
|
||||
* <p>
|
||||
* If user name was successfully retrieved successful is set to true, and
|
||||
* userName and hostType are set to whatever server returned. If however for
|
||||
* some reason user name was not obtained, successful is set to false and
|
||||
* errorCode contains the code explaining the reason of failure, and
|
||||
* errorMessage contains human readable explanation.
|
||||
* <p>
|
||||
* Constructor may block, for a while.
|
||||
*
|
||||
* @param s Socket whose ownership on remote end should be obtained.
|
||||
*/
|
||||
public Ident(Socket s) {
|
||||
Socket sock = null;
|
||||
successful = false; // We are pessimistic
|
||||
|
||||
try {
|
||||
sock = new Socket(s.getInetAddress(), 113);
|
||||
sock.setSoTimeout(connectionTimeout);
|
||||
final byte[] request = ("" + s.getPort() + " , " + s.getLocalPort() + "\r\n")
|
||||
.getBytes();
|
||||
|
||||
sock.getOutputStream().write(request);
|
||||
|
||||
final BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
sock.getInputStream()));
|
||||
|
||||
parseResponse(in.readLine());
|
||||
|
||||
} catch (final InterruptedIOException iioe) {
|
||||
errorCode = ERR_TIMEOUT;
|
||||
errorMessage = "Connection to identd timed out.";
|
||||
} catch (final ConnectException ce) {
|
||||
errorCode = ERR_NO_CONNECT;
|
||||
errorMessage = "Connection to identd server failed.";
|
||||
|
||||
} catch (final IOException ioe) {
|
||||
errorCode = ERR_NO_CONNECT;
|
||||
errorMessage = "" + ioe;
|
||||
} finally {
|
||||
try {
|
||||
if (sock != null) {
|
||||
sock.close();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Could not close socket", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResponse(String response) {
|
||||
if (response == null) {
|
||||
errorCode = ERR_PROTOCOL_INCORRECT;
|
||||
errorMessage = "Identd server closed connection.";
|
||||
return;
|
||||
}
|
||||
|
||||
final StringTokenizer st = new StringTokenizer(response, ":");
|
||||
if (st.countTokens() < 3) {
|
||||
errorCode = ERR_PROTOCOL_INCORRECT;
|
||||
errorMessage = "Can't parse server response.";
|
||||
return;
|
||||
}
|
||||
|
||||
st.nextToken(); // Discard first token, it's basically what we have send
|
||||
final String command = st.nextToken().trim().toUpperCase();
|
||||
|
||||
if (command.equals("USERID") && (st.countTokens() >= 2)) {
|
||||
successful = true;
|
||||
hostType = st.nextToken().trim();
|
||||
userName = st.nextToken("").substring(1);// Get all that is left
|
||||
} else if (command.equals("ERROR")) {
|
||||
errorCode = ERR_PROTOCOL;
|
||||
errorMessage = st.nextToken();
|
||||
} else {
|
||||
errorCode = ERR_PROTOCOL_INCORRECT;
|
||||
System.out.println("Opa!");
|
||||
errorMessage = "Can't parse server response.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////
|
||||
// USED for Testing
|
||||
/*
|
||||
* public static void main(String[] args) throws IOException{
|
||||
*
|
||||
* Socket s = null; s = new Socket("gp101-16", 1391);
|
||||
*
|
||||
* Ident id = new Ident(s); if(id.successful){
|
||||
* System.out.println("User: "+id.userName);
|
||||
* System.out.println("HostType: "+id.hostType); }else{
|
||||
* System.out.println("ErrorCode: "+id.errorCode);
|
||||
* System.out.println("ErrorMessage: "+id.errorMessage);
|
||||
*
|
||||
* }
|
||||
*
|
||||
* if(s!= null) s.close(); } //
|
||||
*/
|
||||
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.InetRange;
|
||||
import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* An implementation of socks.ServerAuthentication which provides simple
|
||||
* authentication based on the host from which the connection is made and the
|
||||
* name of the user on the remote machine, as reported by identd daemon on the
|
||||
* remote machine.
|
||||
* <p>
|
||||
* It can also be used to provide authentication based only on the contacting
|
||||
* host address.
|
||||
*/
|
||||
|
||||
public class IdentAuthenticator extends ServerAuthenticatorBase {
|
||||
/**
|
||||
* Vector of InetRanges
|
||||
*/
|
||||
Vector<InetRange> hosts;
|
||||
|
||||
/**
|
||||
* Vector of user hashes
|
||||
*/
|
||||
Vector<Hashtable<?, ?>> users;
|
||||
|
||||
String user;
|
||||
|
||||
/**
|
||||
* Constructs empty IdentAuthenticator.
|
||||
*/
|
||||
public IdentAuthenticator() {
|
||||
hosts = new Vector<InetRange>();
|
||||
users = new Vector<Hashtable<?, ?>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create instances returned from startSession.
|
||||
*
|
||||
* @param in Input stream.
|
||||
* @param out OutputStream.
|
||||
* @param user Username associated with this connection,could be null if name
|
||||
* was not required.
|
||||
*/
|
||||
IdentAuthenticator(InputStream in, OutputStream out, String user) {
|
||||
super(in, out);
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds range of addresses from which connection is allowed. Hashtable users
|
||||
* should contain user names as keys and anything as values (value is not
|
||||
* used and will be ignored).
|
||||
*
|
||||
* @param hostRange Range of ip addresses from which connection is allowed.
|
||||
* @param users Hashtable of users for whom connection is allowed, or null to
|
||||
* indicate that anybody is allowed to connect from the hosts
|
||||
* within given range.
|
||||
*/
|
||||
public synchronized void add(InetRange hostRange, Hashtable<?, ?> users) {
|
||||
this.hosts.addElement(hostRange);
|
||||
this.users.addElement(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants permission only to those users, who connect from one of the hosts
|
||||
* registered with add(InetRange,Hashtable) and whose names, as reported by
|
||||
* identd daemon, are listed for the host the connection came from.
|
||||
*/
|
||||
public ServerAuthenticator startSession(Socket s) throws IOException {
|
||||
|
||||
final int ind = getRangeIndex(s.getInetAddress());
|
||||
String user = null;
|
||||
|
||||
// System.out.println("getRangeReturned:"+ind);
|
||||
|
||||
if (ind < 0) {
|
||||
return null; // Host is not on the list.
|
||||
}
|
||||
|
||||
final ServerAuthenticator serverAuthenticator = super.startSession(s);
|
||||
final ServerAuthenticatorBase auth = (ServerAuthenticatorBase) serverAuthenticator;
|
||||
|
||||
// System.out.println("super.startSession() returned:"+auth);
|
||||
if (auth == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// do the authentication
|
||||
|
||||
final Hashtable<?, ?> user_names = users.elementAt(ind);
|
||||
|
||||
if (user_names != null) { // If need to do authentication
|
||||
Ident ident;
|
||||
ident = new Ident(s);
|
||||
// If can't obtain user name, fail
|
||||
if (!ident.successful) {
|
||||
return null;
|
||||
}
|
||||
// If user name is not listed for this address, fail
|
||||
if (!user_names.containsKey(ident.userName)) {
|
||||
return null;
|
||||
}
|
||||
user = ident.userName;
|
||||
}
|
||||
return new IdentAuthenticator(auth.in, auth.out, user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* For SOCKS5 requests allways returns true. For SOCKS4 requests checks
|
||||
* wether the user name supplied in the request corresponds to the name
|
||||
* obtained from the ident daemon.
|
||||
*/
|
||||
public boolean checkRequest(ProxyMessage msg, java.net.Socket s) {
|
||||
// If it's version 5 request, or if anybody is permitted, return true;
|
||||
if ((msg.version == 5) || (user == null)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.version != 4) {
|
||||
return false; // Who knows?
|
||||
}
|
||||
|
||||
return user.equals(msg.user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get String representaion of the IdentAuthenticator.
|
||||
*/
|
||||
public String toString() {
|
||||
String s = "";
|
||||
|
||||
for (int i = 0; i < hosts.size(); ++i) {
|
||||
s += "(Range:" + hosts.elementAt(i) + "," + //
|
||||
" Users:" + userNames(i) + ") ";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
// ////////////////
|
||||
private int getRangeIndex(InetAddress ip) {
|
||||
int index = 0;
|
||||
final Enumeration<InetRange> enumx = hosts.elements();
|
||||
while (enumx.hasMoreElements()) {
|
||||
final InetRange ir = enumx.nextElement();
|
||||
if (ir.contains(ip)) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
private String userNames(int i) {
|
||||
if (users.elementAt(i) == null) {
|
||||
return "Everybody is permitted.";
|
||||
}
|
||||
|
||||
final Enumeration<?> enumx = ((Hashtable<?, ?>) users.elementAt(i))
|
||||
.keys();
|
||||
if (!enumx.hasMoreElements()) {
|
||||
return "";
|
||||
}
|
||||
String s = enumx.nextElement().toString();
|
||||
while (enumx.hasMoreElements()) {
|
||||
s += "; " + enumx.nextElement();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
|
||||
import com.runjva.sourceforge.jsocks.protocol.UDPEncapsulation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* Classes implementing this interface should provide socks server with
|
||||
* authentication and authorization of users.
|
||||
**/
|
||||
public interface ServerAuthenticator {
|
||||
|
||||
/**
|
||||
* This method is called when a new connection accepted by the server.
|
||||
* <p>
|
||||
* At this point no data have been extracted from the connection. It is
|
||||
* responsibility of this method to ensure that the next byte in the stream
|
||||
* after this method have been called is the first byte of the socks request
|
||||
* message. For SOCKSv4 there is no authentication data and the first byte
|
||||
* in the stream is part of the request. With SOCKSv5 however there is an
|
||||
* authentication data first. It is expected that implementaions will
|
||||
* process this authentication data.
|
||||
* <p>
|
||||
* If authentication was successful an instance of ServerAuthentication
|
||||
* should be returned, it later will be used by the server to perform
|
||||
* authorization and some other things. If authentication fails null should
|
||||
* be returned, or an exception may be thrown.
|
||||
*
|
||||
* @param s Accepted Socket.
|
||||
* @return An instance of ServerAuthenticator to be used for this connection
|
||||
* or null
|
||||
*/
|
||||
ServerAuthenticator startSession(Socket s) throws IOException;
|
||||
|
||||
/**
|
||||
* This method should return input stream which should be used on the
|
||||
* accepted socket.
|
||||
* <p>
|
||||
* SOCKSv5 allows to have multiple authentication methods, and these methods
|
||||
* might require some kind of transformations being made on the data.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*/
|
||||
InputStream getInputStream();
|
||||
|
||||
/**
|
||||
* This method should return output stream to use to write to the accepted
|
||||
* socket.
|
||||
* <p>
|
||||
* SOCKSv5 allows to have multiple authentication methods, and these methods
|
||||
* might require some kind of transformations being made on the data.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*/
|
||||
OutputStream getOutputStream();
|
||||
|
||||
/**
|
||||
* This method should return UDPEncapsulation, which should be used on the
|
||||
* datagrams being send in/out.
|
||||
* <p>
|
||||
* If no transformation should be done on the datagrams, this method should
|
||||
* return null.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*/
|
||||
|
||||
UDPEncapsulation getUdpEncapsulation();
|
||||
|
||||
/**
|
||||
* This method is called when a request have been read.
|
||||
* <p>
|
||||
* Implementation should decide wether to grant request or not. Returning
|
||||
* true implies granting the request, false means request should be
|
||||
* rejected.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*
|
||||
* @param msg Request message.
|
||||
* @return true to grant request, false to reject it.
|
||||
*/
|
||||
boolean checkRequest(ProxyMessage msg);
|
||||
|
||||
/**
|
||||
* This method is called when datagram is received by the server.
|
||||
* <p>
|
||||
* Implementaions should decide wether it should be forwarded or dropped. It
|
||||
* is expecteed that implementation will use datagram address and port
|
||||
* information to make a decision, as well as anything else. Address and
|
||||
* port of the datagram are always correspond to remote machine. It is
|
||||
* either destination or source address. If out is true address is
|
||||
* destination address, else it is a source address, address of the machine
|
||||
* from which datagram have been received for the client.
|
||||
* <p>
|
||||
* Implementaions should return true if the datagram is to be forwarded, and
|
||||
* false if the datagram should be dropped.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*
|
||||
* @param out If true the datagram is being send out(from the client),
|
||||
* otherwise it is an incoming datagram.
|
||||
* @return True to forward datagram false drop it silently.
|
||||
*/
|
||||
boolean checkRequest(DatagramPacket dp, boolean out);
|
||||
|
||||
/**
|
||||
* This method is called when session is completed. Either due to normal
|
||||
* termination or due to any error condition.
|
||||
* <p>
|
||||
* This method is called on the object returned from the startSession
|
||||
* function.
|
||||
*/
|
||||
void endSession();
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.ProxyMessage;
|
||||
import com.runjva.sourceforge.jsocks.protocol.UDPEncapsulation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* An implementation of ServerAuthenticator, which does <b>not</b> do any
|
||||
* authentication.
|
||||
* <p>
|
||||
* <FONT size="+3" color ="FF0000"> Warning!!</font><br>
|
||||
* Should not be used on machines which are not behind the firewall.
|
||||
* <p>
|
||||
* It is only provided to make implementing other authentication schemes easier.
|
||||
* <br>
|
||||
* For Example: <tt><pre>
|
||||
* class MyAuth extends socks.server.ServerAuthenticator{
|
||||
* ...
|
||||
* public ServerAuthenticator startSession(java.net.Socket s){
|
||||
* if(!checkHost(s.getInetAddress()) return null;
|
||||
* return super.startSession(s);
|
||||
* }
|
||||
* <p>
|
||||
* boolean checkHost(java.net.Inetaddress addr){
|
||||
* boolean allow;
|
||||
* //Do it somehow
|
||||
* return allow;
|
||||
* }
|
||||
* }
|
||||
* </pre></tt>
|
||||
*/
|
||||
public abstract class ServerAuthenticatorBase implements ServerAuthenticator {
|
||||
|
||||
static final byte[] socks5response = {5, 0};
|
||||
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
|
||||
/**
|
||||
* Creates new instance of the ServerAuthenticatorNone.
|
||||
*/
|
||||
public ServerAuthenticatorBase() {
|
||||
this.in = null;
|
||||
this.out = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new ServerAuthenticatorNone object suitable for returning from
|
||||
* the startSession function.
|
||||
*
|
||||
* @param in Input stream to return from getInputStream method.
|
||||
* @param out Output stream to return from getOutputStream method.
|
||||
*/
|
||||
public ServerAuthenticatorBase(InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants access to everyone.Removes authentication related bytes from the
|
||||
* stream, when a SOCKS5 connection is being made, selects an authentication
|
||||
* NONE.
|
||||
*/
|
||||
public ServerAuthenticator startSession(Socket s) throws IOException {
|
||||
|
||||
final PushbackInputStream in = new PushbackInputStream(s
|
||||
.getInputStream());
|
||||
final OutputStream out = s.getOutputStream();
|
||||
|
||||
final int version = in.read();
|
||||
if (version == 5) {
|
||||
if (!selectSocks5Authentication(in, out, 0)) {
|
||||
return null;
|
||||
}
|
||||
} else if (version == 4) {
|
||||
// Else it is the request message already, version 4
|
||||
in.unread(version);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ServerAuthenticatorNone(in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input stream.
|
||||
*
|
||||
* @return Input stream speciefied in the constructor.
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output stream.
|
||||
*
|
||||
* @return Output stream speciefied in the constructor.
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allways returns null.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public UDPEncapsulation getUdpEncapsulation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allways returns true.
|
||||
*/
|
||||
public boolean checkRequest(ProxyMessage msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allways returns true.
|
||||
*/
|
||||
public boolean checkRequest(java.net.DatagramPacket dp, boolean out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing.
|
||||
*/
|
||||
public void endSession() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convinience routine for selecting SOCKSv5 authentication.
|
||||
* <p>
|
||||
* This method reads in authentication methods that client supports, checks
|
||||
* wether it supports given method. If it does, the notification method is
|
||||
* written back to client, that this method have been chosen for
|
||||
* authentication. If given method was not found, authentication failure
|
||||
* message is send to client ([5,FF]).
|
||||
*
|
||||
* @param in Input stream, version byte should be removed from the stream
|
||||
* before calling this method.
|
||||
* @param out Output stream.
|
||||
* @param methodId Method which should be selected.
|
||||
* @return true if methodId was found, false otherwise.
|
||||
*/
|
||||
static public boolean selectSocks5Authentication(InputStream in,
|
||||
OutputStream out, int methodId) throws IOException {
|
||||
|
||||
final int num_methods = in.read();
|
||||
if (num_methods <= 0) {
|
||||
return false;
|
||||
}
|
||||
final byte method_ids[] = new byte[num_methods];
|
||||
final byte response[] = new byte[2];
|
||||
boolean found = false;
|
||||
|
||||
response[0] = (byte) 5; // SOCKS version
|
||||
response[1] = (byte) 0xFF; // Not found, we are pessimistic
|
||||
|
||||
int bread = 0; // bytes read so far
|
||||
while (bread < num_methods) {
|
||||
bread += in.read(method_ids, bread, num_methods - bread);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_methods; ++i) {
|
||||
if (method_ids[i] == methodId) {
|
||||
found = true;
|
||||
response[1] = (byte) methodId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out.write(response);
|
||||
return found;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Simplest possible ServerAuthenticator implementation. Extends common base.
|
||||
*/
|
||||
public class ServerAuthenticatorNone extends ServerAuthenticatorBase {
|
||||
|
||||
public ServerAuthenticatorNone(InputStream in, OutputStream out) {
|
||||
super(in, out);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* This class implements SOCKS5 User/Password authentication scheme as defined
|
||||
* in rfc1929,the server side of it. (see docs/rfc1929.txt)
|
||||
*/
|
||||
public class UserPasswordAuthenticator extends ServerAuthenticatorBase {
|
||||
|
||||
static final int METHOD_ID = 2;
|
||||
|
||||
UserValidation validator;
|
||||
|
||||
/**
|
||||
* Construct a new UserPasswordAuthentication object, with given
|
||||
* UserVlaidation scheme.
|
||||
*
|
||||
* @param v UserValidation to use for validating users.
|
||||
*/
|
||||
public UserPasswordAuthenticator(UserValidation validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public ServerAuthenticator startSession(Socket s) throws IOException {
|
||||
final InputStream in = s.getInputStream();
|
||||
final OutputStream out = s.getOutputStream();
|
||||
|
||||
if (in.read() != 5) {
|
||||
return null; // Drop non version 5 messages.
|
||||
}
|
||||
|
||||
if (!selectSocks5Authentication(in, out, METHOD_ID)) {
|
||||
return null;
|
||||
}
|
||||
if (!doUserPasswordAuthentication(s, in, out)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ServerAuthenticatorNone(in, out);
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
// ////////////////
|
||||
|
||||
private boolean doUserPasswordAuthentication(Socket s, InputStream in,
|
||||
OutputStream out) throws IOException {
|
||||
final int version = in.read();
|
||||
if (version != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int ulen = in.read();
|
||||
if (ulen < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] user = new byte[ulen];
|
||||
in.read(user);
|
||||
final int plen = in.read();
|
||||
if (plen < 0) {
|
||||
return false;
|
||||
}
|
||||
final byte[] password = new byte[plen];
|
||||
in.read(password);
|
||||
|
||||
if (validator.isUserValid(new String(user), new String(password), s)) {
|
||||
// System.out.println("user valid");
|
||||
out.write(new byte[]{1, 0});
|
||||
} else {
|
||||
// System.out.println("user invalid");
|
||||
out.write(new byte[]{1, 1});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.runjva.sourceforge.jsocks.server;
|
||||
|
||||
/**
|
||||
* Interface which provides for user validation, based on user name password and
|
||||
* where it connects from.
|
||||
*/
|
||||
public interface UserValidation {
|
||||
/**
|
||||
* Implementations of this interface are expected to use some or all of the
|
||||
* information provided plus any information they can extract from other
|
||||
* sources to decide wether given user should be allowed access to SOCKS
|
||||
* server, or whatever you use it for.
|
||||
*
|
||||
* @param username User whom implementation should validate.
|
||||
* @param password Password this user provided.
|
||||
* @param connection Socket which user used to connect to the server.
|
||||
* @return true to indicate user is valid, false otherwise.
|
||||
*/
|
||||
boolean isUserValid(String username, String password,
|
||||
java.net.Socket connection);
|
||||
}
|
105
jsocks/src/main/resources/socks.properties
Normal file
105
jsocks/src/main/resources/socks.properties
Normal file
|
@ -0,0 +1,105 @@
|
|||
#SOCKS server initialisation file.
|
||||
|
||||
|
||||
#Port on which the socks server should run
|
||||
#If not set deafults to 1080
|
||||
port = 1080
|
||||
|
||||
#Timeout settings for the SOCKS server,in milliseconds.
|
||||
#If not defined, all default to 3 minutes(18000ms).
|
||||
#
|
||||
# iddleTimeout If no data received from/to user during this interval
|
||||
# connection will be aborted.
|
||||
# acceptTimeout If no connection is accepted during this interval,
|
||||
# connection will be aborted.
|
||||
# udpTimeout If no datagrams are received from/to user, in this interval
|
||||
# UDP relay server stops, and control connection is closed.
|
||||
# Any of these can be 0, implying infinit timeout, that is once the
|
||||
# connection is made, it is kept alive until one of the parties closes it.
|
||||
# In case of the BIND command, it implies that server will be listenning
|
||||
# for incoming connection until it is accepted, or until client closes
|
||||
# control connection.
|
||||
# For UDP servers it implies, that they will run as long, as client
|
||||
# keeps control connection open.
|
||||
|
||||
iddleTimeout = 600000 # 10 minutes
|
||||
acceptTimeout = 60000 # 1 minute
|
||||
udpTimeout = 600000 # 10 minutes
|
||||
|
||||
#datagramSize -- Size of the datagrams to use for udp relaying.
|
||||
#Defaults to 64K bytes(0xFFFF = 65535 a bit more than maximum possible size).
|
||||
#datagramSize = 8192
|
||||
|
||||
#log -- Name of the file, to which logging should be done
|
||||
# If log is - (minus sine) do logging to standart output.
|
||||
#Optional field, if not defined, no logging is done.
|
||||
#
|
||||
|
||||
#log = -
|
||||
|
||||
#host -- Host on which to run, for multihomed hosts,
|
||||
# Default -- all(system dependent)
|
||||
#host = some.hostOfMine.com
|
||||
|
||||
#range -- Semicolon(;) separated range of addresses, from which
|
||||
#connections should be accepted.
|
||||
#
|
||||
# Range could be one of those
|
||||
# 1. Stand alone host name -- some.host.com or 33.33.44.101
|
||||
# 2. Host range
|
||||
# .my.domain.net
|
||||
# 190.220.34.
|
||||
# host1:host2
|
||||
# 33.44.100:33.44.200
|
||||
#
|
||||
# Example: .myDomain.com;100.220.30.;myHome.host.com;\
|
||||
# comp1.myWork.com:comp10.myWork.com
|
||||
#
|
||||
# This will include all computers in the domain myDomain.com,
|
||||
# all computers whose addresses start with 100.200.30,
|
||||
# host with the name myHome.host.com,
|
||||
# and computers comp1 through to comp2 in the domain myWork.com,
|
||||
# assuming their names correspond to there ip addresses.
|
||||
#
|
||||
# NOTE: Dot(.) by itself implies all hosts, be sure not to include
|
||||
# one of those.
|
||||
|
||||
|
||||
range = localhost;130.220.
|
||||
|
||||
#users
|
||||
# Semicolon(;) separated list of users, for whom permissions should be
|
||||
# granted, given they connect from one of the hosts speciefied by range.
|
||||
# This field is optional, if not defined, ANY user will be allowed to use
|
||||
# SOCKS server, given he\she is connecting from one of the hosts in the
|
||||
# range.
|
||||
# NOTE: Whitespaces are not ignored (except for the first name, it's how java
|
||||
# parses Property files).
|
||||
# User names are CASESenSitive.
|
||||
# You have been warned.
|
||||
# NOTE2: Misspelling users with Users, or anything, will be understood as
|
||||
# if users were not defined, and hence will imply that ANYBODY, will
|
||||
# be granted access from the hosts in the range.
|
||||
#
|
||||
|
||||
users = kirillka;cis-dude;root
|
||||
|
||||
# Proxy configurations, that is what proxy this proxy should use, if any.
|
||||
# proxy should be a semicolon(;) separated list of proxy entries.
|
||||
# Each entry shoud be in the form: host[:port:user:password].
|
||||
# If only host is supplied SOCKSv5 proxy is assumed, running on port 1080.
|
||||
# If user is supplied, but password not supplied, SOCKSv4 is assumed,
|
||||
# running oon machine 'host' on port 'port', user name will be supplied as
|
||||
# authentication for that proxy.
|
||||
# If both user and password is supplied, SOCKSv5 proxy is assumed, with
|
||||
# user/password authentication.
|
||||
#
|
||||
# directHosts should contain ;-separated list of inetaddress and ranges.
|
||||
# These machines will be addressed directly rather than through
|
||||
# the proxy. See range for more details, what sort of entries
|
||||
# permitted and understood.
|
||||
|
||||
|
||||
|
||||
proxy = www-proxy:1080
|
||||
directHosts = 130.220.;.unisa.edu.au;localhost
|
32
jtorctl/LICENSE
Normal file
32
jtorctl/LICENSE
Normal file
|
@ -0,0 +1,32 @@
|
|||
===============================================================================
|
||||
The Tor controller demonstration code is distributed under this license:
|
||||
|
||||
Copyright (c) 2005, Nick Mathewson, Roger Dingledine
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the names of the copyright owners nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
0
jtorctl/README
Normal file
0
jtorctl/README
Normal file
31
jtorctl/pom.xml
Normal file
31
jtorctl/pom.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<version>0.3.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jtorctl</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
127
jtorctl/src/main/java/net/freehaven/tor/control/Bytes.java
Normal file
127
jtorctl/src/main/java/net/freehaven/tor/control/Bytes.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Static class to do bytewise structure manipulation in Java.
|
||||
*/
|
||||
/* XXXX There must be a better way to do most of this.
|
||||
* XXXX The string logic here uses default encoding, which is stupid.
|
||||
*/
|
||||
final class Bytes {
|
||||
|
||||
/**
|
||||
* Write the two-byte value in 's' into the byte array 'ba', starting at
|
||||
* the index 'pos'.
|
||||
*/
|
||||
public static void setU16(byte[] ba, int pos, short s) {
|
||||
ba[pos] = (byte) ((s >> 8) & 0xff);
|
||||
ba[pos + 1] = (byte) ((s) & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the four-byte value in 'i' into the byte array 'ba', starting at
|
||||
* the index 'pos'.
|
||||
*/
|
||||
public static void setU32(byte[] ba, int pos, int i) {
|
||||
ba[pos] = (byte) ((i >> 24) & 0xff);
|
||||
ba[pos + 1] = (byte) ((i >> 16) & 0xff);
|
||||
ba[pos + 2] = (byte) ((i >> 8) & 0xff);
|
||||
ba[pos + 3] = (byte) ((i) & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the four-byte value starting at index 'pos' within 'ba'
|
||||
*/
|
||||
public static int getU32(byte[] ba, int pos) {
|
||||
return
|
||||
((ba[pos] & 0xff) << 24) |
|
||||
((ba[pos + 1] & 0xff) << 16) |
|
||||
((ba[pos + 2] & 0xff) << 8) |
|
||||
((ba[pos + 3] & 0xff));
|
||||
}
|
||||
|
||||
public static String getU32S(byte[] ba, int pos) {
|
||||
return String.valueOf((getU32(ba, pos)) & 0xffffffffL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the two-byte value starting at index 'pos' within 'ba'
|
||||
*/
|
||||
public static int getU16(byte[] ba, int pos) {
|
||||
return
|
||||
((ba[pos] & 0xff) << 8) |
|
||||
((ba[pos + 1] & 0xff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string starting at position 'pos' of ba and extending
|
||||
* until a zero byte or the end of the string.
|
||||
*/
|
||||
public static String getNulTerminatedStr(byte[] ba, int pos) {
|
||||
int len, maxlen = ba.length - pos;
|
||||
for (len = 0; len < maxlen; ++len) {
|
||||
if (ba[pos + len] == 0)
|
||||
break;
|
||||
}
|
||||
return new String(ba, pos, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from 'ba' starting at 'pos', dividing them into strings
|
||||
* along the character in 'split' and writing them into 'lst'
|
||||
*/
|
||||
public static void splitStr(List<String> lst, byte[] ba, int pos, byte split) {
|
||||
while (pos < ba.length && ba[pos] != 0) {
|
||||
int len;
|
||||
for (len = 0; pos + len < ba.length; ++len) {
|
||||
if (ba[pos + len] == 0 || ba[pos + len] == split)
|
||||
break;
|
||||
}
|
||||
if (len > 0)
|
||||
lst.add(new String(ba, pos, len));
|
||||
pos += len;
|
||||
if (ba[pos] == split)
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from 'ba' starting at 'pos', dividing them into strings
|
||||
* along the character in 'split' and writing them into 'lst'
|
||||
*/
|
||||
public static List<String> splitStr(List<String> lst, String str) {
|
||||
// split string on spaces, include trailing/leading
|
||||
String[] tokenArray = str.split(" ", -1);
|
||||
if (lst == null) {
|
||||
lst = Arrays.asList(tokenArray);
|
||||
} else {
|
||||
lst.addAll(Arrays.asList(tokenArray));
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
private static final char[] NYBBLES = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
public static final String hex(byte[] ba) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < ba.length; ++i) {
|
||||
int b = (ba[i]) & 0xff;
|
||||
buf.append(NYBBLES[b >> 4]);
|
||||
buf.append(NYBBLES[b & 0x0f]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Bytes() {
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* A single key-value pair from Tor's configuration.
|
||||
*/
|
||||
public class ConfigEntry {
|
||||
public ConfigEntry(String k, String v) {
|
||||
key = k;
|
||||
value = v;
|
||||
is_default = false;
|
||||
}
|
||||
|
||||
public ConfigEntry(String k) {
|
||||
key = k;
|
||||
value = "";
|
||||
is_default = true;
|
||||
}
|
||||
|
||||
public final String key;
|
||||
public final String value;
|
||||
public final boolean is_default;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* Abstract interface whose methods are invoked when Tor sends us an event.
|
||||
*
|
||||
* @see TorControlConnection#setEventHandler
|
||||
* @see TorControlConnection#setEvents
|
||||
*/
|
||||
public interface EventHandler {
|
||||
/**
|
||||
* Invoked when a circuit's status has changed.
|
||||
* Possible values for <b>status</b> are:
|
||||
* <ul>
|
||||
* <li>"LAUNCHED" : circuit ID assigned to new circuit</li>
|
||||
* <li>"BUILT" : all hops finished, can now accept streams</li>
|
||||
* <li>"EXTENDED" : one more hop has been completed</li>
|
||||
* <li>"FAILED" : circuit closed (was not built)</li>
|
||||
* <li>"CLOSED" : circuit closed (was built)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <b>circID</b> is the alphanumeric identifier of the affected circuit,
|
||||
* and <b>path</b> is a comma-separated list of alphanumeric ServerIDs.
|
||||
*/
|
||||
public void circuitStatus(String status, String circID, String path);
|
||||
|
||||
/**
|
||||
* Invoked when a stream's status has changed.
|
||||
* Possible values for <b>status</b> are:
|
||||
* <ul>
|
||||
* <li>"NEW" : New request to connect</li>
|
||||
* <li>"NEWRESOLVE" : New request to resolve an address</li>
|
||||
* <li>"SENTCONNECT" : Sent a connect cell along a circuit</li>
|
||||
* <li>"SENTRESOLVE" : Sent a resolve cell along a circuit</li>
|
||||
* <li>"SUCCEEDED" : Received a reply; stream established</li>
|
||||
* <li>"FAILED" : Stream failed and not retriable.</li>
|
||||
* <li>"CLOSED" : Stream closed</li>
|
||||
* <li>"DETACHED" : Detached from circuit; still retriable.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <b>streamID</b> is the alphanumeric identifier of the affected stream,
|
||||
* and its <b>target</b> is specified as address:port.
|
||||
*/
|
||||
public void streamStatus(String status, String streamID, String target);
|
||||
|
||||
/**
|
||||
* Invoked when the status of a connection to an OR has changed.
|
||||
* Possible values for <b>status</b> are ["LAUNCHED" | "CONNECTED" | "FAILED" | "CLOSED"].
|
||||
* <b>orName</b> is the alphanumeric identifier of the OR affected.
|
||||
*/
|
||||
public void orConnStatus(String status, String orName);
|
||||
|
||||
/**
|
||||
* Invoked once per second. <b>read</b> and <b>written</b> are
|
||||
* the number of bytes read and written, respectively, in
|
||||
* the last second.
|
||||
*/
|
||||
public void bandwidthUsed(long read, long written);
|
||||
|
||||
/**
|
||||
* Invoked whenever Tor learns about new ORs. The <b>orList</b> object
|
||||
* contains the alphanumeric ServerIDs associated with the new ORs.
|
||||
*/
|
||||
public void newDescriptors(java.util.List<String> orList);
|
||||
|
||||
/**
|
||||
* Invoked when Tor logs a message.
|
||||
* <b>severity</b> is one of ["DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERR"],
|
||||
* and <b>msg</b> is the message string.
|
||||
*/
|
||||
public void message(String severity, String msg);
|
||||
|
||||
/**
|
||||
* Invoked when an unspecified message is received.
|
||||
* <type> is the message type, and <msg> is the message string.
|
||||
*/
|
||||
public void unrecognized(String type, String msg);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* Implementation of EventHandler that ignores all events. Useful
|
||||
* when you only want to override one method.
|
||||
*/
|
||||
public class NullEventHandler implements EventHandler {
|
||||
public void circuitStatus(String status, String circID, String path) {
|
||||
}
|
||||
|
||||
public void streamStatus(String status, String streamID, String target) {
|
||||
}
|
||||
|
||||
public void orConnStatus(String status, String orName) {
|
||||
}
|
||||
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
}
|
||||
|
||||
public void newDescriptors(java.util.List<String> orList) {
|
||||
}
|
||||
|
||||
public void message(String severity, String msg) {
|
||||
}
|
||||
|
||||
public void unrecognized(String type, String msg) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* A hashed digest of a secret password (used to set control connection
|
||||
* security.)
|
||||
* <p>
|
||||
* For the actual hashing algorithm, see RFC2440's secret-to-key conversion.
|
||||
*/
|
||||
public class PasswordDigest {
|
||||
|
||||
private final byte[] secret;
|
||||
private final String hashedKey;
|
||||
|
||||
/**
|
||||
* Return a new password digest with a random secret and salt.
|
||||
*/
|
||||
public static PasswordDigest generateDigest() {
|
||||
byte[] secret = new byte[20];
|
||||
SecureRandom rng = new SecureRandom();
|
||||
rng.nextBytes(secret);
|
||||
return new PasswordDigest(secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new password digest with a given secret and random salt
|
||||
*/
|
||||
public PasswordDigest(byte[] secret) {
|
||||
this(secret, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new password digest with a given secret and random salt.
|
||||
* Note that the 9th byte of the specifier determines the number of hash
|
||||
* iterations as in RFC2440.
|
||||
*/
|
||||
public PasswordDigest(byte[] secret, byte[] specifier) {
|
||||
this.secret = secret.clone();
|
||||
if (specifier == null) {
|
||||
specifier = new byte[9];
|
||||
SecureRandom rng = new SecureRandom();
|
||||
rng.nextBytes(specifier);
|
||||
specifier[8] = 96;
|
||||
}
|
||||
hashedKey = "16:" + encodeBytes(secretToKey(secret, specifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the secret used to generate this password hash.
|
||||
*/
|
||||
public byte[] getSecret() {
|
||||
return secret.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hashed password in the format used by Tor.
|
||||
*/
|
||||
public String getHashedPassword() {
|
||||
return hashedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter used by RFC2440's s2k algorithm.
|
||||
*/
|
||||
private static final int EXPBIAS = 6;
|
||||
|
||||
/**
|
||||
* Implement rfc2440 s2k
|
||||
*/
|
||||
public static byte[] secretToKey(byte[] secret, byte[] specifier) {
|
||||
MessageDigest d;
|
||||
try {
|
||||
d = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new RuntimeException("Can't run without sha-1.");
|
||||
}
|
||||
int c = (specifier[8]) & 0xff;
|
||||
int count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS);
|
||||
|
||||
byte[] tmp = new byte[8 + secret.length];
|
||||
System.arraycopy(specifier, 0, tmp, 0, 8);
|
||||
System.arraycopy(secret, 0, tmp, 8, secret.length);
|
||||
while (count > 0) {
|
||||
if (count >= tmp.length) {
|
||||
d.update(tmp);
|
||||
count -= tmp.length;
|
||||
} else {
|
||||
d.update(tmp, 0, count);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
byte[] key = new byte[20 + 9];
|
||||
System.arraycopy(d.digest(), 0, key, 9, 20);
|
||||
System.arraycopy(specifier, 0, key, 0, 9);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hexadecimal encoding of a byte array.
|
||||
*/
|
||||
// XXX There must be a better way to do this in Java.
|
||||
private static final String encodeBytes(byte[] ba) {
|
||||
return Bytes.hex(ba);
|
||||
}
|
||||
|
||||
}
|
||||
|
4
jtorctl/src/main/java/net/freehaven/tor/control/README
Normal file
4
jtorctl/src/main/java/net/freehaven/tor/control/README
Normal file
|
@ -0,0 +1,4 @@
|
|||
We broke the version detection stuff in Tor 0.1.2.16 / 0.2.0.4-alpha.
|
||||
Somebody should rip out the v0 control protocol stuff from here, and
|
||||
it should start working again. -RD
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
/**
|
||||
* Interface defining constants used by the Tor controller protocol.
|
||||
*/
|
||||
// XXXX Take documentation for these from control-spec.txt
|
||||
public interface TorControlCommands {
|
||||
|
||||
public static final short CMD_ERROR = 0x0000;
|
||||
public static final short CMD_DONE = 0x0001;
|
||||
public static final short CMD_SETCONF = 0x0002;
|
||||
public static final short CMD_GETCONF = 0x0003;
|
||||
public static final short CMD_CONFVALUE = 0x0004;
|
||||
public static final short CMD_SETEVENTS = 0x0005;
|
||||
public static final short CMD_EVENT = 0x0006;
|
||||
public static final short CMD_AUTH = 0x0007;
|
||||
public static final short CMD_SAVECONF = 0x0008;
|
||||
public static final short CMD_SIGNAL = 0x0009;
|
||||
public static final short CMD_MAPADDRESS = 0x000A;
|
||||
public static final short CMD_GETINFO = 0x000B;
|
||||
public static final short CMD_INFOVALUE = 0x000C;
|
||||
public static final short CMD_EXTENDCIRCUIT = 0x000D;
|
||||
public static final short CMD_ATTACHSTREAM = 0x000E;
|
||||
public static final short CMD_POSTDESCRIPTOR = 0x000F;
|
||||
public static final short CMD_FRAGMENTHEADER = 0x0010;
|
||||
public static final short CMD_FRAGMENT = 0x0011;
|
||||
public static final short CMD_REDIRECTSTREAM = 0x0012;
|
||||
public static final short CMD_CLOSESTREAM = 0x0013;
|
||||
public static final short CMD_CLOSECIRCUIT = 0x0014;
|
||||
|
||||
public static final String[] CMD_NAMES = {
|
||||
"ERROR",
|
||||
"DONE",
|
||||
"SETCONF",
|
||||
"GETCONF",
|
||||
"CONFVALUE",
|
||||
"SETEVENTS",
|
||||
"EVENT",
|
||||
"AUTH",
|
||||
"SAVECONF",
|
||||
"SIGNAL",
|
||||
"MAPADDRESS",
|
||||
"GETINFO",
|
||||
"INFOVALUE",
|
||||
"EXTENDCIRCUIT",
|
||||
"ATTACHSTREAM",
|
||||
"POSTDESCRIPTOR",
|
||||
"FRAGMENTHEADER",
|
||||
"FRAGMENT",
|
||||
"REDIRECTSTREAM",
|
||||
"CLOSESTREAM",
|
||||
"CLOSECIRCUIT",
|
||||
};
|
||||
|
||||
public static final short EVENT_CIRCSTATUS = 0x0001;
|
||||
public static final short EVENT_STREAMSTATUS = 0x0002;
|
||||
public static final short EVENT_ORCONNSTATUS = 0x0003;
|
||||
public static final short EVENT_BANDWIDTH = 0x0004;
|
||||
public static final short EVENT_NEWDESCRIPTOR = 0x0006;
|
||||
public static final short EVENT_MSG_DEBUG = 0x0007;
|
||||
public static final short EVENT_MSG_INFO = 0x0008;
|
||||
public static final short EVENT_MSG_NOTICE = 0x0009;
|
||||
public static final short EVENT_MSG_WARN = 0x000A;
|
||||
public static final short EVENT_MSG_ERROR = 0x000B;
|
||||
|
||||
public static final String[] EVENT_NAMES = {
|
||||
"(0)",
|
||||
"CIRC",
|
||||
"STREAM",
|
||||
"ORCONN",
|
||||
"BW",
|
||||
"OLDLOG",
|
||||
"NEWDESC",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"NOTICE",
|
||||
"WARN",
|
||||
"ERR",
|
||||
};
|
||||
|
||||
public static final byte CIRC_STATUS_LAUNCHED = 0x01;
|
||||
public static final byte CIRC_STATUS_BUILT = 0x02;
|
||||
public static final byte CIRC_STATUS_EXTENDED = 0x03;
|
||||
public static final byte CIRC_STATUS_FAILED = 0x04;
|
||||
public static final byte CIRC_STATUS_CLOSED = 0x05;
|
||||
|
||||
public static final String[] CIRC_STATUS_NAMES = {
|
||||
"LAUNCHED",
|
||||
"BUILT",
|
||||
"EXTENDED",
|
||||
"FAILED",
|
||||
"CLOSED",
|
||||
};
|
||||
|
||||
public static final byte STREAM_STATUS_SENT_CONNECT = 0x00;
|
||||
public static final byte STREAM_STATUS_SENT_RESOLVE = 0x01;
|
||||
public static final byte STREAM_STATUS_SUCCEEDED = 0x02;
|
||||
public static final byte STREAM_STATUS_FAILED = 0x03;
|
||||
public static final byte STREAM_STATUS_CLOSED = 0x04;
|
||||
public static final byte STREAM_STATUS_NEW_CONNECT = 0x05;
|
||||
public static final byte STREAM_STATUS_NEW_RESOLVE = 0x06;
|
||||
public static final byte STREAM_STATUS_DETACHED = 0x07;
|
||||
|
||||
public static final String[] STREAM_STATUS_NAMES = {
|
||||
"SENT_CONNECT",
|
||||
"SENT_RESOLVE",
|
||||
"SUCCEEDED",
|
||||
"FAILED",
|
||||
"CLOSED",
|
||||
"NEW_CONNECT",
|
||||
"NEW_RESOLVE",
|
||||
"DETACHED"
|
||||
};
|
||||
|
||||
public static final byte OR_CONN_STATUS_LAUNCHED = 0x00;
|
||||
public static final byte OR_CONN_STATUS_CONNECTED = 0x01;
|
||||
public static final byte OR_CONN_STATUS_FAILED = 0x02;
|
||||
public static final byte OR_CONN_STATUS_CLOSED = 0x03;
|
||||
|
||||
public static final String[] OR_CONN_STATUS_NAMES = {
|
||||
"LAUNCHED", "CONNECTED", "FAILED", "CLOSED"
|
||||
};
|
||||
|
||||
public static final byte SIGNAL_HUP = 0x01;
|
||||
public static final byte SIGNAL_INT = 0x02;
|
||||
public static final byte SIGNAL_USR1 = 0x0A;
|
||||
public static final byte SIGNAL_USR2 = 0x0C;
|
||||
public static final byte SIGNAL_TERM = 0x0F;
|
||||
|
||||
public static final String ERROR_MSGS[] = {
|
||||
"Unspecified error",
|
||||
"Internal error",
|
||||
"Unrecognized message type",
|
||||
"Syntax error",
|
||||
"Unrecognized configuration key",
|
||||
"Invalid configuration value",
|
||||
"Unrecognized byte code",
|
||||
"Unauthorized",
|
||||
"Failed authentication attempt",
|
||||
"Resource exhausted",
|
||||
"No such stream",
|
||||
"No such circuit",
|
||||
"No such OR",
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,771 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A connection to a running Tor process as specified in control-spec.txt.
|
||||
*/
|
||||
public class TorControlConnection implements TorControlCommands {
|
||||
|
||||
private final LinkedList<Waiter> waiters;
|
||||
private final BufferedReader input;
|
||||
private final Writer output;
|
||||
|
||||
private ControlParseThread thread; // Locking: this
|
||||
|
||||
private volatile EventHandler handler;
|
||||
private volatile PrintWriter debugOutput;
|
||||
private volatile IOException parseThreadException;
|
||||
|
||||
static class Waiter {
|
||||
|
||||
List<ReplyLine> response; // Locking: this
|
||||
|
||||
synchronized List<ReplyLine> getResponse() throws InterruptedException {
|
||||
while (response == null) {
|
||||
wait();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
synchronized void setResponse(List<ReplyLine> response) {
|
||||
this.response = response;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
static class ReplyLine {
|
||||
|
||||
final String status;
|
||||
final String msg;
|
||||
final String rest;
|
||||
|
||||
ReplyLine(String status, String msg, String rest) {
|
||||
this.status = status;
|
||||
this.msg = msg;
|
||||
this.rest = rest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TorControlConnection to communicate with Tor over
|
||||
* a given socket. After calling this constructor, it is typical to
|
||||
* call launchThread and authenticate.
|
||||
*/
|
||||
public TorControlConnection(Socket connection) throws IOException {
|
||||
this(connection.getInputStream(), connection.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TorControlConnection to communicate with Tor over
|
||||
* an arbitrary pair of data streams.
|
||||
*/
|
||||
public TorControlConnection(InputStream i, OutputStream o) {
|
||||
this(new InputStreamReader(i), new OutputStreamWriter(o));
|
||||
}
|
||||
|
||||
public TorControlConnection(Reader i, Writer o) {
|
||||
this.output = o;
|
||||
if (i instanceof BufferedReader)
|
||||
this.input = (BufferedReader) i;
|
||||
else
|
||||
this.input = new BufferedReader(i);
|
||||
this.waiters = new LinkedList<Waiter>();
|
||||
}
|
||||
|
||||
protected final void writeEscaped(String s) throws IOException {
|
||||
StringTokenizer st = new StringTokenizer(s, "\n");
|
||||
while (st.hasMoreTokens()) {
|
||||
String line = st.nextToken();
|
||||
if (line.startsWith("."))
|
||||
line = "." + line;
|
||||
if (line.endsWith("\r"))
|
||||
line += "\n";
|
||||
else
|
||||
line += "\r\n";
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + line);
|
||||
output.write(line);
|
||||
}
|
||||
output.write(".\r\n");
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> .\n");
|
||||
}
|
||||
|
||||
protected static final String quote(String s) {
|
||||
StringBuffer sb = new StringBuffer("\"");
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\\':
|
||||
case '\"':
|
||||
sb.append('\\');
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
sb.append('\"');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected final ArrayList<ReplyLine> readReply() throws IOException {
|
||||
ArrayList<ReplyLine> reply = new ArrayList<ReplyLine>();
|
||||
char c;
|
||||
do {
|
||||
String line = input.readLine();
|
||||
if (line == null) {
|
||||
// if line is null, the end of the stream has been reached, i.e.
|
||||
// the connection to Tor has been closed!
|
||||
if (reply.isEmpty()) {
|
||||
// nothing received so far, can exit cleanly
|
||||
return reply;
|
||||
}
|
||||
// received half of a reply before the connection broke down
|
||||
throw new TorControlSyntaxError("Connection to Tor " +
|
||||
" broke down while receiving reply!");
|
||||
}
|
||||
if (debugOutput != null)
|
||||
debugOutput.println("<< " + line);
|
||||
if (line.length() < 4)
|
||||
throw new TorControlSyntaxError("Line (\"" + line + "\") too short");
|
||||
String status = line.substring(0, 3);
|
||||
c = line.charAt(3);
|
||||
String msg = line.substring(4);
|
||||
String rest = null;
|
||||
if (c == '+') {
|
||||
StringBuffer data = new StringBuffer();
|
||||
while (true) {
|
||||
line = input.readLine();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print("<< " + line);
|
||||
if (line.equals("."))
|
||||
break;
|
||||
else if (line.startsWith("."))
|
||||
line = line.substring(1);
|
||||
data.append(line).append('\n');
|
||||
}
|
||||
rest = data.toString();
|
||||
}
|
||||
reply.add(new ReplyLine(status, msg, rest));
|
||||
} while (c != ' ');
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
protected synchronized List<ReplyLine> sendAndWaitForResponse(String s,
|
||||
String rest) throws IOException {
|
||||
if (parseThreadException != null) throw parseThreadException;
|
||||
checkThread();
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + s);
|
||||
synchronized (waiters) {
|
||||
output.write(s);
|
||||
if (rest != null)
|
||||
writeEscaped(rest);
|
||||
output.flush();
|
||||
waiters.addLast(w);
|
||||
}
|
||||
List<ReplyLine> lst;
|
||||
try {
|
||||
lst = w.getResponse();
|
||||
} catch (InterruptedException ex) {
|
||||
throw new IOException("Interrupted");
|
||||
}
|
||||
for (Iterator<ReplyLine> i = lst.iterator(); i.hasNext(); ) {
|
||||
ReplyLine c = i.next();
|
||||
if (!c.status.startsWith("2"))
|
||||
throw new TorControlError("Error reply: " + c.msg);
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: decode a CMD_EVENT command and dispatch it to our
|
||||
* EventHandler (if any).
|
||||
*/
|
||||
protected void handleEvent(ArrayList<ReplyLine> events) {
|
||||
if (handler == null)
|
||||
return;
|
||||
|
||||
for (Iterator<ReplyLine> i = events.iterator(); i.hasNext(); ) {
|
||||
ReplyLine line = i.next();
|
||||
int idx = line.msg.indexOf(' ');
|
||||
String tp = line.msg.substring(0, idx).toUpperCase();
|
||||
String rest = line.msg.substring(idx + 1);
|
||||
if (tp.equals("CIRC")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.circuitStatus(lst.get(1),
|
||||
lst.get(0),
|
||||
lst.get(1).equals("LAUNCHED")
|
||||
|| lst.size() < 3 ? ""
|
||||
: lst.get(2));
|
||||
} else if (tp.equals("STREAM")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.streamStatus(lst.get(1),
|
||||
lst.get(0),
|
||||
lst.get(3));
|
||||
// XXXX circID.
|
||||
} else if (tp.equals("ORCONN")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.orConnStatus(lst.get(1), lst.get(0));
|
||||
} else if (tp.equals("BW")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.bandwidthUsed(Integer.parseInt(lst.get(0)),
|
||||
Integer.parseInt(lst.get(1)));
|
||||
} else if (tp.equals("NEWDESC")) {
|
||||
List<String> lst = Bytes.splitStr(null, rest);
|
||||
handler.newDescriptors(lst);
|
||||
} else if (tp.equals("DEBUG") ||
|
||||
tp.equals("INFO") ||
|
||||
tp.equals("NOTICE") ||
|
||||
tp.equals("WARN") ||
|
||||
tp.equals("ERR")) {
|
||||
handler.message(tp, rest);
|
||||
} else {
|
||||
handler.unrecognized(tp, rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets <b>w</b> as the PrintWriter for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(PrintWriter w) {
|
||||
debugOutput = w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets <b>s</b> as the PrintStream for debugging output,
|
||||
* which writes out all messages passed between Tor and the controller.
|
||||
* Outgoing messages are preceded by "\>\>" and incoming messages are preceded
|
||||
* by "\<\<"
|
||||
*/
|
||||
public void setDebugging(PrintStream s) {
|
||||
debugOutput = new PrintWriter(s, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the EventHandler object that will be notified of any
|
||||
* events Tor delivers to this connection. To make Tor send us
|
||||
* events, call setEvents().
|
||||
*/
|
||||
public void setEventHandler(EventHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a thread to react to Tor's responses in the background.
|
||||
* This is necessary to handle asynchronous events and synchronous
|
||||
* responses that arrive independantly over the same socket.
|
||||
*/
|
||||
public synchronized Thread launchThread(boolean daemon) {
|
||||
ControlParseThread th = new ControlParseThread();
|
||||
if (daemon)
|
||||
th.setDaemon(true);
|
||||
th.start();
|
||||
this.thread = th;
|
||||
return th;
|
||||
}
|
||||
|
||||
protected class ControlParseThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
react();
|
||||
} catch (IOException ex) {
|
||||
parseThreadException = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void checkThread() {
|
||||
if (thread == null)
|
||||
launchThread(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* helper: implement the main background loop.
|
||||
*/
|
||||
protected void react() throws IOException {
|
||||
while (true) {
|
||||
ArrayList<ReplyLine> lst = readReply();
|
||||
if (lst.isEmpty()) {
|
||||
// connection has been closed remotely! end the loop!
|
||||
return;
|
||||
}
|
||||
if ((lst.get(0)).status.startsWith("6"))
|
||||
handleEvent(lst);
|
||||
else {
|
||||
synchronized (waiters) {
|
||||
if (!waiters.isEmpty()) {
|
||||
Waiter w;
|
||||
w = waiters.removeFirst();
|
||||
w.setResponse(lst);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value of the configuration option 'key' to 'val'.
|
||||
*/
|
||||
public void setConf(String key, String value) throws IOException {
|
||||
List<String> lst = new ArrayList<String>();
|
||||
lst.add(key + " " + value);
|
||||
setConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the values of the configuration options stored in kvMap.
|
||||
*/
|
||||
public void setConf(Map<String, String> kvMap) throws IOException {
|
||||
List<String> lst = new ArrayList<String>();
|
||||
for (Iterator<Map.Entry<String, String>> it = kvMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<String, String> ent = it.next();
|
||||
lst.add(ent.getKey() + " " + ent.getValue() + "\n");
|
||||
}
|
||||
setConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the values of the configuration options stored in
|
||||
* <b>kvList</b>. Each list element in <b>kvList</b> is expected to be
|
||||
* String of the format "key value".
|
||||
* <p>
|
||||
* Tor behaves as though it had just read each of the key-value pairs
|
||||
* from its configuration file. Keywords with no corresponding values have
|
||||
* their configuration values reset to their defaults. setConf is
|
||||
* all-or-nothing: if there is an error in any of the configuration settings,
|
||||
* Tor sets none of them.
|
||||
* <p>
|
||||
* When a configuration option takes multiple values, or when multiple
|
||||
* configuration keys form a context-sensitive group (see getConf below), then
|
||||
* setting any of the options in a setConf command is taken to reset all of
|
||||
* the others. For example, if two ORBindAddress values are configured, and a
|
||||
* command arrives containing a single ORBindAddress value, the new
|
||||
* command's value replaces the two old values.
|
||||
* <p>
|
||||
* To remove all settings for a given option entirely (and go back to its
|
||||
* default value), include a String in <b>kvList</b> containing the key and no value.
|
||||
*/
|
||||
public void setConf(Collection<String> kvList) throws IOException {
|
||||
if (kvList.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("SETCONF");
|
||||
for (Iterator<String> it = kvList.iterator(); it.hasNext(); ) {
|
||||
String kv = it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
if (i == -1)
|
||||
b.append(" ").append(kv);
|
||||
b.append(" ").append(kv.substring(0, i)).append("=")
|
||||
.append(quote(kv.substring(i + 1)));
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to reset the values listed in the collection 'keys' to their
|
||||
* default values.
|
||||
**/
|
||||
public void resetConf(Collection<String> keys) throws IOException {
|
||||
if (keys.size() == 0)
|
||||
return;
|
||||
StringBuffer b = new StringBuffer("RESETCONF");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = it.next();
|
||||
b.append(" ").append(key);
|
||||
}
|
||||
b.append("\r\n");
|
||||
sendAndWaitForResponse(b.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the configuration option 'key'
|
||||
*/
|
||||
public List<ConfigEntry> getConf(String key) throws IOException {
|
||||
List<String> lst = new ArrayList<String>();
|
||||
lst.add(key);
|
||||
return getConf(lst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the values of the configuration variables listed in <b>keys</b>.
|
||||
* Results are returned as a list of ConfigEntry objects.
|
||||
* <p>
|
||||
* If an option appears multiple times in the configuration, all of its
|
||||
* key-value pairs are returned in order.
|
||||
* <p>
|
||||
* Some options are context-sensitive, and depend on other options with
|
||||
* different keywords. These cannot be fetched directly. Currently there
|
||||
* is only one such option: clients should use the "HiddenServiceOptions"
|
||||
* virtual keyword to get all HiddenServiceDir, HiddenServicePort,
|
||||
* HiddenServiceNodes, and HiddenServiceExcludeNodes option settings.
|
||||
*/
|
||||
public List<ConfigEntry> getConf(Collection<String> keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETCONF");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
String key = it.next();
|
||||
sb.append(" ").append(key);
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
List<ConfigEntry> result = new ArrayList<ConfigEntry>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = (it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
if (idx >= 0)
|
||||
result.add(new ConfigEntry(kv.substring(0, idx),
|
||||
kv.substring(idx + 1)));
|
||||
else
|
||||
result.add(new ConfigEntry(kv));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the server inform the client about interesting events.
|
||||
* Each element of <b>events</b> is one of the following Strings:
|
||||
* ["CIRC" | "STREAM" | "ORCONN" | "BW" | "DEBUG" |
|
||||
* "INFO" | "NOTICE" | "WARN" | "ERR" | "NEWDESC" | "ADDRMAP"] .
|
||||
* <p>
|
||||
* Any events not listed in the <b>events</b> are turned off; thus, calling
|
||||
* setEvents with an empty <b>events</b> argument turns off all event reporting.
|
||||
*/
|
||||
public void setEvents(List<String> events) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("SETEVENTS");
|
||||
for (Iterator<String> it = events.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append(it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
sendAndWaitForResponse(sb.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the controller to the Tor server.
|
||||
* <p>
|
||||
* By default, the current Tor implementation trusts all local users, and
|
||||
* the controller can authenticate itself by calling authenticate(new byte[0]).
|
||||
* <p>
|
||||
* If the 'CookieAuthentication' option is true, Tor writes a "magic cookie"
|
||||
* file named "control_auth_cookie" into its data directory. To authenticate,
|
||||
* the controller must send the contents of this file in <b>auth</b>.
|
||||
* <p>
|
||||
* If the 'HashedControlPassword' option is set, <b>auth</b> must contain the salted
|
||||
* hash of a secret password. The salted hash is computed according to the
|
||||
* S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
|
||||
* This is then encoded in hexadecimal, prefixed by the indicator sequence
|
||||
* "16:".
|
||||
* <p>
|
||||
* You can generate the salt of a password by calling
|
||||
* 'tor --hash-password <password>'
|
||||
* or by using the provided PasswordDigest class.
|
||||
* To authenticate under this scheme, the controller sends Tor the original
|
||||
* secret that was used to generate the password.
|
||||
*/
|
||||
public void authenticate(byte[] auth) throws IOException {
|
||||
String cmd = "AUTHENTICATE " + Bytes.hex(auth) + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the server to write out its configuration options into its torrc.
|
||||
*/
|
||||
public void saveConf() throws IOException {
|
||||
sendAndWaitForResponse("SAVECONF\r\n", null);
|
||||
}
|
||||
|
||||
public boolean isHSAvailable(String onionurl) throws IOException {
|
||||
final List<ReplyLine> response = sendAndWaitForResponse("HSFETCH " + onionurl + "\r\n", null);
|
||||
return response.get(0).status.trim().equals("250");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signal from the controller to the Tor server.
|
||||
* <b>signal</b> is one of the following Strings:
|
||||
* <ul>
|
||||
* <li>"RELOAD" or "HUP" : Reload config items, refetch directory</li>
|
||||
* <li>"SHUTDOWN" or "INT" : Controlled shutdown: if server is an OP, exit immediately.
|
||||
* If it's an OR, close listeners and exit after 30 seconds</li>
|
||||
* <li>"DUMP" or "USR1" : Dump stats: log information about open connections and circuits</li>
|
||||
* <li>"DEBUG" or "USR2" : Debug: switch all open logs to loglevel debug</li>
|
||||
* <li>"HALT" or "TERM" : Immediate shutdown: clean up and exit now</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void signal(String signal) throws IOException {
|
||||
String cmd = "SIGNAL " + signal + "\r\n";
|
||||
sendAndWaitForResponse(cmd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signal to the Tor process to shut it down or halt it.
|
||||
* Does not wait for a response.
|
||||
*/
|
||||
public void shutdownTor(String signal) throws IOException {
|
||||
String s = "SIGNAL " + signal + "\r\n";
|
||||
Waiter w = new Waiter();
|
||||
if (debugOutput != null)
|
||||
debugOutput.print(">> " + s);
|
||||
synchronized (waiters) {
|
||||
output.write(s);
|
||||
output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the Tor server that future SOCKS requests for connections to a set of original
|
||||
* addresses should be replaced with connections to the specified replacement
|
||||
* addresses. Each element of <b>kvLines</b> is a String of the form
|
||||
* "old-address new-address". This function returns the new address mapping.
|
||||
* <p>
|
||||
* The client may decline to provide a body for the original address, and
|
||||
* instead send a special null address ("0.0.0.0" for IPv4, "::0" for IPv6, or
|
||||
* "." for hostname), signifying that the server should choose the original
|
||||
* address itself, and return that address in the reply. The server
|
||||
* should ensure that it returns an element of address space that is unlikely
|
||||
* to be in actual use. If there is already an address mapped to the
|
||||
* destination address, the server may reuse that mapping.
|
||||
* <p>
|
||||
* If the original address is already mapped to a different address, the old
|
||||
* mapping is removed. If the original address and the destination address
|
||||
* are the same, the server removes any mapping in place for the original
|
||||
* address.
|
||||
* <p>
|
||||
* Mappings set by the controller last until the Tor process exits:
|
||||
* they never expire. If the controller wants the mapping to last only
|
||||
* a certain time, then it must explicitly un-map the address when that
|
||||
* time has elapsed.
|
||||
*/
|
||||
public Map<String, String> mapAddresses(Collection<String> kvLines) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("MAPADDRESS");
|
||||
for (Iterator<String> it = kvLines.iterator(); it.hasNext(); ) {
|
||||
String kv = it.next();
|
||||
int i = kv.indexOf(' ');
|
||||
sb.append(" ").append(kv.substring(0, i)).append("=")
|
||||
.append(quote(kv.substring(i + 1)));
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
String kv = (it.next()).msg;
|
||||
int idx = kv.indexOf('=');
|
||||
result.put(kv.substring(0, idx),
|
||||
kv.substring(idx + 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> mapAddresses(Map<String, String> addresses) throws IOException {
|
||||
List<String> kvList = new ArrayList<String>();
|
||||
for (Iterator<Map.Entry<String, String>> it = addresses.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry<String, String> e = it.next();
|
||||
kvList.add(e.getKey() + " " + e.getValue());
|
||||
}
|
||||
return mapAddresses(kvList);
|
||||
}
|
||||
|
||||
public String mapAddress(String fromAddr, String toAddr) throws IOException {
|
||||
List<String> lst = new ArrayList<String>();
|
||||
lst.add(fromAddr + " " + toAddr + "\n");
|
||||
Map<String, String> m = mapAddresses(lst);
|
||||
return m.get(fromAddr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the Tor server for keyed values that are not stored in the torrc
|
||||
* configuration file. Returns a map of keys to values.
|
||||
* <p>
|
||||
* Recognized keys include:
|
||||
* <ul>
|
||||
* <li>"version" : The version of the server's software, including the name
|
||||
* of the software. (example: "Tor 0.0.9.4")</li>
|
||||
* <li>"desc/id/<OR identity>" or "desc/name/<OR nickname>" : the latest server
|
||||
* descriptor for a given OR, NUL-terminated. If no such OR is known, the
|
||||
* corresponding value is an empty string.</li>
|
||||
* <li>"network-status" : a space-separated list of all known OR identities.
|
||||
* This is in the same format as the router-status line in directories;
|
||||
* see tor-spec.txt for details.</li>
|
||||
* <li>"addr-mappings/all"</li>
|
||||
* <li>"addr-mappings/config"</li>
|
||||
* <li>"addr-mappings/cache"</li>
|
||||
* <li>"addr-mappings/control" : a space-separated list of address mappings, each
|
||||
* in the form of "from-address=to-address". The 'config' key
|
||||
* returns those address mappings set in the configuration; the 'cache'
|
||||
* key returns the mappings in the client-side DNS cache; the 'control'
|
||||
* key returns the mappings set via the control interface; the 'all'
|
||||
* target returns the mappings set through any mechanism.</li>
|
||||
* <li>"circuit-status" : A series of lines as for a circuit status event. Each line is of the form:
|
||||
* "CircuitID CircStatus Path"</li>
|
||||
* <li>"stream-status" : A series of lines as for a stream status event. Each is of the form:
|
||||
* "StreamID StreamStatus CircID Target"</li>
|
||||
* <li>"orconn-status" : A series of lines as for an OR connection status event. Each is of the
|
||||
* form: "ServerID ORStatus"</li>
|
||||
* </ul>
|
||||
*/
|
||||
public Map<String, String> getInfo(Collection<String> keys) throws IOException {
|
||||
StringBuffer sb = new StringBuffer("GETINFO");
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
sb.append(" ").append(it.next());
|
||||
}
|
||||
sb.append("\r\n");
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(sb.toString(), null);
|
||||
Map<String, String> m = new HashMap<String, String>();
|
||||
for (Iterator<ReplyLine> it = lst.iterator(); it.hasNext(); ) {
|
||||
ReplyLine line = it.next();
|
||||
int idx = line.msg.indexOf('=');
|
||||
if (idx < 0)
|
||||
break;
|
||||
String k = line.msg.substring(0, idx);
|
||||
String v;
|
||||
if (line.rest != null) {
|
||||
v = line.rest;
|
||||
} else {
|
||||
v = line.msg.substring(idx + 1);
|
||||
}
|
||||
m.put(k, v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value of the information field 'key'
|
||||
*/
|
||||
public String getInfo(String key) throws IOException {
|
||||
List<String> lst = new ArrayList<String>();
|
||||
lst.add(key);
|
||||
Map<String, String> m = getInfo(lst);
|
||||
return m.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* An extendCircuit request takes one of two forms: either the <b>circID</b> is zero, in
|
||||
* which case it is a request for the server to build a new circuit according
|
||||
* to the specified path, or the <b>circID</b> is nonzero, in which case it is a
|
||||
* request for the server to extend an existing circuit with that ID according
|
||||
* to the specified <b>path</b>.
|
||||
* <p>
|
||||
* If successful, returns the Circuit ID of the (maybe newly created) circuit.
|
||||
*/
|
||||
public String extendCircuit(String circID, String path) throws IOException {
|
||||
List<ReplyLine> lst = sendAndWaitForResponse(
|
||||
"EXTENDCIRCUIT " + circID + " " + path + "\r\n", null);
|
||||
return (lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the Tor server that the stream specified by <b>streamID</b> should be
|
||||
* associated with the circuit specified by <b>circID</b>.
|
||||
* <p>
|
||||
* Each stream may be associated with
|
||||
* at most one circuit, and multiple streams may share the same circuit.
|
||||
* Streams can only be attached to completed circuits (that is, circuits that
|
||||
* have sent a circuit status "BUILT" event or are listed as built in a
|
||||
* getInfo circuit-status request).
|
||||
* <p>
|
||||
* If <b>circID</b> is 0, responsibility for attaching the given stream is
|
||||
* returned to Tor.
|
||||
* <p>
|
||||
* By default, Tor automatically attaches streams to
|
||||
* circuits itself, unless the configuration variable
|
||||
* "__LeaveStreamsUnattached" is set to "1". Attempting to attach streams
|
||||
* via TC when "__LeaveStreamsUnattached" is false may cause a race between
|
||||
* Tor and the controller, as both attempt to attach streams to circuits.
|
||||
*/
|
||||
public void attachStream(String streamID, String circID)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse("ATTACHSTREAM " + streamID + " " + circID + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor about the server descriptor in <b>desc</b>.
|
||||
* <p>
|
||||
* The descriptor, when parsed, must contain a number of well-specified
|
||||
* fields, including fields for its nickname and identity.
|
||||
*/
|
||||
// More documentation here on format of desc?
|
||||
// No need for return value? control-spec.txt says reply is merely "250 OK" on success...
|
||||
public String postDescriptor(String desc) throws IOException {
|
||||
List<ReplyLine> lst = sendAndWaitForResponse("+POSTDESCRIPTOR\r\n", desc);
|
||||
return (lst.get(0)).msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to change the exit address of the stream identified by <b>streamID</b>
|
||||
* to <b>address</b>. No remapping is performed on the new provided address.
|
||||
* <p>
|
||||
* To be sure that the modified address will be used, this event must be sent
|
||||
* after a new stream event is received, and before attaching this stream to
|
||||
* a circuit.
|
||||
*/
|
||||
public void redirectStream(String streamID, String address) throws IOException {
|
||||
sendAndWaitForResponse("REDIRECTSTREAM " + streamID + " " + address + "\r\n",
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to close the stream identified by <b>streamID</b>.
|
||||
* <b>reason</b> should be one of the Tor RELAY_END reasons given in tor-spec.txt, as a decimal:
|
||||
* <ul>
|
||||
* <li>1 -- REASON_MISC (catch-all for unlisted reasons)</li>
|
||||
* <li>2 -- REASON_RESOLVEFAILED (couldn't look up hostname)</li>
|
||||
* <li>3 -- REASON_CONNECTREFUSED (remote host refused connection)</li>
|
||||
* <li>4 -- REASON_EXITPOLICY (OR refuses to connect to host or port)</li>
|
||||
* <li>5 -- REASON_DESTROY (Circuit is being destroyed)</li>
|
||||
* <li>6 -- REASON_DONE (Anonymized TCP connection was closed)</li>
|
||||
* <li>7 -- REASON_TIMEOUT (Connection timed out, or OR timed out while connecting)</li>
|
||||
* <li>8 -- (unallocated)</li>
|
||||
* <li>9 -- REASON_HIBERNATING (OR is temporarily hibernating)</li>
|
||||
* <li>10 -- REASON_INTERNAL (Internal error at the OR)</li>
|
||||
* <li>11 -- REASON_RESOURCELIMIT (OR has no resources to fulfill request)</li>
|
||||
* <li>12 -- REASON_CONNRESET (Connection was unexpectedly reset)</li>
|
||||
* <li>13 -- REASON_TORPROTOCOL (Sent when closing connection because of Tor protocol violations)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Tor may hold the stream open for a while to flush any data that is pending.
|
||||
*/
|
||||
public void closeStream(String streamID, byte reason)
|
||||
throws IOException {
|
||||
sendAndWaitForResponse("CLOSESTREAM " + streamID + " " + reason + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to close the circuit identified by <b>circID</b>.
|
||||
* If <b>ifUnused</b> is true, do not close the circuit unless it is unused.
|
||||
*/
|
||||
public void closeCircuit(String circID, boolean ifUnused) throws IOException {
|
||||
sendAndWaitForResponse("CLOSECIRCUIT " + circID +
|
||||
(ifUnused ? " IFUNUSED" : "") + "\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to exit when this control connection is closed. This command
|
||||
* was added in Tor 0.2.2.28-beta.
|
||||
*/
|
||||
public void takeOwnership() throws IOException {
|
||||
sendAndWaitForResponse("TAKEOWNERSHIP\r\n", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells Tor to forget any cached client state relating to the hidden
|
||||
* service with the given hostname (excluding the .onion extension).
|
||||
*/
|
||||
public void forgetHiddenService(String hostname) throws IOException {
|
||||
sendAndWaitForResponse("FORGETHS " + hostname + "\r\n", null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception raised when Tor tells us about an error.
|
||||
*/
|
||||
public class TorControlError extends IOException {
|
||||
|
||||
static final long serialVersionUID = 3;
|
||||
|
||||
private final int errorType;
|
||||
|
||||
public TorControlError(int type, String s) {
|
||||
super(s);
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
public TorControlError(String s) {
|
||||
this(-1, s);
|
||||
}
|
||||
|
||||
public int getErrorType() {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
public String getErrorMsg() {
|
||||
try {
|
||||
if (errorType == -1)
|
||||
return null;
|
||||
return TorControlCommands.ERROR_MSGS[errorType];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return "Unrecongized error #" + errorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception raised when Tor behaves in an unexpected way.
|
||||
*/
|
||||
public class TorControlSyntaxError extends IOException {
|
||||
|
||||
static final long serialVersionUID = 3;
|
||||
|
||||
public TorControlSyntaxError(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control.examples;
|
||||
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class DebuggingEventHandler implements EventHandler {
|
||||
|
||||
private final PrintWriter out;
|
||||
|
||||
public DebuggingEventHandler(PrintWriter p) {
|
||||
out = p;
|
||||
}
|
||||
|
||||
public void circuitStatus(String status, String circID, String path) {
|
||||
out.println("Circuit " + circID + " is now " + status + " (path=" + path + ")");
|
||||
}
|
||||
|
||||
public void streamStatus(String status, String streamID, String target) {
|
||||
out.println("Stream " + streamID + " is now " + status + " (target=" + target + ")");
|
||||
}
|
||||
|
||||
public void orConnStatus(String status, String orName) {
|
||||
out.println("OR connection to " + orName + " is now " + status);
|
||||
}
|
||||
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
out.println("Bandwidth usage: " + read + " bytes read; " +
|
||||
written + " bytes written.");
|
||||
}
|
||||
|
||||
public void newDescriptors(java.util.List<String> orList) {
|
||||
out.println("New descriptors for routers:");
|
||||
for (Iterator<String> i = orList.iterator(); i.hasNext(); )
|
||||
out.println(" " + i.next());
|
||||
}
|
||||
|
||||
public void message(String type, String msg) {
|
||||
out.println("[" + type + "] " + msg.trim());
|
||||
}
|
||||
|
||||
public void unrecognized(String type, String msg) {
|
||||
out.println("unrecognized event [" + type + "] " + msg.trim());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2005 Nick Mathewson, Roger Dingledine
|
||||
// See LICENSE file for copying information
|
||||
package net.freehaven.tor.control.examples;
|
||||
|
||||
import net.freehaven.tor.control.*;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
|
||||
public class Main implements TorControlCommands {
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("No command given.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (args[0].equals("set-config")) {
|
||||
setConfig(args);
|
||||
} else if (args[0].equals("get-config")) {
|
||||
getConfig(args);
|
||||
} else if (args[0].equals("get-info")) {
|
||||
getInfo(args);
|
||||
} else if (args[0].equals("listen")) {
|
||||
listenForEvents(args);
|
||||
} else if (args[0].equals("signal")) {
|
||||
signal(args);
|
||||
} else if (args[0].equals("auth")) {
|
||||
authDemo(args);
|
||||
} else {
|
||||
System.err.println("Unrecognized command: " + args[0]);
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
System.out.println("Control socket closed by Tor.");
|
||||
} catch (TorControlError ex) {
|
||||
System.err.println("Error from Tor process: " +
|
||||
ex + " [" + ex.getErrorMsg() + "]");
|
||||
} catch (IOException ex) {
|
||||
System.err.println("IO exception when talking to Tor process: " +
|
||||
ex);
|
||||
ex.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
private static TorControlConnection getConnection(String[] args,
|
||||
boolean daemon) throws IOException {
|
||||
Socket s = new Socket("127.0.0.1", 9100);
|
||||
TorControlConnection conn = new TorControlConnection(s);
|
||||
conn.launchThread(daemon);
|
||||
conn.authenticate(new byte[0]);
|
||||
return conn;
|
||||
}
|
||||
|
||||
private static TorControlConnection getConnection(String[] args)
|
||||
throws IOException {
|
||||
return getConnection(args, true);
|
||||
}
|
||||
|
||||
public static void setConfig(String[] args) throws IOException {
|
||||
// Usage: "set-config [-save] key value key value key value"
|
||||
TorControlConnection conn = getConnection(args);
|
||||
ArrayList<String> lst = new ArrayList<String>();
|
||||
int i = 1;
|
||||
boolean save = false;
|
||||
if (args[i].equals("-save")) {
|
||||
save = true;
|
||||
++i;
|
||||
}
|
||||
for (; i < args.length; i += 2) {
|
||||
lst.add(args[i] + " " + args[i + 1]);
|
||||
}
|
||||
conn.setConf(lst);
|
||||
if (save) {
|
||||
conn.saveConf();
|
||||
}
|
||||
}
|
||||
|
||||
public static void getConfig(String[] args) throws IOException {
|
||||
// Usage: get-config key key key
|
||||
TorControlConnection conn = getConnection(args);
|
||||
List<ConfigEntry> lst = conn.getConf(Arrays.asList(args).subList(1, args.length));
|
||||
for (Iterator<ConfigEntry> i = lst.iterator(); i.hasNext(); ) {
|
||||
ConfigEntry e = i.next();
|
||||
System.out.println("KEY: " + e.key);
|
||||
System.out.println("VAL: " + e.value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void getInfo(String[] args) throws IOException {
|
||||
TorControlConnection conn = getConnection(args);
|
||||
Map<String, String> m = conn.getInfo(Arrays.asList(args).subList(1, args.length));
|
||||
for (Iterator<Map.Entry<String, String>> i = m.entrySet().iterator(); i.hasNext(); ) {
|
||||
Map.Entry<String, String> e = i.next();
|
||||
System.out.println("KEY: " + e.getKey());
|
||||
System.out.println("VAL: " + e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static void listenForEvents(String[] args) throws IOException {
|
||||
// Usage: listen [circ|stream|orconn|bw|newdesc|info|notice|warn|error]*
|
||||
TorControlConnection conn = getConnection(args, false);
|
||||
ArrayList<String> lst = new ArrayList<String>();
|
||||
for (int i = 1; i < args.length; ++i) {
|
||||
lst.add(args[i]);
|
||||
}
|
||||
conn.setEventHandler(
|
||||
new DebuggingEventHandler(new PrintWriter(System.out, true)));
|
||||
conn.setEvents(lst);
|
||||
}
|
||||
|
||||
public static void signal(String[] args) throws IOException {
|
||||
// Usage signal [reload|shutdown|dump|debug|halt]
|
||||
TorControlConnection conn = getConnection(args, false);
|
||||
// distinguish shutdown signal from other signals
|
||||
if ("SHUTDOWN".equalsIgnoreCase(args[1])
|
||||
|| "HALT".equalsIgnoreCase(args[1])) {
|
||||
conn.shutdownTor(args[1].toUpperCase());
|
||||
} else {
|
||||
conn.signal(args[1].toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
public static void authDemo(String[] args) throws IOException {
|
||||
|
||||
PasswordDigest pwd = PasswordDigest.generateDigest();
|
||||
Socket s = new Socket("127.0.0.1", 9100);
|
||||
TorControlConnection conn = new TorControlConnection(s);
|
||||
conn.launchThread(true);
|
||||
conn.authenticate(new byte[0]);
|
||||
|
||||
conn.setConf("HashedControlPassword", pwd.getHashedPassword());
|
||||
|
||||
s = new Socket("127.0.0.1", 9100);
|
||||
conn = new TorControlConnection(s);
|
||||
conn.launchThread(true);
|
||||
conn.authenticate(pwd.getSecret());
|
||||
}
|
||||
|
||||
}
|
||||
|
209
jtorproxy/README.md
Normal file
209
jtorproxy/README.md
Normal file
|
@ -0,0 +1,209 @@
|
|||
JTorProxy
|
||||
=========
|
||||
JTorProxy aims at providing an easy-to-use API for interfacing with Tor from Java. JTorProxy also supports hidden services through standard Java ServerSockets.
|
||||
|
||||
This is a fork of [Thali Project](http://www.thaliproject.org/)'s [Tor_Onion_Proxy_Library](https://github.com/thaliproject/Tor_Onion_Proxy_Library).<br>
|
||||
Includes fixes, an easy-to-use high-level abstraction and an additional module, *net*, showcasing the code by providing an even higher-level API for bi-directional communication with self-advertising communication parties (see `io.nucleo.net.Node`).
|
||||
Although barely tested, this layer is designed to also work with plain TCP Sockets for easier testability.
|
||||
|
||||
A simple test program is present in the sources of the *java* module, illustrating the use of the high-level API.
|
||||
The net module, providing bi-directional communication with self-advertising communication parties, is utilised in a simple test program, `io.nucleo.net.node.NodeTest`, inside the *net* module.
|
||||
|
||||
Original readme follows:
|
||||
|
||||
README
|
||||
======
|
||||
|
||||
NOTE: This project exists independently of the Tor Project.
|
||||
|
||||
__What__: Enable Android and Java applications to easily host their own Tor Onion Proxies using the core Tor binaries. Just by including an AAR or JAR an app can launch and manage the Tor OP as well as start a hidden service.
|
||||
|
||||
__Why__: It's sort of a pain to deploy and manage the Tor OP, we want to make it much easier.
|
||||
|
||||
__How__: We are really just a thin Java wrapper around the Tor OP binaries and jtorctl.
|
||||
|
||||
__Who__: This work is part of the [Thali Project](http://www.thaliproject.org/) and is being actively developed by Yaron Y. Goland assigned to the Microsoft Open Technologies Hub. We absolutely need your help! Please see the FAQ below if you would like to help!
|
||||
|
||||
# How do I use this library?
|
||||
For now you get to build this yourself. Eventually, when it has enough testing, I might consider publishing it to some maven repository.
|
||||
|
||||
1. Install local maven (seriously, if you don't do this, nothing else will work)
|
||||
2. Clone this repo
|
||||
3. Install the JDK and if you are using Android, the Android SDK as well
|
||||
4. Navigate to the 'universal' sub-directory and run 'gradlew install'
|
||||
|
||||
## Android
|
||||
If you are going to use this library with Android then go to the 'android' sub-directory and run 'gradlew install'.
|
||||
|
||||
Now everything should be built and installed into your local maven.
|
||||
|
||||
In your Android project add the following to dependencies in build.gradle:
|
||||
```groovy
|
||||
compile 'com.msopentech.thali:ThaliOnionProxyAndroid:0.0.2'
|
||||
compile 'org.slf4j:slf4j-android:1.7.7'
|
||||
```
|
||||
|
||||
Also add mavenLocal() to your repositories in build.gradle (remember, this is the root level repositories, NOT repositories under buildscript).
|
||||
|
||||
While this code doesn't do much, using it is kind of a pain because of all the ceremony in Java land. So if you are going to host a Tor hidden service on your device or if you want to open connections to the Internet via Tor then there are a variety of things you need to know. My recommendation is that you open android/src/androidTest/java/com/msopentech/thali/toronionproxy/TorOnionProxySmokeTest.java and look up the method testHiddenServiceRecycleTime and start reading.
|
||||
|
||||
But everyone wants sample code so here is some sample code that will start a hidden service and open a socket to it.
|
||||
|
||||
```Java
|
||||
String fileStorageLocation = "torfiles";
|
||||
OnionProxyManager onionProxyManager =
|
||||
new AndroidOnionProxyManager(this.getApplicationContext(), fileStorageLocation);
|
||||
int totalSecondsPerTorStartup = 4 * 60;
|
||||
int totalTriesPerTorStartup = 5;
|
||||
|
||||
// Start the Tor Onion Proxy
|
||||
if (onionProxyManager.startWithRepeat(totalSecondsPerTorStartup, totalTriesPerTorStartup) == false) {
|
||||
Log.e("TorTest", "Couldn't start Tor!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a hidden service listener
|
||||
int hiddenServicePort = 80;
|
||||
int localPort = 9343;
|
||||
String onionAddress = onionProxyManager.publishHiddenService(hiddenServicePort, localPort);
|
||||
|
||||
// It can taken anywhere from 30 seconds to a few minutes for Tor to start properly routing
|
||||
// requests to to a hidden service. So you generally want to try to test connect to it a
|
||||
// few times. But after the previous call the Tor Onion Proxy will route any requests
|
||||
// to the returned onionAddress and hiddenServicePort to 127.0.0.1:localPort. So, for example,
|
||||
// you could just pass localPort into the NanoHTTPD constructor and have a HTTP server listening
|
||||
// to that port.
|
||||
|
||||
// Connect via the TOR network
|
||||
// In this case we are trying to connect to the hidden service but any IP/DNS address and port can be
|
||||
// used here.
|
||||
Socket clientSocket =
|
||||
Utilities.socks4aSocketConnection(onionAddress, hiddenServicePort, "127.0.0.1", localPort);
|
||||
|
||||
// Now the socket is open but note that it can take some time before the Tor network has everything
|
||||
// connected and connection requests can fail for spurious reasons (especially when connecting to
|
||||
// hidden services) so have lots of retry logic.
|
||||
```
|
||||
|
||||
## Java
|
||||
If you are going to use this library with Java then go to the 'java' sub-directory and run 'gradlew install'.
|
||||
|
||||
I would also recommend running 'gradlew test'. This will make sure you are properly set up and will copy your test files which I recommend reading.
|
||||
|
||||
Now everything should be build and installed into your local maven.
|
||||
|
||||
Now go to your build.gradle file (or equivalent) and make sure you add:
|
||||
```groovy
|
||||
apply plugin: 'maven'
|
||||
```
|
||||
|
||||
Then go to your repositories and add:
|
||||
```groovy
|
||||
mavenLocal()
|
||||
```
|
||||
|
||||
Then go to dependencies and add in:
|
||||
```groovy
|
||||
compile 'com.msopentech.thali:ThaliOnionProxyJava:0.0.2'
|
||||
compile 'com.msopentech.thali:BriarJtorctl:0.0.2'
|
||||
compile 'org.slf4j:slf4j-simple:1.7.7'
|
||||
```
|
||||
|
||||
As discussed above, the code in this library is pretty trivial. But using it is hard because of the complexities of Tor and Java. For those in Java land please go to java\src\test\java\com\msopentech\thali\toronionproxy\TorOnionProxySmokeTest and check out testHiddenServiceRecycleTime().
|
||||
|
||||
But here is some sample code to get you started.
|
||||
|
||||
```Java
|
||||
String fileStorageLocation = "torfiles";
|
||||
OnionProxyManager onionProxyManager = new JavaOnionProxyManager(
|
||||
new JavaOnionProxyContext(
|
||||
Files.createTempDirectory(fileStorageLocation).toFile()));
|
||||
|
||||
int totalSecondsPerTorStartup = 4 * 60;
|
||||
int totalTriesPerTorStartup = 5;
|
||||
|
||||
// Start the Tor Onion Proxy
|
||||
if (onionProxyManager.startWithRepeat(totalSecondsPerTorStartup, totalTriesPerTorStartup) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a hidden service listener
|
||||
int hiddenServicePort = 80;
|
||||
int localPort = 9343;
|
||||
String onionAddress = onionProxyManager.publishHiddenService(hiddenServicePort, localPort);
|
||||
|
||||
// It can taken anywhere from 30 seconds to a few minutes for Tor to start properly routing
|
||||
// requests to to a hidden service. So you generally want to try to test connect to it a
|
||||
// few times. But after the previous call the Tor Onion Proxy will route any requests
|
||||
// to the returned onionAddress and hiddenServicePort to 127.0.0.1:localPort. So, for example,
|
||||
// you could just pass localPort into the NanoHTTPD constructor and have a HTTP server listening
|
||||
// to that port.
|
||||
|
||||
// Connect via the TOR network
|
||||
// In this case we are trying to connect to the hidden service but any IP/DNS address and port can be
|
||||
// used here.
|
||||
Socket clientSocket =
|
||||
Utilities.socks4aSocketConnection(onionAddress, hiddenServicePort, "127.0.0.1", localPort);
|
||||
|
||||
// Now the socket is open but note that it can take some time before the Tor network has everything
|
||||
// connected and connection requests can fail for spurious reasons (especially when connecting to
|
||||
// hidden services) so have lots of retry logic.
|
||||
```
|
||||
|
||||
# Acknowledgements
|
||||
A huge thanks to Michael Rogers and the Briar project. This project started by literally copying their code (yes, I asked first) which handled things in Android and then expanding it to deal with Java. We are also using Briar's fork of JTorCtl until their patches are accepted by the Guardian Project.
|
||||
|
||||
Another huge thanks to the Guardian folks for both writing JTorCtl and doing the voodoo to get the Tor OP running on Android.
|
||||
|
||||
And of course an endless amount of gratitude to the heroes of the Tor project for making this all possible in the first place and for their binaries which we are using for all our supported Java platforms.
|
||||
|
||||
# FAQ
|
||||
## What's the relationship between universal, Java and android projects?
|
||||
The universal project produces a JAR that contains code that is common to both the Java and Android versions of the project. We need this JAR available separately because we use this code to build other projects that also share code between Java and Android. So universal is very useful because we can include universal into our project's 'common' code project without getting into any Java or Android specific details.
|
||||
|
||||
On top of universal are the java and android projects. They contain code specific to those platforms along with collateral like binaries.
|
||||
|
||||
Note however that shared files like the jtorctl-briar, geoip and torrc are kept in Universal and we use a gradle task to copy them into the android and java projects.
|
||||
|
||||
One further complication are tests. Hard experience has taught that putting tests into universal doesn't work well because it means we have to write custom wrappers for each test in android and java in order to run them. So instead the tests live primarily in the android project and we use a gradle task to copy them over to the Java project. This lets us share identical tests but it means that all edits to tests have to happen in the android project. Any changes made to shared test code in the java project will be lost. This should not be an issue for anyone but a dev actually working on Tor_Onion_Proxy_Library, to users its irrelevant.
|
||||
|
||||
## What is the maturity of the code in this project?
|
||||
Well the release version is currently 0.0.2 so that should say something. This is an alpha. We have (literally) one test. Obviously we need a heck of a lot more coverage. But we have run that test and it does actually work which means that the Tor OP is being run and is available.
|
||||
|
||||
## Can I run multiple programs next to each other that use this library?
|
||||
Yes, they won't interfere with each other. We use dynamic ports for both the control and socks channel.
|
||||
|
||||
## Can I help with the project?
|
||||
ABSOLUTELY! You will need to sign a [Contributor License Agreement](https://cla.msopentech.com/) before submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to submit a request via the form and then electronically sign the Contributor License Agreement when you receive the email containing the link to the document. This needs to only be done once for any Microsoft Open Technologies OSS project.
|
||||
|
||||
Please make sure to configure git with a username and email address to use for your commits. Your username should be your GitHub username, so that people will be able to relate your commits to you. From a command prompt, run the following commands:
|
||||
```
|
||||
git config user.name YourGitHubUserName
|
||||
git config user.email YourAlias@YourDomain
|
||||
```
|
||||
|
||||
What we most need help with right now is test coverage. But we also have a bunch of features we would like to add. See our issues for a list.
|
||||
|
||||
## Where does jtorctl-briar.jar come from?
|
||||
This is a fork of jtorctl with some fixes from Briar. So we got it out of Briar's depot. The plan is that jtorctl is supposed to accept Briar's changes and then we will start to build jtorctl ourselves from the Tor depot directly.
|
||||
|
||||
## Where did the binaries for the Tor OP come from?
|
||||
### Android
|
||||
The ARM binary for Android came from the OrBot distribution available at https://guardianproject.info/releases/. I take the latest PIE release qualify APK, unzip it and go to res/raw and then decompress tor.mp3 and go into bin and copy out the tor executable file and put it into android/src/main/assets
|
||||
|
||||
### Windows
|
||||
I download the Expert Bundle for Windows from https://www.torproject.org/download/download.html.en and took tor.exe, libeay32.dll, libevent-2-0-5.dll, libgcc_s_sjlj-1.dll and ssleay32.dll from the Tor directory. I then need to zip them all together into a file called tor.zip and stick them into java/src/main/resources/native/windows/x86.
|
||||
|
||||
### Linux
|
||||
I download the 32 bit Tor Browser Bundle for Linux from https://www.torproject.org/download/download.html.en and then unzipped and untared it and navigated to tor-browser_en-US\Browser\TorBrowser\Tor\ and copied out the tor and libevent-2.0.so.5 files. Note that for stupid reasons I really should fix I currently need to zip these files together into a file called tor.zip before sticking them into java/src/main/resources/native/linux/x86.
|
||||
|
||||
I then do the same thing but this time with the 64 bit download and put the results into the x64 linux sub-directory.
|
||||
|
||||
### OS/X
|
||||
I download the OS/X Tor Browser bundle from https://www.torproject.org/download/download.html.en and using 7Zip opened my way into the dmg file inside of 0.unknown partition\TorBrowser.app\TorBrowser\Tor and copied out tor.real and libevent-2.0.5.dylib. And, as with Linux, I then need to zip those two files together into a file called tor.zip and put that into java/src/main/resources/native/osx/x64.
|
||||
|
||||
## Where did the geoip and geoip6 files come from?
|
||||
I took them from the Data/Tor directory of the Windows Expert Bundle (see previous question).
|
||||
|
||||
## Why does the Android code require minSdkVersion 16?!?!?! Why so high?
|
||||
The issue is the tor executable that I get from Guardian. To run on Lollipop the executable has to be PIE. But PIE support only started with SDK 16. So if I'm going to ship a PIE executable I have to set minSdkVersion to 16. But!!!!! Guardian actually also builds a non-PIE version of the executable. So if you have a use case that requires support for an SDK less than 16 PLEASE PLEASE PLEASE send mail to the [Thali Mailing list](https://pairlist10.pair.net/mailman/listinfo/thali-talk). We absolutely can fix this. We just haven't had a good reason to. So please give us one!
|
39
jtorproxy/pom.xml
Normal file
39
jtorproxy/pom.xml
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<version>0.3.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jtorproxy</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<artifactId>jsocks</artifactId>
|
||||
<version>0.3.2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<artifactId>jtorctl</artifactId>
|
||||
<version>0.3.2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.java.toronionproxy;
|
||||
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyContext;
|
||||
import com.msopentech.thali.toronionproxy.WriteObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class JavaOnionProxyContext extends OnionProxyContext {
|
||||
|
||||
public JavaOnionProxyContext(File workingDirectory) {
|
||||
super(workingDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteObserver generateWriteObserver(File file) {
|
||||
try {
|
||||
return new JavaWatchObserver(file);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not create JavaWatchObserver", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream getAssetOrResourceByName(String fileName) throws IOException {
|
||||
return getClass().getResourceAsStream("/" + fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessId() {
|
||||
// This is a horrible hack. It seems like more JVMs will return the process's PID this way, but not guarantees.
|
||||
String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
|
||||
return processName.split("@")[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright (C) 2011-2014 Sublime Software Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.java.toronionproxy;
|
||||
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyContext;
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyManager;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class JavaOnionProxyManager extends OnionProxyManager {
|
||||
public JavaOnionProxyManager(OnionProxyContext onionProxyContext) {
|
||||
super(onionProxyContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean setExecutable(File f) {
|
||||
return f.setExecutable(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright (C) 2011-2014 Sublime Software Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.java.toronionproxy;
|
||||
|
||||
import com.msopentech.thali.toronionproxy.OsData;
|
||||
import com.msopentech.thali.toronionproxy.WriteObserver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Watches to see if a particular file is changed
|
||||
*/
|
||||
public class JavaWatchObserver implements WriteObserver {
|
||||
private WatchService watchService;
|
||||
private WatchKey key;
|
||||
private File fileToWatch;
|
||||
private long lastModified;
|
||||
private long length;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WriteObserver.class);
|
||||
|
||||
|
||||
public JavaWatchObserver(File fileToWatch) throws IOException {
|
||||
if (fileToWatch == null || fileToWatch.exists() == false) {
|
||||
throw new RuntimeException("fileToWatch must not be null and must already exist.");
|
||||
}
|
||||
this.fileToWatch = fileToWatch;
|
||||
lastModified = fileToWatch.lastModified();
|
||||
length = fileToWatch.length();
|
||||
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
// Note that poll depends on us only registering events that are of type path
|
||||
if (OsData.getOsType() != OsData.OsType.Mac) {
|
||||
key = fileToWatch.getParentFile().toPath().register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
} else {
|
||||
// Unfortunately the default watch service on Mac is broken, it uses a separate thread and really slow polling to detect file changes
|
||||
// rather than integrating with the OS. There is a hack to make it poll faster which we can use for now. See
|
||||
// http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else
|
||||
key = fileToWatch.getParentFile().toPath().register(watchService, new WatchEvent.Kind[]
|
||||
{StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean poll(long timeout, TimeUnit unit) {
|
||||
boolean result = false;
|
||||
try {
|
||||
long remainingTimeoutInNanos = unit.toNanos(timeout);
|
||||
while (remainingTimeoutInNanos > 0) {
|
||||
long startTimeInNanos = System.nanoTime();
|
||||
WatchKey receivedKey = watchService.poll(remainingTimeoutInNanos, TimeUnit.NANOSECONDS);
|
||||
long timeWaitedInNanos = System.nanoTime() - startTimeInNanos;
|
||||
|
||||
if (receivedKey != null) {
|
||||
if (receivedKey != key) {
|
||||
throw new RuntimeException("This really shouldn't have happened. EEK!" + receivedKey.toString());
|
||||
}
|
||||
|
||||
for (WatchEvent<?> event : receivedKey.pollEvents()) {
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
|
||||
if (kind == StandardWatchEventKinds.OVERFLOW) {
|
||||
LOG.error("We got an overflow, there shouldn't have been enough activity to make that happen.");
|
||||
}
|
||||
|
||||
|
||||
Path changedEntry = (Path) event.context();
|
||||
if (fileToWatch.toPath().endsWith(changedEntry)) {
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// In case we haven't yet gotten the event we are looking for we have to reset in order to
|
||||
// receive any further notifications.
|
||||
if (key.reset() == false) {
|
||||
LOG.error("The key became invalid which should not have happened.");
|
||||
}
|
||||
}
|
||||
|
||||
if (timeWaitedInNanos >= remainingTimeoutInNanos) {
|
||||
break;
|
||||
}
|
||||
|
||||
remainingTimeoutInNanos -= timeWaitedInNanos;
|
||||
}
|
||||
|
||||
// Even with the high sensitivity setting above for the Mac the polling still misses changes so I've added
|
||||
// a last modified check as a backup. Except I personally witnessed last modified not returning a new value
|
||||
// value even when I saw the file change!!!! So I'm also adding in a length check. Java really seems to
|
||||
// have an issue with the OS/X file system.
|
||||
result = (fileToWatch.lastModified() != lastModified) || (fileToWatch.length() != length);
|
||||
return result;
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Internal error has caused JavaWatchObserver to not be reliable.", e);
|
||||
} finally {
|
||||
if (result) {
|
||||
try {
|
||||
watchService.close();
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Attempt to close watchService failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class FileUtilities {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileUtilities.class);
|
||||
|
||||
/**
|
||||
* Closes both input and output streams when done.
|
||||
*
|
||||
* @param in Stream to read from
|
||||
* @param out Stream to write to
|
||||
* @throws java.io.IOException - If close on input or output fails
|
||||
*/
|
||||
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
try {
|
||||
copyDoNotCloseInput(in, out);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Won't close the input stream when it's done, needed to handle ZipInputStreams
|
||||
*
|
||||
* @param in Won't be closed
|
||||
* @param out Will be closed
|
||||
* @throws java.io.IOException - If close on output fails
|
||||
*/
|
||||
public static void copyDoNotCloseInput(InputStream in, OutputStream out) throws IOException {
|
||||
try {
|
||||
byte[] buf = new byte[4096];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1) break;
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void listFilesToLog(File f) {
|
||||
if (f.isDirectory()) {
|
||||
for (File child : f.listFiles()) {
|
||||
listFilesToLog(child);
|
||||
}
|
||||
} else {
|
||||
LOG.info(f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] read(File f) throws IOException {
|
||||
byte[] b = new byte[(int) f.length()];
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
try {
|
||||
int offset = 0;
|
||||
while (offset < b.length) {
|
||||
int read = in.read(b, offset, b.length - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
return b;
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the input stream, deletes fileToWriteTo if it exists and over writes it with the stream.
|
||||
*
|
||||
* @param readFrom Stream to read from
|
||||
* @param fileToWriteTo File to write to
|
||||
* @throws java.io.IOException - If any of the file operations fail
|
||||
*/
|
||||
public static void cleanInstallOneFile(InputStream readFrom, File fileToWriteTo) throws IOException {
|
||||
if (fileToWriteTo.exists() && fileToWriteTo.delete() == false) {
|
||||
throw new RuntimeException("Could not remove existing file " + fileToWriteTo.getName());
|
||||
}
|
||||
OutputStream out = new FileOutputStream(fileToWriteTo);
|
||||
FileUtilities.copy(readFrom, out);
|
||||
}
|
||||
|
||||
public static void recursiveFileDelete(File fileOrDirectory) {
|
||||
if (fileOrDirectory.isDirectory()) {
|
||||
for (File child : fileOrDirectory.listFiles()) {
|
||||
recursiveFileDelete(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileOrDirectory.exists() && fileOrDirectory.delete() == false) {
|
||||
throw new RuntimeException("Could not delete directory " + fileOrDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This has to exist somewhere! Why isn't it a part of the standard Java library?
|
||||
*
|
||||
* @param destinationDirectory Directory files are to be extracted to
|
||||
* @param zipFileInputStream Stream to unzip
|
||||
* @throws java.io.IOException - If there are any file errors
|
||||
*/
|
||||
public static void extractContentFromZip(File destinationDirectory, InputStream zipFileInputStream)
|
||||
throws IOException {
|
||||
ZipInputStream zipInputStream;
|
||||
try {
|
||||
zipInputStream = new ZipInputStream(zipFileInputStream);
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
File file = new File(destinationDirectory, zipEntry.getName());
|
||||
if (zipEntry.isDirectory()) {
|
||||
if (file.exists() == false && file.mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create directory " + file);
|
||||
}
|
||||
} else {
|
||||
if (file.exists() && file.delete() == false) {
|
||||
throw new RuntimeException(
|
||||
"Could not delete file in preparation for overwriting it. File - " +
|
||||
file.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (file.createNewFile() == false) {
|
||||
throw new RuntimeException("Could not create file " + file);
|
||||
}
|
||||
|
||||
OutputStream fileOutputStream = new FileOutputStream(file);
|
||||
copyDoNotCloseInput(zipInputStream, fileOutputStream);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (zipFileInputStream != null) {
|
||||
zipFileInputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class encapsulates data that is handled differently in Java and Android
|
||||
* as well as managing file locations.
|
||||
*/
|
||||
abstract public class OnionProxyContext {
|
||||
protected final static String hiddenserviceDirectoryName = "hiddenservice";
|
||||
protected final static String geoIpName = "geoip";
|
||||
protected final static String geoIpv6Name = "geoip6";
|
||||
protected final static String torrcName = "torrc";
|
||||
protected final File workingDirectory;
|
||||
protected final File geoIpFile;
|
||||
protected final File geoIpv6File;
|
||||
protected final File torrcFile;
|
||||
protected final File torExecutableFile;
|
||||
protected final File cookieFile;
|
||||
protected final File hostnameFile;
|
||||
|
||||
public OnionProxyContext(File workingDirectory) {
|
||||
this.workingDirectory = workingDirectory;
|
||||
geoIpFile = new File(getWorkingDirectory(), geoIpName);
|
||||
geoIpv6File = new File(getWorkingDirectory(), geoIpv6Name);
|
||||
torrcFile = new File(getWorkingDirectory(), torrcName);
|
||||
torExecutableFile = new File(getWorkingDirectory(), getTorExecutableFileName());
|
||||
cookieFile = new File(getWorkingDirectory(), ".tor/control_auth_cookie");
|
||||
hostnameFile = new File(getWorkingDirectory(), "/" + hiddenserviceDirectoryName
|
||||
+ "/hostname");
|
||||
}
|
||||
|
||||
public void installFiles() throws IOException, InterruptedException {
|
||||
// This is sleezy but we have cases where an old instance of the Tor OP
|
||||
// needs an extra second to
|
||||
// clean itself up. Without that time we can't do things like delete its
|
||||
// binary (which we currently
|
||||
// do by default, something we hope to fix with
|
||||
// https://github.com/thaliproject/Tor_Onion_Proxy_Library/issues/13
|
||||
Thread.sleep(1000, 0);
|
||||
|
||||
try {
|
||||
File dotTorDir = new File(getWorkingDirectory(), ".tor");
|
||||
if (dotTorDir.exists())
|
||||
FileUtilities.recursiveFileDelete(dotTorDir);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (workingDirectory.exists() == false && workingDirectory.mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create root directory!");
|
||||
}
|
||||
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(geoIpName), geoIpFile);
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(geoIpv6Name), geoIpv6File);
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(torrcName), torrcFile);
|
||||
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
FileUtilities.cleanInstallOneFile(getAssetOrResourceByName(getPathToTorExecutable()
|
||||
+ getTorExecutableFileName()), torExecutableFile);
|
||||
break;
|
||||
case Windows:
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
case Mac:
|
||||
FileUtilities.extractContentFromZip(getWorkingDirectory(),
|
||||
getAssetOrResourceByName(getPathToTorExecutable() + "tor.zip"));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS yet");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets environment variables and working directory needed for Tor
|
||||
*
|
||||
* @param processBuilder we will call start on this to run Tor
|
||||
*/
|
||||
public void setEnvironmentArgsAndWorkingDirectoryForStart(ProcessBuilder processBuilder) {
|
||||
processBuilder.directory(getWorkingDirectory());
|
||||
Map<String, String> environment = processBuilder.environment();
|
||||
environment.put("HOME", getWorkingDirectory().getAbsolutePath());
|
||||
switch (OsData.getOsType()) {
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
// We have to provide the LD_LIBRARY_PATH because when looking
|
||||
// for dynamic libraries
|
||||
// Linux apparently will not look in the current directory by
|
||||
// default. By setting this
|
||||
// environment variable we fix that.
|
||||
environment.put("LD_LIBRARY_PATH", getWorkingDirectory().getAbsolutePath());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getEnvironmentArgsForExec() {
|
||||
List<String> envArgs = new ArrayList<String>();
|
||||
envArgs.add("HOME=" + getWorkingDirectory().getAbsolutePath());
|
||||
switch (OsData.getOsType()) {
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
// We have to provide the LD_LIBRARY_PATH
|
||||
envArgs.add("LD_LIBRARY_PATH=" + getWorkingDirectory().getAbsolutePath());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return envArgs.toArray(new String[envArgs.size()]);
|
||||
}
|
||||
|
||||
public File getGeoIpFile() {
|
||||
return geoIpFile;
|
||||
}
|
||||
|
||||
public File getGeoIpv6File() {
|
||||
return geoIpv6File;
|
||||
}
|
||||
|
||||
public File getTorrcFile() {
|
||||
return torrcFile;
|
||||
}
|
||||
|
||||
public File getCookieFile() {
|
||||
return cookieFile;
|
||||
}
|
||||
|
||||
public File getHostNameFile() {
|
||||
return hostnameFile;
|
||||
}
|
||||
|
||||
public File getTorExecutableFile() {
|
||||
return torExecutableFile;
|
||||
}
|
||||
|
||||
public File getWorkingDirectory() {
|
||||
return workingDirectory;
|
||||
}
|
||||
|
||||
public void deleteAllFilesButHiddenServices() throws InterruptedException {
|
||||
// It can take a little bit for the Tor OP to detect the connection is
|
||||
// dead and kill itself
|
||||
Thread.sleep(1000);
|
||||
for (File file : getWorkingDirectory().listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
if (file.getName().compareTo(hiddenserviceDirectoryName) != 0) {
|
||||
FileUtilities.recursiveFileDelete(file);
|
||||
}
|
||||
} else {
|
||||
if (file.delete() == false) {
|
||||
throw new RuntimeException("Could not delete file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Files we pull out of the AAR or JAR are typically at the root but for
|
||||
* executables outside of Android the executable for a particular platform
|
||||
* is in a specific sub-directory.
|
||||
*
|
||||
* @return Path to executable in JAR Resources
|
||||
*/
|
||||
protected String getPathToTorExecutable() {
|
||||
String path = "native/";
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
return "";
|
||||
case Windows:
|
||||
return path + "windows/x86/"; // We currently only support the
|
||||
// x86 build but that should work
|
||||
// everywhere
|
||||
case Mac:
|
||||
return path + "osx/x64/"; // I don't think there even is a x32
|
||||
// build of Tor for Mac, but could be
|
||||
// wrong.
|
||||
case Linux32:
|
||||
return path + "linux/x86/";
|
||||
case Linux64:
|
||||
return path + "linux/x64/";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
protected String getTorExecutableFileName() {
|
||||
switch (OsData.getOsType()) {
|
||||
case Android:
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
return "tor";
|
||||
case Windows:
|
||||
return "tor.exe";
|
||||
case Mac:
|
||||
return "tor.real";
|
||||
default:
|
||||
throw new RuntimeException("We don't support Tor on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
abstract public String getProcessId();
|
||||
|
||||
abstract public WriteObserver generateWriteObserver(File file);
|
||||
|
||||
abstract protected InputStream getAssetOrResourceByName(String fileName) throws IOException;
|
||||
|
||||
public File getHiddenServiceDirectory() {
|
||||
return new File(getWorkingDirectory(), "/" + hiddenserviceDirectoryName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
Copyright (C) 2011-2014 Sublime Software Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import net.freehaven.tor.control.ConfigEntry;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* This is where all the fun is, this is the class that handles the heavy work. Note that you will most likely need
|
||||
* to actually call into the AndroidOnionProxyManager or JavaOnionProxyManager in order to create the right bindings
|
||||
* for your environment.
|
||||
* <p>
|
||||
* This class is thread safe but that's mostly because we hit everything over the head with 'synchronized'. Given the
|
||||
* way this class is used there shouldn't be any performance implications of this.
|
||||
* <p>
|
||||
* This class began life as TorPlugin from the Briar Project
|
||||
*/
|
||||
public abstract class OnionProxyManager {
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "NOTICE", "WARN", "ERR"
|
||||
};
|
||||
|
||||
private static final String OWNER = "__OwningControllerProcess";
|
||||
private static final int COOKIE_TIMEOUT = 3 * 1000; // Milliseconds
|
||||
private static final int HOSTNAME_TIMEOUT = 30 * 1000; // Milliseconds
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OnionProxyManager.class);
|
||||
|
||||
protected final OnionProxyContext onionProxyContext;
|
||||
|
||||
private volatile Socket controlSocket = null;
|
||||
|
||||
// If controlConnection is not null then this means that a connection exists and the Tor OP will die when
|
||||
// the connection fails.
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile int control_port;
|
||||
|
||||
public OnionProxyManager(OnionProxyContext onionProxyContext) {
|
||||
this.onionProxyContext = onionProxyContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a blocking call that will try to start the Tor OP, connect it to the network and get it to be fully
|
||||
* bootstrapped. Sometimes the bootstrap process just hangs for no apparent reason so the method will wait for the
|
||||
* given time for bootstrap to finish and if it doesn't then will restart the bootstrap process the given number of
|
||||
* repeats.
|
||||
*
|
||||
* @param secondsBeforeTimeOut Seconds to wait for boot strapping to finish
|
||||
* @param numberOfRetries Number of times to try recycling the Tor OP before giving up on bootstrapping working
|
||||
* @return True if bootstrap succeeded, false if there is a problem or the bootstrap couldn't complete in the given
|
||||
* time.
|
||||
* @throws java.lang.InterruptedException - You know, if we are interrupted
|
||||
* @throws java.io.IOException - IO Exceptions
|
||||
*/
|
||||
public synchronized boolean startWithRepeat(int secondsBeforeTimeOut, int numberOfRetries) throws
|
||||
InterruptedException, IOException {
|
||||
if (secondsBeforeTimeOut <= 0 || numberOfRetries < 0) {
|
||||
throw new IllegalArgumentException("secondsBeforeTimeOut >= 0 & numberOfRetries > 0");
|
||||
}
|
||||
|
||||
try {
|
||||
for (int retryCount = 0; retryCount < numberOfRetries; ++retryCount) {
|
||||
if (installAndStartTorOp() == false) {
|
||||
return false;
|
||||
}
|
||||
enableNetwork(true);
|
||||
|
||||
// We will check every second to see if boot strapping has finally finished
|
||||
for (int secondsWaited = 0; secondsWaited < secondsBeforeTimeOut; ++secondsWaited) {
|
||||
if (isBootstrapped() == false) {
|
||||
Thread.sleep(1000, 0);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrapping isn't over so we need to restart and try again
|
||||
stop();
|
||||
// Experimentally we have found that if a Tor OP has run before and thus has cached descriptors
|
||||
// and that when we try to start it again it won't start then deleting the cached data can fix this.
|
||||
// But, if there is cached data and things do work then the Tor OP will start faster than it would
|
||||
// if we delete everything.
|
||||
// So our compromise is that we try to start the Tor OP 'as is' on the first round and after that
|
||||
// we delete all the files.
|
||||
onionProxyContext.deleteAllFilesButHiddenServices();
|
||||
}
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
// Make sure we return the Tor OP in some kind of consistent state, even if it's 'off'.
|
||||
if (isRunning() == false) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the socks port on the IPv4 localhost address that the Tor OP is listening on
|
||||
*
|
||||
* @return Discovered socks port
|
||||
* @throws java.io.IOException - File errors
|
||||
*/
|
||||
public synchronized int getIPv4LocalHostSocksPort() throws IOException {
|
||||
if (isRunning() == false) {
|
||||
throw new RuntimeException("Tor is not running!");
|
||||
}
|
||||
|
||||
// This returns a set of space delimited quoted strings which could be Ipv4, Ipv6 or unix sockets
|
||||
String[] socksIpPorts = controlConnection.getInfo("net/listeners/socks").split(" ");
|
||||
|
||||
for (String address : socksIpPorts) {
|
||||
if (address.contains("\"127.0.0.1:")) {
|
||||
// Remember, the last character will be a " so we have to remove that
|
||||
return Integer.parseInt(address.substring(address.lastIndexOf(":") + 1, address.length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("We don't have an Ipv4 localhost binding for socks!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a hidden service
|
||||
*
|
||||
* @param hiddenServicePort The port that the hidden service will accept connections on
|
||||
* @param localPort The local port that the hidden service will relay connections to
|
||||
* @return The hidden service's onion address in the form X.onion.
|
||||
* @throws java.io.IOException - File errors
|
||||
*/
|
||||
public synchronized String publishHiddenService(int hiddenServicePort, int localPort) throws IOException {
|
||||
if (controlConnection == null) {
|
||||
throw new RuntimeException("Service is not running.");
|
||||
}
|
||||
|
||||
List<ConfigEntry> currentHiddenServices = controlConnection.getConf("HiddenServiceOptions");
|
||||
|
||||
if ((currentHiddenServices.size() == 1 &&
|
||||
currentHiddenServices.get(0).key.compareTo("HiddenServiceOptions") == 0 &&
|
||||
currentHiddenServices.get(0).value.compareTo("") == 0) == false) {
|
||||
throw new RuntimeException("Sorry, only one hidden service to a customer and we already have one. Please " +
|
||||
"send complaints to https://github" +
|
||||
".com/thaliproject/Tor_Onion_Proxy_Library/issues/5 with your scenario so we can justify fixing " +
|
||||
"this.");
|
||||
}
|
||||
|
||||
LOG.info("Creating hidden service");
|
||||
File hostnameFile = onionProxyContext.getHostNameFile();
|
||||
|
||||
if (hostnameFile.getParentFile().exists() == false &&
|
||||
hostnameFile.getParentFile().mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create hostnameFile parent directory");
|
||||
}
|
||||
|
||||
if (hostnameFile.exists() == false && hostnameFile.createNewFile() == false) {
|
||||
throw new RuntimeException("Could not create hostnameFile");
|
||||
}
|
||||
// Thanks, Ubuntu!
|
||||
try {
|
||||
switch (OsData.getOsType()) {
|
||||
case Mac:
|
||||
case Android:
|
||||
case Linux32:
|
||||
case Linux64: {
|
||||
Set<PosixFilePermission> perms = new HashSet<>();
|
||||
perms.add(PosixFilePermission.OWNER_READ);
|
||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(onionProxyContext.getHiddenServiceDirectory()
|
||||
.toPath(), perms);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Watch for the hostname file being created/updated
|
||||
WriteObserver hostNameFileObserver = onionProxyContext.generateWriteObserver(hostnameFile);
|
||||
// Use the control connection to update the Tor config
|
||||
List<String> config = Arrays.asList(
|
||||
"HiddenServiceDir " + hostnameFile.getParentFile().getAbsolutePath(),
|
||||
"HiddenServicePort " + hiddenServicePort + " 127.0.0.1:" + localPort);
|
||||
controlConnection.setConf(config);
|
||||
controlConnection.saveConf();
|
||||
// Wait for the hostname file to be created/updated
|
||||
if (!hostNameFileObserver.poll(HOSTNAME_TIMEOUT, MILLISECONDS)) {
|
||||
FileUtilities.listFilesToLog(hostnameFile.getParentFile());
|
||||
throw new RuntimeException("Wait for hidden service hostname file to be created expired.");
|
||||
}
|
||||
|
||||
// Publish the hidden service's onion hostname in transport properties
|
||||
String hostname = new String(FileUtilities.read(hostnameFile), "UTF-8").trim();
|
||||
LOG.info("Hidden service config has completed.");
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the Tor OP Process. Once you have called this method nothing is going to work until you either call
|
||||
* startWithRepeat or installAndStartTorOp
|
||||
*
|
||||
* @throws java.io.IOException - File errors
|
||||
*/
|
||||
public synchronized void stop() throws IOException {
|
||||
try {
|
||||
if (controlConnection == null) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Stopping Tor");
|
||||
controlConnection.setConf("DisableNetwork", "1");
|
||||
controlConnection.shutdownTor("TERM");
|
||||
} finally {
|
||||
if (controlSocket != null) {
|
||||
controlSocket.close();
|
||||
}
|
||||
controlConnection = null;
|
||||
controlSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the Tor OP is running (e.g. fully bootstrapped) and open to network connections.
|
||||
*
|
||||
* @return True if running
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
*/
|
||||
public synchronized boolean isRunning() throws IOException {
|
||||
return isBootstrapped() && isNetworkEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the Tor OP if it should accept network connections
|
||||
*
|
||||
* @param enable If true then the Tor OP will accept SOCKS connections, otherwise not.
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
*/
|
||||
public synchronized void enableNetwork(boolean enable) throws IOException {
|
||||
if (controlConnection == null) {
|
||||
throw new RuntimeException("Tor is not running!");
|
||||
}
|
||||
LOG.info("Enabling network: " + enable);
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if Tor OP is accepting network connections
|
||||
*
|
||||
* @return True if network is enabled (that doesn't mean that the device is online, only that the Tor OP is trying
|
||||
* to connect to the network)
|
||||
* @throws java.io.IOException - IO exceptions
|
||||
*/
|
||||
public synchronized boolean isNetworkEnabled() throws IOException {
|
||||
if (controlConnection == null) {
|
||||
throw new RuntimeException("Tor is not running!");
|
||||
}
|
||||
|
||||
List<ConfigEntry> disableNetworkSettingValues = controlConnection.getConf("DisableNetwork");
|
||||
boolean result = false;
|
||||
// It's theoretically possible for us to get multiple values back, if even one is false then we will
|
||||
// assume all are false
|
||||
for (ConfigEntry configEntry : disableNetworkSettingValues) {
|
||||
if (configEntry.value.equals("1")) {
|
||||
return false;
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the boot strap process has completed.
|
||||
*
|
||||
* @return True if complete
|
||||
*/
|
||||
public synchronized boolean isBootstrapped() {
|
||||
if (controlConnection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String phase = null;
|
||||
try {
|
||||
phase = controlConnection.getInfo("status/bootstrap-phase");
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Control connection is not responding properly to getInfo", e);
|
||||
}
|
||||
|
||||
if (phase != null && phase.contains("PROGRESS=100")) {
|
||||
LOG.info("Tor has already bootstrapped");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs all necessary files and starts the Tor OP in offline mode (e.g. networkEnabled(false)). This would
|
||||
* only be used if you wanted to start the Tor OP so that the install and related is all done but aren't ready to
|
||||
* actually connect it to the network.
|
||||
*
|
||||
* @return True if all files installed and Tor OP successfully started
|
||||
* @throws java.io.IOException - IO Exceptions
|
||||
* @throws java.lang.InterruptedException - If we are, well, interrupted
|
||||
*/
|
||||
public synchronized boolean installAndStartTorOp() throws IOException, InterruptedException {
|
||||
// The Tor OP will die if it looses the connection to its socket so if there is no controlSocket defined
|
||||
// then Tor is dead. This assumes, of course, that takeOwnership works and we can't end up with Zombies.
|
||||
if (controlConnection != null) {
|
||||
LOG.info("Tor is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
// The code below is why this method is synchronized, we don't want two instances of it running at once
|
||||
// as the result would be a mess of screwed up files and connections.
|
||||
LOG.info("Tor is not running");
|
||||
|
||||
installAndConfigureFiles();
|
||||
|
||||
LOG.info("Starting Tor");
|
||||
File cookieFile = onionProxyContext.getCookieFile();
|
||||
if (cookieFile.getParentFile().exists() == false &&
|
||||
cookieFile.getParentFile().mkdirs() == false) {
|
||||
throw new RuntimeException("Could not create cookieFile parent directory");
|
||||
}
|
||||
|
||||
// The original code from Briar watches individual files, not a directory and Android's file observer
|
||||
// won't work on files that don't exist. Rather than take 5 seconds to rewrite Briar's code I instead
|
||||
// just make sure the file exists
|
||||
if (cookieFile.exists() == false && cookieFile.createNewFile() == false) {
|
||||
throw new RuntimeException("Could not create cookieFile");
|
||||
}
|
||||
|
||||
File workingDirectory = onionProxyContext.getWorkingDirectory();
|
||||
// Watch for the auth cookie file being created/updated
|
||||
WriteObserver cookieObserver = onionProxyContext.generateWriteObserver(cookieFile);
|
||||
// Start a new Tor process
|
||||
String torPath = onionProxyContext.getTorExecutableFile().getAbsolutePath();
|
||||
String configPath = onionProxyContext.getTorrcFile().getAbsolutePath();
|
||||
String pid = onionProxyContext.getProcessId();
|
||||
String[] cmd = {torPath, "-f", configPath, OWNER, pid};
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
|
||||
onionProxyContext.setEnvironmentArgsAndWorkingDirectoryForStart(processBuilder);
|
||||
Process torProcess = null;
|
||||
try {
|
||||
// torProcess = Runtime.getRuntime().exec(cmd, env, workingDirectory);
|
||||
torProcess = processBuilder.start();
|
||||
CountDownLatch controlPortCountDownLatch = new CountDownLatch(1);
|
||||
eatStream(torProcess.getInputStream(), false, controlPortCountDownLatch);
|
||||
eatStream(torProcess.getErrorStream(), true, null);
|
||||
|
||||
// On platforms other than Windows we run as a daemon and so we need to wait for the process to detach
|
||||
// or exit. In the case of Windows the equivalent is running as a service and unfortunately that requires
|
||||
// managing the service, such as turning it off or uninstalling it when it's time to move on. Any number
|
||||
// of errors can prevent us from doing the cleanup and so we would leave the process running around. Rather
|
||||
// than do that on Windows we just let the process run on the exec and hence don't look for an exit code.
|
||||
// This does create a condition where the process has exited due to a problem but we should hopefully
|
||||
// detect that when we try to use the control connection.
|
||||
if (OsData.getOsType() != OsData.OsType.Windows) {
|
||||
int exit = torProcess.waitFor();
|
||||
torProcess = null;
|
||||
if (exit != 0) {
|
||||
LOG.warn("Tor exited with value " + exit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the auth cookie file to be created/updated
|
||||
if (!cookieObserver.poll(COOKIE_TIMEOUT, MILLISECONDS)) {
|
||||
LOG.warn("Auth cookie not created");
|
||||
FileUtilities.listFilesToLog(workingDirectory);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now we should be able to connect to the new process
|
||||
controlPortCountDownLatch.await();
|
||||
controlSocket = new Socket("127.0.0.1", control_port);
|
||||
|
||||
// Open a control connection and authenticate using the cookie file
|
||||
TorControlConnection controlConnection = new TorControlConnection(controlSocket);
|
||||
controlConnection.authenticate(FileUtilities.read(cookieFile));
|
||||
// Tell Tor to exit when the control connection is closed
|
||||
controlConnection.takeOwnership();
|
||||
controlConnection.resetConf(Collections.singletonList(OWNER));
|
||||
// Register to receive events from the Tor process
|
||||
controlConnection.setEventHandler(new OnionProxyManagerEventHandler());
|
||||
controlConnection.setEvents(Arrays.asList(EVENTS));
|
||||
|
||||
// We only set the class property once the connection is in a known good state
|
||||
this.controlConnection = controlConnection;
|
||||
return true;
|
||||
} catch (SecurityException e) {
|
||||
LOG.warn(e.toString(), e);
|
||||
return false;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn("Interrupted while starting Tor", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
} finally {
|
||||
if (controlConnection == null && torProcess != null) {
|
||||
// It's possible that something 'bad' could happen after we executed exec but before we takeOwnership()
|
||||
// in which case the Tor OP will hang out as a zombie until this process is killed. This is problematic
|
||||
// when we want to do things like
|
||||
torProcess.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory in which the Tor Onion Proxy keeps its files. This is mostly intended
|
||||
* for debugging purposes.
|
||||
*
|
||||
* @return Working directory for Tor Onion Proxy files
|
||||
*/
|
||||
public File getWorkingDirectory() {
|
||||
return onionProxyContext.getWorkingDirectory();
|
||||
}
|
||||
|
||||
protected void eatStream(final InputStream inputStream, final boolean stdError, final CountDownLatch countDownLatch) {
|
||||
new Thread("eatStream") {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName(stdError ? "eatInputStream" : "eatErrorStream");
|
||||
Scanner scanner = new Scanner(inputStream);
|
||||
try {
|
||||
while (scanner.hasNextLine()) {
|
||||
if (stdError) {
|
||||
LOG.error(scanner.nextLine());
|
||||
} else {
|
||||
String nextLine = scanner.nextLine();
|
||||
// We need to find the line where it tells us what the control port is.
|
||||
// The line that will appear in stdio with the control port looks like:
|
||||
// Control listener listening on port 39717.
|
||||
if (nextLine.contains("Control listener listening on port ")) {
|
||||
// For the record, I hate regex so I'm doing this manually
|
||||
control_port =
|
||||
Integer.parseInt(
|
||||
nextLine.substring(nextLine.lastIndexOf(" ") + 1, nextLine.length() - 1));
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
LOG.info(nextLine);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
scanner.close();
|
||||
try {
|
||||
inputStream.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't close input stream in eatStream", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
protected synchronized void installAndConfigureFiles() throws IOException, InterruptedException {
|
||||
onionProxyContext.installFiles();
|
||||
|
||||
if (!setExecutable(onionProxyContext.getTorExecutableFile())) {
|
||||
throw new RuntimeException("could not make Tor executable.");
|
||||
}
|
||||
|
||||
// We need to edit the config file to specify exactly where the cookie/geoip files should be stored, on
|
||||
// Android this is always a fixed location relative to the configFiles which is why this extra step
|
||||
// wasn't needed in Briar's Android code. But in Windows it ends up in the user's AppData/Roaming. Rather
|
||||
// than track it down we just tell Tor where to put it.
|
||||
PrintWriter printWriter = null;
|
||||
try {
|
||||
printWriter = new PrintWriter(new BufferedWriter(new FileWriter(onionProxyContext.getTorrcFile(), true)));
|
||||
printWriter.println("CookieAuthFile " + onionProxyContext.getCookieFile().getAbsolutePath());
|
||||
// For some reason the GeoIP's location can only be given as a file name, not a path and it has
|
||||
// to be in the data directory so we need to set both
|
||||
printWriter.println("DataDirectory " + onionProxyContext.getWorkingDirectory().getAbsolutePath());
|
||||
printWriter.println("GeoIPFile " + onionProxyContext.getGeoIpFile().getName());
|
||||
printWriter.println("GeoIPv6File " + onionProxyContext.getGeoIpv6File().getName());
|
||||
} finally {
|
||||
if (printWriter != null) {
|
||||
printWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alas old versions of Android do not support setExecutable.
|
||||
*
|
||||
* @param f File to make executable
|
||||
* @return True if it worked, otherwise false.
|
||||
*/
|
||||
protected abstract boolean setExecutable(File f);
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright (C) 2011-2014 Sublime Software Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Logs the data we get from notifications from the Tor OP. This is really just
|
||||
* meant for debugging.
|
||||
*/
|
||||
public class OnionProxyManagerEventHandler implements EventHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OnionProxyManagerEventHandler.class);
|
||||
|
||||
@Override
|
||||
public void circuitStatus(String status, String id, String path) {
|
||||
String msg = "CircuitStatus: " + id + " " + status + ", " + path;
|
||||
LOG.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamStatus(String status, String id, String target) {
|
||||
LOG.info("streamStatus: status: " + status + ", id: " + id + ", target: " + target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
LOG.info("OR connection: status: " + status + ", orName: " + orName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bandwidthUsed(long read, long written) {
|
||||
LOG.info("bandwidthUsed: read: " + read + ", written: " + written);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newDescriptors(List<String> orList) {
|
||||
Iterator<String> iterator = orList.iterator();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
while (iterator.hasNext()) {
|
||||
stringBuilder.append(iterator.next());
|
||||
}
|
||||
LOG.info("newDescriptors: " + stringBuilder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void message(String severity, String msg) {
|
||||
LOG.info("message: severity: " + severity + ", msg: " + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unrecognized(String type, String msg) {
|
||||
LOG.info("unrecognized: type: " + type + ", msg: " + msg);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright (C) 2011-2014 Sublime Software Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class OsData {
|
||||
public enum OsType {Windows, Linux32, Linux64, Mac, Android}
|
||||
|
||||
private static OsType detectedType = null;
|
||||
|
||||
public static OsType getOsType() {
|
||||
if (detectedType == null) {
|
||||
detectedType = actualGetOsType();
|
||||
}
|
||||
|
||||
return detectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type of OS we are running on
|
||||
*/
|
||||
protected static OsType actualGetOsType() {
|
||||
|
||||
//This also works for ART
|
||||
if (System.getProperty("java.vm.name").contains("Dalvik")) {
|
||||
return OsType.Android;
|
||||
}
|
||||
|
||||
String osName = System.getProperty("os.name");
|
||||
if (osName.contains("Windows")) {
|
||||
return OsType.Windows;
|
||||
} else if (osName.contains("Mac")) {
|
||||
return OsType.Mac;
|
||||
} else if (osName.contains("Linux")) {
|
||||
return getLinuxType();
|
||||
}
|
||||
throw new RuntimeException("Unsupported OS");
|
||||
}
|
||||
|
||||
protected static OsType getLinuxType() {
|
||||
String[] cmd = {"uname", "-m"};
|
||||
Process unameProcess = null;
|
||||
try {
|
||||
String unameOutput;
|
||||
unameProcess = Runtime.getRuntime().exec(cmd);
|
||||
|
||||
Scanner scanner = new Scanner(unameProcess.getInputStream());
|
||||
if (scanner.hasNextLine()) {
|
||||
unameOutput = scanner.nextLine();
|
||||
scanner.close();
|
||||
} else {
|
||||
scanner.close();
|
||||
throw new RuntimeException("Couldn't get output from uname call");
|
||||
}
|
||||
|
||||
int exit = unameProcess.waitFor();
|
||||
if (exit != 0) {
|
||||
throw new RuntimeException("Uname returned error code " + exit);
|
||||
}
|
||||
|
||||
if (unameOutput.compareTo("i686") == 0) {
|
||||
return OsType.Linux32;
|
||||
}
|
||||
if (unameOutput.compareTo("x86_64") == 0) {
|
||||
return OsType.Linux64;
|
||||
}
|
||||
throw new RuntimeException("Could not understand uname output, not sure what bitness");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Uname failure", e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Uname failure", e);
|
||||
} finally {
|
||||
|
||||
if (unameProcess != null) {
|
||||
unameProcess.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright (c) Microsoft Open Technologies, Inc.
|
||||
All Rights Reserved
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache 2 License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package com.msopentech.thali.toronionproxy;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Android uses FileObserver and Java uses the WatchService, this class abstracts the two.
|
||||
*/
|
||||
public interface WriteObserver {
|
||||
/**
|
||||
* Waits timeout of unit to see if file is modified
|
||||
*
|
||||
* @param timeout How long to wait before returning
|
||||
* @param unit Unit to wait in
|
||||
* @return True if file was modified, false if it was not
|
||||
*/
|
||||
boolean poll(long timeout, TimeUnit unit);
|
||||
}
|
229
jtorproxy/src/main/java/io/nucleo/net/Connection.java
Normal file
229
jtorproxy/src/main/java/io/nucleo/net/Connection.java
Normal file
|
@ -0,0 +1,229 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import io.nucleo.net.proto.ContainerMessage;
|
||||
import io.nucleo.net.proto.ControlMessage;
|
||||
import io.nucleo.net.proto.Message;
|
||||
import io.nucleo.net.proto.exceptions.ConnectionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class Connection implements Closeable {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Connection.class);
|
||||
|
||||
private final Socket socket;
|
||||
private final ObjectOutputStream out;
|
||||
private final ObjectInputStream in;
|
||||
private final LinkedList<ConnectionListener> connectionListeners;
|
||||
private final String peer;
|
||||
private boolean running;
|
||||
private final AtomicBoolean available;
|
||||
private final AtomicBoolean listening;
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final InputStreamListener inputStreamListener;
|
||||
|
||||
private final AtomicBoolean heartBeating;
|
||||
|
||||
public Connection(String peer, Socket socket) throws IOException {
|
||||
this(peer, socket, Node.prepareOOSForSocket(socket), new ObjectInputStream(socket.getInputStream()));
|
||||
}
|
||||
|
||||
Connection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in) {
|
||||
log.debug("Initiating new connection");
|
||||
this.available = new AtomicBoolean(false);
|
||||
this.peer = peer;
|
||||
this.socket = socket;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
running = true;
|
||||
listening = new AtomicBoolean(false);
|
||||
heartBeating = new AtomicBoolean(false);
|
||||
this.connectionListeners = new LinkedList<>();
|
||||
this.inputStreamListener = new InputStreamListener();
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public abstract boolean isIncoming();
|
||||
|
||||
public void addMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setConnectionListeners(Collection<ConnectionListener> listeners) {
|
||||
synchronized (listeners) {
|
||||
this.connectionListeners.clear();
|
||||
this.connectionListeners.addAll(listeners);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMessageListener(ConnectionListener listener) {
|
||||
synchronized (connectionListeners) {
|
||||
connectionListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
void sendMsg(Message msg) throws IOException {
|
||||
out.writeObject(msg);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void sendMessage(ContainerMessage msg) throws IOException {
|
||||
if (!available.get())
|
||||
throw new IOException("Connection is not yet available!");
|
||||
sendMsg(msg);
|
||||
}
|
||||
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
log.debug("RXD: " + msg.toString());
|
||||
if (msg instanceof ContainerMessage) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners)
|
||||
l.onMessage(this, (ContainerMessage) msg);
|
||||
}
|
||||
} else {
|
||||
if (msg instanceof ControlMessage) {
|
||||
switch ((ControlMessage) msg) {
|
||||
case DISCONNECT:
|
||||
close(false, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, true));
|
||||
break;
|
||||
case AVAILABLE:
|
||||
startHeartbeat();
|
||||
onReady();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onReady() {
|
||||
if (!available.getAndSet(true)) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onReady(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onDisconnect();
|
||||
|
||||
private void onDisconn(DisconnectReason reason) {
|
||||
onDisconnect();
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onDisconnect(this, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTimeout() {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.TIMEOUT);
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onError(Exception e) {
|
||||
synchronized (connectionListeners) {
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onError(this, new ConnectionException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
close(true, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, false));
|
||||
}
|
||||
|
||||
private void close(boolean graceful, DisconnectReason reason) throws IOException {
|
||||
running = false;
|
||||
onDisconn(reason);
|
||||
if (graceful) {
|
||||
try {
|
||||
sendMsg(ControlMessage.DISCONNECT);
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
socket.close();
|
||||
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
void startHeartbeat() {
|
||||
if (!heartBeating.getAndSet(true)) {
|
||||
log.debug("Starting Heartbeat");
|
||||
executorService.submit(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(30000);
|
||||
while (running) {
|
||||
try {
|
||||
log.debug("TX Heartbeat");
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
Thread.sleep(30000);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void listen() throws ConnectionException {
|
||||
if (listening.getAndSet(true))
|
||||
throw new ConnectionException("Already Listening!");
|
||||
executorService.submit(inputStreamListener);
|
||||
}
|
||||
|
||||
private class InputStreamListener implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
Message msg = (Message) in.readObject();
|
||||
onMessage(msg);
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
onTimeout();
|
||||
} else {
|
||||
if (running) {
|
||||
onError(new ConnectionException(e));
|
||||
// TODO: Fault Tolerance?
|
||||
if (e instanceof EOFException) {
|
||||
try {
|
||||
close(false, PredefinedDisconnectReason.RESET);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import io.nucleo.net.proto.ContainerMessage;
|
||||
import io.nucleo.net.proto.exceptions.ConnectionException;
|
||||
|
||||
public interface ConnectionListener {
|
||||
|
||||
public abstract void onMessage(Connection con, ContainerMessage msg);
|
||||
|
||||
public void onDisconnect(Connection con, DisconnectReason reason);
|
||||
|
||||
public void onError(Connection con, ConnectionException e);
|
||||
|
||||
public void onReady(Connection con);
|
||||
|
||||
}
|
11
jtorproxy/src/main/java/io/nucleo/net/DisconnectReason.java
Normal file
11
jtorproxy/src/main/java/io/nucleo/net/DisconnectReason.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
public interface DisconnectReason {
|
||||
|
||||
public abstract String toString();
|
||||
|
||||
public boolean isGraceful();
|
||||
|
||||
public boolean isRemote();
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class HiddenServiceDescriptor extends ServiceDescriptor {
|
||||
|
||||
private final int localPort;
|
||||
|
||||
public HiddenServiceDescriptor(String serviceName, int localPort, int servicePort) throws IOException {
|
||||
super(serviceName, servicePort);
|
||||
this.localPort = localPort;
|
||||
this.serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), localPort));
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
return localPort;
|
||||
}
|
||||
}
|
389
jtorproxy/src/main/java/io/nucleo/net/Node.java
Normal file
389
jtorproxy/src/main/java/io/nucleo/net/Node.java
Normal file
|
@ -0,0 +1,389 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import io.nucleo.net.proto.ControlMessage;
|
||||
import io.nucleo.net.proto.HELOMessage;
|
||||
import io.nucleo.net.proto.IDMessage;
|
||||
import io.nucleo.net.proto.Message;
|
||||
import io.nucleo.net.proto.exceptions.ConnectionException;
|
||||
import io.nucleo.net.proto.exceptions.ProtocolViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Node {
|
||||
|
||||
/**
|
||||
* Use this whenever to flush the socket header over the socket!
|
||||
*
|
||||
* @param socket the socket to construct an objectOutputStream from
|
||||
* @return the outputstream from the socket
|
||||
* @throws IOException in case something goes wrong, duh!
|
||||
*/
|
||||
static ObjectOutputStream prepareOOSForSocket(Socket socket) throws IOException {
|
||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
||||
|
||||
out.flush();
|
||||
return out;
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Node.class);
|
||||
|
||||
private final ServiceDescriptor descriptor;
|
||||
|
||||
private final HashMap<String, Connection> connections;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final TorNode tor;
|
||||
|
||||
private final AtomicBoolean serverRunning;
|
||||
|
||||
public Node(TCPServiceDescriptor descriptor) {
|
||||
this(null, descriptor);
|
||||
}
|
||||
|
||||
public Node(HiddenServiceDescriptor descriptor, TorNode<?, ?> tor) {
|
||||
this(tor, descriptor);
|
||||
}
|
||||
|
||||
private Node(TorNode<?, ?> tor, ServiceDescriptor descriptor) {
|
||||
this.connections = new HashMap<>();
|
||||
this.descriptor = descriptor;
|
||||
this.tor = tor;
|
||||
this.serverRunning = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public String getLocalName() {
|
||||
return descriptor.getFullAddress();
|
||||
}
|
||||
|
||||
public Connection connect(String peer, Collection<ConnectionListener> listeners)
|
||||
throws NumberFormatException, IOException {
|
||||
if (!serverRunning.get()) {
|
||||
throw new IOException("This node has not been started yet!");
|
||||
}
|
||||
if (peer.equals(descriptor.getFullAddress()))
|
||||
throw new IOException("If you find yourself talking to yourself too often, you should really seek help!");
|
||||
synchronized (connections) {
|
||||
if (connections.containsKey(peer))
|
||||
throw new IOException("Already connected to " + peer);
|
||||
}
|
||||
|
||||
final Socket sock = connectToService(peer);
|
||||
return new OutgoingConnection(peer, sock, listeners);
|
||||
}
|
||||
|
||||
private Socket connectToService(String hostname, int port) throws IOException, UnknownHostException, SocketException {
|
||||
final Socket sock;
|
||||
if (tor != null)
|
||||
sock = tor.connectToHiddenService(hostname, port);
|
||||
else
|
||||
sock = new Socket(hostname, port);
|
||||
sock.setSoTimeout(60000);
|
||||
return sock;
|
||||
}
|
||||
|
||||
private Socket connectToService(String peer) throws IOException, UnknownHostException, SocketException {
|
||||
final String[] split = peer.split(Pattern.quote(":"));
|
||||
return connectToService(split[0], Integer.parseInt(split[1]));
|
||||
|
||||
}
|
||||
|
||||
public synchronized Server startListening(ServerConnectListener listener) throws IOException {
|
||||
if (serverRunning.getAndSet(true))
|
||||
throw new IOException("This node is already listening!");
|
||||
final Server server = new Server(descriptor.getServerSocket(), listener);
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
public Connection getConnection(String peerAddress) {
|
||||
synchronized (connections) {
|
||||
return connections.get(peerAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Connection> getConnections() {
|
||||
synchronized (connections) {
|
||||
return new HashSet<Connection>(connections.values());
|
||||
}
|
||||
}
|
||||
|
||||
public class Server extends Thread {
|
||||
|
||||
private boolean running;
|
||||
|
||||
private final ServerSocket serverSocket;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final ServerConnectListener serverConnectListener;
|
||||
|
||||
private Server(ServerSocket serverSocket, ServerConnectListener listener) {
|
||||
super("Server");
|
||||
this.serverSocket = descriptor.getServerSocket();
|
||||
this.serverConnectListener = listener;
|
||||
running = true;
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
running = false;
|
||||
synchronized (connections) {
|
||||
final Set<Connection> conns = new HashSet<Connection>(connections.values());
|
||||
for (Connection con : conns) {
|
||||
con.close();
|
||||
}
|
||||
}
|
||||
serverSocket.close();
|
||||
try {
|
||||
executorService.awaitTermination(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Node.this.serverRunning.set(false);
|
||||
log.debug("Server successfully shutdown");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (running) {
|
||||
final Socket socket = serverSocket.accept();
|
||||
log.info("Accepting Client on port " + socket.getLocalPort());
|
||||
executorService.submit(new Acceptor(socket));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (running)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyIdentity(HELOMessage helo, ObjectInputStream in) throws IOException {
|
||||
log.debug("Verifying HELO msg");
|
||||
final Socket sock = connectToService(helo.getHostname(), helo.getPort());
|
||||
|
||||
log.debug("Connected to advertised client " + helo.getPeer());
|
||||
ObjectOutputStream out = prepareOOSForSocket(sock);
|
||||
final IDMessage challenge = new IDMessage(descriptor);
|
||||
out.writeObject(challenge);
|
||||
log.debug("Sent IDMessage to");
|
||||
out.flush();
|
||||
// wait for other side to close
|
||||
try {
|
||||
while (sock.getInputStream().read() != -1)
|
||||
;
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
}
|
||||
out.close();
|
||||
sock.close();
|
||||
log.debug("Closed socket after sending IDMessage");
|
||||
try {
|
||||
log.debug("Waiting for response of challenge");
|
||||
IDMessage response = (IDMessage) in.readObject();
|
||||
log.debug("Got response for challenge");
|
||||
final boolean verified = challenge.verify(response);
|
||||
log.debug("Response verified correctly!");
|
||||
return verified;
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e).printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class Acceptor implements Runnable {
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private Acceptor(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
{
|
||||
try {
|
||||
socket.setSoTimeout(60 * 1000);
|
||||
} catch (SocketException e2) {
|
||||
|
||||
e2.printStackTrace();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectInputStream objectInputStream = null;
|
||||
ObjectOutputStream out = null;
|
||||
|
||||
// get incoming data
|
||||
try {
|
||||
out = prepareOOSForSocket(socket);
|
||||
objectInputStream = new ObjectInputStream(socket.getInputStream());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String peer = null;
|
||||
try {
|
||||
log.debug("Waiting for HELO or Identification");
|
||||
final Message helo = (Message) objectInputStream.readObject();
|
||||
if (helo instanceof HELOMessage) {
|
||||
peer = ((HELOMessage) helo).getPeer();
|
||||
log.debug("Got HELO from " + peer);
|
||||
boolean alreadyConnected;
|
||||
synchronized (connections) {
|
||||
alreadyConnected = connections.containsKey(peer);
|
||||
}
|
||||
if (alreadyConnected || !verifyIdentity((HELOMessage) helo, objectInputStream)) {
|
||||
log.debug(alreadyConnected ? ("already connected to " + peer) : "verification failed");
|
||||
out.writeObject(alreadyConnected ? ControlMessage.ALREADY_CONNECTED : ControlMessage.HANDSHAKE_FAILED);
|
||||
out.writeObject(ControlMessage.DISCONNECT);
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
log.debug("Verification of " + peer + " successful");
|
||||
} else if (helo instanceof IDMessage) {
|
||||
peer = ((IDMessage) helo).getPeer();
|
||||
log.debug("got IDMessage from " + peer);
|
||||
final Connection client = connections.get(peer);
|
||||
if (client != null) {
|
||||
log.debug("Got preexisting connection for " + peer);
|
||||
client.sendMsg(((IDMessage) helo).reply());
|
||||
log.debug("Sent response for challenge");
|
||||
} else {
|
||||
log.debug("Got IDMessage for unknown connection to " + peer);
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
objectInputStream.close();
|
||||
socket.close();
|
||||
log.debug("Closed socket for identification");
|
||||
return;
|
||||
|
||||
} else
|
||||
throw new ClassNotFoundException("First Message was neither HELO, nor ID");
|
||||
} catch (ClassNotFoundException e) {
|
||||
new ProtocolViolationException(e);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
objectInputStream.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Here we go
|
||||
log.debug("Incoming Connection ready!");
|
||||
try {
|
||||
// TODO: listeners are only added afterwards, so messages can be lost!
|
||||
IncomingConnection incomingConnection = new IncomingConnection(peer, socket, out, objectInputStream);
|
||||
serverConnectListener.onConnect(incomingConnection);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IncomingConnection extends Connection {
|
||||
private IncomingConnection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in)
|
||||
throws IOException {
|
||||
super(peer, socket, out, in);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
sendMsg(ControlMessage.AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen() throws ConnectionException {
|
||||
super.listen();
|
||||
onReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(Message msg) throws IOException {
|
||||
if ((msg instanceof ControlMessage) && (ControlMessage.HEARTBEAT == msg)) {
|
||||
log.debug("RX+REPLY HEARTBEAT");
|
||||
try {
|
||||
sendMsg(ControlMessage.HEARTBEAT);
|
||||
} catch (IOException e) {
|
||||
onError(e);
|
||||
}
|
||||
} else
|
||||
super.onMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class OutgoingConnection extends Connection {
|
||||
|
||||
private OutgoingConnection(String peer, Socket socket, Collection<ConnectionListener> listeners)
|
||||
throws IOException {
|
||||
super(peer, socket);
|
||||
synchronized (connections) {
|
||||
connections.put(peer, this);
|
||||
}
|
||||
setConnectionListeners(listeners);
|
||||
try {
|
||||
listen();
|
||||
} catch (ConnectionException e) {
|
||||
// Never happens
|
||||
}
|
||||
log.debug("Sending HELO");
|
||||
sendMsg(new HELOMessage(descriptor));
|
||||
log.debug("Sent HELO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
synchronized (connections) {
|
||||
connections.remove(getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncoming() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
public enum PredefinedDisconnectReason implements DisconnectReason {
|
||||
TIMEOUT("due to timed out", false, true),
|
||||
CONNECTION_CLOSED("as ordered", true),
|
||||
RESET("due to remote reset (EOF)", false, true),
|
||||
UNKNOWN("for unknown reasons", false);
|
||||
|
||||
private Boolean remote;
|
||||
private final boolean graceful;
|
||||
private final String description;
|
||||
|
||||
private PredefinedDisconnectReason(String description, boolean graceful) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
}
|
||||
|
||||
private PredefinedDisconnectReason(String description, boolean graceful, boolean remote) {
|
||||
this.description = description;
|
||||
this.graceful = graceful;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
public static PredefinedDisconnectReason createReason(PredefinedDisconnectReason reason, boolean remote) {
|
||||
reason.remote = remote;
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGraceful() {
|
||||
return graceful;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
if (remote == null)
|
||||
return false;
|
||||
return remote;
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder bld = new StringBuilder("Connection closed ");
|
||||
if (remote != null)
|
||||
bld.append(remote ? "remotely " : "locally ");
|
||||
bld.append(description).append(" (");
|
||||
bld.append(graceful ? "graceful" : "irregular").append(" disconnect)");
|
||||
return bld.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
public interface ServerConnectListener {
|
||||
|
||||
/**
|
||||
* Called whenever an incoming connection was set up properly.
|
||||
* Connection.listen() needs to be called ASAP for the connection to become available
|
||||
*
|
||||
* @param con the newly established connection
|
||||
*/
|
||||
public void onConnect(Connection con);
|
||||
}
|
34
jtorproxy/src/main/java/io/nucleo/net/ServiceDescriptor.java
Normal file
34
jtorproxy/src/main/java/io/nucleo/net/ServiceDescriptor.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
public abstract class ServiceDescriptor {
|
||||
|
||||
protected final String hostname;
|
||||
protected final int servicePort;
|
||||
protected final ServerSocket serverSocket;
|
||||
|
||||
public ServiceDescriptor(String hostname, int servicePort) throws IOException {
|
||||
this.hostname = hostname;
|
||||
this.servicePort = servicePort;
|
||||
this.serverSocket = new ServerSocket();
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public int getServicePort() {
|
||||
return servicePort;
|
||||
}
|
||||
|
||||
public String getFullAddress() {
|
||||
return hostname + ":" + servicePort;
|
||||
}
|
||||
|
||||
public ServerSocket getServerSocket() {
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class TCPServiceDescriptor extends ServiceDescriptor {
|
||||
|
||||
public TCPServiceDescriptor(String hostname, int servicePort) throws IOException {
|
||||
super(hostname, servicePort);
|
||||
getServerSocket().bind(new InetSocketAddress(hostname, servicePort));
|
||||
}
|
||||
|
||||
}
|
170
jtorproxy/src/main/java/io/nucleo/net/TorNode.java
Normal file
170
jtorproxy/src/main/java/io/nucleo/net/TorNode.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyContext;
|
||||
import com.msopentech.thali.toronionproxy.OnionProxyManager;
|
||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public abstract class TorNode<M extends OnionProxyManager, C extends OnionProxyContext> {
|
||||
|
||||
private static final String PROXY_LOCALHOST = "127.0.0.1";
|
||||
private static final int RETRY_SLEEP = 500;
|
||||
private static final int TOTAL_SEC_PER_STARTUP = 4 * 60;
|
||||
private static final int TRIES_PER_STARTUP = 5;
|
||||
private static final int TRIES_PER_HS_STARTUP = 150;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TorNode.class);
|
||||
|
||||
private final OnionProxyManager tor;
|
||||
private final Socks5Proxy proxy;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TorNode(File torDirectory) throws IOException, InstantiationException {
|
||||
Class<M> mgr;
|
||||
Class<C> ctx;
|
||||
try {
|
||||
mgr = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
ctx = (Class<C>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
|
||||
} catch (Throwable t) {
|
||||
throw new InstantiationException(
|
||||
"Could not reify Types of OnionProxyManager and OnionProxyContext! Is this class being used with raw types?");
|
||||
}
|
||||
log.debug("Running Tornode with " + mgr.getSimpleName() + " and " + ctx.getSimpleName());
|
||||
tor = initTor(torDirectory, mgr, ctx);
|
||||
int proxyPort = tor.getIPv4LocalHostSocksPort();
|
||||
log.info("TorSocks running on port " + proxyPort);
|
||||
this.proxy = setupSocksProxy(proxyPort);
|
||||
}
|
||||
|
||||
private Socks5Proxy setupSocksProxy(int proxyPort) throws UnknownHostException {
|
||||
Socks5Proxy proxy = new Socks5Proxy(PROXY_LOCALHOST, proxyPort);
|
||||
proxy.resolveAddrLocally(false);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public Socket connectToHiddenService(String onionUrl, int port) throws IOException {
|
||||
return connectToHiddenService(onionUrl, port, 5);
|
||||
}
|
||||
|
||||
public Socket connectToHiddenService(String onionUrl, int port, int numTries) throws IOException {
|
||||
return connectToHiddenService(onionUrl, port, numTries, true);
|
||||
}
|
||||
|
||||
private Socket connectToHiddenService(String onionUrl, int port, int numTries, boolean debug) throws IOException {
|
||||
long before = GregorianCalendar.getInstance().getTimeInMillis();
|
||||
for (int i = 0; i < numTries; ++i) {
|
||||
try {
|
||||
SocksSocket ssock = new SocksSocket(proxy, onionUrl, port);
|
||||
if (debug)
|
||||
log.info("Took " + (GregorianCalendar.getInstance().getTimeInMillis() - before)
|
||||
+ " milliseconds to connect to " + onionUrl + ":" + port);
|
||||
ssock.setTcpNoDelay(true);
|
||||
return ssock;
|
||||
} catch (UnknownHostException exx) {
|
||||
try {
|
||||
if (debug)
|
||||
log.debug(
|
||||
"Try " + (i + 1) + " connecting to " + onionUrl + ":" + port + " failed. retrying...");
|
||||
Thread.sleep(RETRY_SLEEP);
|
||||
continue;
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Cannot connect to hidden service");
|
||||
}
|
||||
}
|
||||
throw new IOException("Cannot connect to hidden service");
|
||||
}
|
||||
|
||||
public HiddenServiceDescriptor createHiddenService(int localPort, int servicePort) throws IOException {
|
||||
long before = GregorianCalendar.getInstance().getTimeInMillis();
|
||||
String hiddenServiceName = tor.publishHiddenService(servicePort, localPort);
|
||||
final HiddenServiceDescriptor hiddenServiceDescriptor = new HiddenServiceDescriptor(hiddenServiceName,
|
||||
localPort, servicePort);
|
||||
return tryConnectToHiddenService(servicePort, before, hiddenServiceName, hiddenServiceDescriptor);
|
||||
|
||||
}
|
||||
|
||||
private HiddenServiceDescriptor tryConnectToHiddenService(int servicePort, long before, String hiddenServiceName,
|
||||
final HiddenServiceDescriptor hiddenServiceDescriptor) throws IOException {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
hiddenServiceDescriptor.getServerSocket().accept().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
for (int i = 0; i < TRIES_PER_HS_STARTUP; ++i) {
|
||||
try {
|
||||
final Socket socket = connectToHiddenService(hiddenServiceName, servicePort, 1, false);
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
log.info("Hidden service " + hiddenServiceName + ":" + servicePort + " is not yet reachable");
|
||||
try {
|
||||
Thread.sleep(RETRY_SLEEP);
|
||||
} catch (InterruptedException e1) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
log.info("Took " + (GregorianCalendar.getInstance().getTimeInMillis() - before)
|
||||
+ " milliseconds to connect to publish " + hiddenServiceName + ":" + servicePort);
|
||||
return hiddenServiceDescriptor;
|
||||
}
|
||||
throw new IOException("Could not publish Hidden Service!");
|
||||
}
|
||||
|
||||
public HiddenServiceDescriptor createHiddenService(int port) throws IOException {
|
||||
return createHiddenService(port, port);
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
tor.stop();
|
||||
}
|
||||
|
||||
static <M extends OnionProxyManager, C extends OnionProxyContext> OnionProxyManager initTor(File torDir,
|
||||
Class<M> mgrType, Class<C> ctxType) throws IOException {
|
||||
|
||||
log.debug("Trying to start tor in directory {}", torDir);
|
||||
C ctx;
|
||||
final M onionProxyManager;
|
||||
try {
|
||||
ctx = ctxType.getConstructor(File.class).newInstance(torDir);
|
||||
onionProxyManager = mgrType.getConstructor(OnionProxyContext.class).newInstance(ctx);
|
||||
} catch (Exception e1) {
|
||||
throw new IOException(e1);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!onionProxyManager.startWithRepeat(TOTAL_SEC_PER_STARTUP, TRIES_PER_STARTUP)) {
|
||||
throw new IOException("Could not Start Tor. Is another instance already running?");
|
||||
} else {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
onionProxyManager.stop();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
return onionProxyManager;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.nucleo.net.proto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ContainerMessage implements Message {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 9219884444024922023L;
|
||||
private final Serializable payload;
|
||||
|
||||
public ContainerMessage(Serializable payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public Serializable getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.nucleo.net.proto;
|
||||
|
||||
public enum ControlMessage implements Message {
|
||||
HEARTBEAT,
|
||||
AVAILABLE,
|
||||
HANDSHAKE_FAILED,
|
||||
ALREADY_CONNECTED,
|
||||
DISCONNECT;
|
||||
}
|
35
jtorproxy/src/main/java/io/nucleo/net/proto/HELOMessage.java
Normal file
35
jtorproxy/src/main/java/io/nucleo/net/proto/HELOMessage.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
package io.nucleo.net.proto;
|
||||
|
||||
import io.nucleo.net.ServiceDescriptor;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class HELOMessage implements Message {
|
||||
|
||||
private static final long serialVersionUID = -4582946298578924930L;
|
||||
private final String peer;
|
||||
|
||||
public HELOMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress());
|
||||
}
|
||||
|
||||
private HELOMessage(String peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return peer.split(Pattern.quote(":"))[0];
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return Integer.parseInt(peer.split(Pattern.quote(":"))[1]);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "HELO " + peer;
|
||||
}
|
||||
}
|
48
jtorproxy/src/main/java/io/nucleo/net/proto/IDMessage.java
Normal file
48
jtorproxy/src/main/java/io/nucleo/net/proto/IDMessage.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package io.nucleo.net.proto;
|
||||
|
||||
import io.nucleo.net.ServiceDescriptor;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class IDMessage implements Message {
|
||||
|
||||
private static final long serialVersionUID = -2214485311644580948L;
|
||||
private static SecureRandom rnd;
|
||||
|
||||
static {
|
||||
try {
|
||||
rnd = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final long nonce;
|
||||
|
||||
public IDMessage(ServiceDescriptor descriptor) {
|
||||
this(descriptor.getFullAddress(), rnd.nextLong());
|
||||
}
|
||||
|
||||
private IDMessage(String id, long nonce) {
|
||||
this.id = id;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public String getPeer() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public IDMessage reply() {
|
||||
return new IDMessage(id, nonce);
|
||||
}
|
||||
|
||||
public boolean verify(IDMessage msg) {
|
||||
return id.equals(msg.id) && (nonce == msg.nonce);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ID " + id;
|
||||
}
|
||||
}
|
7
jtorproxy/src/main/java/io/nucleo/net/proto/Message.java
Normal file
7
jtorproxy/src/main/java/io/nucleo/net/proto/Message.java
Normal file
|
@ -0,0 +1,7 @@
|
|||
package io.nucleo.net.proto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface Message extends Serializable {
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package io.nucleo.net.proto.exceptions;
|
||||
|
||||
public class ConnectionException extends Exception {
|
||||
public ConnectionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ConnectionException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.nucleo.net.proto.exceptions;
|
||||
|
||||
public class ProtocolViolationException extends Exception {
|
||||
|
||||
public ProtocolViolationException() {
|
||||
}
|
||||
|
||||
public ProtocolViolationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ProtocolViolationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ProtocolViolationException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
92133
jtorproxy/src/main/resources/geoip
Normal file
92133
jtorproxy/src/main/resources/geoip
Normal file
File diff suppressed because it is too large
Load diff
16321
jtorproxy/src/main/resources/geoip6
Normal file
16321
jtorproxy/src/main/resources/geoip6
Normal file
File diff suppressed because it is too large
Load diff
13
jtorproxy/src/main/resources/logback.xml
Normal file
13
jtorproxy/src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %xEx%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
BIN
jtorproxy/src/main/resources/native/linux/x64/tor.zip
Normal file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor.zip
Normal file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libcrypto.so.1.0.0
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libcrypto.so.1.0.0
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libevent-2.0.so.5
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libevent-2.0.so.5
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libgmp.so.10
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libgmp.so.10
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libssl.so.1.0.0
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libssl.so.1.0.0
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libstdc++.so.6
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/libstdc++.so.6
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/tor
Executable file
BIN
jtorproxy/src/main/resources/native/linux/x64/tor/tor
Executable file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/linux/x86/tor.zip
Normal file
BIN
jtorproxy/src/main/resources/native/linux/x86/tor.zip
Normal file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/osx/x64/tor.zip
Normal file
BIN
jtorproxy/src/main/resources/native/osx/x64/tor.zip
Normal file
Binary file not shown.
BIN
jtorproxy/src/main/resources/native/windows/x86/tor.zip
Normal file
BIN
jtorproxy/src/main/resources/native/windows/x86/tor.zip
Normal file
Binary file not shown.
7
jtorproxy/src/main/resources/torrc
Normal file
7
jtorproxy/src/main/resources/torrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
ControlPort auto
|
||||
CookieAuthentication 1
|
||||
DisableNetwork 1
|
||||
PidFile pid
|
||||
RunAsDaemon 1
|
||||
SafeSocks 1
|
||||
SOCKSPort auto
|
119
jtorproxy/src/test/java/io/nucleo/net/TorNodeTest.java
Normal file
119
jtorproxy/src/test/java/io/nucleo/net/TorNodeTest.java
Normal file
|
@ -0,0 +1,119 @@
|
|||
package io.nucleo.net;
|
||||
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class TorNodeTest {
|
||||
|
||||
private static final int hsPort = 55555;
|
||||
private static CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
|
||||
private static TorNode<JavaOnionProxyManager, JavaOnionProxyContext> node;
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException, InstantiationException {
|
||||
File dir = new File("tor-test");
|
||||
dir.mkdirs();
|
||||
for (String str : args)
|
||||
System.out.print(str + " ");
|
||||
node = new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(dir) {
|
||||
};
|
||||
final ServiceDescriptor hiddenService = node.createHiddenService(hsPort);
|
||||
new Thread(new Server(hiddenService.getServerSocket())).start();
|
||||
serverLatch.await();
|
||||
|
||||
if (args.length != 2)
|
||||
new Client(node.connectToHiddenService(hiddenService.getHostname(), hiddenService.getServicePort())).run();
|
||||
else {
|
||||
System.out.println("\nHs Running, pres return to connect to " + args[0] + ":" + args[1]);
|
||||
final Scanner scanner = new Scanner(System.in);
|
||||
scanner.nextLine();
|
||||
new Client(node.connectToHiddenService(args[0], Integer.parseInt(args[1])), scanner).run();
|
||||
}
|
||||
|
||||
// node.shutdown();
|
||||
}
|
||||
|
||||
private static class Client implements Runnable {
|
||||
|
||||
private Socket sock;
|
||||
private final Scanner scanner;
|
||||
|
||||
private Client(Socket sock, Scanner scanner) {
|
||||
this.sock = sock;
|
||||
this.scanner = scanner;
|
||||
}
|
||||
|
||||
private Client(Socket sock) {
|
||||
this(sock, new Scanner(System.in));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
|
||||
OutputStreamWriter out = new OutputStreamWriter(sock.getOutputStream());
|
||||
System.out.print("\n> ");
|
||||
String input = scanner.nextLine();
|
||||
out.write(input + "\n");
|
||||
out.flush();
|
||||
String aLine = null;
|
||||
while ((aLine = in.readLine()) != null) {
|
||||
System.out.println(aLine);
|
||||
System.out.print("\n> ");
|
||||
input = scanner.nextLine();
|
||||
out.write(input + "\n");
|
||||
out.flush();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Server implements Runnable {
|
||||
private final ServerSocket socket;
|
||||
|
||||
private Server(ServerSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
System.out.println("Wating for incoming connections...");
|
||||
serverLatch.countDown();
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
Socket sock = socket.accept();
|
||||
System.out.println("Accepting Client " + sock.getRemoteSocketAddress() + " on port " + sock.getLocalPort());
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
|
||||
OutputStreamWriter out = new OutputStreamWriter(sock.getOutputStream());
|
||||
String aLine = null;
|
||||
while ((aLine = in.readLine()) != null) {
|
||||
System.out.println("ECHOING " + aLine);
|
||||
out.write("ECHO " + aLine + "\n");
|
||||
out.flush();
|
||||
if (aLine.equals("END"))
|
||||
break;
|
||||
}
|
||||
sock.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
146
jtorproxy/src/test/java/io/nucleo/net/node/NodeTest.java
Normal file
146
jtorproxy/src/test/java/io/nucleo/net/node/NodeTest.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package io.nucleo.net.node;
|
||||
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
|
||||
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
|
||||
import io.nucleo.net.*;
|
||||
import io.nucleo.net.Node.Server;
|
||||
import io.nucleo.net.proto.ContainerMessage;
|
||||
import io.nucleo.net.proto.exceptions.ConnectionException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NodeTest {
|
||||
private static boolean running;
|
||||
|
||||
static Connection currentCon = null;
|
||||
|
||||
static class Listener implements ConnectionListener {
|
||||
@Override
|
||||
public void onMessage(Connection con, ContainerMessage msg) {
|
||||
System.err.println("RXD: " + msg.getPayload().toString() + " < " + con.getPeer());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(Connection con, DisconnectReason reason) {
|
||||
if (con.equals(currentCon))
|
||||
currentCon = null;
|
||||
System.err.println(con.getPeer() + " has disconnected: " + reason.toString());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Connection con, ConnectionException e) {
|
||||
System.err.println("Connection " + con.getPeer() + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Connection con) {
|
||||
System.err.println(con.getPeer() + " is ready");
|
||||
currentCon = con;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InstantiationException, IOException {
|
||||
if ((args.length != 2) && (args.length != 1)) {
|
||||
System.err.println("1 or 2 params required: service port, or hidden service dir + port");
|
||||
return;
|
||||
}
|
||||
final Node node;
|
||||
final ArrayList<ConnectionListener> listener = new ArrayList<>(1);
|
||||
listener.add(new Listener());
|
||||
if (args.length == 2) {
|
||||
File dir = new File(args[0]);
|
||||
dir.mkdirs();
|
||||
TorNode<JavaOnionProxyManager, JavaOnionProxyContext> tor = new TorNode<JavaOnionProxyManager, JavaOnionProxyContext>(
|
||||
dir) {
|
||||
};
|
||||
|
||||
node = new Node(tor.createHiddenService(Integer.parseInt(args[1])), tor);
|
||||
} else {
|
||||
node = new Node(new TCPServiceDescriptor("localhost", Integer.parseInt(args[0])));
|
||||
}
|
||||
|
||||
final Server server = node.startListening(new ServerConnectListener() {
|
||||
@Override
|
||||
public void onConnect(Connection con) {
|
||||
con.addMessageListener(listener.get(0));
|
||||
try {
|
||||
con.listen();
|
||||
} catch (ConnectionException e) {
|
||||
// never happens
|
||||
}
|
||||
System.out.println("Connection to " + con.getPeer() + " established :-)");
|
||||
|
||||
}
|
||||
});
|
||||
running = true;
|
||||
Scanner scan = new Scanner(System.in);
|
||||
System.out.println("READY!");
|
||||
String line = null;
|
||||
System.out.print("\n" + node.getLocalName() + " >");
|
||||
while (running && ((line = scan.nextLine()) != null)) {
|
||||
String[] cmd = {line};
|
||||
if (line.contains(" "))
|
||||
cmd = line.split(Pattern.quote(" "));
|
||||
|
||||
switch (cmd[0]) {
|
||||
case "con":
|
||||
if (cmd.length == 2) {
|
||||
String host = cmd[1];
|
||||
try {
|
||||
node.connect(host, listener);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "list":
|
||||
int i = 0;
|
||||
for (Connection con : new LinkedList<>(node.getConnections())) {
|
||||
System.out.println("\t" + (i++) + " " + con.getPeer());
|
||||
}
|
||||
break;
|
||||
case "sel":
|
||||
try {
|
||||
if (cmd.length == 2) {
|
||||
int index = Integer.parseInt(cmd[1]);
|
||||
currentCon = new LinkedList<>(node.getConnections()).get(index);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
break;
|
||||
case "send":
|
||||
try {
|
||||
if (cmd.length >= 2) {
|
||||
if (currentCon != null) {
|
||||
currentCon.sendMessage(new ContainerMessage(line.substring(4)));
|
||||
} else
|
||||
System.err.println("NO node active!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
break;
|
||||
case "end":
|
||||
server.shutdown();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
System.out.print("\n" + node.getLocalName() + ":" + (currentCon == null ? "" : currentCon.getPeer()) + " >");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,49 +17,10 @@
|
|||
<artifactId>common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.msopentech.thali</groupId>
|
||||
<artifactId>universal</artifactId>
|
||||
<version>0.0.3-SNAPSHOT</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${basedir}/src/main/resources/jars/universal-0.0.3-SNAPSHOT.jar</systemPath>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.msopentech.thali</groupId>
|
||||
<artifactId>java</artifactId>
|
||||
<version>0.0.3-SNAPSHOT</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${basedir}/src/main/resources/jars/java-0.0.3-SNAPSHOT.jar</systemPath>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ravn.jsocks</groupId>
|
||||
<artifactId>jsocks</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${basedir}/src/main/resources/jars/jsocks-0.0.1-SNAPSHOT.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.freehaven</groupId>
|
||||
<artifactId>jtorctl</artifactId>
|
||||
<version>2015-09</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${basedir}/src/main/resources/jars/jtorctl-2015-09.jar</systemPath>
|
||||
<groupId>io.bitsquare</groupId>
|
||||
<artifactId>jtorproxy</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
|
|
@ -85,20 +85,16 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener
|
|||
Connection newConnection;
|
||||
log.trace("We have not found any connection for that peerAddress. " +
|
||||
"We will create a new outbound connection.");
|
||||
try {
|
||||
Socket socket = getSocket(peerAddress); // can take a while when using tor
|
||||
newConnection = new Connection(socket, NetworkNode.this, NetworkNode.this);
|
||||
newConnection.setPeerAddress(peerAddress);
|
||||
outBoundConnections.add(newConnection);
|
||||
Socket socket = getSocket(peerAddress); // can take a while when using tor
|
||||
newConnection = new Connection(socket, NetworkNode.this, NetworkNode.this);
|
||||
newConnection.setPeerAddress(peerAddress);
|
||||
outBoundConnections.add(newConnection);
|
||||
|
||||
log.info("\n\nNetworkNode created new outbound connection:"
|
||||
+ "\npeerAddress=" + peerAddress.port
|
||||
+ "\nconnection.uid=" + newConnection.getUid()
|
||||
+ "\nmessage=" + message
|
||||
+ "\n\n");
|
||||
} catch (Throwable t) {
|
||||
throw t;
|
||||
}
|
||||
log.info("\n\nNetworkNode created new outbound connection:"
|
||||
+ "\npeerAddress=" + peerAddress.port
|
||||
+ "\nconnection.uid=" + newConnection.getUid()
|
||||
+ "\nmessage=" + message
|
||||
+ "\n\n");
|
||||
|
||||
newConnection.sendMessage(message);
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
pom.xml
3
pom.xml
|
@ -40,6 +40,9 @@
|
|||
<modules>
|
||||
<module>common</module>
|
||||
<module>core</module>
|
||||
<module>jsocks</module>
|
||||
<module>jtorctl</module>
|
||||
<module>jtorproxy</module>
|
||||
<module>network</module>
|
||||
<module>seednode</module>
|
||||
<module>gui</module>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue