This commit is contained in:
zzz
2010-03-21 15:08:07 +00:00
commit 35961ea642
27 changed files with 1758 additions and 0 deletions

29
LICENSE.txt Normal file
View File

@ -0,0 +1,29 @@
Copyright 2010 zzz (zzz@mail.i2p)
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.
========================================================================
Includes code from Jetty 5.1.15:
Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
------------------------------------------------------------------------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

42
README.txt Normal file
View File

@ -0,0 +1,42 @@
This is a very simple in-memory open tracker, wrapped into an I2P plugin.
The plugin starts a new http serer tunnel, eepsite, and Jetty server running at port 7662.
The tracker status is available at http://127.0.0.1:7661/tracker/ .
If other files are desired on the eepsite, they can be added at eepsite/docroot .
The open tracker code and jsps were written from scratch, but depend on some code
in i2psnark.jar from the I2P installation for bencoding, and of course
on other i2p libraries.
See the license files in I2P for i2p and i2psnark licenses.
There is also some code modified from Jetty 5.1.15.
See LICENSES.txt for the zzzot and Jetty licenses.
I2P source must be installed and built in ../i2p.i2p to compile this package.
Sure, as a standalone program in its own JVM with Jetty, this would be a pig -
you should use the C opentracker instead. But since you're already running
the JVM and Jetty, running this in the same JVM probably doesn't hog to much more memory.
Valid announce URLs:
/a
/announce
/announce.jsp
/announce.php
/tracker/a
/tracker/announce
/tracker/announce.jsp
/tracker/announce.php
Valid scrape URLs:
/scrape
/scrape.jsp
/scrape.php
/tracker/scrape
/tracker/scrape.jsp
/tracker/scrape.php
The tracker also responds to seedless queries at
/Seedless/index.jsp
You may use the rest of the eepsite for other purposes, for example you
may place torrent files in eepsite/docroot/torrents.

19
TODO.txt Normal file
View File

@ -0,0 +1,19 @@
Configuration file:
- interval
- clean time
- max peers in response
- disable full scrapes
- disable all scrapes
- disable seedless
Stop the cleaner
Throttles:
- full scrapes
- per-requestor
Bans:
- refuse non-GETs
Verifier:
- Check dest vs. b32 in header

67
build.xml Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project basedir="." default="all" name="zzzot">
<target name="all" depends="clean,plugin" />
<target name="war" >
<ant dir="src" target="build" />
</target>
<target name="plugin" depends="war">
<delete file="plugin/i2ptunnel.config" />
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="0.1" />
<!-- make the update xpi2p -->
<!-- this contains everything except i2ptunnel.config -->
<copy file="LICENSE.txt" todir="plugin/" overwrite="true" />
<copy file="README.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" />
</exec>
<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/zzzot.jar.pack" />
<arg value="src/build/zzzot.jar" />
</exec>
<exec executable="pack200" failonerror="true">
<arg value="-g" />
<arg value="plugin/eepsite/webapps/tracker.war.pack" />
<arg value="src/build/tracker.war.jar" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<arg value="plugin" />
</exec>
<move file="zzzot.xpi2p" tofile="zzzot-update.xpi2p" overwrite="true" />
<!-- make the install xpi2p -->
<copy file="scripts/i2ptunnel.config" 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="version=${release.number}-b${build.number}" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<arg value="plugin" />
</exec>
</target>
<target name="distclean" depends="clean" />
<target name="clean" >
<ant dir="src" target="clean" />
<delete file="plugin/i2ptunnel.config" />
<delete file="plugin/plugin.config" />
<delete file="plugin/lib/zzzot.jar.pack" />
<delete file="plugin/eepsite/webapps/tracker.war.pack" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/README.txt" />
<delete file="zzzot.xpi2p" />
<delete file="zzzot-update.xpi2p" />
</target>
</project>

8
plugin/clients.config Normal file
View File

@ -0,0 +1,8 @@
clientApp.0.main=net.i2p.zzzot.ZzzOTController
clientApp.0.name=ZzzOT
clientApp.0.args=-d $PLUGIN start
clientApp.0.stopargs=-d $PLUGIN stop
clientApp.0.delay=15
clientApp.0.startOnLoad=true
# we also use i2p.jar and i2ptunnel.jar, they are in the standard router classpath
clientApp.0.classpath=$PLUGIN/lib/zzzot.jar,$I2P/lib/i2psnark.jar

View File

@ -0,0 +1,6 @@
<html><head>
<!-- edit this file if you want to change your home page -->
<title>zzzot</title>
</head><body style="background-color: #000; color: #c30; font-size: 2000%;">
<center><b>zzzot</b></center>
</body></html>

