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

278
apps/i2ptunnel/doc/COPYING Normal file
View File

@@ -0,0 +1,278 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

View File

@@ -0,0 +1,11 @@
$Id$
the i2p/apps/i2ptunnel module is the root of the
I2PTunnel application, and everything within it
is released according to the terms of the I2P
license policy. That means everything contained
within the i2p/apps/i2ptunnel module is released
under the GPL plus the java exception unless
otherwise marked. Alternate licenses that may be
used include BSD, Cryptix, and MIT, as well as
code granted into the public domain.

View File

@@ -0,0 +1,21 @@
I2PTunnel allows to tunnel a usual TCP connection over I2P.
A server needs to have a public key; a client connects to that.
Starting a client:
tunnel <localport> <pubkey>
localport: the port where you want to connect to
pubkey: the public key of the server
Starting a server:
tunnel <remotehost> <remoteport> <privkey>
remotehost: the host to connect to when a connection comes.
remoteport: the port to connect to when a connection comes.
privkey: your private (secret) key
To stop a client/server, hit <Return>
Have fun!

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2ptunnel">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<ant dir="../../ministreaming/java/" target="build" />
<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:../../ministreaming/java/build/mstreaming.jar" />
</target>
<target name="jar" depends="compile">
<jar destfile="./build/i2ptunnel.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="net.i2p.i2ptunnel.I2PTunnel" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
</manifest>
</jar>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
packagenames="*"
use="true"
splitindex="true"
windowtitle="I2PTunnel" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../../core/java/" target="cleandep" />
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../../core/java/" target="distclean" />
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
</project>

View File

@@ -0,0 +1,61 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.i2p.util.Log;
/**
* Read what i2ptunnel logs, and expose it in a buffer
*
*/
class BufferLogger implements Logging {
private final static Log _log = new Log(BufferLogger.class);
private ByteArrayOutputStream _baos;
private boolean _ignore;
public BufferLogger() {
_baos = new ByteArrayOutputStream(512);
_ignore = false;
}
private final static String EMPTY = "";
public String getBuffer() {
if (_ignore) return EMPTY;
else return new String(_baos.toByteArray());
}
/**
* We don't care about anything else the logger receives. This is useful
* for loggers passed in to servers and clients, since they will continue
* to add info to the logger, but if we're instantiated by the tunnel manager,
* its likely we only care about the first few messages it sends us.
*
*/
public void ignoreFurtherActions() {
_ignore = true;
synchronized (_baos) {
_baos.reset();
}
_baos = null;
}
/**
* Pass in some random data
*/
public void log(String s) {
if (_ignore) return;
if (s != null) {
_log.debug("logging [" + s + "]");
try {
_baos.write(s.getBytes());
_baos.write('\n');
} catch (IOException ioe) {
_log.error("Error logging [" + s + "]");
}
}
}
}

View File

