├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── pom.xml
├── tor.external
├── pom.xml
└── src
│ ├── main
│ └── kotlin
│ │ └── org
│ │ └── berndpruenster
│ │ └── netlayer
│ │ └── tor
│ │ └── ExternalTor.kt
│ └── test
│ └── kotlin
│ └── org
│ └── berndpruenster
│ └── netlayer
│ └── tor
│ └── demo
│ └── ExternalDemo.kt
├── tor.native
├── pom.xml
└── src
│ ├── main
│ ├── kotlin
│ │ └── org
│ │ │ └── berndpruenster
│ │ │ └── netlayer
│ │ │ └── tor
│ │ │ ├── FileUtilities.kt
│ │ │ ├── NativeTor.kt
│ │ │ ├── NativeWatchObserver.kt
│ │ │ └── TorContext.kt
│ └── resources
│ │ └── native
│ │ ├── linux
│ │ └── torrc.native
│ │ ├── osx
│ │ └── torrc.native
│ │ └── windows
│ │ └── torrc.native
│ └── test
│ ├── java
│ └── org
│ │ └── berndpruenster
│ │ └── netlayer
│ │ └── tor
│ │ └── demo
│ │ └── JavaDemo.java
│ └── kotlin
│ └── org
│ └── berndpruenster
│ └── netlayer
│ └── tor
│ └── demo
│ └── Demo.kt
└── tor
├── pom.xml
└── src
└── main
├── kotlin
└── org
│ └── berndpruenster
│ └── netlayer
│ └── tor
│ ├── OsType.kt
│ ├── Tor.kt
│ ├── TorEventHandler.kt
│ └── TorSockets.kt
└── resources
├── torrc
└── torrc.defaults
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 |
14 | .classpath
15 | .project
16 | .settings/
17 |
18 | .idea
19 | *.iml
20 | .settings
21 | tor-demo
22 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JesusMcCloud/netlayer/3467ae96d1abd834246e6c3629b16e16f6c38cdc/.gitmodules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | European Union Public Licence
2 | V. 1.1
3 |
4 | EUPL (c) the European Community 2007
5 |
6 |
7 | This European Union Public Licence (the "EUPL") applies to the Work or Software
8 | (as defined below) which is provided under the terms of this Licence. Any use
9 | of the Work, other than as authorised under this Licence is prohibited (to the
10 | extent such use is covered by a right of the copyright holder of the Work).
11 |
12 | The Original Work is provided under the terms of this Licence when the Licensor
13 | (as defined below) has placed the following notice immediately following the
14 | copyright notice for the Original Work:
15 |
16 | Licensed under the EUPL V.1.1
17 |
18 | or has expressed by any other mean his willingness to license under the EUPL.
19 |
20 |
21 | 1. Definitions
22 |
23 | In this Licence, the following terms have the following meaning:
24 |
25 | * The Licence: this Licence.
26 |
27 | * The Original Work or the Software: the software distributed and/or
28 | communicated by the Licensor under this Licence, available as Source Code
29 | and also as Executable Code as the case may be.
30 |
31 | * Derivative Works: the works or software that could be created by the
32 | Licensee, based upon the Original Work or modifications thereof. This
33 | Licence does not define the extent of modification or dependence on the
34 | Original Work required in order to classify a work as a Derivative Work;
35 | this extent is determined by copyright law applicable in the country
36 | mentioned in Article 15.
37 |
38 | * The Work: the Original Work and/or its Derivative Works.
39 |
40 | * The Source Code: the human-readable form of the Work which is the most
41 | convenient for people to study and modify.
42 |
43 | * The Executable Code: any code which has generally been compiled and which is
44 | meant to be interpreted by a computer as a program.
45 |
46 | * The Licensor: the natural or legal person that distributes and/or
47 | communicates the Work under the Licence.
48 |
49 | * Contributor(s): any natural or legal person who modifies the Work under the
50 | Licence, or otherwise contributes to the creation of a Derivative Work.
51 |
52 | * The Licensee or "You": any natural or legal person who makes any usage of
53 | the Software under the terms of the Licence.
54 |
55 | * Distribution and/or Communication: any act of selling, giving, lending,
56 | renting, distributing, communicating, transmitting, or otherwise making
57 | available, on-line or off-line, copies of the Work or providing access to
58 | its essential functionalities at the disposal of any other natural or legal
59 | person.
60 |
61 |
62 | 2. Scope of the rights granted by the Licence
63 |
64 | The Licensor hereby grants You a world-wide, royalty-free, non-exclusive,
65 | sublicensable licence to do the following, for the duration of copyright vested
66 | in the Original Work:
67 |
68 | * use the Work in any circumstance and for all usage,
69 | * reproduce the Work,
70 | * modify the Original Work, and make Derivative Works based upon the Work,
71 | * communicate to the public, including the right to make available or display
72 | the Work or copies thereof to the public and perform publicly, as the case
73 | may be, the Work,
74 | * distribute the Work or copies thereof,
75 | * lend and rent the Work or copies thereof,
76 | * sub-license rights in the Work or copies thereof.
77 |
78 | Those rights can be exercised on any media, supports and formats, whether now
79 | known or later invented, as far as the applicable law permits so.
80 |
81 | In the countries where moral rights apply, the Licensor waives his right to
82 | exercise his moral right to the extent allowed by law in order to make
83 | effective the licence of the economic rights here above listed.
84 |
85 | The Licensor grants to the Licensee royalty-free, non exclusive usage rights to
86 | any patents held by the Licensor, to the extent necessary to make use of the
87 | rights granted on the Work under this Licence.
88 |
89 |
90 | 3. Communication of the Source Code
91 |
92 | The Licensor may provide the Work either in its Source Code form, or as
93 | Executable Code. If the Work is provided as Executable Code, the Licensor
94 | provides in addition a machine-readable copy of the Source Code of the Work
95 | along with each copy of the Work that the Licensor distributes or indicates, in
96 | a notice following the copyright notice attached to the Work, a repository
97 | where the Source Code is easily and freely accessible for as long as the
98 | Licensor continues to distribute and/or communicate the Work.
99 |
100 |
101 | 4. Limitations on copyright
102 |
103 | Nothing in this Licence is intended to deprive the Licensee of the benefits
104 | from any exception or limitation to the exclusive rights of the rights owners
105 | in the Original Work or Software, of the exhaustion of those rights or of other
106 | applicable limitations thereto.
107 |
108 |
109 | 5. Obligations of the Licensee
110 |
111 | The grant of the rights mentioned above is subject to some restrictions and
112 | obligations imposed on the Licensee. Those obligations are the following:
113 |
114 | - Attribution right: the Licensee shall keep intact all copyright, patent or
115 | trademarks notices and all notices that refer to the Licence and to the
116 | disclaimer of warranties. The Licensee must include a copy of such notices
117 | and a copy of the Licence with every copy of the Work he/she distributes
118 | and/or communicates. The Licensee must cause any Derivative Work to carry
119 | prominent notices stating that the Work has been modified and the date of
120 | modification.
121 |
122 | - Copyleft clause: If the Licensee distributes and/or communicates copies of
123 | the Original Works or Derivative Works based upon the Original Work, this
124 | Distribution and/or Communication will be done under the terms of this
125 | Licence or of a later version of this Licence unless the Original Work is
126 | expressly distributed only under this version of the Licence. The Licensee
127 | (becoming Licensor) cannot offer or impose any additional terms or
128 | conditions on the Work or Derivative Work that alter or restrict the terms
129 | of the Licence.
130 |
131 | - Compatibility clause: If the Licensee Distributes and/or Communicates
132 | Derivative Works or copies thereof based upon both the Original Work and
133 | another work licensed under a Compatible Licence, this Distribution and/or
134 | Communication can be done under the terms of this Compatible Licence. For
135 | the sake of this clause, "Compatible Licence" refers to the licences listed
136 | in the appendix attached to this Licence. Should the Licensee's obligations
137 | under the Compatible Licence conflict with his/her obligations under this
138 | Licence, the obligations of the Compatible Licence shall prevail.
139 |
140 | - Provision of Source Code: When distributing and/or communicating copies of
141 | the Work, the Licensee will provide a machine-readable copy of the Source
142 | Code or indicate a repository where this Source will be easily and freely
143 | available for as long as the Licensee continues to distribute and/or
144 | communicate the Work. Legal Protection: This Licence does not grant
145 | permission to use the trade names, trademarks, service marks, or names of
146 | the Licensor, except as required for reasonable and customary use in
147 | describing the origin of the Work and reproducing the content of the
148 | copyright notice.
149 |
150 |
151 | 6. Chain of Authorship
152 |
153 | The original Licensor warrants that the copyright in the Original Work granted
154 | hereunder is owned by him/her or licensed to him/her and that he/she has the
155 | power and authority to grant the Licence.
156 |
157 | Each Contributor warrants that the copyright in the modifications he/she brings
158 | to the Work are owned by him/her or licensed to him/her and that he/she has the
159 | power and authority to grant the Licence.
160 |
161 | Each time You accept the Licence, the original Licensor and subsequent
162 | Contributors grant You a licence to their contributions to the Work, under the
163 | terms of this Licence.
164 |
165 |
166 | 7. Disclaimer of Warranty
167 |
168 | The Work is a work in progress, which is continuously improved by numerous
169 | contributors. It is not a finished work and may therefore contain defects or
170 | "bugs" inherent to this type of software development.
171 |
172 | For the above reason, the Work is provided under the Licence on an "as is"
173 | basis and without warranties of any kind concerning the Work, including without
174 | limitation merchantability, fitness for a particular purpose, absence of
175 | defects or errors, accuracy, non-infringement of intellectual property rights
176 | other than copyright as stated in Article 6 of this Licence.
177 |
178 | This disclaimer of warranty is an essential part of the Licence and a condition
179 | for the grant of any rights to the Work.
180 |
181 |
182 | 8. Disclaimer of Liability
183 |
184 | Except in the cases of wilful misconduct or damages directly caused to natural
185 | persons, the Licensor will in no event be liable for any direct or indirect,
186 | material or moral, damages of any kind, arising out of the Licence or of the
187 | use of the Work, including without limitation, damages for loss of goodwill,
188 | work stoppage, computer failure or malfunction, loss of data or any commercial
189 | damage, even if the Licensor has been advised of the possibility of such
190 | damage. However, the Licensor will be liable under statutory product liability
191 | laws as far such laws apply to the Work.
192 |
193 |
194 | 9. Additional agreements
195 |
196 | While distributing the Original Work or Derivative Works, You may choose to
197 | conclude an additional agreement to offer, and charge a fee for, acceptance of
198 | support, warranty, indemnity, or other liability obligations and/or services
199 | consistent with this Licence. However, in accepting such obligations, You may
200 | act only on your own behalf and on your sole responsibility, not on behalf of
201 | the original Licensor or any other Contributor, and only if You agree to
202 | indemnify, defend, and hold each Contributor harmless for any liability
203 | incurred by, or claims asserted against such Contributor by the fact You have
204 | accepted any such warranty or additional liability.
205 |
206 |
207 | 10. Acceptance of the Licence
208 |
209 | The provisions of this Licence can be accepted by clicking on an icon "I agree"
210 | placed under the bottom of a window displaying the text of this Licence or by
211 | affirming consent in any other similar way, in accordance with the rules of
212 | applicable law. Clicking on that icon indicates your clear and irrevocable
213 | acceptance of this Licence and all of its terms and conditions.
214 |
215 | Similarly, you irrevocably accept this Licence and all of its terms and
216 | conditions by exercising any rights granted to You by Article 2 of this
217 | Licence, such as the use of the Work, the creation by You of a Derivative Work
218 | or the Distribution and/or Communication by You of the Work or copies thereof.
219 |
220 |
221 | 11. Information to the public
222 |
223 | In case of any Distribution and/or Communication of the Work by means of
224 | electronic communication by You (for example, by offering to download the Work
225 | from a remote location) the distribution channel or media (for example, a
226 | website) must at least provide to the public the information requested by the
227 | applicable law regarding the Licensor, the Licence and the way it may be
228 | accessible, concluded, stored and reproduced by the Licensee.
229 |
230 |
231 | 12. Termination of the Licence
232 |
233 | The Licence and the rights granted hereunder will terminate automatically upon
234 | any breach by the Licensee of the terms of the Licence.
235 |
236 | Such a termination will not terminate the licences of any person who has
237 | received the Work from the Licensee under the Licence, provided such persons
238 | remain in full compliance with the Licence.
239 |
240 |
241 | 13. Miscellaneous
242 |
243 | Without prejudice of Article 9 above, the Licence represents the complete
244 | agreement between the Parties as to the Work licensed hereunder.
245 |
246 | If any provision of the Licence is invalid or unenforceable under applicable
247 | law, this will not affect the validity or enforceability of the Licence as a
248 | whole. Such provision will be construed and/or reformed so as necessary to make
249 | it valid and enforceable.
250 |
251 | The European Commission may publish other linguistic versions and/or new
252 | versions of this Licence, so far this is required and reasonable, without
253 | reducing the scope of the rights granted by the Licence. New versions of the
254 | Licence will be published with a unique version number.
255 |
256 | All linguistic versions of this Licence, approved by the European Commission,
257 | have identical value. Parties can take advantage of the linguistic version of
258 | their choice.
259 |
260 |
261 | 14. Jurisdiction
262 |
263 | Any litigation resulting from the interpretation of this License, arising
264 | between the European Commission, as a Licensor, and any Licensee, will be
265 | subject to the jurisdiction of the Court of Justice of the European
266 | Communities, as laid down in article 238 of the Treaty establishing the
267 | European Community.
268 |
269 | Any litigation arising between Parties, other than the European Commission, and
270 | resulting from the interpretation of this License, will be subject to the
271 | exclusive jurisdiction of the competent court where the Licensor resides or
272 | conducts its primary business.
273 |
274 |
275 | 15. Applicable Law
276 |
277 | This Licence shall be governed by the law of the European Union country where
278 | the Licensor resides or has his registered office.
279 |
280 | This licence shall be governed by the Belgian law if:
281 |
282 | * a litigation arises between the European Commission, as a Licensor, and any
283 | Licensee;
284 | * the Licensor, other than the European Commission, has no residence or
285 | registered office inside a European Union country.
286 |
287 |
288 | Appendix
289 |
290 | "Compatible Licences" according to article 5 EUPL are:
291 |
292 | * GNU General Public License (GNU GPL) v. 2
293 | * Open Software License (OSL) v. 2.1, v. 3.0
294 | * Common Public License v. 1.0
295 | * Eclipse Public License v. 1.0
296 | * Cecill v. 2.0
297 |
298 | 0
299 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NetLayer
2 | > Come for the Proxy, stay for the Tor Bindings
3 |
4 | This repository currently contains a Kotlin/Java8 Tor Library supporting
5 | * Tunnelling traffic through Tor using a custom Socket implementation
6 | * Stream isolation
7 | * Bridges and pluggable transports
8 | * Connecting to hidden services
9 | * Hosting of hidden services
10 |
11 | This project was originally based on a [previous fork](https://github.com/ManfredKarrer/Tor_Onion_Proxy_Library
12 | ) of [thaliproject/Tor_Onion_Proxy_Library](https://github.com/thaliproject/Tor_Onion_Proxy_Library), but deviated significatnly since.
13 |
14 | ## Usage
15 | This is essentially a Wrapper around the official Tor releases, pre-packaged for easiy use and convenient integration into Kotlin/Java Projects.
16 | As of you, simply add `tor.native` as dependency to your project (using JitPack):
17 | ```XML
18 |
19 |
20 | jitpack.io
21 | https://jitpack.io
22 |
23 |
24 | ```
25 | ```XML
26 |
27 | com.github.JesusMcCloud.netlayer
28 | tor.native
29 | 0.4.8
30 |
31 | ```
32 |
33 |
34 | ### Tunneling Traffic through Tor
35 | This library provides a plain TCP socket which can be used like any other:
36 |
37 | #### Kotlin
38 | ```Kotlin
39 | //set default instance, so it can be omitted whenever creating Tor (Server)Sockets
40 | //This will take some time
41 | Tor.default = NativeTor(/*Tor installation destination*/ File("tor-demo"))
42 | TorSocket("www.google.com", 80, streamId = "FOO" /*this one is optional*/) //clear web
43 | TorSocket("facebookcorewwwi.onion", 443, streamId = "BAR") //hidden service
44 | ```
45 |
46 | #### Java
47 | ```Java
48 | //set default instance, so it can be omitted whenever creating Tor (Server)Sockets
49 | //This will take some time
50 | Tor.setDefault(new NativeTor(/*Tor installation destination*/ new File("tor-demo")));
51 | new TorSocket("www.google.com", 80, "FOO");
52 | new TorSocket("facebookcorewwwi.onion", 443, "BAR");
53 | ```
54 |
55 | ### Using Bridges and Pluggable Transports
56 | To use bridges, simply pass the contents of a bridge configuration obtained from https://bridges.torproject.org/ (line-by-line wrapped in a *Collection*) as second parameter to the constructor of the `NativeTor` class.
57 |
58 | ### Hosting Hidden Services
59 | Hidden services can be hosted by creating a torified `ServerSocket`.
60 |
61 | #### Kotlin
62 | ```Kotlin
63 | //create a hidden service in directory 'test' inside the tor installation directory
64 | HiddenServiceSocket(8080, "test")
65 | //optionally attack a ready listener to be notified as soon as the service becomes reachable
66 | hiddenServiceSocket.addReadyListener { socket -> /*your code here*/}
67 | ```
68 |
69 | #### Java
70 | ```Java
71 | //create a hidden service in directory 'test' inside the tor installation directory
72 | HiddenServiceSocket hiddenServiceSocket = new HiddenServiceSocket(8080, "test");
73 | //it takes some time for a hidden service to be ready, so adding a listener only after creating the HS is not an issue
74 | hiddenServiceSocket.addReadyListener(socket -> { /*your code here*/ return null});
75 | ```
76 |
77 | ## Verifying the Authenticity/Integrity of the Tor Distribution
78 | This library ships the official Tor binaries. To verify their authenticity, simply rebuild [the prepackaged Tor binaries](https://github.com/JesusMcCloud/tor-binary) (courtesy of [cedric walter](https://github.com/cedricwalter)) and rebuild the `tor.native`
79 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.berndpruenster.netlayer
7 | parent
8 | 0.4.9-pre
9 | pom
10 |
11 | Netlayer
12 |
13 |
14 |
15 | 1.6
16 | 1.3.0
17 | UTF-8
18 |
19 | 3.7.0
20 | 8.0.3
21 | false
22 |
23 |
24 |
25 | tor
26 | tor.native
27 | tor.external
28 |
29 |
30 |
31 |
32 | jitpack.io
33 | https://jitpack.io
34 |
35 |
36 | tor-binary
37 | https://raw.githubusercontent.com/JesusMcCloud/tor-binary/master/release/
38 |
39 |
40 |
41 |
42 | org.jetbrains.kotlin
43 | kotlin-stdlib
44 | ${kotlin.version}
45 |
46 |
47 | com.github.guardianproject
48 | jsocks
49 | 9ae7520
50 |
51 |
52 | io.github.microutils
53 | kotlin-logging
54 | 1.6.10
55 |
56 |
57 |
58 |
59 |
60 |
61 | org.honton.chas
62 | exists-maven-plugin
63 | 0.0.6
64 |
65 | com.cedricwalter:tor-binary-windows:${tor-binary.version}
66 | tor-binary-windows-${tor-binary.version}.pom
67 | tor-binary-exists
68 | ${force-update-tor-binaries}
69 |
70 |
71 |
72 | generate-resources
73 |
74 | local
75 |
76 |
77 |
78 |
79 |
80 | maven-antrun-plugin
81 | 1.8
82 |
83 |
84 | org.apache.ant
85 | ant-compress
86 | 1.4
87 |
88 |
89 |
90 |
91 | fetch tor-binaries
92 | generate-resources
93 |
94 |
95 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | run
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/tor.external/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | 4.0.0
6 |
7 | org.berndpruenster.netlayer
8 | parent
9 | 0.4.9-pre
10 |
11 |
12 | 1.8
13 |
14 |
15 | tor.external
16 | jar
17 | Tor External Bindings
18 |
19 |
20 |
21 | org.jetbrains.kotlin
22 | kotlin-stdlib-jdk8
23 | ${kotlin.version}
24 |
25 |
26 | org.berndpruenster.netlayer
27 | tor
28 | ${project.version}
29 |
30 |
31 | com.beust
32 | jcommander
33 | 1.69
34 | test
35 |
36 |
37 | ch.qos.logback
38 | logback-classic
39 | 1.1.3
40 | test
41 |
42 |
43 |
44 |
45 | ${project.basedir}/src/main/kotlin
46 | ${project.basedir}/src/test/kotlin
47 |
48 |
49 | kotlin-maven-plugin
50 | org.jetbrains.kotlin
51 | ${kotlin.version}
52 |
53 |
54 | compile
55 |
56 | compile
57 |
58 |
59 |
60 |
61 | test-compile
62 |
63 | test-compile
64 |
65 |
66 |
67 |
68 |
69 | org.codehaus.mojo
70 | build-helper-maven-plugin
71 | 3.0.0
72 |
73 |
74 | generate-sources
75 |
76 | add-source
77 |
78 |
79 |
80 | src/main/kotlin
81 |
82 |
83 |
84 |
85 |
86 |
87 | org.apache.maven.plugins
88 | maven-javadoc-plugin
89 |
90 |
91 | attach-javadocs
92 |
93 | jar
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/tor.external/src/main/kotlin/org/berndpruenster/netlayer/tor/ExternalTor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017, Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 | */
18 | package org.berndpruenster.netlayer.tor
19 |
20 | import com.runjva.sourceforge.jsocks.protocol.SocksSocket
21 | import java.net.InetAddress
22 | import java.net.ServerSocket
23 | import java.net.SocketAddress
24 |
25 |
26 | class ExternalTorSocket
27 | @JvmOverloads constructor(proxyPort: Int, private val destination: String, port: Int, streamID: String? = null) :
28 | SocksSocket(Tor.getProxy(proxyPort, streamID), destination, port) {
29 | override fun getRemoteSocketAddress(): SocketAddress = HiddenServiceSocketAddress(destination, port)
30 | }
31 |
32 | class ExternalHiddenServiceSocket(localPort: Int) : ServerSocket(localPort, 50, InetAddress.getLoopbackAddress())
--------------------------------------------------------------------------------
/tor.external/src/test/kotlin/org/berndpruenster/netlayer/tor/demo/ExternalDemo.kt:
--------------------------------------------------------------------------------
1 | package org.berndpruenster.netlayer.tor.demo
2 |
3 | import org.berndpruenster.netlayer.tor.ExternalHiddenServiceSocket
4 | import org.berndpruenster.netlayer.tor.ExternalTorSocket
5 | import java.io.BufferedReader
6 | import java.io.InputStreamReader
7 | import kotlin.concurrent.thread
8 |
9 | fun main(args: Array) {
10 |
11 | val sock = ExternalTorSocket(34465, "google.com", 443)
12 | val hsName = "x4hqvjg6pogtujst.onion"
13 | val server = ExternalHiddenServiceSocket(10024)
14 | thread { BufferedReader(InputStreamReader(server.accept().getInputStream())).use { println(it.readLine()) } }
15 | ExternalTorSocket(34465, hsName, 10024).outputStream.write("Hello Tor\n".toByteArray())
16 |
17 | }
--------------------------------------------------------------------------------
/tor.native/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | 4.0.0
6 |
7 | org.berndpruenster.netlayer
8 | parent
9 | 0.4.9-pre
10 |
11 |
12 | 1.8
13 |
14 |
15 | tor.native
16 | jar
17 | Tor Native Files
18 |
19 |
20 |
21 | org.jetbrains.kotlin
22 | kotlin-stdlib-jdk8
23 | ${kotlin.version}
24 |
25 |
26 | org.berndpruenster.netlayer
27 | tor
28 | ${project.version}
29 |
30 |
31 | com.cedricwalter
32 | tor-binary-macos
33 | ${tor-binary.version}
34 |
35 |
36 | com.cedricwalter
37 | tor-binary-linux32
38 | ${tor-binary.version}
39 |
40 |
41 | com.cedricwalter
42 | tor-binary-linux64
43 | ${tor-binary.version}
44 |
45 |
46 | com.cedricwalter
47 | tor-binary-windows
48 | ${tor-binary.version}
49 |
50 |
51 | com.beust
52 | jcommander
53 | 1.69
54 | test
55 |
56 |
57 | ch.qos.logback
58 | logback-classic
59 | 1.1.3
60 | test
61 |
62 |
63 | org.jetbrains.kotlin
64 | kotlin-test
65 | ${kotlin.version}
66 | test
67 |
68 |
69 |
70 |
71 | src/main/kotlin
72 |
73 |
74 | kotlin-maven-plugin
75 |
76 | ${java.version}
77 |
78 | org.jetbrains.kotlin
79 | ${kotlin.version}
80 |
81 |
82 | compile
83 | compile
84 |
85 | compile
86 |
87 |
88 |
89 | test-compile
90 | test-compile
91 |
92 | test-compile
93 |
94 |
95 |
96 | src/test/java
97 | src/test/kotlin
98 |
99 |
100 |
101 |
102 |
103 |
104 | org.apache.maven.plugins
105 | maven-compiler-plugin
106 | ${maven-compiler-plugin.version}
107 |
108 | ${java.version}
109 | ${java.version}
110 |
111 |
112 |
113 |
114 | default-compile
115 | none
116 |
117 |
118 |
119 | default-testCompile
120 | none
121 |
122 |
123 | java-compile
124 | compile
125 |
126 | compile
127 |
128 |
129 |
130 | java-test-compile
131 | test-compile
132 |
133 | testCompile
134 |
135 |
136 |
137 | compile
138 | compile
139 |
140 | compile
141 |
142 |
143 |
144 | testCompile
145 | test-compile
146 |
147 | testCompile
148 |
149 |
150 |
151 |
152 |
153 | org.codehaus.mojo
154 | build-helper-maven-plugin
155 | 3.0.0
156 |
157 |
158 | generate-sources
159 |
160 | add-source
161 |
162 |
163 |
164 | src/main/kotlin
165 |
166 |
167 |
168 |
169 |
170 |
171 | org.apache.maven.plugins
172 | maven-javadoc-plugin
173 |
174 |
175 | attach-javadocs
176 |
177 | jar
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/tor.native/src/main/kotlin/org/berndpruenster/netlayer/tor/FileUtilities.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | All Rights Reserved
21 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
22 | with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
23 |
24 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
25 | IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR
26 | PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
27 |
28 | See the Apache 2 License for the specific language governing permissions and limitations under the License.
29 | */
30 |
31 | package org.berndpruenster.netlayer.tor
32 |
33 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry
34 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
35 | import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
36 | import java.io.File
37 | import java.io.FileOutputStream
38 | import java.io.IOException
39 | import java.io.InputStream
40 | import java.util.concurrent.TimeUnit
41 |
42 | /**
43 | * ANDROID uses FileObserver and Java uses the WatchService, this class
44 | * abstracts the two.
45 | */
46 | interface WriteObserver {
47 | /**
48 | * Waits timeout of unit to see if file is modified
49 | *
50 | * @param timeout
51 | * How long to wait before returning
52 | * @param unit
53 | * Unit to wait in
54 | * @return True if file was modified, false if it was not
55 | */
56 | fun poll(timeout: Long, unit: TimeUnit): Boolean
57 | }
58 |
59 | internal fun File.log() {
60 | if (isDirectory) {
61 | listFiles().forEach {
62 | (it.log())
63 | }
64 | } else {
65 | logger?.info(absolutePath)
66 | }
67 | }
68 |
69 | /**
70 | * Reads the input stream, deletes dst if it exists and over writes
71 | * it with the stream.
72 | *
73 | * @param src
74 | * Stream to read from
75 | * @param dst
76 | * File to write to
77 | * @throws java.io.IOException
78 | * - If any of the file operations fail
79 | */
80 | @Throws(IOException::class)
81 | internal fun cleanInstallFile(src: InputStream, dst: File) {
82 | try {
83 | if (dst.exists() && !dst.delete()) {
84 | throw IOException("Could not remove existing file ${dst.name}")
85 | }
86 | dst.outputStream().buffered().use { out ->
87 | src.copyTo(out)
88 | }
89 | } catch (e: Exception) {
90 | throw IOException(e)
91 | }
92 | }
93 |
94 | /**
95 | * @param destinationDirectory
96 | * Directory files are to be extracted to
97 | * @param archiveInputStream
98 | * Stream to extract
99 | * @throws java.io.IOException
100 | * - If there are any file errors
101 | */
102 | fun extractContentFromArchive(destinationDirectory: File, archiveInputStream: InputStream) {
103 |
104 | TarArchiveInputStream(XZCompressorInputStream(archiveInputStream)).use { tarIn ->
105 | var entry = tarIn.nextEntry
106 |
107 | while (entry != null) {
108 |
109 | val f = File(destinationDirectory.canonicalPath + File.separator + entry.name.replace('/',
110 | File.separatorChar))
111 | if (entry.isDirectory) {
112 | if (!f.exists() && !f.mkdirs()) {
113 | throw IOException("could not create directory $f")
114 | }
115 | } else {
116 |
117 | if (!f.parentFile.exists() && !f.parentFile.mkdirs()) {
118 | throw IOException("could not create directory ${f.parentFile}")
119 | }
120 |
121 | if (f.exists() && !f.delete()) {
122 | throw RuntimeException("Could not delete file ${f.absolutePath} in preparation for overwriting it")
123 | }
124 |
125 | if (!f.createNewFile()) {
126 | throw RuntimeException("Could not create file $f")
127 | }
128 |
129 | FileOutputStream(f).use { outStream ->
130 | tarIn.copyTo(outStream)
131 | val mode = (entry as TarArchiveEntry).mode
132 |
133 | if ((mode and 65) > 0) {
134 | f.setExecutable(true, (mode and 1) == 0)
135 | }
136 | if (OsType.current == OsType.MACOS) {
137 | f.setExecutable(true, true)
138 | }
139 | }
140 | }
141 | entry = tarIn.nextTarEntry
142 | }
143 | }
144 | }
145 |
146 |
--------------------------------------------------------------------------------
/tor.native/src/main/kotlin/org/berndpruenster/netlayer/tor/NativeTor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | Copyright (C) 2011-2014 Sublime Software Ltd
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 | */
34 |
35 | package org.berndpruenster.netlayer.tor
36 |
37 | import java.io.File
38 | import java.io.IOException
39 | import java.nio.file.Files
40 | import java.nio.file.attribute.PosixFilePermission
41 | import java.util.concurrent.TimeUnit.MILLISECONDS
42 |
43 | private const val FILE_ARCHIVE = "tor.tar.xz"
44 | private const val BINARY_TOR_MACOS = "tor.real"
45 | private const val BINARY_TOR_WIN = "tor.exe"
46 | private const val BINARY_TOR_LNX = "tor"
47 | private const val PATH_LNX = "linux/"
48 | private const val PATH_LNX64 = "${PATH_LNX}x64/"
49 | private const val PATH_LNX32 = "${PATH_LNX}x86/"
50 | private const val PATH_MACOS = "osx/"
51 | private const val PATH_MACOS64 = "${PATH_MACOS}x64/"
52 | private const val PATH_WIN = "windows/"
53 | private const val PATH_WIN32 = "${PATH_WIN}x86/"
54 | private const val PATH_NATIVE = "native/"
55 |
56 | private const val OS_UNSUPPORTED = "We don't support Tor on this OS"
57 |
58 | private const val HS_PORT = "HiddenServicePort"
59 | private const val HS_DIR = "HiddenServiceDir"
60 |
61 | private const val HOSTNAME_TIMEOUT = 30 * 1000 // Milliseconds
62 |
63 | class NativeTor @JvmOverloads @Throws(TorCtlException::class) constructor(workingDirectory: File, bridgeLines: Collection? = null, torrcOverrides: Torrc? = null) : Tor() {
64 |
65 | private val context : NativeContext = NativeContext(workingDirectory, torrcOverrides)
66 |
67 | private val bridgeConfig: List = bridgeLines?.filter { it.length > 10 } ?: emptyList()
68 |
69 | init {
70 | var torController: TorController? = null
71 | try {
72 | loop@ for (retryCount in 1..TRIES_PER_STARTUP) {
73 | torController = context.installAndStartTorOp(bridgeConfig, eventHandler)
74 | torController.enableNetwork()
75 | // We will check every second to see if boot strapping has
76 | // finally finished
77 | loop@ for (secondsWaited in 1..TOTAL_SEC_PER_STARTUP) {
78 | if (!torController.bootstrapped) {
79 | Thread.sleep(1000, 0)
80 | } else {
81 | control = Control(torController)
82 | break@loop
83 | }
84 | }
85 | if(null == control) {
86 |
87 | // Bootstrapping isn't over so we need to restart and try again
88 | torController.shutdown()
89 |
90 | // Experimentally we have found that if a Tor OP has run before and thus
91 | // has cached descriptors
92 | // and that when we try to start it again it won't start then deleting
93 | // the cached data can fix this.
94 | // But, if there is cached data and things do work then the Tor OP will
95 | // start faster than it would
96 | // if we delete everything.
97 | // So our compromise is that we try to start the Tor OP 'as is' on the
98 | // first round and after that
99 | // we delete all the files.
100 | context.deleteAllFilesButHS()
101 | }
102 | }
103 |
104 | throw TorCtlException("Could not setup Tor")
105 |
106 |
107 | } finally {
108 | // Make sure we return the Tor OP in some kind of consistent state,
109 | // even if it's 'off'.
110 | if (torController?.bootstrapped != true) {
111 | try {
112 | context.deleteAllFilesButHS()
113 | torController?.shutdown()
114 | } catch (e: Exception) {
115 | logger?.error { e.localizedMessage }
116 | }
117 | }
118 | }
119 | }
120 |
121 | override fun publishHiddenService(hsDirName: String, hiddenServicePort: Int, localPort: Int): HsContainer {
122 | synchronized(control) {
123 |
124 | val currentHiddenServices = control.hiddenServices
125 |
126 | val hiddenServiceDirectory = context.getHiddenServiceDirectory(hsDirName)
127 |
128 | val config = mutableListOf()
129 |
130 | for (service in currentHiddenServices) {
131 | if (service.is_default) {
132 | continue
133 | }
134 | if (service.key == (HS_DIR) && service.value == hiddenServiceDirectory.canonicalPath) {
135 | throw TorCtlException("Hidden Service ${hiddenServiceDirectory.canonicalPath} is already published")
136 | }
137 | config.add("${service.key} ${service.value}")
138 | }
139 |
140 | logger?.debug("Creating hidden service $hsDirName")
141 | val hostnameFile = context.getHostNameFile(hsDirName)
142 |
143 | if (!(hostnameFile.parentFile.exists() || hostnameFile.parentFile.mkdirs())) {
144 | throw TorCtlException("Could not create hostnameFile parent directory")
145 | }
146 |
147 | if (!(hostnameFile.exists() || hostnameFile.createNewFile())) {
148 | throw TorCtlException("Could not create hostnameFile")
149 | }
150 | // Thanks, Ubuntu!
151 | try {
152 | if (OsType.current.isUnixoid()) {
153 | val perms = mutableSetOf(PosixFilePermission.OWNER_READ,
154 | PosixFilePermission.OWNER_WRITE,
155 | PosixFilePermission.OWNER_EXECUTE)
156 | Files.setPosixFilePermissions(hiddenServiceDirectory.toPath(), perms)
157 | }
158 | } catch (e: Exception) {
159 | logger?.error("could not set permissions, hidden service $hsDirName will most probably not work", e)
160 | }
161 |
162 | control.enableHiddenServiceEvents()
163 | // Watch for the hostname file being created/updated
164 | val hostNameFileObserver = context.generateWriteObserver(hostnameFile)
165 | // Use the control connection to update the Tor config
166 | config.addAll(listOf("${HS_DIR} ${hostnameFile.parentFile.canonicalPath}",
167 | "${HS_PORT} $hiddenServicePort ${LOCAL_IP}:$localPort"))
168 | control.saveConfig(config)
169 | // Wait for the hostname file to be created/updated
170 | if (!hostNameFileObserver.poll(HOSTNAME_TIMEOUT.toLong(), MILLISECONDS)) {
171 | hostnameFile.parentFile.log()
172 | throw RuntimeException("Wait for hidden service hostname file to be created expired.")
173 | }
174 |
175 | // Publish the hidden service's onion hostname in transport properties
176 | val hostname = hostnameFile.readBytes().toString(Charsets.UTF_8).trim()
177 | logger?.debug("PUBLISH: Hidden service config has completed: $config")
178 |
179 | return HsContainer(hostname, eventHandler)
180 | }
181 | }
182 |
183 | override fun unpublishHiddenService(hsDir: String) {
184 | synchronized(control) {
185 |
186 | val currentHiddenServices = control.hiddenServices
187 | val hiddenServiceDirectory = context.getHiddenServiceDirectory(hsDir)
188 | val conf = mutableListOf()
189 | var removeNext = false
190 | for (service in currentHiddenServices) {
191 | if (removeNext) {
192 | removeNext = false
193 | continue
194 | }
195 | if (service.is_default) {
196 | continue
197 | }
198 |
199 |
200 | if (service.key == (HS_DIR) && service.value == hiddenServiceDirectory.canonicalPath) {
201 | removeNext = true
202 | continue
203 | }
204 |
205 | conf.add("${service.key} ${service.value}")
206 | }
207 | logger?.debug("UNPUBL Hidden service config has completed: $conf")
208 | control.saveConfig(conf)
209 | }
210 | }
211 |
212 | override fun shutdown() {
213 | synchronized(control) {
214 | control.shutdown()
215 | }
216 | }
217 | }
218 |
219 |
220 | class NativeContext(workingDirectory: File, overrides: Torrc?) : TorContext(workingDirectory, overrides) {
221 |
222 | override val processId: String
223 | get() {
224 | val processName = java.lang.management.ManagementFactory.getRuntimeMXBean().name
225 | return processName.split("@")[0]
226 | }
227 |
228 | override val pathToTorExecutable: String by lazy {
229 | when (OsType.current) {
230 | OsType.WIN -> PATH_NATIVE + PATH_WIN32
231 | OsType.MACOS -> PATH_NATIVE + PATH_MACOS64
232 | OsType.LNX32 -> PATH_NATIVE + PATH_LNX32
233 | OsType.LNX64 -> PATH_NATIVE + PATH_LNX64
234 | else -> throw RuntimeException(OS_UNSUPPORTED)
235 | }
236 | }
237 |
238 | private val rcPath: String by lazy {
239 | when (OsType.current) {
240 | OsType.WIN -> PATH_NATIVE + PATH_WIN
241 | OsType.MACOS -> PATH_NATIVE + PATH_MACOS
242 | OsType.LNX32, OsType.LNX64 -> PATH_NATIVE + PATH_LNX
243 | else -> throw RuntimeException(OS_UNSUPPORTED)
244 | }
245 | }
246 | override val pathToRC: String = "$rcPath$FILE_TORRC_NATIVE"
247 |
248 | override val torExecutableFileName: String by lazy {
249 | when (OsType.current) {
250 | OsType.LNX32, OsType.LNX64 -> BINARY_TOR_LNX
251 | OsType.WIN -> BINARY_TOR_WIN
252 | OsType.MACOS -> BINARY_TOR_MACOS
253 | else -> throw RuntimeException(OS_UNSUPPORTED)
254 | }
255 | }
256 |
257 | override fun getByName(fileName: String) = this::class.java.getResourceAsStream("/$fileName") ?: throw IOException(
258 | "Could not load $fileName")
259 |
260 | override fun generateWriteObserver(file: File): WriteObserver = NativeWatchObserver(file)
261 |
262 | override fun installFiles() {
263 | super.installFiles()
264 | when (OsType.current) {
265 | OsType.WIN, OsType.LNX32, OsType.LNX64, OsType.MACOS -> extractContentFromArchive(workingDirectory,
266 | getByName(
267 | pathToTorExecutable + FILE_ARCHIVE))
268 | else -> throw RuntimeException(OS_UNSUPPORTED)
269 | }
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/tor.native/src/main/kotlin/org/berndpruenster/netlayer/tor/NativeWatchObserver.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | Copyright (C) 2011-2014 Sublime Software Ltd
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 | */
34 |
35 | package org.berndpruenster.netlayer.tor
36 |
37 | import java.io.File
38 | import java.nio.file.*
39 | import java.util.concurrent.TimeUnit
40 |
41 | /**
42 | * Watches to see if a particular file is changed
43 | */
44 | class NativeWatchObserver(private val fileToWatch: File) : WriteObserver {
45 | private val watchService: WatchService
46 | private val key: WatchKey
47 | private val lastModified: Long
48 | private val length: Long
49 |
50 | init {
51 | if (!fileToWatch.exists()) {
52 | throw RuntimeException("$fileToWatch does not exist")
53 | }
54 |
55 | lastModified = fileToWatch.lastModified()
56 | length = fileToWatch.length()
57 |
58 | watchService = FileSystems.getDefault().newWatchService()
59 | // Note that poll depends on us only registering events that are of current
60 | // path
61 | if (OsType.current != OsType.MACOS) {
62 | key = fileToWatch.parentFile.toPath().register(watchService,
63 | StandardWatchEventKinds.ENTRY_CREATE,
64 | StandardWatchEventKinds.ENTRY_DELETE,
65 | StandardWatchEventKinds.ENTRY_MODIFY)
66 | } else {
67 | // Unfortunately the default watch service on MACOS is broken, it uses a
68 | // separate thread and really slow polling to detect file changes
69 | // rather than integrating with the OS. There is a hack to make it poll
70 | // faster which we can use for now. See
71 | // http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else
72 | key = fileToWatch.parentFile.toPath().register(watchService,
73 | arrayOf(StandardWatchEventKinds.ENTRY_CREATE,
74 | StandardWatchEventKinds.ENTRY_DELETE,
75 | StandardWatchEventKinds.ENTRY_MODIFY))
76 | }
77 | }
78 |
79 | override fun poll(timeout: Long, unit: TimeUnit): Boolean {
80 | var result = false
81 | try {
82 | var remainingTimeoutInNanos = unit.toNanos(timeout)
83 | while (remainingTimeoutInNanos > 0) {
84 | val startTimeInNanos = System.nanoTime()
85 | val receivedKey = watchService.poll(remainingTimeoutInNanos, TimeUnit.NANOSECONDS)
86 | val timeWaitedInNanos = System.nanoTime() - startTimeInNanos
87 |
88 | if (receivedKey != null) {
89 | if (receivedKey != key) {
90 | throw RuntimeException("This really shouldn't have happened. EEK!" + receivedKey.toString())
91 | }
92 |
93 | for (event in receivedKey.pollEvents()) {
94 | val kind = event.kind()
95 |
96 | if (kind == StandardWatchEventKinds.OVERFLOW) {
97 | logger?.error("We got an overflow, there shouldn't have been enough activity to make that happen.")
98 | }
99 |
100 | val changedEntry = event.context() as Path
101 | if (fileToWatch.toPath().endsWith(changedEntry)) {
102 | result = true
103 | return result
104 | }
105 | }
106 |
107 | // In case we haven't yet gotten the event we are looking for we have
108 | // to reset in order to
109 | // receive any further notifications.
110 | if (!key.reset()) {
111 | logger?.error("The key became invalid which should not have happened.")
112 | }
113 | }
114 |
115 | if (timeWaitedInNanos >= remainingTimeoutInNanos) {
116 | break
117 | }
118 |
119 | remainingTimeoutInNanos -= timeWaitedInNanos
120 | }
121 |
122 | // Even with the high sensitivity setting above for the MACOS the polling
123 | // still misses changes so I've added
124 | // a last modified check as a backup. Except I personally witnessed last
125 | // modified not returning a new value
126 | // value even when I saw the file change!!!! So I'm also adding in a
127 | // length check. Java really seems to
128 | // have an issue with the OS/X file system.
129 | result = (fileToWatch.lastModified() != lastModified) || (fileToWatch.length() != length)
130 | return result
131 | } finally {
132 | if (result) {
133 | watchService.close()
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tor.native/src/main/kotlin/org/berndpruenster/netlayer/tor/TorContext.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 |
21 | Licensed under the Apache License, Version 2.0 (the "License");
22 | you may not use this file except in compliance with the License.
23 | You may obtain a copy of the License at
24 |
25 | http://www.apache.org/licenses/LICENSE-2.0
26 |
27 | Unless required by applicable law or agreed to in writing, software
28 | distributed under the License is distributed on an "AS IS" BASIS,
29 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | See the License for the specific language governing permissions and
31 | limitations under the License.
32 | */
33 |
34 | package org.berndpruenster.netlayer.tor
35 |
36 | import net.freehaven.tor.control.TorControlConnection
37 | import java.io.*
38 | import java.net.Socket
39 | import java.util.concurrent.CountDownLatch
40 | import java.util.concurrent.TimeUnit
41 | import java.util.concurrent.atomic.AtomicReference
42 | import kotlin.concurrent.thread
43 |
44 | /**
45 | * This class encapsulates data that is handled differently in Java and ANDROID
46 | * as well as managing file locations.
47 | */
48 | private const val FILE_AUTH_COOKIE = ".tor/control_auth_cookie"
49 | private const val DIR_HS_ROOT = "hiddenservice"
50 | private const val FILE_PID = "pid"
51 | private const val FILE_GEOIP = "geoip"
52 | private const val FILE_GEOIP_6 = "geoip6"
53 | private const val FILE_TORRC = "torrc"
54 | private const val FILE_TORRC_DEFAULTS = "torrc.defaults"
55 | private const val FILE_HOSTNAME = "hostname"
56 | private const val DIRECTIVE_GEOIP6_FILE = "GeoIPv6File "
57 | private const val DIRECTIVE_GEOIP_FILE = "GeoIPFile "
58 | private const val DIRECTIVE_PIDFILE = "PidFile "
59 | private const val DIRECTIVE_DATA_DIRECTORY = "DataDirectory "
60 | private const val DIRECTIVE_COOKIE_AUTH_FILE = "CookieAuthFile "
61 |
62 | private const val OWNER = "__OwningControllerProcess"
63 | private const val COOKIE_TIMEOUT = 3 * 1000 // Milliseconds
64 |
65 |
66 |
67 | class Torrc @Throws(IOException::class) internal constructor(defaults: InputStream?, overrides: Map?) {
68 |
69 | @Throws(IOException::class)
70 | internal constructor(defaults: InputStream, str: InputStream?) : this(defaults, parse(str))
71 |
72 | @Throws(IOException::class)
73 | internal constructor(defaults: InputStream, overrides: Torrc?) : this(defaults, overrides?.rc)
74 |
75 | @Throws(IOException::class)
76 | constructor(src: InputStream) : this(src, str = null)
77 |
78 | @Throws(IOException::class)
79 | constructor(rc: LinkedHashMap) : this(null, rc)
80 |
81 | private val rc = LinkedHashMap()
82 |
83 | init {
84 | overrides?.forEach { rc.put(it.key, it.value.trim()) }
85 | parse(defaults)?.forEach { rc.put(it.key, it.value.trim()) }
86 | }
87 |
88 | internal val inputStream: InputStream
89 | get() {
90 | val outputStream = ByteArrayOutputStream()
91 | outputStream.bufferedWriter().use { str ->
92 | rc.forEach {
93 | str.write("${it.key} ${it.value}")
94 | str.newLine()
95 | }
96 | }
97 | outputStream.bufferedWriter().use { it.newLine() }
98 | return ByteArrayInputStream(outputStream.toByteArray())
99 | }
100 |
101 | companion object {
102 | @Throws(IOException::class)
103 | private fun parse(src: InputStream?): LinkedHashMap? {
104 | if (src == null) return null
105 | val map = LinkedHashMap()
106 | BufferedReader(src.reader()).useLines {
107 | it.map { it.trim() }.filter { it.length > 5 && (!it.startsWith("#")) && it.contains(' ') }.forEach {
108 | val delim = it.indexOf(' ')
109 | val k = it.substring(0, delim)
110 | val v = it.substring(delim, it.length).trim()
111 | map.put(k, v)
112 | }
113 | }
114 | return map
115 | }
116 | }
117 |
118 | override fun toString(): String = StringBuilder().apply {
119 | rc.forEach {
120 | this.append(it.key).append(" ").append(it.value).append(" ")
121 | }
122 | }.toString()
123 |
124 | }
125 |
126 | abstract class TorContext @Throws(IOException::class) protected constructor(val workingDirectory: File,
127 | private val overrides: Torrc?) {
128 | companion object {
129 | @JvmStatic
130 | protected val FILE_TORRC_NATIVE = "torrc.native"
131 | @JvmStatic
132 | private val EVENTS = listOf("CIRC", "WARN", "ERR")
133 |
134 | private fun parseBootstrap(inputStream: InputStream, latch: CountDownLatch, port: AtomicReference) {
135 | thread {
136 | Thread.currentThread().name = "NFO"
137 | BufferedReader(inputStream.reader()).use { reader ->
138 | reader.forEachLine {
139 | logger?.debug { it }
140 | if (it.contains("Control listener listening on port ")) {
141 | port.set(Integer.parseInt(it.substring(it.lastIndexOf(" ") + 1, it.length - 1)))
142 | latch.countDown()
143 | }
144 | }
145 | }
146 | }
147 | }
148 |
149 | private fun forwardErr(inputStream: InputStream) {
150 | thread {
151 | Thread.currentThread().name = "ERR"
152 | BufferedReader(inputStream.reader()).use { reader ->
153 | reader.forEachLine {
154 | logger?.error { it }
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
161 |
162 | protected abstract val pathToTorExecutable: String
163 | abstract val pathToRC: String
164 | protected abstract val torExecutableFileName: String
165 | abstract val processId: String
166 |
167 | internal val pidFile = File(workingDirectory, FILE_PID)
168 | internal val geoIpFile = File(workingDirectory, FILE_GEOIP)
169 | internal val geoIpv6File = File(workingDirectory, FILE_GEOIP_6)
170 | internal val torrcFile = File(workingDirectory, FILE_TORRC)
171 | internal val torExecutableFile get() = File(workingDirectory, torExecutableFileName)
172 | internal val cookieFile = File(workingDirectory, FILE_AUTH_COOKIE)
173 |
174 | @Throws(IOException::class)
175 | open fun installFiles() {
176 | // This is sleazy but we have cases where an old instance of the Tor OP
177 | // needs an extra second to
178 | // clean itself up. Without that time we can't do things like delete its
179 | // binary (which we currently
180 | // do by default, something we hope to fix with
181 | // https://github.com/thaliproject/Tor_Onion_Proxy_Library/issues/13
182 | Thread.sleep(1000, 0)
183 |
184 | workingDirectory.listFiles()?.forEach {
185 | if (it.absolutePath.startsWith(torrcFile.absolutePath)) {
186 | it.delete()
187 | }
188 | }
189 |
190 | val dotTorDir = File(workingDirectory, ".tor")
191 | if (dotTorDir.exists()) {
192 | dotTorDir.deleteRecursively()
193 | }
194 | if (!(workingDirectory.exists() || workingDirectory.mkdirs())) {
195 | throw RuntimeException("Could not create root directory $workingDirectory!")
196 | }
197 |
198 | getByName(FILE_GEOIP).use { str ->
199 | cleanInstallFile(str, geoIpFile)
200 | }
201 | getByName(FILE_GEOIP_6).use { str ->
202 | cleanInstallFile(str, geoIpv6File)
203 | }
204 | getByName(FILE_TORRC).use { str ->
205 | Torrc(str, overrides).inputStream.use { rc ->
206 | getByName(FILE_TORRC_DEFAULTS).use {
207 | Torrc(rc, it).inputStream.use {
208 | cleanInstallFile(it, torrcFile)
209 | }
210 | }
211 | }
212 | }
213 | }
214 |
215 | /**
216 | * Sets environment variables and working directory needed for Tor
217 | *
218 | * @param processBuilder
219 | * we will call start on this to run Tor
220 | */
221 | internal fun setEnvAndWD(processBuilder: ProcessBuilder) {
222 | processBuilder.directory(workingDirectory)
223 | val environment = processBuilder.environment()
224 | environment.put("HOME", workingDirectory.absolutePath)
225 | when (OsType.current) {
226 | OsType.LNX32, OsType.LNX64 ->
227 | // We have to provide the LD_LIBRARY_PATH because when looking
228 | // for dynamic libraries
229 | // Linux apparently will not look in the current directory by
230 | // default. By setting this
231 | // environment variable we fix that.
232 | environment.put("LD_LIBRARY_PATH", workingDirectory.absolutePath)
233 | //$FALL-THROUGH$
234 | else -> {
235 | }
236 | }
237 | }
238 |
239 | internal fun getHostNameFile(hsDir: String): File = File(getHiddenServiceDirectory(hsDir).canonicalPath + "/" + FILE_HOSTNAME)
240 |
241 | internal fun deleteAllFilesButHS() {
242 | // It can take a little bit for the Tor OP to detect the connection is
243 | // dead and kill itself
244 | Thread.sleep(1000)
245 | workingDirectory.listFiles()?.forEach {
246 | if (it.isDirectory) {
247 | if (it.name != (DIR_HS_ROOT)) {
248 | it.deleteRecursively()
249 | }
250 | } else {
251 | if (!it.delete()) {
252 | throw RuntimeException("Could not delete file ${it.absolutePath}")
253 | }
254 | }
255 | }
256 | }
257 |
258 | /**
259 | * Files we pull out of the AAR or JAR are typically at the root but for
260 | * executables outside of ANDROID the executable for a particular platform is
261 | * in a specific sub-directory.
262 | *
263 | * @return Path to executable in JAR Resources
264 | */
265 |
266 | abstract fun generateWriteObserver(file: File): WriteObserver
267 |
268 | abstract fun getByName(fileName: String): InputStream
269 |
270 | fun getHiddenServiceDirectory(hsDir: String): File {
271 | return File(workingDirectory, "/$DIR_HS_ROOT/$hsDir")
272 | }
273 |
274 |
275 | /**
276 | * Installs all necessary files and starts the Tor OP in offline mode (e.g.
277 | * networkEnabled(false)). This would only be used if you wanted to start the
278 | * Tor OP so that the install and related is all done but aren't ready to
279 | * actually connect it to the network.
280 | *
281 | * @return True if all files installed and Tor OP successfully started
282 | * @throws java.io.IOException
283 | * - IO Exceptions
284 | * @throws java.lang.InterruptedException
285 | * - If we are, well, interrupted
286 | */
287 | @Throws(IOException::class)
288 | fun installAndStartTorOp(bridgeConfig: List, eventHandler: TorEventHandler): TorController {
289 |
290 | installAndConfigureFiles(bridgeConfig)
291 |
292 | logger?.info("Starting Tor")
293 | val cookieFile = cookieFile
294 | if (!cookieFile.parentFile.exists() && !cookieFile.parentFile.mkdirs()) {
295 | throw RuntimeException("Could not create cookieFile parent directory")
296 | }
297 |
298 | if (!cookieFile.exists() && !cookieFile.createNewFile()) {
299 | throw RuntimeException("Could not create cookieFile")
300 | }
301 |
302 | val workingDirectory = workingDirectory
303 | // Watch for the auth cookie file being created/updated
304 | val cookieObserver = generateWriteObserver(cookieFile)
305 | // Start a new Tor process
306 | val torPath = torExecutableFile.absolutePath
307 | val configPath = torrcFile.absolutePath
308 | val pid = processId
309 | val cmd = listOf(torPath, "-f", configPath, OWNER, pid)
310 | val processBuilder = ProcessBuilder(cmd)
311 | setEnvAndWD(processBuilder)
312 | var torProcess: Process? = null
313 | var ctrlCon: TorControlConnection? = null
314 | try {
315 |
316 | torProcess = processBuilder.start()
317 | val controlPortCountDownLatch = CountDownLatch(1)
318 | val port = AtomicReference()
319 | parseBootstrap(torProcess.inputStream, controlPortCountDownLatch, port)
320 | forwardErr(torProcess.errorStream)
321 |
322 | // On platforms other than WIN we run as a daemon and so we need
323 | // to wait for the process to detach
324 | // or exit. In the case of WIN the equivalent is running as a
325 | // service and unfortunately that requires
326 | // managing the service, such as turning it off or uninstalling it
327 | // when it's time to move on. Any number
328 | // of errors can prevent us from doing the cleanup and so we would
329 | // leave the process running around. Rather
330 | // than do that on WIN we just let the process run on the exec
331 | // and hence don't look for an exit code.
332 | // This does create a condition where the process has exited due to
333 | // a problem but we should hopefully
334 | // detect that when we try to use the control connection.
335 | if (OsType.current != OsType.WIN) {
336 | val exit = torProcess.waitFor()
337 | torProcess = null
338 | if (exit != 0) {
339 | throw IOException("Tor exited with value $exit")
340 | }
341 | }
342 |
343 | // Wait for the auth cookie file to be created/updated
344 | if (!cookieObserver.poll(COOKIE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)) {
345 | workingDirectory.log()
346 | throw IOException("Auth cookie not created")
347 | }
348 |
349 | // Now we should be able to connect to the new process
350 | controlPortCountDownLatch.await()
351 | val sock = Socket(LOCAL_IP, port.get())
352 |
353 | // Open a control connection and authenticate using the cookie file
354 | ctrlCon = TorController(sock)
355 |
356 | var cookie: ByteArray?
357 | while (true) {
358 | try {
359 | cookie = cookieFile.readBytes()
360 | break;
361 | } catch (e: Exception) {
362 | Thread.sleep(50)
363 | Thread.yield()
364 | }
365 | }
366 | ctrlCon.authenticate(cookie)
367 | // Tell Tor to exit when the control connection is closed
368 | ctrlCon.takeOwnership()
369 | ctrlCon.resetConf(listOf(OWNER))
370 |
371 | ctrlCon.setEventHandler(eventHandler)
372 | ctrlCon.setEvents(EVENTS)
373 |
374 |
375 | return ctrlCon
376 | } catch (e: Exception) {
377 | throw IOException(e)
378 | } finally {
379 | // It's possible that something 'bad' could happen after we
380 | // executed exec but before we takeOwnership()
381 | // in which case the Tor OP will hang out as a zombie until this
382 | // process is killed. This is problematic
383 | // when we want to do things like
384 | if (ctrlCon == null) {
385 | torProcess?.destroy()
386 | }
387 | }
388 | }
389 |
390 | @Throws(IOException::class, InterruptedException::class)
391 | protected fun installAndConfigureFiles(bridgeConfig: List) {
392 |
393 | installFiles()
394 |
395 | PrintWriter(FileWriter(torrcFile, true).buffered()).use { confWriter ->
396 | confWriter.println()
397 | confWriter.println(DIRECTIVE_COOKIE_AUTH_FILE + cookieFile.absolutePath)
398 | // For some reason the GeoIP's location can only be given as a file
399 | // name, not a path and it has
400 | // to be in the data directory so we need to set both
401 | confWriter.println(DIRECTIVE_DATA_DIRECTORY + workingDirectory.absolutePath)
402 | confWriter.println(DIRECTIVE_GEOIP_FILE + geoIpFile.absolutePath)
403 | confWriter.println(DIRECTIVE_PIDFILE + pidFile.absolutePath)
404 | confWriter.println(DIRECTIVE_GEOIP6_FILE + geoIpv6File.absolutePath)
405 |
406 | getByName(pathToRC).reader().buffered().use { reader ->
407 | confWriter.println()
408 | reader.forEachLine {
409 | confWriter.println(it)
410 | }
411 |
412 | }
413 | if (!bridgeConfig.isEmpty()) {
414 | confWriter.println()
415 | confWriter.println("UseBridges 1")
416 | }
417 | bridgeConfig.forEach {
418 | confWriter.print("Bridge ")
419 | confWriter.println(it)
420 | }
421 | }
422 | }
423 | }
--------------------------------------------------------------------------------
/tor.native/src/main/resources/native/linux/torrc.native:
--------------------------------------------------------------------------------
1 | ## fteproxy configuration
2 | ClientTransportPlugin fte exec ./PluggableTransports/fteproxy.bin --managed
3 |
4 | ## obfs4proxy configuration
5 | ClientTransportPlugin obfs2,obfs3,obfs4,scramblesuit exec ./PluggableTransports/obfs4proxy
6 |
7 | ## meek configuration
8 | ClientTransportPlugin meek exec ./PluggableTransports/meek-client
--------------------------------------------------------------------------------
/tor.native/src/main/resources/native/osx/torrc.native:
--------------------------------------------------------------------------------
1 | ## obfs4proxy configuration
2 | ClientTransportPlugin obfs2,obfs3,obfs4,scramblesuit exec PluggableTransports/obfs4proxy
3 |
4 | ## meek configuration
5 | ClientTransportPlugin meek exec PluggableTransports/meek-client
6 |
--------------------------------------------------------------------------------
/tor.native/src/main/resources/native/windows/torrc.native:
--------------------------------------------------------------------------------
1 | ## fteproxy configuration
2 | ClientTransportPlugin fte exec PluggableTransports\fteproxy --managed
3 |
4 | ## obfs4proxy configuration
5 | ClientTransportPlugin obfs2,obfs3,obfs4,scramblesuit exec PluggableTransports\obfs4proxy
6 |
7 | ## meek configuration
8 | ClientTransportPlugin meek exec PluggableTransports\terminateprocess-buffer PluggableTransports\meek-client
9 |
--------------------------------------------------------------------------------
/tor.native/src/test/java/org/berndpruenster/netlayer/tor/demo/JavaDemo.java:
--------------------------------------------------------------------------------
1 | package org.berndpruenster.netlayer.tor.demo;
2 |
3 | import com.beust.jcommander.JCommander;
4 | import com.beust.jcommander.Parameter;
5 | import org.berndpruenster.netlayer.tor.*;
6 |
7 | import java.io.BufferedReader;
8 | import java.io.File;
9 | import java.io.FileReader;
10 | import java.io.IOException;
11 | import java.util.Collection;
12 | import java.util.LinkedList;
13 | import java.util.List;
14 | import java.util.Scanner;
15 |
16 | public class JavaDemo {
17 | @Parameter(names = {"-b"},
18 | description = "path to a file containing bridge configuration lines as obtainable from bridges.torproject.org")
19 | private String pathBridges;
20 |
21 | @Parameter(names = {"-p"}, description = "hidden Service Port")
22 | private int port;
23 |
24 | public static void main(String[] args) throws IOException, TorCtlException {
25 | JavaDemo demo = new JavaDemo();
26 | demo.port = 10024;
27 | new JCommander(demo).parse();
28 |
29 |
30 | //set default instance, so it can be omitted whenever creating Tor (Server)Sockets
31 | //This will take some time
32 | Tor.setDefault(new NativeTor(/*Tor installation destination*/
33 | new File("tor-demo"),
34 | /*bridge configuration*/ parseBridgeLines(demo.pathBridges)));
35 |
36 | System.out.println("Tor has been bootstrapped");
37 |
38 | //create a hidden service in directory 'test' inside the tor installation directory
39 | HiddenServiceSocket hiddenServiceSocket = new HiddenServiceSocket(demo.port, "test");
40 |
41 | //it takes some time for a hidden service to be ready, so adding a listener only after creating the HS is not an issue
42 | hiddenServiceSocket.addReadyListener(socket -> {
43 | System.out.println("Hidden Service " + socket + " is ready");
44 | new Thread(() -> {
45 | System.err.println("we'll try and connect to the just-published hidden service");
46 | try {
47 | new TorSocket(socket.getSocketAddress(), "Foo");
48 | System.err.println("Connected to $socket. closing socket...");
49 | socket.close();
50 | } catch (Exception e) {
51 | System.err.println("This should have worked");
52 | }
53 | //retry connecting
54 | try {
55 |
56 | new TorSocket(socket.getServiceName(), socket.getHiddenServicePort(), "Foo");
57 | } catch (Exception e) {
58 | System.err.println("As exptected, connection to " + socket + " failed!");
59 | }
60 | try {
61 | //let's connect to some regular domains using different streams
62 | new TorSocket("www.google.com", 80, "FOO");
63 | new TorSocket("www.cnn.com", 80, "BAR");
64 | new TorSocket("www.google.com", 80, "BAZ");
65 | } catch (Exception e) {
66 | System.err.println("This should have worked");
67 | }
68 |
69 | System.exit(0);
70 |
71 | }).start();
72 | try {
73 | socket.accept();
74 | } catch (IOException e) {
75 | e.printStackTrace();
76 | }
77 | System.err.println("$socket got a connection");
78 |
79 | return null;
80 | });
81 | System.err.println(
82 | "It will take some time for the HS to be reachable (up to 40 seconds). You will be notified about this");
83 | new Scanner(System.in).nextLine();
84 | }
85 |
86 |
87 | private static Collection parseBridgeLines(String file) throws IOException {
88 | if (file == null) {
89 | return null;
90 | }
91 | try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
92 | String line;
93 | List lines = new LinkedList<>();
94 | while ((line = reader.readLine()) != null) {
95 | lines.add(line);
96 | }
97 | return lines;
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/tor.native/src/test/kotlin/org/berndpruenster/netlayer/tor/demo/Demo.kt:
--------------------------------------------------------------------------------
1 | package org.berndpruenster.netlayer.tor.demo
2 |
3 | import com.beust.jcommander.JCommander
4 | import com.beust.jcommander.Parameter
5 | import org.berndpruenster.netlayer.tor.*
6 | import java.io.BufferedReader
7 | import java.io.File
8 | import java.io.FileReader
9 | import java.util.*
10 | import kotlin.concurrent.thread
11 |
12 |
13 | class Demo {
14 |
15 | @Parameter(names = ["-b"],
16 | description = "path to a file containing bridge configuration lines as obtainable from bridges.torproject.org")
17 | internal var pathBridges: String? = null
18 |
19 | @Parameter(names = ["-p"], description = "hidden Service Port")
20 | internal var port: Int? = null
21 | }
22 |
23 | fun main(args: Array) {
24 | val demo = Demo()
25 | demo.port = 10024
26 | JCommander(demo).parse(*args)
27 |
28 | val override = Torrc(linkedMapOf(Pair("HiddenServiceStatistics", "1")))
29 | println(override)
30 | //set default instance, so it can be omitted whenever creating Tor (Server)Sockets
31 | //This will take some time
32 | Tor.default = NativeTor(/*Tor installation destination*/ File("tor-demo"),
33 | /*bridge configuration*/ parseBridgeLines(demo.pathBridges), override)
34 | println("Tor has been bootstrapped")
35 |
36 | //create a hidden service in directory 'test' inside the tor installation directory
37 | val hiddenServiceSocket = HiddenServiceSocket(demo.port!!, "test")
38 |
39 | //it takes some time for a hidden service to be ready, so adding a listener only after creating the HS is not an issue
40 | hiddenServiceSocket.addReadyListener { socket ->
41 |
42 | println("Hidden Service $socket is ready")
43 | thread {
44 |
45 | System.err.println("we'll try and connect to the just-published hidden service")
46 | TorSocket(socket.serviceName, socket.hiddenServicePort, streamId = "Foo")
47 | System.err.println("Connected to $socket. closing socket...")
48 | socket.close()
49 | //retry connecting
50 | try {
51 |
52 | TorSocket(socket.serviceName, socket.hiddenServicePort, streamId = "Foo")
53 | } catch (e: Exception) {
54 | System.err.println("As exptected, connection to $socket failed!")
55 | }
56 | //let's connect to some regular domains using different streams
57 | TorSocket("www.google.com", 80, streamId = "FOO")
58 | TorSocket("www.cnn.com", 80, streamId = "BAR")
59 | TorSocket("www.google.com", 80, streamId = "BAZ")
60 |
61 | System.exit(0)
62 | }
63 | socket.accept()
64 | System.err.println("$socket got a connection")
65 |
66 |
67 | }
68 | System.err.println("It will take some time for the HS to be reachable (up to 40 seconds). You will be notified about this")
69 | Scanner(System.`in`).nextLine()
70 |
71 | }
72 |
73 |
74 | private fun parseBridgeLines(file: String?): Collection? {
75 | if (file == null) return null
76 | BufferedReader(FileReader(file)).use { reader ->
77 | val lines = mutableListOf()
78 | reader.forEachLine { line ->
79 | lines.add(line)
80 | }
81 | return lines
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tor/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | 4.0.0
6 |
7 | org.berndpruenster.netlayer
8 | parent
9 | 0.4.9-pre
10 |
11 |
12 | tor
13 | jar
14 |
15 | Tor API
16 |
17 |
18 |
19 | com.cedricwalter
20 | tor-binary-geoip
21 | ${tor-binary.version}
22 |
23 |
24 | com.github.JesusMcCloud
25 | jtorctl
26 | 0
27 |
28 |
29 | org.apache.commons
30 | commons-compress
31 | 1.18
32 |
33 |
34 | org.tukaani
35 | xz
36 | 1.6
37 |
38 |
39 | org.jetbrains.kotlin
40 | kotlin-stdlib-jdk8
41 | ${kotlin.version}
42 |
43 |
44 | org.jetbrains.kotlin
45 | kotlin-test
46 | ${kotlin.version}
47 | test
48 |
49 |
50 |
51 |
52 | ${project.basedir}/src/main/kotlin
53 |
54 |
55 | kotlin-maven-plugin
56 | org.jetbrains.kotlin
57 | ${kotlin.version}
58 |
59 |
60 | compile
61 |
62 | compile
63 |
64 |
65 |
66 |
67 | test-compile
68 |
69 | test-compile
70 |
71 |
72 |
73 |
74 |
75 | org.codehaus.mojo
76 | build-helper-maven-plugin
77 | 3.0.0
78 |
79 |
80 | generate-sources
81 |
82 | add-source
83 |
84 |
85 |
86 | src/main/kotlin
87 |
88 |
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-javadoc-plugin
95 | 2.10.4
96 |
97 |
98 | attach-javadocs
99 |
100 | jar
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/tor/src/main/kotlin/org/berndpruenster/netlayer/tor/OsType.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | Copyright (C) 2011-2014 Sublime Software Ltd
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 | */
34 |
35 | package org.berndpruenster.netlayer.tor
36 |
37 | import java.io.IOException
38 | import java.util.*
39 |
40 | enum class OsType { WIN,
41 | LNX32,
42 | LNX64,
43 | MACOS,
44 | ANDROID;
45 |
46 | companion object {
47 | @JvmStatic
48 | val current: OsType by lazy {
49 | //This also works for ART
50 | if (System.getProperty("java.vm.name").contains("Dalvik")) {
51 | ANDROID
52 | } else {
53 | val osName = System.getProperty("os.name")
54 | when {
55 | osName.contains("Windows") -> WIN
56 | osName.contains("Mac") -> MACOS
57 | osName.contains("Linux") -> getLinuxType()
58 | else -> throw RuntimeException("Unsupported OS: $osName")
59 | }
60 | }
61 | }
62 |
63 | private fun getLinuxType(): OsType {
64 | val cmd = arrayOf("uname", "-m")
65 | val unameProcess = Runtime.getRuntime().exec(cmd)
66 | try {
67 | val unameOutput: String
68 |
69 | val scanner = Scanner(unameProcess.inputStream)
70 | if (scanner.hasNextLine()) {
71 | unameOutput = scanner.nextLine()
72 | scanner.close()
73 | } else {
74 | scanner.close()
75 | throw RuntimeException("Couldn't get output from uname call")
76 | }
77 |
78 | val exit = unameProcess.waitFor()
79 | if (exit != 0) {
80 | throw RuntimeException("Uname returned error code $exit")
81 | }
82 |
83 | if (unameOutput.matches(Regex("i.86"))) {
84 | return LNX32
85 | }
86 | if (unameOutput.compareTo("x86_64") == 0) {
87 | return LNX64
88 | }
89 | throw RuntimeException("Could not understand uname output, not sure what bitness")
90 | } catch (e: IOException) {
91 | throw RuntimeException("Uname failure", e)
92 | } catch (e: InterruptedException) {
93 | throw RuntimeException("Uname failure", e)
94 | } finally {
95 | unameProcess.destroy()
96 | }
97 | }
98 |
99 | }
100 |
101 | fun isUnixoid(): Boolean {
102 | return listOf(LNX32, LNX64, MACOS).contains(this)
103 | }
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/tor/src/main/kotlin/org/berndpruenster/netlayer/tor/Tor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | Copyright (C) 2011-2014 Sublime Software Ltd
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 | */
34 |
35 | package org.berndpruenster.netlayer.tor
36 |
37 | import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy
38 | import mu.KotlinLogging
39 | import net.freehaven.tor.control.ConfigEntry
40 | import net.freehaven.tor.control.TorControlConnection
41 | import java.io.File
42 | import java.io.IOException
43 | import java.math.BigInteger
44 | import java.net.Socket
45 | import java.security.MessageDigest
46 |
47 | /**
48 | * This class began life as TorPlugin from the Briar Project
49 | */
50 |
51 |
52 | const val LOCAL_IP = "127.0.0.1"
53 | private const val LOCAL_ADDR_FRAGMENT = "\"" + LOCAL_IP + ":"
54 | private const val NET_LISTENERS_SOCKS = "net/listeners/socks"
55 |
56 | private const val STATUS_BOOTSTRAPPED = "status/bootstrap-phase"
57 | private const val DISABLE_NETWORK = "DisableNetwork"
58 |
59 | val logger = try {
60 | KotlinLogging.logger { }
61 | } catch (e: Throwable) {
62 | System.err.println(e); null
63 | }
64 |
65 |
66 | class TorCtlException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause)
67 |
68 |
69 | class TorController(private val socket: Socket) : TorControlConnection(socket) {
70 |
71 | val bootstrapped: Boolean
72 | get() = getInfo(STATUS_BOOTSTRAPPED)?.contains("PROGRESS=100") ?: false
73 |
74 | fun shutdown() {
75 | socket.use {
76 | logger?.debug("Stopping Tor")
77 | setConf(DISABLE_NETWORK, "1")
78 | shutdownTor("TERM")
79 | }
80 | }
81 |
82 | fun enableNetwork() {
83 | setConf(DISABLE_NETWORK, "0")
84 | }
85 |
86 | }
87 |
88 | class Control(private val con: TorController) {
89 |
90 | companion object {
91 | @JvmStatic
92 | private val EVENTS_HS = listOf("EXTENDED", "CIRC", "ORCONN", "INFO", "NOTICE", "WARN", "ERR", "HS_DESC")
93 |
94 | private const val HS_OPTS = "HiddenServiceOptions"
95 | }
96 |
97 | internal val proxyPort = parsePort()
98 |
99 | internal var running = true
100 | private set
101 |
102 | init {
103 | Runtime.getRuntime().addShutdownHook(Thread({ shutdown() }))
104 | }
105 |
106 |
107 | fun shutdown() {
108 | synchronized(running) {
109 | if (!running) return
110 | running = false
111 | con.shutdown()
112 | }
113 | }
114 |
115 |
116 | private fun parsePort(): Int {
117 | // This returns a set of space delimited quoted strings which could be
118 | // Ipv4, Ipv6 or unix sockets
119 | val socksIpPorts = con.getInfo(NET_LISTENERS_SOCKS)?.split(" ")
120 |
121 | socksIpPorts?.filter { it.contains(LOCAL_ADDR_FRAGMENT) }?.forEach {
122 | return Integer.parseInt(it.substring(it.lastIndexOf(":") + 1, it.length - 1))
123 | }
124 | throw IOException("No IPv4 localhost binding available!")
125 | }
126 |
127 | val hiddenServices: List get() = con.getConf(HS_OPTS)
128 | fun enableHiddenServiceEvents() {
129 | con.setEvents(EVENTS_HS)
130 | }
131 |
132 | fun saveConfig(config: List) {
133 | con.setConf(config)
134 | con.saveConf()
135 | }
136 |
137 | fun hsAvailable(onionUrl: String): Boolean = con.isHSAvailable(onionUrl.substring(0, onionUrl.indexOf(".")))
138 |
139 |
140 | }
141 |
142 | data class HsContainer(internal val hostname: String, internal val handler: TorEventHandler)
143 |
144 |
145 | abstract class Tor @Throws(TorCtlException::class) protected constructor() {
146 |
147 | protected val TOTAL_SEC_PER_STARTUP = 4 * 60
148 | protected val TRIES_PER_STARTUP = 5
149 |
150 | protected val eventHandler: TorEventHandler = TorEventHandler()
151 | lateinit var control: Control
152 |
153 | companion object {
154 |
155 | @JvmStatic
156 | var default: Tor? = null
157 |
158 | internal fun clear() {
159 | default = null
160 | }
161 |
162 |
163 | @Throws(TorCtlException::class)
164 | @JvmStatic
165 | @JvmOverloads
166 | fun getProxy(proxyPort: Int, streamID: String? = null): Socks5Proxy {
167 | val proxy: Socks5Proxy
168 | try {
169 | proxy = Socks5Proxy(LOCAL_IP, proxyPort)
170 | } catch (e: IOException) {
171 | throw TorCtlException(cause = e)
172 | }
173 | proxy.resolveAddrLocally(false)
174 | streamID?.let {
175 | val hash: ByteArray
176 | val authValue = BigInteger(MessageDigest.getInstance("SHA-256").digest(streamID.toByteArray())).toString(
177 | 26)
178 | hash = authValue.toByteArray()
179 |
180 | proxy.setAuthenticationMethod(2, { _, proxySocket ->
181 | logger?.debug("using Stream $authValue")
182 |
183 | val out = proxySocket.getOutputStream()
184 | out.write(byteArrayOf(1.toByte(), hash.size.toByte()))
185 | out.write(hash)
186 | out.write(byteArrayOf(1.toByte(), 0.toByte()))
187 | out.flush()
188 | val status = ByteArray(2)
189 | proxySocket.getInputStream().read(status)
190 | if (status[1] != 0.toByte()) {
191 | throw IOException("auth error: " + status[1])
192 | }
193 | arrayOf(proxySocket.getInputStream(), out)
194 | })
195 | }
196 | return proxy
197 |
198 | }
199 | }
200 |
201 | @Throws(TorCtlException::class)
202 | @JvmOverloads
203 | fun getProxy(streamID: String? = null): Socks5Proxy = Tor.getProxy(control.proxyPort, streamID)
204 |
205 |
206 | /**
207 | * Publishes a hidden service
208 | *
209 | * @param hiddenServicePort
210 | * The port that the hidden service will accept connections on
211 | * @param localPort
212 | * The local port that the hidden service will relay connections to
213 | * @return The hidden service's onion address in the form X.onion.
214 | * @throws java.io.IOException
215 | * - File errors
216 | * @throws TorCtlException
217 | */
218 | @Throws(IOException::class, TorCtlException::class)
219 | abstract fun publishHiddenService(hsDirName: String, hiddenServicePort: Int, localPort: Int): HsContainer
220 |
221 | @Throws(TorCtlException::class, IOException::class)
222 | abstract fun unpublishHiddenService(hsDir: String)
223 |
224 | fun isHiddenServiceAvailable(onionUrl: String): Boolean = control.hsAvailable(onionUrl)
225 |
226 | abstract fun shutdown()
227 | }
228 |
--------------------------------------------------------------------------------
/tor/src/main/kotlin/org/berndpruenster/netlayer/tor/TorEventHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2016, 2017 Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 |
18 |
19 | Copyright (c) 2014-2015 Microsoft Open Technologies, Inc.
20 | Copyright (C) 2011-2014 Sublime Software Ltd
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 | */
34 |
35 | package org.berndpruenster.netlayer.tor
36 |
37 | import net.freehaven.tor.control.EventHandler
38 |
39 | /**
40 | * Logs the data we get from notifications from the Tor OP. This is really just
41 | * meant for debugging.
42 | */
43 | private const val UPLOADED = "UPLOADED"
44 | private const val HS_DESC = "HS_DESC"
45 |
46 | class TorEventHandler : EventHandler {
47 |
48 |
49 | private val socketMap = HashMap()
50 | private val listenerMap = HashMap Unit>>()
51 |
52 |
53 | fun attachReadyListeners(hs: HiddenServiceSocket, listeners: List<(socket: HiddenServiceSocket) -> Unit>) {
54 | synchronized(socketMap) {
55 | socketMap[hs.socketAddress.serviceName] = hs
56 | listenerMap[hs.socketAddress.serviceName] = listeners
57 | }
58 | }
59 |
60 | override fun circuitStatus(status: String, id: String, path: String) {
61 | val msg = "CircuitStatus: $id $status $path"
62 | logger?.debug(msg)
63 | }
64 |
65 | override fun streamStatus(status: String, id: String, target: String) {
66 | val msg = "streamStatus: status: $status $id: , target: $target"
67 | logger?.debug(msg)
68 |
69 | }
70 |
71 | override fun orConnStatus(status: String, orName: String) {
72 | val msg = "OR connection: status: $status, orName: $orName"
73 | logger?.debug(msg)
74 | }
75 |
76 | override fun bandwidthUsed(read: Long, written: Long) {
77 | logger?.debug("bandwidthUsed: read: $read , written: $written")
78 | }
79 |
80 | override fun newDescriptors(orList: List) {
81 |
82 | val stringBuilder = StringBuilder("newDescriptors: ")
83 |
84 | orList.forEach {
85 | stringBuilder.append(it)
86 | }
87 | logger?.debug(stringBuilder.toString())
88 |
89 | }
90 |
91 | override fun message(severity: String, msg: String) {
92 | val msg2 = "message: severity: $severity , msg: $msg"
93 | logger?.debug(msg2)
94 | }
95 |
96 | override fun unrecognized(type: String, msg: String) {
97 | val msg2 = "unrecognized: current: $type , $msg: msg"
98 | logger?.debug(msg2)
99 | if (type == (HS_DESC) && msg.startsWith(UPLOADED)) {
100 | val hiddenServiceID = "${msg.split(" ")[1]}.onion"
101 | synchronized(socketMap) {
102 | val hs = socketMap[hiddenServiceID] ?: return
103 | logger?.info("Hidden Service $hs is ready")
104 | listenerMap[hiddenServiceID]?.forEach {
105 | it(hs)
106 | }
107 | socketMap.remove(hiddenServiceID)
108 | listenerMap.remove(hiddenServiceID)
109 | }
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/tor/src/main/kotlin/org/berndpruenster/netlayer/tor/TorSockets.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2017, Bernd Prünster
3 | This file is part of of the unofficial Java-Tor-bindings.
4 |
5 | Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
6 | European Commission - subsequent versions of the EUPL (the "Licence"); You may
7 | not use this work except in compliance with the Licence. You may obtain a copy
8 | of the Licence at: http://joinup.ec.europa.eu/software/page/eupl
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed
11 | under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
12 | CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
13 | specific language governing permissions and limitations under the Licence.
14 |
15 | This project includes components developed by third parties and provided under
16 | various open source licenses (www.opensource.org).
17 | */
18 | package org.berndpruenster.netlayer.tor
19 |
20 | import com.runjva.sourceforge.jsocks.protocol.SocksSocket
21 | import java.io.IOException
22 | import java.io.InputStream
23 | import java.io.OutputStream
24 | import java.net.*
25 | import java.nio.channels.SocketChannel
26 | import java.util.*
27 |
28 | private const val RETRY_SLEEP: Long = 500
29 |
30 | data class HiddenServiceSocketAddress(val serviceName: String, val hiddenServicePort: Int) : SocketAddress() {
31 | override fun toString(): String = "HiddenServiceSocket[addr=$serviceName,port=$hiddenServicePort]"
32 | }
33 |
34 | class TorSocket @JvmOverloads @Throws(IOException::class) constructor(private val destination: String,
35 | port: Int,
36 | streamId: String? = null,
37 | numTries: Int = 5,
38 | tor: Tor? = null) : Socket() {
39 | @JvmOverloads @Throws(IOException::class) constructor(socketAddress: HiddenServiceSocketAddress,
40 | streamId: String? = null,
41 | numTries: Int = 5,
42 | tor: Tor? = null) : this(socketAddress.serviceName,
43 | socketAddress.hiddenServicePort,
44 | streamId,
45 | numTries,
46 | tor)
47 |
48 | private val socket = setup(destination, port, numTries, streamId, tor)
49 |
50 | @Throws(IOException::class)
51 | override fun connect(addr: SocketAddress) = throw IOException("DONT!")
52 |
53 | @Throws(IOException::class)
54 | override fun connect(addr: SocketAddress, port: Int) = throw IOException("DONT!")
55 |
56 | @Throws(IOException::class)
57 | override fun bind(addr: SocketAddress) {
58 | socket.bind(addr)
59 | }
60 |
61 | override fun getInetAddress(): InetAddress = socket.inetAddress
62 | override fun getLocalAddress(): InetAddress = socket.localAddress
63 | override fun getPort(): Int = socket.port
64 | override fun getLocalPort(): Int = socket.localPort
65 | override fun getRemoteSocketAddress(): SocketAddress = HiddenServiceSocketAddress(destination, port)
66 | override fun getLocalSocketAddress(): SocketAddress = socket.localSocketAddress
67 | override fun getChannel(): SocketChannel = socket.channel
68 | override fun toString(): String = "TorSocket[addr=$destination,port=$port, localPort=$localPort]"
69 | override fun isConnected(): Boolean = socket.isConnected
70 | override fun isBound(): Boolean = socket.isBound
71 | override fun isClosed(): Boolean = socket.isClosed
72 | override fun isInputShutdown(): Boolean = socket.isInputShutdown
73 | override fun isOutputShutdown(): Boolean = socket.isOutputShutdown
74 | @Throws(IOException::class)
75 | override fun getInputStream(): InputStream = socket.getInputStream()
76 |
77 | @Throws(IOException::class)
78 | override fun getOutputStream(): OutputStream = socket.getOutputStream()
79 |
80 | @Throws(SocketException::class)
81 | override fun getTcpNoDelay(): Boolean = socket.tcpNoDelay
82 |
83 | @Throws(SocketException::class)
84 | override fun getOOBInline(): Boolean = socket.oobInline
85 |
86 | @Throws(SocketException::class)
87 | override fun getSoTimeout(): Int = socket.soTimeout
88 |
89 | @Throws(SocketException::class)
90 | override fun getSendBufferSize(): Int = socket.sendBufferSize
91 |
92 | @Throws(SocketException::class)
93 | override fun getReceiveBufferSize(): Int = socket.receiveBufferSize
94 |
95 | @Throws(SocketException::class)
96 | override fun getTrafficClass(): Int = socket.trafficClass
97 |
98 | @Throws(SocketException::class)
99 | override fun getKeepAlive(): Boolean = socket.keepAlive
100 |
101 | @Throws(SocketException::class)
102 | override fun getReuseAddress(): Boolean = socket.reuseAddress
103 |
104 | @Throws(SocketException::class)
105 | override fun getSoLinger(): Int = socket.soLinger
106 |
107 | @Throws(SocketException::class)
108 | override fun setTcpNoDelay(arg0: Boolean) {
109 | socket.tcpNoDelay = arg0
110 | }
111 |
112 | @Throws(SocketException::class)
113 | override fun setSoLinger(arg0: Boolean, arg1: Int) {
114 | socket.setSoLinger(arg0, arg1)
115 | }
116 |
117 | @Throws(IOException::class)
118 | override fun sendUrgentData(arg0: Int) {
119 | socket.sendUrgentData(arg0)
120 | }
121 |
122 | @Throws(SocketException::class)
123 | override fun setOOBInline(arg0: Boolean) {
124 | socket.oobInline = arg0
125 | }
126 |
127 | @Throws(SocketException::class)
128 | override fun setSoTimeout(arg0: Int) {
129 | socket.soTimeout = arg0
130 | }
131 |
132 | @Throws(SocketException::class)
133 | override fun setSendBufferSize(arg0: Int) {
134 | socket.sendBufferSize = arg0
135 | }
136 |
137 | @Throws(SocketException::class)
138 | override fun setReceiveBufferSize(arg0: Int) {
139 | socket.receiveBufferSize = arg0
140 | }
141 |
142 | @Throws(SocketException::class)
143 | override fun setKeepAlive(arg0: Boolean) {
144 | socket.keepAlive = arg0
145 | }
146 |
147 | @Throws(SocketException::class)
148 | override fun setTrafficClass(arg0: Int) {
149 | socket.trafficClass = arg0
150 | }
151 |
152 | @Throws(SocketException::class)
153 | override fun setReuseAddress(arg0: Boolean) {
154 | socket.reuseAddress = arg0
155 | }
156 |
157 | @Throws(IOException::class)
158 | override fun close() {
159 | socket.close()
160 | }
161 |
162 | @Throws(IOException::class)
163 | override fun shutdownInput() {
164 | socket.shutdownInput()
165 | }
166 |
167 | @Throws(IOException::class)
168 | override fun shutdownOutput() {
169 | socket.shutdownOutput()
170 | }
171 |
172 | override fun setPerformancePreferences(arg0: Int, arg1: Int, arg2: Int) {
173 | socket.setPerformancePreferences(arg0, arg1, arg2)
174 | }
175 | }
176 |
177 |
178 | class HiddenServiceSocket @JvmOverloads constructor(internalPort: Int,
179 | val hiddenServiceDir: String,
180 | val hiddenServicePort: Int = internalPort,
181 | tor: Tor? = null) : ServerSocket() {
182 |
183 | private val mgr = getTorInstance(tor)
184 | private val listeners = mutableListOf<(socket: HiddenServiceSocket) -> Unit>()
185 |
186 | val socketAddress: HiddenServiceSocketAddress
187 | val serviceName: String
188 |
189 | init {
190 | val (name, handler) = mgr.publishHiddenService(hiddenServiceDir, hiddenServicePort, internalPort)
191 | serviceName = name
192 | socketAddress = HiddenServiceSocketAddress(name, hiddenServicePort)
193 | bind(InetSocketAddress(LOCAL_IP, internalPort))
194 | handler.attachReadyListeners(this, listeners)
195 | }
196 |
197 | fun addReadyListener(listener: (socket: HiddenServiceSocket) -> Unit) {
198 | synchronized(listeners) {
199 | listeners.add(listener)
200 | }
201 | }
202 |
203 | override fun toString(): String = socketAddress.toString()
204 |
205 |
206 | override fun close() {
207 | super.close()
208 | try {
209 | mgr.unpublishHiddenService(hiddenServiceDir)
210 | } catch (e: TorCtlException) {
211 | throw IOException(e)
212 | }
213 | }
214 |
215 | }
216 |
217 | @Throws(IOException::class)
218 | private fun setup(onionUrl: String, port: Int, numTries: Int, streamID: String?, tor: Tor?): Socket {
219 |
220 | val before = Calendar.getInstance().timeInMillis
221 | val mgr = getTorInstance(tor)
222 | for (i in 1..numTries) {
223 | try {
224 | logger?.debug { "trying to connect to $onionUrl:$port" }
225 | val proxy = mgr.getProxy(streamID)
226 | logger?.debug { "got proxy $proxy" }
227 | val ssock = SocksSocket(proxy, onionUrl, port)
228 |
229 | logger?.debug("Took ${Calendar.getInstance().timeInMillis - before}ms to connect to " + onionUrl + ":" + port)
230 | ssock.tcpNoDelay = true
231 | return ssock
232 | } catch (exx: UnknownHostException) {
233 | logger?.debug("Try $i connecting to $onionUrl:$port failed. retrying...")
234 | Thread.sleep(RETRY_SLEEP)
235 | continue
236 |
237 | } catch (e: Exception) {
238 | throw IOException("Cannot connect to hidden service")
239 | }
240 | }
241 | throw IOException("Cannot connect to HS")
242 | }
243 |
244 | private fun getTorInstance(tor: Tor?): Tor = tor ?: Tor.default
245 | ?: throw IOException("No default Tor Instance configured")
246 |
--------------------------------------------------------------------------------
/tor/src/main/resources/torrc:
--------------------------------------------------------------------------------
1 | ControlPort auto
2 | CookieAuthentication 1
3 | DisableNetwork 1
4 | AvoidDiskWrites 1
5 | RunAsDaemon 1
6 | SOCKSPort auto
--------------------------------------------------------------------------------
/tor/src/main/resources/torrc.defaults:
--------------------------------------------------------------------------------
1 | SafeSocks 0
2 | HiddenServiceStatistics 0
--------------------------------------------------------------------------------