View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@ -0,0 +1,56 @@
<html><head><title>ZzzOT Plugin Help</title></head>
<body style="background-color: #ddd; color: #a30;">
<h2>Welcome to the ZzzOT I2P Plugin!</h2>
A new eepsite tunnel and Jetty server have been started for your open tracker.
<p><a href="/tracker/index.jsp">Click here to see the current stats</a>.
This link is also at the top of your router console when ZzzOT is running.
<p>Report bugs or add comments on
<a href="htp://zzz.i2p//forums/16">the plugin forum on zzz.i2p</a>.
<h3>Eepsite Key and Helpful Hints for I2P</h3>
<p>Your Base 32 address is <a href="http://$B32/">$B32</a>.
Others may access your eepsite using this address, even if you do not publish a hostname.
<p>Once you decide on a host name, you may
<a href="http://127.0.0.1:7657/susidns/addressbook.jsp?book=private&destination=$B64">add the key to your local addressbook here</a>.
<p>Your Base 64 key is: &nbsp;&nbsp;&nbsp;<textarea rows="1" style="height: 3em;" cols="40" readonly="readonly" wrap="off">$B64</textarea>
<br>You will need this key to register a hostname at <a href="http://stats.i2p/i2p/addkey.html">stats.i2p</a>.
<p>Your private key file is $PLUGIN/eepPriv.dat - back it up!!!
<p>Your eepsite document root is $PLUGIN/eepsite/docroot,
you may put other files there is you wish to have additional content on your eepsite.
<p>The supported announce URLs are:
<ul>
<li><a href="http://$B32/a">http://$B32/a</a>
<li><a href="http://$B32/announce">http://$B32/announce</a>
<li><a href="http://$B32/announce.jsp">http://$B32/announce.jsp</a>
<li><a href="http://$B32/announce.php">http://$B32/announce.php</a>
<li><a href="http://$B32/tracker/a">http://$B32/tracker/a</a>
<li><a href="http://$B32/tracker/announce">http://$B32/tracker/announce</a>
<li><a href="http://$B32/tracker/announce.jsp">http://$B32/tracker/announce.jsp</a>
<li><a href="http://$B32/tracker/announce.php">http://$B32/tracker/announce.php</a>
</ul>
<p>The supported scrape URLs are:
<ul>
<li><a href="http://$B32/scrape">http://$B32/scrape</a>
<li><a href="http://$B32/scrape.jsp">http://$B32/scrape.jsp</a>
<li><a href="http://$B32/scrape.php">http://$B32/scrape.php</a>
<li><a href="http://$B32/tracker/scrape">http://$B32/tracker/scrape</a>
<li><a href="http://$B32/tracker/scrape.jsp">http://$B32/tracker/scrape.jsp</a>
<li><a href="http://$B32/tracker/scrape.php">http://$B32/tracker/scrape.php</a>
</ul>
<p>Your eepsite tunnel is configured for 2 inbound and 2 outbound tunnels, 3 hops each.
You may change tunnel settings by editing $PLUGIN/i2ptunnel.config and restarting the plugin.
The tunnel will not appear in <a href="http://127.0.0.1:7657/i2ptunnel/index.jsp">i2ptunnel</a>.
If your tracker gets over 1000 peers, you will probably want to increase the number of tunnels.
<p>The Jetty webserver port is 7662. If you must change it, edit jetty.xml, i2ptunnel.config, and plugins.config
in the directory $PLUGIN. Then stop and restart the plugin.
<p>This help file is $PLUGIN/eepsite/docroot/help.html, you should probably move it
outside of the document root before you announce your eepsite as it may contain your user name.
<p>As you probably know, an open tracker does not require torrents to be registered,
and it does not host torrent files. You can, however, host torrent files elsewhere on
the eepsite, for example at <a href="http://$B32/torrents/">/torrents</a>.
</body></html>

