├── .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 --------------------------------------------------------------------------------