beginning of branch i2p.i2p.i2p

This commit is contained in:
cvs_import
2004-04-08 04:41:54 +00:00
committed by zzz
commit 77bd69c5e5
292 changed files with 41035 additions and 0 deletions

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="phttprelay">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<ant dir="../../../core/java/" target="build" />
</target>
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac srcdir="./src" debug="true" destdir="./build/obj" classpath="../../../core/java/build/i2p.jar:lib/javax.servlet.jar" />
</target>
<target name="jar" depends="compile">
<war destfile="./build/phttprelay.war" webxml="web.xml">
<classes dir="./build/obj/">
<include name="**/*.class" />
</classes>
<lib dir="../../../core/java/build/">
<include name="i2p.jar" />
</lib>
</war>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
classpath="./lib/javax.servlet.jar"
packagenames="*"
use="true"
splitindex="true"
windowtitle="I2P phttp relay" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../../core/java/" target="cleandep" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../../core/java/" target="distclean" />
</target>
</project>

View File

@ -0,0 +1,159 @@
<HTML>
<HEAD>
<TITLE>Jetty License</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<FONT FACE=ARIAL,HELVETICA>
<CENTER><FONT SIZE=+3><B>Jetty License</B></FONT></CENTER>
<CENTER><FONT SIZE=-1><B>$Revision: 1.1 $</B></FONT></CENTER>
<B>Preamble:</B><p>
The intent of this document is to state the conditions under which the
Jetty Package may be copied, such that the Copyright Holder maintains some
semblance of control over the development of the package, while giving the
users of the package the right to use, distribute and make reasonable
modifications to the Package in accordance with the goals and ideals of
the Open Source concept as described at
<A HREF="http://www.opensource.org">http://www.opensource.org</A>.
<P>
It is the intent of this license to allow commercial usage of the Jetty
package, so long as the source code is distributed or suitable visible
credit given or other arrangements made with the copyright holders.
<P><B>Definitions:</B><P>
<UL>
<LI> "Jetty" refers to the collection of Java classes that are
distributed as a HTTP server with servlet capabilities and
associated utilities.<p>
<LI> "Package" refers to the collection of files distributed by the
Copyright Holder, and derivatives of that collection of files
created through textual modification.<P>
<LI> "Standard Version" refers to such a Package if it has not been
modified, or has been modified in accordance with the wishes
of the Copyright Holder.<P>
<LI> "Copyright Holder" is whoever is named in the copyright or
copyrights for the package. <BR>
Mort Bay Consulting Pty. Ltd. (Australia) is the "Copyright
Holder" for the Jetty package.<P>
<LI> "You" is you, if you're thinking about copying or distributing
this Package.<P>
<LI> "Reasonable copying fee" is whatever you can justify on the
basis of media cost, duplication charges, time of people involved,
and so on. (You will not be required to justify it to the
Copyright Holder, but only to the computing community at large
as a market that must bear the fee.)<P>
<LI> "Freely Available" means that no fee is charged for the item
itself, though there may be fees involved in handling the item.
It also means that recipients of the item may redistribute it
under the same conditions they received it.<P>
</UL>
0. The Jetty Package is Copyright (c) Mort Bay Consulting Pty. Ltd.
(Australia) and others. Individual files in this package may contain
additional copyright notices. The javax.servlet packages are copyright
Sun Microsystems Inc. <P>
1. The Standard Version of the Jetty package is
available from <A HREF=http://jetty.mortbay.org>http://jetty.mortbay.org</A>.<P>
2. You may make and distribute verbatim copies of the source form
of the Standard Version of this Package without restriction, provided that
you include this license and all of the original copyright notices
and associated disclaimers.<P>
3. You may make and distribute verbatim copies of the compiled form of the
Standard Version of this Package without restriction, provided that you
include this license.<P>
4. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A Package
modified in such a way shall still be considered the Standard Version.<P>
5. You may otherwise modify your copy of this Package in any way, provided
that you insert a prominent notice in each changed file stating how and
when you changed that file, and provided that you do at least ONE of the
following:<P>
<BLOCKQUOTE>
a) Place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or placing the modifications on a major archive
site such as ftp.uu.net, or by allowing the Copyright Holder to include
your modifications in the Standard Version of the Package.<P>
b) Use the modified Package only within your corporation or organization.<P>
c) Rename any non-standard classes so the names do not conflict
with standard classes, which must also be provided, and provide
a separate manual page for each non-standard class that clearly
documents how it differs from the Standard Version.<P>
d) Make other arrangements with the Copyright Holder.<P>
</BLOCKQUOTE>
6. You may distribute modifications or subsets of this Package in source
code or compiled form, provided that you do at least ONE of the following:<P>
<BLOCKQUOTE>
a) Distribute this license and all original copyright messages, together
with instructions (in the about dialog, manual page or equivalent) on where
to get the complete Standard Version.<P>
b) Accompany the distribution with the machine-readable source of
the Package with your modifications. The modified package must include
this license and all of the original copyright notices and associated
disclaimers, together with instructions on where to get the complete
Standard Version.<P>
c) Make other arrangements with the Copyright Holder.<P>
</BLOCKQUOTE>
7. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this Package.
You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial) software
distribution provided that you meet the other distribution requirements
of this license.<P>
8. Input to or the output produced from the programs of this Package
do not automatically fall under the copyright of this Package, but
belong to whomever generated them, and may be sold commercially, and
may be aggregated with this Package.<P>
9. Any program subroutines supplied by you and linked into this Package
shall not be considered part of this Package.<P>
10. The name of the Copyright Holder may not be used to endorse or promote
products derived from this software without specific prior written
permission.<P>
11. This license may change with each release of a Standard Version of
the Package. You may choose to use the license associated with version
you are using or the license of the latest Standard Version.<P>
12. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<P>
13. If any superior law implies a warranty, the sole remedy under such shall
be , at the Copyright Holders option either a) return of any price paid or
b) use or reasonable endeavours to repair or replace the software.<P>
14. This license shall be read under the laws of Australia. <P>
<center>The End</center>
<center><FONT size=-1>This license was derived from the <I>Artistic</I> license published
on <a href=http://www.opensource.org>http://www.opensource.com</a></font></center>
</FONT>