186
plugin/templates/jetty.xml Normal file
View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
<!-- ========================================================================= -->
<!-- This file configures the Jetty server. -->
<!-- All changes require a restart of I2P. -->
<!-- -->
<!-- Commonly changed settings: -->
<!-- * host: Change 127.0.0.1 to 0.0.0.0 in the addListener section -->
<!-- to access the server directly (bypassing i2p) -->
<!-- from other computers. The included version of Jetty has -->
<!-- been patched to allow IPv6 addresses as well, -->
<!-- enclosed in brackets e.g. [::1] -->
<!-- * port: Default 7662 in the addListener section -->
<!-- * threads: Raise MaxThreads in the addListener section -->
<!-- if you have a high-traffic site and get a lot of warnings. -->
<!-- -->
<!-- I2P uses Jetty 5.1.15. We have no plans to upgrade to Jetty 6, due to -->
<!-- the significant changes in the API. If you need web server features not -->
<!-- found in Jetty 5, you may install and run Jetty 6 in a different JVM, -->
<!-- or run any other web server such as Apache. If you do run another -->
<!-- web server instead, be sure and disable the Jetty 5 server for your -->
<!-- eepsite on http://127.0.0.1/configclients.jsp . -->
<!-- -->
<!-- Jetty errors and warnings will appear in wrapper.log, check there -->
<!-- to diagnose problems. -->
<!-- ========================================================================= -->
<!-- =============================================================== -->
<!-- Configure the Jetty Server -->
<!-- =============================================================== -->
<Configure class="org.mortbay.jetty.Server">
<!-- =============================================================== -->
<!-- Configure the Request Listeners -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add and configure a HTTP listener to port 8080 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SocketListener">
<Arg>
<New class="org.mortbay.util.InetAddrPort">
<Set name="host">127.0.0.1</Set>
<Set name="port">7662</Set>
</New>
</Arg>
<Set name="MinThreads">3</Set>
<Set name="MaxThreads">10</Set>
<Set name="MaxIdleTimeMs">60000</Set>
<Set name="LowResourcePersistTimeMs">1000</Set>
<Set name="ConfidentialPort">8443</Set>
<Set name="IntegralPort">8443</Set>
<Set name="PoolName">main</Set>
</New>
</Arg>
</Call>
<!-- =============================================================== -->
<!-- Configure the Contexts -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a all web application within the webapps directory. -->
<!-- + No virtual host specified -->
<!-- + Look in the webapps directory relative to jetty.home or . -->
<!-- + Use the default webdefault.xml in jetty's install -->
<!-- + Upack the war file -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="rootWebApp">root</Set>
<Call name="addWebApplications">
<Arg></Arg>
<Arg>$PLUGIN/eepsite/webapps/</Arg>
<Arg></Arg>
<Arg type="boolean">true</Arg>
</Call>
<Call name="addContext">
<Arg>
<New class="org.mortbay.http.HttpContext">
<Set name="contextPath">/</Set>
<Set name="resourceBase">$PLUGIN/eepsite/docroot</Set>
<Call name="addHandler">
<Arg>
<New class="org.mortbay.http.handler.ResourceHandler">
<Set name="redirectWelcome">FALSE</Set>
</New>
</Arg>
</Call>
<!-- This custom handler is like Jetty's ForwardHandler,
- but it passes CGI query parameters through.
- Note that it is somewhat misnamed - it does NOT
- return a 301/302, it handles the request directly.
-->
<Call name="addHandler">
<Arg>
<New class="net.i2p.zzzot.QForwardHandler">
<Call name="setHandleQueries">
<Arg type="boolean">true</Arg>
</Call>
<!-- Forward announce requests to /tracker/announce.jsp -->
<Call name="addForward">
<Arg>/a</Arg>
<Arg>/tracker/announce.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/announce</Arg>
<Arg>/tracker/announce.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/announce.jsp</Arg>
<Arg>/tracker/announce.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/announce.php</Arg>
<Arg>/tracker/announce.jsp</Arg>
</Call>
<!-- Forward scrape requests to /tracker/scrape.jsp -->
<Call name="addForward">
<Arg>/scrape</Arg>
<Arg>/tracker/scrape.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/scrape.jsp</Arg>
<Arg>/tracker/scrape.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/scrape.php</Arg>
<Arg>/tracker/scrape.jsp</Arg>
</Call>
<!-- Forward Seedless requests to /tracker/seedless.jsp -->
<Call name="addForward">
<Arg>/Seedless</Arg>
<Arg>/tracker/seedless.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/Seedless/</Arg>
<Arg>/tracker/seedless.jsp</Arg>
</Call>
<Call name="addForward">
<Arg>/Seedless/index.jsp</Arg>
<Arg>/tracker/seedless.jsp</Arg>
</Call>
</New>
</Arg>
</Call>
</New>
</Arg>
</Call>
<Call name="addContext">
<Arg>/cgi-bin/*</Arg>
<Set name="ResourceBase">$PLUGIN/eepsite/cgi-bin</Set>
<Call name="addServlet">
<Arg>Common Gateway Interface</Arg>
<Arg>/</Arg>
<Arg>org.mortbay.servlet.CGI</Arg>
<Put name="Path">/usr/local/bin:/usr/ucb:/bin:/usr/bin</Put>
</Call>
</Call>
<!-- =============================================================== -->
<!-- Configure the Request Log -->
<!-- =============================================================== -->
<Set name="RequestLog">
<New class="org.mortbay.http.I2PRequestLog">
<Arg>$PLUGIN/eepsite/logs/yyyy_mm_dd.request.log</Arg>
<Set name="retainDays">30</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="buffered">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
<!-- =============================================================== -->
<!-- Configure the Other Server Options -->
<!-- =============================================================== -->
<Set name="requestsPerGC">2000</Set>
<Set name="statsOn">false</Set>
</Configure>

25
scripts/i2ptunnel.config Normal file
View File

@ -0,0 +1,25 @@
tunnel.0.description=ZzzOT
tunnel.0.i2cpHost=127.0.0.1
tunnel.0.i2cpPort=7654
tunnel.0.name=zzzot
tunnel.0.option.i2cp.enableAccessList=false
tunnel.0.option.i2cp.encryptLeaseSet=false
tunnel.0.option.i2cp.reduceIdleTime=1200000
tunnel.0.option.i2cp.reduceOnIdle=true
tunnel.0.option.i2cp.reduceQuantity=1
tunnel.0.option.i2p.streaming.connectDelay=0
tunnel.0.option.inbound.backupQuantity=0
tunnel.0.option.inbound.length=3
tunnel.0.option.inbound.lengthVariance=0
tunnel.0.option.inbound.nickname=ZzzOT
tunnel.0.option.inbound.quantity=2
tunnel.0.option.outbound.backupQuantity=0
tunnel.0.option.outbound.length=3
tunnel.0.option.outbound.lengthVariance=0
tunnel.0.option.outbound.nickname=ZzzOT
tunnel.0.option.outbound.quantity=2
tunnel.0.privKeyFile=plugins/zzzot/eepPriv.dat
tunnel.0.startOnLoad=true
tunnel.0.targetHost=127.0.0.1
tunnel.0.targetPort=7662
tunnel.0.type=httpserver

104
scripts/makeplugin.sh Executable file
View File

@ -0,0 +1,104 @@
#!/bin/sh
#
# basic packaging up of a plugin
#
# usage: makeplugin.sh plugindir
#
# zzz 2010-02
#
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
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 ]
then
echo "You must have a $PLUGINDIR directory"
exit 1
fi
OPWD=$PWD
cd $PLUGINDIR
if [ ! -f $PC ]
then
echo "You must have a $PC file"
exit 1
fi
grep -q '^signer=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a signer in $PC"
echo 'For example signer=joe@mail.i2p'
exit 1
fi
grep -q '^name=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
exit 1
fi
grep -q '^version=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a version in $PC"
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
# add our Base64 key
grep -v '^key=' $PC > $PCT
B64KEY=`cat $B64KEYFILE`
echo "key=$B64KEY" >> $PCT || exit 1
mv $PCT $PC
# zip it
zip -r $OPWD/plugin.zip * -x \*.jar || 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
# sign it
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || 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
rm -rf logs/
echo -n 'Plugin created: '
wc -c $XPI2P

8
scripts/plugin.config Normal file
View File

@ -0,0 +1,8 @@
name=zzzot
signer=zzz-plugin@mail.i2p
consoleLinkName=ZzzOT
consoleLinkURL=http://127.0.0.1:7662/tracker/index.jsp
description=Open tracker
author=zzz
updateURL=http://stats.i2p/i2p/plugins/zzzot-update.xpi2p
license=Apache 2.0

116
src/build.xml Normal file
View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2psnark">
<property name="i2pbase" value="../../i2p.i2p"/>
<property name="i2plib" value="${i2pbase}/build"/>
<property name="jettylib" value="${i2pbase}/apps/jetty/jettylib"/>
<property name="war" value="../plugin/eepsite/webapps/blog"/>
<property name="lib" value="${war}/WEB-INF/lib"/>
<path id="cp">
<pathelement path="${java.class.path}" />
<pathelement location="${i2plib}/i2p.jar" />
<pathelement location="${i2plib}/i2ptunnel.jar" />
<pathelement location="${i2plib}/i2psnark.jar" />
<pathelement location="${i2plib}/routerconsole.jar" />
<pathelement location="${jettylib}/ant.jar"/>
<pathelement location="${jettylib}/org.mortbay.jetty.jar"/>
<pathelement location="${jettylib}/jasper-compiler.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" />
</path>
<target name="all" depends="clean, build" />
<target name="build" depends="jar, war" />
<target name="builddep">
</target>
<condition property="depend.available">
<typefound name="depend" />
</condition>
<target name="depend" if="depend.available">
<depend
cache="build"
srcdir="./src"
destdir="./build/obj" >
<!-- Depend on classes instead of jars where available -->
<classpath>
<pathelement location="../../../core/java/build/obj" />
<pathelement location="../../ministreaming/java/build/obj" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
</classpath>
</depend>
</target>
<property name="javac.compilerargs" value="" />
<target name="compile" depends="depend">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
srcdir="./java"
debug="true" deprecation="on" source="1.5" target="1.5"
destdir="./build/obj"
classpath="${i2plib}/i2p.jar:${i2plib}/i2ptunnel.jar:${i2plib}/i2psnark.jar:${i2plib}/systray.jar:${i2plib}/org.mortbay.jetty.jar" >
<compilerarg line="${javac.compilerargs}" />
</javac>
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="build/zzzot.jar" basedir="./build/obj" includes="**/*.class" >
</jar>
</target>
<target name="precompilejsp" depends="compile" >
<mkdir dir="build" />
<mkdir dir="build/war/WEB-INF/classes" />
<path id="jspcp">
<path refid="cp" />
<pathelement location="build/obj" />
</path>
<java classname="org.apache.jasper.JspC" fork="true" classpathref="jspcp" failonerror="true">
<arg value="-d" />
<arg value="build/jspjava" />
<arg value="-v" />
<arg value="-p" />
<arg value="net.i2p.zzzot" />
<arg value="-webinc" />
<arg value="build/web-fragment.xml" />
<arg value="-webapp" />
<arg value="jsp/" />
</java>
<javac
debug="true"
deprecation="on"
source="1.5" target="1.5"
destdir="build/war/WEB-INF/classes"
srcdir="./build/jspjava"
includes="**/*.java"
classpathref="jspcp"
failonerror="true" />
<copy file="jsp/WEB-INF/web.xml" tofile="build/web.xml" />
<loadfile property="jspc.web.fragment" srcfile="build/web-fragment.xml" />
<replace file="build/web.xml">
<replacefilter token="&lt;!-- precompiled servlets --&gt;" value="${jspc.web.fragment}" />
</replace>
</target>
<target name="war" depends="precompilejsp">
<copy file="jsp/index.html" todir="build/war" />
<war destfile="build/tracker.war.jar" webxml="build/web.xml">
<fileset dir="build/war" />
</war>
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
</target>
<target name="distclean" depends="clean">
</target>
</project>