@@ -0,0 +1,998 @@
/*
* I2PTunnel
* (c) 2003 - 2004 mihi
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* In addition, as a special exception, mihi gives permission to link
* the code of this program with the proprietary Java implementation
* provided by Sun (or other vendors as well), and distribute linked
* combinations including the two. You must obey the GNU General
* Public License in all respects for all of the code used other than
* the proprietary Java implementation. If you modify this file, you
* may extend this exception to your version of the file, but you are
* not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
public class I2PTunnel implements Logging, EventDispatcher {
private final static Log _log = new Log(I2PTunnel.class);
private final EventDispatcherImpl _event = new EventDispatcherImpl();
public static final int PACKET_DELAY=100;
public static boolean ownDest = false;
public static String port =
System.getProperty(I2PClient.PROP_TCP_PORT, "7654");
public static String host = System.getProperty
(I2PClient.PROP_TCP_HOST,"127.0.0.1");
public static String listenHost = host;
private static final String nocli_args[] = {"-nocli", "-die"};
private List tasks=new ArrayList();
private int next_task_id = 1;
private Set listeners = new HashSet();
public static void main(String[] args) throws IOException {
new I2PTunnel(args);
}
public I2PTunnel() {
this(nocli_args);
}
public I2PTunnel(String[] args) {
this(args, null);
}
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
addConnectionEventListener(lsnr);
boolean gui=true;
boolean checkRunByE = true;;
boolean cli=true;
boolean dontDie = true;
for(int i=0;i<args.length;i++) {
if (args[i].equals("-die")) {
dontDie = false;
gui=false;
cli=false;
checkRunByE=false;
} else if (args[i].equals("-nogui")) {
gui=false;
_log.warn("The `-nogui' option of I2PTunnel is deprecated.\n"+
"Use `-cli', `-nocli' (aka `-wait') or `-die' instead.");
} else if (args[i].equals("-cli")) {
gui=false;
cli=true;
checkRunByE=false;
} else if (args[i].equals("-nocli") || args[i].equals("-wait")) {
gui=false;
cli=false;
checkRunByE=false;
} else if (args[i].equals("-e")) {
runCommand(args[i+1], this);
i++;
if (checkRunByE) {
checkRunByE = false;
cli=false;
}
} else if (new File(args[i]).exists()) {
runCommand("run "+args[i], this);
} else {
System.out.println("Unknown parameter "+args[i]);
}
}
if (gui) {
new I2PTunnelGUI(this);
} else if (cli) {
try {
System.out.println("Enter 'help' for help.");
BufferedReader r = new BufferedReader
(new InputStreamReader(System.in));
while(true) {
System.out.print("I2PTunnel>");
String cmd = r.readLine();
if (cmd == null) break;
runCommand(cmd, this);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
while (dontDie) {
synchronized (this) {
try { wait(); } catch (InterruptedException ie) {}
}
}
}
private void addtask (I2PTunnelTask tsk)
{
tsk.setTunnel(this);
if (tsk.isOpen()) {
tsk.setId(next_task_id);
next_task_id++;
synchronized (tasks) {
tasks.add(tsk);
}
}
}
/** java 1.3 vs 1.4 :)
*/
private static String[] split(String src, String delim) {
StringTokenizer tok = new StringTokenizer(src, delim);
String vals[] = new String[tok.countTokens()];
for (int i = 0; i < vals.length; i++)
vals[i] = tok.nextToken();
return vals;
}
public void runCommand(String cmd, Logging l) {
if (cmd.indexOf(" ")== -1) cmd+=" ";
int iii=cmd.indexOf(" ");
String cmdname= cmd.substring(0,iii).toLowerCase();
String allargs = cmd.substring(iii+1);
String[] args = split(allargs, " "); // .split(" "); // java 1.4
if ("help".equals(cmdname)) {
runHelp(l);
} else if ("server".equals(cmdname)) {
runServer(args, l);
} else if ("textserver".equals(cmdname)) {
runTextServer(args, l);
} else if ("client".equals(cmdname)) {
runClient(args, l);
} else if ("httpclient".equals(cmdname)) {
runHttpClient(args, l);
} else if ("sockstunnel".equals(cmdname)) {
runSOCKSTunnel(args, l);
} else if ("config".equals(cmdname)) {
runConfig(args, l);
} else if ("listen_on".equals(cmdname)) {
runListenOn(args, l);
} else if ("genkeys".equals(cmdname)) {
runGenKeys(args, l);
} else if ("gentextkeys".equals(cmdname)) {
runGenTextKeys(l);
} else if (cmdname.equals("quit")) {
runQuit(l);
} else if (cmdname.equals("list")) {
runList(l);
} else if (cmdname.equals("close")) {
runClose(args, l);
} else if (cmdname.equals("run")) {
runRun(args, l);
} else if (cmdname.equals("lookup")) {
runLookup(args, l);
} else if (cmdname.equals("ping")) {
runPing(allargs, l);
} else if (cmdname.equals("owndest")) {
runOwnDest(args, l);
} else {
l.log("Unknown command [" + cmdname + "]");
}
}
/**
* Display help information through the given logger.
*
* Does not fire any events to the logger
*
* @param l logger to receive events and output
*/
public void runHelp(Logging l) {
l.log("Command list:");
l.log("config <i2phost> <i2pport>");
l.log("listen_on <ip>");
l.log("owndest yes|no");
l.log("ping <args>");
l.log("server <host> <port> <privkeyfile>");
l.log("textserver <host> <port> <privkey>");
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>|file:<pubkeyfile>");
l.log("httpclient <port>");
l.log("lookup <name>");
l.log("quit");
l.log("close [forced] <jobnumber>|all");
l.log("list");
l.log("run <commandfile>");
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the specified file
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, privKeyFilename}
* @param l logger to receive events and output
*/
public void runServer(String args[], Logging l) {
if (args.length==3) {
InetAddress serverHost = null;
int portNum = -1;
File privKeyFile = null;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error("Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error("Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
privKeyFile = new File(args[2]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error("Private key file does not exist or is not readable: " + args[2]);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PTunnelServer(serverHost, portNum, privKeyFile,
args[2], l, (EventDispatcher)this);
addtask(task);
notifyEvent("serverTaskId", new Integer(task.getId()));
return;
} else {
l.log("server <host> <port> <privkeyfile>");
l.log(" creates a server that sends all incoming data\n"+
" of its destination to host:port.");
notifyEvent("serverTaskId", new Integer(-1));
}
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the given base64 stream
*
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
*
* @param args {hostname, portNumber, privKeyBase64}
* @param l logger to receive events and output
*/
public void runTextServer(String args[], Logging l) {
if (args.length==3) {
InetAddress serverHost = null;
int portNum = -1;
try {
serverHost = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error("Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error("Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PTunnelServer(serverHost, portNum, args[2], l,
(EventDispatcher)this);
addtask(task);
notifyEvent("serverTaskId", new Integer(task.getId()));
} else {
l.log("textserver <host> <port> <privkey>");
l.log(" creates a server that sends all incoming data\n"+
" of its destination to host:port.");
notifyEvent("textserverTaskId", new Integer(-1));
}
}
/**
* Run the client on the given port number pointing at the specified destination
* (either the base64 of the destination or file:fileNameContainingDestination)
*
* Sets the event "clientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
* Also sets the event "openClientResult" = "error" or "ok" (before setting the value to "ok" it also
* adds "Ready! Port #" to the logger as well). In addition, it will also set "clientLocalPort" =
* Integer port number if the client is listening
*
* @param args {portNumber, destinationBase64 or "file:filename"}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
if (args.length==2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error("Port specified is not valid: " + args[0], nfe);
notifyEvent("clientTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PTunnelClient(port, args[1], l, ownDest,
(EventDispatcher)this);
addtask(task);
notifyEvent("clientTaskId", new Integer(task.getId()));
} else {
l.log("client <port> <pubkey>|file:<pubkeyfile>");
l.log(" creates a client that forwards port to the pubkey.\n"+
" use 0 as port to get a free port assigned.");
notifyEvent("clientTaskId", new Integer(-1));
}
}
/**
* Run an HTTP client on the given port number
*
* Sets the event "httpclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "httpclientStatus" = "ok" or "error" after the client tunnel has started.
*
* @param args {portNumber and (optionally) proxy to be used for the WWW}
* @param l logger to receive events and output
*/
public void runHttpClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error("Port specified is not valid: " + args[0], nfe);
notifyEvent("httpclientTaskId", new Integer(-1));
return;
}
String proxy = "squid.i2p";
if (args.length == 2) {
proxy = args[1];
}
I2PTunnelTask task;
task = new I2PTunnelHTTPClient(port, l, ownDest, proxy,
(EventDispatcher)this);
addtask(task);
notifyEvent("httpclientTaskId", new Integer(task.getId()));
} else {
l.log("httpclient <port> [<proxy>]");
l.log(" creates a client that distributes HTTP requests.");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
l.log(" (the default proxy is squid.i2p).");
notifyEvent("httpclientTaskId", new Integer(-1));
}
}
/**
* Run an SOCKS tunnel on the given port number
*
* Sets the event "sockstunnelTaskId" = Integer(taskId) after the
* tunnel has been started (or -1 on error). Also sets
* "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has
* started.
*
* @param args {portNumber}
* @param l logger to receive events and output
*/
public void runSOCKSTunnel(String args[], Logging l) {
if (args.length >= 1 && args.length <= 2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error("Port specified is not valid: " + args[0], nfe);
notifyEvent("sockstunnelTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher)this);
addtask(task);
notifyEvent("sockstunnelTaskId", new Integer(task.getId()));
} else {
l.log("sockstunnel <port>");
l.log(" creates a tunnel that distributes SOCKS requests.");
notifyEvent("sockstunnelTaskId", new Integer(-1));
}
}
/**
* Specify the i2cp host and port
*
* Sets the event "configResult" = "ok" or "error" after the configuration has been specified
*
* @param args {hostname, portNumber}
* @param l logger to receive events and output
*/
public void runConfig(String args[], Logging l) {
if (args.length==2) {
host=args[0];
listenHost=host;
port=args[1];
notifyEvent("configResult", "ok");
} else {
l.log("config <i2phost> <i2pport>");
l.log(" sets the connection to the i2p router.");
notifyEvent("configResult", "error");
}
}
/**
* Specify whether to use its own destination for each outgoing tunnel
*
* Sets the event "owndestResult" = "ok" or "error" after the configuration has been specified
*
* @param args {yes or no}
* @param l logger to receive events and output
*/
public void runOwnDest(String args[], Logging l) {
if (args.length==1 &&
(args[0].equalsIgnoreCase("yes")
|| args[0].equalsIgnoreCase("no"))) {
ownDest = args[0].equalsIgnoreCase("yes");
notifyEvent("owndestResult", "ok");
} else {
l.log("owndest yes|no");
l.log(" Specifies whether to use its own destination \n"+
" for each outgoing tunnel");
notifyEvent("owndestResult", "error");
}
}
/**
* Specify the hostname / IP address of the interface that the tunnels should bind to
*
* Sets the event "listen_onResult" = "ok" or "error" after the interface has been specified
*
* @param args {hostname}
* @param l logger to receive events and output
*/
public void runListenOn(String args[], Logging l) {
if (args.length==1) {
listenHost=args[0];
notifyEvent("listen_onResult", "ok");
} else {
l.log("listen_on <ip>");
l.log(" sets the interface to listen for the I2PClient.");
notifyEvent("listen_onResult", "ok");
}
}
/**
* Generate a new keypair
*
* Sets the event "genkeysResult" = "ok" or "error" after the generation is complete
*
* @param args {privateKeyFilename, publicKeyFilename} or {privateKeyFilename}
* @param l logger to receive events and output
*/
public void runGenKeys(String args[], Logging l) {
OutputStream pubdest=null;
if (args.length == 2) {
try {
pubdest=new FileOutputStream(args[1]);
} catch (IOException ioe) {
l.log("Error opening output stream");
_log.error("Error generating keys to out", ioe);
notifyEvent("genkeysResult", "error");
return;
}
} else if (args.length != 1) {
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log(" creates a new keypair and prints the public key.\n"+
" if pubkeyfile is given, saves the public key there."+
"\n"+
" if the privkeyfile already exists, just print/save"+
"the pubkey.");
notifyEvent("genkeysResult", "error");
}
try {
File privKeyFile = new File(args[0]);
if (privKeyFile.exists()) {
l.log("File already exists.");
showKey(new FileInputStream(privKeyFile), pubdest, l);
} else {
makeKey(new FileOutputStream(privKeyFile), pubdest, l);
}
notifyEvent("genkeysResult", "ok");
} catch (IOException ioe) {
l.log("Error generating keys - " + ioe.getMessage());
notifyEvent("genkeysResult", "error");
_log.error("Error generating keys", ioe);
}
}
/**
* Generate a new keypair
*
* Sets the event "privateKey" = base64 of the privateKey stream and
* sets the event "publicDestination" = base64 of the destination
*
* @param l logger to receive events and output
*/
public void runGenTextKeys(Logging l) {
ByteArrayOutputStream privkey = new ByteArrayOutputStream(512);
ByteArrayOutputStream pubkey = new ByteArrayOutputStream(512);
makeKey(privkey, pubkey, l);
l.log("Private key: "+Base64.encode(privkey.toByteArray()));
notifyEvent("privateKey", Base64.encode(privkey.toByteArray()));
notifyEvent("publicDestination", Base64.encode(pubkey.toByteArray()));
}
/**
* Exit the JVM if there are no more tasks left running. If there are tunnels
* running, it returns.
*
* Sets the event "quitResult" = "error" if there are tasks running (but if there
* aren't, well, there's no point in setting the quitResult to "ok", now is there?)
*
* @param l logger to receive events and output
*/
public void runQuit(Logging l) {
purgetasks(l);
synchronized (tasks) {
if (tasks.isEmpty()) {
System.exit(0);
}
}
l.log("There are running tasks. Try 'list'.");
notifyEvent("quitResult", "error");
}
/**
* Retrieve a list of currently running tasks
*
* Sets the event "listDone" = "done" after dumping the tasks to
* the logger
*
* @param l logger to receive events and output
*/
public void runList(Logging l) {
purgetasks(l);
synchronized (tasks) {
for (int i=0;i<tasks.size();i++) {
I2PTunnelTask t = (I2PTunnelTask) tasks.get(i);
l.log("[" + t.getId() + "] " + t.toString());
}
}
notifyEvent("listDone", "done");
}
/**
* Close the given task (or all tasks), optionally forcing them to die a hard
* death
*
* Sets the event "closeResult" = "ok" after the closing is complete
*
* @param args {jobNumber}, {"forced", jobNumber}, {"forced", "all"}
* @param l logger to receive events and output
*/
public void runClose(String args[], Logging l) {
if (args.length == 0 || args.length > 2) {
l.log("close [forced] <jobnumber>|all");
l.log(" stop running tasks. either only one or all.\n"+
" use 'forced' to also stop tasks with active connections.\n"+
" use the 'list' command to show the job numbers");
notifyEvent("closeResult", "error");
} else {
int argindex=0; // parse optional 'forced' keyword
boolean forced=false;
if (args[argindex].equalsIgnoreCase("forced")) {
forced=true;
argindex++;
}
if (args[argindex].equalsIgnoreCase("all")) {
List curTasks = null;
synchronized (tasks) {
curTasks = new LinkedList(tasks);
}
boolean error = false;
for (int i=0;i<curTasks.size();i++) {
I2PTunnelTask t = (I2PTunnelTask)curTasks.get(i);
if (!closetask(t, forced, l)) {
notifyEvent("closeResult", "error");
error = true;
} else if (!error) { // If there's an error, don't hide it
notifyEvent("closeResult", "ok");
}
}
} else {
try {
if (!closetask(Integer.parseInt(args[argindex]),forced,l)){
notifyEvent("closeResult", "error");
} else {
notifyEvent("closeResult", "ok");
}
} catch (NumberFormatException ex) {
l.log("Incorrect job number: " + args[argindex]);
notifyEvent("closeResult", "error");
}
}
}
}
/**
* Run all of the commands in the given file (one command per line)
*
* Sets the event "runResult" = "ok" or "error" after the closing is complete
*
* @param args {filename}
* @param l logger to receive events and output
*/
public void runRun(String args[], Logging l) {
if(args.length==1) {
try {
BufferedReader br =
new BufferedReader(new FileReader(args[0]));
String line;
while((line = br.readLine()) != null) {
runCommand(line,l);
}
br.close();
notifyEvent("runResult", "ok");
} catch (IOException ioe) {
l.log("IO error running the file");
_log.error("Error running the file", ioe);
notifyEvent("runResult", "error");
}
} else {
l.log("run <commandfile>");
l.log(" loads commandfile and runs each line in it. \n"+
" You can also give the filename on the commandline.");
notifyEvent("runResult", "error");
}
}
/**
* Perform a lookup of the name specified
*
* Sets the event "lookupResult" = base64 of the destination, or an error message
*
* @param args {name}
* @param l logger to receive events and output
*/
public void runLookup(String args[], Logging l) {
if (args.length != 1) {
l.log("lookup <name>");
l.log(" try to resolve the name into a destination key");
notifyEvent("lookupResult", "invalidUsage");
} else {
String target = args[0];
try {
Destination dest = destFromName(args[0]);
if (dest == null) {
l.log("Unknown host");
notifyEvent("lookupResult", "unkown host");
} else {
l.log(dest.toBase64());
notifyEvent("lookupResult", dest.toBase64());
}
} catch (DataFormatException dfe) {
l.log("Unknown or invalid host");
notifyEvent("lookupResult", "invalid host");
}
}
}
/**
* Start up a ping task with the specified args (currently supporting -ns, -h, -l)
*
* Sets the event "pingTaskId" = Integer of the taskId, or -1
*
* @param allargs arguments to pass to the I2Ping task
* @param l logger to receive events and output
*/
public void runPing(String allargs, Logging l) {
if(allargs.length() != 0) {
I2PTunnelTask task;
// pings always use the main destination
task = new I2Ping(allargs, l, false, (EventDispatcher)this);
addtask(task);
notifyEvent("pingTaskId", new Integer(task.getId()));
} else {
l.log("ping <opts> <dest>");
l.log("ping <opts> -h");
l.log("ping <opts> -l <destlistfile>");
l.log(" Tests communication with peers.\n"+
" opts can be -ns (nosync) or not.");
notifyEvent("pingTaskId", new Integer(-1));
}
}
/**
* Helper method to actually close the given task number (optionally forcing
* closure)
*
*/
private boolean closetask(int num, boolean forced, Logging l) {
boolean closed = false;
_log.debug("closetask(): looking for task " + num);
synchronized (tasks) {
for (Iterator it=tasks.iterator(); it.hasNext();) {
I2PTunnelTask t = (I2PTunnelTask) it.next();
int id = t.getId();
_log.debug("closetask(): parsing task " + id + " (" +
t.toString() + ")");
if (id == num) {
closed = closetask(t, forced, l);
break;
} else if (id > num) {
break;
}
}
}
return closed;
}
/**
* Helper method to actually close the given task number
* (optionally forcing closure)
*
*/
private boolean closetask(I2PTunnelTask t, boolean forced, Logging l) {
l.log("Closing task " + t.getId() + (forced ? " forced..." : "..."));
if (t.close(forced)) {
l.log("Task " + t.getId() + " closed.");
return true;
}
return false;
}
/**
* Helper task to remove closed / completed tasks.
*
*/
private void purgetasks(Logging l) {
synchronized (tasks) {
for (Iterator it=tasks.iterator(); it.hasNext();) {
I2PTunnelTask t = (I2PTunnelTask) it.next();
if (!t.isOpen()) {
_log.debug("Purging inactive tunnel: ["
+ t.getId() + "] "
+ t.toString());
it.remove();
}
}
}
}
/**
* Log the given message (using both the logging subsystem and standard output...)
*
*/
public void log(String s) {
System.out.println(s);
_log.info("Display: " + s);
}
/**
* Create a new destination, storing the destination and its private keys where
* instructed
*
* @param writeTo location to store the private keys
* @param pubDest location to store the destination
* @param l logger to send messages to
*/
public static void makeKey(OutputStream writeTo, OutputStream pubDest,
Logging l) {
try {
l.log("Generating new keys...");
ByteArrayOutputStream priv = new ByteArrayOutputStream(),
pub = new ByteArrayOutputStream();
I2PClient client = I2PClientFactory.createClient();
Destination d = client.createDestination(writeTo);
l.log("Secret key saved.");
l.log("Public key: "+d.toBase64());
writeTo.flush();
writeTo.close();
writePubKey(d, pubDest, l);
} catch (I2PException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Read in the given destination, display it, and write it to the given location
*
* @param readFrom stream to read the destination from
* @param pubDest stream to write the destination to
* @param l logger to send messages to
*/
public static void showKey(InputStream readFrom, OutputStream pubDest,
Logging l) {
try {
I2PClient client = I2PClientFactory.createClient();
Destination d = new Destination();
d.readBytes(readFrom);
l.log("Public key: "+d.toBase64());
readFrom.close();
writePubKey(d, pubDest, l);
} catch (I2PException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Write out the destination to the stream
*
* @param d Destination to write
* @param o stream to write the destination to
* @param l logger to send messages to
*/
private static void writePubKey(Destination d, OutputStream o, Logging l)
throws I2PException, IOException {
if (o==null) return;
d.writeBytes(o);
l.log("Public key saved.");
}
/**
* Generates a Destination from a name. Now only supports base64
* names - may support naming servers later. "file:<filename>" is
* also supported, where filename is a file that either contains a
* binary Destination structure or the Base64 encoding of that
* structure.
*/
public static Destination destFromName(String name)
throws DataFormatException {
if ( (name == null) || (name.trim().length() <= 0) )
throw new DataFormatException("Empty destination provided");
if (name.startsWith("file:")) {
Destination result=new Destination();
byte content[] = null;
FileInputStream in = null;
try {
in = new FileInputStream(name.substring("file:".length()));
byte buf[] = new byte[1024];
int read = DataHelper.read(in, buf);
content = new byte[read];
System.arraycopy(buf, 0, content, 0, read);
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
return null;
} finally {
if (in != null) try { in.close(); } catch (IOException io) {}
}
try {
result.fromByteArray(content);
return result;
} catch (Exception ex) {
if (_log.shouldLog(Log.INFO))
_log.info("File is not a binary destination - trying base64");
try {
byte decoded[] = Base64.decode(new String(content));
result.fromByteArray(decoded);
return result;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("File is not a base64 destination either - failing!");
return null;
}
}
} else {
// ask naming service
NamingService inst = NamingService.getInstance();
return inst.lookup(name);
}
}
public void addConnectionEventListener(ConnectionEventListener lsnr) {
if (lsnr == null) return;
synchronized (listeners) {
listeners.add(lsnr);
}
}
public void removeConnectionEventListener(ConnectionEventListener lsnr) {
if (lsnr == null) return;
synchronized (listeners) {
listeners.remove(lsnr);
}
}
/**
* Call this whenever we lose touch with the router involuntarily (aka the router
* is off / crashed / etc)
*
*/
void routerDisconnected() {
_log.error("Router disconnected - firing notification events");
synchronized (listeners) {
for (Iterator iter = listeners.iterator(); iter.hasNext();) {
ConnectionEventListener lsnr = (ConnectionEventListener)iter.next();
if (lsnr != null)
lsnr.routerDisconnected();
}
}
}
/**
* Callback routine to find out
*/
public interface ConnectionEventListener {
public void routerDisconnected();
}
/* Required by the EventDispatcher interface */
public EventDispatcher getEventDispatcher() { return _event; }
public void attachEventDispatcher(EventDispatcher e) { _event.attachEventDispatcher(e.getEventDispatcher()); }
public void detachEventDispatcher(EventDispatcher e) { _event.detachEventDispatcher(e.getEventDispatcher()); }
public void notifyEvent(String e, Object a) { _event.notifyEvent(e,a); }
public Object getEventValue(String n) { return _event.getEventValue(n); }
public Set getEvents() { return _event.getEvents(); }
public void ignoreEvents() { _event.ignoreEvents(); }
public void unIgnoreEvents() { _event.unIgnoreEvents(); }
public Object waitEventValue(String n) { return _event.waitEventValue(n); }
}

View File

@@ -0,0 +1,61 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.net.Socket;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2PTunnelClient extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PTunnelClient.class);
protected Destination dest;
public I2PTunnelClient(int localPort, String destination,
Logging l, boolean ownDest,
EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "SynSender");
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openClientResult", "error");
return;
}
try {
dest=I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve " + destination + ".");
return;
}
} catch (DataFormatException e) {
l.log("Bad format in destination \"" + destination + "\".");
notifyEvent("openClientResult", "error");
return;
}
setName(getLocalPort() + " -> " + destination);
startRunning();
notifyEvent("openClientResult", "ok");
}
protected void clientConnectionRun(Socket s) {
try {
I2PSocket i2ps = createI2PSocket(dest);
new I2PTunnelRunner(s, i2ps, sockLock, null);
} catch (I2PException ex) {
_log.info("Error connecting", ex);
l.log("Unable to reach peer");
// s has been initialized before the try block...
closeSocket(s);
}
}
}

View File

@@ -0,0 +1,292 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public abstract class I2PTunnelClientBase extends I2PTunnelTask
implements Runnable {
private static final Log _log = new Log(I2PTunnelClientBase.class);
protected Logging l;
private static final long DEFAULT_CONNECT_TIMEOUT = 60*1000;
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
private I2PSocketManager sockMgr;
private List mySockets = new ArrayList();
protected Destination dest = null;
private int localPort;
private boolean listenerReady = false;
private ServerSocket ss;
private Object startLock = new Object();
private boolean startRunning = false;
private Object closeLock = new Object();
private byte[] pubkey;
private String handlerName;
//public I2PTunnelClientBase(int localPort, boolean ownDest,
// Logging l) {
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
//}
public I2PTunnelClientBase(int localPort, boolean ownDest,
Logging l, EventDispatcher notifyThis,
String handlerName) {
super(localPort+" (uninitialized)", notifyThis);
this.localPort=localPort;
this.l = l;
this.handlerName=handlerName;
synchronized(sockLock) {
if (ownDest) {
sockMgr=buildSocketManager();
} else {
sockMgr=getSocketManager();
}
}
if (sockMgr == null) throw new NullPointerException();
l.log("I2P session created");
Thread t = new Thread(this);
t.setName("Client");
listenerReady=false;
t.start();
open=true;
synchronized (this) {
while (!listenerReady) {
try {
wait();
}
catch (InterruptedException e) {
// ignore
}
}
}
if (open && listenerReady) {
l.log("Ready! Port " + getLocalPort());
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error!");
notifyEvent("openBaseClientResult", "error");
}
}
private static I2PSocketManager socketManager;
protected static synchronized I2PSocketManager getSocketManager() {
if (socketManager == null) {
socketManager = buildSocketManager();
}
return socketManager;
}
protected static I2PSocketManager buildSocketManager() {
Properties props = new Properties();
props.putAll(System.getProperties());
return I2PSocketManagerFactory.createManager
(I2PTunnel.host, Integer.parseInt(I2PTunnel.port), props);
}
public final int getLocalPort() {
return localPort;
}
protected final InetAddress getListenHost(Logging l) {
try {
return InetAddress.getByName(I2PTunnel.listenHost);
} catch (UnknownHostException uhe) {
l.log("Could not find listen host to bind to [" +
I2PTunnel.host + "]");
_log.error("Error finding host to bind", uhe);
notifyEvent("openBaseClientResult", "error");
return null;
}
}
/**
* Actually start working on incoming connections. *Must* be
* called by derived classes after initialization.
*
*/
public final void startRunning() {
synchronized (startLock) {
startRunning = true;
startLock.notify();
}
}
/**
* create the default options (using the default timeout, etc)
*
*/
private I2PSocketOptions getDefaultOptions() {
I2PSocketOptions opts = new I2PSocketOptions();
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
/**
* Create a new I2PSocket towards to the specified destination,
* adding it to the list of connections actually managed by this
* tunnel.
*
* @param dest The destination to connect to
* @return a new I2PSocket
*/
public I2PSocket createI2PSocket(Destination dest) throws I2PException {
return createI2PSocket(dest, getDefaultOptions());
}
/**
* Create a new I2PSocket towards to the specified destination,
* adding it to the list of connections actually managed by this
* tunnel.
*
* @param dest The destination to connect to
* @param opt Option to be used to open when opening the socket
* @return a new I2PSocket
*/
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException {
I2PSocket i2ps;
synchronized (sockLock) {
i2ps = sockMgr.connect(dest, opt);
mySockets.add(i2ps);
}
return i2ps;
}
public final void run() {
try {
InetAddress addr = getListenHost(l);
if (addr == null) return;
ss = new ServerSocket(localPort, 0, addr);
// If a free port was requested, find out what we got
if (localPort == 0) {
localPort = ss.getLocalPort();
}
notifyEvent("clientLocalPort", new Integer(ss.getLocalPort()));
l.log("Listening for clients on port " + localPort +
" of " + I2PTunnel.listenHost);
// Notify constructor that port is ready
synchronized(this) {
listenerReady = true;
notify();
}
// Wait until we are authorized to process data
synchronized (startLock) {
while (!startRunning) {
try {
startLock.wait();
} catch (InterruptedException ie) {}
}
}
while (true) {
Socket s = ss.accept();
manageConnection(s);
}
} catch (IOException ex) {
_log.error("Error listening for connections", ex);
notifyEvent("openBaseClientResult", "error");
}
}
/**
* Manage the connection just opened on the specified socket
*
* @param s Socket to take care of
*/
protected void manageConnection(Socket s) {
new ClientConnectionRunner(s, handlerName);
}
public boolean close(boolean forced) {
if (!open) return true;
// FIXME: here we might have to wait quite a long time if
// there is a connection attempt atm. But without waiting we
// might risk to create an orphan socket. Would be better
// to return with an error in that situation quickly.
synchronized(sockLock) {
mySockets.retainAll(sockMgr.listSockets());
if (!forced && mySockets.size() != 0) {
l.log("There are still active connections!");
_log.debug("can't close: there are still active connections!");
for (Iterator it = mySockets.iterator(); it.hasNext();) {
l.log("->"+it.next());
}
return false;
}
l.log("Closing client "+toString());
try {
if (ss != null) ss.close();
} catch (IOException ex) {
ex.printStackTrace();
return false;
}
l.log("Client closed.");
open=false;
return true;
}
}
public static void closeSocket(Socket s) {
try {
s.close();
} catch (IOException ex) {
_log.error("Could not close socket", ex);
}
}
public class ClientConnectionRunner extends Thread {
private Socket s;
public ClientConnectionRunner(Socket s, String name) {
this.s=s;
setName(name);
start();
}
public void run() {
clientConnectionRun(s);
}
}
/**
* Manage a connection in a separate thread. This only works if
* you do not override manageConnection()
*/
protected abstract void clientConnectionRun(Socket s);
}

View File

@@ -0,0 +1,48 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* AWT gui since kaffe doesn't support swing yet
*/
public class I2PTunnelGUI extends Frame implements ActionListener, Logging {
TextField input;
TextArea log;
I2PTunnel t;
public I2PTunnelGUI(I2PTunnel t) {
super("I2PTunnel control panel");
this.t=t;
setLayout(new BorderLayout());
add("South", input=new TextField());
input.addActionListener(this);
Font font = new Font("Monospaced",Font.PLAIN,12);
add("Center",log=new TextArea("",20,80,TextArea.SCROLLBARS_VERTICAL_ONLY));
log.setFont(font);
log.setEditable(false);
log("enter 'help' for help.");
pack();
show();
}
public void log(String s) {
log.append(s+"\n");
}
public void actionPerformed(ActionEvent evt) {
log("I2PTunnel>"+input.getText());
t.runCommand(input.getText(), this);
log("---");
input.setText("");
}
}

View File

@@ -0,0 +1,334 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
public class I2PTunnelHTTPClient extends I2PTunnelClientBase
implements Runnable {
private static final Log _log =
new Log(I2PTunnelHTTPClient.class);
private String wwwProxy;
private final static byte[] ERR_REQUEST_DENIED = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: REQUEST DENIED</H1>You attempted to connect to a non-I2P website or location.<BR>".getBytes();
private final static byte[] ERR_DESTINATION_UNKNOWN = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: NOT FOUND</H1>That Desitination was not found. Perhaps you pasted in the wrong BASE64 I2P Destination or the link you are following is bad. The host (or the WWW proxy, if you're using one) could also be temporarily offline. Could not find the following Destination:<BR><BR>".getBytes();
private final static byte[] ERR_TIMEOUT = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=iso-8859-1\r\nCache-control: no-cache\r\n\r\n<html><body><H1>I2P ERROR: TIMEOUT</H1>That Desitination was reachable, but timed out getting a response. This may be a temporary error, so you should simply try to refresh, though if the problem persists, the remote destination may have issues. Could not get a response from the following Destination:<BR><BR>".getBytes();
//public I2PTunnelHTTPClient(int localPort, Logging l,
// boolean ownDest,
// String wwwProxy) {
// I2PTunnelHTTPClient(localPort, l, ownDest, wwwProxy,
// (EventDispatcher)null);
//}
public I2PTunnelHTTPClient(int localPort, Logging l,
boolean ownDest,
String wwwProxy, EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openHTTPClientResult", "error");
return;
}
this.wwwProxy = wwwProxy;
setName(getLocalPort()
+ " -> HTTPClient [WWW outproxy: " + this.wwwProxy + "]");
startRunning();
notifyEvent("openHTTPClientResult", "ok");
}
protected void clientConnectionRun(Socket s) {
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
InactivityTimeoutThread timeoutThread = null;
try {
out = s.getOutputStream();
BufferedReader br = new BufferedReader
(new InputStreamReader(s.getInputStream(),
"ISO-8859-1"));
String line, method=null, protocol=null, host=null, destination=null;
StringBuffer newRequest=new StringBuffer();
while ((line=br.readLine()) != null) {
if (method==null) { // first line (GET /base64/realaddr)
int pos=line.indexOf(" ");
if (pos == -1) break;
method=line.substring(0, pos);
String request = line.substring(pos+1);
if (request.startsWith("/") &&
System.getProperty("i2ptunnel.noproxy") != null) {
request="http://i2p"+request;
}
pos = request.indexOf("//");
if (pos == -1) {
method=null;
break;
}
protocol=request.substring(0,pos+2);
request=request.substring(pos+2);
targetRequest = request;
pos = request.indexOf("/");
if (pos == -1) {
method=null;
break;
}
host=request.substring(0,pos);
// Quick hack for foo.bar.i2p
if (host.toLowerCase().endsWith( ".i2p")) {
destination=host;
host=getHostName(destination);
line=method+" "+request.substring(pos);
} else if (host.indexOf(".") != -1) {
// The request must be forwarded to a WWW proxy
destination = wwwProxy;
usingWWWProxy = true;
} else {
request=request.substring(pos+1);
pos = request.indexOf("/");
destination=request.substring(0,pos);
line=method+" "+request.substring(pos);
}
boolean isValid = usingWWWProxy ||
isSupportedAddress(host, protocol);
if (!isValid) {
if (_log.shouldLog(Log.INFO))
_log.info("notValid(" + host + ")");
method=null;
destination=null;
break;
} else if (!usingWWWProxy) {
if (_log.shouldLog(Log.INFO))
_log.info("host=getHostName(" + destination + ")");
host=getHostName(destination); // hide original host
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("METHOD:"+method+":");
_log.debug("PROTOC:"+protocol+":");
_log.debug("HOST :"+host+":");
_log.debug("DEST :"+destination+":");
}
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
line="Host: "+host;
if (_log.shouldLog(Log.INFO))
_log.info("Setting host = " + host);
}
newRequest.append(line).append("\r\n"); // HTTP spec
if (line.length()==0) break;
}
while (br.ready()) { // empty the buffer (POST requests)
int i=br.read();
if (i != -1) {
newRequest.append((char)i);
}
}
if (method==null || destination==null) {
l.log("No HTTP method found in the request.");
if (out != null) {
out.write(ERR_REQUEST_DENIED);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
Destination dest=I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve "+destination+".");
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest,
usingWWWProxy, destination);
s.close();
return;
}
String remoteID;
I2PSocket i2ps = createI2PSocket(dest);
byte[] data=newRequest.toString().getBytes("ISO-8859-1");
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data);
timeoutThread = new InactivityTimeoutThread(runner, out, targetRequest, usingWWWProxy, s);
timeoutThread.start();
} catch (IOException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.error("Error sending syn", ex);
handleHTTPClientException(ex, out, targetRequest,
usingWWWProxy, wwwProxy);
closeSocket(s);
} catch (I2PException ex) {
if (timeoutThread != null) timeoutThread.disable();
_log.info("Error sending syn", ex);
l.log("Unable to reach peer");
handleHTTPClientException(ex, out, targetRequest,
usingWWWProxy, wwwProxy);
closeSocket(s);
}
}
private static final long INACTIVITY_TIMEOUT = 120*1000;
private class InactivityTimeoutThread extends I2PThread {
private Socket s;
private I2PTunnelRunner _runner;
private OutputStream _out;
private String _targetRequest;
private boolean _useWWWProxy;
private boolean _disabled;
private Object _disableLock = new Object();
public InactivityTimeoutThread(I2PTunnelRunner runner, OutputStream out, String targetRequest, boolean useWWWProxy, Socket s) {
this.s=s;
_runner = runner;
_out = out;
_targetRequest = targetRequest;
_useWWWProxy = useWWWProxy;
_disabled = false;
}
public void disable() {
_disabled = true;
synchronized (_disableLock) { _disableLock.notifyAll(); }
}
public void run() {
while (!_disabled) {
if (_runner.isFinished()) {
if (_log.shouldLog(Log.INFO))
_log.info("HTTP client request completed prior to timeout");
return;
}
if (_runner.getLastActivityOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
if (_runner.getStartedOn() < Clock.getInstance().now() - INACTIVITY_TIMEOUT) {
if (_log.shouldLog(Log.WARN))
_log.warn("HTTP client request timed out (lastActivity: " + new Date(_runner.getLastActivityOn()) + ", startedOn: " + new Date(_runner.getLastActivityOn()) + ")");
timeout();
return;
} else {
// runner hasn't been going to long enough
}
} else {
// there has been activity in the period
}
synchronized (_disableLock) {
try {
_disableLock.wait(INACTIVITY_TIMEOUT);
} catch (InterruptedException ie) {}
}
}
}
private void timeout() {
_log.info("Inactivity timeout reached");
l.log("Inactivity timeout reached");
if (_out != null) {
try {
if (_runner.getLastActivityOn() > 0) {
// some data has been sent, so don't 404 it
} else {
writeErrorMessage(ERR_TIMEOUT, _out, _targetRequest,
_useWWWProxy, wwwProxy);
}
} catch (IOException ioe) {
_log.warn("Error writing out the 'timeout' message", ioe);
}
} else {
_log.warn("Client disconnected before we could say we timed out");
}
closeSocket(s);
}
}
private final static String getHostName(String host) {
try {
Destination dest=I2PTunnel.destFromName(host);
if (dest == null) return "i2p";
return dest.toBase64();
} catch (DataFormatException dfe) {
return "i2p";
}
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out,
String targetRequest,
boolean usingWWWProxy,
String wwwProxy)
throws IOException {
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
out.write(targetRequest.getBytes());
if (usingWWWProxy)
out.write(("<br>WWW proxy: " +
wwwProxy).getBytes());
}
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
}
private static void handleHTTPClientException (Exception ex, OutputStream out,
String targetRequest,
boolean usingWWWProxy,
String wwwProxy) {
if (out != null) {
try {
writeErrorMessage(ERR_DESTINATION_UNKNOWN, out, targetRequest,
usingWWWProxy, wwwProxy);
} catch (IOException ioe) {
_log.warn("Error writing out the 'destination was unknown' "+
"message", ioe);
}
} else {
_log.warn("Client disconnected before we could say that destination "+
"was unknown", ex);
}
}
private final static String SUPPORTED_HOSTS[] = { "i2p", "www.i2p.com",
"i2p." };
private boolean isSupportedAddress(String host, String protocol) {
if ( (host == null) || (protocol == null) ) return false;
boolean found = false;
String lcHost = host.toLowerCase();
for (int i = 0; i < SUPPORTED_HOSTS.length; i++) {
if (SUPPORTED_HOSTS[i].equals(lcHost)) {
found = true;
break;
}
}
if (!found) {
try {
Destination d = I2PTunnel.destFromName(host);
if (d == null) return false;
} catch (DataFormatException dfe) {}
}
return protocol.equalsIgnoreCase("http://");
}
}