View File

@ -0,0 +1,6 @@
The file javax.servlet.jar is distributed under the terms of LICENSE.html,
which is the implementation of the java servlet classes as retrieved from
http://jetty.mortbay.org/jetty/
It is only included to assist in building the phttprelay.war file on hosts
that do not have a servlet container / implementation.

View File

@ -0,0 +1,113 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Check the status of previous message delivery, returning either pending or
* unknown, where pending means that particular message ID for that particular
* target is still on the server, and unknown means it has either not been created
* or it has been sent successfully. It does this by sending HTTP 204 (NO CONTENT)
* for pending, and HTTP 404 (NOT FOUND) for unknown. <p />
*
* This servlet should be set up in web.xml as follows:
*
* <servlet>
* <servlet-name>CheckSendStatus</servlet-name>
* <servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
* <init-param>
* <param-name>baseDir</param-name>
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
* </init-param>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>CheckSendStatus</servlet-name>
* <url-pattern>/phttpCheckSendStatus</url-pattern>
* </servlet-mapping>
*
* baseDir is the directory under which registrants and their pending messages are stored
*
*/
public class CheckSendStatusServlet extends PHTTPRelayServlet {
/* URL parameters on the check */
/** H(routerIdent).toBase64() of the target to receive the message */
public final static String PARAM_SEND_TARGET = "target";
/** msgId parameter */
public final static String PARAM_MSG_ID = "msgId";
public final static String PROP_STATUS = "status";
public final static String STATUS_PENDING = "pending";
public final static String STATUS_UNKNOWN = "unknown";
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String target = req.getParameter(PARAM_SEND_TARGET);
String msgIdStr = req.getParameter(PARAM_MSG_ID);
log("Checking status of [" + target + "] message [" + msgIdStr + "]");
if (!isKnownMessage(target, msgIdStr)) {
log("Not known - its not pending");
notPending(req, resp);
return;
} else {
log("Known - its still pending");
pending(req, resp);
return;
}
}
private boolean isKnownMessage(String target, String msgId) throws IOException {
if ( (target == null) || (target.trim().length() <= 0) ) return false;
if ( (msgId == null) || (msgId.trim().length() <= 0) ) return false;
File identDir = getIdentDir(target);
if (identDir.exists()) {
File identFile = new File(identDir, "identity.dat");
if (identFile.exists()) {
// known and valid (maybe we need to check the file format... naw, fuck it
File msgFile = new File(identDir, "msg" + msgId + ".dat");
if (msgFile.exists())
return true;
else
return false;
} else {
return false;
}
} else {
return false;
}
}
private void pending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream out = resp.getOutputStream();
StringBuffer buf = new StringBuffer();
buf.append(PROP_STATUS).append('=').append(STATUS_PENDING).append('\n');
out.write(buf.toString().getBytes());
out.flush();
out.close();
}
private void notPending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setStatus(HttpServletResponse.SC_OK);
ServletOutputStream out = resp.getOutputStream();
StringBuffer buf = new StringBuffer();
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
out.write(buf.toString().getBytes());
out.flush();
out.close();
}
}