View File

@ -0,0 +1,37 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
/**
* A 20-byte SHA1 info hash
*/
public class InfoHash extends ByteArray {
public InfoHash(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public InfoHash(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad infohash length: " + data.length);
}
}

View File

@ -0,0 +1,37 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
/**
* A 20-byte peer ID
*/
public class PID extends ByteArray {
public PID(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public PID(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + data.length);
}
}

View File

@ -0,0 +1,58 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import net.i2p.data.Destination;
/*
* A single peer for a single torrent.
* Save a couple stats, and implements
* a Map so we can BEncode it
* So it's like PeerID but in reverse - we make a Map from the
* data. PeerID makes the data from a Map.
*/
public class Peer extends HashMap<String, Object> {
private long lastSeen;
private long bytesLeft;
private static final Integer PORT = Integer.valueOf(6881);
public Peer(byte[] id, Destination address) {
super(3);
if (id.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + id.length);
put("peer id", id);
put("ip", address.toBase64() + ".i2p");
put("port", PORT);
}
public void setLeft(long l) {
bytesLeft = l;
lastSeen = System.currentTimeMillis();
}
public boolean isSeed() {
return bytesLeft <= 0;
}
public long lastSeen() {
return lastSeen;
}
}

View File

@ -0,0 +1,42 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.util.concurrent.ConcurrentHashMap;
/**
* All the peers for a single torrent
*/
public class Peers extends ConcurrentHashMap<PID, Peer> {
public Peers() {
super();
}
public int countSeeds() {
int rv = 0;
for (Peer p : values()) {
if (p.isSeed())
rv++;
}
return rv;
}
public int countLeeches() {
return size() - countSeeds();
}
}

View File

@ -0,0 +1,158 @@
// ========================================================================
// $Id: ForwardHandler.java,v 1.16 2005/08/13 00:01:26 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package net.i2p.zzzot;
import java.io.IOException;
import java.util.Map;
import org.mortbay.http.HttpException;
import org.mortbay.http.HttpMessage;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.PathMap;
import org.mortbay.http.handler.AbstractHttpHandler;
import org.mortbay.util.URI;
import org.mortbay.util.UrlEncoded;
/* ------------------------------------------------------------ */
/** Forward Request Handler.
* Forwards a request to a new URI. Experimental - use with caution.
* @version $Revision: 1.16 $
* @author Greg Wilkins (gregw)
*
* Just like ForwardHandler but forwards query parameters too
* And took out the dependency on apache logging
* @author zzz
*/
public class QForwardHandler extends AbstractHttpHandler
{
PathMap _forward = new PathMap();
String _root;
boolean _handleQueries = false;
/* ------------------------------------------------------------ */
/** Constructor.
*/
public QForwardHandler()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param rootForward
*/
public QForwardHandler(String rootForward)
{
_root=rootForward;
}
/* ------------------------------------------------------------ */
/** Add a forward mapping.
* @param pathSpecInContext The path to forward from
* @param newPath The path to forward to.
*/
public void addForward(String pathSpecInContext,
String newPath)
{
_forward.put(pathSpecInContext,newPath);
}
/* ------------------------------------------------------------ */
/** Add a forward mapping for root path.
* This allows a forward for exactly / which is the default
* path in a pathSpec.
* @param newPath The path to forward to.
*/
public void setRootForward(String newPath)
{
_root=newPath;
}
/* ------------------------------------------------------------ */
/** Set the Handler up to cope with forwards to paths that contain query
* elements (e.g. "/blah"->"/foo?a=b").
* AND (I2P) pass params through (e.g. "/blah?c=d? -> "/foo?c=d? ).
* @param b
*/
public void setHandleQueries(boolean b)
{
_handleQueries = b;
}
/* ------------------------------------------------------------ */
public void handle(String pathInContext,
String pathParams,
HttpRequest request,
HttpResponse response)
throws HttpException, IOException
{
String newPath=null;
String query=null;
if (_root!=null && ("/".equals(pathInContext) || pathInContext.startsWith("/;")))
newPath=_root;
else
{
Map.Entry entry = _forward.getMatch(pathInContext);
if (entry!=null)
{
String match = (String)entry.getValue();
if (_handleQueries)
{
int hook = match.indexOf('?');
if (hook != -1){
query = match.substring(hook+1);
match = match.substring(0, hook);
}
}
String info=PathMap.pathInfo((String)entry.getKey(),pathInContext);
newPath=info==null?match:(URI.addPaths(match,info));
}
}
if (newPath!=null)
{
// this is the new part for i2p
// setPath() changes the request URI and loses the parameters
// so save them and add them back
Map saved = null;
if (_handleQueries){
request.setCharacterEncoding("ISO-8859-1", false);
saved = request.getParameters();
}
int last=request.setState(HttpMessage.__MSG_EDITABLE);
String context=getHttpContext().getContextPath();
if (context.length()==1)
request.setPath(newPath);
else
request.setPath(URI.addPaths(context,newPath));
if (_handleQueries && query != null){
// add forwarded to query string to parameters
UrlEncoded.decodeTo(query, request.getParameters());
}
// this is the new part for i2p
if (_handleQueries){
// add them back
request.getParameters().putAll(saved);
}
request.setState(last);
getHttpContext().getHttpServer().service(request,response);
return;
}
}
}

View File

@ -0,0 +1,37 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.util.concurrent.ConcurrentHashMap;
/**
* All the torrents
*/
public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
public Torrents() {
super();
}
public int countPeers() {
int rv = 0;
for (Peers p : values()) {
rv += p.size();
}
return rv;
}
}