View File

@@ -0,0 +1,184 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.util.Log;
import net.i2p.util.Clock;
public class I2PTunnelRunner extends Thread {
private final static Log _log = new Log(I2PTunnelRunner.class);
/**
* max bytes streamed in a packet - smaller ones might be filled
* up to this size. Larger ones are not split (at least not on
* Sun's impl of BufferedOutputStream), but that is the streaming
* api's job...
*/
static int MAX_PACKET_SIZE = 1024*32;
static final int NETWORK_BUFFER_SIZE = MAX_PACKET_SIZE;
private Socket s;
private I2PSocket i2ps;
Object slock, finishLock = new Object();
boolean finished=false;
HashMap ostreams, sockets;
I2PSession session;
byte[] initialData;
/** when the last data was sent/received (or -1 if never) */
private long lastActivityOn;
/** when the runner started up */
private long startedOn;
public I2PTunnelRunner(Socket s, I2PSocket i2ps, Object slock,
byte[] initialData) {
this.s=s;
this.i2ps=i2ps;
this.slock=slock;
this.initialData = initialData;
lastActivityOn = -1;
startedOn = -1;
_log.info("I2PTunnelRunner started");
setName("I2PTunnelRunner");
start();
}
/**
* have we closed at least one (if not both) of the streams
* [aka we're done running the streams]?
*
*/
public boolean isFinished() { return finished; }
/**
* When was the last data for this runner sent or received? (-1 if no data
* has been transferred yet)
*
*/
public long getLastActivityOn() { return lastActivityOn; }
private void updateActivity() { lastActivityOn = Clock.getInstance().now(); }
/**
* When this runner started up transferring data
*
*/
public long getStartedOn() { return startedOn; }
public void run() {
startedOn = Clock.getInstance().now();
try {
InputStream in = s.getInputStream();
OutputStream out = new BufferedOutputStream(s.getOutputStream(),
NETWORK_BUFFER_SIZE);
InputStream i2pin = i2ps.getInputStream();
OutputStream i2pout = new BufferedOutputStream
(i2ps.getOutputStream(), MAX_PACKET_SIZE);
if (initialData != null) {
synchronized(slock) {
i2pout.write(initialData);
i2pout.flush();
}
}
Thread t1 = new StreamForwarder(in, i2pout);
Thread t2 = new StreamForwarder(i2pin, out);
synchronized(finishLock) {
while (!finished) {
finishLock.wait();
}
}
// now one connection is dead - kill the other as well.
s.close();
s = null;
i2ps.close();
i2ps = null;
t1.join();
t2.join();
} catch (InterruptedException ex) {
_log.error("Interrupted", ex);
} catch (IOException ex) {
ex.printStackTrace();
_log.error("Error forwarding", ex);
} finally {
try {
if (s != null) s.close();
if (i2ps != null) i2ps.close();
} catch (IOException ex) {
ex.printStackTrace();
_log.error("Could not close socket", ex);
}
}
}
private class StreamForwarder extends Thread {
InputStream in;
OutputStream out;
private StreamForwarder(InputStream in, OutputStream out) {
this.in=in;
this.out=out;
setName("StreamForwarder");
start();
}
public void run() {
byte[] buffer = new byte[NETWORK_BUFFER_SIZE];
try {
int len;
while ((len=in.read(buffer)) != -1) {
out.write(buffer, 0, len);
if (len > 0)
updateActivity();
if (in.available()==0) {
try {
Thread.sleep(I2PTunnel.PACKET_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (in.available()==0) {
out.flush(); // make sure the data get though
}
}
} catch (SocketException ex) {
// this *will* occur when the other threads closes the socket
synchronized(finishLock) {
if (!finished)
_log.error("Error reading and writing", ex);
else
_log.warn("You may ignore this", ex);
}
} catch (IOException ex) {
if (!finished)
_log.error("Error forwarding", ex);
else
_log.warn("You may ignore this", ex);
} finally {
try {
out.close();
in.close();
} catch (IOException ex) {
_log.error("Error closing streams", ex);
}
synchronized(finishLock) {
finished=true;
finishLock.notifyAll();
// the main thread will close sockets etc. now
}
}
}
}
}

View File

@@ -0,0 +1,138 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.*;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.data.Base64;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2PTunnelServer extends I2PTunnelTask
implements Runnable {
private final static Log _log = new Log(I2PTunnelServer.class);
private I2PSocketManager sockMgr;
private I2PServerSocket i2pss;
private Object lock = new Object(), slock = new Object();
private InetAddress remoteHost;
private int remotePort;
private Logging l;
public I2PTunnelServer(InetAddress host, int port,
String privData, Logging l,
EventDispatcher notifyThis) {
super(host+":"+port+" <- "+privData, notifyThis);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
init(host, port, bais, privData, l);
}
public I2PTunnelServer(InetAddress host, int port,
File privkey, String privkeyname,
Logging l, EventDispatcher notifyThis) {
super(host+":"+port+" <- "+privkeyname, notifyThis);
try {
init(host, port, new FileInputStream(privkey), privkeyname, l);
} catch (IOException ioe) {
_log.error("Error starting server", ioe);
notifyEvent("openServerResult", "error");
}
}
public I2PTunnelServer(InetAddress host, int port,
InputStream privData, String privkeyname,
Logging l, EventDispatcher notifyThis) {
super(host+":"+port+" <- "+privkeyname, notifyThis);
init(host, port, privData, privkeyname, l);
}
private void init(InetAddress host, int port, InputStream privData,
String privkeyname, Logging l) {
this.l=l;
this.remoteHost=host;
this.remotePort=port;
I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
props.putAll(System.getProperties());
synchronized(slock) {
sockMgr = I2PSocketManagerFactory.createManager
(privData, I2PTunnel.host,
Integer.parseInt(I2PTunnel.port), props);
}
l.log("Ready!");
notifyEvent("openServerResult", "ok");
open=true;
Thread t = new Thread(this);
t.setName("Server");
t.start();
}
public boolean close(boolean forced) {
if (!open) return true;
synchronized(lock) {
if (!forced && sockMgr.listSockets().size() != 0) {
l.log("There are still active connections!");
for (Iterator it = sockMgr.listSockets().iterator();
it.hasNext();) {
l.log("->"+it.next());
}
return false;
}
l.log("Shutting down server "+toString());
try {
if (i2pss != null) i2pss.close();
sockMgr.getSession().destroySession();
} catch (I2PException ex) {
_log.error("Error destroying the session", ex);
System.exit(1);
}
l.log("Server shut down.");
open=false;
return true;
}
}
public void run() {
try {
I2PServerSocket i2pss = sockMgr.getServerSocket();
while (true) {
I2PSocket i2ps = i2pss.accept();
//local is fast, so synchronously. Does not need that many
//threads.
try {
Socket s = new Socket(remoteHost, remotePort);
new I2PTunnelRunner(s, i2ps, slock, null);
} catch (SocketException ex) {
i2ps.close();
}
}
} catch (I2PException ex) {
_log.error("Error while waiting for I2PConnections", ex);
} catch (IOException ex) {
_log.error("Error while waiting for I2PConnections", ex);
}
}
}

View File

@@ -0,0 +1,75 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.util.Set;
import net.i2p.client.I2PSession;
import net.i2p.util.EventDispatcher;
import net.i2p.util.EventDispatcherImpl;
/**
* Either a Server or a Client.
*/
public abstract class I2PTunnelTask implements EventDispatcher {
private final EventDispatcherImpl _event = new EventDispatcherImpl();
private int id;
private String name;
protected boolean open;
private I2PTunnel tunnel;
//protected I2PTunnelTask(String name) {
// I2PTunnelTask(name, (EventDispatcher)null);
//}
protected I2PTunnelTask(String name, EventDispatcher notifyThis) {
attachEventDispatcher(notifyThis);
this.name=name;
this.id = -1;
}
/** for apps that use multiple I2PTunnel instances */
public void setTunnel(I2PTunnel pTunnel) { tunnel = pTunnel; }
public int getId() {
return this.id;
}
public boolean isOpen() {return open;}
public void setId(int id) {
this.id = id;
}
protected void setName(String name) {
this.name=name;
}
protected void routerDisconnected() { tunnel.routerDisconnected(); }
public abstract boolean close(boolean forced);
public void disconnected(I2PSession session) { routerDisconnected(); }
public void errorOccurred(I2PSession session, String message,
Throwable error) {}
public void reportAbuse(I2PSession session, int severity) {}
public String toString() {
return name;
}
/* Required by the EventDispatcher interface */
public EventDispatcher getEventDispatcher() { return _event; }
public void attachEventDispatcher(EventDispatcher e) { _event.attachEventDispatcher(e.getEventDispatcher()); }
public void detachEventDispatcher(EventDispatcher e) { _event.detachEventDispatcher(e.getEventDispatcher()); }
public void notifyEvent(String e, Object a) { _event.notifyEvent(e,a); }
public Object getEventValue(String n) { return _event.getEventValue(n); }
public Set getEvents() { return _event.getEvents(); }
public void ignoreEvents() { _event.ignoreEvents(); }
public void unIgnoreEvents() { _event.unIgnoreEvents(); }
public Object waitEventValue(String n) { return _event.waitEventValue(n); }
}

View File

@@ -0,0 +1,228 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2Ping extends I2PTunnelTask implements Runnable {
private final static Log _log = new Log(I2Ping.class);
private static final int PING_COUNT = 3;
private static final int CPING_COUNT = 5;
private static final int PING_TIMEOUT= 5000;
private static final long PING_DISTANCE=1000;
private int MAX_SIMUL_PINGS=10; // not really final...
private boolean countPing=false;
private I2PSocketManager sockMgr;
private Logging l;
private boolean finished=false;
private String command;
private long timeout = PING_TIMEOUT;
private Object simulLock = new Object();
private int simulPings = 0;
private long lastPingTime = 0;
private Object lock = new Object(), slock = new Object();
//public I2Ping(String cmd, Logging l,
// boolean ownDest) {
// I2Ping(cmd, l, (EventDispatcher)null);
//}
public I2Ping(String cmd, Logging l,
boolean ownDest, EventDispatcher notifyThis) {
super("I2Ping ["+cmd+"]", notifyThis);
this.l=l;
command=cmd;
synchronized(slock) {
if (ownDest) {
sockMgr = I2PTunnelClient.buildSocketManager();
} else {
sockMgr = I2PTunnelClient.getSocketManager();
}
}
Thread t = new Thread(this);
t.setName("Client");
t.start();
open=true;
}
public void run() {
l.log("*** I2Ping results:");
try {
runCommand(command);
} catch (InterruptedException ex) {
l.log("*** Interrupted");
_log.error("Pinger interrupted",ex);
} catch (IOException ex) {
_log.error("Pinger exception",ex);
}
l.log("*** Finished.");
synchronized(lock) {
finished=true;
}
close(false);
}
public void runCommand(String cmd) throws InterruptedException,
IOException {
if (cmd.startsWith("-t ")) { // timeout
cmd = cmd.substring(3);
int pos = cmd.indexOf(" ");
if (pos == -1) {
l.log("Syntax error");
return;
} else {
timeout = Long.parseLong(cmd.substring(0, pos));
cmd=cmd.substring(pos+1);
}
}
if (cmd.startsWith("-m ")) { // max simultaneous pings
cmd = cmd.substring(3);
int pos = cmd.indexOf(" ");
if (pos == -1) {
l.log("Syntax error");
return;
} else {
MAX_SIMUL_PINGS = Integer.parseInt(cmd.substring(0, pos));
cmd=cmd.substring(pos+1);
}
}
if (cmd.startsWith("-c ")) { // "count" ping
countPing=true;
cmd=cmd.substring(3);
}
if (cmd.equals("-h")) { // ping all hosts
cmd="-l hosts.txt";
}
if (cmd.startsWith("-l ")) { // ping a list of hosts
BufferedReader br = new BufferedReader
(new FileReader(cmd.substring(3)));
String line;
List pingHandlers = new ArrayList();
while ((line = br.readLine()) != null) {
if (line.startsWith("#")) continue; // comments
if (line.startsWith(";")) continue;
if (line.startsWith("!")) continue;
if (line.indexOf("=") != -1) { // maybe file is hosts.txt?
line=line.substring(0,line.indexOf("="));
}
pingHandlers.add(new PingHandler(line));
}
br.close();
for (Iterator it= pingHandlers.iterator(); it.hasNext(); ) {
Thread t = (Thread) it.next();
t.join();
}
} else {
Thread t = new PingHandler(cmd);
t.join();
}
}
public boolean close(boolean forced) {
if (!open) return true;
synchronized(lock) {
if (!forced && !finished) {
l.log("There are still pings running!");
return false;
}
l.log("Closing pinger "+toString());
l.log("Pinger closed.");
open=false;
return true;
}
}
public boolean ping(Destination dest) throws I2PException {
try {
synchronized(simulLock) {
while (simulPings >= MAX_SIMUL_PINGS) {
simulLock.wait();
}
simulPings++;
while (lastPingTime + PING_DISTANCE >
System.currentTimeMillis()) {
// no wait here, to delay all pingers
Thread.sleep(PING_DISTANCE/2);
}
lastPingTime=System.currentTimeMillis();
}
boolean sent = sockMgr.ping(dest, PING_TIMEOUT);
synchronized(simulLock) {
simulPings--;
simulLock.notifyAll();
}
return sent;
} catch (InterruptedException ex) {
_log.error("Interrupted", ex);
return false;
}
}
public class PingHandler extends Thread {
private String destination;
public PingHandler(String dest) {
this.destination=dest;
setName("PingHandler for " + dest);
start();
}
public void run() {
try {
Destination dest=I2PTunnel.destFromName(destination);
if (dest == null) {
synchronized(lock) { // Logger is not thread safe
l.log("Unresolvable: "+destination+"");
}
return;
}
int cnt = countPing ? CPING_COUNT : PING_COUNT;
StringBuffer pingResults = new StringBuffer
(2*cnt+ destination.length()+3);
for (int i=0;i<cnt; i++) {
boolean sent;
sent = ping(dest);
if (countPing) {
if (!sent) {
pingResults.append(i).append(" ");
break;
} else if (i == cnt - 1) {
pingResults.append("+ ");
}
} else {
pingResults.append(sent?"+ ":"- ");
}
// System.out.println(sent+" -> "+destination);
}
pingResults.append(" ").append(destination);
synchronized(lock) { // Logger is not thread safe
l.log(pingResults.toString());
}
} catch (I2PException ex) {
_log.error("Error pinging " + destination, ex);
}
}
}
}

View File

@@ -0,0 +1,9 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
public interface Logging {
public void log(String s);
}

View File

@@ -0,0 +1,433 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Quick and dirty socket listener to control an I2PTunnel.
* Basically run this class as TunnelManager [listenHost] [listenPort] and
* then send it commands on that port. Commands are one shot deals -
* Send a command + newline, get a response plus newline, then get disconnected.
* <p />
* <b>Implemented commands:</b>
* <pre>
* -------------------------------------------------
* lookup &lt;name&gt;\n
* --
* &lt;base64 of the destination&gt;\n
* or
* &lt;error message, usually 'Unknown host'&gt;\n
*
* Lookup the public key of a named destination (i.e. listed in hosts.txt)
* -------------------------------------------------
* genkey\n
* --
* &lt;base64 of the destination&gt;\t&lt;base64 of private data&gt;\n
*
* Generates a new public and private key pair
* -------------------------------------------------
* convertprivate &lt;base64 of privkey&gt;
* --
* &lt;base64 of destination&gt;\n
* or
* &lt;error message&gt;\n
*
* Returns the destination (pubkey) of a given private key.
* -------------------------------------------------
* listen_on &lt;ip&gt;\n
* --
* ok\n
* or
* error\n
*
* Sets the ip address clients will listen on. By default this is the
* localhost (127.0.0.1)
* -------------------------------------------------
* openclient &lt;listenPort&gt; &lt;peer&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open a tunnel on the given &lt;listenport&gt; to the destination specified
* by &lt;peer&gt;. If &lt;listenPort&gt; is 0 a free port is picked and returned in
* the reply message. Otherwise the short reply message is used.
* Peer can be the base64 of the destination, a file with the public key
* specified as 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and &lt;listenport&gt; can
* later be used as argument for the "close" command.
* -------------------------------------------------
* openhttpclient &lt;listenPort&gt; [&lt;proxy&gt;]\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an HTTP proxy through the I2P on the given
* &lt;listenport&gt;. &lt;proxy&gt; (optional) specifies a
* destination to be used as an outbound proxy, to access normal WWW
* sites out of the .i2p domain. If &lt;listenPort&gt; is 0 a free
* port is picked and returned in the reply message. Otherwise the
* short reply message is used. &lt;proxy&gt; can be the base64 of the
* destination, a file with the public key specified as
* 'file:&lt;filename&gt;' or the name of a destination listed in
* hosts.txt. The &lt;jobId&gt; returned together with "ok" and
* &lt;listenport&gt; can later be used as argument for the "close"
* command.
* -------------------------------------------------
* opensockstunnel &lt;listenPort&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* ok &lt;listenPort&gt; [&lt;jobId&gt;]\n
* or
* error\n
*
* Open an SOCKS tunnel through the I2P on the given
* &lt;listenport&gt;. If &lt;listenPort&gt; is 0 a free port is
* picked and returned in the reply message. Otherwise the short
* reply message is used. The &lt;jobId&gt; returned together with
* "ok" and &lt;listenport&gt; can later be used as argument for the
* "close" command.
* -------------------------------------------------
* openserver &lt;serverHost&gt; &lt;serverPort&gt; &lt;serverKeys&gt;\n
* --
* ok [&lt;jobId&gt;]\n
* or
* error\n
*
* Starts receiving traffic for the destination specified by &lt;serverKeys&gt;
* and forwards it to the &lt;serverPort&gt; of &lt;serverHost&gt;.
* &lt;serverKeys&gt; is the base 64 encoded private key set of the local
* destination. The &lt;joId&gt; returned together with "ok" can later be used
* as argument for the "close" command.
* -------------------------------------------------
* close [forced] &lt;jobId&gt;\n
* or
* close [forced] all\n
* --
* ok\n
* or
* error\n
*
* Closes the job specified by &lt;jobId&gt; or all jobs. Use the list command
* for a list of running jobs.
* Normally a connection job is not closed when it still has an active
* connection. Use the optional 'forced' keyword to close connections
* regardless of their use.
* -------------------------------------------------
* list\n
* --
* Example output:
*
* [0] i2p.dnsalias.net/69.55.226.145:5555 &lt;- C:\i2pKeys\squidPriv
* [1] 8767 -&gt; HTTPClient
* [2] 7575 -&gt; file:C:\i2pKeys\squidPub
* [3] 5252 -&gt; sCcSANIO~f4AQtCNI1BvDp3ZBS~9Ag5O0k0Msm7XBWWz5eOnZWL3MQ-2rxlesucb9XnpASGhWzyYNBpWAfaIB3pux1J1xujQLOwscMIhm7T8BP76Ly5jx6BLZCYrrPj0BI0uV90XJyT~4UyQgUlC1jzFQdZ9HDgBPJDf1UI4-YjIwEHuJgdZynYlQ1oUFhgno~HhcDByXO~PDaO~1JDMDbBEfIh~v6MgmHp-Xchod1OfKFrxFrzHgcJbn7E8edTFjZA6JCi~DtFxFelQz1lSBd-QB1qJnA0g-pVL5qngNUojXJCXs4qWcQ7ICLpvIc-Fpfj-0F1gkVlGDSGkb1yLH3~8p4czYgR3W5D7OpwXzezz6clpV8kmbd~x2SotdWsXBPRhqpewO38coU4dJG3OEUbuYmdN~nJMfWbmlcM1lXzz2vBsys4sZzW6dV3hZnbvbfxNTqbdqOh-KXi1iAzXv7CVTun0ubw~CfeGpcAqutC5loRUq7Mq62ngOukyv8Z9AAAA
*
* Lists descriptions of all running jobs. The exact format of the
* description depends on the type of job.
* -------------------------------------------------
* </pre>
*/
public class TunnelManager implements Runnable {
private final static Log _log = new Log(TunnelManager.class);
private I2PTunnel _tunnel;
private ServerSocket _socket;
private boolean _keepAccepting;
public TunnelManager(int listenPort) {
this(null, listenPort);
}
public TunnelManager(String listenHost, int listenPort) {
_tunnel = new I2PTunnel();
_keepAccepting = true;
try {
if (listenHost != null) {
_socket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
_log.info("Listening for tunnel management clients on " + listenHost + ":" + listenPort);
} else {
_socket = new ServerSocket(listenPort);
_log.info("Listening for tunnel management clients on localhost:" + listenPort);
}
} catch (Exception e) {
_log.error("Error starting up tunnel management listener on " + listenPort, e);
}
}
public static void main(String args[]) {
int port = 7676;
String host = null;
if (args.length == 1) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
} else if (args.length == 2) {
host = args[0];
try {
port = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
_log.error("Usage: TunnelManager [host] [port]");
return;
}
}
TunnelManager mgr = new TunnelManager(host, port);
Thread t = new Thread(mgr, "Listener");
t.start();
}
public void run() {
if (_socket == null) {
_log.error("Unable to start listening, since the socket was not bound. Already running?");
return;
}
_log.debug("Running");
try {
while (_keepAccepting) {
Socket socket = _socket.accept();
_log.debug("Client accepted");
if (socket != null) {
Thread t = new I2PThread(new TunnelManagerClientRunner(this, socket));
t.setName("TunnelManager Client");
t.setPriority(I2PThread.MIN_PRIORITY);
t.start();
}
}
} catch (IOException ioe) {
_log.error("Error accepting connections", ioe);
} catch (Exception e) {
_log.error("Other error?!", e);
} finally {
if (_socket != null) try { _socket.close(); } catch (IOException ioe) {}
}
try { Thread.sleep(5000); } catch (InterruptedException ie) {}
}
public void error(String msg, OutputStream out) throws IOException {
out.write(msg.getBytes());
out.write('\n');
}
public void processQuit(OutputStream out) throws IOException {
out.write("Nice try".getBytes());
out.write('\n');
}
public void processList(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
long startCommand = Clock.getInstance().now();
_tunnel.runCommand("list", buf);
Object obj = _tunnel.waitEventValue("listDone");
long endCommand = Clock.getInstance().now();
String str = buf.getBuffer();
_log.debug("ListDone complete after " + (endCommand-startCommand) + "ms: [" + str + "]");
out.write(str.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processListenOn(String ip, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("listen_on " + ip, buf);
String status = (String)_tunnel.waitEventValue("listen_onResult");
out.write((status + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "lookup <name>" returns with the result in base64, else "Unknown host" [or something like that],
* then a newline.
*
*/
public void processLookup(String name, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("lookup " + name, buf);
String rv = (String)_tunnel.waitEventValue("lookupResult");
out.write(rv.getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processTestDestination(String destKey, OutputStream out) throws IOException {
try {
Destination d = new Destination();
d.fromBase64(destKey);
out.write("valid\n".getBytes());
} catch (DataFormatException dfe) {
out.write("invalid\n".getBytes());
}
out.flush();
}
public void processConvertPrivate(String priv, OutputStream out) throws IOException {
try {
Destination dest = new Destination();
dest.fromBase64(priv);
String str = dest.toBase64();
out.write(str.getBytes());
out.write('\n');
} catch (DataFormatException dfe) {
_log.error("Error converting private data", dfe);
out.write("Error converting private key\n".getBytes());
}
}
public void processClose(String which, boolean forced, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand((forced?"close forced ":"close ") + which, buf);
String str = (String)_tunnel.waitEventValue("closeResult");
out.write((str + "\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* "genkey" returns with the base64 of the destination, followed by a tab, then the base64 of that
* destination's private keys, then a newline.
*
*/
public void processGenKey(OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("gentextkeys", buf);
String priv = (String)_tunnel.waitEventValue("privateKey");
String pub = (String)_tunnel.waitEventValue("publicDestination");
out.write((pub + "\t" + priv).getBytes());
out.write('\n');
buf.ignoreFurtherActions();
}
public void processOpenClient(int listenPort, String peer, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("client " + listenPort + " " + peer, buf);
Integer taskId = (Integer)_tunnel.waitEventValue("clientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String)_tunnel.waitEventValue("openClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
+ "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenHTTPClient(int listenPort,
String proxy,
OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("httpclient " + listenPort + " " + proxy, buf);
Integer taskId = (Integer)_tunnel.waitEventValue("httpclientTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String)_tunnel.waitEventValue("openHTTPClientResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
+ "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenSOCKSTunnel(int listenPort,
OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("sockstunnel " + listenPort, buf);
Integer taskId = (Integer)_tunnel.waitEventValue("sockstunnelTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String)_tunnel.waitEventValue("openSOCKSTunnelResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
if (listenPort != 0) {
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
return;
}
Integer port = (Integer)_tunnel.waitEventValue("clientLocalPort");
out.write((rv + " " + port.intValue() + " [" + taskId.intValue()
+ "]\n").getBytes());
buf.ignoreFurtherActions();
}
public void processOpenServer(String serverHost, int serverPort, String privateKeys, OutputStream out) throws IOException {
BufferLogger buf = new BufferLogger();
_tunnel.runCommand("textserver " + serverHost + " " + serverPort + " " + privateKeys, buf);
Integer taskId = (Integer)_tunnel.waitEventValue("serverTaskId");
if (taskId.intValue() < 0) {
out.write("error\n".getBytes());
buf.ignoreFurtherActions();
return;
}
String rv = (String)_tunnel.waitEventValue("openServerResult");
if (rv.equals("error")) {
out.write((rv + "\n").getBytes());
buf.ignoreFurtherActions();
return;
}
out.write((rv + " [" + taskId.intValue() + "]\n").getBytes());
buf.ignoreFurtherActions();
}
/**
* Frisbee.
*
*/
public void unknownCommand(String command, OutputStream out) throws IOException {
out.write("Unknown command: ".getBytes());
out.write(command.getBytes());
out.write("\n".getBytes());
}
}

View File

@@ -0,0 +1,193 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel;
import net.i2p.util.Log;
import java.util.StringTokenizer;
import java.net.Socket;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.OutputStream;
/**
* Runner thread that reads commands from the socket and fires off commands to
* the TunnelManager
*
*/
class TunnelManagerClientRunner implements Runnable {
private final static Log _log = new Log(TunnelManagerClientRunner.class);
private TunnelManager _mgr;
private Socket _clientSocket;
public TunnelManagerClientRunner(TunnelManager mgr, Socket socket) {
_clientSocket = socket;
_mgr = mgr;
}
public void run() {
_log.debug("Client running");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(_clientSocket.getInputStream()));
OutputStream out = _clientSocket.getOutputStream();
String cmd = reader.readLine();
if (cmd != null)
processCommand(cmd, out);
} catch (IOException ioe) {
_log.error("Error processing client commands", ioe);
} finally {
if (_clientSocket != null) try { _clientSocket.close(); } catch (IOException ioe) {}
}
_log.debug("Client closed");
}
/**
* Parse the command string and fire off the appropriate tunnelManager method,
* sending the results to the output stream
*/
private void processCommand(String command, OutputStream out) throws IOException {
_log.debug("Processing [" + command + "]");
StringTokenizer tok = new StringTokenizer(command);
if (!tok.hasMoreTokens()) {
_mgr.unknownCommand(command, out);
} else {
String cmd = tok.nextToken();
if ("quit".equalsIgnoreCase(cmd)) {
_mgr.processQuit(out);
} else if ("lookup".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processLookup(tok.nextToken(), out);
else
_mgr.error("Usage: lookup <hostname>", out);
} else if ("testdestination".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processTestDestination(tok.nextToken(), out);
else
_mgr.error("Usage: testdestination <publicDestination>", out);
} else if ("convertprivate".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens())
_mgr.processConvertPrivate(tok.nextToken(), out);
else
_mgr.error("Usage: convertprivate <privateData>", out);
} else if ("close".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
String closeArg;
if ((closeArg = tok.nextToken()).equals("forced")) {
if (tok.hasMoreTokens()) {
_mgr.processClose(tok.nextToken(), true, out);
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else {
_mgr.processClose(closeArg, false, out);
}
} else {
_mgr.error("Usage: close [forced] <jobnumber>|all", out);
}
} else if ("genkey".equalsIgnoreCase(cmd)) {
_mgr.processGenKey(out);
} else if ("list".equalsIgnoreCase(cmd)) {
_mgr.processList(out);
} else if ("listen_on".equalsIgnoreCase(cmd)) {
if (tok.hasMoreTokens()) {
_mgr.processListenOn(tok.nextToken(), out);
} else {
_mgr.error("Usage: listen_on <ip>", out);
}
} else if ("openclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String peer = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenPort> <peer>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> <peer>", out);
return;
}
peer = tok.nextToken();
_mgr.processOpenClient(listenPort, peer, out);
} else if ("openhttpclient".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String proxy = "squid.i2p";
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openhttpclient <listenPort> [<proxy>]",out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
proxy = tok.nextToken();
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: openclient <listenport> [<proxy>]",out);
return;
}
_mgr.processOpenHTTPClient(listenPort, proxy, out);
} else if ("opensockstunnel".equalsIgnoreCase(cmd)) {
int listenPort = 0;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenPort>",out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (tok.hasMoreTokens()) {
_mgr.error("Usage: opensockstunnel <listenport>",out);
return;
}
_mgr.processOpenSOCKSTunnel(listenPort, out);
} else if ("openserver".equalsIgnoreCase(cmd)) {
int listenPort = 0;
String serverHost = null;
String serverKeys = null;
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverHost = tok.nextToken();
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
try {
String portStr = tok.nextToken();
listenPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
_mgr.error("Bad listen port", out);
return;
}
if (!tok.hasMoreTokens()) {
_mgr.error("Usage: openserver <serverHost> <serverPort> <serverKeys>", out);
return;
}
serverKeys = tok.nextToken();
_mgr.processOpenServer(serverHost, listenPort, serverKeys, out);
} else {
_mgr.unknownCommand(command, out);
}
}
}
}

View File

@@ -0,0 +1,56 @@
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
import java.net.Socket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnelClientBase;
import net.i2p.i2ptunnel.I2PTunnelRunner;
import net.i2p.i2ptunnel.Logging;
import net.i2p.util.EventDispatcher;
import net.i2p.util.Log;
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PSOCKSTunnel.class);
protected Destination outProxyDest = null;
//public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) {
// I2PSOCKSTunnel(localPort, l, ownDest, (EventDispatcher)null);
//}
public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest,
EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "SOCKSHandler");
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openSOCKSTunnelResult", "error");
return;
}
setName(getLocalPort() + " -> SOCKSTunnel");
startRunning();
notifyEvent("openSOCKSTunnelResult", "ok");
}
protected void clientConnectionRun(Socket s) {
try {
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket();
new I2PTunnelRunner (clientSock, destSock, sockLock, null);
} catch (SOCKSException e) {
_log.error("Error from SOCKS connection: " + e.getMessage());
closeSocket(s);
}
}
}

View File

@@ -0,0 +1,309 @@
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/*
* Class that manages SOCKS5 connections, and forwards them to
* destination hosts or (eventually) some outproxy.
*
* @author human
*/
public class SOCKS5Server extends SOCKSServer {
private static final Log _log = new Log(SOCKS5Server.class);
private static final int SOCKS_VERSION_5 = 0x05;
private Socket clientSock = null;
private boolean setupCompleted = false;
/**
* Create a SOCKS5 server that communicates with the client using
* the specified socket. This method should not be invoked
* directly: new SOCKS5Server objects should be created by using
* SOCKSServerFactory.createSOCSKServer(). It is assumed that the
* SOCKS VER field has been stripped from the input stream of the
* client socket.
*
* @param clientSock client socket
*/
public SOCKS5Server(Socket clientSock) {
this.clientSock = clientSock;
}
public Socket getClientSocket() throws SOCKSException {
setupServer();
return clientSock;
}
protected void setupServer() throws SOCKSException {
if (setupCompleted) {
return;
}
DataInputStream in;
DataOutputStream out;
try {
in = new DataInputStream(clientSock.getInputStream());
out = new DataOutputStream(clientSock.getOutputStream());
init(in, out);
manageRequest(in, out);
} catch (IOException e) {
throw new SOCKSException("Connection error ("
+ e.getMessage() + ")");
}
setupCompleted = true;
}
/**
* SOCKS5 connection initialization. This method assumes that
* SOCKS "VER" field has been stripped from the input stream.
*/
private void init (DataInputStream in,
DataOutputStream out) throws IOException, SOCKSException {
int nMethods = in.readByte() & 0xff;
boolean methodOk = false;
int method = Method.NO_ACCEPTABLE_METHODS;
for (int i = 0; i < nMethods; ++i) {
method = in.readByte() & 0xff;
if (method == Method.NO_AUTH_REQUIRED) {
// That's fine, we do support this method
break;
}
}
boolean canContinue = false;
switch (method) {
case Method.NO_AUTH_REQUIRED:
_log.debug("no authentication required");
sendInitReply(Method.NO_AUTH_REQUIRED, out);
return;
default:
_log.debug("no suitable authentication methods found ("
+ Integer.toHexString(method)+ ")");
sendInitReply(Method.NO_ACCEPTABLE_METHODS, out);
throw new SOCKSException("Unsupported authentication method");
}
}
/**
* SOCKS5 request management. This method assumes that all the
* stuff preceding or enveloping the actual request (e.g. protocol
* initialization, integrity/confidentiality encapsulations, etc)
* has been stripped out of the input/output streams.
*/
private void manageRequest(DataInputStream in,
DataOutputStream out) throws IOException, SOCKSException {
int socksVer = in.readByte() & 0xff;
if (socksVer != SOCKS_VERSION_5) {
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
throw new SOCKSException("Invalid protocol version in request");
}
int command = in.readByte() & 0xff;
switch (command) {
case Command.CONNECT:
break;
case Command.BIND:
_log.debug("BIND command is not supported!");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED,
AddressType.DOMAINNAME, null,
"0.0.0.0", 0, out);
throw new SOCKSException("BIND command not supported");
case Command.UDP_ASSOCIATE:
_log.debug("UDP ASSOCIATE command is not supported!");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED,
AddressType.DOMAINNAME, null,
"0.0.0.0", 0, out);
throw new SOCKSException("UDP ASSOCIATE command not supported");
default:
_log.debug("unknown command in request ("
+ Integer.toHexString(command) + ")");
throw new SOCKSException("Invalid command in request");
}
{
// Reserved byte, should be 0x00
byte rsv = in.readByte();
}
int addressType = in.readByte() & 0xff;
switch (addressType) {
case AddressType.IPV4:
connHostName = new String("");
for (int i = 0; i < 4; ++i) {
int octet = in.readByte() & 0xff;
connHostName += Integer.toString(octet);
if (i != 3) {
connHostName += ".";
}
}
_log.warn("IPV4 address type in request: " + connHostName
+ ". Is your client secure?");
break;
case AddressType.DOMAINNAME:
{
int addrLen = in.readByte() & 0xff;
if (addrLen == 0) {
_log.debug("0-sized address length? wtf?");
throw new SOCKSException("Illegal DOMAINNAME length");
}
byte addr[] = new byte[addrLen];
in.readFully(addr);
connHostName = new String(addr);
}
_log.debug("DOMAINNAME address type in request: " + connHostName);
break;
case AddressType.IPV6:
_log.warn("IP V6 address type in request! Is your client secure?"
+ " (IPv6 is not supported, anyway :-)");
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED,
AddressType.DOMAINNAME, null,
"0.0.0.0", 0, out);
throw new SOCKSException("IPV6 addresses not supported");
default:
_log.debug("unknown address type in request ("
+ Integer.toHexString(command) + ")");
throw new SOCKSException("Invalid addresses type in request");
}
connPort = in.readUnsignedShort();
if (connPort == 0) {
_log.debug("trying to connect to TCP port 0? Dropping!");
throw new SOCKSException("Invalid port number in request");
}
}
protected void confirmConnection() throws SOCKSException {
DataInputStream in;
DataOutputStream out;
try {
out = new DataOutputStream(clientSock.getOutputStream());
sendRequestReply(Reply.SUCCEEDED,
AddressType.IPV4,
InetAddress.getByName("127.0.0.1"),
null, 1, out);
} catch (IOException e) {
throw new SOCKSException("Connection error ("
+ e.getMessage() + ")");
}
}
/**
* Send the specified reply during SOCKS5 initialization
*/
private void sendInitReply(int replyCode,
DataOutputStream out) throws IOException {
ByteArrayOutputStream reps = new ByteArrayOutputStream();
reps.write(SOCKS_VERSION_5);
reps.write(replyCode);
byte[] reply = reps.toByteArray();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Sending init reply:\n" + HexDump.dump(reply));
}
out.write(reply);
}
/**
* Send the specified reply to a request of the client. Either
* one of inetAddr or domainName can be null, depending on
* addressType.
*/
private void sendRequestReply(int replyCode,
int addressType,
InetAddress inetAddr,
String domainName,
int bindPort,
DataOutputStream out) throws IOException {
ByteArrayOutputStream reps = new ByteArrayOutputStream();
DataOutputStream dreps = new DataOutputStream(reps);
dreps.write(SOCKS_VERSION_5);
dreps.write(replyCode);
// Reserved byte, should be 0x00
dreps.write(0x00);
dreps.write(addressType);
switch (addressType) {
case AddressType.IPV4:
dreps.write(inetAddr.getAddress());
break;
case AddressType.DOMAINNAME:
dreps.writeByte(domainName.length());
dreps.writeBytes(domainName);
break;
default:
_log.error("unknown address type passed to sendReply() ("
+ Integer.toHexString(addressType) + ")! wtf?");
return;
}
dreps.writeShort(bindPort);
byte[] reply = reps.toByteArray();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
}
out.write(reply);
}
/*
* Some namespaces to enclose SOCKS protocol codes
*/
private class Method {
private static final int NO_AUTH_REQUIRED = 0x00;
private static final int NO_ACCEPTABLE_METHODS = 0xff;
}
private class AddressType {
private static final int IPV4 = 0x01;
private static final int DOMAINNAME = 0x03;
private static final int IPV6 = 0x04;
}
private class Command {
private static final int CONNECT = 0x01;
private static final int BIND = 0x02;
private static final int UDP_ASSOCIATE = 0x03;
}
private class Reply {
private static final int SUCCEEDED = 0x00;
private static final int GENERAL_SOCKS_SERVER_FAILURE = 0x01;
private static final int CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
private static final int NETWORK_UNREACHABLE = 0x03;
private static final int HOST_UNREACHABLE = 0x04;
private static final int CONNECTION_REFUSED = 0x05;
private static final int TTL_EXPIRED = 0x06;
private static final int COMMAND_NOT_SUPPORTED = 0x07;
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
}
}