View File

@ -0,0 +1,40 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.HashSet;
import java.util.Set;
/**
* Lock identities for updating messages (so that they aren't read / deleted
* while being written)
*
*/
class LockManager {
private volatile static Set _locks = new HashSet(); // target
public static void lockIdent(String target) {
while (true) {
synchronized (_locks) {
if (!_locks.contains(target)) {
_locks.add(target);
return;
}
try { _locks.wait(1000); } catch (InterruptedException ie) {}
}
}
}
public static void unlockIdent(String target) {
synchronized (_locks) {
_locks.remove(target);
_locks.notifyAll();
}
}
}

View File

@ -0,0 +1,73 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import net.i2p.util.Log;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
abstract class PHTTPRelayServlet extends HttpServlet {
private Log _log = new Log(getClass());
protected String _baseDir;
/* config params */
/*public final static String PARAM_BASEDIR = "baseDir";*/
public final static String ENV_BASEDIR = "phttpRelay.baseDir";
/** match the clock fudge factor on the router, rather than importing the entire router cvs module */
public final static long CLOCK_FUDGE_FACTOR = 1*60*1000;
protected String buildURL(HttpServletRequest req, String path) {
StringBuffer buf = new StringBuffer();
buf.append(req.getScheme()).append("://");
buf.append(req.getServerName()).append(":").append(req.getServerPort());
buf.append(req.getContextPath());
buf.append(path);
log("URL built: " + buf.toString());
return buf.toString();
}
protected File getIdentDir(String target) throws IOException {
if ( (_baseDir == null) || (target == null) ) throw new IOException("dir not specified to deal with");
File baseDir = new File(_baseDir);
if (!baseDir.exists()) {
boolean created = baseDir.mkdirs();
log("Creating PHTTP Relay Base Directory: " + baseDir.getAbsolutePath() + " - ok? " + created);
}
File identDir = new File(baseDir, target);
log("Ident dir: " + identDir.getAbsolutePath());
return identDir;
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
String dir = System.getProperty(ENV_BASEDIR);
if (dir == null) {
_log.warn("Base directory for the polling http relay system not in the environment [" + ENV_BASEDIR +"]");
_log.warn("Setting the base directory to ./relayDir for " + getServletName());
_baseDir = ".relayDir";
} else {
_baseDir = dir;
log("Loaded up " + getServletName() + " with base directory " + _baseDir);
}
}
public void log(String msg) {
_log.debug(msg);
}
public void log(String msg, Throwable t) {
_log.debug(msg, t);
}
}

View File

