35 Commits

Author SHA1 Message Date
eyedeekay
bd8b31845a Add github sync for i2p.plugins.orchid
Some checks failed
Sync Primary Repository to GitHub Mirror / sync (push) Has been cancelled
2025-05-10 18:48:47 -04:00
zzz
c7e0df48bb Release 1.2.2-0.6 2019-08-21 12:30:46 +00:00
zzz
29bdda5d0a Update dizum dirauth IP (tor ticket #31406)
log tweak
2019-08-21 12:19:53 +00:00
zzz
a7a9b2642a microdesc fixes part 3 2019-08-18 14:29:01 +00:00
zzz
cd53feb4a7 microdesc fix part 2 2019-08-18 12:05:26 +00:00
zzz
5350fcae7e fix for empty tooltip with microdescriptors 2019-08-17 23:01:56 +00:00
zzz
c4038916c4 Selected and modified portions of patch from drz3d:
- Overhaul front-end to use full html output and a default light theme
 - Enable GeoIP lookups for nodes to permit display of flags
 - Add country code lookups for nodes and display on tooltip
 - Mitigate logging output issues caused by html output
 - Set useMicroDescriptors to FALSE so additional node information can
   be presented in the UI, if enough memory available
 - Allow 2nd parameter in extra-info-digest when pulling full descriptors
 - Display platform, observed bandwidth and uptime on circuit node tooltips
 - Add hints and notes to config section, and include missing options
 - Change maxCircuitDirtiness to Tor default of 10 minutes

The modifications to the Orchid source code are released under the same license as the parent application.
--
dr|z3d - z3d@mail.i2p

Thanks to George Hodan for the orchid image
http://www.publicdomainpictures.net/view-image.php?image=35307
See LICENSE-FatCowIcons.txt & LICENSE-Fugue-Icons.txt in i2p/licenses/ for icon licences

Conversion from new MaxMind format to v.1 GeoIP.dat format courtesy of:
https://github.com/sherpya/geolite2legacy
Usage:
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
./geolite2legacy.py -i GeoLite2-Country-CSV.zip -f geoname2fips.csv -o GeoIP.dat
For GeoIP licensing information, See the MaxMind license in the I2P application directory:
i2p/licenses/LICENSE-GeoIP.txt
2019-08-17 14:35:17 +00:00
zzz
d0ef965cd9 set BSD license for i2p code 2019-08-16 18:38:56 +00:00
zzz
861f7e94ee 1.2.2-0.5
Reduce max descriptor age
Enforce max certificates file age
2019-06-24 15:38:10 +00:00
zzz
b701694222 Remove unused XMLRPC Transport and related libs
Shrinks su3 by 50KB
Remove unused things from build classpath
2019-01-17 16:39:09 +00:00
zzz
d8fee276f0 Remove restrictive SSL cipher list which caused nothing to work (ticket #2079)
Version 1.2.2-0.4
Use the I2P utility to blacklist weak ciphers, rather than specifying
a whitelist of 4 ciphers, for the V3 handshake.
2019-01-16 15:33:51 +00:00
zzz
9c88c24c84 1.2.2-0.3 2018-04-15 15:54:02 +00:00
zzz
6d3523ba32 Parse new cert types (see tor-spec 4.2) (ticket #2079)
from:
d216480ae0
also at:
56f602da50
2018-04-15 14:26:28 +00:00
zzz
78234aeba7 Cherry-pick fix for certificate types from github (ticket #2079) 2018-04-15 11:09:50 +00:00
zzz
25a1f4e710 Don't load a cached-microdesc from disk that is too old (ticket #1937) 2018-04-15 11:08:26 +00:00
zzz
9a3b7c12ca Update dirauths (ticket #2079) 2018-04-15 11:07:48 +00:00
zzz
1821a76194 Change exception to reduce spurious logs at shutdown (ticket #2079) 2017-11-24 15:54:55 +00:00
zzz
71b7b4b2a5 update script 2017-11-24 15:49:20 +00:00
zzz
da1198a45c fix build 2017-11-24 15:38:30 +00:00
zzz
e7befc9c45 Rename translation methods for Java 9
not used anyway
2017-11-24 15:34:20 +00:00
zzz
9419bfd337 reduce max consensus age 2017-04-17 12:39:30 +00:00
zzz
47a1372fbd add date 2017-02-21 14:54:36 +00:00
zzz
682a94fff6 Add Stream.isClosed()
Implement more methods in TorStreamSocket
2016-08-12 13:45:32 +00:00
zzz
78c674ea28 Escape HTML on status page
Fix HTML error on status page
2016-08-12 13:37:10 +00:00
zzz
d9b2aa77f0 add ticket numbers to changelog 2016-08-05 15:32:32 +00:00
zzz
f2ada83acb Remove duplicate registration with the ClientAppManager;
wait until initialization complete
Debug logging on connect fail
2016-08-05 14:16:12 +00:00
zzz
8741e066d7 Fix for unable to reopen circuit to HS after the circuit is closed.
The circuit is stored in the HiddenService object,
so reset it to null after it's closed so a new one can be created.
Not sure if this fix is correct, but
don't think a circuit can be reopened after it's destroyed?
2016-08-04 23:07:43 +00:00
zzz
5cdb55a6c1 Move two more thread pools to use Threading so that name is set
Set name of ReadCells threads - todo use thread pool for them too?
2016-08-04 14:54:04 +00:00
zzz
cae24e352a HTTPConnection.getHost() fix
Plucked from subgraph develop branch
305b713f3c
2016-08-03 17:49:43 +00:00
zzz
3a1e800a00 Don't load a consensus from disk that is too old
Bump version to 1.2.2-0.1
2016-08-03 17:31:46 +00:00
zzz
ac18316253 Remove guava dependencies, including deadlock detection, in Threading.java.
Bump version to 1.2.1-0.1, to roughly track the bitcoinj maven version
at https://github.com/bitcoinj/bitcoinj/commits/master/orchid,
their history is:
 1.1   2014-11-17 Dirauth changes, deadlock fixes, config redesign
 1.2   2015-07-03 Guava 18.0, don't throw on socket options
 1.2.1 2016-05-18 Because "incomplete version 1.2 uploaded to maven central last year"
Briefly tested
2016-08-03 16:48:07 +00:00
zzz
c0c41a90a3 From a patch by thebland dated 2016-04-11, based on the
subgraph 'develop' branch rev c76d492560795d1b0482c2dfd24978e7bb032cd4 2015-05-02

Upstream changes are almost all as merged by Bruce Leidl
from the bitcoinj fork, and then merged to our fork by thebland, including:
- Move all thread creation to the new Threading class,
  using a thread factory
- Move all lock acquisition to a new Threading class,
  and use Google Guava classes for deadlock detection
- Converts most synchronization to use ReentrantLocks
  obtained from Threading
- Fix deadlocks in OrchidSocketImpl
- Socket improvements: Allow creation of unconnected sockets,
  avoid exceptions for unrecognized options
- Replace the configuration system, moving from TorConfigProxy
  (a reflection/proxy-based InvocationHandler with annotation dependencies)
  to TorConfigImpl which is more straightforward

Other changes by thebland:
- Update OrchidController config code to track changes above

This rev will not compile. Next checkin will remove guava dependencies,
including deadlock detection, in Threading.java.

Requires Java 7 for the switch on string in TorConfigParser
2016-08-03 16:09:19 +00:00
zzz
8ec0c3e6d2 Bump min java to 1.7 as required by next checkin
Pull compiler args (bootclasspath) from override.properties
Fix project name in src/build.xml
2016-08-03 16:04:48 +00:00
zzz
76b79e6952 su3 build fix 2016-07-30 13:17:59 +00:00
zzz
e0d70176a1 Update makeplugin.sh script, add su3 plugin support (patch from thebland)
Update hardcoded dirauths (patch from thebland, and remove urras)
Add changelog
Bump to 0.5
2016-07-30 13:15:09 +00:00
98 changed files with 2952 additions and 1040 deletions

66
.github/workflows/sync.yaml vendored Normal file
View File

@@ -0,0 +1,66 @@
# GitHub Actions workflow file to sync an external repository to this GitHub mirror.
# This file was automatically generated by go-github-sync.
#
# The workflow does the following:
# - Runs on a scheduled basis (and can also be triggered manually)
# - Clones the GitHub mirror repository
# - Fetches changes from the primary external repository
# - Applies those changes to the mirror repository
# - Pushes the updated content back to the GitHub mirror
#
# Authentication is handled by the GITHUB_TOKEN secret provided by GitHub Actions.
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Validate Github Actions Environment
run: if [ "$GITHUB_ACTIONS" != "true" ]; then echo 'This script must be run in a GitHub Actions environment.'; exit 1; fi
- name: Checkout GitHub Mirror
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |-
git config user.name 'GitHub Actions'
git config user.email 'actions@github.com'
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Sync Primary Repository
run: |-
# Add the primary repository as a remote
git remote add primary https://i2pgit.org/I2P_Developers/i2p.plugins.orchid.git
# Fetch the latest changes from the primary repository
git fetch primary
# Check if the primary branch exists in the primary repository
if git ls-remote --heads primary master | grep -q master; then
echo "Primary branch master found in primary repository"
else
echo "Error: Primary branch master not found in primary repository"
exit 1
fi
# Check if we're already on the mirror branch
if git rev-parse --verify --quiet master; then
git checkout master
else
# Create the mirror branch if it doesn't exist
git checkout -b master
fi
# Force-apply all changes from primary, overriding any conflicts
echo "Performing force sync from primary/master to master"
git reset --hard primary/master
# Push changes back to the mirror repository
git push origin master
name: Sync Primary Repository to GitHub Mirror
"on":
push: {}
schedule:
- cron: 0 * * * *
workflow_dispatch: {}

69
CHANGES.txt Normal file
View File

@@ -0,0 +1,69 @@
* 2019-08-21 1.2.2-0.6
- Overhaul front-end to use full html output and a default light theme
- Enable GeoIP lookups for nodes to permit display of flags
- Add country code lookups for nodes and display on tooltip
- Mitigate logging output issues caused by html output
- Set useMicroDescriptors to FALSE so additional node information can
be presented in the UI, if enough memory available
- Allow 2nd parameter in extra-info-digest when pulling full descriptors
- Display platform, observed bandwidth and uptime on circuit node tooltips
- Add hints and notes to config section, and include missing options
- Change maxCircuitDirtiness to Tor default of 10 minutes
- Update dizum dirauth IP
* 2019-06-24 1.2.2-0.5
- Remove unused XMLRPC Transport and related libs
- Reduce max descriptor age
- Enforce max certificates file age
* 2019-01-16 1.2.2-0.4
- Remove restrictive SSL cipher list which caused nothing to work (ticket #2079)
* 2018-04-15 1.2.2-0.3
- Reduce max consensus age again (ticket #1220)
- Don't load a cached-microdesc from disk that is too old (ticket #1937)
- Update dirauths (ticket #2079)
- Fix spurious errors at shutdown (ticket #2079)
- Fix for new certificate types in tor-spec 4.2 (ticket #2079)
* 2016-08-14 1.2.2-0.2
- Add Stream.isClosed()
- Implement more methods in TorStreamSocket
- Escape HTML on status page
- Fix HTML error on status page
* 2016-08-05 1.2.2-0.1
- Don't load a consensus from disk that is too old
(ticket #1220, orchid issue #24, bitcoinj issue #1064)
- Fix reopening HS circuit after close (ticket #1251)
- Delay registration until initialization complete
- HTTPConnection.getHost() fix
* 2016-08-03 1.2.1-0.1 (unreleased)
- Merge of bitcoinj changes, including deadlock fixes (ticket #1207),
NPE fix (ticket #1221), and configuration rewrite
- ConfigNodeFilter.createIdentityFilter() fix
* 2016-07-30 1.0.0-0.5
- Update hardcoded dirauths
- Add su3 plugin build support
* 2014-03-01 1.0.0-0.4
- Catch policy exception to correctly set failed state (ticket #1201)
- Hopefully fix deadlock (ticket #1207)
* 2014-01-10 1.0.0-0.3
- Better logging of startup errors
- Add config file support
- Add circuit status to servlet
* 2014-01-08 1.0.0-0.2
- Change from jsp to java servlet
- Start controller from servlet, not clients.config, to avoid class loader issues
- Fix webapps.config location
- Fix console link
- Fix classpath issues
- Add status info using TorConfig
* 2014-01-04 1.0.0-0.1
- Initial checkin

View File

@@ -1,3 +1,3 @@
I2P license: TBD
I2P license: 3-clause BSD
Bundled software: see plugins/licenses

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project basedir="." default="all" name="orchid">
<property file="override.properties"/>
<target name="all" depends="clean,plugin" />
<property name="xmlrpc.version" value="3.1.3" />
<target name="war" >
<ant dir="src" target="build" />
</target>
@@ -12,11 +11,19 @@
<target name="plugin" depends="war">
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="1.0.0-0.4" />
<property name="release.number" value="1.2.2-0.6" />
<!-- add the GeoIP.dat file & README.txt -->
<copy file="geoip/GeoIP.dat" todir="plugin/geoip/" overwrite="true" />
<copy file="geoip/README.txt" todir="plugin/geoip/" overwrite="true" />
<!-- add info about maxmind license -->
<copy file="geoip/license.txt" tofile="plugin/licenses/LICENSE-GeoIP.txt" overwrite="true" />
<!-- make the update xpi2p -->
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
<copy file="README.txt" todir="plugin/" overwrite="true" />
<copy file="CHANGES.txt" todir="plugin/" overwrite="true" />
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="update-only=true" />
@@ -34,10 +41,18 @@
<arg value="plugin/console/webapps/orchid.war.pack" />
<arg value="src/build/orchid.war.jar" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<input message="Enter su3 signing key password:" addproperty="release.password.su3" />
<fail message="You must enter a password." >
<condition>
<equals arg1="${release.password.su3}" arg2=""/>
</condition>
</fail>
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
<move file="orchid.xpi2p" tofile="orchid-update.xpi2p" overwrite="true" />
<move file="orchid.su3" tofile="orchid-update.su3" overwrite="true" />
<!-- make the install xpi2p -->
<copy file="scripts/orchid.config" todir="plugin/" overwrite="true" />
@@ -45,17 +60,7 @@
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />
</exec>
<exec executable="pack200" failonerror="true">
<arg value="-g" />
<arg value="plugin/lib/xmlrpc-client.jar.pack" />
<arg value="lib/xmlrpc-client-${xmlrpc.version}.jar" />
</exec>
<exec executable="pack200" failonerror="true">
<arg value="-g" />
<arg value="plugin/lib/xmlrpc-common.jar.pack" />
<arg value="lib/xmlrpc-common-${xmlrpc.version}.jar" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
</target>
@@ -64,17 +69,26 @@
<target name="clean" >
<ant dir="src" target="clean" />
<defaultexcludes remove="**/*~"/>
<delete>
<fileset dir="." includes="*/*.~ **/*.*~ *.*~" />
</delete>
<delete file="plugin/i2ptunnel.config" />
<delete file="plugin/orchid.config" />
<delete file="plugin/plugin.config" />
<delete file="plugin/lib/orchid.jar.pack" />
<!-- following two removed in 0.5 -->
<delete file="plugin/lib/xmlrpc-common.jar.pack" />
<delete file="plugin/lib/xmlrpc-client.jar.pack" />
<delete file="plugin/console/webapps/orchid.war.pack" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/README.txt" />
<delete file="plugin/CHANGES.txt" />
<delete file="orchid.xpi2p" />
<delete file="orchid-update.xpi2p" />
<delete file="orchid.su3" />
<delete file="orchid-update.su3" />
</target>
</project>

BIN
geoip/GeoIP.dat Normal file

Binary file not shown.

8
geoip/README.txt Normal file
View File

@@ -0,0 +1,8 @@
Conversion from new MaxMind format to v.1 GeoIP.dat format courtesy of:
https://github.com/sherpya/geolite2legacy
Usage:
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
./geolite2legacy.py -i GeoLite2-Country-CSV.zip -f geoname2fips.csv -o GeoIP.dat

2
geoip/license.txt Normal file
View File

@@ -0,0 +1,2 @@
For licensing information, See the MaxMind license in the I2P application directory:
i2p/licenses/LICENSE-GeoIP.txt

Binary file not shown.

Binary file not shown.

11
resources/ajaxRefresh.js Normal file
View File

@@ -0,0 +1,11 @@
setInterval(function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/orchid/?" + new Date().getTime(), true);
xhr.responseType = "text";
xhr.onreadystatechange = function () {
if (xhr.readyState==4 && xhr.status==200) {
document.getElementById("orchid").innerHTML = xhr.responseText;
}
}
xhr.send();
}, 15000);

13
resources/collapse.css Normal file
View File

@@ -0,0 +1,13 @@
#configuration {
display: none !important;
}
#expand {
display: inline-block !important;
z-index: 100 !important;
}
#collapse {
display: none !important;
z-index: -1 !important;
}

13
resources/expand.css Normal file
View File

@@ -0,0 +1,13 @@
#configuration {
display: table !important;
}
#expand {
display: none !important;
z-index: -1 !important;
}
#collapse {
display: inline-block !important;
z-index: 100 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
resources/images/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

BIN
resources/images/exit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
resources/images/expand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/images/globe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/images/node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
resources/images/orchid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
resources/images/rx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/images/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

BIN
resources/images/target.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

BIN
resources/images/tick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

BIN
resources/images/tile2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/images/tx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

966
resources/orchid.css Normal file
View File

@@ -0,0 +1,966 @@
/* I2P Orchid Plugin Theme by dr|z3d 2019 */
/* Thanks to George Hodan for the orchid image */
/* http://www.publicdomainpictures.net/view-image.php?image=35307 */
/* See LICENSE-FatCowIcons.txt & LICENSE-Fugue-Icons.txt in i2p/licenses/ for icon licences */
body {
margin: 10px;
padding: 0;
min-width: 860px;
color: #41465f;
background: url(images/tile2.png) fixed #a4a4cb;
}
body, table {
font-family: "Droid Sans", "Noto Sans", Ubuntu, "Segoe UI", Verdana, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
html {
scrollbar-color: rgba(16,16,48,.3) rgba(0,0,0,0);
}
html:hover {
scrollbar-color: rgba(16,16,48,.4) rgba(0,0,0,0);
}
::selection {
background: #77f;
color: #fff;
text-shadow: none;
}
hr, .hidden {
display: none;
}
a:link, .node a:visited, .nickname {
color: #3b6bbf;
text-decoration: none;
outline: none;
}
a:visited {
color: #2c4e8f;
text-decoration: none;
}
a:hover, a:focus {
color: #f60;
}
a:active {
color: #f30;
}
#container {
margin: 15px 5px;
padding: 8px;
max-width: 1920px;
border: 1px solid #447;
box-shadow: inset 0 0 0 1px #bbf;
background: #fff;
background: linear-gradient(to right, #fff, #efefff, #fff);
/* background: repeating-linear-gradient(45deg, rgba(255,255,255,.5) 1px, rgba(221, 221, 255, .1) 2px, rgba(255,255,255,.1) 2px), repeating-linear-gradient(135deg, rgba(255,255,255,1), rgba(221, 221, 255, .3) 1px, #fff 2px) #fafaff !important;
background-blend-mode: multiply, normal !important;*/
filter: drop-shadow(0 1px 1px #97a2ce);
}
code {
padding: 0 2px;
font-family: "Droid Sans Mono", "Noto Mono", "DejaVu Sans Mono", "Lucida Console", monospace;
color: #373;
}
.notice code {
font-weight: bold;
color: #fff;
border-radius: 2px;
background: #99a;
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
}
#refresh {
margin: -1px -2px 0 0;
float: right;
}
#refresh a, #refresh a:visited {
margin: -2px 0 !important;
padding: 5px 5px 5px 20px;
display: inline-block;
line-height: 100%;
letter-spacing: normal;
text-decoration: none;
font-weight: normal;
text-transform: none;
font-size: 9.5pt;
color: #41465f !important;
border: 1px solid #97a2ce;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff;
background: #fff url(images/refresh.png) 4px center no-repeat;
background: url(images/refresh.png) 4px center no-repeat, linear-gradient(to bottom, #fff, #eef);
background-size: 14px 14px, 100% 100% !important;
}
#refresh a:hover, #refresh a:focus {
border: 1px solid #f60;
background: #eee url(images/refresh.png) 4px center no-repeat;
background: url(images/refresh.png) 4px center no-repeat, linear-gradient(to bottom, #ddd, #fff);
filter: drop-shadow(0 0 1px #89f);
}
#refresh a:active {
box-shadow: inset 3px 3px 3px #999;
box-shadow: inset 0 0 0 1px #fff, inset 3px 3px 3px #999;
}
table {
width: 100%;
border-collapse: collapse;
border: 1px solid #7778bf;
background: #f8f8ff;
}
tr {
border: 1px solid #7778bf;
}
th#circuitsatus, #configtitle {
font-size: 13pt;
}
tr.subtitle th {
padding: 6px 8px;
letter-spacing: normal;
text-transform: none;
font-size: 10.5pt;
background: #f6f6ff;
background: linear-gradient(to bottom, #fdfdff, #f0f0ff);
}
#status th, #status td {
text-align: center;
}
#status th, #status td {
width: 25% !important;
}
#status td {
padding: 9px 8px;
border: 1px solid #7778bf;
box-shadow: inset 0 0 0 1px #fff;
background: #f2f2ff;
}
#status td:first-child {
text-transform: lowercase;
}
#status td:first-child::first-letter {
text-transform: uppercase;
}
#status td::before {
display: none;
}
#starting, #running, #registered, #notregistered {
font-size: 0;
}
#starting::before, #running::before, #registered::before, #notregistered::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
background: url(images/starting.png) center center no-repeat;
background-size: 16px 16px;
animation: spin linear 3s forwards infinite;
}
#running::before, #registered::before {
background: url(images/tick.png) center center no-repeat;
background-size: 16px 16px;
animation: none;
}
#notregistered::before {
background: url(images/cross.png) center center no-repeat;
background-size: 16px 16px;
animation: none;
}
@keyframes spin {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
table table {
box-shadow: 0 0 1px 0 #bbc;
}
table table tr:not(.stream):nth-child(odd), #configuration tr:not(.stream):nth-child(odd) {
background: #f2f2ff;
background: repeating-linear-gradient(45deg, rgba(255,255,255,.5) 2px, rgba(220, 220, 255, .3) 3px, #fafaff 5px), #fafaff;
}
table table tr:not(.stream):nth-child(even), #configuration tr:not(.stream):nth-child(even) {
background: #ededff;
background: repeating-linear-gradient(135deg, rgba(252,252,255,.5) 2px, rgba(240, 240, 255, .3) 3px, #fafaff 5px) #f0f0ff;
}
table table:not(#status) tr:nth-child(n+3):hover, #configuration tr:nth-child(n+3):hover td, #status td:hover {
color: #292d3d;
background-color: #ffe;
}
td {
padding: 8px;
}
table table td::before {
content: "";
display: inline-block;
min-height: 20px;
vertical-align: middle;
}
#ports td::before {
min-height: 24px;
}
table table td, #configuration table td {
padding: 5px 8px;
}
td.notice, tr:hover td.notice {
padding: 15px 8px 15px 38px !important;
color: #222;
box-shadow: inset 0 0 0 1px #fff, inset 0 0 1px 1px #bbf;
background: #f8f8ff url(images/infohelp.png) 8px center no-repeat !important;
background-size: 24px 24px !important;
}
th, #configtitle {
padding: 6px 8px;
letter-spacing: 0.08em;
text-transform: uppercase;
text-align: left;
font-size: 11pt;
background: #fff;
background: linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
}
th[colspan="2"] {
width: 50%;
}
#circuitstatus, #configtitle {
font-size: 12.5pt;
}
th#title {
padding: 10px 8px;
font-size: 16pt;
letter-spacing: 0.08em !important;
line-height: 110%;
text-shadow: 0 1px 1px #fff,0 -1px 1px #e2e2ff, 0 2px 1px #ddf;
background: url(images/orchid.png) right top no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
background-size: auto 120%, 100% 100%;
}
#circuitstatus {
background: url(images/circuits.png) 8px center no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
padding: 8px 8px 8px 37px;
background-size: 24px 24px, 100% 100%;
}
#circuitstatus, #configtitle, #circuitmonitor tr:first-child, #circuitstatus tr:first-child, #conncache tr:first-child, #ports tr:first-child {
text-shadow: 0 1px 2px #fff, 0 1px 1px #ccf;
}
#circuitmon tr:nth-child(2) th:nth-child(n+3), #circuitmon td:nth-child(n+3),
#conncache th:nth-child(n+2), #conncache td:nth-child(n+2) {
text-align: center;
}
#circuitmon td:nth-child(2), #circuitmon td:nth-child(3) {
padding-left: 10px;
}
#circuitmon td:nth-child(3) .nowrap {
margin-left: 28px;
min-width: 80px;
display: inline-block;
text-align: left;
}
#circuitmon .circuit td:nth-child(3) i {
padding: 1px 8px;
display: inline-block;
min-width: 120px;
text-align: center;
border: 1px solid #ddf;
box-shadow: inset 0 0 0 1px #fff;
background: repeating-linear-gradient(135deg, rgba(238, 238, 255,.7) 1px, rgba(238, 238, 255, .7) 5px, rgba(221, 221, 255, .7) 6px, rgba(221, 221, 255, .7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
.stream .target {
margin: 0 1px;
}
#circuitmon td:last-child, .stream td:nth-child(2) {
white-space: nowrap;
}
#conncache tr:first-child th, #circuitstatus tr:first-child th, #ports tr:first-child th, #circuitmon tr:first-child th {
font-size: 11.5pt;
}
#conncache .nowrap, #ports .nowrap {
text-align: right;
}
#conncache .nowrap {
min-width: 30px;
}
#conncache td:first-child {
white-space: nowrap;
}
#conncache td:nth-child(2) .nowrap {
min-width: 20px;
}
#ports .nowrap, #conncache td:nth-child(n+4) .nowrap {
min-width: 60px;
}
#ports .subtitle th:last-child, #ports td:last-child {
text-align: center;
}
#ports td:last-child .nowrap {
min-width: 120px;
text-align: right;
}
.circuitcount {
margin-left: 6px;
padding: 3px 4px 2px;
min-width: 24px;
display: inline-block;
vertical-align: middle;
font-weight: bold;
text-align: center;
text-shadow: 0 1px 1px #fff;
border-radius: 2px;
background: #dfdfff;
}
.circuitcontainer {
will-change: transform;
}
.nodecontainer {
position: relative;
vertical-align: middle;
}
.node {
margin: 1px 0;
padding: 2px 8px 2px 0;
position: relative;
display: inline-block;
vertical-align: middle;
border: 1px solid #bbf;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff, 0 0 0 1px #eef;
background: linear-gradient(to bottom, #f8f8ff, #e2e2ff);
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
cursor: text;
}
.node .hidden {
font-size: 0;
display: inline;
}
.node:hover {
border-right: 10px solid #bbf;
}
.node:active {
border-color: #f30;
box-shadow: none;
cursor: copy;
}
.nickname a::selection {
background: rgba(0,0,0,0) !important;
color: #3b6bbf !important;
}
.node:active .nickname a::selection {
color: #fff !important;
}
.node:active .nickname {
background-color: #f30;
box-shadow: none;
}
.node:active .flag {
border-color: #f30;
box-shadow: none;
}
a:hover .nickname::after, .flag::after {
color: #41465f;
}
a:visited:hover .nickname, a:visited:focus .nickname {
color: #f60 !important;
}
a:visited:active .nickname {
color: #f30 !important;
}
.node img {
vertical-align: middle;
margin: -1px 4px 0 0;
}
.node b {
font-size: 0;
}
.flag {
margin-right: 4px;
margin: -3px 4px -3px 0;
padding: 2px 1px 3px 5px;
display: inline-block;
vertical-align: middle;
border-right: 1px solid #bbf;
box-shadow: inset 0 0 0 1px #fff;
cursor: help;
-mozilla-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.flag img {
padding-bottom: 1px
}
.nickname::after, .flag::after {
content: attr(data-ipv4);
padding: 2px 3px 2px 15px;
display: none;
position: absolute;
z-index: 100;
top: -20px;
left: -10px;
white-space: nowrap;
text-align: center;
text-shadow: 0 1px 1px #fff;
border: 1px solid #373;
border-radius: 2px;
box-shadow: inset 0 0 0 1px #fff, 0 0 1px 1px rgba(192,192,192,.3);
background: url(images/node.png) left -1px center no-repeat, #ada;
background: url(images/node.png) left center no-repeat, linear-gradient(to bottom, #efe, #ded);
background-size: 16px 16px, 100% 100%;
opacity: 0;
}
.nickname::after {
padding-left: 17px;
color: #41465f;
}
.nickname.unknown::after {
display: none !important;
}
#circuitmon .nickname::after {
left: -70px;
white-space: nowrap;
text-align: left;
}
.flag::after {
content: attr(data-country);
padding-left: 18px;
z-index: 101;
background: url(images/globe.png) left 4px center no-repeat, #ada;
background: url(images/globe.png) left 4px center no-repeat, linear-gradient(to bottom, #efe, #ded);
background-size: 12px 12px, 100% 100% !important;
}
.nickname {
margin: -2px -8px -2px -4px;
padding: 2px 6px 3px 5px;
display: inline-block;
box-shadow: inset 0 0 0 1px #fff;
width: 100px;
overflow-x: hidden;
text-overflow: ellipsis;
text-align: center;
vertical-align: middle;
}
.node:hover .nickname {
width: auto;
min-width: 100px;
}
#conncache .nickname {
margin-right: -8px;
}
.nickname:hover::after, .flag:hover::after {
display: inline-block;
width: auto;
opacity: 1;
}
#circuitmon .nodecontainer::after {
content: "";
display: inline-block;
position: absolute;
top: calc(50%);
right: -4px !important;
height: 1px;
width: 4px;
vertical-align: middle;
background: #bbf;
z-index: 10;
}
#circuitmon .nodecontainer:last-of-type::after {
display: none !important;
}
.stream {
background-color: #f8fff8;
}
.stream td:first-child, .stream:hover td:first-child, .circuit td:first-child, .circuit:hover td:first-child {
padding-left: 26px !important;
background-image: url(images/tag_stream.png) !important;
background-size: 14px 14px !important;
background-position: 8px center !important;
background-repeat: no-repeat !important;
}
.circuit td:first-child, .circuit:hover td:first-child {
background-image: url(images/tag.png) !important;
}
.stream td:last-child {
text-align: center;
}
.stream td:last-child i {
margin-left: 1px;
padding: 1px 16px;
min-width: 106px;
display: inline-block;
text-align: center;
color: #4a4;
text-shadow: 0 1px 1px #fff;
border: 1px solid #aca;
box-shadow: inset 0 0 0 1px #fff;
background: repeating-linear-gradient(135deg, rgba(240, 255, 240, .7) 1px, rgba(240, 255, 240, .7) 6px, rgba(210, 240, 210,.7) 7px, rgba(210, 240, 210,.7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
.stream:hover td:last-child i {
color: #bb6;
border: 1px solid #cca;
background: repeating-linear-gradient(135deg, rgba(255, 255, 240, .7) 1px, rgba(255, 255, 240, .7) 6px, rgba(240, 240, 220,.7) 7px, rgba(240, 240, 220,.7) 11px);
background-size: 800% 800%;
animation: progress 120s linear infinite both;
}
@keyframes progress {
from {
background-position: 200%
}
to {
background-position: 0;
}
}
.streamID {
display: none;
}
.nowrap {
display: inline-block;
white-space: nowrap;
vertical-align: middle;
}
.stream .nowrap {
min-width: 60px;
text-align: center;
}
.stream .nowrap:first-of-type {
padding-right: 20px;
text-align: left;
}
.stream .nowrap:last-of-type {
padding-left: 20px;
text-align: left;
}
.internal, .exit, .directory, .hiddenservice {
padding-left: 20px;
display: inline-block;
min-width: 120px;
vertical-align: middle;
text-align: left;
background: url(images/internal.png) left center no-repeat;
background-size: 16px 16px !important;
}
.exit {
background: url(images/exit.png) left center no-repeat;
}
.directory {
background: url(images/directory.png) left center no-repeat;
}
.hiddenservice {
background: url(images/hiddenservice.png) left center no-repeat;
}
.rx::before, .tx::before, .target::before {
content: "";
display: inline-block;
width: 14px;
height: 16px;
vertical-align: top;
}
.tx::before {
background: url(images/tx.png) left center no-repeat;
background-size: 14px 14px;
}
.rx::before {
background: url(images/rx.png) left center no-repeat;
background-size: 14px 14px;
}
.target::before {
margin-top: -6px;
width: 15px;
height: 16px;
vertical-align: middle;
background: url(images/target.png) left top no-repeat;
background-size: 14px 14px;
}
.rx, .tx, .target {
font-size: 0;
}
#configsection, #configsection td {
border: none;
}
#configsection td {
padding: 0;
}
#configsection table td {
padding: 5px 8px;
}
#configuration {
position: relative;
border: none;
}
#configuration tr {
border-left: none !important;
border-right: none !important;
}
#configuration td:first-child {
border-left: none !important;
}
#configuration td:last-child {
border-right: none !important;
}
#configuration tr:last-child, #configuration tr:last-child td {
border-bottom: none !important;
}
#configuration th {
width: 25%;
text-transform: none;
letter-spacing: normal;
}
#configuration tr:nth-child(n+3) td:first-child {
font-weight: bold;
}
#configuration tr:hover td {
box-shadow: none;
}
#configuration td:first-child {
border-left: none !important;
}
#configuration td:last-child {
border-right: none !important;
line-height: 1.1;
}
#configtitle {
margin: -1px 0;
padding: 8px;
padding-left: 37px;
width: calc(100% - 47px);
display: inline-block;
position: relative;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 12.5pt;
font-weight: bold;
border: 1px solid #7778bf;
border-left: none;
border-right: none;
background: url(images/configure.png) 8px center no-repeat, linear-gradient(to bottom, #fcfcff 50%, #f2f2ff 50%, #efefff);
background-size: 24px 24px, 100% 100% !important;
}
#expand, #collapse {
display: inline-block !important;
position: absolute;
top: calc(50% - 18px);
right: -1px;
font-size: 0;
border-left: 1px solid #7778bf;
}
#collapse {
display: none !important;
}
#expand img, #collapse img {
padding: 13px;
width: 10px;
height: 10px;
display: inline-block;
vertical-align: middle;
}
#expand:hover img, #collapse:hover img {
filter: drop-shadow(0 0 1px #f60);
}
#configuration td:nth-child(2) code {
color: #050;
line-height: 115%;
}
#configuration td:nth-child(2) code, #configuration td:nth-child(2) .nowrap {
display: inline-block;
vertical-align: middle;
}
#configuration tr:hover td:nth-child(2) code {
color: #070;
}
#configuration tr:hover td:nth-child(2) code::selection {
color: #fff;
background: #070;
}
#configuration .nowrap {
font-style: italic;
color: #41465f;
}
#configuration i {
margin-right: 1px;
}
.stream, .circuit {
border-top: 1px solid #ddf;
border-bottom: 1px solid #ddf;
border-left: 1px solid #7778bf;
border-right: 1px solid #7778bf;
}
.circuit, .stream, #conncache tr, #ports tr, #configuration tr {
border-top: 1px solid #ddf;
border-bottom: 1px solid #ddf;
}
tr:first-child td, tr:last-child, #configuration tr:last-child, #conncache tr:last-child, #ports tr:last-child {
border-bottom: 1px solid #7778bf;
}
#configuration tr:first-child, #configuration tr:nth-child(2),
#conncache tr:first-child, #conncache tr:nth-child(2),
#ports tr:first-child, #ports tr:nth-child(2) {
border-top: 1px solid #7778bf;
border-bottom: 1px solid #7778bf;
}
#conncache tr:nth-child(2), #conncache tr:nth-child(2) th {
border-bottom: 1px solid #7778bf !important;
}
#conncache tr:nth-child(3), #conncache tr:nth-child(3) td {
border-top: 1px solid #7778bf !important;
}
td, #configuration td {
border: 1px inset #dde;
border-left-style: solid;
border-right-style: solid;
box-shadow: inset 0 0 0 1px #fff !important;
}
.stream td, .stream:hover td, .circuit:hover td, #conncache tr:nth-child(n+3):hover td, #ports tr:nth-child(n+3):hover td, #configuration tr:nth-child(n+3):hover td {
border: none;
box-shadow: none !important;
}
td:first-child {
border-left: 1px solid #7778bf !important;
}
td:last-child {
border-right: 1px solid #7778bf !important;
}
.stream td:first-child {
border-right: 1px solid #f8fff8 !important;
}
.stream td:last-child {
border-left: 1px solid #f8fff8 !important;
}
.stream:hover td:first-child {
border-right: 1px solid #ffe !important;
}
.stream:hover td:last-child {
border-left: 1px solid #ffe !important;
}
table table tr:hover td {
background-color: #ffe;
}
@media screen and (max-width: 1480px) {
#circuitmon th, #circuitmon td {
width: auto !important;
}
#circuitmon tr:nth-child(2) th:last-child {
width: 60% !important;
}
#circuitmon th, #conncache th {
white-space: nowrap;
}
}
@media screen and (max-width: 1000px) {
body {
margin: 3px;
}
#container {
margin: 0;
}
body, table {
font-size: 9.5pt !important;
}
#title {
font-size: 14pt !important;
}
th#circuitstatus, #configtitle {
font-size: 12pt !important;
}
#conncache tr:first-child th, #circuitstatus tr:first-child th, #ports tr:first-child th, #circuitmon tr:first-child th {
font-size: 10.5pt;
}
tr.subtitle th {
font-size: 10pt;
}
.flag, .nickname, .circuitcount {
height: 16px;
}
.nickname {
width: 90px;
}
.flag img {
margin-top: 1px;
}
.circuitcount {
margin-top: 2px;
}
#refresh {
margin-top: 0;
}
.stream td:last-child i {
min-width: 96px;
}
#circuitmon .circuit td:nth-child(3) i {
min-width: 86px;
}
}
@media screen and (min-width: 1480px) {
#circuitmon th:first-child, #circuitmon th:nth-child(3) {
width: 12.5%;
}
}