View File

@@ -0,0 +1,23 @@
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
/**
* Exception thrown by socket methods
*
* @author human
*/
public class SOCKSException extends Exception {
public SOCKSException() {
super();
}
public SOCKSException(String s) {
super(s);
}
}

View File

@@ -0,0 +1,100 @@
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
import java.net.Socket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.I2PException;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.Log;
/**
* Abstract base class used by all SOCKS servers.
*
* @author human
*/
public abstract class SOCKSServer {
private static final Log _log = new Log(SOCKSServer.class);
/* Details about the connection requested by client */
protected String connHostName = null;
protected int connPort = 0;
I2PSocket destSocket = null;
Object FIXME = new Object();
/**
* Perform server initialization (expecially regarding protected
* variables).
*/
protected abstract void setupServer() throws SOCKSException;
/**
* Get a socket that can be used to send/receive 8-bit clean data
* to/from the client.
*
* @return a Socket connected with the client
*/
public abstract Socket getClientSocket() throws SOCKSException;
/**
* Confirm to the client that the connection has succeeded
*/
protected abstract void confirmConnection() throws SOCKSException;
/**
* Get an I2PSocket that can be used to send/receive 8-bit clean data
* to/from the destination of the SOCKS connection.
*
* @return an I2PSocket connected with the destination
*/
public I2PSocket getDestinationI2PSocket() throws SOCKSException {
setupServer();
if (connHostName == null) {
_log.error("BUG: destination host name has not been initialized!");
throw new SOCKSException("BUG! See the logs!");
}
if (connPort == 0) {
_log.error("BUG: destination port has not been initialized!");
throw new SOCKSException("BUG! See the logs!");
}
// FIXME: here we should read our config file, select an
// outproxy, and instantiate the proper socket class that
// handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...).
I2PSocket destSock;
try {
if (connHostName.toLowerCase().endsWith(".i2p")) {
_log.debug("connecting to " + connHostName + "...");
I2PSocketManager sm = I2PSocketManagerFactory.createManager();
destSock = sm.connect(I2PTunnel.destFromName(connHostName),
new I2PSocketOptions());
confirmConnection();
_log.debug("connection confirmed - exchanging data...");
} else {
_log.error("We don't support outproxies (yet)");
throw new SOCKSException("Ouproxies not supported (yet)");
}
} catch (DataFormatException e) {
throw new SOCKSException("Error in destination format");
} catch (I2PException e) {
throw new SOCKSException("I2P error (" + e.getMessage() + ")");
}
return destSock;
}
}

View File

@@ -0,0 +1,53 @@
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import net.i2p.util.Log;
/**
* Factory class for creating SOCKS forwarders through I2P
*/
public class SOCKSServerFactory {
private final static Log _log = new Log(SOCKSServerFactory.class);
/**
* Create a new SOCKS server, using the provided socket (that must
* be connected to a client) to select the proper SOCKS protocol
* version. This method wil strip the SOCKS VER field from the
* provided sockets's input stream.
*
* @param s a Socket used to choose the SOCKS server type
*/
public static SOCKSServer createSOCKSServer(Socket s) throws SOCKSException {
SOCKSServer serv;
try {
DataInputStream in = new DataInputStream(s.getInputStream());
int socksVer = in.readByte();
switch (socksVer) {
case 0x05: // SOCKS version 5
serv = new SOCKS5Server(s);
break;
default:
_log.debug("SOCKS protocol version not supported ("
+ Integer.toHexString(socksVer) + ")");
return null;
}
} catch (IOException e) {
_log.debug("error reading SOCKS protocol version");
throw new SOCKSException("Connection error ("
+ e.getMessage() + ")");
}
return serv;
}
}