@ -0,0 +1,263 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.crypto.DSAEngine;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterIdentity;
import net.i2p.data.Signature;
import net.i2p.util.Clock;
/**
* Handle poll requests for new messages - checking the poll request for a valid signature,
* sending back all of the messages found, and after all messages are written out, delete
* them from the local store. If the signature fails, it sends back an HTTP 403 (UNAUTHORIZED).
* If the target is not registered, it sends back an HTTP 404 (NOT FOUND) <p />
*
* This servlet should be set up in web.xml as follows:
*
* <servlet>
* <servlet-name>Poll</servlet-name>
* <servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
* <init-param>
* <param-name>baseDir</param-name>
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
* </init-param>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>Poll</servlet-name>
* <url-pattern>/phttpPoll</url-pattern>
* </servlet-mapping>
*
* baseDir is the directory under which registrants and their pending messages are stored
*
*/
public class PollServlet extends PHTTPRelayServlet {
/* URL parameters on the check */
/** H(routerIdent).toBase64() of the target to receive the message */
public final static String PARAM_SEND_TARGET = "target";
/** HTTP error code if the target is not known*/
public final static int CODE_UNKNOWN = HttpServletResponse.SC_NOT_FOUND;
/** HTTP error code if the signature failed */
public final static int CODE_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
/** HTTP error code if everything is ok */
public final static int CODE_OK = HttpServletResponse.SC_OK;
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
byte data[] = getData(req);
if (data == null) return;
ByteArrayInputStream bais = new ByteArrayInputStream(data);
String target = getTarget(bais);
if (target == null) {
log("Target not specified");
resp.sendError(CODE_UNKNOWN);
return;
}
if (!isKnown(target)) {
resp.sendError(CODE_UNKNOWN);
return;
}
if (!isAuthorized(target, bais)) {
resp.sendError(CODE_UNAUTHORIZED);
return;
} else {
log("Authorized access for target " + target);
}
sendMessages(resp, target);
}
private byte[] getData(HttpServletRequest req) throws ServletException, IOException {
ServletInputStream in = req.getInputStream();
int len = req.getContentLength();
byte data[] = new byte[len];
int cur = 0;
int read = DataHelper.read(in, data);
if (read != len) {
log("Size read is incorrect [" + read + " instead of expected " + len + "]");
return null;
} else {
log("Read data length: " + data.length + " in base64: " + Base64.encode(data));
return data;
}
}
private String getTarget(InputStream in) throws IOException {
StringBuffer buf = new StringBuffer(64);
int numBytes = 0;
int c = 0;
while ( (c = in.read()) != -1) {
if (c == (int)'&') break;
buf.append((char)c);
numBytes++;
if (numBytes > 128) {
log("Target didn't find the & after 128 bytes [" + buf.toString() + "]");
return null;
}
}
if (buf.toString().indexOf("target=") != 0) {
log("Did not start with target= [" + buf.toString() + "]");
return null;
}
return buf.substring("target=".length());
}
private void sendMessages(HttpServletResponse resp, String target) throws IOException {
log("Before lock " + target);
LockManager.lockIdent(target);
log("Locked " + target);
try {
File identDir = getIdentDir(target);
expire(identDir);
File messageFiles[] = identDir.listFiles();
resp.setStatus(CODE_OK);
log("Sending back " + (messageFiles.length -1) + " messages");
ServletOutputStream out = resp.getOutputStream();
DataHelper.writeDate(out, new Date(Clock.getInstance().now()));
DataHelper.writeLong(out, 2, messageFiles.length -1);
for (int i = 0; i < messageFiles.length; i++) {
if ("identity.dat".equals(messageFiles[i].getName())) {
// skip
} else {
log("Message file " + messageFiles[i].getName() + " is " + messageFiles[i].length() + " bytes");
DataHelper.writeLong(out, 4, messageFiles[i].length());
writeFile(out, messageFiles[i]);
boolean deleted = messageFiles[i].delete();
if (!deleted) {
log("!!!Error removing message file " + messageFiles[i].getAbsolutePath() + " - please delete!");
}
}
}
out.flush();
out.close();
} catch (DataFormatException dfe) {
log("Error sending message", dfe);
} finally {
LockManager.unlockIdent(target);
log("Unlocked " + target);
}
}
private final static long EXPIRE_DELAY = 60*1000; // expire messages every minute
private void expire(File identDir) throws IOException {
File files[] = identDir.listFiles();
long now = System.currentTimeMillis();
for (int i = 0 ; i < files.length; i++) {
if ("identity.dat".equals(files[i].getName())) {
continue;
}
if (files[i].lastModified() + EXPIRE_DELAY < now) {
log("Expiring " + files[i].getAbsolutePath());
files[i].delete();
}
}
}
private void writeFile(ServletOutputStream out, File file) throws IOException {
FileInputStream fis = new FileInputStream(file);
try {
byte buf[] = new byte[4096];
while (true) {
int read = DataHelper.read(fis, buf);
if (read > 0)
out.write(buf, 0, read);
else
break;
}
} finally {
fis.close();
}
}
private boolean isKnown(String target) throws IOException {
File identDir = getIdentDir(target);
if (identDir.exists()) {
File identFile = new File(identDir, "identity.dat");
if (identFile.exists()) {
// known and valid (maybe we need to check the file format... naw, fuck it
return true;
} else {
return false;
}
} else {
return false;
}
}
private boolean isAuthorized(String target, InputStream in) throws IOException {
RouterIdentity ident = null;
try {
ident = getRouterIdentity(target);
} catch (DataFormatException dfe) {
log("Identity was not valid", dfe);
}
if (ident == null) {
log("Identity not registered");
return false;
}
try {
long val = DataHelper.readLong(in, 4);
Signature sig = new Signature();
sig.readBytes(in);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataHelper.writeLong(baos, 4, val);
if (DSAEngine.getInstance().verifySignature(sig, baos.toByteArray(), ident.getSigningPublicKey())) {
return true;
} else {
log("Signature does NOT match");
return false;
}
} catch (DataFormatException dfe) {
log("Format error reading the nonce and signature", dfe);
return false;
}
}
private RouterIdentity getRouterIdentity(String target) throws IOException, DataFormatException {
File identDir = getIdentDir(target);
if (identDir.exists()) {
File identFile = new File(identDir, "identity.dat");
if (identFile.exists()) {
// known and valid (maybe we need to check the file format... naw, fuck it
RouterIdentity ident = new RouterIdentity();
ident.readBytes(new FileInputStream(identFile));
return ident;
} else {
return null;
}
} else {
return null;
}
}
}