48
resources/toggleConfig.js Normal file
View File

@@ -0,0 +1,48 @@
var expandConfig = document.getElementById("expandConfig");
var collapseConfig = document.getElementById("collapseConfig");
var config = document.getElementById("configuration");
function hideConfig() {
if (!collapseConfig)
config.style.display = "none";
}
function showConfig() {
if (collapseConfig)
config.style.display = "block";
}
function clean() {
if (expandConfig) {
expandConfig.remove();
}
if (collapseConfig) {
collapseConfig.remove();
}
}
function expand() {
clean();
var x = document.createElement("link");
x.type="text/css";
x.rel="stylesheet";
x.href="/orchid/resources/expand.css";
x.setAttribute("id", "expandConfig");
document.head.appendChild(x);
showConfig();
}
function collapse() {
clean();
var c = document.createElement("link");
c.type="text/css";
c.rel="stylesheet";
c.href="/orchid/resources/collapse.css";
c.setAttribute("id", "collapseConfig");
document.head.appendChild(c);
hideConfig();
}
function copyText() {
document.execCommand("copy");
}

View File

@@ -5,58 +5,95 @@
# usage: makeplugin.sh plugindir
#
# zzz 2010-02
# zzz 2014-08 added support for su3 files
#
if [ -z "$I2P" -a -d "$PWD/../i2p/pkg-temp" ]; then
export I2P=$PWD/../i2p/pkg-temp
fi
if [ ! -d "$I2P" ]; then
echo "Can't locate your I2P installation. Please add a environment variable named I2P with the path to the folder as value"
echo "On OSX this solved with running: export I2P=/Applications/i2p if default install directory is used."
exit 1
fi
CPATH=$I2P/lib/i2p.jar:/usr/share/java/gnu-getopt.jar
PUBKEYDIR=$HOME/.i2p-plugin-keys
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
B64KEYFILE=$PUBKEYDIR/plugin-public-signing.txt
export I2P=../i2p/pkg-temp
PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
KEYTYPE=RSA_SHA512_4096
# put your files in here
PLUGINDIR=${1:-plugin}
PC=plugin.config
PCT=${PC}.tmp
if [ ! -f $PRIVKEYFILE ]
then
mkdir -p $PUBKEYDIR
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
rm -rf logs/
chmod 444 $PUBKEYFILE $B64KEYFILE
chmod 400 $PRIVKEYFILE
echo "Created new keys: $PUBKEYFILE $PRIVKEYFILE"
fi
rm -f plugin.zip
if [ ! -d $PLUGINDIR ]
if [ ! -d "$PLUGINDIR" ]
then
echo "You must have a $PLUGINDIR directory"
exit 1
fi
OPWD=$PWD
cd $PLUGINDIR
if [ ! -f $PC ]
if [ ! -f "$PLUGINDIR/$PC" ]
then
echo "You must have a $PC file"
echo "You must have a $PLUGINDIR/$PC file"
exit 1
fi
grep -q '^signer=' $PC
SIGNER=`grep '^signer=' "$PLUGINDIR/$PC"`
if [ "$?" -ne "0" ]
then
echo "You must have a signer in $PC"
echo 'For example signer=joe@mail.i2p'
echo "You must have a signer name in $PC"
echo 'For example name=foo'
exit 1
fi
SIGNER=`echo $SIGNER | cut -f 2 -d '='`
if [ ! -f "$PRIVKEYFILE" ]
then
echo "Creating new XPI2P DSA keys"
mkdir -p "$PUBKEYDIR" || exit 1
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate keygen "$PUBKEYFILE" "$PRIVKEYFILE" || exit 1
java -cp "$CPATH" net.i2p.data.Base64 encode "$PUBKEYFILE" "$B64KEYFILE" || exit 1
rm -rf logs/
chmod 444 "$PUBKEYFILE" "$B64KEYFILE"
chmod 400 "$PRIVKEYFILE"
echo "Created new XPI2P keys: $PUBKEYFILE $PRIVKEYFILE"
fi
if [ ! -f "$PRIVKEYSTORE" ]
then
echo "Creating new SU3 $KEYTYPE keys for $SIGNER"
java -cp "$CPATH" net.i2p.crypto.SU3File keygen -t $KEYTYPE "$PUBKEYSTORE" "$PRIVKEYSTORE" $SIGNER || exit 1
echo '*** Save your password in a safe place!!! ***'
rm -rf logs/
# copy to the router dir so verify will work
CDIR=$I2P/certificates/plugin
mkdir -p "$CDIR" || exit 1
CFILE=$CDIR/`echo $SIGNER | sed s/@/_at_/`.crt
cp "$PUBKEYSTORE" "$CFILE"
chmod 444 "$PUBKEYSTORE"
chmod 400 "$PRIVKEYSTORE"
chmod 644 "$CFILE"
echo "Created new SU3 keys: $PUBKEYSTORE $PRIVKEYSTORE"
echo "Copied public key to $CFILE for testing"
fi
rm -f plugin.zip
OPWD=$PWD
cd "$PLUGINDIR"
grep -q '^name=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
echo 'For example name=foo'
exit 1
fi
@@ -64,41 +101,49 @@ grep -q '^version=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a version in $PC"
echo 'For example version=0.1.2'
echo 'For example version=0.1.2'
exit 1
fi
# update the date
grep -v '^date=' $PC > $PCT
DATE=`date '+%s000'`
echo "date=$DATE" >> $PCT
mv $PCT $PC
echo "date=$DATE" >> $PCT || exit 1
mv $PCT $PC || exit 1
# add our Base64 key
grep -v '^key=' $PC > $PCT
B64KEY=`cat $B64KEYFILE`
B64KEY=`cat "$B64KEYFILE"`
echo "key=$B64KEY" >> $PCT || exit 1
mv $PCT $PC
mv $PCT $PC || exit 1
# zip it
zip -r $OPWD/plugin.zip * -x \*.jar || exit 1
zip -r "$OPWD/plugin.zip" * || exit 1
# get the version and use it for the sud header
VERSION=`grep '^version=' $PC | cut -f 2 -d '='`
# get the name and use it for the file name
NAME=`grep '^name=' $PC | cut -f 2 -d '='`
XPI2P=${NAME}.xpi2p
cd $OPWD
SU3=${NAME}.su3
cd "$OPWD"
# sign it
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
echo 'Signing. ...'
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate sign plugin.zip "$XPI2P" "$PRIVKEYFILE" "$VERSION" || exit 1
java -cp "$CPATH" net.i2p.crypto.SU3File sign -c PLUGIN -t $KEYTYPE plugin.zip "$SU3" "$PRIVKEYSTORE" "$VERSION" "$SIGNER" || exit 1
rm -f plugin.zip
# verify
echo 'Verifying. ...'
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion $XPI2P || exit 1
java -cp $I2P/lib/i2p.jar -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig $XPI2P || exit 1
java -cp "$CPATH" net.i2p.crypto.TrustedUpdate showversion "$XPI2P" || exit 1
java -cp "$CPATH" -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig "$XPI2P" || exit 1
java -cp "$CPATH" net.i2p.crypto.SU3File showversion "$SU3" || exit 1
java -cp "$CPATH" net.i2p.crypto.SU3File verifysig -k "$PUBKEYSTORE" "$SU3" || exit 1
rm -rf logs/
echo -n 'Plugin created: '
wc -c $XPI2P
echo 'Plugin files created: '
wc -c "$XPI2P"
wc -c "$SU3"
exit 0

View File

@@ -5,7 +5,9 @@ consoleLinkURL=/orchid/
description=Tor Outproxy
author=bruce@subgraph.com (packaged by zzz)
updateURL=http://stats.i2p/i2p/plugins/orchid-update.xpi2p
updateURL.su3=http://stats.i2p/i2p/plugins/orchid-update.su3
websiteURL=http://zzz.i2p/forums/16
license=BSD
min-java-version=1.7
min-jetty-version=7
min-i2p-version=0.9.11

View File