View File

@ -0,0 +1,66 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.util.Iterator;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Instantiate this to fire it up
*/
class ZzzOT {
private Torrents _torrents;
private static final long CLEAN_TIME = 4*60*1000;
private static final long EXPIRE_TIME = 60*60*1000;
ZzzOT() {
_torrents = new Torrents();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
Torrents getTorrents() {
return _torrents;
}
void stop() {
_torrents.clear();
// no way to stop the cleaner
}
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
long now = System.currentTimeMillis();
for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
Peers p = iter.next();
int recent = 0;
for (Iterator<Peer> iterp = p.values().iterator(); iterp.hasNext(); ) {
Peer peer = iterp.next();
if (peer.lastSeen() < now - EXPIRE_TIME)
iterp.remove();
else
recent++;
}
if (recent <= 0)
iter.remove();
}
}
}
}

View File

@ -0,0 +1,229 @@
package net.i2p.zzzot;
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.apps.systray.UrlLauncher;
import org.mortbay.http.HttpContext;
import org.mortbay.jetty.Server;
/**
* This handles the starting and stopping of an eepsite tunnel and jetty
* from a single static class so it can be called via clients.config.
*
* This makes installation of a new eepsite a turnkey operation -
* the user is not required to configure a new tunnel in i2ptunnel manually.
*
* Usage: ZzzOTController -d $PLUGIN [start|stop]
*
* @author zzz
*/
public class ZzzOTController {
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ZzzOTController.class);
private static Server _server;
private static TunnelController _tunnel;
private static ZzzOT _zzzot;
private static Object _lock = new Object();
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
if ("start".equals(args[2]))
start(args);
else if ("stop".equals(args[2]))
stop();
else
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
}
public static Torrents getTorrents() {
synchronized(_lock) {
if (_zzzot == null)
_zzzot = new ZzzOT();
}
return _zzzot.getTorrents();
}
private static void start(String args[]) {
File pluginDir = new File(args[1]);
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
// We create the private key file in advance, so that we can
// create the help.html file from the templates
// without waiting for i2ptunnel to create it AND build the tunnels before returning.
Destination dest = null;
File key = new File(pluginDir, "eepPriv.dat");
if (!key.exists()) {
PrivateKeyFile pkf = new PrivateKeyFile(new File(pluginDir, "eepPriv.dat"));
try {
dest = pkf.createIfAbsent();
} catch (Exception e) {
_log.error("Unable to create " + key.getAbsolutePath() + ' ' + e);
throw new IllegalArgumentException("Unable to create " + key.getAbsolutePath() + ' ' + e);
}
_log.error("NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
_log.error("NOTICE: ZzzOT: You should back up this file!");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
String b64 = dest.toBase64();
_log.error("NOTICE: ZzzOT: Your base 32 address is " + b32);
_log.error("NOTICE: ZzzOT: Your base 64 address is " + b64);
}
startJetty(pluginDir, dest);
startI2PTunnel(pluginDir, dest);
}
private static void startI2PTunnel(File pluginDir, Destination dest) {
File i2ptunnelConfig = new File(pluginDir, "i2ptunnel.config");
Properties i2ptunnelProps = new Properties();
try {
DataHelper.loadProps(i2ptunnelProps, i2ptunnelConfig);
} catch (IOException ioe) {
_log.error("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
throw new IllegalArgumentException("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
}
TunnelController tun = new TunnelController(i2ptunnelProps, "tunnel.0.");
// start in foreground so we can get the destination
//tun.startTunnelBackground();
tun.startTunnel();
if (dest != null) {
List msgs = tun.clearMessages();
for (Object s : msgs) {
_log.error("NOTICE: ZzzOT Tunnel message: " + s);
}
}
_tunnel = tun;
}
private static void startJetty(File pluginDir, Destination dest) {
if (_server != null)
throw new IllegalArgumentException("Jetty already running!");
migrateJettyXML(pluginDir);
I2PAppContext context = I2PAppContext.getGlobalContext();
File tmpdir = new File(context.getTempDir().getAbsolutePath(), "/zzzot-work");
tmpdir.mkdir();
File jettyXml = new File(pluginDir, "jetty.xml");
try {
Server serv = new Server(jettyXml.getAbsolutePath());
HttpContext[] hcs = serv.getContexts();
for (int i = 0; i < hcs.length; i++)
hcs[i].setTempDirectory(tmpdir);
serv.start();
_server = serv;
} catch (Throwable t) {
_log.error("ZzzOT jetty start failed", t);
throw new IllegalArgumentException("Jetty start failed " + t);
}
if (dest != null)
launchHelp(pluginDir, dest);
}
private static void stop() {
stopI2PTunnel();
stopJetty();
if (_zzzot != null)
_zzzot.stop();
}
private static void stopI2PTunnel() {
if (_tunnel == null)
return;
try {
_tunnel.stopTunnel();
} catch (Throwable t) {
_log.error("ZzzOT tunnel stop failed", t);
throw new IllegalArgumentException("Tunnel stop failed " + t);
}
_tunnel = null;
}
private static void stopJetty() {
if (_server == null)
return;
try {
_server.stop();
} catch (Throwable t) {
_log.error("ZzzOT jetty stop failed", t);
throw new IllegalArgumentException("Jetty stop failed " + t);
}
_server = null;
}
/** put the directory in the jetty.xml file */
private static void migrateJettyXML(File pluginDir) {
File outFile = new File(pluginDir, "jetty.xml");
if (outFile.exists())
return;
File fileTmpl = new File(pluginDir, "templates/jetty.xml");
try {
String props = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 250, true);
if (props == null)
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
props = props.replace("$PLUGIN", pluginDir.getAbsolutePath());
FileOutputStream os = new FileOutputStream(outFile);
os.write(props.getBytes("UTF-8"));
os.close();
} catch (IOException ioe) {
_log.error("jetty.xml migrate failed", ioe);
}
}
/** put the directory, base32, and base64 info in the help.html file and launch a browser window to display it */
private static void launchHelp(File pluginDir, Destination dest) {
File fileTmpl = new File(pluginDir, "templates/help.html");
File outFile = new File(pluginDir, "eepsite/docroot/help.html");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
String b64 = dest.toBase64();
try {
String html = FileUtil.readTextFile(fileTmpl.getAbsolutePath(), 100, true);
if (html == null)
throw new IOException(fileTmpl.getAbsolutePath() + " open failed");
html = html.replace("$PLUGIN", pluginDir.getAbsolutePath());
html = html.replace("$B32", b32);
html = html.replace("$B64", b64);
FileOutputStream os = new FileOutputStream(outFile);
os.write(html.getBytes("UTF-8"));
os.close();
Thread t = new I2PAppThread(new Launcher(), "ZzzOTHelp", true);
t.start();
} catch (IOException ioe) {
_log.error("ZzzOT help launch failed", ioe);
}
}
private static class Launcher implements Runnable {
public void run() {
UrlLauncher.main(new String[] { "http://127.0.0.1:7662/help.html" } );
}
}
}

36
src/jsp/WEB-INF/web.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- precompiled servlets -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/announce.php</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/announce</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.announce_jsp</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
<url-pattern>/scrape</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
<url-pattern>/scrape.php</url-pattern>
</servlet-mapping>
</web-app>

200
src/jsp/announce.jsp Normal file
View File

@ -0,0 +1,200 @@
<%@page import="java.util.ArrayList" %><%@page import="java.util.Collections" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.Destination" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
/*
* Above one-liner is so there is no whitespace -> IllegalStateException
*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
/*
* USE CAUTION WHEN EDITING
* Trailing whitespace OR NEWLINE on the last line will cause
* IllegalStateExceptions !!!
*
*/
// would be nice to make these configurable
final int MAX_RESPONSES = 25;
final int INTERVAL = 27*60;
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
response.setHeader("Pragma", "no-cache");
String info_hash = request.getParameter("info_hash");
String peer_id = request.getParameter("peer_id");
// ignored
String port = request.getParameter("port");
// ignored
String uploaded = request.getParameter("uploaded");
// ignored
String downloaded = request.getParameter("downloaded");
String sleft = request.getParameter("left");
String event = request.getParameter("event");
String ip = request.getParameter("ip");
String numwant = request.getParameter("numwant");
// ignored, use someday to enforce destination
String him = request.getHeader("X-I2P-DestB32");
String xff = request.getHeader("X-Forwarded-For");
boolean fail = false;
String msg = "bad announce";
if (xff != null) {
fail = true;
msg = "Non-I2P access denied";
response.setStatus(403, msg);
}
if (info_hash == null && !fail) {
fail = true;
msg = "no info hash";
}
if (ip == null && !fail) {
fail = true;
msg = "no ip (dest)";
}
if (peer_id == null && !fail) {
fail = true;
msg = "no peer id";
}
InfoHash ih = null;
if (!fail) {
try {
ih = new InfoHash(info_hash);
} catch (Exception e) {
fail = true;
msg = "bad infohash " + e;
}
}
Destination d = null;
if (!fail) {
try {
if (ip.endsWith(".i2p"))
ip = ip.substring(0, ip.length() - 4);
d = new Destination(ip); // from b64 string
} catch (Exception e) {
fail = true;
msg = "bad dest " + e;
}
}
PID pid = null;
if (!fail) {
try {
pid = new PID(peer_id);
} catch (Exception e) {
fail = true;
msg = "bad peer id " + e;
}
}
// int params
// ignored
long up = 0;
try {
up = Long.parseLong(uploaded);
if (up < 0)
up = 0;
} catch (NumberFormatException nfe) {};
// ignored
long down = 0;
try {
down = Long.parseLong(downloaded);
if (down < 0)
down = 0;
} catch (NumberFormatException nfe) {};
int want = MAX_RESPONSES;
try {
want = Integer.parseInt(numwant);
if (want > MAX_RESPONSES)
want = MAX_RESPONSES;
else if (want < 0)
want = 0;
} catch (NumberFormatException nfe) {};
long left = 0;
if (!"completed".equals(event)) {
try {
left = Long.parseLong(sleft);
if (left < 0)
left = 0;
} catch (NumberFormatException nfe) {};
}
Torrents torrents = ZzzOTController.getTorrents();
Map<String, Object> m = new HashMap();
if (fail) {
m.put("failure reason", msg);
} else if ("stopped".equals(event)) {
Peers peers = torrents.get(ih);
if (peers != null)
peers.remove(pid);
m.put("interval", Integer.valueOf(INTERVAL));
} else {
Peers peers = torrents.get(ih);
if (peers == null) {
peers = new Peers();
Peers p2 = torrents.putIfAbsent(ih, peers);
if (p2 != null)
peers = p2;
}
// fixme same peer id, different dest
Peer p = peers.get(pid);
if (p == null) {
p = new Peer(pid.getData(), d);
Peer p2 = peers.putIfAbsent(pid, p);
if (p2 != null)
p = p2;
}
p.setLeft(left);
m.put("interval", Integer.valueOf(INTERVAL));
int size = peers.size();
int seeds = peers.countSeeds();
m.put("complete", Integer.valueOf(seeds));
m.put("incomplete", Integer.valueOf(size - seeds));
if (want <= 0) {
// snark < 0.7.13 always wants a list
m.put("peers", java.util.Collections.EMPTY_LIST);
} else {
List<Peer> peerlist = new ArrayList(peers.values());
peerlist.remove(p); // them
if (want < size - 1) {
Collections.shuffle(peerlist);
m.put("peers", peerlist.subList(0, want));
} else {
m.put("peers", peerlist);
}
}
}
BEncoder.bencode(m, cout);
/*
* Remove the newline on the last line or
* it will generate an IllegalStateException
*
*/
%>

10
src/jsp/index.html Normal file
View File

@ -0,0 +1,10 @@
<html>
<head>
<meta http-equiv="refresh" content="0;url=index.jsp" />
<title>zzzot</title>
</head>
<body>
<a href="index.jsp">Enter</a>
</body>
</html>

14
src/jsp/index.jsp Normal file
View File

@ -0,0 +1,14 @@
<%@page import="net.i2p.zzzot.ZzzOTController" %>
<html>
<head>
<title>ZzzOT</title>
</head><body style="background-color: #000; color: #c30; font-size: 400%;">
<p>
zzzot
<p>
<table cellspacing="8">
<tr><td>Torrents:<td align="right"><%=ZzzOTController.getTorrents().size()%>
<tr><td>Peers:<td align="right"><%=ZzzOTController.getTorrents().countPeers()%>
</table>
</body>
</html>

92
src/jsp/scrape.jsp Normal file
View File

@ -0,0 +1,92 @@
<%@page import="java.util.ArrayList" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
/*
* Above one-liner is so there is no whitespace -> IllegalStateException
*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
/*
* USE CAUTION WHEN EDITING
* Trailing whitespace OR NEWLINE on the last line will cause
* IllegalStateExceptions !!!
*
*/
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
response.setHeader("Pragma", "no-cache");
String info_hash = request.getParameter("info_hash");
String xff = request.getHeader("X-Forwarded-For");
boolean fail = false;
String msg = "bad";
if (xff != null) {
fail = true;
msg = "Non-I2P access denied";
response.setStatus(403, msg);
}
boolean all = info_hash == null;
InfoHash ih = null;
if ((!all) && !fail) {
try {
ih = new InfoHash(info_hash);
} catch (Exception e) {
fail = true;
msg = "bad infohash " + e;
}
}
Torrents torrents = ZzzOTController.getTorrents();
// build 3-level dictionary
Map<String, Object> m = new HashMap();
if (fail) {
m.put("failure reason", msg);
} else {
List<InfoHash> ihList = new ArrayList();
if (all)
ihList.addAll(torrents.keySet());
else
ihList.add(ih);
Map<String, Map> files = new HashMap();
for (InfoHash ihash : ihList) {
Peers peers = torrents.get(ihash);
if (peers == null)
continue;
Map<String, Object> dict = new HashMap();
int size = peers.size();
int seeds = peers.countSeeds();
dict.put("complete", Integer.valueOf(seeds));
dict.put("incomplete", Integer.valueOf(size - seeds));
dict.put("downloaded", Integer.valueOf(0));
files.put(new String(ihash.getData(), "ISO-8859-1"), dict);
}
m.put("files", files);
}
BEncoder.bencode(m, cout);
/*
* Remove the newline on the last line or
* it will generate an IllegalStateException
*
*/
%>

74
src/jsp/seedless.jsp Normal file
View File

@ -0,0 +1,74 @@
<%@page import="net.i2p.crypto.SHA256Generator" %><%@page import="net.i2p.data.Base32" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.zzzot.*" %><%
/*
* Copyright 2010 zzz (zzz@mail.i2p)
*
* 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.
*
*/
String req = request.getHeader("X-Seedless");
String me = request.getHeader("Host");
// unused, we don't accept announces
String him = request.getHeader("X-I2P-DestB32");
String xff = request.getHeader("X-Forwarded-For");
response.setContentType("text/plain");
response.setHeader("X-Seedless", me);
final int US_MINUTES = 360;
final int PEER_MINUTES = 60;
if (xff != null) {
String msg = "Non-I2P access denied";
response.setStatus(403, msg);
out.println(msg);
} else if (req == null) {
out.println("seedless server");
} else if (req.startsWith("announce ")) {
out.println("");
} else if (req.startsWith("locate ") && me != null) {
// ignore the search string, if any, in the request
// us
out.println(Base64.encode(me + ' ' + US_MINUTES + " bt-tracker"));
// all the peers
Torrents torrents = ZzzOTController.getTorrents();
for (InfoHash ihash : torrents.keySet()) {
Peers peers = torrents.get(ihash);
if (peers == null)
continue;
for (Peer p : peers.values()) {
// dest to b32
String ip = (String) p.get("ip");
if (ip.endsWith(".i2p"))
ip = ip.substring(0, ip.length() - 4);
String b32 = Base32.encode(SHA256Generator.getInstance().calculateHash(Base64.decode(ip)).getData()) + ".b32.i2p ";
// service type
String role;
if (p.isSeed())
role = " bt-seed";
else
role = " bt-leech";
// spg wants UTF-8 but all we have is binary data, sorry
String ihs = new String(ihash.getData(), "ISO-8859-1");
String ids = new String((byte[])p.get("peer id"), "ISO-8859-1");
out.println(Base64.encode(b32 + PEER_MINUTES + role +
" info_hash=" + ihs +
" peer_id=" + ids));
}
}
} else {
out.println("2");
}
%>