View File

@ -0,0 +1,154 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterIdentity;
import net.i2p.util.Clock;
/**
* Accept registrations for PHTTP relaying, allowing the Polling HTTP (PHTTP)
* transport for I2P to bridge past firewalls, NATs, and proxy servers. <p />
*
* This servlet should be set up in web.xml as follows:
*
* <servlet>
* <servlet-name>Register</servlet-name>
* <servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
* <init-param>
* <param-name>baseDir</param-name>
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
* </init-param>
* <init-param>
* <param-name>pollPath</param-name>
* <param-value>phttpPoll</param-value>
* </init-param>
* <init-param>
* <param-name>sendPath</param-name>
* <param-value>phttpSend</param-value>
* </init-param>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>Register</servlet-name>
* <url-pattern>/phttpRegister</url-pattern>
* </servlet-mapping>
*
* baseDir is the directory under which registrants and their pending messages are stored
* pollPath is the path under the current host that requests polling for messages should be sent
* sendPath is the path under the current host that requests submitting messages should be sent
*
* The pollPath and sendPath must not start with / as they are translated ala http://host:port/[path]
*/
public class RegisterServlet extends PHTTPRelayServlet {
private String _pollPath;
private String _sendPath;
/* config params */
public final static String PARAM_POLL_PATH = "pollPath";
public final static String PARAM_SEND_PATH = "sendPath";
/* key=val keys sent back on registration */
public final static String PROP_STATUS = "status";
public final static String PROP_POLL_URL = "pollURL";
public final static String PROP_SEND_URL = "sendURL";
public final static String PROP_TIME_OFFSET = "timeOffset"; // ms (local-remote)
/* values for the PROP_STATUS */
public final static String STATUS_FAILED = "failed";
public final static String STATUS_REGISTERED = "registered";
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletInputStream in = req.getInputStream();
RouterIdentity ident = new RouterIdentity();
try {
Date remoteTime = DataHelper.readDate(in);
long skew = getSkew(remoteTime);
ident.readBytes(in);
boolean ok = registerIdent(ident);
sendURLs(req, resp, skew, ok);
} catch (DataFormatException dfe) {
log("Invalid format for router identity posted", dfe);
} finally {
in.close();
}
}
private long getSkew(Date remoteDate) {
if (remoteDate == null) {
log("*ERROR: remote date was null");
return Long.MAX_VALUE;
} else {
long diff = Clock.getInstance().now() - remoteDate.getTime();
return diff;
}
}
private boolean registerIdent(RouterIdentity ident) throws DataFormatException, IOException {
File identDir = getIdentDir(ident.getHash().toBase64());
boolean created = identDir.mkdirs();
File identFile = new File(identDir, "identity.dat");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(identFile);
ident.writeBytes(fos);
} finally {
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
}
log("Identity registered into " + identFile.getAbsolutePath());
return true;
}
private void sendURLs(HttpServletRequest req, HttpServletResponse resp, long skew, boolean ok) throws IOException {
ServletOutputStream out = resp.getOutputStream();
log("*Debug: clock skew of " + skew + "ms (local-remote)");
StringBuffer buf = new StringBuffer();
if (ok) {
buf.append(PROP_POLL_URL).append("=").append(buildURL(req, _pollPath)).append("\n");
buf.append(PROP_SEND_URL).append("=").append(buildURL(req, _sendPath)).append("\n");
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
buf.append(PROP_STATUS).append("=").append(STATUS_REGISTERED).append("\n");
} else {
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
buf.append(PROP_STATUS).append("=").append(STATUS_FAILED).append("\n");
}
out.write(buf.toString().getBytes());
out.close();
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
String pollPath = config.getInitParameter(PARAM_POLL_PATH);
if (pollPath == null)
throw new ServletException("Polling path for the registration servlet required [" + PARAM_POLL_PATH + "]");
else
_pollPath = pollPath;
String sendPath = config.getInitParameter(PARAM_SEND_PATH);
if (sendPath == null)
throw new ServletException("Sending path for the registration servlet required [" + PARAM_SEND_PATH + "]");
else
_sendPath = sendPath;
}
}