@@ -1,18 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2psnark">
<project basedir="." default="all" name="orchid">
<property name="i2pbase" value="../../i2p.i2p"/>
<property name="i2plib" value="${i2pbase}/build"/>
<property name="jettylib" value="${i2pbase}/apps/jetty/jettylib"/>
<path id="cp">
<pathelement path="${java.class.path}" />
<pathelement location="${i2plib}/i2p.jar" />
<pathelement location="${ant.home}/lib/ant.jar"/>
<pathelement location="${jettylib}/jasper-runtime.jar" />
<pathelement location="${jettylib}/javax.servlet.jar" />
<pathelement location="${jettylib}/commons-logging.jar" />
<pathelement location="${jettylib}/commons-el.jar" />
<pathelement location="../lib/xmlrpc-client-${xmlrpc.version}.jar" />
<pathelement location="../lib/xmlrpc-common-${xmlrpc.version}.jar" />
<pathelement location="${jettylib}/jetty-util.jar" />
</path>
<target name="all" depends="clean, build" />
@@ -21,16 +17,17 @@
</target>
<property name="javac.compilerargs" value="" />
<property name="javac.version" value="1.7" />
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
srcdir="./java"
debug="true" deprecation="on" source="1.5" target="1.5"
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
destdir="./build/obj"
includeAntRuntime="false"
classpath="${i2plib}/i2p.jar:../lib/xmlrpc-client-${xmlrpc.version}.jar:../lib/xmlrpc-common-${xmlrpc.version}.jar:${jettylib}/javax.servlet.jar" >
classpath="${i2plib}/i2p.jar:${jettylib}/javax.servlet.jar" >
<compilerarg line="${javac.compilerargs}" />
</javac>
</target>
@@ -81,9 +78,13 @@
</target>
<target name="war" depends="precompilejsp">
<!-- add css and resources -->
<copy todir="build/war/resources" overwrite="true" >
<fileset dir="../resources" />
</copy>
<war destfile="build/orchid.war.jar" webxml="build/web.xml">
<classes dir="./build/obj" includes="**/web/*" />
<fileset dir="build/war" />
<fileset dir="./build/war" />
</war>
</target>

View File

@@ -23,6 +23,9 @@ public interface Circuit {
boolean isClean();
boolean isMarkedForClose();
/** @since 1.2.2 */
boolean isClosed();
int getSecondsDirty();

View File

@@ -18,4 +18,6 @@ public interface ConnectionCache {
Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException;
void close();
boolean isClosed();
}

View File

@@ -16,6 +16,10 @@ public interface DirectoryServer extends Router {
boolean isBridgeAuthority();
boolean isExtraInfoCache();
/**
* https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
*/
KeyCertificate getCertificateByAuthority(HexDigest fingerprint);
KeyCertificate getCertificateByFingerprint(HexDigest fingerprint);
List<KeyCertificate> getCertificates();
void addCertificate(KeyCertificate certificate);

View File

@@ -11,6 +11,9 @@ public interface Router {
String getNickname();
String getCountryCode();
IPv4Address getAddress();
String getPlatform();
String getCountryName();
int getUptime();
int getOnionPort();
int getDirectoryPort();
TorPublicKey getIdentityKey();

View File

@@ -26,6 +26,9 @@ public interface Stream {
*/
void close();
/** @since 1.2.2-0.2 */
boolean isClosed();
/**
* Returns an {@link InputStream} for sending data on this stream.
*

View File

@@ -0,0 +1,107 @@
package com.subgraph.orchid;
//import com.google.common.util.concurrent.CycleDetectingLockFactory;
//import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
public class Threading {
static {
// Default policy goes here. If you want to change this, use one of the static methods before
// instantiating any orchid objects. The policy change will take effect only on new objects
// from that point onwards.
throwOnLockCycles();
}
//private static CycleDetectingLockFactory.Policy policy;
//public static CycleDetectingLockFactory factory;
public static ReentrantLock lock(String name) {
//return factory.newReentrantLock(name);
return new ReentrantLock();
}
public static void warnOnLockCycles() {
//setPolicy(CycleDetectingLockFactory.Policies.WARN);
}
public static void throwOnLockCycles() {
//setPolicy(CycleDetectingLockFactory.Policies.THROW);
}
public static void ignoreLockCycles() {
//setPolicy(CycleDetectingLockFactory.Policies.DISABLED);
}
/****
public static void setPolicy(CycleDetectingLockFactory.Policy policy) {
Threading.policy = policy;
factory = CycleDetectingLockFactory.newInstance(policy);
}
public static CycleDetectingLockFactory.Policy getPolicy() {
return policy;
}
****/
public static ExecutorService newPool(final String name) {
//ThreadFactory factory = new ThreadFactoryBuilder()
// .setDaemon(true)
// .setNameFormat(name + "-%d").build();
ThreadFactory factory = new CustomThreadFactory(name);
return Executors.newCachedThreadPool(factory);
}
public static ScheduledExecutorService newSingleThreadScheduledPool(final String name) {
//ThreadFactory factory = new ThreadFactoryBuilder()
// .setDaemon(true)
// .setNameFormat(name + "-%d").build();
ThreadFactory factory = new CustomThreadFactory(name);
return Executors.newSingleThreadScheduledExecutor(factory);
}
public static ScheduledExecutorService newScheduledPool(final String name) {
//ThreadFactory factory = new ThreadFactoryBuilder()
// .setDaemon(true)
// .setNameFormat(name + "-%d").build();
ThreadFactory factory = new CustomThreadFactory(name);
return Executors.newScheduledThreadPool(1, factory);
}
/*
* Modified from Guava ThreadFactorBuilder
*
* Copyright (C) 2010 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
private static class CustomThreadFactory implements ThreadFactory {
private final String fmt;
private final AtomicLong count = new AtomicLong();
public CustomThreadFactory(String name) {
fmt = name + "-%d";
}
@Override
public Thread newThread(Runnable runnable) {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
thread.setName(String.format(fmt, count.getAndIncrement()));
thread.setDaemon(true);
return thread;
}
}
}

View File

@@ -6,7 +6,7 @@ import java.util.logging.Logger;
import com.subgraph.orchid.circuits.CircuitManagerImpl;
import com.subgraph.orchid.circuits.TorInitializationTracker;
import com.subgraph.orchid.config.TorConfigProxy;
import com.subgraph.orchid.config.TorConfigImpl;
import com.subgraph.orchid.connections.ConnectionCacheImpl;
import com.subgraph.orchid.directory.DirectoryImpl;
import com.subgraph.orchid.directory.downloader.DirectoryDownloaderImpl;
@@ -36,7 +36,7 @@ public class Tor {
private final static String implementation = "Orchid";
private final static String version = "1.0.0";
private final static String version = "1.2.2";
private final static Charset defaultCharset = createDefaultCharset();
@@ -93,7 +93,7 @@ public class Tor {
* @see TorConfig
*/
static public TorConfig createConfig() {
final TorConfig config = (TorConfig) Proxy.newProxyInstance(TorConfigProxy.class.getClassLoader(), new Class[] { TorConfig.class }, new TorConfigProxy());
final TorConfig config = new TorConfigImpl();
if(isAndroidRuntime()) {
logger.warning("Android Runtime detected, disabling V2 Link protocol");
config.setHandshakeV2Enabled(false);

View File

@@ -73,9 +73,9 @@ public class TorClient {
return;
}
if(isStopped) {
throw new IllegalStateException("Cannot restart a TorClient instance. Create a new instance instead.");
throw new IllegalStateException("Cannot restart Orchid instance. Create a new instance instead.");
}
logger.info("Starting Orchid (version: "+ Tor.getFullVersion() +")");
logger.info("Starting Orchid... [version: " + Tor.getFullVersion() + "]");
verifyUnlimitedStrengthPolicyInstalled();
directoryDownloader.start(directory);
circuitManager.startBuildingCircuits();
@@ -99,7 +99,7 @@ public class TorClient {
directory.close();
connectionCache.close();
} catch (Exception e) {
logger.log(Level.WARNING, "Unexpected exception while shutting down TorClient instance: "+ e, e);
logger.log(Level.WARNING, "Unexpected exception while shutting down Orchid instance: " + e, e);
} finally {
isStopped = true;
}
@@ -117,10 +117,6 @@ public class TorClient {
return circuitManager;
}
public DirectoryDownloader getDirectoryDownloader() {
return directoryDownloader;
}
public void waitUntilReady() throws InterruptedException {
readyLatch.await();
}
@@ -212,7 +208,7 @@ public class TorClient {
throw new TorException(message);
}
} catch (NoSuchAlgorithmException e) {
logger.log(Level.SEVERE, "No AES provider found");
logger.log(Level.SEVERE, "WARNING! No AES provider found");
throw new TorException(e);
} catch (NoSuchMethodError e) {
logger.info("Skipped check for Unlimited Strength Jurisdiction Policy Files");

View File

@@ -16,136 +16,97 @@ import com.subgraph.orchid.data.IPv4Address;
public interface TorConfig {
@ConfigVar(type=ConfigVarType.PATH, defaultValue="~/.orchid")
File getDataDirectory();
void setDataDirectory(File directory);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="60 seconds")
long getCircuitBuildTimeout();
void setCircuitBuildTimeout(long time, TimeUnit unit);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="0")
long getCircuitStreamTimeout();
void setCircuitStreamTimeout(long time, TimeUnit unit);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="1 hour")
long getCircuitIdleTimeout();
void setCircuitIdleTimeout(long time, TimeUnit unit);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="30 seconds")
long getNewCircuitPeriod();
void setNewCircuitPeriod(long time, TimeUnit unit);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="10 minutes")
long getMaxCircuitDirtiness();
void setMaxCircuitDirtiness(long time, TimeUnit unit);
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="32")
int getMaxClientCircuitsPending();
void setMaxClientCircuitsPending(int value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getEnforceDistinctSubnets();
void setEnforceDistinctSubnets(boolean value);
@ConfigVar(type=ConfigVarType.INTERVAL, defaultValue="2 minutes")
long getSocksTimeout();
void setSocksTimeout(long value);
@ConfigVar(type=ConfigVarType.INTEGER, defaultValue="3")
int getNumEntryGuards();
void setNumEntryGuards(int value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getUseEntryGuards();
void setUseEntryGuards(boolean value);
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300")
List<Integer> getLongLivedPorts();
void setLongLivedPorts(List<Integer> ports);
@ConfigVar(type=ConfigVarType.STRINGLIST)
List<String> getExcludeNodes();
void setExcludeNodes(List<String> nodes);
@ConfigVar(type=ConfigVarType.STRINGLIST)
List<String> getExcludeExitNodes();
void setExcludeExitNodes(List<String> nodes);
@ConfigVar(type=ConfigVarType.STRINGLIST)
List<String> getExitNodes();
void setExitNodes(List<String> nodes);
@ConfigVar(type=ConfigVarType.STRINGLIST)
List<String> getEntryNodes();
void setEntryNodes(List<String> nodes);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
boolean getStrictNodes();
void setStrictNodes(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
boolean getFascistFirewall();
void setFascistFirewall(boolean value);
@ConfigVar(type=ConfigVarType.PORTLIST, defaultValue="80,443")
List<Integer> getFirewallPorts();
void setFirewallPorts(List<Integer> ports);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
boolean getSafeSocks();
void setSafeSocks(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getSafeLogging();
void setSafeLogging(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getWarnUnsafeSocks();
void setWarnUnsafeSocks(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getClientRejectInternalAddress();
void setClientRejectInternalAddress(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getHandshakeV3Enabled();
void setHandshakeV3Enabled(boolean value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="true")
boolean getHandshakeV2Enabled();
void setHandshakeV2Enabled(boolean value);
@ConfigVar(type=ConfigVarType.HS_AUTH)
HSDescriptorCookie getHidServAuth(String key);
void addHidServAuth(String key, String value);
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
AutoBoolValue getUseNTorHandshake();
void setUseNTorHandshake(AutoBoolValue value);
@ConfigVar(type=ConfigVarType.AUTOBOOL, defaultValue="auto")
AutoBoolValue getUseMicrodescriptors();
void setUseMicrodescriptors(AutoBoolValue value);
@ConfigVar(type=ConfigVarType.BOOLEAN, defaultValue="false")
boolean getUseBridges();
void setUseBridges(boolean value);
@ConfigVar(type=ConfigVarType.BRIDGE_LINE)
List<TorConfigBridgeLine> getBridges();
void addBridge(IPv4Address address, int port);
void addBridge(IPv4Address address, int port, HexDigest fingerprint);
enum ConfigVarType { INTEGER, STRING, HS_AUTH, BOOLEAN, INTERVAL, PORTLIST, STRINGLIST, PATH, AUTOBOOL, BRIDGE_LINE };
enum AutoBoolValue { TRUE, FALSE, AUTO }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ConfigVar {
ConfigVarType type();
String defaultValue() default "";
}
}

View File

@@ -42,7 +42,7 @@ public class CircuitBuildTask implements Runnable {
circuit.notifyCircuitBuildStart();
creationRequest.choosePath();
if(logger.isLoggable(Level.FINE)) {
logger.fine("Opening a new circuit to "+ pathToString(creationRequest));
logger.fine("Selecting path for new circuit " + pathToString(creationRequest));
}
firstRouter = creationRequest.getPathElement(0);
openEntryNodeConnection(firstRouter);
@@ -72,7 +72,7 @@ public class CircuitBuildTask implements Runnable {
sb.append("[");
for(Router r: ccr.getPath()) {
if(sb.length() > 1)
sb.append(",");
sb.append(" -> ");
sb.append(r.getNickname());
}
sb.append("]");

View File

@@ -5,7 +5,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -19,6 +18,7 @@ import com.subgraph.orchid.Directory;
import com.subgraph.orchid.ExitCircuit;
import com.subgraph.orchid.InternalCircuit;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.circuits.CircuitManagerImpl.CircuitFilter;
import com.subgraph.orchid.circuits.path.CircuitPathChooser;
@@ -26,8 +26,10 @@ import com.subgraph.orchid.data.exitpolicy.ExitTarget;
public class CircuitCreationTask implements Runnable {
private final static Logger logger = Logger.getLogger(CircuitCreationTask.class.getName());
private final static int MAX_CIRCUIT_DIRTINESS = 300; // seconds
private final static int MAX_PENDING_CIRCUITS = 4;
// private final static int MAX_CIRCUIT_DIRTINESS = 300; // seconds
private final static int MAX_CIRCUIT_DIRTINESS = 600; // 10 minutes (Tor default)
// private final static int MAX_PENDING_CIRCUITS = 4;
private final static int MAX_PENDING_CIRCUITS = 32; // Tor default
private final TorConfig config;
private final Directory directory;
@@ -52,7 +54,7 @@ public class CircuitCreationTask implements Runnable {
this.circuitManager = circuitManager;
this.initializationTracker = initializationTracker;
this.pathChooser = pathChooser;
this.executor = Executors.newCachedThreadPool();
this.executor = Threading.newPool("CircuitCreationTask worker");
this.buildHandler = createCircuitBuildHandler();
this.internalBuildHandler = createInternalCircuitBuildHandler();
this.predictor = new CircuitPredictor();
@@ -112,7 +114,7 @@ public class CircuitCreationTask implements Runnable {
}
});
for(Circuit c: circuits) {
logger.fine("Closing idle dirty circuit: "+ c);
logger.fine("Closing idle dirty circuit \n* CircuitID: " + c);
((CircuitImpl)c).markForClose();
}
}
@@ -124,7 +126,7 @@ public class CircuitCreationTask implements Runnable {
if(!directory.haveMinimumRouterInfo()) {
if(notEnoughDirectoryInformationWarningCounter % 20 == 0)
logger.info("Cannot build circuits because we don't have enough directory information");
logger.info("Not enough directory information to build circuits");
notEnoughDirectoryInformationWarningCounter++;
return;
}
@@ -142,6 +144,11 @@ public class CircuitCreationTask implements Runnable {
}
private void buildCircuitIfNeeded() {
if (connectionCache.isClosed()) {
logger.warning("Not building circuits, because connection cache is closed");
return;
}
final List<StreamExitRequest> pendingExitStreams = circuitManager.getPendingExitStreams();
final List<PredictedPortTarget> predictedPorts = predictor.getPredictedPortTargets();
final List<ExitTarget> exitTargets = new ArrayList<ExitTarget>();
@@ -223,7 +230,7 @@ public class CircuitCreationTask implements Runnable {
return new CircuitBuildHandler() {
public void circuitBuildCompleted(Circuit circuit) {
logger.fine("Circuit completed to: "+ circuit);
logger.fine("Built new circuit \n* CircuitID: " + circuit);
circuitOpenedHandler(circuit);
lastNewCircuit.set(System.currentTimeMillis());
}
@@ -265,7 +272,7 @@ public class CircuitCreationTask implements Runnable {
return new CircuitBuildHandler() {
public void nodeAdded(CircuitNode node) {
logger.finer("Node added to internal circuit: "+ node);
logger.finer("Added " + node + " to internal circuit");
}
public void connectionFailed(String reason) {
@@ -283,7 +290,7 @@ public class CircuitCreationTask implements Runnable {
}
public void circuitBuildCompleted(Circuit circuit) {
logger.fine("Internal circuit build completed: "+ circuit);
logger.fine("Built new circuit \n* CircuitID: " + circuit);
lastNewCircuit.set(System.currentTimeMillis());
circuitManager.addCleanInternalCircuit((InternalCircuit) circuit);
}

View File

@@ -35,7 +35,7 @@ public class CircuitExtender {
CircuitNode createFastTo(Router targetRouter) {
logger.fine("Creating 'fast' to "+ targetRouter);
logger.fine("Performing CREATE_FAST handshake with " + targetRouter);
final TorCreateFastKeyAgreement kex = new TorCreateFastKeyAgreement();
sendCreateFastCell(kex);
return receiveAndProcessCreateFastResponse(targetRouter, kex);
@@ -50,7 +50,7 @@ public class CircuitExtender {
private CircuitNode receiveAndProcessCreateFastResponse(Router targetRouter, TorKeyAgreement kex) {
final Cell cell = circuit.receiveControlCellResponse();
if(cell == null) {
throw new TorException("Timeout building circuit waiting for CREATE_FAST response from "+ targetRouter);
throw new TorException("Circuit build timeout waiting for CREATE_FAST response from " + targetRouter);
}
return processCreatedFastCell(targetRouter, cell, kex);
@@ -89,14 +89,14 @@ public class CircuitExtender {
}
private void logProtocolViolation(String sourceName, Router targetRouter) {
final String version = (targetRouter == null) ? "(none)" : targetRouter.getVersion();
final String targetName = (targetRouter == null) ? "(none)" : targetRouter.getNickname();
logger.warning("Protocol error extending circuit from ("+ sourceName +") to ("+ targetName +") [version: "+ version +"]");
final String version = (targetRouter == null) ? "n/a" : targetRouter.getVersion();
final String targetName = (targetRouter == null) ? "n/a" : targetRouter.getNickname();
logger.warning("Protocol error extending circuit from [" + sourceName + "] to [" + targetName + "] -> version: " + version);
}
private String nodeToName(CircuitNode node) {
if(node == null || node.getRouter() == null) {
return "(null)";
return "n/a";
}
final Router router = node.getRouter();
return router.getNickname();
@@ -121,7 +121,7 @@ public class CircuitExtender {
if(code == Cell.ERROR_PROTOCOL) {
logProtocolViolation(source, extendTarget);
}
throw new TorException("Error from ("+ source +") while extending to ("+ extendTarget.getNickname() + "): "+ msg);
throw new TorException("Error from [" + source + "] while extending to [" + extendTarget.getNickname() + "] " + msg);
} else if(command != expectedCommand) {
final String expected = RelayCellImpl.commandToDescription(expectedCommand);
final String received = RelayCellImpl.commandToDescription(command);
@@ -134,7 +134,7 @@ public class CircuitExtender {
public CircuitNode createNewNode(Router r, byte[] keyMaterial, byte[] verifyDigest) {
final CircuitNode node = CircuitNodeImpl.createNode(r, circuit.getFinalCircuitNode(), keyMaterial, verifyDigest);
logger.fine("Adding new circuit node for "+ r.getNickname());
logger.fine("Adding new circuit node [" + r.getNickname() + "]"); // we're extending the circuit with this node, not for it.
circuit.appendNode(node);
return node;

View File

@@ -9,6 +9,7 @@ import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -18,6 +19,7 @@ import com.subgraph.orchid.Connection;
import com.subgraph.orchid.ConnectionIOException;
import com.subgraph.orchid.RelayCell;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorException;
import com.subgraph.orchid.circuits.cells.CellImpl;
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
@@ -36,11 +38,10 @@ public class CircuitIO implements DashboardRenderable {
private final BlockingQueue<RelayCell> relayCellResponseQueue;
private final BlockingQueue<Cell> controlCellResponseQueue;
private final Map<Integer, StreamImpl> streamMap;
private final Object relaySendLock = new Object();
/** LOCKING: streamMap */
private final ReentrantLock streamLock = Threading.lock("stream");
private final ReentrantLock relaySendLock = Threading.lock("relaySend");
private boolean isMarkedForClose;
/** LOCKING: streamMap */
private boolean isClosed;
CircuitIO(CircuitImpl circuit, Connection connection, int circuitId) {
@@ -120,7 +121,7 @@ public class CircuitIO implements DashboardRenderable {
}
private void processDestroyCell(int reason) {
logger.fine("DESTROY cell received ("+ CellImpl.errorToDescription(reason) +") on "+ circuit);
logger.fine("DESTROY cell received on " + circuit + CellImpl.errorToDescription(reason));
destroyCircuit();
}
@@ -171,7 +172,8 @@ public class CircuitIO implements DashboardRenderable {
}
}
synchronized(streamMap) {
streamLock.lock();
try {
final StreamImpl stream = streamMap.get(cell.getStreamId());
// It's not unusual for the stream to not be found. For example, if a RELAY_CONNECTED arrives after
// the client has stopped waiting for it, the stream will never be tracked and eventually the edge node
@@ -179,6 +181,8 @@ public class CircuitIO implements DashboardRenderable {
if(stream != null) {
stream.addInputCell(cell);
}
} finally {
streamLock.unlock();
}
}
@@ -187,7 +191,8 @@ public class CircuitIO implements DashboardRenderable {
}
void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
synchronized(relaySendLock) {
relaySendLock.lock();
try {
logRelayCell("Sending: ", cell);
cell.setLength();
targetNode.updateForwardDigest(cell);
@@ -200,6 +205,8 @@ public class CircuitIO implements DashboardRenderable {
targetNode.waitForSendWindowAndDecrement();
sendCell(cell);
} finally {
relaySendLock.unlock();
}
}
@@ -236,31 +243,35 @@ public class CircuitIO implements DashboardRenderable {
void markForClose() {
boolean shouldClose;
synchronized (streamMap) {
streamLock.lock();
try {
if(isMarkedForClose) {
return;
}
isMarkedForClose = true;
shouldClose = streamMap.isEmpty();
} finally {
streamLock.unlock();
}
if (shouldClose)
if(shouldClose)
closeCircuit();
}
boolean isMarkedForClose() {
synchronized (streamMap) {
streamLock.lock();
try {
return isMarkedForClose;
} finally {
streamLock.unlock();
}
}
private void closeCircuit() {
logger.fine("Closing circuit "+ circuit);
logger.fine("Closing CircuitID="+ circuit);
sendDestroyCell(Cell.ERROR_NONE);
connection.removeCircuit(circuit);
circuit.setStateDestroyed();
synchronized(streamMap) {
isClosed = true;
}
isClosed = true;
}
void sendDestroyCell(int reason) {
@@ -278,7 +289,8 @@ public class CircuitIO implements DashboardRenderable {
}
void destroyCircuit() {
synchronized(streamMap) {
streamLock.lock();
try {
if(isClosed) {
return;
}
@@ -289,31 +301,42 @@ public class CircuitIO implements DashboardRenderable {
s.close();
}
isClosed = true;
} finally {
streamLock.unlock();
}
}
StreamImpl createNewStream(boolean autoclose) {
synchronized(streamMap) {
streamLock.lock();
try {
final int streamId = circuit.getStatus().nextStreamId();
final StreamImpl stream = new StreamImpl(circuit, circuit.getFinalCircuitNode(), streamId, autoclose);
streamMap.put(streamId, stream);
return stream;
} finally {
streamLock.unlock();
}
}
void removeStream(StreamImpl stream) {
boolean shouldClose;
synchronized(streamMap) {
streamLock.lock();
try {
streamMap.remove(stream.getStreamId());
shouldClose = streamMap.isEmpty() && isMarkedForClose;
} finally {
streamLock.unlock();
}
if (shouldClose)
if(shouldClose)
closeCircuit();
}
List<Stream> getActiveStreams() {
synchronized (streamMap) {
streamLock.lock();
try {
return new ArrayList<Stream>(streamMap.values());
} finally {
streamLock.unlock();
}
}

View File

@@ -124,6 +124,11 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
return !status.isDirty();
}
/** @since 1.2.2 */
public boolean isClosed() {
return status.isClosed();
}
public int getSecondsDirty() {
return (int) (status.getMillisecondsDirty() / 1000);
}
@@ -229,7 +234,10 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
}
public void destroyCircuit() {
io.destroyCircuit();
// We might not have bound this circuit yet
if (io != null) {
io.destroyCircuit();
}
circuitManager.removeActiveCircuit(this);
}
@@ -253,19 +261,87 @@ public abstract class CircuitImpl implements Circuit, DashboardRenderable {
protected abstract String getCircuitTypeLabel();
public String toString() {
return " Circuit ("+ getCircuitTypeLabel() + ") id="+ getCircuitId() +" state=" + status.getStateAsString() +" "+ pathToString();
return "<tr class=\"circuit\"><td title=\"Circuit ID\">" + getCircuitId() + "</td><td>" +
getCircuitTypeLabel() + "</td><td>" +
status.getStateAsString().replace("Open ", "").replace("[", "").replace("]", "") + "</td><td>" +
// "bandwidth usage stats here</td><td> -->" +
pathToString() + "</td></tr>";
}
protected String pathToString() {
final StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append("<span class=\"circuitcontainer\">");
for(CircuitNode node: nodeList) {
Router r = node.getRouter();
if(sb.length() > 1)
sb.append(",");
sb.append(node.toString());
sb.append(" <span class=\"hidden\">-> </span>");
sb.append("<span class=\"nodecontainer\"><span class=\"hidden\">[</span><span class=\"node\" onclick=\"copyText();\"><span class=\"flag\" data-country=\"");
if (r != null) {
if (r.getCountryName() != null)
sb.append(r.getCountryName() + " (" + r.getAddress() + ")");
else
sb.append(r.getAddress());
} else {
sb.append("unknown");
}
sb.append("]");
sb.append("\">");
sb.append("<img height=\"11\" width=\"16\" src=\"/flags.jsp?c=");
if (r != null && r.getCountryName() != null)
sb.append(r.getCountryCode().toLowerCase().replace("--", "a0"));
else
sb.append("a0");
sb.append("\"></span>");
if (r != null) {
String idHash = r.getIdentityHash().toString().toUpperCase();
int uptime = r.getUptime();
long bw = r.getObservedBandwidth();
long bwEst = r.getEstimatedBandwidth();
sb.append("<span class=\"nickname");
if (r.getPlatform() != null || r.getUptime() > 0 || r.getObservedBandwidth() > 0 || r.getEstimatedBandwidth() > 0)
sb.append("\" data-ipv4=\"");
if (r.getPlatform() != null)
sb.append(r.getPlatform().replace("Tor ", "").replace(" on ", " / ").replace("-alpha-dev", "-alpha"));
if (uptime > 0 && bw > 0)
sb.append(" \u2022 ");
if (bw > 0 && bw < 1048576)
sb.append((bw / 1024) + " KB/s");
else if (bw >= 1048576)
sb.append(((bw / 1024) / 1024) + " MB/s");
// useMicrodescriptors=true
else if (bwEst > 0 && bwEst < 2048)
sb.append(bwEst + " KB/s (estimated)");
else if (bwEst >= 2048)
sb.append((bwEst / 1024) + " MB/s (estimated)");
if (uptime > 0)
sb.append(" \u2022 Up: ");
if (uptime > 172800)
sb.append((((uptime / 60) / 60) / 24) + " days");
else if (uptime > 1440)
sb.append(((uptime / 60) / 60) + " hours");
else if (uptime > 3600)
sb.append(uptime / 60 + " minutes");
else if (uptime > 0)
sb.append(uptime + " seconds");
sb.append("\">");
sb.append("<a class=\"script\" href=\"https://metrics.torproject.org/rs.html#search/" + idHash + "\" target=\"_blank\">" + node.toString() + "</a>");
// <noscript> alternative lookup
sb.append("<noscript>");
sb.append("<a href=\"https://torstatus.blutmagie.de/router_detail.php?FP=" + idHash + "\" target=\"_blank\">" + node.toString() + "</a>");
sb.append("</noscript>");
sb.append("</span><span class=\"hidden\"> (<b>" + r.getAddress() + "</b>)</span></span><span class=\"hidden\">]</span></span>");
} else {
sb.append("<span class=\"nickname unknown\"><i>unknown</i></span>");
sb.append("</span><span class=\"hidden\">]</span></span>");
}
}
sb.append("</span>");
return sb.toString();
}

View File

@@ -11,8 +11,10 @@ import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
import com.subgraph.orchid.Circuit;
import com.subgraph.orchid.CircuitBuildHandler;
@@ -29,6 +31,7 @@ import com.subgraph.orchid.OpenFailedException;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.StreamConnectFailedException;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.circuits.guards.EntryGuards;
@@ -57,11 +60,14 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
private int pendingInternalCircuitCount = 0;
private final TorRandom random;
private final PendingExitStreams pendingExitStreams;
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService scheduledExecutor = Threading.newSingleThreadScheduledPool("CircuitManager worker");
private final CircuitCreationTask circuitCreationTask;
private final TorInitializationTracker initializationTracker;
private final CircuitPathChooser pathChooser;
private final HiddenServiceManager hiddenServiceManager;
private final ReentrantLock lock = Threading.lock("circuitManager");
private boolean isBuilding = false;
public CircuitManagerImpl(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker initializationTracker) {
this.config = config;
@@ -84,14 +90,30 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
}
public void startBuildingCircuits() {
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
lock.lock();
try {
isBuilding = true;
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
} finally {
lock.unlock();
}
}
public synchronized void stopBuildingCircuits(boolean killCircuits) {
scheduledExecutor.shutdownNow();
if(killCircuits) {
List<CircuitImpl> circuits = new ArrayList<CircuitImpl>(activeCircuits);
for(CircuitImpl c: circuits) {
public void stopBuildingCircuits(boolean killCircuits) {
lock.lock();
try {
isBuilding = false;
scheduledExecutor.shutdownNow();
} finally {
lock.unlock();
}
if (killCircuits) {
ArrayList<CircuitImpl> circuits;
synchronized (activeCircuits) {
circuits = new ArrayList<CircuitImpl>(activeCircuits);
}
for (CircuitImpl c : circuits) {
c.destroyCircuit();
}
}
@@ -106,16 +128,31 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
activeCircuits.add(circuit);
activeCircuits.notifyAll();
}
boolean doDestroy;
lock.lock();
try {
doDestroy = !isBuilding;
} finally {
lock.unlock();
}
if (doDestroy) {
// we were asked to stop since this circuit was started
circuit.destroyCircuit();
}
}
void removeActiveCircuit(CircuitImpl circuit) {
synchronized (activeCircuits) {
activeCircuits.remove(circuit);
}
}
synchronized int getActiveCircuitCount() {
return activeCircuits.size();
int getActiveCircuitCount() {
synchronized (activeCircuits) {
return activeCircuits.size();
}
}
Set<Circuit> getPendingCircuits() {
@@ -126,17 +163,28 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
});
}
synchronized int getPendingCircuitCount() {
return getPendingCircuits().size();
int getPendingCircuitCount() {
lock.lock();
try {
return getPendingCircuits().size();
} finally {
lock.unlock();
}
}
Set<Circuit> getCircuitsByFilter(CircuitFilter filter) {
final Set<Circuit> result = new HashSet<Circuit>();
final Set<CircuitImpl> circuits = new HashSet<CircuitImpl>();
synchronized (activeCircuits) {
for(CircuitImpl c: activeCircuits) {
if(filter == null || filter.filter(c)) {
result.add(c);
}
// the filter might lock additional objects, causing a deadlock, so don't
// call it inside the monitor
circuits.addAll(activeCircuits);
}
for(CircuitImpl c: circuits) {
if(filter == null || filter.filter(c)) {
result.add(c);
}
}
return result;
@@ -182,6 +230,8 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
throw new OpenFailedException("Hidden services not supported");
} else if(hostname.toLowerCase().endsWith(".exit")) {
throw new OpenFailedException(".exit addresses are not supported");
} else if (hostname.toLowerCase().endsWith(".i2p")) {
throw new OpenFailedException(".i2p addresses are not supported");
}
}
@@ -288,13 +338,15 @@ public class CircuitManagerImpl implements CircuitManager, DashboardRenderable {
if((flags & DASHBOARD_CIRCUITS) == 0) {
return;
}
renderer.renderComponent(writer, flags, connectionCache);
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
writer.println("[Circuit Manager]");
writer.println();
writer.println("<table id=\"circuitmon\" width=\"100%\">\n<tr><th colspan=\"4\" align=\"left\">Circuit Monitor</th></tr>");
writer.println("<tr class=\"subtitle\"><th align=\"left\">Circuit ID</th><th align=\"left\">Type</th>" +
"<th align=\"left\">State</th><th align=\"left\" width=\"50%\">Participants</th></tr>");
for(Circuit c: getCircuitsByFilter(null)) {
renderer.renderComponent(writer, flags, c);
}
writer.println("</table>\n</td></tr>");
renderer.renderComponent(writer, flags, connectionCache);
renderer.renderComponent(writer, flags, circuitCreationTask.getCircuitPredictor());
}
public InternalCircuit getCleanInternalCircuit() throws InterruptedException {

View File

@@ -67,9 +67,9 @@ public class CircuitNodeImpl implements CircuitNode {
public String toString() {
if(router != null) {
return "|"+ router.getNickname() + "|";
return router.getNickname();
} else {
return "|()|";
return "<i>unknown</i>";
}
}

View File

@@ -79,21 +79,31 @@ public class CircuitPredictor implements DashboardRenderable {
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags)
throws IOException {
if((flags & DASHBOARD_PREDICTED_PORTS) == 0) {
return;
}
writer.println("[Predicted Ports] ");
if (getPredictedPorts().size() > 0)
writer.print("<tr>");
else
writer.print("<tr class=\".hidden\" hidden>");
writer.println("<td>\n<hr>\n<table id=\"ports\" width=\"100%\">\n<tr><th colspan=\"4\" align=\"left\">Predicted Ports</th></tr>");
writer.println("<tr class=\"subtitle\"><th colspan=\"2\" align=\"left\" width=\"50%\">Port</th><th colspan=\"2\" align=\"left\">Last Seen</th></tr>");
for(int port : portsSeen.keySet()) {
writer.write(" "+ port);
writer.write("<tr><td colspan=\"2\">" + port + "</td>");
Long lastSeen = portsSeen.get(port);
writer.write("<td colspan=\"2\">");
if(lastSeen != null) {
long now = System.currentTimeMillis();
long ms = now - lastSeen;
writer.write(" (last seen "+ TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) +" minutes ago)");
String min = "minutes";
if (ms > 59999 && ms < 120000)
min = "minute";
if (ms < 60000)
writer.write("<span class=\"nowrap\">in the last 60s</span>");
else
writer.write("<span class=\"nowrap\">" + TimeUnit.MINUTES.convert(ms, TimeUnit.MILLISECONDS) + " " + min + " ago</span>");
}
writer.println();
writer.println("</td></tr>");
}
writer.println();
}
}

View File

@@ -6,7 +6,7 @@ public class CircuitStatus {
enum CircuitState {
UNCONNECTED("Unconnected"),
BUILDING("Building"),
BUILDING("<i>Building&hellip;</i>"),
FAILED("Failed"),
OPEN("Open"),
DESTROYED("Destroyed");
@@ -89,6 +89,11 @@ public class CircuitStatus {
return state == CircuitState.UNCONNECTED;
}
/** @since 1.2.2 */
boolean isClosed() {
return state == CircuitState.FAILED || state == CircuitState.DESTROYED;
}
String getStateAsString() {
if(state == CircuitState.OPEN) {
return state.toString() + " ["+ getDirtyString() + "]";
@@ -98,9 +103,9 @@ public class CircuitStatus {
private String getDirtyString() {
if(!isDirty()) {
return "Clean";
return "<span class=\"nowrap\"><span class=\"hidden\">(</span>Clean<span class=\"hidden\">)</span></span>";
} else {
return "Dirty "+ (getMillisecondsDirty() / 1000) +"s";
return "<span class=\"nowrap\"><span class=\"hidden\">(</span>Dirty: " + (getMillisecondsDirty() / 1000) + "s<span class=\"hidden\">)</span></span>";
}
}
int nextStreamId() {

View File

@@ -37,6 +37,6 @@ public class DirectoryCircuitImpl extends CircuitImpl implements DirectoryCircui
@Override
protected String getCircuitTypeLabel() {
return "Directory";
return "<span class=\"directory\">Directory</span>";
}
}

View File

@@ -82,6 +82,6 @@ public class ExitCircuitImpl extends CircuitImpl implements ExitCircuit {
@Override
protected String getCircuitTypeLabel() {
return "Exit";
return "<span class=\"exit\">Exit</span>";
}
}

View File

@@ -104,15 +104,15 @@ public class InternalCircuitImpl extends CircuitImpl implements InternalCircuit,
protected String getCircuitTypeLabel() {
switch(type) {
case HS_CIRCUIT:
return "Hidden Service";
return "<span class=\"hiddenservice\">Hidden Service</span>";
case HS_DIRECTORY:
return "HS Directory";
return "<span class=\"hiddenservice\">HS Directory</span>";
case HS_INTRODUCTION:
return "HS Introduction";
return "<span class=\"hiddenservice\">HS Introduction</span>";
case UNUSED:
return "Internal";
return "<span class=\"internal\">Internal</span>";
default:
return "(null)";
return "[null]";
}
}
}

View File

@@ -117,7 +117,7 @@ public class StreamImpl implements Stream, DashboardRenderable {
if(isClosed)
return;
logger.fine("Closing stream "+ this);
logger.fine("Closing " + this);
isClosed = true;
inputStream.close();
@@ -134,8 +134,13 @@ public class StreamImpl implements Stream, DashboardRenderable {
}
}
/** @since 1.2.2-0.2 */
public boolean isClosed() {
return isClosed;
}
public void openDirectory(long timeout) throws InterruptedException, TimeoutException, StreamConnectFailedException {
streamTarget = "[Directory]";
streamTarget = "Directory Service";
final RelayCell cell = new RelayCellImpl(circuit.getFinalCircuitNode(), circuit.getCircuitId(), streamId, RelayCell.RELAY_BEGIN_DIR);
circuit.sendRelayCellToFinalNode(cell);
waitForRelayConnected(timeout);
@@ -202,18 +207,20 @@ public class StreamImpl implements Stream, DashboardRenderable {
}
public String toString() {
return "[Stream stream_id="+ streamId + " circuit="+ circuit +" target="+ streamTarget +"]";
return "StreamID=" + streamId + " Circuit=" + circuit + " Target=" + streamTarget;
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
writer.print(" ");
writer.print("[Stream stream_id="+ streamId + " cid="+ circuit.getCircuitId());
writer.print("<tr class=\"stream\"><td title=\"Stream ID\">");
writer.print("<b class=\"streamID\">Stream ID: </b>" + streamId + "</td>");
// TODO: Indicate target if .onion > hiddenServiceManager.getStreamTo(hostname, port);
writer.print("<td colspan=\"2\"><b class=\"target\" title=\"Target\">Target:</b> " + streamTarget + "</td><td>");
if(relayConnectedReceived) {
writer.print(" sent="+outputStream.getBytesSent() + " recv="+ inputStream.getBytesReceived());
writer.print("<span class=\"nowrap\" title=\"Received\"><b class=\"rx\">Received:</b> " + (inputStream.getBytesReceived() / 1024) + " KB</span> " +
"<span class=\"nowrap\" title=\"Sent\"><b class=\"tx\">Sent:</b> " + (outputStream.getBytesSent() / 1024) + " KB</span>");
} else {
writer.print(" (waiting connect)");
writer.print("<i>Connecting&hellip;</i>");
}
writer.print(" target="+ streamTarget);
writer.println("]");
writer.println("</td></tr>");
}
}

View File

@@ -177,39 +177,39 @@ public class CellImpl implements Cell {
}
public String toString() {
return "Cell: circuit_id="+ circuitId +" command="+ command +" payload_len="+ cellBuffer.position();
return "Cell: CircuitID=" + circuitId +" Command=" + command +" PayloadLength=" + cellBuffer.position();
}
public static String errorToDescription(int errorCode) {
switch(errorCode) {
case ERROR_NONE:
return "No error reason given";
return "\n* Reason: No error reason given";
case ERROR_PROTOCOL:
return "Tor protocol violation";
return "\n* Reason: Tor protocol violation";
case ERROR_INTERNAL:
return "Internal error";
return "\n* Reason: Internal error";
case ERROR_REQUESTED:
return "Response to a TRUNCATE command sent from client";
return "\n* Reason: Response to a TRUNCATE command sent from client";
case ERROR_HIBERNATING:
return "Not currently operating; trying to save bandwidth.";
return "\n* Reason: Not currently operating; trying to save bandwidth.";
case ERROR_RESOURCELIMIT:
return "Out of memory, sockets, or circuit IDs.";
return "\n* Reason: Out of memory, sockets, or circuit IDs.";
case ERROR_CONNECTFAILED:
return "Unable to reach server.";
return "\n* Reason: Unable to reach server.";
case ERROR_OR_IDENTITY:
return "Connected to server, but its OR identity was not as expected.";
return "\n* Reason: Connected to server, but its OR identity was not as expected.";
case ERROR_OR_CONN_CLOSED:
return "The OR connection that was carrying this circuit died.";
return "\n* Reason: The OR connection that was carrying this circuit died.";
case ERROR_FINISHED:
return "The circuit has expired for being dirty or old.";
return "\n* Reason: The circuit has expired for being dirty or old.";
case ERROR_TIMEOUT:
return "Circuit construction took too long.";
return "\n* Reason: Circuit construction took too long.";
case ERROR_DESTROYED:
return "The circuit was destroyed without client TRUNCATE";
return "\n* Reason: The circuit was destroyed without client TRUNCATE";
case ERROR_NOSUCHSERVICE:
return "Request for unknown hidden service";
return "\n* Reason: Request for unknown hidden service";
default:
return "Error code "+ errorCode;
return "\n* Reason: Error code " + errorCode;
}
}
}

View File

@@ -5,6 +5,8 @@ import java.util.Set;
import com.subgraph.orchid.BridgeRouter;
import com.subgraph.orchid.Descriptor;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.RouterDescriptor;
import com.subgraph.orchid.crypto.TorPublicKey;
import com.subgraph.orchid.data.HexDigest;
@@ -18,7 +20,9 @@ public class BridgeRouterImpl implements BridgeRouter {
private HexDigest identity;
private Descriptor descriptor;
private Directory directory;
private volatile String cachedCountryCode;
private volatile String cachedCountryName;
BridgeRouterImpl(IPv4Address address, int port) {
this.address = address;
@@ -29,6 +33,14 @@ public class BridgeRouterImpl implements BridgeRouter {
return address;
}
public String getPlatform() {
return null;
}
public int getUptime() {
return 0;
}
public HexDigest getIdentity() {
return identity;
}
@@ -41,6 +53,10 @@ public class BridgeRouterImpl implements BridgeRouter {
this.descriptor = descriptor;
}
public void setDirectory(Directory directory) {
this.directory = directory;
}
@Override
public int hashCode() {
final int prime = 31;
@@ -88,6 +104,15 @@ public class BridgeRouterImpl implements BridgeRouter {
return cc;
}
public String getCountryName() {
String cn = cachedCountryName;
if (cn == null) {
cn = CountryCodeService.getInstance().getCountryNameForAddress(getAddress());
cachedCountryName = cn;
}
return cn;
}
public int getOnionPort() {
return port;
}

View File

@@ -6,7 +6,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -15,6 +14,7 @@ import com.subgraph.orchid.Directory;
import com.subgraph.orchid.DirectoryDownloader;
import com.subgraph.orchid.GuardEntry;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.circuits.path.CircuitNodeChooser;
import com.subgraph.orchid.circuits.path.CircuitNodeChooser.WeightRule;
@@ -47,7 +47,7 @@ public class EntryGuards {
this.pendingProbes = new HashSet<GuardEntry>();
this.bridges = new Bridges(config, directoryDownloader);
this.lock = new Object();
this.executor = Executors.newCachedThreadPool();
this.executor = Threading.newPool("EntryGuards worker");
}
public boolean isUsingBridges() {

View File

@@ -58,11 +58,16 @@ public class HiddenService {
this.descriptor = descriptor;
}
HiddenServiceCircuit getCircuit() {
/**
* As of 1.2.2, returns null if the previously set circuit is now closed.
*/
synchronized HiddenServiceCircuit getCircuit() {
if (circuit != null && circuit.isClosed())
circuit = null;
return circuit;
}
void setCircuit(HiddenServiceCircuit circuit) {
synchronized void setCircuit(HiddenServiceCircuit circuit) {
this.circuit = circuit;
}

View File

@@ -47,14 +47,16 @@ public class HiddenServiceManager {
}
private synchronized HiddenServiceCircuit getCircuitTo(HiddenService hs) throws OpenFailedException {
if(hs.getCircuit() == null) {
HiddenServiceCircuit rv = hs.getCircuit();
if(rv == null) {
final HiddenServiceCircuit c = openCircuitTo(hs);
if(c == null) {
throw new OpenFailedException("Failed to open circuit to "+ hs.getOnionAddressForLogging());
}
hs.setCircuit(c);
rv = c;
}
return hs.getCircuit();
return rv;
}
private HiddenServiceCircuit openCircuitTo(HiddenService hs) throws OpenFailedException {
@@ -118,4 +120,4 @@ public class HiddenServiceManager {
return null;
}
}
}
}

View File

@@ -147,7 +147,7 @@ public class ConfigNodeFilter implements RouterFilter {
}
private static RouterFilter createIdentityFilter(String s) {
if(isIdentityString(s)) {
if(!isIdentityString(s)) {
throw new IllegalArgumentException();
}
final HexDigest identity = HexDigest.createFromString(s);

View File

@@ -0,0 +1,425 @@
package com.subgraph.orchid.config;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.data.IPv4Address;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import net.i2p.util.SystemVersion;
public class TorConfigImpl implements TorConfig {
private void resetDefaults() {
dataDirectory = toFile("~/.orchid");
circuitBuildTimeout = toMS(60, TimeUnit.SECONDS);
// circuitStreamTimeout = 0; // a value of 0 will cause a request for an unavailable resource to never fail?
circuitStreamTimeout = toMS(90, TimeUnit.SECONDS);
circuitIdleTimeout = toMS(1, TimeUnit.HOURS);
newCircuitPeriod = toMS(30, TimeUnit.SECONDS);
maxCircuitDirtiness = toMS(10, TimeUnit.MINUTES);
maxClientCircuitsPending = 32;
enforceDistinctSubnets = true;
socksTimeout = toMS(2, TimeUnit.MINUTES);
numEntryGuards = 3;
useEntryGuards = true;
longLivedPorts = toIntList("21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300");
excludedNodes = Collections.emptyList();
excludedExitNodes = Collections.emptyList();
exitNodes = Collections.emptyList();
entryNodes = Collections.emptyList();
strictNodes = false;
fascistFirewall = false;
firewallPorts = toIntList("80,443");
safeSocks = false;
// safeLogging = true; // ineffective as target info displays in logs
safeLogging = false;
warnUnsafeSocks = true;
clientRejectInternalAddress = true;
handshakeV3Enabled = true;
handshakeV2Enabled = true;
hsAuth = new TorConfigHSAuth();
useNtorHandshake = AutoBoolValue.AUTO;
// full descriptors OOMs for < 192MB
if (SystemVersion.getMaxMemory() < 180*1024*1024L)
useMicrodescriptors = AutoBoolValue.AUTO;
else
useMicrodescriptors = AutoBoolValue.FALSE; // pull the full descriptors for extra info e.g. uptime stats
useBridges = false;
bridgeLines = new ArrayList<TorConfigBridgeLine>();
}
private File dataDirectory;
private long circuitBuildTimeout;
private long circuitStreamTimeout;
private long circuitIdleTimeout;
private long newCircuitPeriod;
private long maxCircuitDirtiness;
private int maxClientCircuitsPending;
private boolean enforceDistinctSubnets;
private long socksTimeout;
private int numEntryGuards;
private boolean useEntryGuards;
private List<Integer> longLivedPorts;
private List<String> excludedNodes;
private List<String> excludedExitNodes;
private List<String> exitNodes;
private List<String> entryNodes;
private boolean strictNodes;
private boolean fascistFirewall;
private List<Integer> firewallPorts;
private boolean safeSocks;
private boolean safeLogging;
private boolean warnUnsafeSocks;
private boolean clientRejectInternalAddress;
private boolean handshakeV3Enabled;
private boolean handshakeV2Enabled;
private TorConfigHSAuth hsAuth;
private AutoBoolValue useNtorHandshake;
private AutoBoolValue useMicrodescriptors;
private boolean useBridges;
private List<TorConfigBridgeLine> bridgeLines;
private static long toMS(long time, TimeUnit unit) {
return TimeUnit.MILLISECONDS.convert(time, unit);
}
private static File toFile(String path) {
if(path.startsWith("~/")) {
final File home = new File(System.getProperty("user.home"));
return new File(home, path.substring(2));
}
return new File(path);
}
private static List<Integer> toIntList(String val) {
final List<Integer> list = new ArrayList<Integer>();
for(String s: val.split(",")) {
list.add(Integer.parseInt(s));
}
return list;
}
private static List<String> toStringList(String val) {
final List<String> list = new ArrayList<String>();
for(String s: val.split(",")) {
list.add(s);
}
return list;
}
public TorConfigImpl() {
resetDefaults();
}
@Override
public File getDataDirectory() {
return dataDirectory;
}
@Override
public void setDataDirectory(File directory) {
this.dataDirectory = directory;
}
@Override
public long getCircuitBuildTimeout() {
return circuitBuildTimeout;
}
@Override
public void setCircuitBuildTimeout(long time, TimeUnit unit) {
circuitBuildTimeout = toMS(time, unit);
}
@Override
public long getCircuitStreamTimeout() {
return circuitStreamTimeout;
}
@Override
public void setCircuitStreamTimeout(long time, TimeUnit unit) {
circuitStreamTimeout = toMS(time, unit);
}
@Override
public long getCircuitIdleTimeout() {
return circuitIdleTimeout;
}
@Override
public void setCircuitIdleTimeout(long time, TimeUnit unit) {
circuitIdleTimeout = toMS(time, unit);
}
@Override
public long getNewCircuitPeriod() {
return newCircuitPeriod;
}
@Override
public void setNewCircuitPeriod(long time, TimeUnit unit) {
newCircuitPeriod = toMS(time, unit);
}
@Override
public long getMaxCircuitDirtiness() {
return maxCircuitDirtiness;
}
@Override
public void setMaxCircuitDirtiness(long time, TimeUnit unit) {
maxCircuitDirtiness = toMS(time, unit);
}
@Override
public int getMaxClientCircuitsPending() {
return maxClientCircuitsPending;
}
@Override
public void setMaxClientCircuitsPending(int value) {
maxClientCircuitsPending = value;
}
@Override
public boolean getEnforceDistinctSubnets() {
return enforceDistinctSubnets;
}
@Override
public void setEnforceDistinctSubnets(boolean value) {
enforceDistinctSubnets = value;
}
@Override
public long getSocksTimeout() {
return socksTimeout;
}
@Override
public void setSocksTimeout(long value) {
socksTimeout = value;
}
@Override
public int getNumEntryGuards() {
return numEntryGuards;
}
@Override
public void setNumEntryGuards(int value) {
numEntryGuards = value;
}
@Override
public boolean getUseEntryGuards() {
return useEntryGuards;
}
@Override
public void setUseEntryGuards(boolean value) {
useEntryGuards = value;
}
@Override
public List<Integer> getLongLivedPorts() {
return longLivedPorts;
}
@Override
public void setLongLivedPorts(List<Integer> ports) {
longLivedPorts = ports;
}
@Override
public List<String> getExcludeNodes() {
return excludedNodes;
}
@Override
public void setExcludeNodes(List<String> nodes) {
excludedNodes = nodes;
}
@Override
public List<String> getExcludeExitNodes() {
return excludedExitNodes;
}
@Override
public void setExcludeExitNodes(List<String> nodes) {
excludedExitNodes = nodes;
}
@Override
public List<String> getExitNodes() {
return exitNodes;
}
@Override
public void setExitNodes(List<String> nodes) {
exitNodes = nodes;
}
@Override
public List<String> getEntryNodes() {
return entryNodes;
}
@Override
public void setEntryNodes(List<String> nodes) {
entryNodes = nodes;
}
@Override
public boolean getStrictNodes() {
return strictNodes;
}
@Override
public void setStrictNodes(boolean value) {
strictNodes = value;
}
@Override
public boolean getFascistFirewall() {
return fascistFirewall;
}
@Override
public void setFascistFirewall(boolean value) {
fascistFirewall = value;
}
@Override
public List<Integer> getFirewallPorts() {
return firewallPorts;
}
@Override
public void setFirewallPorts(List<Integer> ports) {
firewallPorts = ports;
}
@Override
public boolean getSafeSocks() {
return safeSocks;
}
@Override
public void setSafeSocks(boolean value) {
safeSocks = value;
}
@Override
public boolean getSafeLogging() {
return safeLogging;
}
@Override
public void setSafeLogging(boolean value) {
safeLogging = value;
}
@Override
public boolean getWarnUnsafeSocks() {
return warnUnsafeSocks;
}
@Override
public void setWarnUnsafeSocks(boolean value) {
warnUnsafeSocks = value;
}
@Override
public boolean getClientRejectInternalAddress() {
return clientRejectInternalAddress;
}
@Override
public void setClientRejectInternalAddress(boolean value) {
clientRejectInternalAddress = value;
}
@Override
public boolean getHandshakeV3Enabled() {
return handshakeV3Enabled;
}
@Override
public void setHandshakeV3Enabled(boolean value) {
handshakeV3Enabled = value;
}
@Override
public boolean getHandshakeV2Enabled() {
return handshakeV2Enabled;
}
@Override
public void setHandshakeV2Enabled(boolean value) {
handshakeV2Enabled = value;
}
@Override
public HSDescriptorCookie getHidServAuth(String key) {
return hsAuth.get(key);
}
@Override
public void addHidServAuth(String key, String value) {
hsAuth.add(key, value);
}
@Override
public AutoBoolValue getUseNTorHandshake() {
return useNtorHandshake;
}
@Override
public void setUseNTorHandshake(AutoBoolValue value) {
useNtorHandshake = value;
}
@Override
public AutoBoolValue getUseMicrodescriptors() {
return useMicrodescriptors;
}
@Override
public void setUseMicrodescriptors(AutoBoolValue value) {
useMicrodescriptors = value;
}
@Override
public boolean getUseBridges() {
return useBridges;
}
@Override
public void setUseBridges(boolean value) {
useBridges = value;
}
@Override
public List<TorConfigBridgeLine> getBridges() {
return bridgeLines;
}
@Override
public void addBridge(IPv4Address address, int port) {
bridgeLines.add(new TorConfigBridgeLine(address, port, null));
}
@Override
public void addBridge(IPv4Address address, int port, HexDigest fingerprint) {
bridgeLines.add(new TorConfigBridgeLine(address, port, fingerprint));
}
}

View File

@@ -6,29 +6,29 @@ import java.util.List;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorConfig.AutoBoolValue;
import com.subgraph.orchid.TorConfig.ConfigVarType;
//import com.subgraph.orchid.TorConfig.ConfigVarType;
public class TorConfigParser {
public Object parseValue(String value, ConfigVarType type) {
public Object parseValue(String value, String type) {
switch(type) {
case BOOLEAN:
case "BOOLEAN":
return Boolean.parseBoolean(value);
case INTEGER:
case "INTEGER":
return Integer.parseInt(value);
case INTERVAL:
case "INTERVAL":
return parseIntervalValue(value);
case PATH:
case "PATH":
return parseFileValue(value);
case PORTLIST:
case "PORTLIST":
return parseIntegerList(value);
case STRING:
case "STRING":
return value;
case STRINGLIST:
case "STRINGLIST":
return parseCSV(value);
case AUTOBOOL:
case "AUTOBOOL":
return parseAutoBool(value);
case HS_AUTH:
case "HS_AUTH":
default:
throw new IllegalArgumentException();
}

View File

@@ -1,199 +0,0 @@
package com.subgraph.orchid.config;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorConfig.ConfigVar;
import com.subgraph.orchid.TorConfig.ConfigVarType;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.data.IPv4Address;
public class TorConfigProxy implements InvocationHandler {
private final Map<String, Object> configValues;
private final List<TorConfigBridgeLine> bridges;
private final TorConfigParser parser;
public TorConfigProxy() {
this.configValues = new HashMap<String, Object>();
this.bridges = new ArrayList<TorConfigBridgeLine>();
this.configValues.put("Bridges", bridges);
this.parser = new TorConfigParser();
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith("set")) {
invokeSetMethod(method, args);
return null;
} else if(method.getName().startsWith("get")) {
if(args == null) {
return invokeGetMethod(method);
} else {
return invokeGetMethodWithArgs(method, args);
}
} else if(method.getName().startsWith("add")) {
invokeAddMethod(method, args);
return null;
} else {
throw new IllegalArgumentException();
}
}
void invokeSetMethod(Method method, Object[] args) {
final String name = getVariableNameForMethod(method);
final ConfigVar annotation = getAnnotationForVariable(name);
if(annotation != null && annotation.type() == ConfigVarType.INTERVAL) {
setIntervalValue(name, args);
} else {
configValues.put(name, args[0]);
}
}
private void setIntervalValue(String varName, Object[] args) {
if(!(args[0] instanceof Long && args[1] instanceof TimeUnit)) {
throw new IllegalArgumentException();
}
final long time = (Long) args[0];
final TimeUnit unit = (TimeUnit) args[1];
final TorConfigInterval interval = new TorConfigInterval(time, unit);
configValues.put(varName, interval);
}
private Object invokeGetMethodWithArgs(Method method, Object[] args) {
final String varName = getVariableNameForMethod(method);
if(getVariableType(varName) == ConfigVarType.HS_AUTH) {
return invokeHSAuthGet(varName, args);
} else {
throw new IllegalArgumentException();
}
}
private Object invokeGetMethod(Method method) {
final String varName = getVariableNameForMethod(method);
final Object value = getVariableValue(varName);
if(value instanceof TorConfigInterval) {
final TorConfigInterval interval = (TorConfigInterval) value;
return interval.getMilliseconds();
} else {
return value;
}
}
private Object invokeHSAuthGet(String varName, Object[] args) {
if(!(args[0] instanceof String)) {
throw new IllegalArgumentException();
}
final TorConfigHSAuth hsAuth = getHSAuth(varName);
return hsAuth.get((String) args[0]);
}
private void invokeAddMethod(Method method, Object[] args) {
final String name = getVariableNameForMethod(method);
final ConfigVarType type = getVariableType(name);
switch(type) {
case HS_AUTH:
invokeHSAuthAdd(name, args);
break;
case BRIDGE_LINE:
invokeBridgeAdd(args);
break;
default:
throw new UnsupportedOperationException("addX configuration methods only supported for HS_AUTH or BRIDGE_LINE type");
}
}
private void invokeBridgeAdd(Object[] args) {
if(args.length >= 2 && (args[0] instanceof IPv4Address) && (args[1] instanceof Integer)) {
if(args.length == 2) {
bridges.add(new TorConfigBridgeLine((IPv4Address)args[0], (Integer)args[1], null));
return;
} else if(args.length == 3 && (args[2] instanceof HexDigest)) {
bridges.add(new TorConfigBridgeLine((IPv4Address) args[0], (Integer) args[1], (HexDigest) args[2]));
return;
}
}
throw new IllegalArgumentException();
}
private void invokeHSAuthAdd(String name, Object[] args) {
if(!(args.length == 2 && (args[0] instanceof String) && (args[1] instanceof String))) {
throw new IllegalArgumentException();
}
final TorConfigHSAuth hsAuth = getHSAuth(name);
hsAuth.add((String)args[0], (String)args[1]);
}
private TorConfigHSAuth getHSAuth(String keyName) {
if(!configValues.containsKey(keyName)) {
configValues.put(keyName, new TorConfigHSAuth());
}
return (TorConfigHSAuth) configValues.get(keyName);
}
private Object getVariableValue(String varName) {
if(configValues.containsKey(varName)) {
return configValues.get(varName);
} else {
return getDefaultVariableValue(varName);
}
}
private Object getDefaultVariableValue(String varName) {
final String defaultValue = getDefaultValueString(varName);
final ConfigVarType type = getVariableType(varName);
if(defaultValue == null || type == null) {
return null;
}
return parser.parseValue(defaultValue, type);
}
private String getDefaultValueString(String varName) {
final ConfigVar var = getAnnotationForVariable(varName);
if(var == null) {
return null;
} else {
return var.defaultValue();
}
}
private ConfigVarType getVariableType(String varName) {
if("Bridge".equals(varName)) {
return ConfigVarType.BRIDGE_LINE;
}
final ConfigVar var = getAnnotationForVariable(varName);
if(var == null) {
return null;
} else {
return var.type();
}
}
private String getVariableNameForMethod(Method method) {
final String methodName = method.getName();
if(methodName.startsWith("get") || methodName.startsWith("set") || methodName.startsWith("add")) {
return methodName.substring(3);
}
throw new IllegalArgumentException();
}
private ConfigVar getAnnotationForVariable(String varName) {
final String getName = "get"+ varName;
for(Method m: TorConfig.class.getDeclaredMethods()) {
if(getName.equals(m.getName())) {
return m.getAnnotation(TorConfig.ConfigVar.class);
}
}
return null;
}
}

View File

@@ -24,6 +24,7 @@ import com.subgraph.orchid.ConnectionFailedException;
import com.subgraph.orchid.ConnectionHandshakeException;
import com.subgraph.orchid.ConnectionTimeoutException;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.circuits.TorInitializationTracker;
import com.subgraph.orchid.dashboard.DashboardRenderable;
@@ -65,7 +66,7 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
private final ConcurrentMap<Router, Future<ConnectionImpl>> activeConnections = new ConcurrentHashMap<Router, Future<ConnectionImpl>>();
private final ConnectionSocketFactory factory = new ConnectionSocketFactory();
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService scheduledExecutor = Threading.newSingleThreadScheduledPool("Connection");
private final TorConfig config;
private final TorInitializationTracker initializationTracker;
@@ -94,6 +95,9 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
logger.warning("Exception closing connection: "+ e.getCause());
}
} else {
// FIXME this doesn't close the socket, so the connection task lingers
// A proper fix would require maintaining pending connections in a separate
// collection.
f.cancel(true);
}
}
@@ -101,11 +105,18 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
scheduledExecutor.shutdownNow();
}
@Override
public boolean isClosed() {
return isClosed;
}
public Connection getConnectionTo(Router router, boolean isDirectoryConnection) throws InterruptedException, ConnectionTimeoutException, ConnectionFailedException, ConnectionHandshakeException {
if(isClosed) {
throw new IllegalStateException("ConnectionCache has been closed");
// I2P prevent logs at shutdown
//throw new IllegalStateException("ConnectionCache has been closed");
throw new ConnectionFailedException("ConnectionCache has been closed");
}
logger.fine("Get connection to "+ router.getAddress() + " "+ router.getOnionPort() + " " + router.getNickname());
logger.fine("Connecting to [" + router.getNickname() + " (" + router.getAddress() + ":" + router.getOnionPort() +")]");
while(true) {
Future<ConnectionImpl> f = getFutureFor(router, isDirectoryConnection);
try {
@@ -157,23 +168,37 @@ public class ConnectionCacheImpl implements ConnectionCache, DashboardRenderable
if((flags & DASHBOARD_CONNECTIONS) == 0) {
return;
}
if (!getActiveConnections().isEmpty()) // not working.. conncache section should be displayed only when active circuits
writer.print("<tr><td>\n");
else
writer.print("<tr hidden><td>\n");
printDashboardBanner(writer, flags);
for(Connection c: getActiveConnections()) {
if(!c.isClosed()) {
renderer.renderComponent(writer, flags, c);
}
}
writer.println();
// close connection cache table
writer.print("</table>\n</td></tr>\n");
}
private void printDashboardBanner(PrintWriter writer, int flags) {
final boolean verbose = (flags & DASHBOARD_CONNECTIONS_VERBOSE) != 0;
if(verbose) {
writer.println("[Connection Cache (verbose)]");
String useMds = String.valueOf(this.config.getUseMicrodescriptors());
writer.print("<hr>\n<table id=\"conncache\" width=\"100%\">\n");
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
writer.print("<tr><th colspan=\"3\" align=\"left\">Connection Cache</th></tr>\n<tr class=\"subtitle\">" +
"<th align=\"left\" width=\"33%\">Guard Node <span title=\"Circuits available for immediate use\">(Circuits)</span></th>" +
"<th align=\"left\" width=\"34%\">Idle</th>" +
"<th align=\"left\" width=\"33%\" title=\"Estimated node bandwidth\">Bandwidth</th>");
} else {
writer.println("[Connection Cache]");
writer.print("<tr><th colspan=\"4\" align=\"left\">Connection Cache</th></tr>\n<tr class=\"subtitle\">" +
"<th align=\"left\" width=\"25%\">Guard Node <span title=\"Circuits available for immediate use\">(Circuits)</span></th>" +
"<th align=\"left\" width=\"25%\">Idle</th>" +
"<th align=\"left\" width=\"25%\" title=\"Observed node bandwidth\">Bandwidth</th>" +
"<th align=\"left\" width=\"25%\">Uptime</th>");
}
writer.println();
writer.print("</tr>\n");
}
List<Connection> getActiveConnections() {

View File

@@ -21,8 +21,7 @@ import com.subgraph.orchid.ConnectionIOException;
public class ConnectionHandshakeV3 extends ConnectionHandshake {
private X509Certificate linkCertificate;
private X509Certificate identityCertificate;
private X509Certificate linkCertificate, identityCertificate, signing, tls, cross;
ConnectionHandshakeV3(ConnectionImpl connection, SSLSocket socket) {
super(connection, socket);
@@ -40,12 +39,15 @@ public class ConnectionHandshakeV3 extends ConnectionHandshake {
void recvCerts() throws ConnectionHandshakeException {
final Cell cell = expectCell(Cell.CERTS);
final int ncerts = cell.getByte();
if(ncerts != 2) {
throw new ConnectionHandshakeException("Expecting 2 certificates and got "+ ncerts);
if (ncerts != 2 && ncerts != 5) {
throw new ConnectionHandshakeException("Expecting 2 or 5 certificates and got "+ ncerts);
}
linkCertificate = null;
identityCertificate = null;
signing = null;
tls = null;
cross = null;
for(int i = 0; i < ncerts; i++) {
int type = cell.getByte();
@@ -53,6 +55,12 @@ public class ConnectionHandshakeV3 extends ConnectionHandshake {
linkCertificate = testAndReadCertificate(cell, linkCertificate, "Link (type = 1)");
} else if(type == 2) {
identityCertificate = testAndReadCertificate(cell, identityCertificate, "Identity (type = 2)");
} else if(type == 4) {
signing = testAndReadCertificate(cell, signing, "Ed25519 signing key (type = 4)");
} else if(type == 5) {
tls = testAndReadCertificate(cell, tls, "TLS Link certificate (type = 5)");
} else if(type == 7) {
cross = testAndReadCertificate(cell, cross, "RSA Identity cross verification (type = 7)");
} else {
throw new ConnectionHandshakeException("Unexpected certificate type = "+ type + " in CERTS cell");
}

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -27,6 +28,7 @@ import com.subgraph.orchid.ConnectionHandshakeException;
import com.subgraph.orchid.ConnectionIOException;
import com.subgraph.orchid.ConnectionTimeoutException;
import com.subgraph.orchid.Router;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorException;
@@ -61,21 +63,27 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
private boolean isConnected;
private volatile boolean isClosed;
private final Thread readCellsThread;
private final Object connectLock = new Object();
private final ReentrantLock connectLock = Threading.lock("connect");
private final ReentrantLock circuitsLock = Threading.lock("circuits");
private final ReentrantLock outputLock = Threading.lock("output");
private final AtomicLong lastActivity = new AtomicLong();
private static final AtomicLong num = new AtomicLong();
public ConnectionImpl(TorConfig config, SSLSocket socket, Router router, TorInitializationTracker tracker, boolean isDirectoryConnection) {
this.config = config;
this.socket = socket;
this.router = router;
this.circuitMap = new HashMap<Integer, Circuit>();
// TODO use thread pool from Threading?
this.readCellsThread = new Thread(createReadCellsRunnable());
this.readCellsThread.setDaemon(true);
this.readCellsThread.setName("ReadCells-" + num.getAndIncrement());
this.connectionControlCells = new LinkedBlockingQueue<Cell>();
this.initializationTracker = tracker;
this.isDirectoryConnection = isDirectoryConnection;
initializeCurrentCircuitId();
this.readCellsThread.setName("ConnectionImpl-" + num.getAndIncrement());
}
private void initializeCurrentCircuitId() {
@@ -92,13 +100,16 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
}
public int bindCircuit(Circuit circuit) {
synchronized(circuitMap) {
circuitsLock.lock();
try {
while(circuitMap.containsKey(currentId))
incrementNextId();
final int id = currentId;
incrementNextId();
circuitMap.put(id, circuit);
return id;
} finally {
circuitsLock.unlock();
}
}
@@ -109,7 +120,8 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
}
void connect() throws ConnectionFailedException, ConnectionTimeoutException, ConnectionHandshakeException {
synchronized (connectLock) {
connectLock.lock();
try {
if(isConnected) {
return;
}
@@ -128,6 +140,8 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
throw new ConnectionFailedException(e.getMessage());
}
isConnected = true;
} finally {
connectLock.unlock();
}
}
@@ -171,14 +185,17 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
throw new ConnectionIOException("Cannot send cell because connection is not connected");
}
updateLastActivity();
synchronized(output) {
outputLock.lock();
try {
try {
output.write(cell.getCellBytes());
} catch (IOException e) {
logger.fine("IOException writing cell to connection "+ e.getMessage());
logger.fine("IOException writing cell to connection: " + e.getMessage());
closeSocket();
throw new ConnectionIOException(e.getClass().getName() + " : "+ e.getMessage());
}
} finally {
outputLock.unlock();
}
}
@@ -276,32 +293,45 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
}
private void processRelayCell(Cell cell) {
synchronized(circuitMap) {
final Circuit circuit = circuitMap.get(cell.getCircuitId());
Circuit circuit;
circuitsLock.lock();
try {
circuit = circuitMap.get(cell.getCircuitId());
if(circuit == null) {
logger.warning("Could not deliver relay cell for circuit id = "+ cell.getCircuitId() +" on connection "+ this +". Circuit not found");
logger.warning("Could not deliver relay cell for CircuitID=" + cell.getCircuitId() + " on connection " + this + ". Circuit not found");
return;
}
circuit.deliverRelayCell(cell);
} finally {
circuitsLock.unlock();
}
circuit.deliverRelayCell(cell);
}
private void processControlCell(Cell cell) {
synchronized(circuitMap) {
final Circuit circuit = circuitMap.get(cell.getCircuitId());
if(circuit != null) {
circuit.deliverControlCell(cell);
}
Circuit circuit;
circuitsLock.lock();
try {
circuit = circuitMap.get(cell.getCircuitId());
} finally {
circuitsLock.unlock();
}
if(circuit != null) {
circuit.deliverControlCell(cell);
}
}
void idleCloseCheck() {
synchronized (circuitMap) {
circuitsLock.lock();
try {
final boolean needClose = (!isClosed && circuitMap.isEmpty() && getIdleMilliseconds() > CONNECTION_IDLE_TIMEOUT);
if(needClose) {
logger.fine("Closing connection to "+ this +" on idle timeout");
closeSocket();
}
}
} finally {
circuitsLock.unlock();
}
}
@@ -317,26 +347,96 @@ public class ConnectionImpl implements Connection, DashboardRenderable {
}
public void removeCircuit(Circuit circuit) {
synchronized(circuitMap) {
circuitsLock.lock();
try {
circuitMap.remove(circuit.getCircuitId());
} finally {
circuitsLock.unlock();
}
}
public String toString() {
return "!" + router.getNickname() + "!";
return "[" + router.getNickname() + "]";
}
public void dashboardRender(DashboardRenderer renderer, PrintWriter writer, int flags) throws IOException {
final int circuitCount;
synchronized (circuitMap) {
circuitsLock.lock();
try {
circuitCount = circuitMap.size();
} finally {
circuitsLock.unlock();
}
if(circuitCount == 0 && (flags & DASHBOARD_CONNECTIONS_VERBOSE) == 0) {
return;
}
writer.print(" [Connection router="+ router.getNickname());
writer.print(" circuits="+ circuitCount);
writer.print(" idle="+ (getIdleMilliseconds()/1000) + "s");
writer.println("]");
if (circuitCount > 0) {
String idHash = router.getIdentityHash().toString().toUpperCase();
writer.print("<tr><td><span class=\"nodecontainer\"><span class=\"hidden\">[</span><span class=\"node\" onclick=\"copyText();\">" +
"<span class=\"flag\" data-country=\"");
if (router.getCountryName() != null)
writer.print(router.getCountryName() + " (" + router.getAddress() + ")");
else
writer.print(router.getAddress());
writer.print("\"><img height=\"11\" width=\"16\" src=\"/flags.jsp?c=" +
router.getCountryCode().toLowerCase().replace("--", "a0") + "\"></span><span class=\"nickname\"");
if (router.getPlatform() != null)
writer.print(" data-ipv4=\"" + router.getPlatform().replace("Tor ", "").replace(" on ", " / ") + "\"");
writer.print(">");
writer.print("<a class=\"script\" href=\"https://metrics.torproject.org/rs.html#search/" + idHash + "\" target=\"_blank\">" +
router.getNickname() + "</a>" +
// <noscript> alternative lookup
"<noscript>" +
"<a href=\"https://torstatus.blutmagie.de/router_detail.php?FP=" + idHash + "\" target=\"_blank\">" +
router.getNickname() + "</a>" +
"</noscript>" +
"</span><span class=\"hidden\"> (</span><b>" + router.getAddress() + "</b>" +
"<span class=\"hidden\">)</span></span><span class=\"hidden\">]</span></span> <span class=\"circuitcount\">" + circuitCount + "</span></td>");
writer.print("<td><span class=\"nowrap\">" + (getIdleMilliseconds() / 1000) + "s</span></td>");
writer.print("<td>");
String useMds = String.valueOf(this.config.getUseMicrodescriptors());
long bw = router.getObservedBandwidth();
long bwEst = router.getEstimatedBandwidth();
writer.print("<span class=\"nowrap\">");
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
if (bwEst > 0 && bwEst < 2048)
writer.print(bwEst + " KB/s");
else if (bwEst >= 2048)
writer.print((bwEst / 1024) + " MB/s");
else
writer.print("unknown");
} else {
if (bw > 0 && bw < 1048576)
writer.print((bw / 1024) + " KB/s");
else if (bw >= 1048576)
writer.print(((bw / 1024) / 1024) + " MB/s");
else
writer.print("unknown");
}
writer.print("</span></td>");
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true")) {
writer.print("<!-- useMicroDescriptors=TRUE, not showing uptime column -->");
} else {
writer.print("<td><span class=\"nowrap\">");
int uptime = router.getUptime();
if (uptime > 172800)
writer.print((((uptime / 60) / 60) / 24) + " days");
else if (uptime > 7200)
writer.print(((uptime / 60) / 60) + " hours");
else if (uptime > 120)
writer.print(uptime / 60 + " mins");
else if (uptime > 0)
writer.print(uptime + " secs");
else
writer.print("unknown");
writer.print("</span></td>");
}
writer.print("</tr>\n");
}
}
}

View File

@@ -12,6 +12,8 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import net.i2p.util.I2PSSLSocketFactory;
import com.subgraph.orchid.TorException;
public class ConnectionSocketFactory {
@@ -66,7 +68,9 @@ public class ConnectionSocketFactory {
SSLSocket createSocket() {
try {
final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
socket.setEnabledCipherSuites(MANDATORY_CIPHERS);
// Use I2P lib to blacklist weak protocols and ciphers
// We do not detect the the "Fixed Ciphersuite List", see tor-spec section 2.1
I2PSSLSocketFactory.setProtocolsAndCiphers(socket);
socket.setUseClientMode(true);
return socket;
} catch (IOException e) {

View File

@@ -7,9 +7,9 @@ import java.net.Socket;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.data.IPv4Address;
import com.subgraph.orchid.misc.GuardedBy;
@@ -37,7 +37,7 @@ public class Dashboard implements DashboardRenderable, DashboardRenderer {
public Dashboard() {
renderables = new CopyOnWriteArrayList<DashboardRenderable>();
renderables.add(this);
executor = Executors.newCachedThreadPool();
executor = Threading.newPool("Dashboard worker");
listeningPort = chooseListeningPort();
}

View File

@@ -3,7 +3,6 @@ package com.subgraph.orchid.directory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -12,6 +11,7 @@ import java.util.logging.Logger;
import com.subgraph.orchid.Descriptor;
import com.subgraph.orchid.DirectoryStore;
import com.subgraph.orchid.DirectoryStore.CacheFile;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.directory.parsing.DocumentParser;
import com.subgraph.orchid.directory.parsing.DocumentParsingResult;
@@ -23,7 +23,9 @@ public abstract class DescriptorCache <T extends Descriptor> {
private final DescriptorCacheData<T> data;
private final DirectoryStore store;
private final ScheduledExecutorService rebuildExecutor = Executors.newScheduledThreadPool(1);
private final ScheduledExecutorService rebuildExecutor =
Threading.newScheduledPool("DescriptorCache rebuild worker");
private final CacheFile cacheFile;
private final CacheFile journalFile;

View File

@@ -1,5 +1,5 @@
package com.subgraph.orchid.directory;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
@@ -129,14 +129,45 @@ public class DirectoryImpl implements Directory {
}
boolean useMicrodescriptors = config.getUseMicrodescriptors() != AutoBoolValue.FALSE;
last = System.currentTimeMillis();
logger.info("Loading certificates");
loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
// shouldn't have to do this, but this is the easiest way to make it work
File f0 = new File(config.getDataDirectory(), CacheFile.CERTIFICATES.getFilename());
long m0 = f0.lastModified();
if (m0 > 0 && (last - m0) > 60*24*60*60*1000L) {
logger.info("Certificates not cached or too old");
} else {
logger.info("Loading certificates");
loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
}
logElapsed();
logger.info("Loading consensus");
loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
//loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
CacheFile consensusCacheFile = useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS;
File consensusFile = new File(config.getDataDirectory(), consensusCacheFile.getFilename());
long consensusDate = consensusFile.lastModified();
long now = System.currentTimeMillis();
long age = now - consensusDate;
ByteBuffer consensus;
if (age < 24*60*60*1000L) {
logger.info("Loading consensus");
consensus = store.loadCacheFile(consensusCacheFile);
} else {
logger.info("Consensus not cached or too old");
consensus = ByteBuffer.allocate(0);
}
loadConsensus(consensus);
logElapsed();
// shouldn't have to do this, but this is the easiest way to make it work
File f1 = new File(config.getDataDirectory(),
(useMicrodescriptors ? CacheFile.MICRODESCRIPTOR_CACHE : CacheFile.DESCRIPTOR_CACHE).getFilename());
File f2 = new File(config.getDataDirectory(),
(useMicrodescriptors ? CacheFile.MICRODESCRIPTOR_JOURNAL : CacheFile.DESCRIPTOR_JOURNAL).getFilename());
long m1 = f1.lastModified();
long m2 = f2.lastModified();
if (m1 > 0 && (now - m1) > 2*24*60*60*1000L)
f1.delete();
if (m2 > 0 && (now - m2) > 2*24*60*60*1000L)
f2.delete();
if(!useMicrodescriptors) {
logger.info("Loading descriptors");
basicDescriptorCache.initialLoad();

View File

@@ -67,6 +67,18 @@ public class DirectoryServerImpl extends RouterImpl implements DirectoryServer {
return v3Ident;
}
/**
* https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
*/
public KeyCertificate getCertificateByAuthority(HexDigest fingerprint){
for(KeyCertificate kc: getCertificates()) {
if(kc.getAuthorityFingerprint().equals(fingerprint)) {
return kc;
}
}
return null;
}
public KeyCertificate getCertificateByFingerprint(HexDigest fingerprint) {
for(KeyCertificate kc: getCertificates()) {
if(kc.getAuthoritySigningKey().getFingerprint().equals(fingerprint)) {

View File

@@ -25,6 +25,7 @@ public class RouterImpl implements Router {
private Descriptor descriptor;
private volatile String cachedCountryCode;
private volatile String cachedCountryName;
protected RouterImpl(Directory directory, RouterStatus status) {
this.directory = directory;
@@ -38,6 +39,7 @@ public class RouterImpl implements Router {
throw new TorException("Identity hash does not match status update");
this.status = status;
this.cachedCountryCode = null;
this.cachedCountryName = null;
this.descriptor = null;
refreshDescriptor();
}
@@ -147,6 +149,15 @@ public class RouterImpl implements Router {
}
}
public String getPlatform() {
final RouterDescriptor rd = downcastDescriptor();
if (rd != null) {
return rd.getPlatform();
} else {
return null;
}
}
public String getNickname() {
return status.getNickname();
}
@@ -173,6 +184,15 @@ public class RouterImpl implements Router {
}
}
public int getUptime() {
final RouterDescriptor rd = downcastDescriptor();
if (rd != null) {
return rd.getUptime();
} else {
return 0;
}
}
public boolean hasBandwidth() {
return status.hasBandwidth();
}
@@ -237,7 +257,7 @@ public class RouterImpl implements Router {
}
public String toString() {
return "Router["+ getNickname() +" ("+getAddress() +":"+ getOnionPort() +")]";
return "[" + getNickname() + " (" + getAddress() +":" + getOnionPort() + ")]";
}
public String getCountryCode() {
@@ -249,6 +269,15 @@ public class RouterImpl implements Router {
return cc;
}
public String getCountryName() {
String cn = cachedCountryName;
if (cn == null) {
cn = CountryCodeService.getInstance().getCountryNameForAddress(getAddress());
cachedCountryName = cn;
}
return cn;
}
private RouterDescriptor downcastDescriptor() {
refreshDescriptor();
if(descriptor instanceof RouterDescriptor) {

View File

@@ -14,20 +14,22 @@ import com.subgraph.orchid.directory.parsing.DocumentParsingHandler;
/*
* This class contains the hardcoded 'bootstrap' directory authority
* server information.
* https://github.com/torproject/tor/blob/master/src/app/config/auth_dirs.inc
*/
public class TrustedAuthorities {
private final static String[] dirServers = {
"authority moria1 orport=9101 no-v2 v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
"authority moria1 orport=9101 v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
"authority tor26 v1 orport=443 v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D",
"authority dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
"authority Tonga orport=443 bridge no-v2 82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D",
"authority turtles orport=9090 no-v2 v3ident=27B6B5996C426270A5C95488AA5BCEB6BCC86956 76.73.17.194:9030 F397 038A DC51 3361 35E7 B80B D99C A384 4360 292B",
"authority dannenberg orport=443 no-v2 v3ident=585769C78764D58426B8B52B6651A5A71137189A 193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
"authority urras orport=80 no-v2 v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417",
"authority maatuska orport=80 no-v2 v3ident=49015F787433103580E3B66A1707A00E60F2D15B 171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
"authority Faravahar orport=443 no-v2 v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 154.35.32.5:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC",
"authority gabelmoo orport=443 no-v2 v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 212.112.245.170:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
"authority dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 45.66.33.45:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
"authority longclaw orport=443 v3ident=23D15D965BC35114467363C165C4F724B64B4F66 199.58.81.140:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145",
"authority dannenberg orport=443 v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E 193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
"authority maatuska orport=80 v3ident=49015F787433103580E3B66A1707A00E60F2D15B 171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
"authority Faravahar orport=443 v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 154.35.175.225:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC",
"authority gabelmoo orport=443 v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 131.188.40.189:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
"authority bastet orport=443 v3ident=27102BC123E7AF1D4741AE047E160C91ADC76B21 204.13.164.118:80 24E2 F139 121D 4394 C54B 5BCC 368B 3B41 1857 C413",
// bridges don't work with orchid? and non-bridges listed after bridges don't work either
// "authority Bifroest orport=443 bridge 37.218.247.217:80 1D8F 3A91 C37C 5D1C 4C19 B1AD 1D0C FBE8 BF72 D8E1",
};
private final List<DirectoryServer> directoryServers = new ArrayList<DirectoryServer>();

View File

@@ -86,8 +86,8 @@ public class KeyCertificateImpl implements KeyCertificate {
}
public String toString() {
return "(Certificate: address="+ directoryAddress +":"+ directoryPort
+" fingerprint="+ fingerprint +" published="+ keyPublished +" expires="+ keyExpires +")"+
return "[Certificate: address=" + directoryAddress + ":" + directoryPort
+ " fingerprint=" + fingerprint + " published=" + keyPublished + " expires=" + keyExpires + "]" +
"\nident="+ identityKey +" sign="+ signingKey;
}
}

View File

@@ -262,7 +262,9 @@ public class ConsensusDocumentImpl implements ConsensusDocument {
}
private SignatureStatus verifySignatureForTrustedAuthority(DirectoryServer trustedAuthority, DirectorySignature signature) {
final KeyCertificate certificate = trustedAuthority.getCertificateByFingerprint(signature.getSigningKeyDigest());
// https://github.com/geo-gs/Orchid/commit/22beeae1b881707491addaba6a7654e9de9f9db1
//final KeyCertificate certificate = trustedAuthority.getCertificateByFingerprint(signature.getSigningKeyDigest());
final KeyCertificate certificate = trustedAuthority.getCertificateByAuthority(trustedAuthority.getV3Identity());
if(certificate == null) {
logger.fine("Missing certificate for signing key: "+ signature.getSigningKeyDigest());
addRequiredCertificateForSignature(signature);

View File

@@ -42,8 +42,8 @@ public class RouterStatusImpl implements RouterStatus {
void setRejectedPorts(String portList) { this.exitPorts = ExitPorts.createRejectExitPorts(portList); }
public String toString() {
return "Router: ("+ nickname +" "+ identity +" "+ digest +" "+ address +" "+ routerPort +" " + directoryPort
+" "+ version +" "+ exitPorts +")";
return "Router: [" + nickname + " " + identity + " " + digest + " " + address + " " + routerPort + " " + directoryPort
+ " " + version + " " + exitPorts + "]";
}
public String getNickname() {
return nickname;

View File

@@ -6,7 +6,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
@@ -14,6 +13,7 @@ import com.subgraph.orchid.ConsensusDocument;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.DirectoryDownloader;
import com.subgraph.orchid.KeyCertificate;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorConfig.AutoBoolValue;
import com.subgraph.orchid.crypto.TorRandom;
@@ -31,7 +31,7 @@ public class DirectoryDownloadTask implements Runnable {
private final TorRandom random;
private final DescriptorProcessor descriptorProcessor;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final ExecutorService executor = Threading.newPool("DirectoryDownloadTask worker");
private volatile boolean isDownloadingCertificates;
private volatile boolean isDownloadingConsensus;

View File

@@ -80,7 +80,7 @@ public class HttpConnection {
}
public String getHost() {
if(hostname == null) {
if(hostname != null) {
return hostname;
} else {
return "(none)";

View File

@@ -24,7 +24,9 @@ public enum RouterDescriptorKeyword {
WRITE_HISTORY("write-history"),
EVENTDNS("eventdns", 1),
CACHES_EXTRA_INFO("caches-extra-info", 0),
EXTRA_INFO_DIGEST("extra-info-digest", 1),
// could now be 1 or 2
// https://github.com/torproject/torspec/commit/0f03581e748d4867a009d3d9473d61a400a3f5c1
EXTRA_INFO_DIGEST("extra-info-digest"),
HIDDEN_SERVICE_DIR("hidden-service-dir"),
PROTOCOLS("protocols"),
ALLOW_SINGLE_HOP_EXITS("allow-single-hop-exits", 0),

View File

@@ -11,6 +11,8 @@ import java.util.logging.Logger;
import com.subgraph.orchid.data.IPv4Address;
import net.i2p.I2PAppContext;
public class CountryCodeService {
private final static Logger logger = Logger.getLogger(CountryCodeService.class.getName());
private final static String DATABASE_FILENAME = "GeoIP.dat";
@@ -48,6 +50,71 @@ public class CountryCodeService {
"WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1",
"AX", "GG", "IM", "JE", "BL", "MF", "BQ", "SS", "O1" };
private static final String[] COUNTRY_NAMES = { "N/A", "Asia/Pacific Region",
"Europe", "Andorra", "United Arab Emirates", "Afghanistan",
"Antigua and Barbuda", "Anguilla", "Albania", "Armenia", "Curacao",
"Angola", "Antarctica", "Argentina", "American Samoa", "Austria",
"Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",
"Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria",
"Bahrain", "Burundi", "Benin", "Bermuda", "Brunei Darussalam",
"Bolivia", "Brazil", "Bahamas", "Bhutan", "Bouvet Island",
"Botswana", "Belarus", "Belize", "Canada",
"Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",
"Central African Republic", "Congo", "Switzerland",
"Cote D'Ivoire", "Cook Islands", "Chile", "Cameroon", "China",
"Colombia", "Costa Rica", "Cuba", "Cape Verde", "Christmas Island",
"Cyprus", "Czech Republic", "Germany", "Djibouti", "Denmark",
"Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",
"Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia",
"Finland", "Fiji", "Falkland Islands (Malvinas)",
"Micronesia, Federated States of", "Faroe Islands", "France",
"Sint Maarten (Dutch part)", "Gabon", "United Kingdom", "Grenada",
"Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",
"Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece",
"South Georgia and the South Sandwich Islands", "Guatemala",
"Guam", "Guinea-Bissau", "Guyana", "Hong Kong",
"Heard Island and McDonald Islands", "Honduras", "Croatia",
"Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",
"British Indian Ocean Territory", "Iraq",
"Iran, Islamic Republic of", "Iceland", "Italy", "Jamaica",
"Jordan", "Japan", "Kenya", "Kyrgyzstan", "Cambodia", "Kiribati",
"Comoros", "Saint Kitts and Nevis",
"Korea, Democratic People's Republic of", "Korea, Republic of",
"Kuwait", "Cayman Islands", "Kazakhstan",
"Lao People's Democratic Republic", "Lebanon", "Saint Lucia",
"Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania",
"Luxembourg", "Latvia", "Libya", "Morocco", "Monaco",
"Moldova, Republic of", "Madagascar", "Marshall Islands",
"Macedonia", "Mali", "Myanmar", "Mongolia", "Macau",
"Northern Mariana Islands", "Martinique", "Mauritania",
"Montserrat", "Malta", "Mauritius", "Maldives", "Malawi", "Mexico",
"Malaysia", "Mozambique", "Namibia", "New Caledonia", "Niger",
"Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",
"Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru",
"French Polynesia", "Papua New Guinea", "Philippines", "Pakistan",
"Poland", "Saint Pierre and Miquelon", "Pitcairn Islands",
"Puerto Rico", "Palestinian Territory", "Portugal", "Palau",
"Paraguay", "Qatar", "Reunion", "Romania", "Russian Federation",
"Rwanda", "Saudi Arabia", "Solomon Islands", "Seychelles", "Sudan",
"Sweden", "Singapore", "Saint Helena", "Slovenia",
"Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino",
"Senegal", "Somalia", "Suriname", "Sao Tome and Principe",
"El Salvador", "Syrian Arab Republic", "Swaziland",
"Turks and Caicos Islands", "Chad", "French Southern Territories",
"Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",
"Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago",
"Tuvalu", "Taiwan", "Tanzania, United Republic of", "Ukraine",
"Uganda", "United States Minor Outlying Islands", "United States",
"Uruguay", "Uzbekistan", "Holy See (Vatican City State)",
"Saint Vincent and the Grenadines", "Venezuela",
"Virgin Islands, British", "Virgin Islands, U.S.", "Vietnam",
"Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",
"Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
"Anonymous Proxy", "Satellite Provider", "Other", "Aland Islands",
"Guernsey", "Isle of Man", "Jersey", "Saint Barthelemy",
"Saint Martin", "Bonaire, Saint Eustatius and Saba", "South Sudan",
"Other" };
private final byte[] database;
public CountryCodeService() {
@@ -56,9 +123,13 @@ public class CountryCodeService {
private static byte[] loadDatabase() {
final InputStream input = openDatabaseStream();
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
final File dbFile = new File(dataDir, DATABASE_FILENAME);
if(input == null) {
logger.warning("Failed to open '"+ DATABASE_FILENAME + "' database file for country code lookups");
logger.warning("Failed to open '" + DATABASE_FILENAME + "' database file in " + dataDir + " for country code lookups");
return null;
} else {
logger.info("Loaded '" + dataDir + "/" + DATABASE_FILENAME + "' for country code lookups");
}
try {
return loadEntireStream(input);
@@ -82,7 +153,7 @@ public class CountryCodeService {
}
private static InputStream tryFilesystemOpen() {
final File dataDir = new File(System.getProperty("user.dir"), "data");
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
final File dbFile = new File(dataDir, DATABASE_FILENAME);
if(!dbFile.canRead()) {
return null;
@@ -95,7 +166,8 @@ public class CountryCodeService {
}
private static InputStream tryResourceOpen() {
return CountryCodeService.class.getResourceAsStream("/data/"+ DATABASE_FILENAME);
final File dataDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins/orchid/geoip");
return CountryCodeService.class.getResourceAsStream(dataDir + "/" + DATABASE_FILENAME);
}
private static byte[] loadEntireStream(InputStream input) throws IOException {
@@ -119,6 +191,10 @@ public class CountryCodeService {
return COUNTRY_CODES[seekCountry(address)];
}
public String getCountryNameForAddress(IPv4Address address) {
return COUNTRY_NAMES[seekCountry(address)];
}
private int seekCountry(IPv4Address address) {
if(database == null) {
return 0;
@@ -140,7 +216,7 @@ public class CountryCodeService {
if(xx >= COUNTRY_BEGIN) {
final int idx = xx - COUNTRY_BEGIN;
if(idx < 0 || idx > COUNTRY_CODES.length) {
logger.warning("Invalid index calculated looking up country code record for ("+ address +") idx = "+ idx);
logger.warning("Invalid index calculated looking up country code record for: [" + address + "] idx = " + idx);
return 0;
} else {
return idx;
@@ -150,7 +226,7 @@ public class CountryCodeService {
}
}
logger.warning("No record found looking up country code record for ("+ address + ")");
logger.warning("No country code record found for: " + address);
return 0;
}

View File

@@ -26,10 +26,16 @@ public class OrchidSocketFactory extends SocketFactory {
this.exceptionOnLocalBind = exceptionOnLocalBind;
}
@Override
@Override
public Socket createSocket() throws IOException {
return createSocketInstance();
}
@Override
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
return createOrchidSocket(host, port);
final Socket s = createSocketInstance();
return connectOrchidSocket(s, host, port);
}
@Override
@@ -43,7 +49,8 @@ public class OrchidSocketFactory extends SocketFactory {
@Override
public Socket createSocket(InetAddress address, int port) throws IOException {
return createOrchidSocket(address.getHostAddress(), port);
final Socket s = createSocketInstance();
return connectOrchidSocket(s, address.getHostAddress(), port);
}
@Override
@@ -55,8 +62,7 @@ public class OrchidSocketFactory extends SocketFactory {
return createSocket(address, port);
}
private Socket createOrchidSocket(String host, int port) throws IOException {
final Socket s = createSocketInstance();
private Socket connectOrchidSocket(Socket s, String host, int port) throws IOException {
final SocketAddress endpoint = InetSocketAddress.createUnresolved(host, port);
s.connect(endpoint);
return s;

View File

@@ -1,5 +1,10 @@
package com.subgraph.orchid.sockets;
import com.subgraph.orchid.OpenFailedException;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorClient;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
@@ -13,16 +18,12 @@ import java.net.SocketImpl;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeoutException;
import com.subgraph.orchid.OpenFailedException;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.TorClient;
import java.util.concurrent.locks.Lock;
public class OrchidSocketImpl extends SocketImpl {
private final TorClient torClient;
private final Object streamLock = new Object();
private Lock streamLock = Threading.lock("stream");
private Stream stream;
OrchidSocketImpl(TorClient torClient) {
@@ -31,7 +32,7 @@ public class OrchidSocketImpl extends SocketImpl {
}
public void setOption(int optID, Object value) throws SocketException {
throw new UnsupportedOperationException();
// Ignored.
}
public Object getOption(int optID) throws SocketException {
@@ -53,13 +54,16 @@ public class OrchidSocketImpl extends SocketImpl {
@Override
protected void connect(String host, int port) throws IOException {
throw new UnsupportedOperationException();
SocketAddress endpoint =
InetSocketAddress.createUnresolved(host, port);
connect(endpoint, 0);
}
@Override
protected void connect(InetAddress address, int port) throws IOException {
throw new UnsupportedOperationException();
SocketAddress endpoint =
InetSocketAddress.createUnresolved(address.getHostAddress(), port);
connect(endpoint, 0);
}
@Override
@@ -82,20 +86,36 @@ public class OrchidSocketImpl extends SocketImpl {
}
private void doConnect(String host, int port) throws IOException {
synchronized(streamLock) {
if(stream != null) {
throw new SocketException("Already connected");
}
try {
stream = torClient.openExitStreamTo(host, port);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SocketException("connect() interrupted");
} catch (TimeoutException e) {
throw new SocketTimeoutException();
} catch (OpenFailedException e) {
throw new ConnectException(e.getMessage());
}
Stream stream;
// Try to avoid holding the stream lock here whilst calling into torclient to avoid accidental inversions.
streamLock.lock();
stream = this.stream;
streamLock.unlock();
if (stream != null)
throw new SocketException("Already connected");
try {
stream = torClient.openExitStreamTo(host, port);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SocketException("connect() interrupted");
} catch (TimeoutException e) {
throw new SocketTimeoutException();
} catch (OpenFailedException e) {
throw new ConnectException(e.getMessage());
}
streamLock.lock();
if (this.stream != null) {
// Raced with another concurrent call.
streamLock.unlock();
stream.close();
} else {
this.stream = stream;
streamLock.unlock();
}
}
@@ -114,44 +134,41 @@ public class OrchidSocketImpl extends SocketImpl {
throw new UnsupportedOperationException();
}
private Stream getStream() throws IOException {
streamLock.lock();
try {
if (stream == null)
throw new IOException("Not connected");
return stream;
} finally {
streamLock.unlock();
}
}
@Override
protected InputStream getInputStream() throws IOException {
synchronized (streamLock) {
if(stream == null) {
throw new IOException("Not connected");
}
return stream.getInputStream();
}
return getStream().getInputStream();
}
@Override
protected OutputStream getOutputStream() throws IOException {
synchronized (streamLock) {
if(stream == null) {
throw new IOException("Not connected");
}
return stream.getOutputStream();
}
return getStream().getOutputStream();
}
@Override
protected int available() throws IOException {
synchronized(streamLock) {
if(stream == null) {
throw new IOException("Not connected");
}
return stream.getInputStream().available();
}
return getStream().getInputStream().available();
}
@Override
protected void close() throws IOException {
synchronized (streamLock) {
if(stream != null) {
stream.close();
stream = null;
}
}
Stream toClose;
streamLock.lock();
toClose = this.stream;
this.stream = null;
streamLock.unlock();
if (toClose != null)
toClose.close();
}
@Override
@@ -160,10 +177,10 @@ public class OrchidSocketImpl extends SocketImpl {
}
protected void shutdownInput() throws IOException {
//throw new IOException("Method not implemented!");
}
//throw new IOException("Method not implemented!");
}
protected void shutdownOutput() throws IOException {
//throw new IOException("Method not implemented!");
}
//throw new IOException("Method not implemented!");
}
}

View File

@@ -13,6 +13,7 @@ import java.util.logging.Logger;
import com.subgraph.orchid.CircuitManager;
import com.subgraph.orchid.SocksPortListener;
import com.subgraph.orchid.Threading;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.TorException;
@@ -28,7 +29,7 @@ public class SocksPortListenerImpl implements SocksPortListener {
public SocksPortListenerImpl(TorConfig config, CircuitManager circuitManager) {
this.config = config;
this.circuitManager = circuitManager;
executor = Executors.newCachedThreadPool();
executor = Threading.newPool("Socks");
}
public void addListeningPort(int port) {

View File

@@ -1,309 +0,0 @@
package com.subgraph.orchid.xmlrpc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientException;
import org.apache.xmlrpc.client.XmlRpcHttpClientConfig;
import org.apache.xmlrpc.client.XmlRpcHttpTransport;
import org.apache.xmlrpc.client.XmlRpcHttpTransportException;
import org.apache.xmlrpc.client.XmlRpcLiteHttpTransport;
import org.apache.xmlrpc.common.XmlRpcStreamRequestConfig;
import org.apache.xmlrpc.util.HttpUtil;
import org.apache.xmlrpc.util.LimitedInputStream;
import org.xml.sax.SAXException;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.sockets.AndroidSSLSocketFactory;
public class OrchidXmlRpcTransport extends XmlRpcHttpTransport {
private final static Logger logger = Logger.getLogger(OrchidXmlRpcTransport.class.getName());
private final SocketFactory socketFactory;
private final SSLContext sslContext;
private SSLSocketFactory sslSocketFactory;
public OrchidXmlRpcTransport(XmlRpcClient pClient, SocketFactory socketFactory, SSLContext sslContext) {
super(pClient, userAgent);
this.socketFactory = socketFactory;
this.sslContext = sslContext;
}
public synchronized SSLSocketFactory getSSLSocketFactory() {
if(sslSocketFactory == null) {
sslSocketFactory = createSSLSocketFactory();
}
return sslSocketFactory;
}
private SSLSocketFactory createSSLSocketFactory() {
if(Tor.isAndroidRuntime()) {
return createAndroidSSLSocketFactory();
}
if(sslContext == null) {
return (SSLSocketFactory) SSLSocketFactory.getDefault();
} else {
return sslContext.getSocketFactory();
}
}
private SSLSocketFactory createAndroidSSLSocketFactory() {
if(sslContext == null) {
try {
return new AndroidSSLSocketFactory();
} catch (NoSuchAlgorithmException e) {
logger.severe("Failed to create default ssl context");
System.exit(1);
return null;
}
} else {
return new AndroidSSLSocketFactory(sslContext);
}
}
protected Socket newSocket(boolean pSSL, String pHostName, int pPort) throws UnknownHostException, IOException {
final Socket s = socketFactory.createSocket(pHostName, pPort);
if(pSSL) {
return getSSLSocketFactory().createSocket(s, pHostName, pPort, true);
} else {
return s;
}
}
private static final String userAgent = USER_AGENT + " (Lite HTTP Transport)";
private boolean ssl;
private String hostname;
private String host;
private int port;
private String uri;
private Socket socket;
private OutputStream output;
private InputStream input;
private final Map<String, Object> headers = new HashMap<String, Object>();
private boolean responseGzipCompressed = false;
private XmlRpcHttpClientConfig config;
public Object sendRequest(XmlRpcRequest pRequest) throws XmlRpcException {
config = (XmlRpcHttpClientConfig) pRequest.getConfig();
URL url = config.getServerURL();
ssl = "https".equals(url.getProtocol());
hostname = url.getHost();
int p = url.getPort();
port = p < 1 ? 80 : p;
String u = url.getFile();
uri = (u == null || "".equals(u)) ? "/" : u;
host = port == 80 ? hostname : hostname + ":" + port;
headers.put("Host", host);
return super.sendRequest(pRequest);
}
protected void setRequestHeader(String pHeader, String pValue) {
Object value = headers.get(pHeader);
if (value == null) {
headers.put(pHeader, pValue);
} else {
List<Object> list;
if (value instanceof String) {
list = new ArrayList<Object>();
list.add((String)value);
headers.put(pHeader, list);
} else {
list = (List<Object>) value;
}
list.add(pValue);
}
}
protected void close() throws XmlRpcClientException {
IOException e = null;
if (input != null) {
try {
input.close();
} catch (IOException ex) {
e = ex;
}
}
if (output != null) {
try {
output.close();
} catch (IOException ex) {
if (e != null) {
e = ex;
}
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException ex) {
if (e != null) {
e = ex;
}
}
}
if (e != null) {
throw new XmlRpcClientException("Failed to close connection: " + e.getMessage(), e);
}
}
private OutputStream getOutputStream() throws XmlRpcException {
try {
final int retries = 3;
final int delayMillis = 100;
for (int tries = 0; ; tries++) {
try {
socket = newSocket(ssl, hostname, port);
output = new BufferedOutputStream(socket.getOutputStream()){
/** Closing the output stream would close the whole socket, which we don't want,
* because the don't want until the request is processed completely.
* A close will later occur within
* {@link XmlRpcLiteHttpTransport#close()}.
*/
public void close() throws IOException {
flush();
if(!(socket instanceof SSLSocket)) {
socket.shutdownOutput();
}
}
};
break;
} catch (ConnectException e) {
if (tries >= retries) {
throw new XmlRpcException("Failed to connect to "
+ hostname + ":" + port + ": " + e.getMessage(), e);
} else {
try {
Thread.sleep(delayMillis);
} catch (InterruptedException ignore) {
}
}
}
}
sendRequestHeaders(output);
return output;
} catch (IOException e) {
throw new XmlRpcException("Failed to open connection to "
+ hostname + ":" + port + ": " + e.getMessage(), e);
}
}
private byte[] toHTTPBytes(String pValue) throws UnsupportedEncodingException {
return pValue.getBytes("US-ASCII");
}
private void sendHeader(OutputStream pOut, String pKey, String pValue) throws IOException {
pOut.write(toHTTPBytes(pKey + ": " + pValue + "\r\n"));
}
private void sendRequestHeaders(OutputStream pOut) throws IOException {
pOut.write(("POST " + uri + " HTTP/1.0\r\n").getBytes("US-ASCII"));
for (Iterator iter = headers.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
sendHeader(pOut, key, (String) value);
} else {
List list = (List) value;
for (int i = 0; i < list.size(); i++) {
sendHeader(pOut, key, (String) list.get(i));
}
}
}
pOut.write(toHTTPBytes("\r\n"));
}
protected boolean isResponseGzipCompressed(XmlRpcStreamRequestConfig pConfig) {
return responseGzipCompressed;
}
protected InputStream getInputStream() throws XmlRpcException {
final byte[] buffer = new byte[2048];
try {
// If reply timeout specified, set the socket timeout accordingly
if (config.getReplyTimeout() != 0)
socket.setSoTimeout(config.getReplyTimeout());
input = new BufferedInputStream(socket.getInputStream());
// start reading server response headers
String line = HttpUtil.readLine(input, buffer);
StringTokenizer tokens = new StringTokenizer(line);
tokens.nextToken(); // Skip HTTP version
String statusCode = tokens.nextToken();
String statusMsg = tokens.nextToken("\n\r");
final int code;
try {
code = Integer.parseInt(statusCode);
} catch (NumberFormatException e) {
throw new XmlRpcClientException("Server returned invalid status code: "
+ statusCode + " " + statusMsg, null);
}
if (code < 200 || code > 299) {
throw new XmlRpcHttpTransportException(code, statusMsg);
}
int contentLength = -1;
for (;;) {
line = HttpUtil.readLine(input, buffer);
if (line == null || "".equals(line)) {
break;
}
line = line.toLowerCase();
if (line.startsWith("content-length:")) {
contentLength = Integer.parseInt(line.substring("content-length:".length()).trim());
} else if (line.startsWith("content-encoding:")) {
responseGzipCompressed = HttpUtil.isUsingGzipEncoding(line.substring("content-encoding:".length()));
}
}
InputStream result;
if (contentLength == -1) {
result = input;
} else {
result = new LimitedInputStream(input, contentLength);
}
return result;
} catch (IOException e) {
throw new XmlRpcClientException("Failed to read server response: " + e.getMessage(), e);
}
}
protected boolean isUsingByteArrayOutput(XmlRpcHttpClientConfig pConfig) {
boolean result = super.isUsingByteArrayOutput(pConfig);
if (!result) {
throw new IllegalStateException("The Content-Length header is required with HTTP/1.0, and HTTP/1.1 is unsupported by the Lite HTTP Transport.");
}
return result;
}
protected void writeRequest(ReqWriter pWriter) throws XmlRpcException, IOException, SAXException {
pWriter.write(getOutputStream());
}
}

View File

@@ -1,30 +0,0 @@
package com.subgraph.orchid.xmlrpc;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcTransport;
import org.apache.xmlrpc.client.XmlRpcTransportFactory;
import com.subgraph.orchid.TorClient;
import com.subgraph.orchid.sockets.OrchidSocketFactory;
public class OrchidXmlRpcTransportFactory implements XmlRpcTransportFactory {
private final XmlRpcClient client;
private final SSLContext sslContext;
private final SocketFactory socketFactory;
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient) {
this(client, torClient, null);
}
public OrchidXmlRpcTransportFactory(XmlRpcClient client, TorClient torClient, SSLContext sslContext) {
this.client = client;
this.socketFactory = new OrchidSocketFactory(torClient);
this.sslContext = sslContext;
}
public XmlRpcTransport getTransport() {
return new OrchidXmlRpcTransport(client, socketFactory, sslContext);
}
}

View File

@@ -8,7 +8,9 @@ package net.i2p.orchid;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -17,13 +19,14 @@ import java.util.concurrent.TimeUnit;
import com.subgraph.orchid.TorClient;
import com.subgraph.orchid.TorConfig;
import static com.subgraph.orchid.TorConfig.ConfigVarType.*;
// import static com.subgraph.orchid.TorConfig.ConfigVarType.*;
import com.subgraph.orchid.TorConfig.AutoBoolValue;
import com.subgraph.orchid.TorInitializationListener;
import com.subgraph.orchid.config.TorConfigBridgeLine;
import com.subgraph.orchid.config.TorConfigInterval;
import com.subgraph.orchid.config.TorConfigParser;
import com.subgraph.orchid.dashboard.Dashboard;
import com.subgraph.orchid.geoip.CountryCodeService;
import net.i2p.I2PAppContext;
import net.i2p.app.*;
@@ -92,7 +95,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
if (_mgr != null)
_mgr.register(this);
if (_log.shouldLog(Log.INFO))
_log.info("Orchid ready");
_log.info("Orchid plugin ready");
}
/**
@@ -104,7 +107,8 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
throw new IllegalStateException();
changeState(STARTING);
if (_log.shouldLog(Log.INFO))
_log.info("Starting Orchid");
_log.info("Starting Orchid plugin...");
// TODO config dir
try {
_logger = new OrchidLogHandler(_context);
@@ -112,18 +116,21 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
_client.getConfig().setDataDirectory(_configDir);
loadConfig(_client.getConfig());
_client.addInitializationListener(this);
CountryCodeService.getInstance();
_client.start();
} catch (RuntimeException t) {
// TorException extends RuntimeException,
// unlimited strength policy files not installed
changeState(START_FAILED);
changeState(START_FAILED, t);
throw t;
}
if (_mgr != null)
_mgr.register(this);
if (_mgr != null) {
// Don't register until initializationCompleted()
//_mgr.register(this);
// RouterAppManager registers its own shutdown hook
else
} else {
_context.addShutdownTask(new Shutdown());
}
}
/**
@@ -187,7 +194,7 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
return;
changeState(STOPPING);
if (_log.shouldLog(Log.INFO))
_log.info("Stopping Orchid");
_log.info("Stopping Orchid plugin...");
if (_mgr != null)
_mgr.unregister(this);
if (_client != null) {
@@ -200,13 +207,13 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
}
changeState(STOPPED);
if (_log.shouldLog(Log.INFO))
_log.info("Orchid stopped");
_log.info("Orchid plugin stopped");
}
public Socket connect(String host, int port) throws IOException {
if (host.equals("127.0.0.1") || host.equals("localhost") ||
host.toLowerCase(Locale.US).endsWith(".i2p"))
throw new IOException("unsupported host " + host);
throw new IOException("unsupported host: " + host);
ClientAppState state = _state;
if (state != RUNNING)
throw new IOException("Cannot connect in state " + state);
@@ -219,6 +226,8 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
} catch (Exception e) {
IOException ioe = new IOException("connect error");
ioe.initCause(e);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error connecting to " + host + ':' + port + "\n* Reason:" + ioe.getMessage());
throw ioe;
}
}
@@ -240,79 +249,79 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
DataHelper.loadProps(p, _configFile);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("error loading config file", ioe);
_log.warn("Error loading Orchid config file \n* Reason: " + ioe.getMessage());
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Loading " + p.size() + " configuations");
_log.info("Loading " + p.size() + " Orchid configuration options");
TorConfigParser tcp = new TorConfigParser();
for (Map.Entry e : p.entrySet()) {
String k = (String) e.getKey();
String v = (String) e.getValue();
if (k.equals("bridges")) {
// unimplemented in parser, will throw IAE
List<TorConfigBridgeLine> list = (List<TorConfigBridgeLine>) tcp.parseValue(v, BRIDGE_LINE);
List<TorConfigBridgeLine> list = (List<TorConfigBridgeLine>) tcp.parseValue(v, "BRIDGE_LINE");
for (TorConfigBridgeLine tcbl : list) {
tc.addBridge(tcbl.getAddress(), tcbl.getPort(), tcbl.getFingerprint());
}
} else if (k.equals("circuitBuildTimeout")) {
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
tc.setCircuitBuildTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
} else if (k.equals("circuitIdleTimeout")) {
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
tc.setCircuitIdleTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
} else if (k.equals("circuitStreamTimeout")) {
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
tc.setCircuitStreamTimeout(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
} else if (k.equals("clientRejectInternalAddress")) {
tc.setClientRejectInternalAddress((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setClientRejectInternalAddress((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("enforceDistinctSubnets")) {
tc.setEnforceDistinctSubnets((Boolean) tcp.parseValue(v, INTEGER));
tc.setEnforceDistinctSubnets((Boolean) tcp.parseValue(v, "INTEGER"));
} else if (k.equals("numEntryGuards")) {
tc.setNumEntryGuards((Integer) tcp.parseValue(v, INTEGER));
tc.setNumEntryGuards((Integer) tcp.parseValue(v, "INTEGER"));
} else if (k.equals("entryNodes")) {
tc.setEntryNodes((List<String>) tcp.parseValue(v, STRINGLIST));
tc.setEntryNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
} else if (k.equals("excludeExitNodes")) {
tc.setExcludeExitNodes((List<String>) tcp.parseValue(v, STRINGLIST));
tc.setExcludeExitNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
} else if (k.equals("excludeNodes")) {
tc.setExcludeNodes((List<String>) tcp.parseValue(v, STRINGLIST));
tc.setExcludeNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
} else if (k.equals("exitNodes")) {
tc.setExitNodes((List<String>) tcp.parseValue(v, STRINGLIST));
tc.setExitNodes((List<String>) tcp.parseValue(v, "STRINGLIST"));
} else if (k.equals("fascistFirewall")) {
tc.setFascistFirewall((Boolean) tcp.parseValue(v, INTEGER));
tc.setFascistFirewall((Boolean) tcp.parseValue(v, "INTEGER"));
} else if (k.equals("firewallPorts")) {
tc.setFirewallPorts((List<Integer>) tcp.parseValue(v, PORTLIST));
tc.setFirewallPorts((List<Integer>) tcp.parseValue(v, "PORTLIST"));
} else if (k.equals("handshakeV2Enabled")) {
tc.setHandshakeV2Enabled((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setHandshakeV2Enabled((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("handshakeV3Enabled")) {
tc.setHandshakeV3Enabled((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setHandshakeV3Enabled((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("longLivedPorts")) {
tc.setLongLivedPorts((List<Integer>) tcp.parseValue(v, PORTLIST));
tc.setLongLivedPorts((List<Integer>) tcp.parseValue(v, "PORTLIST"));
} else if (k.equals("maxCircuitDirtiness")) {
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
tc.setMaxCircuitDirtiness(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
} else if (k.equals("maxClientCircuitsPending")) {
tc.setMaxClientCircuitsPending((Integer) tcp.parseValue(v, INTEGER));
tc.setMaxClientCircuitsPending((Integer) tcp.parseValue(v, "INTEGER"));
} else if (k.equals("newCircuitPeriod")) {
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, INTERVAL);
TorConfigInterval tci = (TorConfigInterval) tcp.parseValue(v, "INTERVAL");
tc.setNewCircuitPeriod(tci.getMilliseconds(), TimeUnit.MILLISECONDS);
} else if (k.equals("safeLogging")) {
tc.setSafeLogging((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setSafeLogging((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("safeSocks")) {
tc.setSafeSocks((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setSafeSocks((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("strictNodes")) {
tc.setStrictNodes((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setStrictNodes((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("useBridges")) {
tc.setUseBridges((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setUseBridges((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else if (k.equals("useMicrodescriptors")) {
tc.setUseMicrodescriptors((AutoBoolValue) tcp.parseValue(v, AUTOBOOL));
tc.setUseMicrodescriptors((AutoBoolValue) tcp.parseValue(v, "AUTOBOOL"));
} else if (k.equals("useNTorHandshake")) {
tc.setUseNTorHandshake((AutoBoolValue) tcp.parseValue(v, AUTOBOOL));
tc.setUseNTorHandshake((AutoBoolValue) tcp.parseValue(v, "AUTOBOOL"));
} else if (k.equals("warnUnsafeSocks")) {
tc.setWarnUnsafeSocks((Boolean) tcp.parseValue(v, BOOLEAN));
tc.setWarnUnsafeSocks((Boolean) tcp.parseValue(v, "BOOLEAN"));
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown config entry " + k + " = " + v);
_log.warn("Invalid Orchid config entry: " + k + " = " + v);
}
}
}
@@ -321,8 +330,12 @@ public class OrchidController implements ClientApp, TorInitializationListener, O
if (_client == null)
return;
// can't get to TorConfig's Dashboard from here so make a new one
// FIXME strip HTML
(new Dashboard()).renderComponent(out, 0xff, _client.getCircuitManager());
StringWriter sw = new StringWriter(1024);
PrintWriter pw = new PrintWriter(sw);
(new Dashboard()).renderComponent(pw, 0xff, _client.getCircuitManager());
pw.close();
out.write(sw.toString());
}
}

View File

@@ -35,7 +35,25 @@ public class OrchidLogHandler extends Handler {
public void publish(LogRecord record) {
Log log = _mgr.getLog(record.getLoggerName());
int level = toI2PLevel(record.getLevel());
log.log(level, record.getMessage(), record.getThrown());
// fix logs so they don't spew html tags; various cleanups & detritus removal
log.log(level, "[Orchid] " + record.getMessage().replaceAll("<noscript>((?:.*?\r?\n?)*)</noscript>", "")
.replaceAll("<.+?>", " ")
.replaceAll("Building&hellip;", "")
.replaceAll("&hellip;", "...")
.replaceAll("Target=", "\\\n* Target: ")
.replaceAll("Circuit=", "CircuitID=")
.replaceAll("Exit ", "")
.replaceAll("Open ", "")
.replaceAll("( )+", " ")
.replaceAll("\\(.+?\\)", "")
.replaceAll("\\[ ", "\\[")
.replaceAll(" \\]", "\\]")
.replaceAll(" +\\]", "\\]")
.replaceAll(" =", "=")
.replaceAll("= ", "=")
.replaceAll("\\] -> \\[", " -> ")
.replaceAll("\\( ", "\\(")
.replaceAll(" \\)", "\\)"), record.getThrown());
}
private static int toI2PLevel(Level level) {

View File

@@ -15,9 +15,13 @@ import com.subgraph.orchid.TorClient;
public class TorStreamSocket extends Socket {
private final Stream _stream;
//private final String _host;
private final int _port;
public TorStreamSocket(TorClient client, String host, int port) throws Exception {
_stream = client.openExitStreamTo(host, port);
//_host = host;
_port = port;
}
@Override
@@ -32,7 +36,7 @@ public class TorStreamSocket extends Socket {
@Override
public boolean isClosed() {
return false;
return _stream.isClosed();
}
@Override
@@ -50,7 +54,8 @@ public class TorStreamSocket extends Socket {
return 0;
}
// everything below here unsupported
// several below here unsupported
/** @deprecated unsupported */
@Override
public void bind(SocketAddress endpoint) {
@@ -66,46 +71,71 @@ public class TorStreamSocket extends Socket {
public void connect(SocketAddress endpoint, int timeout) {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public SocketChannel getChannel() {
throw new UnsupportedOperationException();
return null;
}
/** @deprecated unsupported */
/**
* We could return InetAddress.getByName(), but that's not
* necessarily the actual IP of the socket.
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public InetAddress getInetAddress() {
throw new UnsupportedOperationException();
return null;
}
/** @deprecated unsupported */
@Override
public boolean getKeepAlive() {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public InetAddress getLocalAddress() {
throw new UnsupportedOperationException();
return null;
}
/** @deprecated unsupported */
/**
* @return 0 since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public int getLocalPort() {
throw new UnsupportedOperationException();
return 0;
}
/** @deprecated unsupported */
/**
* @return null since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public SocketAddress getLocalSocketAddress() {
throw new UnsupportedOperationException();
return null;
}
/** @deprecated unsupported */
/**
* @return false since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean getOOBInline() {
throw new UnsupportedOperationException();
return false;
}
/** @deprecated unsupported */
/**
* @return remote port since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public int getPort() {
throw new UnsupportedOperationException();
return _port;
}
/** @deprecated unsupported */
@Override
public int getReceiveBufferSize() {
@@ -116,11 +146,15 @@ public class TorStreamSocket extends Socket {
public SocketAddress getRemoteSocketAddress() {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* @return false since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean getReuseAddress() {
throw new UnsupportedOperationException();
return false;
}
/** @deprecated unsupported */
@Override
public int getSendBufferSize() {
@@ -136,46 +170,69 @@ public class TorStreamSocket extends Socket {
public boolean getTcpNoDelay() {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* @return 0 since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public int getTrafficClass() {
throw new UnsupportedOperationException();
return 0;
}
/** @deprecated unsupported */
/**
* @return true since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean isBound() {
throw new UnsupportedOperationException();
return true;
}
/** @deprecated unsupported */
/**
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean isConnected() {
throw new UnsupportedOperationException();
return !_stream.isClosed();
}
/** @deprecated unsupported */
/**
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean isInputShutdown() {
throw new UnsupportedOperationException();
return _stream.isClosed();
}
/** @deprecated unsupported */
/**
* Supported since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public boolean isOutputShutdown() {
throw new UnsupportedOperationException();
return _stream.isClosed();
}
/** @deprecated unsupported */
@Override
public void sendUrgentData(int data) {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* Does nothing since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public void setKeepAlive(boolean on) {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
public void setKeepAlive(boolean on) {}
/**
* On is unsupported
* @throws UnsupportedOperationException if on is true
*/
@Override
public void setOOBInline(boolean on) {
throw new UnsupportedOperationException();
if (on)
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
@Override
public void setReceiveBufferSize(int size) {
@@ -206,15 +263,21 @@ public class TorStreamSocket extends Socket {
public void setTrafficClass(int cize) {
throw new UnsupportedOperationException();
}
/** @deprecated unsupported */
/**
* Closes both sides since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public void shutdownInput() {
throw new UnsupportedOperationException();
public void shutdownInput() throws IOException {
close();
}
/** @deprecated unsupported */
/**
* Closes both sides since 1.2.2-0.2, prior to that threw UnsupportedOperationException
*/
@Override
public void shutdownOutput() {
throw new UnsupportedOperationException();
public void shutdownOutput() throws IOException {
close();
}

View File

@@ -9,6 +9,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.subgraph.orchid.Tor;
import com.subgraph.orchid.TorConfig;
import net.i2p.app.ClientAppManager;
@@ -16,6 +17,8 @@ import net.i2p.orchid.OrchidController;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Translate;
import net.i2p.I2PAppContext;
/**
* From base in i2psnark
*/
@@ -30,10 +33,10 @@ public class OrchidServlet extends BasicServlet {
private static final String DEFAULT_NAME = "orchid";
public static final String PROP_CONFIG_FILE = "orchid.configFile";
private static final String DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
private static final String FOOTER = "</div></center></body></html>";
private static final String DOCTYPE = "<!DOCTYPE HTML>\n";
private static final String FOOTER = "</div>\n<span id=\"endOfPage\" data-iframe-height></span>\n</body>\n</html>";
private static final String BUNDLE = "net.i2p.orchid.messages";
private static final String RESOURCES = "/orchid/resources/";
public OrchidServlet() {
super();
@@ -118,7 +121,7 @@ public class OrchidServlet extends BasicServlet {
*/
private void doGetAndPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
// this is the part after /i2psnark
// this is the part after /orchid
String path = req.getServletPath();
resp.setHeader("X-Frame-Options", "SAMEORIGIN");
@@ -127,82 +130,144 @@ public class OrchidServlet extends BasicServlet {
resp.setContentType("text/html; charset=UTF-8");
PrintWriter out = resp.getWriter();
out.write(DOCTYPE + "<html>\n" +
"<head>\n" +
"<title>");
out.write(_("Orchid Controller"));
out.write(DOCTYPE + "<html>\n<head>\n<title>");
out.write(_t("Orchid Tor Client"));
out.write("</title>\n");
out.write("<meta http-equiv=\"Content-Security-Policy\" content=\"script-src \'self\' \'unsafe-inline\';\">\n");
out.write("<link rel=\"icon\" href=\"" + RESOURCES + "images/favicon.png\">\n");
out.write("<link href=\"" + RESOURCES + "orchid.css\" rel=\"stylesheet\" type=\"text/css\">\n");
out.write("<noscript><style>.script, #expand, #collapse {display: none !important;} " +
"#configuration {display: table !important;} *::selection {color: #fff; background: #77f;}" +
".node:active {cursor: text;}</style></noscript>\n");
out.write("</head>\n");
out.write("<body><div><h3>Plugin Status</h3>");
out.write("<body id=\"orchid\" onload=\"hideConfig();\">\n<div id=\"container\">\n<table id=\"main\" width=\"100%\">\n" +
"<thead><tr><th id=\"title\" align=\"left\">Orchid Tor Client</th></tr></thead>\n");
out.write("<tbody>\n<tr><td>\n<hr>\n<table id=\"status\" width=\"100%\">\n<tr class=\"subtitle\">" +
"<th width=\"33%\">Status</th><th width=\"34%\">Registered with I2P</th><th width=\"33%\">Plugin Version</th></tr>\n");
out.write("<tr><td align=\"center\">");
OrchidController c = _manager;
String status = c.getState().toString();
if (c != null) {
out.write("Status is: " + c.getState());
ClientAppManager cam = _context.clientAppManager();
if (cam != null)
out.write("<br>Registered? " + (cam.getRegisteredApp("outproxy") != null));
if (status.equals("RUNNING"))
out.write("<span id=\"running\">Running</span>");
else if (status.equals("STARTING"))
out.write("<span id=\"starting\">Starting...</span>");
else
out.write("<br>Not registered, no client manager");
out.write("<h3>Circuit Status</h3><pre>");
// not really in HTML for now
out.write("" + c.getState());
} else {
out.write("Not initialized");
}
out.write("</td><td align=\"center\">");
ClientAppManager cam = _context.clientAppManager();
if (c != null && cam != null) {
if (cam.getRegisteredApp("outproxy") != null) {
// out.write("<span id=\"registered\">" + (cam.getRegisteredApp("outproxy") != null) + "</span>");
out.write("<span id=\"registered\">Yes</span>");
} else {
out.write("<span id=\"starting\">In progress...</span>");
}
} else {
out.write("<span id=\"notregistered\" title=\"Not registered, no client manager\">Not registered, no client manager</span>");
}
out.write("</td>" +
"<td align=\"center\">" +
Tor.getFullVersion() + "</td></tr>\n</table>\n");
if (c != null) {
out.write("<hr>\n<!-- Circuit Status -->\n<tr><th id=\"circuitstatus\" align=\"left\">Circuit Status&nbsp;" +
"<span id=\"refresh\" style=\"float: right;\"><a href=\".\">Refresh</a></span></th></tr>\n");
out.write("<tr><td>\n<hr>\n");
// yes, really in HTML now!
c.renderStatusHTML(out);
out.write("</pre>");
out.write("</table>\n</td></tr>\n<!-- end Circuit Status -->\n");
TorConfig tc = c.getConfig();
if (tc != null)
out.write("<tr id=\"configsection\"><td>\n<hr>\n<div id=\"configtitle\"><b>Configuration Parameters</b>&nbsp;\n" +
"<a class=\"script\" id=\"expand\" href=\"#\" onclick=\"clean();expand();\"><img alt=\"Expand\" src=\"/orchid/resources/images/expand.png\" title=\"Expand\"></a>\n" +
"<a class=\"script\" id=\"collapse\" href=\"#\" onclick=\"clean();collapse();\"><img alt=\"Collapse\" src=\"/orchid/resources/images/collapse.png\" title=\"Collapse\"></a></div>\n");
out.write(getHTMLConfig(tc));
} else {
out.write("Status is: UNINITIALIZED");
}
out.write("<script src=\"" + RESOURCES + "ajaxRefresh.js\" type=\"application/javascript\"></script>\n");
out.write("<script src=\"" + RESOURCES + "toggleConfig.js\" type=\"application/javascript\"></script>\n");
out.write(FOOTER);
}
private static String getHTMLConfig(TorConfig tc) {
File configPath = I2PAppContext.getGlobalContext().getConfigDir();
String slash = System.getProperty("file.separator");
StringBuilder buf = new StringBuilder(1024);
buf.append("<h3>Configuration</h3>");
buf.append("<table cellspacing=\"8\">");
buf.append("<tr><th>Config</th><th>Value</th></tr>");
buf.append("<tr><td>Bridges</td><td>").append(tc.getBridges()).append("</tr>");
buf.append("<tr><td>Circuit Build Timeout</td><td>").append(tc.getCircuitBuildTimeout()).append("</tr>");
buf.append("<tr><td>Circuit Idle Timeout</td><td>").append(tc.getCircuitIdleTimeout()).append("</tr>");
buf.append("<tr><td>Circuit Stream Timeout</td><td>").append(tc.getCircuitStreamTimeout()).append("</tr>");
buf.append("<tr><td>Client Reject Internal Address</td><td>").append(tc.getClientRejectInternalAddress()).append("</tr>");
buf.append("<tr><td>Enforce Distinct Subnets</td><td>").append(tc.getEnforceDistinctSubnets()).append("</tr>");
buf.append("<tr><td>Entry Guards</td><td>").append(tc.getNumEntryGuards()).append("</tr>");
buf.append("<tr><td>Entry Nodes</td><td>").append(tc.getEntryNodes()).append("</tr>");
buf.append("<tr><td>Exclude Exit Nodes</td><td>").append(tc.getExcludeExitNodes()).append("</tr>");
buf.append("<tr><td>Exclude Nodes</td><td>").append(tc.getExcludeNodes()).append("</tr>");
buf.append("<tr><td>Exit Nodes</td><td>").append(tc.getExitNodes()).append("</tr>");
buf.append("<tr><td>Fascist Firewall</td><td>").append(tc.getFascistFirewall()).append("</tr>");
buf.append("<tr><td>Firewall Ports</td><td>").append(tc.getFirewallPorts()).append("</tr>");
buf.append("<tr><td>Handshake V2 Enabled</td><td>").append(tc.getHandshakeV2Enabled()).append("</tr>");
buf.append("<tr><td>Handshake V3 Enabled</td><td>").append(tc.getHandshakeV3Enabled()).append("</tr>");
buf.append("<tr><td>Long Lived Ports</td><td>").append(tc.getLongLivedPorts()).append("</tr>");
buf.append("<tr><td>Max Circuit Dirtiness</td><td>").append(tc.getMaxCircuitDirtiness()).append("</tr>");
buf.append("<tr><td>Max Client Circuits Pending</td><td>").append(tc.getMaxClientCircuitsPending()).append("</tr>");
buf.append("<tr><td>New Circuit Period</td><td>").append(tc.getNewCircuitPeriod()).append("</tr>");
buf.append("<tr><td>Safe Logging</td><td>").append(tc.getSafeLogging()).append("</tr>");
buf.append("<tr><td>Safe Socks</td><td>").append(tc.getSafeSocks()).append("</tr>");
buf.append("<tr><td>Strict Nodes</td><td>").append(tc.getStrictNodes()).append("</tr>");
buf.append("<tr><td>Use Bridges</td><td>").append(tc.getUseBridges()).append("</tr>");
buf.append("<tr><td>Use Microdescriptors</td><td>").append(tc.getUseMicrodescriptors()).append("</tr>");
buf.append("<tr><td>Use NTor Handshake</td><td>").append(tc.getUseNTorHandshake()).append("</tr>");
buf.append("<tr><td>Warn Unsafe Socks</td><td>").append(tc.getWarnUnsafeSocks()).append("</tr>");
buf.append("</table>");
buf.append("<hr>\n<table id=\"configuration\" width=\"100%\">\n");
buf.append("<tr><td class=\"notice\" colspan=\"3\">");
buf.append("The configuration is stored at <code>" + configPath + slash + "plugins" + slash + "orchid" + slash + "orchid.config</code>. " +
"Any changes will require a restart of the plugin to take effect. " +
"For more information on the configuration options, see <a href=\"https://www.torproject.org/docs/tor-manual.html.en\" target=\"_blank\">Tor's Online Manual</a>.");
buf.append("</td></tr>\n");
buf.append("<tr><th align=\"left\">Setting Name</th><th align=\"left\">Value <i>(hint)</i></th><th align=\"left\" width=\"50%\">Notes</th></tr>\n");
buf.append("<tr><td>Bridges</td><td><code>");
if (!tc.getBridges().isEmpty())
buf.append(tc.getBridges());
buf.append("</code></td><td>See <a href=\"https://bridges.torproject.org\" target=\"_blank\">bridges.torproject.org</a></td></tr>\n");
buf.append("<tr><td>Circuit Build Timeout</td><td><code>").append(tc.getCircuitBuildTimeout()).append("</code> <span class=\"nowrap\">(")
.append(tc.getCircuitBuildTimeout() / 1000).append(" seconds)").append("</span></td><td>Time limit for new circuit build</td></tr>\n");
buf.append("<tr><td>Circuit Idle Timeout</td><td><code>").append(tc.getCircuitIdleTimeout()).append("</code> <span class=\"nowrap\">(")
.append((tc.getCircuitIdleTimeout() / 1000) / 60).append(" minutes)</span>").append("</td><td>Expire circuit if unused for period</td></tr>\n");
buf.append("<tr><td>Circuit Stream Timeout</td><td><code>").append(tc.getCircuitStreamTimeout()).append("</code> <span class=\"nowrap\">(")
.append(tc.getCircuitStreamTimeout() / 1000).append(" seconds)</span>").append("</td><td>Timeout for trying new circuit if stream fails</td></tr>\n");
buf.append("<tr><td>Client Reject Internal Address</td><td><code>").append(tc.getClientRejectInternalAddress()).append("</code></td><td>Reject connection attempts to internal addresses</td></tr>\n");
buf.append("<tr><td>Enforce Distinct Subnets</td><td><code>").append(tc.getEnforceDistinctSubnets()).append("</code></td><td>Don't use nodes from the same /16 in a single circuit</td></tr>\n");
buf.append("<tr><td>Entry Nodes</td><td><code>");
if (!tc.getEntryNodes().isEmpty())
buf.append(tc.getEntryNodes());
buf.append("</code></td><td>List of desired entry nodes</td></tr>\n");
buf.append("<tr><td>Exclude Exit Nodes</td><td><code>");
if (!tc.getExcludeExitNodes().isEmpty())
buf.append(tc.getExcludeExitNodes());
buf.append("</code></td><td>Don't use specified nodes as Exits</td></tr>\n");
buf.append("<tr><td>Exclude Nodes</td><td><code>");
if (!tc.getExcludeNodes().isEmpty())
buf.append(tc.getExcludeNodes());
buf.append("</code></td><td>Don't use specified nodes in any circuit</td></tr>\n");
buf.append("<tr><td>Exit Nodes</td><td><code>");
if (!tc.getExitNodes().isEmpty())
buf.append(tc.getExitNodes());
buf.append("</code></td><td>Limit Exit nodes to those specified</td></tr>\n");
buf.append("<tr><td>Fascist Firewall</td><td><code>").append(tc.getFascistFirewall()).append("</code></td><td>Only connect to nodes using &lt;Firewall Ports&gt;</td></tr>\n");
buf.append("<tr><td>Firewall Ports</td><td><code>").append(tc.getFirewallPorts()).append("</code></td><td>Specify outgoing ports if behind strict firewall</td></tr>\n");
buf.append("<tr><td>Handshake V2 Enabled</td><td><code>").append(tc.getHandshakeV2Enabled()).append("</code></td><td>Use version 2 of the Tor handshake</td></tr>\n");
buf.append("<tr><td>Handshake V3 Enabled</td><td><code>").append(tc.getHandshakeV3Enabled()).append("</code></td><td>Use version 3 of the Tor handshake</td></tr>\n");
buf.append("<tr><td>Long Lived Ports</td><td><code>").append(tc.getLongLivedPorts()).append("</code></td><td>Map these ports to high-uptime nodes</td></tr>\n");
buf.append("<tr><td>Max Circuit Dirtiness</td><td><code>").append(tc.getMaxCircuitDirtiness()).append("</code> <span class=\"nowrap\">(")
.append((tc.getMaxCircuitDirtiness() / 1000) / 60).append(" minutes)</span>").append("</td><td>Max time to use circuit before cycling</td></tr>\n");
buf.append("<tr><td>Max Client Circuits Pending</td><td><code>").append(tc.getMaxClientCircuitsPending()).append("</code></td><td>Max number of circuit builds in progess</td></tr>\n");
buf.append("<tr><td>New Circuit Period</td><td><code>").append(tc.getNewCircuitPeriod()).append("</code> <span class=\"nowrap\">(")
.append(tc.getNewCircuitPeriod() / 1000).append(" seconds)</span>").append("</td><td>Period of new circuit build consideration</td></tr>\n");
buf.append("<tr><td>Number of Entry Guards</td><td><code>").append(tc.getNumEntryGuards()).append("</code></td><td>Number of long-term entry guards to use if &lt;Use Entry Guards&gt; is set to <i>true</i></td></tr>\n");
buf.append("<tr><td>Safe Logging</td><td><code>").append(tc.getSafeLogging()).append("</code></td><td>Scrub sensitive strings (eg. addresses) from logs</td></tr>\n");
buf.append("<tr><td>Safe Socks</td><td><code>").append(tc.getSafeSocks()).append("</code></td><td>Reject requests that only provide ip address</td></tr>\n");
buf.append("<tr><td>Strict Nodes</td><td><code>").append(tc.getStrictNodes()).append("</code></td><td>Use excluded nodes if necessary for network task</td></tr>\n");
buf.append("<tr><td>Use Bridges</td><td><code>").append(tc.getUseBridges()).append("</code></td><td>Use &lt;bridges&gt; to connect to network</td></tr>\n");
buf.append("<tr><td>Use Entry Guards</td><td><code>").append(tc.getUseEntryGuards()).append("</code></td><td>Select a few long-term entry nodes and stick with them</td></tr>\n");
buf.append("<tr><td>Use Microdescriptors</td><td><code>").append(tc.getUseMicrodescriptors()).append("</code></td><td>Use bandwidth-saving info to build circuits</td></tr>\n");
buf.append("<tr><td>Use NTor Handshake</td><td><code>").append(tc.getUseNTorHandshake()).append("</code></td><td>Use the ntor circuit-creation handshake</td></tr>\n");
buf.append("<tr><td>Warn Unsafe Socks</td><td><code>").append(tc.getWarnUnsafeSocks()).append("</code></td><td>Warn when requests only provide ip address</td></tr>\n");
buf.append("</table>\n</td></tr>\n</table>\n");
String useMds = String.valueOf(tc.getUseMicrodescriptors());
if (useMds.equals("AUTO") || useMds.equals("TRUE") || useMds.equals("true"))
buf.append("<style>#conncache .nickname:hover::after {display: none !important;}</style>\n");
return buf.toString();
}
/** translate */
private String _(String s) {
private String _t(String s) {
return Translate.getString(s, _context, BUNDLE);
}
/** translate */
private String _(String s, Object o) {
private String _t(String s, Object o) {
return Translate.getString(s, o, _context, BUNDLE);
}
/** translate */
private String _(String s, Object o, Object o2) {
private String _t(String s, Object o, Object o2) {
return Translate.getString(s, o, o2, _context, BUNDLE);
}

View File

@@ -11,7 +11,7 @@
<servlet-mapping>
<servlet-name>net.i2p.orchid.web.OrchidServlet</servlet-name>
<url-pattern>/</url-pattern>
<url-pattern></url-pattern>
</servlet-mapping>
<servlet-mapping>