View File

@ -0,0 +1,318 @@
package net.i2p.phttprelay;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Accept messages for PHTTP relaying, allowing the Polling HTTP (PHTTP)
* transport for I2P to bridge past firewalls, NATs, and proxy servers. This
* delivers them into the queue, returning HTTP 201 (created) if the queue is
* known, as well as the URL at which requests can be made to check the delivery
* status of the message. If the queue is not known, HTTP 410 (resource gone) is
* sent back. <p />
*
* This servlet should be set up in web.xml as follows:
*
* <servlet>
* <servlet-name>Send</servlet-name>
* <servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
* <init-param>
* <param-name>baseDir</param-name>
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
* </init-param>
* <init-param>
* <param-name>checkPath</param-name>
* <param-value>phttpCheckStatus</param-value>
* </init-param>
* <init-param>
* <param-name>maxMessagesPerIdent</param-name>
* <param-value>100</param-value>
* </init-param>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>Send</servlet-name>
* <url-pattern>/phttpSend</url-pattern>
* </servlet-mapping>
*
* baseDir is the directory under which registrants and their pending messages are stored
* checkPath is the path under the current host that requests for the status of delivery should be sent
* maxMessagesPerIdent is the maximum number of outstanding messages per peer being relayed
*
* The checkPath must not start with / as they are translated ala http://host:port/[path]
*/
public class SendServlet extends PHTTPRelayServlet {
private String _checkPath;
private int _maxMessagesPerIdent;
/* config params */
public final static String PARAM_CHECK_PATH = "checkPath";
public final static String PARAM_MAX_MESSAGES_PER_IDENT = "maxMessagesPerIdent";
/* URL parameters on the send */
/** H(routerIdent).toBase64() of the target to receive the message */
public final static String PARAM_SEND_TARGET = "target";
/** # ms to wait for the message to be delivered before failing it */
public final static String PARAM_SEND_TIMEOUTMS = "timeoutMs";
/** # bytes to be sent in the message */
public final static String PARAM_SEND_DATA_LENGTH = "dataLength";
/** sending router's time in ms */
public final static String PARAM_SEND_TIME = "localTime";
/** msgId parameter to access the check path servlet with (along side PARAM_SEND_TARGET) */
public final static String PARAM_MSG_ID = "msgId";
/* key=val keys sent back on registration */
public final static String PROP_CHECK_URL = "statusCheckURL";
public final static String PROP_STATUS = "status";
public final static String STATUS_OK = "accepted";
public final static String STATUS_UNKNOWN = "unknown";
private final static String STATUS_CLOCKSKEW = "clockSkew_"; /** prefix for (local-remote) */
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletInputStream in = req.getInputStream();
try {
int contentLen = req.getContentLength();
String firstLine = getFirstLine(in, contentLen);
if (firstLine == null) {
return;
}
Map params = getParameters(firstLine);
String target = (String)params.get(PARAM_SEND_TARGET);
String timeoutStr = (String)params.get(PARAM_SEND_TIMEOUTMS);
String lenStr = (String)params.get(PARAM_SEND_DATA_LENGTH);
String remoteTimeStr = (String)params.get(PARAM_SEND_TIME);
long skew = 0;
try {
long remTime = Long.parseLong(remoteTimeStr);
skew = System.currentTimeMillis() - remTime;
} catch (Throwable t) {
skew = Long.MAX_VALUE;
log("*ERROR could not parse the remote time from [" + remoteTimeStr + "]");
}
log("Target [" + target + "] timeout [" + timeoutStr + "] length [" + lenStr + "] skew [" + skew + "]");
if ( (skew > CLOCK_FUDGE_FACTOR) || (skew < 0 - CLOCK_FUDGE_FACTOR) ) {
log("Attempt to send by a skewed router: skew = " + skew + "ms (local-remote)");
failSkewed(req, resp, skew);
}
if (!isValidTarget(target)) {
log("Attempt to send to an invalid target [" + target + "]");
fail(req, resp, "Unknown or invalid target");
return;
}
long len = -1;
try {
len = Long.parseLong(lenStr);
} catch (Throwable t) {
log("Unable to parse length parameter [" + PARAM_SEND_DATA_LENGTH + "] (" + lenStr + ")");
fail(req, resp, "Invalid length parameter");
return;
}
int msgId = saveFile(in, resp, target, len);
if (msgId >= 0) {
sendSuccess(req, resp, target, msgId);
} else {
fail(req, resp, "Unable to queue up the message for delivery");
}
} finally {
try { in.close(); } catch (IOException ioe) {}
}
}
private String getFirstLine(ServletInputStream in, int len) throws ServletException, IOException {
StringBuffer buf = new StringBuffer(128);
int numBytes = 0;
int c = 0;
while ( (c = in.read()) != -1) {
if (c == (int)'\n') break;
buf.append((char)c);
numBytes++;
if (numBytes > 512) {
log("First line is > 512 bytes [" + buf.toString() + "]");
return null;
}
}
log("First line: " + buf.toString());
return buf.toString();
}
private static Map getParameters(String line) {
//StringTokenizer tok = new StringTokenizer(line, "&=", true);
Map params = new HashMap();
while (line != null) {
String key = null;
String val = null;
int firstAmp = line.indexOf('&');
int firstEq = line.indexOf('=');
if (firstAmp > 0) {
key = line.substring(0, firstEq);
val = line.substring(firstEq+1, firstAmp);
line = line.substring(firstAmp+1);
params.put(key, val);
} else {
line = null;
}
}
return params;
}
private boolean isValidTarget(String target) throws IOException {
File identDir = getIdentDir(target);
if (identDir.exists()) {
File identFile = new File(identDir, "identity.dat");
if (identFile.exists()) {
// known and valid (maybe we need to check the file format... naw, fuck it
String files[] = identDir.list();
// we skip 1 because of identity.dat
if (files.length -1 > _maxMessagesPerIdent) {
log("Too many messages pending for " + target + ": " + (files.length-1));
return false;
} else {
return true;
}
} else {
log("Ident directory exists, but identity does not... corrupt for " + target);
return false;
}
} else {
log("Unknown ident " + target);
return false;
}
}
private int saveFile(InputStream in, HttpServletResponse resp, String target, long len) throws IOException {
File identDir = getIdentDir(target);
if (!identDir.exists()) return -1;
try {
LockManager.lockIdent(target);
int i = 0;
while (true) {
File curFile = new File(identDir, "msg" + i + ".dat");
if (!curFile.exists()) {
boolean ok = writeFile(curFile, in, len);
if (ok)
return i;
else
return -1;
}
i++;
continue;
}
} finally {
LockManager.unlockIdent(target);
}
}
private boolean writeFile(File file, InputStream in, long len) throws IOException {
long remaining = len;
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
byte buf[] = new byte[4096];
while (remaining > 0) {
int read = in.read(buf);
if (read == -1)
break;
remaining -= read;
if (read > 0)
fos.write(buf, 0, read);
}
} finally {
if (fos != null) {
try { fos.close(); } catch (IOException ioe) {}
}
if (remaining != 0) {
log("Invalid remaining bytes [" + remaining + " out of " + len + "] - perhaps message was cancelled partway through delivery? deleting " + file.getAbsolutePath());
boolean deleted = file.delete();
if (!deleted)
log("!!!Error deleting temporary file " + file.getAbsolutePath());
return false;
}
}
return true;
}
private void sendSuccess(HttpServletRequest req, HttpServletResponse resp, String target, int msgId) throws IOException {
ServletOutputStream out = resp.getOutputStream();
StringBuffer buf = new StringBuffer();
buf.append(PROP_STATUS).append('=').append(STATUS_OK).append('\n');
buf.append(PROP_CHECK_URL).append('=').append(buildURL(req, _checkPath));
buf.append('?');
buf.append(PARAM_SEND_TARGET).append('=').append(target).append("&");
buf.append(PARAM_MSG_ID).append('=').append(msgId).append("\n");
out.write(buf.toString().getBytes());
out.flush();
}
private void fail(HttpServletRequest req, HttpServletResponse resp, String err) throws IOException {
ServletOutputStream out = resp.getOutputStream();
StringBuffer buf = new StringBuffer();
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
out.write(buf.toString().getBytes());
out.flush();
}
private void failSkewed(HttpServletRequest req, HttpServletResponse resp, long skew) throws IOException {
ServletOutputStream out = resp.getOutputStream();
StringBuffer buf = new StringBuffer();
buf.append(PROP_STATUS).append('=').append(STATUS_CLOCKSKEW).append(skew).append('\n');
out.write(buf.toString().getBytes());
out.flush();
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
String checkPath = config.getInitParameter(PARAM_CHECK_PATH);
if (checkPath == null)
throw new ServletException("Check status path for the sending servlet required [" + PARAM_CHECK_PATH + "]");
else
_checkPath = checkPath;
String maxMessagesPerIdentStr = config.getInitParameter(PARAM_MAX_MESSAGES_PER_IDENT);
if (maxMessagesPerIdentStr == null)
throw new ServletException("Max messages per ident for the sending servlet required [" + PARAM_MAX_MESSAGES_PER_IDENT + "]");
try {
_maxMessagesPerIdent = Integer.parseInt(maxMessagesPerIdentStr);
} catch (Throwable t) {
throw new ServletException("Valid max messages per ident for the sending servlet required [" + PARAM_MAX_MESSAGES_PER_IDENT + "]");
}
}
public static void main(String args[]) {
String line = "target=pp0ARjQiB~IKC-0FsMUsPEMrwR3gxVBZGRYfEr1IzHI=&timeoutMs=52068&dataLength=2691&";
Map props = getParameters(line);
for (java.util.Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = (String)props.get(key);
System.out.println("[" + key + "] = [" + val + "]");
}
}
}

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>I2P Polling HTTP Relay</display-name>
<servlet>
<servlet-name>Register</servlet-name>
<servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
<init-param>
<param-name>pollPath</param-name>
<param-value>/phttpPoll</param-value>
</init-param>
<init-param>
<param-name>sendPath</param-name>
<param-value>/phttpSend</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Send</servlet-name>
<servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
<init-param>
<param-name>checkPath</param-name>
<param-value>/phttpCheckSendStatus</param-value>
</init-param>
<init-param>
<param-name>maxMessagesPerIdent</param-name>
<param-value>100</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>CheckSendStatus</servlet-name>
<servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Poll</servlet-name>
<servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Register</servlet-name>
<url-pattern>/phttpRegister</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Send</servlet-name>
<url-pattern>/phttpSend</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CheckSendStatus</servlet-name>
<url-pattern>/phttpCheckSendStatus</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Poll</servlet-name>
<url-pattern>/phttpPoll</url-pattern>
</servlet-mapping>
</web-app>