explicit merge of '7bae8d314209ec279a4da918dc3255e31bda7e45'

and '3b133e76d8124df27791cb78006e7c2b9a8b6430'
This commit is contained in:
mkvore-commit
2009-04-02 08:57:46 +00:00
539 changed files with 12038 additions and 55442 deletions

14
apps/BOB/bob.config Normal file
View File

@@ -0,0 +1,14 @@
#bob.config
#Tue Dec 30 00:00:00 UTC 2008
# Please leave this file here for testing.
# Thank you,
# Sponge
i2cp.tcp.port=7654
BOB.host=localhost
inbound.lengthVariance=0
i2cp.messageReliability=BestEffort
BOB.port=45067
outbound.length=1
inbound.length=1
outbound.lengthVariance=0
i2cp.tcp.host=localhost

View File

@@ -1,5 +1,11 @@
application.title=BOB
application.vendor=root
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=false
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
@@ -76,6 +82,12 @@ javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
jnlp.codebase.type=local
jnlp.codebase.url=file:/root/NetBeansProjects/i2p.i2p/apps/BOB/dist/
jnlp.descriptor=application
jnlp.enabled=false
jnlp.offline-allowed=false
jnlp.signed=false
main.class=net.i2p.BOB.Main
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF

View File

@@ -34,7 +34,6 @@ import java.util.Properties;
import net.i2p.client.I2PClient;
import net.i2p.client.streaming.RetransmissionTimer;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* <span style="font-size:8px;font-family:courier;color:#EEEEEE;background-color:#000000">
* ################################################################################<br>
@@ -114,6 +113,8 @@ public class BOB {
public final static String PROP_BOB_HOST = "BOB.host";
private static int maxConnections = 0;
private static NamedDB database;
private static Properties props = new Properties();
/**
* Log a warning
@@ -157,11 +158,10 @@ public class BOB {
// Set up all defaults to be passed forward to other threads.
// Re-reading the config file in each thread is pretty damn stupid.
// I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
// This is here just to ensure there is no interference with our threadgroups.
SimpleTimer Y = RetransmissionTimer.getInstance();
RetransmissionTimer Y = RetransmissionTimer.getInstance();
i = Y.hashCode();
{
try {
@@ -216,6 +216,7 @@ public class BOB {
}
}
i = 0;
try {
info("BOB is now running.");
ServerSocket listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST)));

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@ public class I2Plistener implements Runnable {
boolean g = false;
I2PSocket sessSocket = null;
serverSocket.setSoTimeout(100);
serverSocket.setSoTimeout(50);
database.getReadLock();
info.getReadLock();
if(info.exists("INPORT")) {

View File

@@ -0,0 +1,56 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
* http://sam.zoy.org/wtfpl/
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
*/
package net.i2p.BOB;
import java.util.Enumeration;
import java.util.Properties;
/**
* Sets of "friendly" utilities to make life easier.
* Any "Lifted" code will apear here, and credits given.
* It's better to "Lift" a small chunk of "free" code than add in piles of
* code we don't need, and don't want.
*
* @author sponge
*/
public class Lifted {
/**
* Copy a set of properties from one Property to another.
* Lifted from Apache Derby code svn repository.
* Liscenced as follows:
* http://svn.apache.org/repos/asf/db/derby/code/trunk/LICENSE
*
* @param src_prop Source set of properties to copy from.
* @param dest_prop Dest Properties to copy into.
*
**/
public static void copyProperties(Properties src_prop, Properties dest_prop) {
for (Enumeration propertyNames = src_prop.propertyNames();
propertyNames.hasMoreElements();) {
Object key = propertyNames.nextElement();
dest_prop.put(key, src_prop.get(key));
}
}
}

View File

@@ -74,7 +74,10 @@ public class MUXlisten implements Runnable {
this.info.getReadLock();
N = this.info.get("NICKNAME").toString();
prikey = new ByteArrayInputStream((byte[])info.get("KEYS"));
Properties Q = (Properties)info.get("PROPERTIES");
// Make a new copy so that anything else won't muck with our database.
Properties R = (Properties)info.get("PROPERTIES");
Properties Q = new Properties();
Lifted.copyProperties(R, Q);
this.database.releaseReadLock();
this.info.releaseReadLock();
@@ -170,7 +173,7 @@ die: {
boolean spin = true;
while(spin) {
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
Thread.sleep(200); //sleep for 200 ms (Two thenths second)
} catch(InterruptedException e) {
// nop
}
@@ -210,14 +213,21 @@ die: {
}
} // die
try {
Thread.sleep(500); //sleep for 500 ms (One half second)
} catch(InterruptedException ex) {
// nop
}
// wait for child threads and thread groups to die
// System.out.println("MUXlisten: waiting for children");
while(tg.activeCount() + tg.activeGroupCount() != 0) {
if(tg.activeCount() + tg.activeGroupCount() != 0) {
tg.interrupt(); // unwedge any blocking threads.
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(InterruptedException ex) {
// nop
while(tg.activeCount() + tg.activeGroupCount() != 0) {
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(InterruptedException ex) {
// nop
}
}
}
tg.destroy();
@@ -234,7 +244,7 @@ die: {
System.out.println("BOB: MUXlisten: Please email the following dump to sponge@mail.i2p");
WrapperManager.requestThreadDump();
System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
System.out.println("BOB: MUXlisten: Please email the avove dump to sponge@mail.i2p");
System.out.println("BOB: MUXlisten: Please email the above dump to sponge@mail.i2p");
}
// zero out everything, just incase.
try {
@@ -257,17 +267,33 @@ die: {
}
// This is here to catch when something fucks up REALLY bad.
if(tg != null) {
while(tg.activeCount() + tg.activeGroupCount() != 0) {
if(tg.activeCount() + tg.activeGroupCount() != 0) {
tg.interrupt(); // unwedge any blocking threads.
while(tg.activeCount() + tg.activeGroupCount() != 0) {
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(InterruptedException ex) {
// nop
}
}
}
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
}
// Lastly try to close things again.
if(this.come_in) {
try {
listener.close();
} catch(IOException e) {
}
}
try {
socketManager.destroySocketManager();
} catch(Exception e) {
// nop
}
}
}

View File

@@ -24,7 +24,6 @@
package net.i2p.BOB;
import net.i2p.client.streaming.RetransmissionTimer;
import net.i2p.util.SimpleTimer;
/**
* Start from command line
@@ -39,8 +38,8 @@ public class Main {
*/
public static void main(String[] args) {
// THINK THINK THINK THINK THINK THINK
SimpleTimer Y = RetransmissionTimer.getInstance();
RetransmissionTimer Y = RetransmissionTimer.getInstance();
BOB.main(args);
Y.removeSimpleTimer();
Y.stop();
}
}

View File

@@ -56,9 +56,28 @@ public class TCPio implements Runnable {
* Copy from source to destination...
* and yes, we are totally OK to block here on writes,
* The OS has buffers, and I intend to use them.
* We send an interrupt signal to the threadgroup to
* unwedge any pending writes.
*
*/
public void run() {
/*
* NOTE:
* The write method of OutputStream calls the write method of
* one argument on each of the bytes to be written out.
* Subclasses are encouraged to override this method and provide
* a more efficient implementation.
*
* So, is this really a performance problem?
* Should we expand to several bytes?
* I don't believe there would be any gain, since read method
* has the same reccomendations. If anyone has a better way to
* do this, I'm interested in performance improvements.
*
* --Sponge
*
*/
int b;
byte a[] = new byte[1];
boolean spin = true;

View File

@@ -77,7 +77,7 @@ public class TCPlistener implements Runnable {
}
try {
Socket server = new Socket();
listener.setSoTimeout(1000);
listener.setSoTimeout(50); // Half of the expected time from MUXlisten
info.releaseReadLock();
database.releaseReadLock();
while(spin) {

View File

@@ -179,6 +179,11 @@ public class AddressBook {
// IDN - basic check, not complete validation
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
// Base32 spoofing (52chars.i2p)
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
// ... or maybe we do Base32 this way ...
(! host.equals("b32.i2p")) &&
(! host.endsWith(".b32.i2p")) &&
// some reserved names that may be used for local configuration someday
(! host.equals("proxy.i2p")) &&
(! host.equals("router.i2p")) &&

View File

@@ -1,347 +0,0 @@
/*
* bogobot - A simple join/part stats logger bot for I2P IRC.
*
* Bogobot.java
* 2004 The I2P Project
* http://www.i2p.net
* This code is public domain.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;
import org.jibble.pircbot.PircBot;
import org.jibble.pircbot.User;
/**
* TODO 0.5 Add multi-server capability.
*
* @author hypercubus, oOo
* @version 0.4
*/
public class Bogobot extends PircBot {
private static final String INTERVAL_DAILY = "daily";
private static final String INTERVAL_MONTHLY = "monthly";
private static final String INTERVAL_WEEKLY = "weekly";
private boolean _isIntentionalDisconnect = false;
private long _lastUserlistCommandTimestamp = 0;
private Logger _logger = Logger.getLogger(Bogobot.class);
private int _currentAutoRoundTripTag = 0;
private long _lastAutoRoundTripSentTime = 0;
private Timer _tickTimer;
private String _configFile;
private String _botPrimaryNick;
private String _botSecondaryNick;
private String _botNickservPassword;
private String _botUsername;
private String _ownerPrimaryNick;
private String _ownerSecondaryNick;
private String _botShutdownPassword;
private String _ircChannel;
private String _ircServer;
private int _ircServerPort;
private boolean _isLoggerEnabled;
private String _loggedHostnamePattern;
private boolean _isUserlistCommandEnabled;
private String _logFilePrefix;
private String _logFileRotationInterval;
private long _commandAntiFloodInterval;
private String _userlistCommandTrigger;
private boolean _isRoundTripDelayEnabled;
private int _roundTripDelayPeriod;
class BogobotTickTask extends TimerTask {
private Bogobot _caller;
public BogobotTickTask(Bogobot caller) {
_caller = caller;
}
public void run() {
_caller.onTick();
}
}
private void loadConfigFile(String configFileName) {
_configFile = configFileName;
Properties config = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(configFileName);
config.load(fis);
} catch (IOException ioe) {
System.err.println("Error loading configuration file");
System.exit(2);
} finally {
if (fis != null) try {
fis.close();
} catch (IOException ioe) { // nop
}
}
_botPrimaryNick = config.getProperty("botPrimaryNick", "somebot");
_botSecondaryNick = config.getProperty("botSecondaryNick", "somebot_");
_botNickservPassword = config.getProperty("botNickservPassword", "");
_botUsername = config.getProperty("botUsername", "somebot");
_ownerPrimaryNick = config.getProperty("ownerPrimaryNick", "somenick");
_ownerSecondaryNick = config.getProperty("ownerSecondaryNick", "somenick_");
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
_roundTripDelayPeriod = Integer.parseInt(config.getProperty("roundTripDelayPeriod", "300"));
_isUserlistCommandEnabled = Boolean.valueOf(config.getProperty("isUserlistCommandEnabled", "true")).booleanValue();
_userlistCommandTrigger = config.getProperty("userlistCommandTrigger", "!who");
_commandAntiFloodInterval = Long.parseLong(config.getProperty("commandAntiFloodInterval", "60"));
}
public Bogobot(String configFileName) {
loadConfigFile(configFileName);
this.setName(_botPrimaryNick);
this.setLogin(_botUsername);
_tickTimer = new Timer();
_tickTimer.scheduleAtFixedRate(new BogobotTickTask(this), 1000, 10 * 1000);
}
public static void main(String[] args) {
Bogobot bogobot;
if (args.length > 1) {
System.err.println("Too many arguments, the only allowed parameter is configuration file name");
System.exit(3);
}
if (args.length == 1) {
bogobot = new Bogobot(args[0]);
} else {
bogobot = new Bogobot("bogobot.config");
}
bogobot.setVerbose(true);
if (bogobot._isLoggerEnabled)
bogobot.initLogger();
bogobot.connectToServer();
}
protected void onTick() {
// Tick about once every ten seconds
if (this.isConnected() && _isRoundTripDelayEnabled) {
if( ( (System.currentTimeMillis() - _lastAutoRoundTripSentTime) >= (_roundTripDelayPeriod * 1000) ) && (this.getOutgoingQueueSize() == 0) ) {
// Connected, sending queue is empty and last RoundTrip is more then 5 minutes old -> Send a new one
_currentAutoRoundTripTag ++;
_lastAutoRoundTripSentTime = System.currentTimeMillis();
sendNotice(this.getNick(),"ROUNDTRIP " + _currentAutoRoundTripTag);
}
}
}
protected void onDisconnect() {
if (_isIntentionalDisconnect)
System.exit(0);
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " *** (Lost connection)");
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// No worries.
}
connectToServer();
}
protected void onJoin(String channel, String sender, String login, String hostname) {
if (_isLoggerEnabled) {
if (sender.equals(this.getName())) {
_logger.info(System.currentTimeMillis() + " joins *** " + _botPrimaryNick + " ***");
} else {
String prependedHostname = "@" + hostname;
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " joins " + sender);
}
}
}
}
protected void onMessage(String channel, String sender, String login, String hostname, String message) {
message = message.replaceFirst("<.+?> ", "");
if (_isUserlistCommandEnabled && message.equals(_userlistCommandTrigger)) {
if (System.currentTimeMillis() - _lastUserlistCommandTimestamp < _commandAntiFloodInterval * 1000)
return;
Object[] users = getUsers(_ircChannel);
String output = "Userlist for " + _ircChannel + ": ";
for (int i = 0; i < users.length; i++)
output += "[" + ((User) users[i]).getNick() + "] ";
sendMessage(_ircChannel, output);
_lastUserlistCommandTimestamp = System.currentTimeMillis();
}
}
protected void onPart(String channel, String sender, String login, String hostname) {
if (_isLoggerEnabled) {
if (sender.equals(this.getName())) {
_logger.info(System.currentTimeMillis() + " parts *** " + _botPrimaryNick + " ***");
} else {
String prependedHostname = "@" + hostname;
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " parts " + sender);
}
}
}
}
protected void onPrivateMessage(String sender, String login, String hostname, String message) {
/*
* Nobody else except the bot's owner can shut it down, unless of
* course the owner's nick isn't registered and someone's spoofing it.
*/
if ((sender.equals(_ownerPrimaryNick) || sender.equals(_ownerSecondaryNick)) && message.equals(_botShutdownPassword)) {
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " ***");
_isIntentionalDisconnect = true;
disconnect();
}
}
protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {
String prependedHostname = "@" + sourceHostname;
if (sourceNick.equals(_botPrimaryNick))
changeNick(_botPrimaryNick);
if (_isLoggerEnabled) {
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " quits " + sourceNick + " " + reason);
}
}
}
private void connectToServer() {
int loginAttempts = 0;
while (true) {
try {
connect(_ircServer, _ircServerPort);
break;
} catch (NickAlreadyInUseException e) {
if (loginAttempts == 1) {
System.out.println("Sorry, the primary and secondary bot nicks are already taken. Exiting.");
System.exit(1);
}
loginAttempts++;
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// Hmph.
}
if (getName().equals(_botPrimaryNick))
setName(_botSecondaryNick);
else
setName(_botPrimaryNick);
continue;
} catch (IOException e) {
System.out.println("Error during login: ");
e.printStackTrace();
System.exit(1);
} catch (IrcException e) {
System.out.println("Error during login: ");
e.printStackTrace();
System.exit(1);
}
}
joinChannel(_ircChannel);
}
protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {
if (sourceNick.equals("NickServ") && (notice.indexOf("/msg NickServ IDENTIFY") >= 0) && (_botNickservPassword != "")) {
sendRawLineViaQueue("NICKSERV IDENTIFY " + _botNickservPassword);
}
if (sourceNick.equals(getNick()) && notice.equals( "ROUNDTRIP " + _currentAutoRoundTripTag)) {
int delay = (int)((System.currentTimeMillis() - _lastAutoRoundTripSentTime) / 100);
// sendMessage(_ircChannel, "Round-trip delay = " + (delay / 10.0f) + " seconds");
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " roundtrip " + delay);
}
}
private void initLogger() {
String logFilePath = "logs" + File.separator + _logFilePrefix;
DailyRollingFileAppender rollingFileAppender = null;
if (!(new File("logs").exists()))
(new File("logs")).mkdirs();
try {
if (_logFileRotationInterval.equals("monthly"))
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM'.log'");
else if (_logFileRotationInterval.equals("weekly"))
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-ww'.log'");
else
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM-dd'.log'");
rollingFileAppender.setThreshold(Level.INFO);
_logger.addAppender(rollingFileAppender);
} catch (IOException ex) {
System.out.println("Error: Couldn't create or open an existing log file. Exiting.");
System.exit(1);
}
}
}

View File

@@ -1,353 +0,0 @@
/*
* bogoparser - A simple logfile analyzer for bogobot.
*
* Bogoparser.java
* 2004 The I2P Project
* http://www.i2p.net
* This code is public domain.
*/
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author hypercubus
* @version 0.4
*/
public class Bogoparser {
private static void displayUsageAndExit() {
System.out.println("\r\nUsage:\r\n\r\n java Bogoparser [--by-duration] <logfile>\r\n");
System.exit(1);
}
public static void main(String[] args) {
Bogoparser bogoparser;
if (args.length < 1 || args.length > 2)
displayUsageAndExit();
if (args.length == 2) {
if (!args[0].equals("--by-duration"))
displayUsageAndExit();
bogoparser = new Bogoparser(args[1], true);
}
if (args.length == 1)
bogoparser = new Bogoparser(args[0], false);
}
private Bogoparser(String logfile, boolean sortByDuration) {
ArrayList sortedSessions;
if (sortByDuration) {
sortedSessions = sortSessionsByDuration(calculateSessionDurations(sortSessionsByTime(readLogfile(logfile))));
formatAndOutputByDuration(sortedSessions);
} else {
sortedSessions = calculateSessionDurations(sortSessionsByQuitReason(sortSessionsByNick(sortSessionsByTime(readLogfile(logfile)))));
formatAndOutput(sortedSessions);
}
}
private ArrayList calculateSessionDurations(ArrayList sortedSessionsByQuitReasonOrDuration) {
ArrayList calculatedSessionDurations = new ArrayList();
for (int i = 0; i+1 < sortedSessionsByQuitReasonOrDuration.size(); i += 2) {
String joinsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i);
String[] joinsEntryFields = joinsEntry.split(" ");
String quitsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i+1);
Pattern p = Pattern.compile("^([^ ]+) [^ ]+ ([^ ]+) (.*)$");
Matcher m = p.matcher(quitsEntry);
if (m.matches()) {
String currentJoinTime = joinsEntryFields[0];
String currentNick = m.group(2);
String currentQuitReason = m.group(3);
String currentQuitTime = m.group(1);
long joinsTimeInMilliseconds;
long quitsTimeInMilliseconds;
long sessionLengthInMilliseconds;
joinsTimeInMilliseconds = Long.parseLong(currentJoinTime);
quitsTimeInMilliseconds = Long.parseLong(currentQuitTime);
sessionLengthInMilliseconds = quitsTimeInMilliseconds - joinsTimeInMilliseconds;
String hours = "" + sessionLengthInMilliseconds/1000/60/60;
String minutes = "" + (sessionLengthInMilliseconds/1000/60)%60;
if (hours.length() < 2)
hours = "0" + hours;
if (hours.length() < 3)
hours = "0" + hours;
if (minutes.length() < 2)
minutes = "0" + minutes;
int columnPadding = 19-currentNick.length();
String columnPaddingString = " ";
for (int j = 0; j < columnPadding; j++)
columnPaddingString = columnPaddingString + " ";
calculatedSessionDurations.add(sessionLengthInMilliseconds + " " + currentNick + columnPaddingString + " online " + hours + " hours " + minutes + " minutes " + currentQuitReason);
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + quitsEntry);
System.exit(1);
}
}
return calculatedSessionDurations;
}
private void formatAndOutput(ArrayList sortedSessions) {
String quitReason = null;
for (int i = 0; i < sortedSessions.size(); i++) {
String entry = (String) sortedSessions.get(i);
Pattern p = Pattern.compile("^[\\d]+ ([^ ]+ +online [\\d]+ hours [\\d]+ minutes) (.*)$");
Matcher m = p.matcher(entry);
if (m.matches()) {
if (quitReason == null) {
quitReason = m.group(2);
System.out.println("\r\nQUIT: " + ((m.group(2).equals("")) ? "No Reason Given" : quitReason) + "\r\n");
}
String tempQuitReason = m.group(2);
String tempSession = m.group(1);
if (tempQuitReason.equals(quitReason)) {
System.out.println(" " + tempSession);
} else {
quitReason = null;
i -= 1;
continue;
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
System.exit(1);
}
}
System.out.println("\r\n");
}
private void formatAndOutputByDuration(ArrayList sortedSessions) {
System.out.println("\r\n");
for (int i = 0; i < sortedSessions.size(); i++) {
String[] columns = ((String) sortedSessions.get(i)).split(" ", 2);
System.out.println(columns[1]);
}
System.out.println("\r\n");
}
private ArrayList readLogfile(String logfile) {
ArrayList log = new ArrayList();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(logfile)));
for (String line; (line = in.readLine()) != null; )
log.add(line);
in.close();
} catch (FileNotFoundException e) {
System.out.println("\r\nError: Can't find logfile '" + logfile + "'.\r\n");
System.exit(1);
} catch (IOException e) {
System.out.println("\r\nError: Can't read logfile '" + logfile + "'.\r\n");
System.exit(1);
}
return log;
}
/*
* Performs an odd-even transposition sort.
*/
private ArrayList sortSessionsByDuration(ArrayList calculatedSessionDurations) {
for (int i = 0; i < calculatedSessionDurations.size()/2; i++) {
for (int j = 0; j+1 < calculatedSessionDurations.size(); j += 2) {
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
long currentDuration = Long.parseLong(currentDurationString[0]);
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
long nextDuration = Long.parseLong(nextDurationString[0]);
if (currentDuration > nextDuration) {
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
calculatedSessionDurations.remove(j+2);
}
}
for (int j = 1; j+1 < calculatedSessionDurations.size(); j += 2) {
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
long currentDuration = Long.parseLong(currentDurationString[0]);
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
long nextDuration = Long.parseLong(nextDurationString[0]);
if (currentDuration > nextDuration) {
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
calculatedSessionDurations.remove(j+2);
}
}
}
return calculatedSessionDurations;
}
private ArrayList sortSessionsByNick(ArrayList sortedSessionsByTime) {
ArrayList sortedSessionsByNick = new ArrayList();
while (sortedSessionsByTime.size() != 0) {
String entry = (String) sortedSessionsByTime.get(0);
String[] entryFields = entry.split(" ");
String currentNick = entryFields[2];
sortedSessionsByNick.add(entry);
sortedSessionsByNick.add(sortedSessionsByTime.get(1));
sortedSessionsByTime.remove(0);
sortedSessionsByTime.remove(0);
for (int i = 0; i+1 < sortedSessionsByTime.size(); i += 2) {
String nextEntry = (String) sortedSessionsByTime.get(i);
String[] nextEntryFields = nextEntry.split(" ");
if (nextEntryFields[2].equals(currentNick)) {
sortedSessionsByNick.add(nextEntry);
sortedSessionsByNick.add(sortedSessionsByTime.get(i+1));
sortedSessionsByTime.remove(i);
sortedSessionsByTime.remove(i);
i -= 2;
}
}
}
return sortedSessionsByNick;
}
private ArrayList sortSessionsByQuitReason(ArrayList sortedSessionsByNick) {
ArrayList sortedSessionsByQuitReason = new ArrayList();
while (sortedSessionsByNick.size() != 0) {
String entry = (String) sortedSessionsByNick.get(1);
Pattern p = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
Matcher m = p.matcher(entry);
if (m.matches()) {
String currentQuitReason = m.group(1);
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(0));
sortedSessionsByQuitReason.add(entry);
sortedSessionsByNick.remove(0);
sortedSessionsByNick.remove(0);
for (int i = 0; i+1 < sortedSessionsByNick.size(); i += 2) {
String nextEntry = (String) sortedSessionsByNick.get(i+1);
Pattern p2 = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
Matcher m2 = p2.matcher(nextEntry);
if (m2.matches()) {
String nextQuitReason = m2.group(1);
if (nextQuitReason.equals(currentQuitReason)) {
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(i));
sortedSessionsByQuitReason.add(nextEntry);
sortedSessionsByNick.remove(i);
sortedSessionsByNick.remove(i);
i -= 2;
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + nextEntry);
System.exit(1);
}
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
System.exit(1);
}
}
return sortedSessionsByQuitReason;
}
/**
* Sessions terminated with "parts" messages instead of "quits" are filtered
* out.
*/
private ArrayList sortSessionsByTime(ArrayList log) {
ArrayList sortedSessionsByTime = new ArrayList();
mainLoop:
while (log.size() > 0) {
String entry = (String) log.get(0);
String[] entryFields = entry.split(" ");
if (entryFields[1].equals("quits") && !entryFields[1].equals("joins")) {
/*
* Discard entry. The specified log either doesn't contain
* the corresponding "joins" time for this quit entry or the
* entry is a "parts" or unknown message, and in both cases
* the entry's data is useless.
*/
log.remove(0);
continue;
}
for (int i = 1; i < log.size(); i++) { // Find corresponding "quits" entry.
String tempEntry = (String) log.get(i);
String[] tempEntryFields = tempEntry.split(" ");
if (tempEntryFields[2].equals(entryFields[2])) { // Check if the nick fields for the two entries match.
if (!tempEntryFields[1].equals("quits")) {
if (tempEntryFields[1].equals("joins")) { // Don't discard a subsequent "joins" entry.
log.remove(0);
continue mainLoop;
}
log.remove(i);
continue;
}
sortedSessionsByTime.add(entry);
sortedSessionsByTime.add(tempEntry);
log.remove(i);
break;
}
}
/*
* Discard "joins" entry. The specified log doesn't contain the
* corresponding "quits" time for this entry so the entry's
* data is useless.
*/
log.remove(0);
}
return sortedSessionsByTime;
}
}

View File

@@ -1,48 +0,0 @@
/*
* ============================================================================
* The Apache Software License, Version 1.1
* ============================================================================
*
* Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by the Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "log4j" and "Apache Software Foundation" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache", nor may
* "Apache" appear in their name, without prior written permission of the
* Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
* DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the Apache Software Foundation. For more information on the
* Apache Software Foundation, please see <http://www.apache.org/>.
*
*/

View File

@@ -1,340 +0,0 @@
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.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 of the License, 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; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@@ -1 +0,0 @@
java -cp .;log4j-1.2.8.jar;pircbot.jar Bogobot

View File

@@ -1,101 +0,0 @@
#####
# Bogobot user configuration
#####
###
# The bot's nick and backup nick. You will probably want to register these with
# the IRC server's NickServ.(a NickServ interface is forthcoming).
#
botPrimaryNick=somebot
botSecondaryNick=somebot_
###
# The bot's password required by Nickserv service's identify command.
# You have to register the nickname yourself first, the bot will not.
#
botNickservPassword=
###
# The bot's username. Appears in the whois replies
#
botUsername=somebot
#####
# The bot owner's nick and backup nick. One of these must match the owner's
# currently-used nick or else remote shutdown will not be possible. You will
# probably want to register these with the IRC server's NickServ.
#
ownerPrimaryNick=somenick
ownerSecondaryNick=somenick_
###
# The bot will disconnect and shut down when sent this password via private
# message (aka query) from either of the owner nicks specified above. DO NOT USE
# THIS DEFAULT VALUE!
#
botShutdownPassword=take off eh
###
# The server, channel, and port the bot will connect to.
#
ircChannel=#i2p-chat
ircServer=irc.duck.i2p
ircServerPort=6668
###
# Set to "true" to enable logging, else "false" (but don't use quotation marks).
#
isLoggerEnabled=true
###
# Restrict logging of joins and parts on the user hostname.
# Leave empty to log all of them
# Prepend with a @ for a perfect match
# Otherwise, specify the required end of the user hostname
#
loggedHostnamePattern=@free.duck.i2p
###
# The prefix to be used for the filenames of logs.
#
logFilePrefix=irc.duck.i2p.i2p-chat
###
# How often the logs should be rotated. Either "daily", "weekly", or "monthly"
# (but don't use quotation marks).
#
logFileRotationInterval=daily
###
# Set to "true" to enable the regular round-trip delay computation,
# else "false" (but don't use quotation marks).
#
isRoundTripDelayEnabled=false
###
# How often should the round-trip delay be recorded.
# (in seconds)
#
roundTripDelayPeriod=300
###
# Set to "true" to enable the userlist command, else "false" (but don't use
# quotation marks).
#
isUserlistCommandEnabled=true
###
# The userlist trigger command to listen for. It is a good idea to prefix
# triggers with some non-alphanumeric character in order to avoid accidental
# trigger use during normal channel conversation. In most cases you will
# probably want to choose a unique trigger here that no other bots in the
# channel will respond to.
#
userlistCommandTrigger=!who
###
# The number of seconds to rest after replying to a userlist command issued by
# a user in the channel. The bot will ignore subsequent userlist commands during
# this period. This helps prevent flooding.
#
commandAntiFloodInterval=60

View File

@@ -1,2 +0,0 @@
#!/bin/sh
java -cp .:log4j-1.2.8.jar:pircbot.jar Bogobot

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ********************************************************** -->
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
<!-- -->
<!-- build-eclipse.xml -->
<!-- 2004 The I2P Project -->
<!-- http://www.i2p.net -->
<!-- This code is public domain. -->
<!-- -->
<!-- authors: hypercubus, oOo -->
<!-- version 0.4 -->
<!-- ********************************************************** -->
<project basedir="." default="dist" name="Bogobot">
<!-- init:
Create distribution directory if missing and initialize time stamp for
archive naming -->
<target name="init">
<mkdir dir="dist" />
<tstamp>
<format pattern="yyyy-MM-dd" property="DSTAMP" />
</tstamp>
</target>
<!-- dist.bin:
Create the binary distribution archive -->
<target depends="init" description="Create the binary distribution archive" name="dist.bin">
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist.source:
Create the source distribution archive -->
<target depends="init" description="Create the source distribution archive" name="dist.source">
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist:
Create both the binary and source distribution archives -->
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
<echo message="Successfully created binary and source distribution archives in directory &apos;dist&apos;." />
</target>
<!-- clean:
Delete all class files and temporary directories -->
<target description="Delete all class files and temporary directories" name="clean">
<delete>
<fileset dir="${basedir}" includes="**/*.class" />
</delete>
<echo message="Clean successful." />
</target>
</project>

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ********************************************************** -->
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
<!-- -->
<!-- build.xml -->
<!-- 2004 The I2P Project -->
<!-- http://www.i2p.net -->
<!-- This code is public domain. -->
<!-- -->
<!-- authors: hypercubus, oOo -->
<!-- version 0.4 -->
<!-- ********************************************************** -->
<project basedir="." default="compile" name="Bogobot">
<!-- init:
Create distribution directory if missing and initialize time stamp for
archive naming -->
<target name="init">
<mkdir dir="dist" />
<tstamp>
<format pattern="yyyy-MM-dd" property="DSTAMP" />
</tstamp>
</target>
<!-- compile:
Compile source code -->
<target depends="init" description="Compile source code" name="compile">
<javac classpath="${basedir};log4j-1.2.8.jar;pircbot.jar" source="1.4" srcdir="." />
</target>
<!-- dist.bin:
Create the binary distribution archive -->
<target depends="init,compile" description="Create the binary distribution archive" name="dist.bin">
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class Bogobot$BogobotTickTask.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist.source:
Create the source distribution archive -->
<target depends="init" description="Create the source distribution archive" name="dist.source">
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist:
Create both the binary and source distribution archives -->
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
<echo message="Successfully created binary and source distribution archives in directory &apos;dist&apos;." />
</target>
<!-- clean:
Delete all class files and temporary directories -->
<target description="Delete all class files and temporary directories" name="clean">
<delete>
<fileset dir="${basedir}" includes="**/*.class" />
</delete>
<echo message="Clean successful." />
</target>
</project>

Binary file not shown.

Binary file not shown.

View File

@@ -152,6 +152,7 @@ public class ConnectionAcceptor implements Runnable
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
// catch oom?
}
try

View File

@@ -23,7 +23,9 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
@@ -48,6 +50,7 @@ public class I2PSnarkUtil {
private int _maxUploaders;
private int _maxUpBW;
private int _maxConnections;
private File _tmpDir;
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
@@ -67,6 +70,12 @@ public class I2PSnarkUtil {
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
_maxUpBW = DEFAULT_MAX_UP_BW;
_maxConnections = MAX_CONNECTIONS;
// This is used for both announce replies and .torrent file downloads,
// so it must be available even if not connected to I2CP.
// so much for multiple instances
_tmpDir = new File("tmp", "i2psnark");
FileUtil.rmdir(_tmpDir, false);
_tmpDir.mkdirs();
}
/**
@@ -94,6 +103,7 @@ public class I2PSnarkUtil {
_i2cpHost = i2cpHost;
if (i2cpPort > 0)
_i2cpPort = i2cpPort;
// can't remove any options this way...
if (opts != null)
_opts.putAll(opts);
_configured = true;
@@ -166,6 +176,10 @@ public class I2PSnarkUtil {
_manager = null;
_shitlist.clear();
mgr.destroySocketManager();
// this will delete a .torrent file d/l in progress so don't do that...
FileUtil.rmdir(_tmpDir, false);
// in case the user will d/l a .torrent file next...
_tmpDir.mkdirs();
}
/** connect to the given destination */
@@ -183,7 +197,7 @@ public class I2PSnarkUtil {
synchronized (_shitlist) {
_shitlist.add(dest);
}
SimpleTimer.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
SimpleScheduler.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
}
}
@@ -204,13 +218,15 @@ public class I2PSnarkUtil {
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
File out = null;
try {
out = File.createTempFile("i2psnark", "url", new File("."));
// we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms...
out = File.createTempFile("i2psnark", null, _tmpDir);
} catch (IOException ioe) {
ioe.printStackTrace();
if (out != null)
out.delete();
return null;
}
out.deleteOnExit();
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);

View File

@@ -28,6 +28,7 @@ import java.util.List;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
class PeerConnectionOut implements Runnable
@@ -215,7 +216,7 @@ class PeerConnectionOut implements Runnable
private void addMessage(Message m)
{
if (m.type == Message.PIECE)
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
synchronized(sendQueue)
{
sendQueue.add(m);

View File

@@ -60,7 +60,7 @@ class PeerState
// If we have te resend outstanding requests (true after we got choked).
private boolean resend = false;
private final static int MAX_PIPELINE = 2; // this is for outbound requests
private final static int MAX_PIPELINE = 3; // this is for outbound requests
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
public final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this

View File

@@ -232,7 +232,7 @@ public class Snark
}
// Explicit shutdown.
Runtime.getRuntime().removeShutdownHook(snarkhook);
//Runtime.getRuntime().removeShutdownHook(snarkhook);
snarkhook.start();
}
}

View File

@@ -81,8 +81,7 @@ public class SnarkManager implements Snark.CompleteListener {
I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
if (_context instanceof RouterContext)
((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
_context.addShutdownTask(new SnarkManagerShutdown());
}
/** hook to I2PSnarkUtil for the servlet */
@@ -141,7 +140,7 @@ public class SnarkManager implements Snark.CompleteListener {
if (!_config.containsKey(PROP_I2CP_PORT))
_config.setProperty(PROP_I2CP_PORT, "7654");
if (!_config.containsKey(PROP_I2CP_OPTS))
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0");
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0 inbound.quantity=3 outbound.quantity=3");
if (!_config.containsKey(PROP_EEP_HOST))
_config.setProperty(PROP_EEP_HOST, "localhost");
if (!_config.containsKey(PROP_EEP_PORT))
@@ -539,7 +538,7 @@ public class SnarkManager implements Snark.CompleteListener {
String announce = info.getAnnounce();
// basic validation of url
if ((!announce.startsWith("http://")) ||
(announce.indexOf(".i2p/") < 0))
(announce.indexOf(".i2p/") < 0)) // need to do better than this
return "Non-i2p tracker in " + info.getName() + ", deleting it";
List files = info.getFiles();
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
@@ -693,6 +692,7 @@ public class SnarkManager implements Snark.CompleteListener {
, "welterde", "http://BGKmlDOoH3RzFbPRfRpZV2FjpVj8~3moFftw5-dZfDf2070TOe8Tf2~DAVeaM6ZRLdmFEt~9wyFL8YMLMoLoiwGEH6IGW6rc45tstN68KsBDWZqkTohV1q9XFgK9JnCwE~Oi89xLBHsLMTHOabowWM6dkC8nI6QqJC2JODqLPIRfOVrDdkjLwtCrsckzLybNdFmgfoqF05UITDyczPsFVaHtpF1sRggOVmdvCM66otyonlzNcJbn59PA-R808vUrCPMGU~O9Wys0i-NoqtIbtWfOKnjCRFMNw5ex4n9m5Sxm9e20UkpKG6qzEuvKZWi8vTLe1NW~CBrj~vG7I3Ok4wybUFflBFOaBabxYJLlx4xTE1zJIVxlsekmAjckB4v-cQwulFeikR4LxPQ6mCQknW2HZ4JQIq6hL9AMabxjOlYnzh7kjOfRGkck8YgeozcyTvcDUcUsOuSTk06L4kdrv8h2Cozjbloi5zl6KTbj5ZTciKCxi73Pn9grICn-HQqEAAAA.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
// , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
, "crstrack", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/"
};
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */

View File

@@ -696,9 +696,6 @@ public class Storage
listener.setWantedPieces(this);
_util.debug("WARNING: Not really done, missing " + needed
+ " pieces", Snark.WARNING);
} else {
if (listener != null)
listener.storageCompleted(this);
}
}

View File

@@ -346,7 +346,6 @@ public class TrackerClient extends I2PAppThread
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = null;
try {

View File

@@ -5,6 +5,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -62,7 +63,7 @@ public class I2PSnarkServlet extends HttpServlet {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
long stats[] = {0,0,0,0};
long stats[] = {0,0,0,0,0};
String nonce = req.getParameter("nonce");
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
@@ -142,8 +143,10 @@ public class I2PSnarkServlet extends HttpServlet {
if (snarks.size() <= 0) {
out.write(TABLE_EMPTY);
} else if (snarks.size() > 1) {
out.write(TABLE_TOTAL);
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
out.write("<tfoot><tr>\n" +
" <th align=\"left\" valign=\"top\" colspan=\"2\">Totals (" + snarks.size() + " torrents, " + stats[4] + " connected peers)</th>\n" +
" <th>&nbsp;</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
@@ -199,10 +202,14 @@ public class I2PSnarkServlet extends HttpServlet {
} catch (IOException ioe) {
_log.warn("hrm: " + local, ioe);
}
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
_manager.addMessage("Fetching " + newURL);
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
fetch.start();
} else if (newURL != null) {
if (newURL.startsWith("http://")) {
_manager.addMessage("Fetching " + newURL);
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
fetch.start();
} else {
_manager.addMessage("Invalid URL - must start with http://");
}
} else {
// no file or URL specified
}
@@ -378,7 +385,8 @@ public class I2PSnarkServlet extends HttpServlet {
private List getSortedSnarks(HttpServletRequest req) {
Set files = _manager.listTorrentFiles();
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
TreeSet fileNames = new TreeSet(Collator.getInstance()); // sorts it alphabetically
fileNames.addAll(files);
ArrayList rv = new ArrayList(fileNames.size());
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
@@ -437,6 +445,7 @@ public class I2PSnarkServlet extends HttpServlet {
if (snark.coordinator != null) {
err = snark.coordinator.trackerProblems;
curPeers = snark.coordinator.getPeerCount();
stats[4] += curPeers;
knownPeers = snark.coordinator.trackerSeenPeers;
}
@@ -575,10 +584,10 @@ public class I2PSnarkServlet extends HttpServlet {
client = "Azureus";
else if ("CwsL".equals(ch))
client = "I2PSnarkXL";
else if ("AUZV".equals(ch))
else if ("ZV".equals(ch.substring(2,4)))
client = "Robert";
else
client = "Unknown";
client = "Unknown (" + ch + ')';
out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;<tt>" + peer.toString().substring(5, 9) + "</tt>");
if (showDebug)
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
@@ -639,7 +648,7 @@ public class I2PSnarkServlet extends HttpServlet {
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String newURL = req.getParameter("newURL");
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "";
String newFile = req.getParameter("newFile");
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
@@ -767,7 +776,7 @@ public class I2PSnarkServlet extends HttpServlet {
return bytes + "B";
else if (bytes < 5*1024*1024)
return ((bytes + 512)/1024) + "KB";
else if (bytes < 5*1024*1024*1024l)
else if (bytes < 10*1024*1024*1024l)
return ((bytes + 512*1024)/(1024*1024)) + "MB";
else
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
@@ -856,11 +865,6 @@ public class I2PSnarkServlet extends HttpServlet {
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
private static final String TABLE_TOTAL = "<tfoot>\n" +
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
" <th>&nbsp;</th>\n" +
" <th>&nbsp;</th>\n";
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
"<td class=\"snarkTorrentEven\" align=\"left\"" +
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";

View File

@@ -42,7 +42,7 @@
</target>
<target name="war" depends="precompilejsp">
<war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
basedir="../jsp/" excludes="web.xml, *.java, *.jsp">
basedir="../jsp/" excludes="web.xml, **/*.java, *.jsp">
</war>
</target>
<target name="precompilejsp" unless="precompilejsp.uptodate">

View File

@@ -62,6 +62,8 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
import net.i2p.i2ptunnel.streamr.StreamrConsumer;
import net.i2p.i2ptunnel.streamr.StreamrProducer;
import net.i2p.util.EventDispatcher;
import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
@@ -234,6 +236,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
runServer(args, l);
} else if ("httpserver".equals(cmdname)) {
runHttpServer(args, l);
} else if ("ircserver".equals(cmdname)) {
runIrcServer(args, l);
} else if ("textserver".equals(cmdname)) {
runTextServer(args, l);
} else if ("client".equals(cmdname)) {
@@ -244,6 +248,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
runIrcClient(args, l);
} else if ("sockstunnel".equals(cmdname)) {
runSOCKSTunnel(args, l);
} else if ("connectclient".equals(cmdname)) {
runConnectClient(args, l);
} else if ("streamrclient".equals(cmdname)) {
runStreamrClient(args, l);
} else if ("streamrserver".equals(cmdname)) {
runStreamrServer(args, l);
} else if ("config".equals(cmdname)) {
runConfig(args, l);
} else if ("listen_on".equals(cmdname)) {
@@ -296,6 +306,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>");
l.log("quit");
l.log("close [forced] <jobnumber>|all");
@@ -380,6 +391,53 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Same args as runServer
* (we should stop duplicating all this code...)
*/
public void runIrcServer(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(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
try {
portNum = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
privKeyFile = new File(args[2]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
I2PTunnelServer serv = new I2PTunnelIRCServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", Integer.valueOf(serv.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", Integer.valueOf(-1));
}
}
/**
* Run the HTTP server pointing at the host and port specified using the private i2p
* destination loaded from the specified file, replacing the HTTP headers
@@ -494,14 +552,14 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Integer port number if the client is listening
* sharedClient parameter is a String "true" or "false"
*
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient]}
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient [, privKeyFile]]}
* @param l logger to receive events and output
*/
public void runClient(String args[], Logging l) {
boolean isShared = true;
if (args.length == 3)
if (args.length >= 3)
isShared = Boolean.valueOf(args[2].trim()).booleanValue();
if ( (args.length == 2) || (args.length == 3) ) {
if (args.length >= 2) {
int portNum = -1;
try {
portNum = Integer.parseInt(args[0]);
@@ -514,7 +572,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this);
String privateKeyFile = null;
if (args.length >= 4)
privateKeyFile = args[3];
task = new I2PTunnelClient(portNum, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
addtask(task);
notifyEvent("clientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
@@ -523,7 +584,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("clientTaskId", Integer.valueOf(-1));
}
} else {
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>]");
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>] [<privKeyFile>]");
l.log(" creates a client that forwards port to the pubkey.\n"
+ " use 0 as port to get a free port assigned. If you specify\n"
+ " a comma delimited list of pubkeys, it will rotate among them\n"
@@ -555,7 +616,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
return;
}
String proxy = "squid.i2p";
String proxy = "";
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
@@ -595,11 +656,66 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
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", Integer.valueOf(-1));
}
}
/**
* Run a CONNECT client on the given port number
*
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
* @param l logger to receive events and output
*/
public void runConnectClient(String args[], Logging l) {
if (args.length >= 1 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
return;
}
String proxy = "";
boolean isShared = true;
if (args.length > 1) {
if ("true".equalsIgnoreCase(args[1].trim())) {
isShared = true;
if (args.length == 3)
proxy = args[2];
} else if ("false".equalsIgnoreCase(args[1].trim())) {
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
isShared = false;
if (args.length == 3)
proxy = args[2];
} else if (args.length == 3) {
isShared = false; // not "true"
proxy = args[2];
_log.warn("args[1] == [" + args[1] + "] but rejected");
} else {
// isShared not specified, default to true
isShared = true;
proxy = args[1];
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelConnectClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
addtask(task);
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
}
} else {
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
l.log(" creates a client that for SSL/HTTPS requests.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
l.log(" <proxy> (optional) indicates a proxy server to be used");
l.log(" when trying to access an address out of the .i2p domain");
}
}
/**
* Run an IRC client on the given port number
*
@@ -607,11 +723,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient [, privKeyFile]]}
* @param l logger to receive events and output
*/
public void runIrcClient(String args[], Logging l) {
if (args.length >= 2 && args.length <= 3) {
if (args.length >= 2) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
@@ -638,7 +754,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
String privateKeyFile = null;
if (args.length >= 4)
privateKeyFile = args[3];
task = new I2PTunnelIRCClient(port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
addtask(task);
notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
} catch (IllegalArgumentException iae) {
@@ -647,7 +766,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
}
} else {
l.log("ircclient <port> [<sharedClient>]");
l.log("ircclient <port> [<sharedClient> [<privKeyFile>]]");
l.log(" creates a client that filter IRC protocol.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
@@ -662,7 +781,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
* "openSOCKSTunnelResult" = "ok" or "error" after the client tunnel has
* started.
*
* @param args {portNumber}
* @param args {portNumber [, sharedClient]}
* @param l logger to receive events and output
*/
public void runSOCKSTunnel(String args[], Logging l) {
@@ -677,6 +796,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
return;
}
boolean isShared = false;
if (args.length > 1)
isShared = "true".equalsIgnoreCase(args[1].trim());
ownDest = !isShared;
I2PTunnelTask task;
task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher) this, this);
addtask(task);
@@ -688,6 +812,82 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Streamr client
*
* @param args {targethost, targetport, destinationString}
* @param l logger to receive events and output
*/
public void runStreamrClient(String args[], Logging l) {
if (args.length == 3) {
InetAddress host;
try {
host = InetAddress.getByName(args[0]);
} catch (UnknownHostException uhe) {
l.log("unknown host");
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
return;
}
int port = -1;
try {
port = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
return;
}
StreamrConsumer task = new StreamrConsumer(host, port, args[2], l, (EventDispatcher) this, this);
task.startRunning();
addtask(task);
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
} else {
l.log("streamrclient <host> <port> <destination>");
l.log(" creates a tunnel that receives streaming data.");
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
}
}
/**
* Streamr server
*
* @param args {port, privkeyfile}
* @param l logger to receive events and output
*/
public void runStreamrServer(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(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
return;
}
File privKeyFile = new File(args[1]);
if (!privKeyFile.canRead()) {
l.log("private key file does not exist");
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
notifyEvent("serverTaskId", Integer.valueOf(-1));
return;
}
StreamrProducer task = new StreamrProducer(port, privKeyFile, args[1], l, (EventDispatcher) this, this);
task.startRunning();
addtask(task);
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
} else {
l.log("streamrserver <port> <privkeyfile>");
l.log(" creates a tunnel that sends streaming data.");
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
}
}
/**
* Specify the i2cp host and port
*

View File

@@ -31,8 +31,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
*/
public I2PTunnelClient(int localPort, String destinations, Logging l,
boolean ownDest, EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel);
I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel, pkf);
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openClientResult", "error");

View File

@@ -3,6 +3,7 @@
*/
package net.i2p.i2ptunnel;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
@@ -27,6 +28,7 @@ import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
@@ -58,6 +60,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private byte[] pubkey;
private String handlerName;
private String privKeyFile;
private Object conLock = new Object();
@@ -90,18 +93,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// I2PTunnelClientBase(localPort, ownDest, l, (EventDispatcher)null);
//}
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l,
EventDispatcher notifyThis, String handlerName,
I2PTunnel tunnel) throws IllegalArgumentException {
this(localPort, ownDest, l, notifyThis, handlerName, tunnel, null);
}
/**
* @param privKeyFile null to generate a transient key
*
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
* badly that we cant create a socketManager
*/
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l,
EventDispatcher notifyThis, String handlerName,
I2PTunnel tunnel) throws IllegalArgumentException{
I2PTunnel tunnel, String pkf) throws IllegalArgumentException{
super(localPort + " (uninitialized)", notifyThis, tunnel);
_clientId = ++__clientId;
this.localPort = localPort;
this.l = l;
this.handlerName = handlerName + _clientId;
this.privKeyFile = pkf;
_context = tunnel.getContext();
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
@@ -113,26 +126,28 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
while (sockMgr == null) {
synchronized (sockLock) {
if (ownDest) {
sockMgr = buildSocketManager();
} else {
sockMgr = getSocketManager();
boolean openNow = !Boolean.valueOf(tunnel.getClientOptions().getProperty("i2cp.delayOpen")).booleanValue();
if (openNow) {
while (sockMgr == null) {
synchronized (sockLock) {
if (ownDest) {
sockMgr = buildSocketManager();
} else {
sockMgr = getSocketManager();
}
}
if (sockMgr == null) {
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
}
if (sockMgr == null) {
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
l.log("Invalid I2CP configuration");
throw new IllegalArgumentException("Socket manager could not be created");
}
}
if (sockMgr == null) {
l.log("Invalid I2CP configuration");
throw new IllegalArgumentException("Socket manager could not be created");
}
l.log("I2P session created");
l.log("I2P session created");
getTunnel().addSession(sockMgr.getSession());
} // else delay creating session until createI2PSocket() is called
Thread t = new I2PThread(this);
t.setName("Client " + _clientId);
@@ -152,7 +167,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
configurePool(tunnel);
if (open && listenerReady) {
l.log("Ready! Port " + getLocalPort());
if (openNow)
l.log("Ready! Port " + getLocalPort());
else
l.log("Listening on port " + getLocalPort() + ", delaying tunnel open until required");
notifyEvent("openBaseClientResult", "ok");
} else {
l.log("Error listening - please see the logs!");
@@ -194,28 +212,36 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static I2PSocketManager socketManager;
protected synchronized I2PSocketManager getSocketManager() {
return getSocketManager(getTunnel());
return getSocketManager(getTunnel(), this.privKeyFile);
}
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
return getSocketManager(tunnel, null);
}
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) {
if (socketManager != null) {
I2PSession s = socketManager.getSession();
if ( (s == null) || (s.isClosed()) ) {
_log.info("Building a new socket manager since the old one closed [s=" + s + "]");
socketManager = buildSocketManager(tunnel);
if (s != null)
tunnel.removeSession(s);
socketManager = buildSocketManager(tunnel, pkf);
} else {
_log.info("Not building a new socket manager since the old one is open [s=" + s + "]");
}
} else {
_log.info("Building a new socket manager since there is no other one");
socketManager = buildSocketManager(tunnel);
socketManager = buildSocketManager(tunnel, pkf);
}
return socketManager;
}
protected I2PSocketManager buildSocketManager() {
return buildSocketManager(getTunnel());
return buildSocketManager(getTunnel(), this.privKeyFile);
}
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) {
return buildSocketManager(tunnel, null);
}
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf) {
Properties props = new Properties();
props.putAll(tunnel.getClientOptions());
int portNum = 7654;
@@ -229,7 +255,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
I2PSocketManager sockManager = null;
while (sockManager == null) {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
if (pkf != null) {
// Persistent client dest
FileInputStream fis = null;
try {
fis = new FileInputStream(pkf);
sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props);
} catch (IOException ioe) {
_log.error("Error opening key file", ioe);
// this is going to loop but if we break we'll get a NPE
} finally {
if (fis != null)
try { fis.close(); } catch (IOException ioe) {}
}
} else {
sockManager = I2PSocketManagerFactory.createManager(tunnel.host, portNum, props);
}
if (sockManager == null) {
_log.log(Log.CRIT, "Unable to create socket manager");
@@ -301,6 +342,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
* @return a new I2PSocket
*/
public I2PSocket createI2PSocket(Destination dest) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
if (sockMgr == null) {
// we need this before getDefaultOptions()
sockMgr = getSocketManager();
}
return createI2PSocket(dest, getDefaultOptions());
}
@@ -321,6 +366,19 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
I2PSocket i2ps;
if (sockMgr == null) {
// delayed open - call get instead of build because the locking is up there
sockMgr = getSocketManager();
} else if (Boolean.valueOf(getTunnel().getClientOptions().getProperty("i2cp.newDestOnResume")).booleanValue()) {
synchronized(sockMgr) {
I2PSocketManager oldSockMgr = sockMgr;
// This will build a new socket manager and a new dest if the session is closed.
sockMgr = getSocketManager();
if (oldSockMgr != sockMgr) {
_log.warn("Built a new destination on resume");
}
}
} // else the old socket manager will reconnect the old session if necessary
i2ps = sockMgr.connect(dest, opt);
synchronized (sockLock) {
mySockets.add(i2ps);
@@ -373,8 +431,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
_context.statManager().addRateData("i2ptunnel.client.manageTime", total, total);
}
} catch (IOException ex) {
_log.error("Error listening for connections on " + localPort, ex);
notifyEvent("openBaseClientResult", "error");
if (open) {
_log.error("Error listening for connections on " + localPort, ex);
notifyEvent("openBaseClientResult", "error");
}
synchronized (sockLock) {
mySockets.clear();
}
@@ -401,7 +461,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
if (_maxWaitTime > 0)
SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
SimpleScheduler.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
synchronized (_waitingSockets) {
_waitingSockets.add(s);
@@ -455,20 +515,23 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
// 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());
if (sockMgr != null) {
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;
}
I2PSession session = sockMgr.getSession();
if (session != null) {
getTunnel().removeSession(session);
}
return false;
}
I2PSession session = sockMgr.getSession();
if (session != null) {
getTunnel().removeSession(session);
}
l.log("Closing client " + toString());
open = false;
try {
if (ss != null) ss.close();
} catch (IOException ex) {
@@ -476,7 +539,6 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
return false;
}
l.log("Client closed.");
open = false;
}
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }

View File

@@ -0,0 +1,369 @@
/* 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.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
/**
* Supports the following:
* (where protocol is generally HTTP/1.1 but is ignored)
* (where host is one of:
* example.i2p
* 52chars.b32.i2p
* 516+charsbase64
* example.com (sent to one of the configured proxies)
* )
*
* (port and protocol are ignored for i2p destinations)
* CONNECT host
* CONNECT host protocol
* CONNECT host:port
* CONNECT host:port protocol (this is the standard)
*
* Additional lines after the CONNECT line but before the blank line are ignored and stripped.
* The CONNECT line is removed for .i2p accesses
* but passed along for outproxy accesses.
*
* Ref:
* INTERNET-DRAFT Ari Luotonen
* Expires: September 26, 1997 Netscape Communications Corporation
* <draft-luotonen-ssl-tunneling-03.txt> March 26, 1997
* Tunneling SSL Through a WWW Proxy
*
* @author zzz a stripped-down I2PTunnelHTTPClient
*/
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelConnectClient.class);
private List<String> _proxyList;
private final static byte[] ERR_DESTINATION_UNKNOWN =
("HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: DESTINATION NOT FOUND</H1>"+
"That I2P Destination was not found. "+
"The host (or the outproxy, if you're using one) could also "+
"be temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR><div>")
.getBytes();
private final static byte[] ERR_NO_OUTPROXY =
("HTTP/1.1 503 Service Unavailable\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
"Your request was for a site outside of I2P, but you have no "+
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
.getBytes();
private final static byte[] ERR_BAD_PROTOCOL =
("HTTP/1.1 405 Bad Method\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: METHOD NOT ALLOWED</H1>"+
"The request uses a bad protocol. "+
"The Connect Proxy supports CONNECT requests ONLY. Other methods such as GET are not allowed - Maybe you wanted the HTTP Proxy?.<BR>")
.getBytes();
private final static byte[] ERR_LOCALHOST =
("HTTP/1.1 403 Access Denied\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static byte[] SUCCESS_RESPONSE =
("HTTP/1.1 200 Connection Established\r\n"+
"Proxy-agent: I2P\r\n"+
"\r\n")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelConnectClient(int localPort, Logging l, boolean ownDest,
String wwwProxy, EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openConnectClientResult", "error");
return;
}
_proxyList = new ArrayList();
if (wwwProxy != null) {
StringTokenizer tok = new StringTokenizer(wwwProxy, ",");
while (tok.hasMoreTokens())
_proxyList.add(tok.nextToken().trim());
}
setName(getLocalPort() + " -> ConnectClient [Outproxy list: " + wwwProxy + "]");
startRunning();
}
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
private String selectProxy() {
synchronized (_proxyList) {
int size = _proxyList.size();
if (size <= 0)
return null;
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
return _proxyList.get(index);
}
}
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
/**
* create the default options (using the default timeout, etc)
*
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
if (!defaultOpts.contains(I2PSocketOptions.PROP_READ_TIMEOUT))
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
return opts;
}
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
InputStream in = null;
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
String currentProxy = null;
long requestId = ++__requestId;
try {
out = s.getOutputStream();
in = s.getInputStream();
String line, method = null, host = null, destination = null, restofline = null;
StringBuffer newRequest = new StringBuffer();
int ahelper = 0;
while (true) {
// Use this rather than BufferedReader because we can't have readahead,
// since we are passing the stream on to I2PTunnelRunner
line = DataHelper.readLine(in);
line = line.trim();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
if (method == null) { // first line CONNECT blah.i2p:80 HTTP/1.1
int pos = line.indexOf(" ");
if (pos == -1) break; // empty first line
method = line.substring(0, pos);
String request = line.substring(pos + 1);
pos = request.indexOf(":");
if (pos == -1)
pos = request.indexOf(" ");
if (pos == -1) {
host = request;
restofline = "";
} else {
host = request.substring(0, pos);
restofline = request.substring(pos); // ":80 HTTP/1.1" or " HTTP/1.1"
}
if (host.toLowerCase().endsWith(".i2p")) {
// Destination gets the host name
destination = host;
} else if (host.indexOf(".") != -1) {
// The request must be forwarded to a outproxy
currentProxy = selectProxy();
if (currentProxy == null) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Host wants to be outproxied, but we dont have any!");
writeErrorMessage(ERR_NO_OUTPROXY, out);
s.close();
return;
}
destination = currentProxy;
usingWWWProxy = true;
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n\r\n"); // HTTP spec
} else if (host.toLowerCase().equals("localhost")) {
writeErrorMessage(ERR_LOCALHOST, out);
s.close();
return;
} else { // full b64 address (hopefully)
destination = host;
}
targetRequest = host;
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(getPrefix(requestId) + "METHOD:" + method + ":");
_log.debug(getPrefix(requestId) + "HOST :" + host + ":");
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
}
} else if (line.length() > 0) {
// Additional lines - shouldn't be too many. Firefox sends:
// User-Agent: blabla
// Proxy-Connection: keep-alive
// Host: blabla.i2p
//
// We could send these (filtered like in HTTPClient) on to the outproxy,
// but for now just chomp them all.
line = null;
} else {
// do it
break;
}
}
if (destination == null || !"CONNECT".equalsIgnoreCase(method)) {
writeErrorMessage(ERR_BAD_PROTOCOL, out);
s.close();
return;
}
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null) {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
s.close();
return;
}
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions());
byte[] data = null;
byte[] response = null;
if (usingWWWProxy)
data = newRequest.toString().getBytes("ISO-8859-1");
else
response = SUCCESS_RESPONSE;
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
I2PTunnelRunner runner = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
} catch (SocketException ex) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (IOException ex) {
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (I2PException ex) {
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
handleConnectClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
}
}
private static class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
private boolean _usingProxy;
private String _wwwProxy;
private long _requestId;
public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
_socket = s;
_out = out;
_target = target;
_usingProxy = usingProxy;
_wwwProxy = wwwProxy;
_requestId = id;
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Timeout occured requesting " + _target);
handleConnectClientException(new RuntimeException("Timeout"), _out,
_target, _usingProxy, _wwwProxy, _requestId);
closeSocket(_socket);
}
}
private static void writeErrorMessage(byte[] errMessage, OutputStream out) throws IOException {
if (out == null)
return;
out.write(errMessage);
out.write("\n</body></html>\n".getBytes());
out.flush();
}
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("</div>".getBytes());
out.write("\n</body></html>\n".getBytes());
out.flush();
}
}
private static void handleConnectClientException(Exception ex, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy, long requestId) {
if (out == null)
return;
try {
String str;
byte[] header;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
} catch (IOException ioe) {}
}
}

View File

@@ -5,6 +5,7 @@ package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
@@ -21,6 +22,7 @@ import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.FileUtil;
@@ -131,16 +133,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
private final static byte[] ERR_MAXPOST =
("HTTP/1.1 503 Bad POST\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@@ -193,7 +185,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
/**
* create the default options (using the default timeout, etc)
*
* unused?
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
@@ -218,6 +210,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
defaultOpts.setProperty(I2PSocketOptions.PROP_READ_TIMEOUT, ""+DEFAULT_READ_TIMEOUT);
if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
// delayed start
if (sockMgr == null)
sockMgr = getSocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
@@ -232,6 +227,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
private static long __requestId = 0;
protected void clientConnectionRun(Socket s) {
InputStream in = null;
OutputStream out = null;
String targetRequest = null;
boolean usingWWWProxy = false;
@@ -239,11 +235,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
long requestId = ++__requestId;
try {
out = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "ISO-8859-1"));
InputReader reader = new InputReader(s.getInputStream());
String line, method = null, protocol = null, host = null, destination = null;
StringBuffer newRequest = new StringBuffer();
int ahelper = 0;
while ((line = br.readLine()) != null) {
while ((line = reader.readLine(method)) != null) {
line = line.trim();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Line=[" + line + "]");
@@ -257,7 +254,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Method is null for [" + line + "]");
line = line.trim();
int pos = line.indexOf(" ");
if (pos == -1) break;
method = line.substring(0, pos);
@@ -470,7 +466,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Setting host = " + host);
} else if (lowercaseLine.startsWith("user-agent: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
line = null;
continue;
} else if (lowercaseLine.startsWith("accept")) {
@@ -479,13 +475,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
line = null;
continue;
} else if (lowercaseLine.startsWith("referer: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_REFERER)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_REFERER)).booleanValue()) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("via: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_VIA)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_VIA)).booleanValue()) {
//line = "Via: i2p";
line = null;
continue; // completely strip the line
@@ -498,7 +494,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (line.length() == 0) {
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
@@ -509,35 +505,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
newRequest.append("Accept-Encoding: \r\n");
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}
if (!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue())
if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue())
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
newRequest.append(line.trim()).append("\r\n"); // HTTP spec
newRequest.append(line).append("\r\n"); // HTTP spec
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
int postbytes = 0;
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
newRequest.append((char) i);
if (++postbytes > MAX_POSTBYTES) {
if (out != null) {
out.write(ERR_MAXPOST);
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;
}
}
}
if (method == null || destination == null) {
l.log("No HTTP method found in the request.");
@@ -560,7 +537,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve " + destination + ".");
//l.log("Could not resolve " + destination + ".");
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
String str;
@@ -570,6 +547,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else if(ahelper != 0)
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
else if (destination.length() == 60 && destination.endsWith(".b32.i2p"))
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
else {
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
showAddrHelper = true;
@@ -608,8 +587,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
IOException ex = new IOException("OOM (in POST?)");
} catch (OutOfMemoryError oom) {
IOException ex = new IOException("OOM");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
@@ -617,6 +596,29 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
/**
* Read the first line unbuffered.
* After that, switch to a BufferedReader, unless the method is "POST".
* We can't use BufferedReader for POST because we can't have readahead,
* since we are passing the stream on to I2PTunnelRunner for the POST data.
*
*/
private static class InputReader {
BufferedReader _br;
InputStream _s;
public InputReader(InputStream s) {
_br = null;
_s = s;
}
String readLine(String method) throws IOException {
if (method == null || "POST".equals(method))
return DataHelper.readLine(_s);
if (_br == null)
_br = new BufferedReader(new InputStreamReader(_s, "ISO-8859-1"));
return _br.readLine();
}
}
private final static String getHostName(String host) {
if (host == null) return null;
try {
@@ -628,7 +630,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private class OnTimeout implements Runnable {
private static class OnTimeout implements Runnable {
private Socket _socket;
private OutputStream _out;
private String _target;
@@ -706,11 +708,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private void handleHTTPClientException(Exception ex, OutputStream out, String targetRequest,
private static void handleHTTPClientException(Exception ex, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy, long requestId) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
// static
//if (_log.shouldLog(Log.WARN))
// _log.warn(getPrefix(requestId) + "Error sending to " + wwwProxy + " (proxy? " + usingWWWProxy + ", request: " + targetRequest, ex);
if (out != null) {
try {
String str;
@@ -725,16 +728,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
} catch (IOException ioe) {
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
// static
//_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
}
} else {
_log.warn(getPrefix(requestId) + "Client disconnected before we could say that destination " + "was unknown", ex);
// static
//_log.warn(getPrefix(requestId) + "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) {
private static boolean isSupportedAddress(String host, String protocol) {
if ((host == null) || (protocol == null)) return false;
boolean found = false;
String lcHost = host.toLowerCase();

View File

@@ -124,8 +124,17 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
try {
socket.close();
} catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new HTTP request", ex);
} catch (OutOfMemoryError oom) {
try {
socket.close();
} catch (IOException ioe) {}
if (_log.shouldLog(Log.ERROR))
_log.error("OOM in HTTP server", oom);
}
long afterHandle = getTunnel().getContext().clock().now();
@@ -162,7 +171,24 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
sender.start();
browserout = _browser.getOutputStream();
serverin = _webserver.getInputStream();
// NPE seen here in 0.7-7, caused by addition of socket.close() in the
// catch (IOException ioe) block above in blockingHandle() ???
// CRIT [ad-130280.hc] net.i2p.util.I2PThread : Killing thread Thread-130280.hc
// java.lang.NullPointerException
// at java.io.FileInputStream.<init>(FileInputStream.java:131)
// at java.net.SocketInputStream.<init>(SocketInputStream.java:44)
// at java.net.PlainSocketImpl.getInputStream(PlainSocketImpl.java:401)
// at java.net.Socket$2.run(Socket.java:779)
// at java.security.AccessController.doPrivileged(Native Method)
// at java.net.Socket.getInputStream(Socket.java:776)
// at net.i2p.i2ptunnel.I2PTunnelHTTPServer$CompressedRequestor.run(I2PTunnelHTTPServer.java:174)
// at java.lang.Thread.run(Thread.java:619)
// at net.i2p.util.I2PThread.run(I2PThread.java:71)
try {
serverin = _webserver.getInputStream();
} catch (NullPointerException npe) {
throw new IOException("getInputStream NPE");
}
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
if (_log.shouldLog(Log.INFO))

View File

@@ -39,12 +39,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
Logging l,
boolean ownDest,
EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
super(localPort,
ownDest,
l,
notifyThis,
"IRCHandler " + (++__clientId), tunnel);
"IRCHandler " + (++__clientId), tunnel, pkf);
StringTokenizer tok = new StringTokenizer(destinations, ",");
dests = new ArrayList(1);
@@ -83,9 +83,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
StringBuffer expectedPong = new StringBuffer();
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " in");
in.start();
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " out");
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))

View File

@@ -0,0 +1,191 @@
package net.i2p.i2ptunnel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.Base32;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Simple extension to the I2PTunnelServer that filters the registration
* sequence to pass the destination hash of the client through as the hostname,
* so an IRC Server may track users across nick changes.
*
* Of course, this requires the ircd actually use the hostname sent by
* the client rather than the IP. It is common for ircds to ignore the
* hostname in the USER message (unless it's coming from another server)
* since it is easily spoofed. So you have to fix or, if you are lucky,
* configure your ircd first. At least in unrealircd and ngircd this is
* not configurable.
*
* There are three options for mangling the desthash. Put the option in the
* "custom options" section of i2ptunnel.
* - ircserver.cloakKey unset: Cloak with a random value that is persistent for
* the life of this tunnel. This is the default.
* - ircserver.cloakKey=somepassphrase: Cloak with the hash of the passphrase. Use this to
* have consistent mangling across restarts, or to
* have multiple IRC servers cloak consistently to
* be able to track users even when they switch servers.
* Note: don't quote or put spaces in the passphrase,
* the i2ptunnel gui can't handle it.
* - ircserver.fakeHostname=%f.b32.i2p: Set the fake hostname sent by I2PTunnel,
* %f is the full B32 destination hash
* %c is the cloaked hash.
*
* There is no outbound filtering.
*
* @author zzz
*/
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
public static final String PROP_CLOAK="ircserver.cloakKey";
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
private static final Log _log = new Log(I2PTunnelIRCServer.class);
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelIRCServer(InetAddress host, int port, File privkey, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
initCloak(tunnel);
}
/** generate a random 32 bytes, or the hash of the passphrase */
private void initCloak(I2PTunnel tunnel) {
Properties opts = tunnel.getClientOptions();
String passphrase = opts.getProperty(PROP_CLOAK);
if (passphrase == null) {
this.cloakKey = new byte[Hash.HASH_LENGTH];
tunnel.getContext().random().nextBytes(this.cloakKey);
} else {
this.cloakKey = SHA256Generator.getInstance().calculateHash(passphrase.trim().getBytes()).getData();
}
this.hostname = opts.getProperty(PROP_HOSTNAME, PROP_HOSTNAME_DEFAULT);
}
protected void blockingHandle(I2PSocket socket) {
try {
// give them 15 seconds to send in the request
socket.setReadTimeout(15*1000);
InputStream in = socket.getInputStream();
String modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
socket.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null);
} catch (SocketException ex) {
try {
socket.close();
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error while closing the received i2p con", ex);
}
} catch (IOException ex) {
try {
socket.close();
} catch (IOException ioe) {}
if (_log.shouldLog(Log.WARN))
_log.warn("Error while receiving the new IRC Connection", ex);
} catch (OutOfMemoryError oom) {
try {
socket.close();
} catch (IOException ioe) {}
if (_log.shouldLog(Log.ERROR))
_log.error("OOM in IRC server", oom);
}
}
/**
* (Optionally) append 32 bytes of crap to the destination then return
* the first few characters of the hash of the whole thing, + ".i2p".
* Or do we want the full hash if the ircd is going to use this for
* nickserv auto-login? Or even Base32 if it will be used in a
* case-insensitive manner?
*
*/
String cloakDest(Destination d) {
String hf;
String hc;
byte[] b = new byte[d.size() + this.cloakKey.length];
System.arraycopy(b, 0, d.toByteArray(), 0, d.size());
System.arraycopy(b, d.size(), this.cloakKey, 0, this.cloakKey.length);
hc = Base32.encode(SHA256Generator.getInstance().calculateHash(b).getData());
hf = Base32.encode(d.calculateHash().getData());
return this.hostname.replace("%f", hf).replace("%c", hc);
}
/** keep reading until we see USER or SERVER */
private String filterRegistration(InputStream in, String newHostname) throws IOException {
StringBuffer buf = new StringBuffer(128);
int lineCount = 0;
while (true) {
String s = DataHelper.readLine(in);
if (s == null)
throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
if (++lineCount > 10)
throw new IOException("Too many lines before USER or SERVER, giving up");
s = s.trim();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got line: " + s);
String field[]=s.split(" ",5);
String command;
int idx=0;
if(field[0].charAt(0)==':')
idx++;
try {
command = field[idx++];
} catch (IndexOutOfBoundsException ioobe) {
// wtf, server sent borked command?
throw new IOException("Dropping defective message: index out of bounds while extracting command.");
}
if ("USER".equalsIgnoreCase(command)) {
if (field.length < idx + 4)
throw new IOException("Too few parameters in USER message: " + s);
// USER zzz1 hostname localhost :zzz
// =>
// USER zzz1 abcd1234.i2p localhost :zzz
// this whole class is for these two lines...
buf.append("USER ").append(field[idx]).append(' ').append(newHostname);
buf.append(' ');
buf.append(field[idx+2]).append(' ').append(field[idx+3]).append("\r\n");
break;
}
buf.append(s).append("\r\n");
if ("SERVER".equalsIgnoreCase(command))
break;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("All done, sending: " + buf.toString());
return buf.toString();
}
private byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
private String hostname;
}

View File

@@ -14,6 +14,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Base32;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@@ -57,7 +58,7 @@ public class TunnelController implements Logging {
setConfig(config, prefix);
_messages = new ArrayList(4);
_running = false;
if (createKey && ("server".equals(getType()) || "httpserver".equals(getType())) )
if (createKey && (getType().endsWith("server") || getPersistentClientKey()))
createPrivateKey();
_starting = getStartOnLoad();
}
@@ -72,7 +73,7 @@ public class TunnelController implements Logging {
File keyFile = new File(getPrivKeyFile());
if (keyFile.exists()) {
log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
//log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
return;
} else {
File parent = keyFile.getParentFile();
@@ -86,6 +87,7 @@ public class TunnelController implements Logging {
String destStr = dest.toBase64();
log("Private key created and saved in " + keyFile.getAbsolutePath());
log("New destination: " + destStr);
log("Base32: " + Base32.encode(dest.calculateHash().getData()) + ".b32.i2p");
} catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error creating new destination", ie);
@@ -132,25 +134,38 @@ public class TunnelController implements Logging {
_log.warn("Cannot start the tunnel - no type specified");
return;
}
setI2CPOptions();
setSessionOptions();
if ("httpclient".equals(type)) {
startHttpClient();
}else if("ircclient".equals(type)) {
} else if("ircclient".equals(type)) {
startIrcClient();
} else if("sockstunnel".equals(type)) {
startSocksClient();
} else if("connectclient".equals(type)) {
startConnectClient();
} else if ("client".equals(type)) {
startClient();
} else if ("streamrclient".equals(type)) {
startStreamrClient();
} else if ("server".equals(type)) {
startServer();
} else if ("httpserver".equals(type)) {
startHttpServer();
} else if ("ircserver".equals(type)) {
startIrcServer();
} else if ("streamrserver".equals(type)) {
startStreamrServer();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Cannot start tunnel - unknown type [" + type + "]");
return;
}
acquire();
_running = true;
}
private void startHttpClient() {
setI2CPOptions();
setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
@@ -159,20 +174,62 @@ public class TunnelController implements Logging {
_tunnel.runHttpClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runHttpClient(new String[] { listenPort, sharedClient, proxyList }, this);
acquire();
_running = true;
}
private void startConnectClient() {
setListenOn();
String listenPort = getListenPort();
String proxyList = getProxyList();
String sharedClient = getSharedClient();
if (proxyList == null)
_tunnel.runConnectClient(new String[] { listenPort, sharedClient }, this);
else
_tunnel.runConnectClient(new String[] { listenPort, sharedClient, proxyList }, this);
}
private void startIrcClient() {
setI2CPOptions();
setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
String sharedClient = getSharedClient();
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
acquire();
_running = true;
if (getPersistentClientKey()) {
String privKeyFile = getPrivKeyFile();
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
} else {
_tunnel.runIrcClient(new String[] { listenPort, dest, sharedClient }, this);
}
}
private void startSocksClient() {
setListenOn();
String listenPort = getListenPort();
String sharedClient = getSharedClient();
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
}
/*
* Streamr client is a UDP server, use the listenPort field for targetPort
* and the listenOnInterface field for the targetHost
*/
private void startStreamrClient() {
String targetHost = getListenOnInterface();
String targetPort = getListenPort();
String dest = getTargetDestination();
_tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this);
}
/**
* Streamr server is a UDP client, use the targetPort field for listenPort
* and the targetHost field for the listenOnInterface
*/
private void startStreamrServer() {
String listenOn = getTargetHost();
if ( (listenOn != null) && (listenOn.length() > 0) ) {
_tunnel.runListenOn(new String[] { listenOn }, this);
}
String listenPort = getTargetPort();
String privKeyFile = getPrivKeyFile();
_tunnel.runStreamrServer(new String[] { listenPort, privKeyFile }, this);
}
/**
@@ -208,38 +265,38 @@ public class TunnelController implements Logging {
}
private void startClient() {
setI2CPOptions();
setSessionOptions();
setListenOn();
String listenPort = getListenPort();
String dest = getTargetDestination();
String sharedClient = getSharedClient();
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
acquire();
_running = true;
if (getPersistentClientKey()) {
String privKeyFile = getPrivKeyFile();
_tunnel.runClient(new String[] { listenPort, dest, sharedClient, privKeyFile }, this);
} else {
_tunnel.runClient(new String[] { listenPort, dest, sharedClient }, this);
}
}
private void startServer() {
setI2CPOptions();
setSessionOptions();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String privKeyFile = getPrivKeyFile();
_tunnel.runServer(new String[] { targetHost, targetPort, privKeyFile }, this);
acquire();
_running = true;
}
private void startHttpServer() {
setI2CPOptions();
setSessionOptions();
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String spoofedHost = getSpoofedHost();
String privKeyFile = getPrivKeyFile();
_tunnel.runHttpServer(new String[] { targetHost, targetPort, spoofedHost, privKeyFile }, this);
acquire();
_running = true;
}
private void startIrcServer() {
String targetHost = getTargetHost();
String targetPort = getTargetPort();
String privKeyFile = getPrivKeyFile();
_tunnel.runIrcServer(new String[] { targetHost, targetPort, privKeyFile }, this);
}
private void setListenOn() {
@@ -348,6 +405,7 @@ public class TunnelController implements Logging {
public String getProxyList() { return _config.getProperty("proxyList"); }
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); }
public String getMyDestination() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
@@ -361,6 +419,19 @@ public class TunnelController implements Logging {
return null;
}
public String getMyDestHashBase32() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
for (int i = 0; i < sessions.size(); i++) {
I2PSession session = (I2PSession)sessions.get(i);
Destination dest = session.getMyDestination();
if (dest != null)
return Base32.encode(dest.calculateHash().getData());
}
}
return null;
}
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }

View File

@@ -7,6 +7,12 @@
package net.i2p.i2ptunnel.socks;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Destination;
@@ -20,7 +26,7 @@ import net.i2p.util.Log;
public class I2PSOCKSTunnel extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PSOCKSTunnel.class);
private HashMap<String, List<String>> proxies = null; // port# + "" or "default" -> hostname list
protected Destination outProxyDest = null;
//public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest) {
@@ -36,7 +42,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
}
setName(getLocalPort() + " -> SOCKSTunnel");
parseOptions();
startRunning();
notifyEvent("openSOCKSTunnelResult", "ok");
@@ -46,11 +52,49 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
try {
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
Socket clientSock = serv.getClientSocket();
I2PSocket destSock = serv.getDestinationI2PSocket();
I2PSocket destSock = serv.getDestinationI2PSocket(this);
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
} catch (SOCKSException e) {
_log.error("Error from SOCKS connection: " + e.getMessage());
closeSocket(s);
}
}
}
private static final String PROP_PROXY = "i2ptunnel.socks.proxy.";
private void parseOptions() {
Properties opts = getTunnel().getClientOptions();
proxies = new HashMap(0);
for (Map.Entry e : opts.entrySet()) {
String prop = (String)e.getKey();
if ((!prop.startsWith(PROP_PROXY)) || prop.length() <= PROP_PROXY.length())
continue;
String port = prop.substring(PROP_PROXY.length());
List proxyList = new ArrayList(1);
StringTokenizer tok = new StringTokenizer((String)e.getValue(), ", \t");
while (tok.hasMoreTokens()) {
String proxy = tok.nextToken().trim();
if (proxy.endsWith(".i2p"))
proxyList.add(proxy);
else
_log.error("Non-i2p SOCKS outproxy: " + proxy);
}
proxies.put(port, proxyList);
}
}
public HashMap<String, List<String>> getProxyMap() {
return proxies;
}
public List<String> getProxies(int port) {
List<String> rv = proxies.get(port + "");
if (rv == null)
rv = getDefaultProxies();
return rv;
}
public List<String> getDefaultProxies() {
return proxies.get("default");
}
}

View File

@@ -0,0 +1,35 @@
package net.i2p.i2ptunnel.socks;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.util.Log;
/**
* Sends to one of many Sinks
* @author zzz modded from streamr/MultiSource
*/
public class MultiSink implements Source, Sink {
private static final Log _log = new Log(MultiSink.class);
public MultiSink(Map cache) {
this.cache = cache;
}
/** Don't use this - put sinks in the cache */
public void setSink(Sink sink) {}
public void start() {}
public void send(Destination from, byte[] data) {
Sink s = this.cache.get(from);
if (s == null) {
_log.error("No where to go for " + from.calculateHash().toBase64().substring(0, 6));
return;
}
s.send(from, data);
}
private Map<Destination, Sink> cache;
}

View File

@@ -0,0 +1,36 @@
package net.i2p.i2ptunnel.socks;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.util.Log;
/**
* Track who the reply goes to
* @author zzz
*/
public class ReplyTracker implements Source, Sink {
private static final Log _log = new Log(MultiSink.class);
public ReplyTracker(Sink reply, Map cache) {
this.reply = reply;
this.cache = cache;
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {}
public void send(Destination to, byte[] data) {
this.cache.put(to, this.reply);
this.sink.send(to, data);
}
private Sink reply;
private Map<Destination, Sink> cache;
private Sink sink;
}

View File

@@ -0,0 +1,284 @@
/* 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 java.net.SocketException;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/*
* Class that manages SOCKS 4/4a connections, and forwards them to
* destination hosts or (eventually) some outproxy.
*
* @author zzz modded from SOCKS5Server
*/
public class SOCKS4aServer extends SOCKSServer {
private static final Log _log = new Log(SOCKS4aServer.class);
private Socket clientSock = null;
private boolean setupCompleted = false;
/**
* Create a SOCKS4a server that communicates with the client using
* the specified socket. This method should not be invoked
* directly: new SOCKS4aServer 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 SOCKS4aServer(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());
manageRequest(in, out);
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
setupCompleted = true;
}
/**
* SOCKS4a request management. This method assumes that all the
* stuff preceding or enveloping the actual request
* has been stripped out of the input/output streams.
*/
private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
int command = in.readByte() & 0xff;
switch (command) {
case Command.CONNECT:
break;
case Command.BIND:
_log.debug("BIND command is not supported!");
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
throw new SOCKSException("BIND command not supported");
default:
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
throw new SOCKSException("Invalid command in request");
}
connPort = in.readUnsignedShort();
if (connPort == 0) {
_log.debug("trying to connect to TCP port 0? Dropping!");
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
throw new SOCKSException("Invalid port number in request");
}
connHostName = new String("");
boolean alreadyWarned = false;
for (int i = 0; i < 4; ++i) {
int octet = in.readByte() & 0xff;
connHostName += Integer.toString(octet);
if (i != 3) {
connHostName += ".";
if (octet != 0 && !alreadyWarned) {
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
alreadyWarned = true;
}
}
}
// discard user name
readString(in);
// SOCKS 4a
if (connHostName.startsWith("0.0.0.") && !connHostName.equals("0.0.0.0"))
connHostName = readString(in);
}
private String readString(DataInputStream in) throws IOException {
StringBuffer sb = new StringBuffer(16);
char c;
while ((c = (char) (in.readByte() & 0xff)) != 0)
sb.append(c);
return sb.toString();
}
protected void confirmConnection() throws SOCKSException {
DataInputStream in;
DataOutputStream out;
try {
out = new DataOutputStream(clientSock.getOutputStream());
sendRequestReply(Reply.SUCCEEDED, InetAddress.getByName("127.0.0.1"), 1, out);
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
}
/**
* 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, InetAddress inetAddr,
int bindPort, DataOutputStream out) throws IOException {
ByteArrayOutputStream reps = new ByteArrayOutputStream();
DataOutputStream dreps = new DataOutputStream(reps);
// Reserved byte, should be 0x00
dreps.write(0x00);
dreps.write(replyCode);
dreps.writeShort(bindPort);
dreps.write(inetAddr.getAddress());
byte[] reply = reps.toByteArray();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
}
out.write(reply);
}
/**
* 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(I2PSOCKSTunnel t) 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!");
}
DataOutputStream out; // for errors
try {
out = new DataOutputStream(clientSock.getOutputStream());
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
// FIXME: here we should read our config file, select an
// outproxy, and instantiate the proper socket class that
// handles the outproxy itself (SOCKS4a, SOCKS4a, HTTP CONNECT...).
I2PSocket destSock;
try {
if (connHostName.toLowerCase().endsWith(".i2p") ||
connHostName.toLowerCase().endsWith(".onion")) {
_log.debug("connecting to " + connHostName + "...");
// Let's not due a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
} else if (connPort == 80) {
// rewrite GET line to include hostname??? or add Host: line???
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
// use eepProxy configured outproxies?
String err = "No handler for HTTP outproxy implemented - to: " + connHostName;
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.size() <= 0) {
String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
String proxy = proxies.get(p);
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
// this isn't going to work, these need to be socks outproxies so we need
// to do a socks session to them?
destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
}
confirmConnection();
_log.debug("connection confirmed - exchanging data...");
} catch (DataFormatException e) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error in destination format");
} catch (SocketException e) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (IOException e) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (I2PException e) {
try {
sendRequestReply(Reply.CONNECTION_REFUSED, InetAddress.getByName("127.0.0.1"), 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
}
return destSock;
}
/*
* Some namespaces to enclose SOCKS protocol codes
*/
private static class Command {
private static final int CONNECT = 0x01;
private static final int BIND = 0x02;
}
private static class Reply {
private static final int SUCCEEDED = 0x5a;
private static final int CONNECTION_REFUSED = 0x5b;
}
}

View File

@@ -12,7 +12,17 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
@@ -28,7 +38,6 @@ public class SOCKS5Server extends SOCKSServer {
private static final int SOCKS_VERSION_5 = 0x05;
private Socket clientSock = null;
private boolean setupCompleted = false;
/**
@@ -61,7 +70,8 @@ public class SOCKS5Server extends SOCKSServer {
out = new DataOutputStream(clientSock.getOutputStream());
init(in, out);
manageRequest(in, out);
if (manageRequest(in, out) == Command.UDP_ASSOCIATE)
handleUDP(in, out);
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
@@ -105,7 +115,7 @@ public class SOCKS5Server extends SOCKSServer {
* initialization, integrity/confidentiality encapsulations, etc)
* has been stripped out of the input/output streams.
*/
private void manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
private int 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?)");
@@ -121,11 +131,15 @@ public class SOCKS5Server extends SOCKSServer {
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:
/*** if(!Boolean.valueOf(tunnel.getOptions().getProperty("i2ptunnel.socks.allowUDP")).booleanValue()) {
_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");
***/
break;
default:
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid command in request");
}
@@ -145,7 +159,8 @@ public class SOCKS5Server extends SOCKSServer {
connHostName += ".";
}
}
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
if (command != Command.UDP_ASSOCIATE)
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
break;
case AddressType.DOMAINNAME:
{
@@ -161,19 +176,25 @@ public class SOCKS5Server extends SOCKSServer {
_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");
if (command != Command.UDP_ASSOCIATE) {
_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");
}
break;
default:
_log.debug("unknown address type in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid addresses type in request");
}
connPort = in.readUnsignedShort();
if (connPort == 0) {
_log.debug("trying to connect to TCP port 0? Dropping!");
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid port number in request");
}
return command;
}
protected void confirmConnection() throws SOCKSException {
@@ -248,27 +269,188 @@ public class SOCKS5Server extends SOCKSServer {
out.write(reply);
}
/**
* 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(I2PSOCKSTunnel t) 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!");
}
DataOutputStream out; // for errors
try {
out = new DataOutputStream(clientSock.getOutputStream());
} catch (IOException e) {
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
}
// 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 + "...");
// Let's not due a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
Destination dest = I2PTunnel.destFromName(connHostName);
if (dest == null) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Host not found");
}
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
} else if (connPort == 80) {
// rewrite GET line to include hostname??? or add Host: line???
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
// use eepProxy configured outproxies?
String err = "No handler for HTTP outproxy implemented";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.size() <= 0) {
String err = "No outproxy configured for port " + connPort + " and no default configured either";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
String proxy = proxies.get(p);
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
// this isn't going to work, these need to be socks outproxies so we need
// to do a socks session to them?
destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
}
confirmConnection();
_log.debug("connection confirmed - exchanging data...");
} catch (DataFormatException e) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error in destination format");
} catch (SocketException e) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (IOException e) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (I2PException e) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
}
return destSock;
}
// This isn't really the right place for this, we can't stop the tunnel once it starts.
static SOCKSUDPTunnel _tunnel;
static Object _startLock = new Object();
static byte[] dummyIP = new byte[4];
/**
* We got a UDP associate command.
* Loop here looking for more, never return normally,
* or else I2PSocksTunnel will create a streaming lib connection.
*
* Do UDP Socks clients actually send more than one Associate request?
* RFC 1928 isn't clear... maybe not.
*/
private void handleUDP(DataInputStream in, DataOutputStream out) throws SOCKSException {
List<Integer> ports = new ArrayList(1);
synchronized (_startLock) {
if (_tunnel == null) {
// tunnel options?
_tunnel = new SOCKSUDPTunnel(new I2PTunnel());
_tunnel.startRunning();
}
}
while (true) {
// Set it up. connHostName and connPort are the client's info.
InetAddress ia = null;
try {
ia = InetAddress.getByAddress(connHostName, dummyIP);
} catch (UnknownHostException uhe) {} // won't happen, no resolving done here
int myPort = _tunnel.add(ia, connPort);
ports.add(Integer.valueOf(myPort));
try {
sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, myPort, out);
} catch (IOException ioe) { break; }
// wait for more ???
try {
int command = manageRequest(in, out);
// don't do this...
if (command != Command.UDP_ASSOCIATE)
break;
} catch (IOException ioe) { break; }
catch (SOCKSException ioe) { break; }
}
for (Integer i : ports)
_tunnel.remove(i);
// Prevent I2PSocksTunnel from calling getDestinationI2PSocket() above
// to create a streaming lib connection...
// This isn't very elegant...
//
throw new SOCKSException("End of UDP Processing");
}
/*
* Some namespaces to enclose SOCKS protocol codes
*/
private class Method {
private static class Method {
private static final int NO_AUTH_REQUIRED = 0x00;
private static final int NO_ACCEPTABLE_METHODS = 0xff;
}
private class AddressType {
private static 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 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 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;
@@ -279,4 +461,4 @@ public class SOCKS5Server extends SOCKSServer {
private static final int COMMAND_NOT_SUPPORTED = 0x07;
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
}
}
}

View File

@@ -0,0 +1,89 @@
package net.i2p.i2ptunnel.socks;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
/**
* Save the SOCKS header from a datagram
* Ref: RFC 1928
*
* @author zzz
*/
public class SOCKSHeader {
/**
* @param data the whole packet
*/
public SOCKSHeader(byte[] data) {
if (data.length <= 8)
throw new IllegalArgumentException("Header too short: " + data.length);
if (data[0] != 0 || data[1] != 0)
throw new IllegalArgumentException("Not a SOCKS datagram?");
if (data[2] != 0)
throw new IllegalArgumentException("We can't handle fragments!");
int headerlen = 0;
int addressType = data[3];
if (addressType == 1) {
// this will fail in getDestination()
headerlen = 6 + 4;
} else if (addressType == 3) {
headerlen = 6 + 1 + (data[4] & 0xff);
} else if (addressType == 4) {
// this will fail in getDestination()
// but future garlicat partial hash lookup possible?
headerlen = 6 + 16;
} else {
throw new IllegalArgumentException("Unknown address type: " + addressType);
}
if (data.length < headerlen)
throw new IllegalArgumentException("Header too short: " + data.length);
this.header = new byte[headerlen];
System.arraycopy(this.header, 0, data, 0, headerlen);
}
private static final byte[] beg = {0,0,0,3,60};
private static final byte[] end = {'.','b','3','2','.','i','2','p',0,0};
/**
* Make a dummy header from a dest,
* for those cases where we want to receive unsolicited datagrams.
* Unused for now.
*/
public SOCKSHeader(Destination dest) {
this.header = new byte[beg.length + 52 + end.length];
System.arraycopy(this.header, 0, beg, 0, beg.length);
String b32 = Base32.encode(dest.calculateHash().getData());
System.arraycopy(this.header, beg.length, b32.getBytes(), 0, 52);
System.arraycopy(this.header, beg.length + 52, end, 0, end.length);
}
public String getHost() {
int addressType = this.header[3];
if (addressType != 3)
return null;
int namelen = (this.header[4] & 0xff);
byte[] nameBytes = new byte[namelen];
System.arraycopy(nameBytes, 0, this.header, 5, namelen);
return new String(nameBytes);
}
public Destination getDestination() {
String name = getHost();
if (name == null)
return null;
try {
// the naming service does caching (thankfully)
return I2PTunnel.destFromName(name);
} catch (DataFormatException dfe) {}
return null;
}
public byte[] getBytes() {
return header;
}
private byte[] header;
}

View File

@@ -6,15 +6,9 @@
*/
package net.i2p.i2ptunnel.socks;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
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.data.DataFormatException;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.Log;
@@ -30,10 +24,6 @@ public abstract class SOCKSServer {
protected String connHostName = null;
protected int connPort = 0;
I2PSocket destSocket = null;
Object FIXME = new Object();
/**
* Perform server initialization (expecially regarding protected
* variables).
@@ -59,47 +49,6 @@ public abstract class SOCKSServer {
*
* @return an I2PSocket connected with the destination
*/
public I2PSocket getDestinationI2PSocket() throws SOCKSException {
setupServer();
public abstract I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException;
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), null);
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 (SocketException e) {
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (IOException e) {
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
} catch (I2PException e) {
throw new SOCKSException("Error connecting ("
+ e.getMessage() + ")");
}
return destSock;
}
}

View File

@@ -7,6 +7,7 @@
package net.i2p.i2ptunnel.socks;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
@@ -18,6 +19,15 @@ import net.i2p.util.Log;
public class SOCKSServerFactory {
private final static Log _log = new Log(SOCKSServerFactory.class);
private final static String ERR_REQUEST_DENIED =
"HTTP/1.1 403 Access Denied\r\n" +
"Content-Type: text/html; charset=iso-8859-1\r\n" +
"Cache-control: no-cache\r\n" +
"\r\n" +
"<html><body><H1>I2P SOCKS PROXY ERROR: REQUEST DENIED</H1>" +
"Your browser is misconfigured. This is a SOCKS proxy, not a HTTP proxy" +
"</body></html>";
/**
* Create a new SOCKS server, using the provided socket (that must
* be connected to a client) to select the proper SOCKS protocol
@@ -34,13 +44,23 @@ public class SOCKSServerFactory {
int socksVer = in.readByte();
switch (socksVer) {
case 0x04:
// SOCKS version 4/4a
serv = new SOCKS4aServer(s);
break;
case 0x05:
// SOCKS version 5
serv = new SOCKS5Server(s);
break;
case 'C':
case 'G':
case 'H':
case 'P':
DataOutputStream out = new DataOutputStream(s.getOutputStream());
out.write(ERR_REQUEST_DENIED.getBytes());
throw new SOCKSException("HTTP request to socks");
default:
_log.debug("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")");
return null;
throw new SOCKSException("SOCKS protocol version not supported (" + Integer.toHexString(socksVer) + ")");
}
} catch (IOException e) {
_log.debug("error reading SOCKS protocol version");
@@ -49,4 +69,4 @@ public class SOCKSServerFactory {
return serv;
}
}
}

View File

@@ -0,0 +1,77 @@
package net.i2p.i2ptunnel.socks;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
/**
* Implements a UDP port and Socks encapsulation / decapsulation.
* This is for a single port. If there is demuxing for multiple
* ports, it happens outside of here.
*
* TX:
* UDPSource -> SOCKSUDPUnwrapper -> ReplyTracker ( -> I2PSink in SOCKSUDPTunnel)
*
* RX:
* UDPSink <- SOCKSUDPWrapper ( <- MultiSink <- I2PSource in SOCKSUDPTunnel)
*
* The Unwrapper passes headers to the Wrapper through a cache.
* The ReplyTracker passes sinks to MultiSink through a cache.
*
* @author zzz
*/
public class SOCKSUDPPort implements Source, Sink {
public SOCKSUDPPort(InetAddress host, int port, Map replyMap) {
// this passes the host and port from UDPUnwrapper to UDPWrapper
Map cache = new ConcurrentHashMap(4);
// rcv from I2P and send to a port
this.wrapper = new SOCKSUDPWrapper(cache);
this.udpsink = new UDPSink(host, port);
this.wrapper.setSink(this.udpsink);
// rcv from the same port and send to I2P
DatagramSocket sock = this.udpsink.getSocket();
this.udpsource = new UDPSource(sock);
this.unwrapper = new SOCKSUDPUnwrapper(cache);
this.udpsource.setSink(this.unwrapper);
this.udptracker = new ReplyTracker(this, replyMap);
this.unwrapper.setSink(this.udptracker);
}
/** Socks passes this back to the client on the TCP connection */
public int getPort() {
return this.udpsink.getPort();
}
public void setSink(Sink sink) {
this.udptracker.setSink(sink);
}
public void start() {
// the other Sources don't use start
this.udpsource.start();
}
public void stop() {
this.udpsink.stop();
this.udpsource.stop();
}
public void send(Destination from, byte[] data) {
this.wrapper.send(from, data);
}
private UDPSink udpsink;
private UDPSource udpsource;
private SOCKSUDPWrapper wrapper;
private SOCKSUDPUnwrapper unwrapper;
private ReplyTracker udptracker;
}

View File

@@ -0,0 +1,94 @@
package net.i2p.i2ptunnel.socks;
import java.net.InetAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
import net.i2p.util.EventDispatcher;
/**
* A Datagram Tunnel that can have multiple bidirectional ports on the UDP side.
*
* TX:
* (ReplyTracker in multiple SOCKSUDPPorts -> ) I2PSink
*
* RX:
* (SOCKSUDPWrapper in multiple SOCKSUDPPorts <- ) MultiSink <- I2PSource
*
* The reply from a dest goes to the last SOCKSUDPPort that sent to that dest.
* If multiple ports are talking to a dest at the same time, this isn't
* going to work very well.
*
* @author zzz modded from streamr/StreamrConsumer
*/
public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
/**
* Set up a tunnel with no UDP side yet.
* Use add() for each port.
*/
public SOCKSUDPTunnel(I2PTunnel tunnel) {
super(null, tunnel, tunnel, tunnel);
this.ports = new ConcurrentHashMap(1);
this.cache = new ConcurrentHashMap(1);
this.demuxer = new MultiSink(this.cache);
setSink(this.demuxer);
}
/** @return the UDP port number */
public int add(InetAddress host, int port) {
SOCKSUDPPort sup = new SOCKSUDPPort(host, port, this.cache);
this.ports.put(Integer.valueOf(sup.getPort()), sup);
sup.setSink(this);
sup.start();
return sup.getPort();
}
public void remove(Integer port) {
SOCKSUDPPort sup = this.ports.remove(port);
if (sup != null)
sup.stop();
for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();) {
Map.Entry<Destination, SOCKSUDPPort> e = (Map.Entry) iter.next();
if (e.getValue() == sup)
iter.remove();
}
}
public final void startRunning() {
super.startRunning();
// demuxer start() doesn't do anything
startall();
}
public boolean close(boolean forced) {
stopall();
return super.close(forced);
}
/** you should really add() after startRunning() */
private void startall() {
}
private void stopall() {
for (SOCKSUDPPort sup : this.ports.values()) {
sup.stop();
}
this.ports.clear();
this.cache.clear();
}
private Map<Integer, SOCKSUDPPort> ports;
private Map<Destination, SOCKSUDPPort> cache;
private MultiSink demuxer;
}

View File

@@ -0,0 +1,59 @@
package net.i2p.i2ptunnel.socks;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.util.Log;
/**
* Strip a SOCKS header off a datagram, convert it to a Destination
* Ref: RFC 1928
*
* @author zzz
*/
public class SOCKSUDPUnwrapper implements Source, Sink {
private static final Log _log = new Log(SOCKSUDPUnwrapper.class);
/**
* @param cache put headers here to pass to SOCKSUDPWrapper
*/
public SOCKSUDPUnwrapper(Map<Destination, SOCKSHeader> cache) {
this.cache = cache;
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {}
/**
*
*/
public void send(Destination ignored_from, byte[] data) {
SOCKSHeader h;
try {
h = new SOCKSHeader(data);
} catch (IllegalArgumentException iae) {
_log.error(iae.toString());
return;
}
Destination dest = h.getDestination();
if (dest == null) {
// no, we aren't going to send non-i2p traffic to a UDP outproxy :)
_log.error("Destination not found: " + h.getHost());
return;
}
cache.put(dest, h);
int headerlen = h.getBytes().length;
byte unwrapped[] = new byte[data.length - headerlen];
System.arraycopy(unwrapped, 0, data, headerlen, unwrapped.length);
this.sink.send(dest, unwrapped);
}
private Sink sink;
private Map<Destination, SOCKSHeader> cache;
}

View File

@@ -0,0 +1,49 @@
package net.i2p.i2ptunnel.socks;
import java.util.Map;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
/**
* Put a SOCKS header on a datagram
* Ref: RFC 1928
*
* @author zzz
*/
public class SOCKSUDPWrapper implements Source, Sink {
public SOCKSUDPWrapper(Map<Destination, SOCKSHeader> cache) {
this.cache = cache;
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {}
/**
* Use the cached header, which should have the host string and port
*
*/
public void send(Destination from, byte[] data) {
if (this.sink == null)
return;
SOCKSHeader h = cache.get(from);
if (h == null) {
// RFC 1928 says drop
// h = new SOCKSHeader(from);
return;
}
byte[] header = h.getBytes();
byte wrapped[] = new byte[header.length + data.length];
System.arraycopy(wrapped, 0, header, 0, header.length);
System.arraycopy(wrapped, header.length, data, 0, data.length);
this.sink.send(from, wrapped);
}
private Sink sink;
private Map<Destination, SOCKSHeader> cache;
}

View File

@@ -0,0 +1,64 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.streamr;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;
/**
* Sends to many Sinks
* @author welterde
* @author zzz modded for I2PTunnel
*/
public class MultiSource implements Source, Sink {
public MultiSource() {
this.sinks = new CopyOnWriteArrayList<Destination>();
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {}
public void stop() {
this.sinks.clear();
}
public void send(Destination ignored_from, byte[] data) {
for(Destination dest : this.sinks) {
this.sink.send(dest, data);
}
}
public void add(Destination sink) {
this.sinks.add(sink);
}
public void remove(Destination sink) {
this.sinks.remove(sink);
}
private Sink sink;
private List<Destination> sinks;
}

View File

@@ -0,0 +1,59 @@
package net.i2p.i2ptunnel.streamr;
import net.i2p.i2ptunnel.udp.*;
/**
*
* @author welterde/zzz
*/
public class Pinger implements Source, Runnable {
public Pinger() {
this.thread = new Thread(this);
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {
this.running = true;
this.waitlock = new Object();
this.thread.start();
}
public void stop() {
this.running = false;
synchronized(this.waitlock) {
this.waitlock.notifyAll();
}
// send unsubscribe-message
byte[] data = new byte[1];
data[0] = 1;
this.sink.send(null, data);
}
public void run() {
// send subscribe-message
byte[] data = new byte[1];
data[0] = 0;
int i = 0;
while(this.running) {
//System.out.print("p");
this.sink.send(null, data);
synchronized(this.waitlock) {
int delay = 10000;
if (i < 5) {
i++;
delay = 2000;
}
try {
this.waitlock.wait(delay);
} catch(InterruptedException ie) {}
}
}
}
protected Sink sink;
protected Thread thread;
protected Object waitlock;
protected boolean running;
}

View File

@@ -0,0 +1,66 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.streamr;
import java.net.InetAddress;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
import net.i2p.util.EventDispatcher;
/**
* Compared to a standard I2PTunnel,
* this acts like a client on the I2P side (no privkey file)
* but a server on the UDP side (sends to a configured host/port)
*
* @author welterde
* @author zzz modded for I2PTunnel
*/
public class StreamrConsumer extends I2PTunnelUDPClientBase {
public StreamrConsumer(InetAddress host, int port, String destination,
Logging l, EventDispatcher notifyThis,
I2PTunnel tunnel) {
super(destination, l, notifyThis, tunnel);
// create udp-destination
this.sink = new UDPSink(host, port);
setSink(this.sink);
// create pinger
this.pinger = new Pinger();
this.pinger.setSink(this);
}
public final void startRunning() {
super.startRunning();
// send subscribe-message
this.pinger.start();
l.log("Streamr client ready");
}
public boolean close(boolean forced) {
// send unsubscribe-message
this.pinger.stop();
this.sink.stop();
return super.close(forced);
}
private UDPSink sink;
private Pinger pinger;
}

View File

@@ -0,0 +1,72 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.streamr;
// system
import java.io.File;
// i2p
import net.i2p.client.I2PSession;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase;
import net.i2p.util.EventDispatcher;
/**
* Compared to a standard I2PTunnel,
* this acts like a server on the I2P side (persistent privkey file)
* but a client on the UDP side (receives on a configured port)
*
* @author welterde
* @author zzz modded for I2PTunnel
*/
public class StreamrProducer extends I2PTunnelUDPServerBase {
public StreamrProducer(int port,
File privkey, String privkeyname, Logging l,
EventDispatcher notifyThis, I2PTunnel tunnel) {
// verify subscription requests
super(true, privkey, privkeyname, l, notifyThis, tunnel);
// The broadcaster
this.multi = new MultiSource();
this.multi.setSink(this);
// The listener
this.subscriber = new Subscriber(this.multi);
setSink(this.subscriber);
// now start udp-server
this.server = new UDPSource(port);
this.server.setSink(this.multi);
}
public final void startRunning() {
super.startRunning();
this.server.start();
l.log("Streamr server ready");
}
public boolean close(boolean forced) {
this.server.stop();
this.multi.stop();
return super.close(forced);
}
private MultiSource multi;
private UDPSource server;
private Sink subscriber;
}

View File

@@ -0,0 +1,75 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.streamr;
// system
import java.io.File;
import java.util.Set;
// i2p
import net.i2p.client.I2PSession;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase;
import net.i2p.util.EventDispatcher;
import net.i2p.util.ConcurrentHashSet;
/**
* server-mode
* @author welterde
* @author zzz modded from Producer for I2PTunnel
*/
public class Subscriber implements Sink {
public Subscriber(MultiSource multi) {
this.multi = multi;
// subscriptions
this.subscriptions = new ConcurrentHashSet<Destination>();
}
public void send(Destination dest, byte[] data) {
if(dest == null || data.length < 1) {
// invalid packet
// TODO: write to log
} else {
byte ctrl = data[0];
if(ctrl == 0) {
if (!this.subscriptions.contains(dest)) {
// subscribe
System.out.println("Add subscription: " + dest.toBase64().substring(0,4));
this.subscriptions.add(dest);
this.multi.add(dest);
} // else already subscribed
} else if(ctrl == 1) {
// unsubscribe
System.out.println("Remove subscription: " + dest.toBase64().substring(0,4));
boolean removed = this.subscriptions.remove(dest);
if(removed)
multi.remove(dest);
} else {
// invalid packet
// TODO: write to log
}
}
}
private I2PSession sess;
private Source listener;
private Set<Destination> subscriptions;
private MultiSource multi;
private Source server;
}

View File

@@ -0,0 +1,71 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// i2p
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Destination;
import net.i2p.client.datagram.I2PDatagramMaker;
/**
* Producer
*
* This sends to a fixed destination specified in the constructor
*
* @author welterde
*/
public class I2PSink implements Sink {
public I2PSink(I2PSession sess, Destination dest) {
this(sess, dest, false);
}
public I2PSink(I2PSession sess, Destination dest, boolean raw) {
this.sess = sess;
this.dest = dest;
this.raw = raw;
// create maker
if (!raw)
this.maker = new I2PDatagramMaker(this.sess);
}
/** @param src ignored */
public synchronized void send(Destination src, byte[] data) {
//System.out.print("w");
// create payload
byte[] payload;
if(!this.raw)
payload = this.maker.makeI2PDatagram(data);
else
payload = data;
// send message
try {
this.sess.sendMessage(this.dest, payload, I2PSession.PROTO_DATAGRAM,
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
} catch(I2PSessionException exc) {
// TODO: handle better
exc.printStackTrace();
}
}
protected boolean raw;
protected I2PSession sess;
protected Destination dest;
protected I2PDatagramMaker maker;
}

View File

@@ -0,0 +1,69 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// i2p
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Destination;
import net.i2p.client.datagram.I2PDatagramMaker;
/**
* Producer
*
* This sends to any destination specified in send()
*
* @author zzz modded from I2PSink by welterde
*/
public class I2PSinkAnywhere implements Sink {
public I2PSinkAnywhere(I2PSession sess) {
this(sess, false);
}
public I2PSinkAnywhere(I2PSession sess, boolean raw) {
this.sess = sess;
this.raw = raw;
// create maker
if (!raw)
this.maker = new I2PDatagramMaker(this.sess);
}
/** @param to - where it's going */
public synchronized void send(Destination to, byte[] data) {
// create payload
byte[] payload;
if(!this.raw)
payload = this.maker.makeI2PDatagram(data);
else
payload = data;
// send message
try {
this.sess.sendMessage(to, payload, I2PSession.PROTO_DATAGRAM,
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
} catch(I2PSessionException exc) {
// TODO: handle better
exc.printStackTrace();
}
}
protected boolean raw;
protected I2PSession sess;
protected Destination dest;
protected I2PDatagramMaker maker;
}

View File

@@ -0,0 +1,123 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// system
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// i2p
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionListener;
import net.i2p.client.datagram.I2PDatagramDissector;
/**
*
* @author welterde
*/
public class I2PSource implements Source, Runnable {
public I2PSource(I2PSession sess) {
this(sess, true, false);
}
public I2PSource(I2PSession sess, boolean verify) {
this(sess, verify, false);
}
public I2PSource(I2PSession sess, boolean verify, boolean raw) {
this.sess = sess;
this.sink = null;
this.verify = verify;
this.raw = raw;
// create queue
this.queue = new ArrayBlockingQueue(256);
// create listener
this.sess.setSessionListener(new Listener());
// create thread
this.thread = new Thread(this);
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {
this.thread.start();
}
public void run() {
// create dissector
I2PDatagramDissector diss = new I2PDatagramDissector();
while(true) {
try {
// get id
int id = this.queue.take();
// receive message
byte[] msg = this.sess.receiveMessage(id);
if(!this.raw) {
// load datagram into it
diss.loadI2PDatagram(msg);
// now call sink
if(this.verify)
this.sink.send(diss.getSender(), diss.getPayload());
else
this.sink.send(diss.extractSender(), diss.extractPayload());
} else {
// verify is ignored
this.sink.send(null, msg);
}
//System.out.print("r");
} catch(Exception e) {
e.printStackTrace();
}
}
}
protected class Listener implements I2PSessionListener {
public void messageAvailable(I2PSession sess, int id, long size) {
try {
queue.put(id);
} catch(Exception e) {
// ignore
}
}
public void reportAbuse(I2PSession arg0, int arg1) {
// ignore
}
public void disconnected(I2PSession arg0) {
// ignore
}
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
// ignore
}
}
protected I2PSession sess;
protected BlockingQueue<Integer> queue;
protected Sink sink;
protected Thread thread;
protected boolean verify;
protected boolean raw;
}

View File

@@ -0,0 +1,17 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// i2p
import net.i2p.data.Destination;
/**
*
* @author welterde
*/
public interface Sink {
public void send(Destination src, byte[] data);
}

View File

@@ -0,0 +1,15 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
/**
*
* @author welterde
*/
public interface Source {
public void setSink(Sink sink);
public void start();
}

View File

@@ -0,0 +1,15 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
/**
*
* @author welterde
*/
public interface Stream {
public void start();
public void stop();
}

View File

@@ -0,0 +1,77 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// system
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
// i2p
import net.i2p.data.Destination;
/**
*
* @author welterde
*/
public class UDPSink implements Sink {
public UDPSink(InetAddress host, int port) {
// create socket
try {
this.sock = new DatagramSocket();
} catch(Exception e) {
// TODO: fail better
throw new RuntimeException("failed to open udp-socket", e);
}
this.remoteHost = host;
// remote port
this.remotePort = port;
}
public void send(Destination src, byte[] data) {
// if data.length > this.sock.getSendBufferSize() ...
// create packet
DatagramPacket packet = new DatagramPacket(data, data.length, this.remoteHost, this.remotePort);
// send packet
try {
this.sock.send(packet);
} catch(Exception e) {
// TODO: fail a bit better
e.printStackTrace();
}
}
public int getPort() {
return this.sock.getLocalPort();
}
/** to pass to UDPSource constructor */
public DatagramSocket getSocket() {
return this.sock;
}
public void stop() {
this.sock.close();
}
protected DatagramSocket sock;
protected InetAddress remoteHost;
protected int remotePort;
}

View File

@@ -0,0 +1,91 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.i2p.i2ptunnel.udp;
// system
import java.net.DatagramSocket;
import java.net.DatagramPacket;
/**
*
* @author welterde
*/
public class UDPSource implements Source, Runnable {
public static final int MAX_SIZE = 15360;
public UDPSource(int port) {
this.sink = null;
// create udp-socket
try {
this.sock = new DatagramSocket(port);
} catch(Exception e) {
throw new RuntimeException("failed to listen...", e);
}
// create thread
this.thread = new Thread(this);
}
/** use socket from UDPSink */
public UDPSource(DatagramSocket sock) {
this.sink = null;
this.sock = sock;
this.thread = new Thread(this);
}
public void setSink(Sink sink) {
this.sink = sink;
}
public void start() {
this.thread.start();
}
public void run() {
// create packet
byte[] buf = new byte[MAX_SIZE];
DatagramPacket pack = new DatagramPacket(buf, buf.length);
while(true) {
try {
// receive...
this.sock.receive(pack);
// create new data array
byte[] nbuf = new byte[pack.getLength()];
// copy over
System.arraycopy(pack.getData(), 0, nbuf, 0, nbuf.length);
// transfer to sink
this.sink.send(null, nbuf);
//System.out.print("i");
} catch(Exception e) {
e.printStackTrace();
break;
}
}
}
public void stop() {
this.sock.close();
}
protected DatagramSocket sock;
protected Sink sink;
protected Thread thread;
}

View File

@@ -0,0 +1,210 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel.udpTunnel;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.NoRouteToHostException;
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.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.I2PTunnelTask;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink {
private static final Log _log = new Log(I2PTunnelUDPClientBase.class);
protected I2PAppContext _context;
protected Logging l;
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
private static volatile long __clientId = 0;
protected long _clientId;
protected Destination dest = null;
private boolean listenerReady = false;
private ServerSocket ss;
private Object startLock = new Object();
private boolean startRunning = false;
private byte[] pubkey;
private String handlerName;
private Object conLock = new Object();
/** How many connections will we allow to be in the process of being built at once? */
private int _numConnectionBuilders;
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
private int _maxWaitTime;
private I2PSession _session;
private Source _i2pSource;
private Sink _i2pSink;
private Destination _otherDest;
/**
* Base client class that sets up an I2P Datagram client destination.
* The UDP side is not implemented here, as there are at least
* two possibilities:
*
* 1) UDP side is a "server"
* Example: Streamr Consumer
* - Configure a destination host and port
* - External application sends no data
* - Extending class must have a constructor with host and port arguments
*
* 2) UDP side is a client/server
* Example: SOCKS UDP (DNS requests?)
* - configure an inbound port and a destination host and port
* - External application sends and receives data
* - Extending class must have a constructor with host and 2 port arguments
*
* So the implementing class must create a UDPSource and/or UDPSink,
* and must call setSink().
*
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
* badly that we cant create a socketManager
*
* @author zzz with portions from welterde's streamr
*/
public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super("UDPServer", notifyThis, tunnel);
_clientId = ++__clientId;
this.l = l;
_context = tunnel.getContext();
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
// create i2pclient and destination
I2PClient client = I2PClientFactory.createClient();
Destination dest;
byte[] key;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
dest = client.createDestination(out);
key = out.toByteArray();
} catch(Exception exc) {
throw new RuntimeException("failed to create i2p-destination", exc);
}
// create a session
try {
ByteArrayInputStream in = new ByteArrayInputStream(key);
_session = client.createSession(in, tunnel.getClientOptions());
} catch(Exception exc) {
throw new RuntimeException("failed to create session", exc);
}
// Setup the source. Always expect raw unverified datagrams.
_i2pSource = new I2PSource(_session, false, true);
// Setup the sink. Always send repliable datagrams.
if (destination != null && destination.length() > 0) {
try {
_otherDest = I2PTunnel.destFromName(destination);
} catch (DataFormatException dfe) {}
if (_otherDest == null) {
l.log("Could not resolve " + destination);
throw new RuntimeException("failed to create session - could not resolve " + destination);
}
_i2pSink = new I2PSink(_session, _otherDest, false);
} else {
_i2pSink = new I2PSinkAnywhere(_session, false);
}
}
/**
* Actually start working on outgoing connections.
* Classes should override to start UDP side as well.
*
* Not specified in I2PTunnelTask but used in both
* I2PTunnelClientBase and I2PTunnelServer so let's
* implement it here too.
*/
public void startRunning() {
synchronized (startLock) {
try {
_session.connect();
} catch(I2PSessionException exc) {
throw new RuntimeException("failed to connect session", exc);
}
start();
startRunning = true;
startLock.notify();
}
open = true;
}
/**
* I2PTunnelTask Methods
*
* Classes should override to close UDP side as well
*/
public boolean close(boolean forced) {
if (!open) return true;
if (_session != null) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {}
}
l.log("Closing client " + toString());
open = false;
return true;
}
/**
* Source Methods
*
* Sets the receiver of the UDP datagrams from I2P
* Subclass must call this after constructor
* and before start()
*/
public void setSink(Sink s) {
_i2pSource.setSink(s);
}
/** start the source */
public void start() {
_i2pSource.start();
}
/**
* Sink Methods
*
* @param to - ignored if configured for a single destination
* (we use the dest specified in the constructor)
*/
public void send(Destination to, byte[] data) {
_i2pSink.send(to, data);
}
}

View File

@@ -0,0 +1,211 @@
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
* (c) 2003 - 2004 mihi
*/
package net.i2p.i2ptunnel.udpTunnel;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Iterator;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.i2ptunnel.I2PTunnelTask;
import net.i2p.i2ptunnel.Logging;
import net.i2p.i2ptunnel.udp.*;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink {
private final static Log _log = new Log(I2PTunnelUDPServerBase.class);
private Object lock = new Object();
protected Object slock = new Object();
private static volatile long __serverId = 0;
protected Logging l;
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
/** default timeout to 3 minutes - override if desired */
protected long readTimeout = DEFAULT_READ_TIMEOUT;
private I2PSession _session;
private Source _i2pSource;
private Sink _i2pSink;
/**
* Base client class that sets up an I2P Datagram server destination.
* The UDP side is not implemented here, as there are at least
* two possibilities:
*
* 1) UDP side is a "client"
* Example: Streamr Producer
* - configure an inbound port
* - External application receives no data
* - Extending class must have a constructor with a port argument
*
* 2) UDP side is a client/server
* Example: DNS
* - configure an inbound port and a destination host and port
* - External application sends and receives data
* - Extending class must have a constructor with host and 2 port arguments
*
* So the implementing class must create a UDPSource and/or UDPSink,
* and must call setSink().
*
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
* badly that we cant create a socketManager
*
* @author zzz with portions from welterde's streamr
*/
public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l,
EventDispatcher notifyThis, I2PTunnel tunnel) {
super("UDPServer <- " + privkeyname, notifyThis, tunnel);
FileInputStream fis = null;
try {
fis = new FileInputStream(privkey);
init(verify, fis, privkeyname, l);
} catch (IOException ioe) {
_log.error("Error starting server", ioe);
notifyEvent("openServerResult", "error");
} finally {
if (fis != null)
try { fis.close(); } catch (IOException ioe) {}
}
}
private void init(boolean verify, InputStream privData, String privkeyname, Logging l) {
this.l = l;
int portNum = 7654;
if (getTunnel().port != null) {
try {
portNum = Integer.parseInt(getTunnel().port);
} catch (NumberFormatException nfe) {
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
}
}
// create i2pclient
I2PClient client = I2PClientFactory.createClient();
try {
_session = client.createSession(privData, getTunnel().getClientOptions());
} catch(I2PSessionException exc) {
throw new RuntimeException("failed to create session", exc);
}
// Setup the source. Always expect repliable datagrams, optionally verify
_i2pSource = new I2PSource(_session, verify, false);
// Setup the sink. Always send raw datagrams.
_i2pSink = new I2PSinkAnywhere(_session, true);
}
/**
* Classes should override to start UDP side as well.
*
* Not specified in I2PTunnelTask but used in both
* I2PTunnelClientBase and I2PTunnelServer so let's
* implement it here too.
*/
public void startRunning() {
//synchronized (startLock) {
try {
_session.connect();
} catch(I2PSessionException exc) {
throw new RuntimeException("failed to connect session", exc);
}
start();
//}
notifyEvent("openServerResult", "ok");
open = true;
}
/**
* Set the read idle timeout for newly-created connections (in
* milliseconds). After this time expires without data being reached from
* the I2P network, the connection itself will be closed.
*/
public void setReadTimeout(long ms) {
readTimeout = ms;
}
/**
* Get the read idle timeout for newly-created connections (in
* milliseconds).
*
* @return The read timeout used for connections
*/
public long getReadTimeout() {
return readTimeout;
}
/**
* I2PTunnelTask Methods
*
* Classes should override to close UDP side as well
*/
public boolean close(boolean forced) {
if (!open) return true;
synchronized (lock) {
l.log("Shutting down server " + toString());
try {
if (_session != null) {
_session.destroySession();
}
} catch (I2PException ex) {
_log.error("Error destroying the session", ex);
}
l.log("Server shut down.");
open = false;
return true;
}
}
/**
* Source Methods
*
* Sets the receiver of the UDP datagrams from I2P
* Subclass must call this after constructor
* and before start()
*/
public void setSink(Sink s) {
_i2pSource.setSink(s);
}
/** start the source */
public void start() {
_i2pSource.start();
}
/**
* Sink Methods
*
* @param to
*
*/
public void send(Destination to, byte[] data) {
_i2pSink.send(to, data);
}
}

View File

@@ -8,6 +8,7 @@ package net.i2p.i2ptunnel.web;
*
*/
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
@@ -28,9 +29,7 @@ public class EditBean extends IndexBean {
if (controllers.size() > tunnel) {
TunnelController cur = (TunnelController)controllers.get(tunnel);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType()))||
("ircclient".equals(cur.getType())));
return isClient(cur.getType());
} else {
return false;
}
@@ -38,7 +37,7 @@ public class EditBean extends IndexBean {
public String getTargetHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
if (tun != null && tun.getTargetHost() != null)
return tun.getTargetHost();
else
return "127.0.0.1";
@@ -52,7 +51,7 @@ public class EditBean extends IndexBean {
}
public String getSpoofedHost(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null)
if (tun != null && tun.getSpoofedHost() != null)
return tun.getSpoofedHost();
else
return "";
@@ -61,8 +60,9 @@ public class EditBean extends IndexBean {
TunnelController tun = getController(tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
else
return "";
if (tunnel < 0)
tunnel = _group.getControllers().size();
return "i2ptunnel" + tunnel + "-privKeys.dat";
}
public boolean startAutomatically(int tunnel) {
@@ -82,119 +82,123 @@ public class EditBean extends IndexBean {
}
public boolean shouldDelay(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String delay = opts.getProperty("i2p.streaming.connectDelay");
if ( (delay == null) || ("0".equals(delay)) )
return false;
else
return true;
} else {
return false;
}
} else {
return false;
}
return getProperty(tunnel, "i2p.streaming.connectDelay", 0) > 0;
}
public boolean isInteractive(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String wsiz = opts.getProperty("i2p.streaming.maxWindowSize");
if ( (wsiz == null) || (!"1".equals(wsiz)) )
return false;
else
return true;
} else {
return false;
}
} else {
return false;
}
return getProperty(tunnel, "i2p.streaming.maxWindowSize", 128) == 12;
}
public int getTunnelDepth(int tunnel, int defaultLength) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.length");
if (len == null) return defaultLength;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultLength;
}
} else {
return defaultLength;
}
} else {
return defaultLength;
}
return getProperty(tunnel, "inbound.length", defaultLength);
}
public int getTunnelQuantity(int tunnel, int defaultQuantity) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.quantity");
if (len == null) return defaultQuantity;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultQuantity;
}
} else {
return defaultQuantity;
}
} else {
return defaultQuantity;
}
return getProperty(tunnel, "inbound.quantity", defaultQuantity);
}
public int getTunnelBackupQuantity(int tunnel, int defaultBackupQuantity) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.backupQuantity");
if (len == null) return defaultBackupQuantity;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultBackupQuantity;
}
} else {
return defaultBackupQuantity;
}
} else {
return defaultBackupQuantity;
}
return getProperty(tunnel, "inbound.backupQuantity", defaultBackupQuantity);
}
public int getTunnelVariance(int tunnel, int defaultVariance) {
return getProperty(tunnel, "inbound.lengthVariance", defaultVariance);
}
public boolean getReduce(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.reduceOnIdle");
}
public int getReduceCount(int tunnel) {
return getProperty(tunnel, "i2cp.reduceQuantity", 1);
}
public int getReduceTime(int tunnel) {
return getProperty(tunnel, "i2cp.reduceIdleTime", 20*60*1000) / (60*1000);
}
public int getCert(int tunnel) {
return 0;
}
public int getEffort(int tunnel) {
return 23;
}
public String getSigner(int tunnel) {
return "";
}
public boolean getEncrypt(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.encryptLeaseSet");
}
public String getEncryptKey(int tunnel) {
return getProperty(tunnel, "i2cp.leaseSetKey", "");
}
public boolean getAccess(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.enableAccessList");
}
public String getAccessList(int tunnel) {
return getProperty(tunnel, "i2cp.accessList", "").replaceAll(",", "\n");
}
public boolean getClose(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.closeOnIdle");
}
public int getCloseTime(int tunnel) {
return getProperty(tunnel, "i2cp.closeIdleTime", 30*60*1000) / (60*1000);
}
public boolean getNewDest(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.newDestOnResume");
}
public boolean getPersistentClientKey(int tunnel) {
return getBooleanProperty(tunnel, "persistentClientKey");
}
public boolean getDelayOpen(int tunnel) {
return getBooleanProperty(tunnel, "i2cp.delayOpen");
}
private int getProperty(int tunnel, String prop, int def) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null) {
String len = opts.getProperty("inbound.lengthVariance");
if (len == null) return defaultVariance;
String s = opts.getProperty(prop);
if (s == null) return def;
try {
return Integer.parseInt(len);
} catch (NumberFormatException nfe) {
return defaultVariance;
}
} else {
return defaultVariance;
return Integer.parseInt(s);
} catch (NumberFormatException nfe) {}
}
} else {
return defaultVariance;
}
return def;
}
private String getProperty(int tunnel, String prop, String def) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null)
return opts.getProperty(prop, def);
}
return def;
}
/** default is false */
private boolean getBooleanProperty(int tunnel, String prop) {
TunnelController tun = getController(tunnel);
if (tun != null) {
Properties opts = getOptions(tun);
if (opts != null)
return Boolean.valueOf(opts.getProperty(prop)).booleanValue();
}
return false;
}
public String getI2CPHost(int tunnel) {
@@ -222,19 +226,9 @@ public class EditBean extends IndexBean {
int i = 0;
for (Iterator iter = opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
if (_noShowSet.contains(key))
continue;
String val = opts.getProperty(key);
if ("inbound.length".equals(key)) continue;
if ("outbound.length".equals(key)) continue;
if ("inbound.lengthVariance".equals(key)) continue;
if ("outbound.lengthVariance".equals(key)) continue;
if ("inbound.backupQuantity".equals(key)) continue;
if ("outbound.backupQuantity".equals(key)) continue;
if ("inbound.quantity".equals(key)) continue;
if ("outbound.quantity".equals(key)) continue;
if ("inbound.nickname".equals(key)) continue;
if ("outbound.nickname".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
if (i != 0) buf.append(' ');
buf.append(key).append('=').append(val);
i++;

View File

@@ -8,13 +8,24 @@ package net.i2p.i2ptunnel.web;
*
*/
import java.util.concurrent.ConcurrentHashMap;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.data.Base32;
import net.i2p.data.Certificate;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.data.SessionKey;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.Log;
/**
@@ -57,6 +68,11 @@ public class IndexBean {
private boolean _sharedClient;
private boolean _privKeyGenerate;
private boolean _removeConfirmed;
private Set<String> _booleanOptions;
private Map<String, String> _otherOptions;
private int _hashCashValue;
private int _certType;
private String _certSigner;
public static final int RUNNING = 1;
public static final int STARTING = 2;
@@ -85,6 +101,8 @@ public class IndexBean {
} catch (NumberFormatException nfe) {}
_nextNonce = _context.random().nextLong();
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
_booleanOptions = new ConcurrentHashSet(4);
_otherOptions = new ConcurrentHashMap(4);
}
public long getNextNonce() { return _nextNonce; }
@@ -146,6 +164,12 @@ public class IndexBean {
else if ("Delete this proxy".equals(_action) || // IE workaround:
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
return deleteTunnel();
else if ("Estimate".equals(_action))
return PrivateKeyFile.estimateHashCashTime(_hashCashValue);
else if ("Modify".equals(_action))
return modifyDestination();
else if ("Generate".equals(_action))
return generateNewEncryptionKey();
else
return "Action " + _action + " unknown";
}
@@ -209,10 +233,7 @@ public class IndexBean {
}
// Only modify other shared tunnels
// if the current tunnel is shared, and of supported type
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
("ircclient".equals(cur.getType()) ||
"httpclient".equals(cur.getType()) ||
"client".equals(cur.getType()))) {
if ("true".equalsIgnoreCase(cur.getSharedClient()) && isClient(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same I2CP options
List controllers = _group.getControllers();
@@ -224,11 +245,7 @@ public class IndexBean {
// Only modify this non-current tunnel
// if it belongs to a shared destination, and is of supported type
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
("httpclient".equals(c.getType()) ||
"ircclient".equals(c.getType()) ||
"client".equals(c.getType()))) {
if ("true".equalsIgnoreCase(c.getSharedClient()) && isClient(c.getType())) {
Properties cOpt = c.getConfig("");
if (_tunnelQuantity != null) {
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
@@ -326,9 +343,16 @@ public class IndexBean {
public boolean isClient(int tunnelNum) {
TunnelController cur = getController(tunnelNum);
if (cur == null) return false;
return ( ("client".equals(cur.getType())) ||
("httpclient".equals(cur.getType())) ||
("ircclient".equals(cur.getType())));
return isClient(cur.getType());
}
public static boolean isClient(String type) {
return ( ("client".equals(type)) ||
("httpclient".equals(type)) ||
("sockstunnel".equals(type)) ||
("connectclient".equals(type)) ||
("streamrclient".equals(type)) ||
("ircclient".equals(type)));
}
public String getTunnelName(int tunnel) {
@@ -361,6 +385,11 @@ public class IndexBean {
else if ("ircclient".equals(internalType)) return "IRC client";
else if ("server".equals(internalType)) return "Standard server";
else if ("httpserver".equals(internalType)) return "HTTP server";
else if ("sockstunnel".equals(internalType)) return "SOCKS 4/4a/5 proxy";
else if ("connectclient".equals(internalType)) return "CONNECT/SSL/HTTPS proxy";
else if ("ircserver".equals(internalType)) return "IRC server";
else if ("streamrclient".equals(internalType)) return "Streamr client";
else if ("streamrserver".equals(internalType)) return "Streamr server";
else return internalType;
}
@@ -407,13 +436,13 @@ public class IndexBean {
public String getClientDestination(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun == null) return "";
if ("client".equals(tun.getType())||"ircclient".equals(tun.getType())) {
if (tun.getTargetDestination() != null)
return tun.getTargetDestination();
else
return "";
}
else return tun.getProxyList();
String rv;
if ("client".equals(tun.getType()) || "ircclient".equals(tun.getType()) ||
"streamrclient".equals(tun.getType()))
rv = tun.getTargetDestination();
else
rv = tun.getProxyList();
return rv != null ? rv : "";
}
public String getServerTarget(int tunnel) {
@@ -430,11 +459,28 @@ public class IndexBean {
String rv = tun.getMyDestination();
if (rv != null)
return rv;
else
return "";
} else {
return "";
// if not running, do this the hard way
String keyFile = tun.getPrivKeyFile();
if (keyFile != null && keyFile.trim().length() > 0) {
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
try {
Destination d = pkf.getDestination();
if (d != null)
return d.toBase64();
} catch (Exception e) {}
}
}
return "";
}
public String getDestHashBase32(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestHashBase32();
if (rv != null)
return rv;
}
return "";
}
///
@@ -556,6 +602,164 @@ public class IndexBean {
_profile = profile;
}
public void setReduce(String moo) {
_booleanOptions.add("i2cp.reduceOnIdle");
}
public void setClose(String moo) {
_booleanOptions.add("i2cp.closeOnIdle");
}
public void setEncrypt(String moo) {
_booleanOptions.add("i2cp.encryptLeaseSet");
}
public void setAccess(String moo) {
_booleanOptions.add("i2cp.enableAccessList");
}
public void setDelayOpen(String moo) {
_booleanOptions.add("i2cp.delayOpen");
}
public void setNewDest(String val) {
if ("1".equals(val))
_booleanOptions.add("i2cp.newDestOnResume");
else if ("2".equals(val))
_booleanOptions.add("persistentClientKey");
}
public void setReduceTime(String val) {
if (val != null) {
try {
_otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
} catch (NumberFormatException nfe) {}
}
}
public void setReduceCount(String val) {
if (val != null)
_otherOptions.put("i2cp.reduceQuantity", val.trim());
}
public void setEncryptKey(String val) {
if (val != null)
_otherOptions.put("i2cp.leaseSetKey", val.trim());
}
public void setAccessList(String val) {
if (val != null)
_otherOptions.put("i2cp.accessList", val.trim().replaceAll("\r\n", ",").replaceAll("\n", ",").replaceAll(" ", ","));
}
public void setCloseTime(String val) {
if (val != null) {
try {
_otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
} catch (NumberFormatException nfe) {}
}
}
/** params needed for hashcash and dest modification */
public void setEffort(String val) {
if (val != null) {
try {
_hashCashValue = Integer.parseInt(val.trim());
} catch (NumberFormatException nfe) {}
}
}
public void setCert(String val) {
if (val != null) {
try {
_certType = Integer.parseInt(val.trim());
} catch (NumberFormatException nfe) {}
}
}
public void setSigner(String val) {
_certSigner = val;
}
/** Modify or create a destination */
private String modifyDestination() {
if (_privKeyFile == null || _privKeyFile.trim().length() <= 0)
return "Private Key File not specified";
TunnelController tun = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (tun == null) {
// creating new
tun = new TunnelController(config, "", true);
_group.addController(tun);
saveChanges();
} else if (tun.getIsRunning() || tun.getIsStarting()) {
return "Tunnel must be stopped before modifying destination";
}
PrivateKeyFile pkf = new PrivateKeyFile(_privKeyFile);
try {
pkf.createIfAbsent();
} catch (Exception e) {
return "Create private key file failed: " + e;
}
switch (_certType) {
case Certificate.CERTIFICATE_TYPE_NULL:
case Certificate.CERTIFICATE_TYPE_HIDDEN:
pkf.setCertType(_certType);
break;
case Certificate.CERTIFICATE_TYPE_HASHCASH:
pkf.setHashCashCert(_hashCashValue);
break;
case Certificate.CERTIFICATE_TYPE_SIGNED:
if (_certSigner == null || _certSigner.trim().length() <= 0)
return "No signing destination specified";
// find the signer's key file...
String signerPKF = null;
for (int i = 0; i < getTunnelCount(); i++) {
TunnelController c = getController(i);
if (_certSigner.equals(c.getConfig("").getProperty("name")) ||
_certSigner.equals(c.getConfig("").getProperty("spoofedHost"))) {
signerPKF = c.getConfig("").getProperty("privKeyFile");
break;
}
}
if (signerPKF == null || signerPKF.length() <= 0)
return "Signing destination " + _certSigner + " not found";
if (_privKeyFile.equals(signerPKF))
return "Self-signed destinations not allowed";
Certificate c = pkf.setSignedCert(new PrivateKeyFile(signerPKF));
if (c == null)
return "Signing failed - does signer destination exist?";
break;
default:
return "Unknown certificate type";
}
Destination newdest;
try {
pkf.write();
newdest = pkf.getDestination();
} catch (Exception e) {
return "Modification failed: " + e;
}
return "Destination modified - " +
"New Base32 is " + Base32.encode(newdest.calculateHash().getData()) + ".b32.i2p " +
"New Destination is " + newdest.toBase64();
}
/** New key */
private String generateNewEncryptionKey() {
TunnelController tun = getController(_tunnel);
Properties config = getConfig();
if (config == null)
return "Invalid params";
if (tun == null) {
// creating new
tun = new TunnelController(config, "", true);
_group.addController(tun);
saveChanges();
} else if (tun.getIsRunning() || tun.getIsStarting()) {
return "Tunnel must be stopped before modifying leaseset encryption key";
}
byte[] data = new byte[SessionKey.KEYSIZE_BYTES];
_context.random().nextBytes(data);
SessionKey sk = new SessionKey(data);
setEncryptKey(sk.toBase64());
setEncrypt("");
saveChanges();
return "New Leaseset Encryption Key: " + sk.toBase64();
}
/**
* Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to)
@@ -566,82 +770,79 @@ public class IndexBean {
Properties config = new Properties();
updateConfigGeneric(config);
if ("httpclient".equals(_type)) {
if (isClient(_type)) {
// generic client stuff
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
}else if ("ircclient".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
for (String p : _booleanClientOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
for (String p : _otherClientOpts)
if (_otherOptions.containsKey(p))
config.setProperty("option." + p, _otherOptions.get(p));
} else {
// generic server stuff
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
for (String p : _booleanServerOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
for (String p : _otherServerOpts)
if (_otherOptions.containsKey(p))
config.setProperty("option." + p, _otherOptions.get(p));
}
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("client".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableByOther != null)
config.setProperty("interface", _reachableByOther);
else
config.setProperty("interface", _reachableBy);
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
} else if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
if (_name != null && !_sharedClient) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
config.setProperty("sharedClient", _sharedClient + "");
} else if ("server".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
} else if ("httpserver".equals(_type)) {
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
if (_spoofedHost != null)
config.setProperty("spoofedHost", _spoofedHost);
} else {
return null;
}
return config;
}
private static final String _noShowOpts[] = {
"inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
"inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize"
};
private static final String _booleanClientOpts[] = {
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
};
private static final String _booleanServerOpts[] = {
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", "i2cp.enableAccessList"
};
private static final String _otherClientOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime"
};
private static final String _otherServerOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList"
};
protected static final Set _noShowSet = new HashSet();
static {
_noShowSet.addAll(Arrays.asList(_noShowOpts));
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
}
private void updateConfigGeneric(Properties config) {
config.setProperty("type", _type);
if (_name != null)
@@ -655,6 +856,8 @@ public class IndexBean {
} else {
config.setProperty("i2cpPort", "7654");
}
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
@@ -664,19 +867,9 @@ public class IndexBean {
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
if (_noShowSet.contains(key))
continue;
String val = pair.substring(eq+1);
if ("inbound.length".equals(key)) continue;
if ("outbound.length".equals(key)) continue;
if ("inbound.quantity".equals(key)) continue;
if ("outbound.quantity".equals(key)) continue;
if ("inbound.lengthVariance".equals(key)) continue;
if ("outbound.lengthVariance".equals(key)) continue;
if ("inbound.backupQuantity".equals(key)) continue;
if ("outbound.backupQuantity".equals(key)) continue;
if ("inbound.nickname".equals(key)) continue;
if ("outbound.nickname".equals(key)) continue;
if ("i2p.streaming.connectDelay".equals(key)) continue;
if ("i2p.streaming.maxWindowSize".equals(key)) continue;
config.setProperty("option." + key, val);
}
}
@@ -704,14 +897,14 @@ public class IndexBean {
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
if (_name != null) {
if ( ((!"client".equals(_type)) && (!"httpclient".equals(_type))&& (!"ircclient".equals(_type))) || (!_sharedClient) ) {
if ( (!isClient(_type)) || (!_sharedClient) ) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
} else {
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
}
}
}
if ("interactive".equals(_profile))
// This was 1 which doesn't make much sense
// The real way to make it interactive is to make the streaming lib

View File

@@ -14,12 +14,10 @@ String tun = request.getParameter("tunnel");
} else {
String type = request.getParameter("type");
int curTunnel = -1;
if ("client".equals(type) || "httpclient".equals(type) || "ircclient".equals(type)) {
if (EditBean.isClient(type)) {
%><jsp:include page="editClient.jsp" /><%
} else if ("server".equals(type) || "httpserver".equals(type)) {
%><jsp:include page="editServer.jsp" /><%
} else {
%>Invalid tunnel type<%
%><jsp:include page="editServer.jsp" /><%
}
}
%>
%>

View File

@@ -75,7 +75,11 @@
</div>
<div id="accessField" class="rowItem">
<% if ("streamrclient".equals(tunnelType)) { %>
<label>Target:</label>
<% } else { %>
<label>Access Point:</label>
<% } %>
</div>
<div id="portField" class="rowItem">
<label for="port" accesskey="P">
@@ -87,14 +91,17 @@
</label>
<input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
</div>
<% String otherInterface = "";
String clientInterface = editBean.getClientInterface(curTunnel);
if ("streamrclient".equals(tunnelType)) {
otherInterface = clientInterface;
} else { %>
<div id="reachField" class="rowItem">
<label for="reachableBy" accesskey="r">
<span class="accessKey">R</span>eachable by:
</label>
<select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
<% String clientInterface = editBean.getClientInterface(curTunnel);
String otherInterface = "";
if (!("127.0.0.1".equals(clientInterface)) &&
<% if (!("127.0.0.1".equals(clientInterface)) &&
!("0.0.0.0".equals(clientInterface)) &&
(clientInterface != null) &&
(clientInterface.trim().length() > 0)) {
@@ -105,9 +112,18 @@
<option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>>LAN Hosts (Please specify your LAN address)</option>
</select>
</div>
<% } // streamrclient %>
<div id="otherField" class="rowItem">
<label for="reachableByOther" accesskey="O">
<% if ("streamrclient".equals(tunnelType)) { %>
Host:
<% String vvv = otherInterface;
if (vvv == null || "".equals(vvv.trim()))
out.write(" <font color=\"red\">(required)</font>");
%>
<% } else { %>
<span class="accessKey">O</span>ther:
<% } %>
</label>
<input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />
</div>
@@ -116,14 +132,14 @@
<hr />
</div>
<% if ("httpclient".equals(editBean.getInternalType(curTunnel))) {
<% if ("httpclient".equals(tunnelType) || "connectclient".equals(tunnelType)) {
%><div id="destinationField" class="rowItem">
<label for="proxyList" accesskey="x">
Outpro<span class="accessKey">x</span>ies:
</label>
<input type="text" size="30" id="proxyList" name="proxyList" title="List of Outproxy I2P destinations" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
</div>
<% } else {
<% } else if ("client".equals(tunnelType) || "ircclient".equals(tunnelType) || "streamrclient".equals(tunnelType)) {
%><div id="destinationField" class="rowItem">
<label for="targetDestination" accesskey="T">
<span class="accessKey">T</span>unnel Destination:
@@ -135,8 +151,9 @@
<input type="text" size="30" id="targetDestination" name="targetDestination" title="Destination of the Tunnel" value="<%=editBean.getClientDestination(curTunnel)%>" class="freetext" />
<span class="comment">(name or destination)</span>
</div>
<% }
%><div id="profileField" class="rowItem">
<% } %>
<% if (!"streamrclient".equals(tunnelType)) { %>
<div id="profileField" class="rowItem">
<label for="profile" accesskey="f">
Pro<span class="accessKey">f</span>ile:
</label>
@@ -160,6 +177,7 @@
<input value="true" type="checkbox" id="shared" name="shared" title="Share tunnels with other clients"<%=(editBean.isSharedClient(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment">(Share tunnels with other clients and irc/httpclients? Change requires restart of client proxy)</span>
</div>
<% } // !streamrclient %>
<div id="startupField" class="rowItem">
<label for="startOnLoad" accesskey="a">
<span class="accessKey">A</span>uto Start:
@@ -205,12 +223,12 @@
<span class="accessKey">V</span>ariance:
</label>
<select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox">
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1);
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, 0);
%><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option>
<option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option>
<option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (not recommended)</option>
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
<% }
@@ -265,11 +283,118 @@
</label>
<input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="reduce" accesskey="d">
Re<span class="accessKey">d</span>uce tunnel quantity when idle:
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="d">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="reduce" title="Reduce Tunnels"<%=(editBean.getReduce(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label for="reduceCount" accesskey="d">
Reduced tunnel count:
</label>
<input type="text" id="port" name="reduceCount" size="1" maxlength="1" title="Reduced Tunnel Count" value="<%=editBean.getReduceCount(curTunnel)%>" class="freetext" />
</div>
<div id="portField" class="rowItem">
<label for="reduceTime" accesskey="d">
Idle minutes:
</label>
<input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="reduce" accesskey="c">
<span class="accessKey">C</span>lose tunnels when idle: <i>Experimental</i>
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="c">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="close" title="Close Tunnels"<%=(editBean.getClose(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="c">
New Keys on Reopen:
</label>
<table border="0"><tr><!-- I give up -->
<td><input value="1" type="radio" id="startOnLoad" name="newDest" title="New Destination"
<%=(editBean.getNewDest(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
<td valign="center">Enable
<td><input value="0" type="radio" id="startOnLoad" name="newDest" title="New Destination"
<%=(editBean.getNewDest(curTunnel) || editBean.getPersistentClientKey(curTunnel) ? "" : " checked=\"checked\"")%> class="tickbox" />
<td valign="center">Disable
</table>
</div>
<div id="portField" class="rowItem">
<label for="reduceTime" accesskey="c">
Idle minutes:
</label>
<input type="text" id="port" name="closeTime" size="4" maxlength="4" title="Close Tunnel Idle Time" value="<%=editBean.getCloseTime(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="reduce" accesskey="c">
<span class="accessKey">D</span>elay tunnel open until required: <i>Experimental</i>
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="c">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="delayOpen" title="Delay Tunnel Open"<%=(editBean.getDelayOpen(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div class="subdivider">
<hr />
</div>
<% if ("client".equals(tunnelType) || "ircclient".equals(tunnelType)) { %>
<div id="optionsField" class="rowItem">
<label for="privKeyFile" accesskey="k">
Persistent private <span class="accessKey">k</span>ey:
</label>
</div>
<div id="portField" class="rowItem">
<label>Enable:</label>
<input value="2" type="radio" id="startOnLoad" name="newDest" title="New Destination"
<%=(editBean.getPersistentClientKey(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="reachField" class="rowItem">
<label>File:</label>
<input type="text" size="30" id="clientHost" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
</div>
<div id="destinationField" class="rowItem">
<label for="localDestination" accesskey="L">
<span class="accessKey">L</span>ocal destination:
</label>
<textarea rows="1" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off"><%=editBean.getDestinationBase64(curTunnel)%></textarea>
<span class="comment">(if known)</span>
</div>
<div class="subdivider">
<hr />
</div>
<% } %>
<div id="customOptionsField" class="rowItem">
<label for="customOptions" accesskey="u">
C<span class="accessKey">u</span>stom options:
@@ -284,8 +409,11 @@
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<span class="comment">NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted</span>
<input type="hidden" value="true" name="removeConfirm" />
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button>
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
<button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel">Cancel</button>
</div>
</div>
</div>

View File

@@ -82,11 +82,19 @@
</div>
<div id="targetField" class="rowItem">
<% if ("streamrserver".equals(tunnelType)) { %>
<label>Access Point:</label>
<% } else { %>
<label>Target:</label>
<% } %>
</div>
<div id="hostField" class="rowItem">
<label for="targetHost" accesskey="H">
<% if ("streamrserver".equals(tunnelType)) { %>
<span class="accessKey">R</span>eachable by:
<% } else { %>
<span class="accessKey">H</span>ost:
<% } %>
</label>
<input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" />
</div>
@@ -110,7 +118,8 @@
<label for="spoofedHost" accesskey="W">
<span class="accessKey">W</span>ebsite name:
</label>
<input type="text" size="20" id="spoofedHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" />
<input type="text" size="20" id="targetHost" name="spoofedHost" title="Website Host Name" value="<%=editBean.getSpoofedHost(curTunnel)%>" class="freetext" />
<span class="comment">(leave blank for outproxies)</span>
</div>
<% }
%><div id="privKeyField" class="rowItem">
@@ -123,6 +132,7 @@
</label>
<input type="text" size="30" id="privKeyFile" name="privKeyFile" title="Path to Private Key File" value="<%=editBean.getPrivateKeyFile(curTunnel)%>" class="freetext" />
</div>
<% if (!"streamrserver".equals(tunnelType)) { %>
<div id="profileField" class="rowItem">
<label for="profile" accesskey="f">
Pro<span class="accessKey">f</span>ile:
@@ -133,12 +143,15 @@
<option <%=(interactiveProfile == false ? "selected=\"selected\" " : "")%>value="bulk">bulk connection (downloads/websites/BT) </option>
</select>
</div>
<% } // !streamrserver %>
<div id="destinationField" class="rowItem">
<label for="localDestination" accesskey="L">
<span class="accessKey">L</span>ocal destination:
</label>
<textarea rows="1" cols="60" readonly="readonly" id="localDestination" title="Read Only: Local Destination (if known)" wrap="off"><%=editBean.getDestinationBase64(curTunnel)%></textarea>
<span class="comment">(if known)</span>
<% if (!"".equals(editBean.getDestinationBase64(curTunnel))) { %>
<a href="/susidns/addressbook.jsp?book=private&hostname=<%=editBean.getTunnelName(curTunnel)%>&destination=<%=editBean.getDestinationBase64(curTunnel)%>#add">Add to local addressbook</a>
<% } %>
</div>
<div class="footer">
@@ -177,12 +190,12 @@
<span class="accessKey">V</span>ariance:
</label>
<select id="tunnelVariance" name="tunnelVariance" title="Level of Randomization for Tunnel Depth" class="selectbox">
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, -1);
<% int tunnelVariance = editBean.getTunnelVariance(curTunnel, 0);
%><option value="0"<%=(tunnelVariance == 0 ? " selected=\"selected\"" : "") %>>0 hop variance (no randomisation, consistant performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (high randomisation, variable performance)</option>
<option value="1"<%=(tunnelVariance == 1 ? " selected=\"selected\"" : "") %>>+ 0-1 hop variance (medium additive randomisation, subtractive performance)</option>
<option value="2"<%=(tunnelVariance == 2 ? " selected=\"selected\"" : "") %>>+ 0-2 hop variance (high additive randomisation, subtractive performance)</option>
<option value="-1"<%=(tunnelVariance == -1 ? " selected=\"selected\"" : "") %>>+/- 0-1 hop variance (standard randomisation, standard performance)</option>
<option value="-2"<%=(tunnelVariance == -2 ? " selected=\"selected\"" : "") %>>+/- 0-2 hop variance (not recommended)</option>
<% if (tunnelVariance > 2 || tunnelVariance < -2) {
%> <option value="<%=tunnelVariance%>" selected="selected"><%= (tunnelVariance > 2 ? "+ " : "+/- ") %>0-<%=tunnelVariance%> hop variance</option>
<% }
@@ -242,6 +255,136 @@
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="encrypt" accesskey="e">
<span class="accessKey">E</span>ncrypt Leaseset:
</label>
</div>
<div id="portField" class="rowItem">
<label for="encrypt" accesskey="e">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="encrypt" title="Encrypt LeaseSet"<%=(editBean.getEncrypt(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label for="encrypt" accesskey="e">
Leaseset Encryption Key:
</label>
<textarea rows="1" cols="44" id="portField" name="encryptKey" title="Encrypt Key" wrap="off"><%=editBean.getEncryptKey(curTunnel)%></textarea>
</div>
<div id="portField" class="rowItem">
<label for="force" accesskey="c">
Generate New Key:
</label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Generate" title="Generate New Key Now">Generate</button>
<span class="comment">(Tunnel must be stopped first)</span>
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="access" accesskey="s">
Restricted Acce<span class="accessKey">s</span>s List: <i>Unimplemented</i>
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="s">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="access" title="Enable Access List"<%=(editBean.getAccess(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="hostField" class="rowItem">
<label for="accessList" accesskey="s">
Access List:
</label>
<textarea rows="2" cols="60" id="hostField" name="accessList" title="Access List" wrap="off"><%=editBean.getAccessList(curTunnel)%></textarea>
<span class="comment">(Restrict to these clients only)</span>
</div>
<div class="subdivider">
<hr />
</div>
<div id="optionsField" class="rowItem">
<label for="reduce" accesskey="d">
Re<span class="accessKey">d</span>uce tunnel quantity when idle:
</label>
</div>
<div id="portField" class="rowItem">
<label for="access" accesskey="d">
Enable:
</label>
<input value="1" type="checkbox" id="startOnLoad" name="reduce" title="Reduce Tunnels"<%=(editBean.getReduce(curTunnel) ? " checked=\"checked\"" : "")%> class="tickbox" />
</div>
<div id="portField" class="rowItem">
<label for="reduceCount" accesskey="d">
Reduced tunnel count:
</label>
<input type="text" id="port" name="reduceCount" size="1" maxlength="1" title="Reduced Tunnel Count" value="<%=editBean.getReduceCount(curTunnel)%>" class="freetext" />
</div>
<div id="portField" class="rowItem">
<label for="reduceTime" accesskey="d">
Idle minutes:
</label>
<input type="text" id="port" name="reduceTime" size="4" maxlength="4" title="Reduced Tunnel Idle Time" value="<%=editBean.getReduceTime(curTunnel)%>" class="freetext" />
</div>
<div class="subdivider">
<hr />
</div>
<div id="tunnelOptionsField" class="rowItem">
<label for="cert" accesskey="c">
New <span class="accessKey">C</span>ertificate type:
</label>
</div>
<div id="hostField" class="rowItem">
<div id="portField" class="rowItem">
<label>None</label>
<input value="0" type="radio" id="startOnLoad" name="cert" title="No Certificate"<%=(editBean.getCert(curTunnel)==0 ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment"></span>
</div>
<div id="portField" class="rowItem">
<label>Hashcash (effort)</label>
<input value="1" type="radio" id="startOnLoad" name="cert" title="Hashcash Certificate"<%=(editBean.getCert(curTunnel)==1 ? " checked=\"checked\"" : "")%> class="tickbox" />
<input type="text" id="port" name="effort" size="2" maxlength="2" title="Hashcash Effort" value="<%=editBean.getEffort(curTunnel)%>" class="freetext" />
</div>
</div>
<div id="portField" class="rowItem">
<label for="force" accesskey="c">
Hashcash Calc Time:
</label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Estimate" title="Estimate Calculation Time">Estimate</button>
</div>
<div id="hostField" class="rowItem">
<div id="portField" class="rowItem">
<label>Hidden</label>
<input value="2" type="radio" id="startOnLoad" name="cert" title="Hidden Certificate"<%=(editBean.getCert(curTunnel)==2 ? " checked=\"checked\"" : "")%> class="tickbox" />
<span class="comment"></span>
</div>
<div id="portField" class="rowItem">
<label for="signer" accesskey="c">
Signed (signed by):
</label>
<input value="3" type="radio" id="startOnLoad" name="cert" title="Signed Certificate"<%=(editBean.getCert(curTunnel)==3 ? " checked=\"checked\"" : "")%> class="tickbox" />
<input type="text" id="port" name="signer" size="50" title="Cert Signer" value="<%=editBean.getSigner(curTunnel)%>" class="freetext" />
<span class="comment"></span>
</div>
</div>
<div id="portField" class="rowItem">
<label for="force" accesskey="c">
Modify Certificate:
</label>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Modify" title="Force New Cert Now">Modify</button>
<span class="comment">(Tunnel must be stopped first)</span>
</div>
<div class="subdivider">
<hr />
</div>
<div id="customOptionsField" class="rowItem">
<label for="customOptions" accesskey="u">
C<span class="accessKey">u</span>stom options:
@@ -256,8 +399,11 @@
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<span class="comment">NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted</span>
<input type="hidden" value="true" name="removeConfirm" />
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button><button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><span class="accessKey">S</span>ave</button>
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><span class="accessKey">D</span>elete</button>
<button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel">Cancel</button>
</div>
</div>
</div>

View File

@@ -112,10 +112,18 @@
}
%></div>
<% if (!"sockstunnel".equals(indexBean.getInternalType(curClient))) { %>
<div class="destinationField rowItem">
<label>Destination:</label>
<label>
<% if ("httpclient".equals(indexBean.getInternalType(curClient)) || "connectclient".equals(indexBean.getInternalType(curClient))) { %>
Outproxy:
<% } else { %>
Destination:
<% } %>
</label>
<input class="freetext" size="40" readonly="readonly" value="<%=indexBean.getClientDestination(curClient)%>" />
</div>
<% } %>
<div class="descriptionField rowItem">
<label>Description:</label>
@@ -140,6 +148,9 @@
<option value="client">Standard</option>
<option value="httpclient">HTTP</option>
<option value="ircclient">IRC</option>
<option value="sockstunnel">SOCKS 4/4a/5</option>
<option value="connectclient">CONNECT</option>
<option value="streamrclient">Streamr</option>
</select>
<input class="control" type="submit" value="Create" />
</div>
@@ -159,10 +170,10 @@
<div class="nameHeaderField rowItem">
<label>Name:</label>
</div>
<div class="targetHeaderField rowItem">
<div class="previewHeaderField rowItem">
<label>Points at:</label>
</div>
<div class="previewHeaderField rowItem">
<div class="targetHeaderField rowItem">
<label>Preview:</label>
</div>
<div class="statusHeaderField rowItem">
@@ -178,15 +189,28 @@
<label>Name:</label>
<span class="text"><a href="edit.jsp?tunnel=<%=curServer%>" title="Edit Server Tunnel Settings for <%=indexBean.getTunnelName(curServer)%>"><%=indexBean.getTunnelName(curServer)%></a></span>
</div>
<div class="targetField rowItem">
<label>Points at:</label>
<span class="text"><%=indexBean.getServerTarget(curServer)%></span>
</div>
<div class="previewField rowItem">
<label>Points at:</label>
<span class="text">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
%>
<a href="http://<%=indexBean.getServerTarget(curServer)%>/" title="Test HTTP server, bypassing I2P"><%=indexBean.getServerTarget(curServer)%></a>
<%
} else {
%><%=indexBean.getServerTarget(curServer)%>
<%
}
%></span>
</div>
<div class="targetField rowItem">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer)) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><label>Preview:</label>
<a class="control" title="Preview this Tunnel" href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>" target="_new">Preview</a>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p">Preview</a>
<%
} else if (indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><span class="text">Base32 Address:<br><%=indexBean.getDestHashBase32(curServer)%>.b32.i2p</span>
<%
} else {
%><span class="comment">No Preview</span>
@@ -237,6 +261,8 @@
<select name="type">
<option value="server">Standard</option>
<option value="httpserver">HTTP</option>
<option value="ircserver">IRC</option>
<option value="streamrserver">Streamr</option>
</select>
<input class="control" type="submit" value="Create" />
</div>

Binary file not shown.

View File

@@ -1 +0,0 @@
This is JDOM 1.0 from http://jdom.org/, released under an Apache style license

View File

@@ -78,7 +78,6 @@
<include name="jasper-runtime.jar" />
<include name="javax.servlet.jar" />
<include name="org.mortbay.jetty.jar" />
<include name="xercesImpl.jar" />
</fileset>
</copy>
<delete dir="jetty-5.1.12" />

View File

@@ -1,236 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Ports + Ant = Pants, a simple Ant-based package manager
free (adj.): unencumbered; not under the control of others
Written by smeghead in 2005 and released into the public domain with no
warranty of any kind, either expressed or implied. It probably won't make
your computer catch on fire, or eat your children, but it might. Use at your
own risk.
-->
<project basedir="." default="help" name="pants-interface">
<!-- .......................... Global Properties .......................... -->
<!-- ........................... Internal Tasks ............................ -->
<target name="-fetchCvs" unless="cvs.source.available" if="using.cvs">
<cvs compressionlevel="${cvs.compression.level}"
date="${cvs.date}"
dest="./distfiles/cvs-src/${pbuild}"
failonerror="true"
package="${cvs.package}"
passfile="${cvs.passfile}"
port="${cvs.port}"
cvsRoot="${cvs.root}"
cvsRsh="${cvs.rsh}"
tag="${cvs.tag}"
/>
</target>
<target name="-fetchPackage" unless="using.cvs">
<get src="${package.url}"
verbose="true"
dest="./distfiles"
/>
</target>
<target name="-init">
<!--
TODO: Create dist/ and working/ folders for each pbuild subdir in case
they've been wiped.
-->
<loadproperties srcfile="world" />
<taskdef name="mergetypedproperties"
classname="net.i2p.pants.MergeTypedPropertiesTask"
classpath="./lib/pants.jar"
/>
<mergetypedproperties input="./pbuilds/${pbuild}/pbuild.properties"
output="./pbuilds/${pbuild}/merged-properties.temp"
booleanList="version.latest.find.regex.canonicaleq, version.latest.find.regex.caseinsensitive, version.latest.find.regex.comments, version.latest.find.regex.dotall, version.latest.find.regex.multiline, version.latest.find.regex.unicodecase, version.latest.find.regex.unixlines"
stringList="cvs.compression.level, cvs.date, cvs.package, cvs.passfile, cvs.port, cvs.root, cvs.rsh, cvs.tag, package.url, version.latest, version.latest.find.url, version.latest.find.regex"
/>
<loadproperties srcfile="./pbuilds/${pbuild}/merged-properties.temp" />
<delete file="./pbuilds/${pbuild}/merged-properties.temp" />
<!--
If '-Dpbuild={name}' isn't specified, the 'build', 'fetch', 'update'
and 'version' commands should default to 'world' behavior.
-->
<antcall target="-setWorld" />
<condition property="using.cvs">
<or>
<equals arg1="CVS" arg2="${version.using.${pbuild}}" />
<equals arg1="cvs" arg2="${version.using.${pbuild}}" />
</or>
</condition>
<!--
If 'version.recommended' isn't defined in pbuild.properties, default
to latest available version.
-->
</target>
<target name="-setWorld" unless="pbuild">
<property name="pbuild" value="world" />
</target>
<target name="-unpackTarBz2">
<untar src="${pbuild.package}"
compression="bzip2"
dest="./${pbuild}/working"
/>
</target>
<target name="-unpackTarGzip">
<untar src="${pbuild.package}"
compression="gzip"
dest="./${pbuild}/working"
/>
</target>
<target name="-unpackZip">
<unzip src="${pbuild.package}" dest="./${pbuild}/working" />
</target>
<target name="-updateCvs" if="using.cvs">
<cvs command="update -d"
compressionlevel="${compression.level}"
date="${cvs.date}"
dest="./distfiles/cvs-src"
failonerror="true"
package="${cvs.package}"
passfile="${cvs.passfile}"
port="${cvs.port}"
cvsRoot="${cvs.root}"
cvsRsh="${cvs.rsh}"
tag="${cvs.tag}"
/>
</target>
<target name="-updateConfirm" if="confirm.update" unless="no.prompts">
<input validargs="y,Y,n,N"
defaultvalue="n"
addproperty="confirm.update.answer">
You currently have the recommended version installed. A newer
version will be installed if you continue and this may break some
applications which depend on this package. Are you sure you want
to update? [y/N]
</input>
<condition property="abort.update">
<or>
<equals arg1="n" arg2="${confirm.update.answer}" />
<equals arg1="N" arg2="${confirm.update.answer}" />
</or>
</condition>
<fail if="abort.update">Update aborted.</fail>
</target>
<target name="-versionLatest">
<get src="${version.latest.find.url}"
dest="version.latest.in.temp"
verbose="true"
/>
<taskdef name="match"
classname="net.i2p.pants.MatchTask"
classpath="./lib/pants.jar"
/>
<match input="version.latest.in.temp"
output="version.latest.parsed.temp"
regex="${version.latest.find.regex}"
canonicaleq="${version.latest.find.regex.canonicaleq}"
caseinsensitive="${version.latest.find.regex.caseinsensitive}"
comments="${version.latest.find.regex.comments}"
dotall="${version.latest.find.regex.dotall}"
multiline="${version.latest.find.regex.multiline}"
unicodecase="${version.latest.find.regex.unicodecase}"
unixlines="${version.latest.find.regex.unixlines}"
/>
<loadproperties srcFile="version.latest.parsed.temp" />
<delete file="version.latest.in.temp" />
<delete file="version.latest.parsed.temp" />
<property name="version.latest" value="${group.1}" />
</target>
<target name="-versionRecommended">
<property name="version.recommended" value="x" />
</target>
<target name="-versionUsing">
<property name="version.using" value="x" />
</target>
<!-- .......................... Public Interface ........................... -->
<target name="build" depends="-init,fetch"
description="Build a pbuild and its dependencies">
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="clean" />
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="build" />
<!--
Perform a 'clean' on the target first (but not 'distclean')
-->
</target>
<target name="fetch" depends="-init"
description="Get package only">
<antcall target="-fetchPackage" />
<antcall target="-fetchCvs" />
<copydir dest="./pbuilds/${pbuild}/working"
src="./distfiles/cvs-src/${pbuild}"
/>
</target>
<target name="help"
description="Display usage synopsis">
<echo>
Pants usage:
ant [--usejikes] [-Dpbuild={name}] [-Dpbuild.version={version}]
[-D{property}={value}] [-Dno.prompts=true] build | fetch |
help | install | uninstall | update | version
build Build a pbuild and its dependencies
fetch Get package only
help Display usage synopsis
install Fetch, build and install a pbuild
uninstall Uninstall a pbuild
update Update pbuild(s) to the latest version(s)
version Display pbuild version information
</echo>
</target>
<!--
Install recommended version by default unless 'version' property is set.
Do not install if package is already installed.
-->
<target name="install" depends="-init, build"
description="Install a pbuild">
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="dist" />
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}"
target="distclean"
/>
</target>
<target name="uninstall" depends="-init"
description="Uninstall a pbuild" />
<target name="update" depends="-init"
description="Update pbuild(s) to the latest version(s)">
<condition property="${confirm.update}">
<equals arg1="${version.using}" arg2="${version.recommended}" />
</condition>
<antcall target="-updateConfirm" />
</target>
<target name="version"
depends="-init, -versionRecommended, -versionUsing, -versionLatest"
description="Display pbuild version information">
<echo message="Latest version: ${version.recommended}" />
<echo message="Latest version: ${version.using}" />
<echo message="Latest version: ${version.latest}" />
</target>
</project>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="build-pants">
<target name="build"
description="Build the source">
<mkdir dir="./java/build"/>
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
</target>
<target name="clean"
description="Remove all object files">
<delete dir="./java/build" />
<delete dir="./java/jartemp" />
</target>
<target name="dist" depends="build, jar"
description="Create the jar and copy it to ../lib">
<copy todir="../lib" file="./java/build/pants.jar" />
</target>
<target name="distclean" depends="clean"
description="Remove the jar and all object files" >
<delete file="../lib/pants.jar" />
</target>
<target name="jar">
<delete dir="./java/jartemp" />
<mkdir dir="./java/jartemp" />
<copy todir="./java/jartemp">
<fileset dir="./java/build" includes="**/*.class" />
</copy>
<jar basedir="./java/jartemp" jarfile="./java/build/pants.jar">
<manifest>
<section name="net.i2p.pants">
<attribute name="Implementation-Title" value="Pants" />
<attribute name="Implementation-Version" value="0.0.1" />
<attribute name="Implementation-Vendor" value="I2P" />
<attribute name="Implementation-Vendor-Id" value="I2P" />
<attribute name="Implementation-URL" value="http://www.i2p.net" />
</section>
</manifest>
</jar>
<delete dir="./java/jartemp" />
</target>
</project>

View File

@@ -1,212 +0,0 @@
/*
* Ports + Ant = Pants, a simple Ant-based package manager
*
* free (adj.): unencumbered; not under the control of others
*
* Written by smeghead in 2005 and released into the public domain with no
* warranty of any kind, either expressed or implied. It probably won't make
* your computer catch on fire, or eat your children, but it might. Use at your
* own risk.
*/
package net.i2p.pants;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* <p>Custom Ant task for matching the contents of a file against a given
* regular expression and writing any matching groups to a file in
* <code>java.util.Properties</code> format.
* </p>
* <p>Each key in the properties file is named after the number corresponding to
* its matching group and its value is the contents of the matching group.
* </p>
* <p>Regular expressions passed to this task must conform to the specification
* used by Sun's <code>java.util.regex</code> package and thus are mostly
* compatible with Perl 5 regular expressions.
* </p>
* <p>When calling the <code>match</code> task, the attributes
* <code>input</code>, <code>output</code>, and <code>regex</code> are required.
* </p>
* <p>Optional boolean attributes may be used to toggle various modes for the
* regular expression engine (all are set to <code>false</code> by default):
* </p>
* <table>
* <tr><td><code>canonicaleq</code></td><td>Enable canonical equivalence</td></tr>
* <tr><td><code>caseinsensitive</code></td><td>Enable case-insensitive matching</td></tr>
* <tr><td><code>comments</code></td><td>Permit whitespace and comments in pattern</td></tr>
* <tr><td><code>dotall</code></td><td>Enable dotall mode</td></tr>
* <tr><td><code>multiline</code></td><td>Enable multi-line mode</td></tr>
* <tr><td><code>unicodecase</code></td><td>Enable Unicode-aware case folding</td></tr>
* <tr><td><code>unixlines</code></td><td>Enable Unix lines mode</td></tr>
* </table>
* <p>There is one additional optional boolean attribute,
* <code>failOnNoMatch</code>. If this attribute is <code>true</code> it causes
* the <code>match</code> task to throw a
* <code>org.apache.tools.ant.BuildException</code> and fail if no matches for
* the regular expression are found. The default value is <code>false</code>,
* meaning a failed match will simply result in a warning message to
* <code>STDERR</code> and an empty (0 byte) <code>output</code> file being
* created.
* </p>
* <p>
* <h4>Example</h4>
* </p>
* <p>Contents of input file <code>letter.txt</code>:
* <pre>
* Dear Alice,
*
* How's about you and me gettin' together for some anonymous foo action?
*
* Kisses,
* Bob
* </pre>
* </p>
* <p>Ant <code>match</code> task and a <code>taskdef</code> defining it:
* <pre>
* &lt;taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" /&gt;
* &lt;match input="letter.txt"
* output="matches.txt"
* regex="about (\S*?) and (\S*?) .+anonymous (\S*?)"
* /&gt;
* </pre>
* </p>
* <p>Contents of properties file <code>matches.txt</code> written by this task:
* <pre>
* group.0=about you and me gettin' together for some anonymous foo
* group.1=you
* group.2=me
* group.3=foo
* </pre>
* </p>
* <p>These values can be loaded from <code>matches.txt</code> into Ant
* properties like so:
* <pre>
* &lt;loadproperties srcFile="matches.txt" /&gt;
* </pre>
* </p>
*
* @author smeghead
*/
public class MatchTask extends Task {
private boolean _failOnNoMatch;
private String _inputFile;
private String _outputFile;
private String _regex;
private int _regexFlags;
public void execute() throws BuildException {
int charRead = 0;
FileReader fileReader = null;
FileWriter fileWriter = null;
Matcher matcher = null;
Pattern pattern = null;
PrintWriter printWriter = null;
StringBuffer text = new StringBuffer();
if (_inputFile == null)
throw new BuildException("Error: 'match' task requires 'input' attribute");
if (_outputFile == null)
throw new BuildException("Error: 'match' task requires 'output' attribute");
if (_regex == null)
throw new BuildException("Error: 'match' task requires 'regex' attribute");
pattern = Pattern.compile(_regex, _regexFlags);
try {
fileReader = new FileReader(_inputFile);
while ((charRead = fileReader.read()) != -1)
text.append((char) charRead);
fileReader.close();
matcher = pattern.matcher(text);
if (matcher.find()) {
printWriter = new PrintWriter(new FileWriter(_outputFile));
for (int i = 0; i <= matcher.groupCount(); i++)
printWriter.println("group." + Integer.toString(i) + "=" + matcher.group(i));
printWriter.flush();
printWriter.close();
} else {
if (_failOnNoMatch) {
throw new BuildException("Error: No matches found in " + _inputFile);
} else {
System.err.println("Warning: No matches found in " + _inputFile);
// Create 0 byte output file.
fileWriter = new FileWriter(_outputFile);
fileWriter.close();
}
}
} catch (FileNotFoundException fnfe) {
throw new BuildException("File " + _inputFile + " not found", fnfe);
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
public void setCanonicalEq(boolean enableCanonicalEq) {
if (enableCanonicalEq)
_regexFlags |= Pattern.CANON_EQ;
}
public void setCaseInsensitive(boolean enableCaseInsensitive) {
if (enableCaseInsensitive)
_regexFlags |= Pattern.CASE_INSENSITIVE;
}
public void setComments(boolean enableComments) {
if (enableComments)
_regexFlags |= Pattern.COMMENTS;
}
public void setDotall(boolean enableDotall) {
if (enableDotall)
_regexFlags |= Pattern.DOTALL;
}
public void setFailOnNoMatch(boolean failOnNoMatch) {
_failOnNoMatch = failOnNoMatch;
}
public void setInput(String inputFile) {
_inputFile = inputFile;
}
public void setMultiLine(boolean enableMultiLine) {
if (enableMultiLine)
_regexFlags |= Pattern.MULTILINE;
}
public void setOutput(String outputFile) {
_outputFile = outputFile;
}
public void setRegex(String regex) {
_regex = regex;
}
public void setUnicodeCase(boolean enableUnicodeCase) {
if (enableUnicodeCase)
_regexFlags |= Pattern.UNICODE_CASE;
}
public void setUnixLines(boolean enableUnixLines) {
if (enableUnixLines)
_regexFlags |= Pattern.UNIX_LINES;
}
}

View File

@@ -1,164 +0,0 @@
/*
* Ports + Ant = Pants, a simple Ant-based package manager
*
* free (adj.): unencumbered; not under the control of others
*
* Written by smeghead in 2005 and released into the public domain with no
* warranty of any kind, either expressed or implied. It probably won't make
* your computer catch on fire, or eat your children, but it might. Use at your
* own risk.
*/
package net.i2p.pants;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* <p>Custom Ant task for loading properties from a
* <code>java.util.Properties</code> file then merging them with lists of
* expected properties. When an expected property is found in the properties
* file it is set to the value given for it in the file. If an expected property
* from a list isn't found in the properties file its value will be set to "" or
* "false", depending on the property's data type.
* </p>
* <p>A property's data type is determined by membership in one of two lists
* which can be passed into an instance of this class: a string-typed list and a
* boolean-typed list. Values for string-typed properties may be any valid
* string accepted by <code>java.util.Properties</code>, and values for
* boolean-typed properties must be either "false" or "true".
* </p>
* <p>Lists holding more than one property must be comma-delimited.
* </p>
* <p>The output of this class is a temporary <code>java.util.Properties</code>
* file which is suitable for reading by the standard Ant
* <code>loadproperties</code> task.
* </p>
* <p>Note that if any properties in the given lists have already been defined
* before the <code>mergetypedproperties</code> task is called, their values
* cannot be changed since Ant properties are immutable.
* </p>
* <h4>Example</h4>
* </p>
* <p>Contents of a properties file <code>my.properties</code>:
* <pre>
* some.property.exists=true
* hasValue=false
* some.property=this is a value
* property0=bork bork
* propertyX=this property wasn't passed in a list
* </pre>
* </p>
* <p>Ant <code>mergetypedproperties</code> task and a <code>taskdef</code>
* defining it:
* <pre>
* &lt;taskdef name="mergetypedproperties" classname="net.i2p.pants.MergeTypedPropertiesTask" classpath="../../lib/pants.jar" /&gt;
* &lt;mergetypedproperties input="my.properties"
* output="merged-properties.temp"
* booleanList="some.property.exists,is.valid,hasValue"
* stringList="some.property,another.property,property0"
* /&gt;
* </pre>
* </p>
* <p>Contents of properties file <code>merged-properties.temp</code> written by this task:
* <pre>
* some.property.exists=true
* is.valid=false
* hasValue=false
* some.property=this is a value
* another.property=
* property0=bork bork
* propertyX=this property wasn't passed in a list
* </pre>
* </p>
* <p>If you don't want this task's output to include properties which weren't
* in the lists of expected properties, you can set the attribute
* <code>onlyExpected</code> to <code>true</code>. In the example, this would
* result in the file <code>merged-properties.temp</code> containing only the
* following properties:
* <pre>
* some.property.exists=true
* is.valid=false
* hasValue=false
* some.property=this is a value
* another.property=
* property0=bork bork
* </pre>
* </p>
*
* @author smeghead
*/
public class MergeTypedPropertiesTask extends Task {
private String _booleanList = "";
private String _inputFile;
private boolean _onlyExpected;
private String _outputFile;
private Properties _propertiesIn = new Properties();
private Properties _propertiesOut = new Properties();
private String _stringList = "";
public void execute() throws BuildException {
StringTokenizer strtokBoolean = new StringTokenizer(_booleanList, ",");
StringTokenizer strtokString = new StringTokenizer(_stringList, ",");
String property = "";
if (_inputFile == null)
throw new BuildException("Error: 'mergetypedproperties' task requires 'input' attribute");
if (_outputFile == null)
throw new BuildException("Error: 'mergetypedproperties' task requires 'output' attribute");
// Add some type-checking on the list elements
try {
_propertiesIn.load(new FileInputStream(_inputFile));
while (strtokBoolean.hasMoreTokens())
_propertiesOut.setProperty(strtokBoolean.nextToken().trim(), "false");
while (strtokString.hasMoreTokens())
_propertiesOut.setProperty(strtokString.nextToken().trim(), "");
for (Enumeration enumm = _propertiesIn.elements(); enumm.hasMoreElements(); ) {
property = (String) enumm.nextElement();
if (_onlyExpected && !_propertiesOut.containsKey(property))
continue;
else
_propertiesOut.setProperty(property, _propertiesIn.getProperty(property));
}
_propertiesOut.store(new FileOutputStream(_inputFile), "This is a temporary file. It is safe to delete it.");
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
public void setBooleanList(String booleanList) {
_booleanList = booleanList;
}
public void setInput(String inputFile) {
_inputFile = inputFile;
}
public void setOnlyExpected(boolean onlyExpected) {
_onlyExpected = onlyExpected;
}
public void setOutput(String outputFile) {
_outputFile = outputFile;
}
public void setStringList(String stringList) {
_stringList = stringList;
}
}

View File

@@ -1,116 +0,0 @@
What is Pants?
--------------
Pants is an Apache Ant-based package manager for the management of 3rd party
dependencies in Java development projects. It's loosely modeled after
FreeBSD's Ports and Gentoo Linux's Portage, with two major differences:
* Pants isn't intended for system-wide package management. It's tailored for
per-project 3rd party package management. You will typically have one
Pants repository per project and each repository will be located somewhere
under your project's root directory. If you're familiar with Ports or
Portage, a Pants repository is roughly analogous to /usr/ports or
/usr/portage.
* Pants is extremely portable. It goes anywhere Apache Ant goes.
Pants takes a modular approach to the standard Ant buildfile, breaking it
into 3 files for functionality and convenience:
1. The Pants public interface, pants/build.xml, provides a single consistent
way to access and manipulate dependency packages and relieves some of the
developer's burden by providing implementations for some frequently-used
and complex Ant operations.
2. pbuild.xml is a specially-structured and slimmed-down Ant buildfile in
which you implement custom handling for a package your project depends
on. This is known as the "pbuild" and is roughly analogous to a FreeBSD
port or a Gentoo ebuild. A fairly explanatory template for pbuilds,
pbuild.template.xml, is provided.
3. pbuild.properties contains those properties for a specific pbuild which
are most likely to change over time. It uses the java.util.Properties
format which is more human-friendly for hand-editing than Ant/XML. A
fairly explanatory template, pbuild.template.properties, is provided.
There is one more file that completes the Pants system: the metadata file
pants/world is a database for keeping track of all packages managed by Pants
for your project.
Pants automatically handles versioning for your project's dependency
packages and keeps track of their recommended versions, currently used
versions, and latest available versions. This makes it extremely simple for
project developers to switch back and forth between different versions of a
dependency, and makes it just as easy to update a dependency. You can even
update all your project's Pants-managed packages with a single command.
Pbuilds are designed to automatically handle the downloading, building,
repackaging and deployment of source archives, binary archives, and CVS
sources, all in a manner that's completely transparent to the project
developer. Pbuilds currently support tar + gzip, tar + bzip2, and zip
archives.
Because it is based on Ant, Pants integrates very well with Ant buildfiles
and will fit easily into your project's Ant build framework. However, its
interface is simple enough to be called just as easily by more traditional
build systems such as GNU Make.
Why Should I Use Pants?
-----------------------
There are many applications for Pants, but a few use cases should best serve
to illustrate its usefulness:
1. You have a project that you ship with several 3rd party libraries but the
versions you're using are stale. With a single command, Pants can
automatically discover the latest release versions for all of these, then
download, build, and place the fresh libraries where your project's main
build system expects them to be at build time.
2. You want to test multiple versions of a 3rd party library against your
project. Pants only requires you to issue a single command to switch
library versions, so can spend more time testing and less time hunting
packages down, unpackaging them, symlinking, etc.
3. Pants is public domain. You can ship it with your project if you need to
without having to worry about petty intellectual property or licensing
issues.
Minimum Requirements
--------------------
* Apache Ant 1.6.2 or higher is recommended
* Any Java runtime and operating system that will run Ant
Installation
------------
Not finished yet.
Why the Silly Name?
-------------------
Ports + Ant = Pants. Any other explanation is purely a product of your
twisted imagination.
Miscellaneous Pocket Fluff
--------------------------
Author: smeghead <smeghead@i2pmail.org> <smeghead@mail.i2p>
License: No license necessary. This work is released into the public domain.
Price: Free! But if you really appreciate Pants, or you're just a sicko,
please send me a picture of your worst or most unusual pair of
pants so I can add it to the Whirling Hall of Pants on pants.i2p,
the official Pants eepsite (that's an anonymous website on I2P--see
http://www.i2p.net for more information).
$Id$

View File

@@ -1,110 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is a template for Pants pbuilds. Pbuilds use standard Apache Ant syntax.
For each target in the Public Interface section you must provide either an
implementation or a stub. You may also add your own custom tasks and
properties to this file. Be careful that none of your custom properties'
names clash with the properties defined in pants/build.xml.
-->
<project basedir="." default="build" name="name-of-pbuild-here">
<!-- ....................... Begin Public Interface ........................ -->
<!--
When this target is called, the pbuild's sources and/or binaries have
already been extracted/copied by Pants into the pbuild's working/
subdirectory. This target must prepare those sources and/or binaries in
the working/ subdirectory into deployable form, for example by building
all necessary classes and jar files.
This target must not create or modify any files outside the pbuild's
working/ subdirectory. (An automatic sandboxing mechanism should be added
to Pants at some point.) It is however acceptable for a task called by
'builddep' to modify files outside of this pbuild's working/ directory.
-->
<target name="build" depends="builddep" />
<!--
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
etc. which perform tasks this pbuild's 'build' target depends on. If other
pbuilds are called here, they must be called through the Pants interface
or else it may leave Pants in an inconsistent state.
Most pbuilds probably won't need to implement this target.
-->
<target name="builddep" />
<!--
This target must undo the actions performed by the 'build' target.
-->
<target name="clean" depends="depclean" />
<!--
If the 'builddep' target is implemented, this target must be implemented
to undo its actions.
-->
<target name="depclean" />
<!--
This target must copy all deployable files generated by the 'build' target
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
processes) or to their final deployment locations outside the pants/
directory hierarchy. Note that the latter may require the user to gain
superuser/admin privileges.
-->
<target name="dist" depends="build" />
<!--
This target must remove all files from the pbuild's dist/ subdirectory
and final deployment locations, reversing the actions of the 'dist'
target. Note that removal of files from their final deployment locations
may require the user to gain superuser/admin privileges.
-->
<target name="distclean" depends="clean" />
<!-- ........................ End Public Interface ......................... -->
</project>

View File

@@ -1,112 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
version.recommended=CVS
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork
cvs.root=:ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto
cvs.rsh=ssh
cvs.package=gnu-crypto

View File

@@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="fortuna-pbuild">
<property name="gnucrypt.base.dir" value="./working/gnu-crypto" />
<property name="gnucrypt.etc.dir" value="${gnucrypt.base.dir}/etc" />
<property name="gnucrypt.lib.dir" value="${gnucrypt.base.dir}/lib" />
<property name="gnucrypt.object.dir" value="${gnucrypt.base.dir}/classes" />
<property name="gnucrypt.base.crypto.object.dir" value="${gnucrypt.object.dir}/gnu/crypto" />
<property name="gnucrypt.cipher.object.dir" value="${gnucrypt.base.crypto.object.dir}/cipher" />
<property name="gnucrypt.hash.object.dir" value="${gnucrypt.base.crypto.object.dir}/hash" />
<property name="gnucrypt.prng.object.dir" value="${gnucrypt.base.crypto.object.dir}/prng" />
<patternset id="fortuna.files">
<include name="${gnucrypt.base.crypto.object.dir}/Registry.class" />
<include name="${gnucrypt.prng.object.dir}/Fortuna*.class" />
<include name="${gnucrypt.prng.object.dir}/BasePRNG.class" />
<include name="${gnucrypt.prng.object.dir}/RandomEventListener.class" />
<include name="${gnucrypt.prng.object.dir}/IRandom.class" />
<include name="${gnucrypt.cipher.object.dir}/CipherFactory.class" />
<include name="${gnucrypt.cipher.object.dir}/IBlockCipher.class" />
<include name="${gnucrypt.hash.object.dir}/HashFactory.class" />
<include name="${gnucrypt.hash.object.dir}/IMessageDigest.class" />
</patternset>
<!--
Add this when Fortuna tests are added to GNU Crypto, else write some
-->
<target name="-test" />
<!-- ....................... Begin Public Interface ........................ -->
<!--
When this target is called, the pbuild's sources and/or binaries have
already been extracted/copied by Pants into the pbuild's working/
subdirectory. This target must prepare those sources and/or binaries in
the working/ subdirectory into deployable form, for example by building
all necessary classes and jar files.
This target must not create or modify any files outside the pbuild's
working/ subdirectory. (An automatic sandboxing mechanism should be added
to Pants at some point.) It is however acceptable for a task called by
'builddep' to modify files outside of this pbuild's working/ directory.
-->
<target name="build" depends="builddep">
<delete dir="./working/build" />
<delete dir="./working/jartemp" />
<mkdir dir="./working/build" />
<mkdir dir="./working/jartemp/${gnucrypt.object.dir}" />
<copy todir="./working/jartemp">
<fileset dir=".">
<patternset refid="fortuna.files" />
</fileset>
</copy>
<jar basedir="./working/jartemp/${gnucrypt.object.dir}" jarfile="./working/build/fortuna.jar">
<manifest>
<section name="fortuna">
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
<attribute name="Implementation-Version" value="CVS HEAD" />
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
<attribute name="Implementation-Vendor-Id" value="FSF" />
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
</section>
</manifest>
</jar>
<delete dir="./working/jartemp" />
</target>
<!--
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
etc. which perform tasks this pbuild's 'build' target depends on. If other
pbuilds are called here, they must be called through the Pants interface
or else it may leave Pants in an inconsistent state.
Most pbuilds probably won't need to implement this target.
-->
<target name="builddep">
<ant dir="${gnucrypt.base.dir}" target="jar" />
</target>
<!--
This target must undo the actions performed by the 'build' target.
-->
<target name="clean" depends="depclean">
<delete dir="./working/jartemp" />
</target>
<!--
If the 'builddep' target is implemented, this target must be implemented
to undo its actions.
-->
<target name="depclean">
<!--
Annoyingly the GNU Crypto distclean task called here doesn't clean
*all* derived files from java/gnu-crypto/lib like it should (because
a couple of lines are commented out).....
-->
<ant dir="${gnucrypt.base.dir}" target="distclean" />
<!--
.....and so we mop up the rest ourselves.
-->
<delete dir="${gnucrypt.lib.dir}" />
</target>
<!--
This target must copy all deployable files generated by the 'build' target
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
processes) or to their final deployment locations outside the pants/
directory hierarchy. Note that the latter may require the user to gain
superuser/admin privileges.
-->
<target name="dist" depends="build">
<copy todir="./dist/fortuna.jar" file="./working/build/fortuna.jar" />
</target>
<!--
This target must remove all files from the pbuild's dist/ subdirectory
and final deployment locations, reversing the actions of the 'dist'
target. Note that removal of files from their final deployment locations
may require the user to gain superuser/admin privileges.
-->
<target name="distclean" depends="clean">
<delete file="./dist/fortuna.jar" />
</target>
<!-- ........................ End Public Interface ......................... -->
</project>

View File

@@ -1,112 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
version.recommended=5.1.2
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
version.latest=5.1.2
version.latest.find.url=http://sourceforge.net/projects/jetty/
version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
package.url=http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${pbuild.version}.zip
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork

View File

@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="jetty">
<!-- make this generic, place variables in properties file -->
<target name="all" depends="build"
description="Run the build target" />
<target name="assignProperties" if="group.0">
<property name="latest.jetty.version" value="${group.1}" />
<available property="jetty.package.available" file="jetty-${latest.jetty.version}.zip" />
<available property="jetty.package.unpacked.available" file="jettypkg/jetty-${latest.jetty.version}" />
<echo message="Properties assigned" />
</target>
<target name="build" depends="init, unpackJettyPackage" if="latest.jetty.version"
description="Download latest Jetty package and copy needed libs to jettylib/">
<property name="unpack.dir" value="jettypkg/jetty-${latest.jetty.version}" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/ant.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-compiler.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-runtime.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xercesImpl.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xml-apis.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/extra/lib/org.mortbay.jetty-jdk1.2.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/javax.servlet.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/org.mortbay.jetty.jar" />
<copy todir="jettylib" overwrite="true">
<fileset dir="${unpack.dir}/ext" includes="xmlParserAPIs*.jar" />
</copy>
</target>
<target name="builddep"
description="Build the custom helper Ant task for this buildfile">
<mkdir dir="java/build"/>
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
</target>
<target name="clean"
description="Remove temp files and zip only; jettypkg/ requires manual deletion">
<echo message="Not actually deleting the Jetty package directory since it's so large" />
<delete>
<fileset dir="." includes="*.zip jettytemp.html parsed.temp" />
</delete>
</target>
<target name="cleandep"
description="Remove custom helper Ant task">
<delete dir="java/build" />
</target>
<target name="compile" />
<target name="distclean" depends="clean"
description="Remove temp files, zip and jettylib/ contents" >
<delete>
<fileset dir="jettylib" includes="*.jar"/>
</delete>
</target>
<target name="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.available">
<echo message="The Jetty libs are not necessary for using I2P, but are used by some" />
<echo message="applications on top of I2P such as the routerconsole." />
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${latest.jetty.version}.zip" verbose="true" dest="jetty-${latest.jetty.version}.zip" />
</target>
<target name="init" depends="builddep">
<echo message="Checking SourceForge for latest Jetty version....." />
<get src="http://sourceforge.net/projects/jetty/" dest="jettytemp.html" verbose="true" />
<taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" />
<match input="jettytemp.html"
output="parsed.temp"
regex="Stable.+?Jetty-(.+?)&lt;/A&gt;"
/>
<loadproperties srcFile="parsed.temp" />
<antcall target="assignProperties" />
</target>
<target name="jar" />
<target name="showlatest" depends="init"
description="Display latest version number for Jetty">
<echo message="Latest Jetty version: ${latest.jetty.version}" />
</target>
<target name="unpackJettyPackage" depends="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.unpacked.available">
<mkdir dir="jettypkg" />
<unzip src="jetty-${latest.jetty.version}.zip" dest="jettypkg" />
</target>
</project>

View File

@@ -1,2 +0,0 @@
version.using.fortuna=CVS
version.using.jetty=5.1.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,26 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q System Diagrams</title>
</head>
<body>
<h1>Q Diagrams</h1>
Informal system diagrams of Q network, hubs and clients.
<center>
<hr>
<img src="overall.jpg">
<hr>
<img src="client.jpg">
<hr>
<img src="hub.jpg">
</center>
<hr>
<address><a href="mailto:aum@mail.i2p">aum</a></address>
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 14:06:02 NZST 2005
<!-- hhmts end -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,80 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Quartermaster - I2P Distributed File Store</title>
</head>
<body>
<center>
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
<h3>STATUS<h3>
<i>Whole new (incompatible) version currently in development;
ETA for release approx 4-7 days;
view screenshots <a href="screenshots.html">here</a>
</i>
<br>
<hr>
<small>
<a href="manual/index.html">User Manual</a> |
<a href="spec/index.html">Protocol Spec</a> |
<a href="metadata.html">Metadata Spec</a> |
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
<a href="api/index.html">API Spec</a> |
<a href="qnoderefs.txt">qnoderefs.txt</a> |
Full Download |
Updated jar
</small>
</center>
<hr>
<h2>Intro</h2>
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
<h2>Features</h2>
<ul>
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
static websites which are retrievable even if author is offline</li>
<li>Easy web interface - interact with Q (and view/insert QSites)
from your web browser</li>
<li>Maximum expectations of content retrievability</li>
<li>Content security akin to Freenet CHK and SSK keys</li>
<li>Powerful, flexible search engine</li>
<li>Comfortably accommodates both permanent and transient
nodes without significant network disruption (for instance,
no flooding of the I2P network with futile
calls to offline nodes)</li>
<li>Rapid query resolution, due to distributed catalogue
mirroring which eliminates all in-network query traffic</li>
<li>Modular, extensible architecture</li>
<li>Simple interfaces for 3rd-party app developers</li>
<li>Is custom-designed and built around I2P, so no duplication of
I2P's encryption/anonymity features</li>
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
implement user-level clients in any language; also allows alternative
implementations of core server and/or client nodes.</li>
</ul>
<hr>
<h2>Status</h2>
Q is presently under development, and a test release is expected soon.
<hr>
<h2>Architecture</h2>
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
<hr>
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 18:55:19 NZST 2005
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,805 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q User/Programmer Manual</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bold }
-->
</style>
</head>
<body style="font-family: arial, helvetica, sans-serif">
<center>
<h1>Q User/Programmer Manual</h1>
<i>A brief but hopefully easy guide to installing and using the Q distributed file
store within the I2P network</i>
<br><br>
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
<br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<a name="intro"/>
<hr>
<h2>1. Introduction</h2>
<blockquote>
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
performance by respecting the properties of the I2P network.<br>
<br>
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
download, to everyday usage. It also provides information for the benefit of higher-level
client application authors.
</blockquote>
<a name="checklist"/>
<hr>
<h2>2. Preliminary Checklist</h2>
<blockquote>
OK, we assume here that you've already cracked the tarball, and are looking at
the distribution files.<br>
<br>
In order to get Q set up and running, you'll need:
<ol>
<li>An I2P router installed, set up and (permanently or transiently) running</li>
<li>Your system shell set up with at the environment variables:
<ul>
<li><b>CLASSPATH</b> - this should include:
<ul>
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
<li>Apache's XML-RPC support jarfile - included in this Q distro as
<b>xmlrpc.jar</b></li>
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
</ul>
</li>
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
in which your main java VM execution program (<b>java</b>, or on windows systems,
<b>java.exe</b>) resides.<br>
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
not run</i>.</li>
</ul>
</ol>
</blockquote>
<a name="serverorclient"/>
<hr>
<h2>3. Q Server or Q Client?</h2>
<blockquote>
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
<Br>
It is only client nodes which provide users with full access to the Q network.<br>
<br>
However, if you have a (near-) permanently running I2P Router, and you're a kind and
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
to your <b>Q Client Node</b>.<br>
<br>
If you do choose to run a server node, you'll be expected to keep it running as near as
possible to 24/7. While transience of client nodes - frequent entering and leaving the
Q network - causes little or no disruption, transience of server nodes can significantly
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
more than the smallest percentage of the server node pool.<br>
<br>
Until you're feeling well "settled in" with Q, your best approach is to just run a
client node for now, and add a server node later when you feel ready.<br>
</blockquote>
<a name="walkthrough"/>
<hr>
<h2>4. Q Walkthrough</h2>
<h3>4.1. Introduction</h3>
<blockquote>
This chapter discusses the deployment and usage of a Q Client Node, and will take you
through the steps of:
<ol>
<li>Double-checking that you've met the installation requirements</li>
<li>Launching a Q Client Node</li>
<li>Verifying that your Q Client Node is running</li>
<li>If your node fails to launch, figuring out why</li>
<li>Importing one or more noderefs into your node</li>
<li>Observing that your node is discovering other nodes on the network</li>
<li>Observing that your node is discovering content on the network</li>
<li>Searching for items of content that match chosen criteria</li>
<li>Retrieving stuff from the network</li>
<li>Inserting stuff to the network</li>
<li>Shutting down your client node</li>
</ol>
Setup and running of Q Server Nodes will be discussed in a later chapter.
</blockquote>
<hr>
<h3>4.2. Verify Your Q Installation Is Correct</h3>
<blockquote>
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
variable.<br>
<br>
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
the other I2P jar files. Wherever you choose to put these files, make sure they're
correctly listed in your <b>CLASSPATH</b>.
<br>
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
environment variable.<br>
</blockquote>
<hr>
<h3>4.3. Get Familiar With qmgr</h3>
<blockquote>
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
sore fingers from needless typing. It's just a wrapper which passes arguments
to the java command <b><code>java&nbsp;net.i2p.aum.q.QMgr</code></b><br>
<br>
You can verify you've set up qmgr correctly with the command:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
This displays a brief summary of qmgr commands. On the other hand, the command:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
floods your terminal window with a detailed explanation of all the qmgr commands
and their arguments.<br>
</blockquote>
<hr>
<h3>4.4. Running A Q Client Node For The First Time</h3>
<blockquote>
Provided you've successfully completed the preliminaries, you can launch your
Q Client Node with the command:
<blockquote><code><pre>
qmgr start</pre></code></blockquote>
All going well, you should have a Q Client Node now running in background.
</blockquote>
<hr>
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
<blockquote>
After typed the <b>qmgr start</b> command, you will see little or no
evidence that Q is actually running.<br>
<br>
You can test if the node is actually up by typing the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
If your Q Client Node is running, this <b>status</b> command should produce
something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
Node Ping:
status=ok
numPeers=0
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
uptime=4952
load=0.0
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
numLocalItems=47
numRemoteItems=2173</pre></code></blockquote>
If you see something like this, then smile, because Q is now up on your system.<br>
<br>
If the node launch failed, you might see something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
java.io.IOException: Connection refused
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
Failed to ping node</pre></code></blockquote>
This indicates that your Q client node has either crashed, or failed to launch in the
first place.<br>
<br>
If you're having trouble like this, you might like to try running your Q client node
in foreground, instead of spawning it off into background.<br>
<br>
The command to run a Q client node in foreground is:
<blockquote><code><pre>
qmgr foreground</pre></code></blockquote>
You should see some meaningless startup messages, and no return to your shell prompt.<br>
</blockquote>
<hr>
<h3>4.6. Diversion - Q Storage Directories</h3>
<blockquote>
By default, when you run a Q Client Node, it creates a datastore directory tree
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
wherever your user home directory is - this depends on what version of Windows
you have installed).<br>
<br>
Within this directory tree, you should see a file called <b>node.log</b>, which
will contain various debug log messages, and can help you to rectify any problems
with your Q installation. If you hit a wall and can't rectify the problems
yourself, you should send this file to the Q author (aum).<br>
<br>
It's possible to run your Q node from another directory, by passing that directory
as a <b>-dir &lt;path&gt;</b> argument to the
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
commands. See <b>qmgr help verbose</b> for more information.
</blockquote>
<hr>
<h3>4.7. Importing a Noderef</h3>
<blockquote>
Note from the prior <b>qmgr status</b> command the line:
<blockquote><code><pre>
numPeers=0</pre></code></blockquote>
This means that your Q client node is running standalone, and doesn't have any contact
with any Q network. As such, your node is effectively useless. We need to hook up
your node with other nodes in the Q network.<br>
<br>
Q doesn't ship with any means for new client nodes to automatically connect to any Q
server nodes. This is deliberate.<br>
<br>
In all likelihood, there will be one 'main' Q network running within I2P, largely
based around the author's own Q server node, and most people will likely want to
use this Q network. But the author doesn't want to stop other people running their
own private Q networks, for whatever purpose has meaning for them.
<blockquote><i><small>
<hr>
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
no way for a user to know of the existence of any item of content without
first being given its 'key'. However, since Q works with published catalogs,
any user can know everything that's available on a Q network, which might
not be desirable to those wishing to share content in a private situation.<br>
<Br>
The Q author anticipates, and warmly supports, people running their own
private Q networks within I2P, in addition to accessing the mainstream
'official' Q network.<br>
<br>
The way Q is designed and implemented, there is no way for anyone, including
Q's author, to know of the existence of anyone else's private Q network.
It is beyond the author's control, (and thus arguably the author's
legal responsibility), what private Q networks people set up, and what
kind of content is trafficked on these networks. This claim of plausible
deniability on the part of Q's author parallels that of a hardware retailer
denying responsibility for what people do with tools that they purchase.
<hr>
</small>
</i></blockquote>
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
and desperately needs some Q server nodes to talk to. So let's hook up your node to
the mainstream Q network.<br>
<br>
You'll need to get one or more 'noderefs' for Q server nodes.<br>
<br>
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
which your Q Client Node can connect with a Q Server Node.<br>
<br>
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
<br>
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
wanting to subscribe into a private Q network, then get a noderef for at least one
of that network's server nodes from someone on that network who trusts you).<br>
<br>
Import these noderefs into your Q client node via the command:
<blockquote><code><pre>
qmgr addref qnoderefs.txt</pre></code></blockquote>
If all goes well, you should see no output from this command, or (possibly) a brief
line or two suggesting success.<br>
<br>
Your client node is now subscribed into the Q network of your choice. Verify this
with the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
In the output from that command, you should see the <b>numPeers=</b> line showing at least
1 peer.<br>
<br>
If there is more than one Q Server Node on the Q network you've just subscribed to,
then your local node should sooner or later discover all these server nodes, and
the <b>numPeers</b> value should increase over time.<br>
<br>
<blockquote>
<hr>
While Q is in its early development and testing stages, the author may abdicate
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
This will especially happen if the author makes any substantial changes to the
inter-node protocol, and/or releases incompatible new versions of Q client/server
nodes. Remember that
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
serve as the authoritative source for noderefs for the mainstream Q network within
the mainstream I2P network.
<hr>
</blockquote>
When your client node gets its noderefs to a Q network, it will periodically,
from then on, retrieve differential peer list and catalog updates from servers
it knows about.<br>
<br>
Even if you only feed your client just one ref for a single server node, it will
in time discover all other operating server nodes on that Q network, and will
build up a full local catalog of everything that's available on that Q network.<br>
<br>
Provided that your client is running ok, and has been fed with at least one
ref for a live Q network that contains content, then over time, successive:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
commands should report increasing values in the fields:
<ul>
<li><b>numPeers</b> - number of peers this client node knows about</li>
<li><b>numLocalItems</b> - number of locally stored content items, ie items
which you have either inserted to, or retrieved from, your client node</li>
<li><b>numRemoteItems</b> - number of unique data items which are available
on remote server nodes in the Q network, and which can be retrieved through
your local client node.</li>
</ul>
<blockquote>
<hr>
<h4>4.7.1. One Big Warning</h4>
If you are participating in more than one distinct Q network, then <b>do not</b>
insert noderefs for different networks into the same running instance of a
local Q client, unless you don't plan on inserting content via that client.<br>
<Br>
For instance, let's say you are participating in two different Q networks:
<ul>
<li>The 'mainstream' Q netowrk</li>
<li>A secret Q network - "My friends' teen angst diaries"</li>
</ul>
If you get a noderef for both these networks, and insert both of these into the
same running Q client node, then this local client node will be transparently
connected to both networks.<br>
<br>
If you only ever plan on retrieving content, and never inserting content, this
won't be a problem, except that you won't be able to tell which content
resides on the mainstream Q network, and which resides in the secret Q network.<br>
<Br>
The big problem arises from inserting content. Whenever you insert data through this
'contaminated'
Q client node, this node picks 3 different servers to which upload a copy of this
data. You won't have any control over whether the data gets inserted to the mainstream
Q network, or your secret Q network. You might insert something sensitive, intending it
to go only into the secret Q network, where in fact it also ends up in the mainstream
network, with consequences you might not want.
</blockquote>
</blockquote>
<hr>
<h3>4.8. Content Data and Metadata</h3>
<blockquote>
Whenever content gets stored on Q, it is actually stored as two separate items:
<ul>
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
audio files etc</li>
<li>The <b>metadata</b>, which contains human-readable and machine-readable
descriptions of the data</li>
</ul>
Metadata consists of a set of <b>category=value</b> pairs.<br>
<br>
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
example of metadata for an MP3 audio recording:
<ul>
<li>title=Fight_Last_Thursday.mp3</li>
<li>type=audio</li>
<li>mimetype=audio/mpeg</li>
<li>abstract=upcoming single recorded in our garage last April</li>
<li>keywords=grunge,country,indie</li>
<li>artist=Ring of Fire</li>
<li>size=4379443</li>
<li>contact=ring-of-fire@mail.i2p</li>
<li>key=blah37blah24-yada23hfhyada</li>
</ul>
All metadata categories are optional. In fact, you can insert content with no metadata
at all.<br>
<br>
If you fail to provide metadata when inserting an item, a blank set of metadata will
be created with at least the following categories:
<ul>
<li><b>key</b> - the derived key, under which the item will later be retrievable
by yourself and others</li>
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
<li><b>size</b> - size of the item's raw data, in bytes</li>
</ul>
Within Q, there is a convention to supply a minimal amount of metadata. While this
is not expected or enforced, including all these categories is most strongly
recommended. These core categories are:
<ul>
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
which are legal in filenames on all platforms, and which ends with a file extension.</li>
<li><b>type</b> - one of a superset of eMule classifiers, such as:
<ul>
<li><b>text</b> - plain text</li>
<li><b>html</b> - HTML content</li>
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
it's unlikely people will be inserting video content anytime soon (unless it's
very short)</li>
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
<li><b>misc</b> - content does not fit into any of the above categories</li>
</ul>
</li>
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
this category in your metadata is still strongly encouraged. Value for this category
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
<li><b>keywords</b> - a comma-separated list of keywords, intended for
machine-readability, should be all lowercase, no spaces</li>
</ul>
Note that you can supply extra metadata categories in addition to the above, and that
people searching for content can search on these extra categories if they know about
them.
</blockquote>
<hr>
<h3>4.9. Searching For Content</h3>
<blockquote>
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
catalog of all available content on whatever Q network they are connected to.<br>
<br>
This is a design decision, based on the choice to eliminate query traffic.<br>
<br>
The author hopes that this will result in a distributed storage network with a
high retrievability guarantee, in contrast with freenet which offers no such
guarantee.<br>
<br>
With Freenet, you only ever know of the existence of something if someone tells
you about it.<br>
<br>
But with Q, your local client node builds up a global catalog of everything that's
available within the whole network.<br>
<br>
The QMgr client has a command for searching your Q client node:
<blockquote><code><pre>
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
For example:
<blockquote><code><pre>
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
or:
<blockquote><code><pre>
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
As implied in the latter example, search patterns are regular expressions. This example will
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag&nbsp;Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
<br>
Please use the search function carefully, otherwise (if and when Q usage grows) you
could be inundated with thousands or even millions of entries.<br>
<br>
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
it will the items out one at a time, with the key first, then each metadata entry
on an indented line following.
</blockquote>
<hr>
<h3>4.10. Retrieving Content</h3>
<blockquote>
Now, we're actually going to retrieve something.<br>
<br>
Presumably, after following the previous section, you will have seen one or more search
results come up, with the 'keys' under which the items can be accessed.<br>
<br>
Now, choose one of the keys, preferably for a short text item. Try either of the following
commands:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; something.txt</pre></code></blockquote>
<i>or</i>:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; &gt; something.txt</pre></code></blockquote>
(both have the same effect - the first one explicitly writes to the named file, the second
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
<br>
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
the first form till we fix the bug.</i></b>
</blockquote>
<hr>
<h3>4.11. Inserting Content</h3>
<blockquote>
Our last example in this walkthrough relates to inserting content.<br>
<br>
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
myqinsert.txt.<br>
<br>
Now, think of some metadata to insert along with the file. Or, you can just use
the set:
<blockquote><code><pre>
type=text
keywords=test
abstract=My simple test of inserting into Q
title=myqinsert.txt</pre></code></blockquote>
Now, let's insert the file. Ensure your Q client node is running, then type:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q"</pre></code></blockquote>
If all went well, this command should produce half a line of gibberish, followed
immediately by your shell prompt, eg:
<blockquote><code><pre>
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
</pre></code></blockquote>
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
which was derived from the content you just inserted.<br>
<br>
To avoid the hassle of copying/pasting the key, you could just add output redirection
to the above command, eg:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q" \
> myqinsert.key</pre></code></blockquote>
This will cause the generated key to be written safe and sound into the file
<b>myqinsert.key</b>.<br>
<br>
You can verify that this insert worked by a 'get' command, as in:
<blockquote><code><pre>
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
you're using Windows, you <b>will</b> have to cut/paste the key.
</blockquote>
<hr>
<h3>4.12. Shutting Down your Node</h3>
<blockquote>
If you've worked through to here, then congratulations! You've got your Q Client Node set up
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
<br>
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
client node running as much of the time as possible, so that you get prompt catalog updates,
and can more quickly stay in touch with new content.<br>
<br>
However, if you need to shut down your node, the command for doing this is:
<blockquote><code><pre>
qmgr stop</pre></code></blockquote>
This command will take a while to complete (since the node has to wait for the I2P
java shutdown hooks to complete before it can rest in peace). But once your node is
shut down, you can start it up again at any time and pick up where you left off.
</blockquote>
<a name="server"/>
<hr>
<h2>5. Running a Q Server Node</h2>
<h3>5.1. Introduction</h3>
<blockquote>
This section describes the requirements for, and procedures involved with, running
a Q Server Node.<br>
<br>
We'll use a similar 'walkthrough' style to that which we used in the previous section
on client nodes.
</blockquote>
<hr>
<h3>5.2. Requirements and Choices</h3>
<blockquote>
Running a Q server is a generous thing to do, and helps substantially with making
Q work at its best for everyone. However, please do make sure you can meet some
basic requirements:
<ul>
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
98% uptime.</li>
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
CPU-usage footprint of running a fulltime Q server node</li>
<li>You have already been able to successfully run a Q client node.</li>
</ul>
Also, please decide whether you want your server node to contribute to the mainstream
Q network, or whether you want to create your own private Q network, or join someone
else's private network. Your contribution will be most appreciated, though, if you
can run a server within the mainstream Q network.
</blockquote>
<hr>
<h3>5.3. Starting Your Server Node</h3>
<blockquote>
Starting up a Q Server node is very similar to starting up a Q client node, except
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
command word. So the command is:
<blockquote><code><pre>
qmgr server start</pre></code></blockquote>
Similar to Q client nodes, you can check the status of a running Q server node with
the command:
<blockquote><code><pre>
qmgr server status</pre></code></blockquote>
(Note that this command will take longer to complete than with client nodes, because
the communication passes through a multi-hop I2P tunnel, rather than just through
localhost TCP).<br>
<br>
If the status command succeeds, then you'll know your new Q Server Node is happily
running in background.
</blockquote>
<hr>
<h3>5.4. Joining A Q Network</h3>
<blockquote>
When a Q Server node starts up for the first time, it is in a private network
all by itself.<br>
<br>
If you want to link your server into an existing Q network, you'll have to add a
noderef for at least one other server on that network. The command to do this
is similar to that for subscribing a client node to a network:
<blockquote><code><pre>
qmgr server addref &lt;noderef-file&gt;</pre></code></blockquote>
where &lt;noderef-file&gt; is a file into which you've saved the noderef for
the network you want to join.
<blockquote>
<hr><i><small>
Recall from the section on client nodes that the authoritative noderefs
for the mainstream Q network can be downloaded from
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
</small></i><hr>
</blockquote>
After you've added the noderef, subsequent <b>qmgr server status</b> commands
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
server nodes come online in the mainstream Q network.)
</blockquote>
<hr>
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
<blockquote>
If you're planning to start your own private Q network, and want to include other
server operators in this network, then you'll have to export your server's noderef
and make it available to the others you want to invite into your network.<br>
<br>
The command to export your Q Server noderef is:
<blockquote><code><pre>
qmgr server getref &lt;noderef-file&gt;</pre></code></blockquote>
This will extract the <i>I2P Destination</i> of your running server node, and
write it into &lt;noderef-file&gt;. You can then privately share this file with
others who you want to invite into your private network. Each recipient of
this file will do a <b>qmgr server addref &lt;noderef-file&gt;</b> command
to import your ref into their servers.<br>
<br>
Don't forget that if you're running, or participating in, a private Q network, then
you'll need to run a separate client for accessing this network, separate from any
mainstream Q network client you may already be running.<br>
<br>
To start this extra client, you'll have to choose a directory where you want this
client to reside, a port number you want your client to listen on locally for
user commands, and run the command:
<blockquote><code><pre>
qmgr -dir /path/to/my/new/client -port &lt;portnum&gt; start</pre></code></blockquote>
You need the <b>-port &lt;portnum&gt;</b> command, because otherwise it'll fail
to launch (if you already have a client node running off the mainstream Q network).<br>
<br>
This will create, and launch, a new instance of a Q client, accessing your private
Q network. Don't forget to import your server's noderef into this client. Also,
note that you'll have to use this same <b>-port &lt;portnum&gt;</b> argument when
doing any operation on this client instance, such as get, put, status, search.
</blockquote>
<a name="qmgr"/>
<hr>
<h2>6. About the qmgr Utility</h2>
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
Q client application, that happens to be bundled in with the Q distro.<br>
<br>
It is by no means the only, or even main facility for accessing the Q network. We
anticipate that folks will write all manner of client apps, including fancy GUI
apps.<br>
<br>
Anyway, qmgr does give you a rudimentary yet workable client for basic access
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
<br>
Don't forget that qmgr has very detailed inbuilt help. Run:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
for a quick help summary, or:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
for the 'War and Peace' treatise.<br>
<br>
<blockquote><hr>
One crucial concept to remember with qmgr is that client and server node instances
are uniquely identified by the directories at which they reside. If you are running
multiple server and/or client instances, you can specify an instance with the
<b>-dir &lt;dirpath&gt;</b> option - see the help for details.
<hr></blockquote>
<hr>
One last note - we strongly discourage any writing of client apps that spawn a qmgr
process, pass it arguments and parse its results. This is most definitely a path to
pain, since qmgr's shell interface is subject to radical change at any time without
notice.<br>
<br>
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
this.<br>
<br>
If you want to write higher-level clients, your best course of action is to use the
official client api library, which we anticipate will have versions available in
Java, Python, Perl and C++. If you want to write in another language, such as
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
reference to support you in writing a native port for your own language.
<a name="contact"/>
<hr>
<h2>8. Contacting the Author</h2>
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
<br>
<hr>
<center>
Return to <a href="../index.html">Q Homepage</a><br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<hr>
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
<!-- hhmts start -->
Last modified: Sun Apr 3 20:06:53 NZST 2005
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,23 +0,0 @@
rise on each hit:
dy = (1 - y) / kRise
fall after each time unit:
dy = y / kFall
fall after time dt:
dy = - y ** - (dt / kFall)
after the next hit:
y = y - y ** (- dt / kFall) + (1 - y) / kRise
first attempt at a load measurement algorithm:
- kFall is an arbitrary constant which dictates decay rate of load
in the absence of hits
- kRise is another constant which dictates rise of load with each hit
- dt is the time between each hit

View File

@@ -1,372 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q Metadata Specification</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
-->
</style>
</head>
<body>
<h1>Q Metadata Specification</h1>
<h2>1. Introduction</h2>
This document lists the standard metadata keys for Q data items,
discussing the rules of metadata insertion, processing and validation.<br>
<hr>
<h3>1.1. Definitions</h3>
To avoid confusions in terminology, this document will strictly abide the following definitions:
<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Term</td>
<td>Definition</td>
</tr>
<tr>
<td><code>key</code></td>
<td>A metadata category name, technically a <code>key</code> as the word is used with
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
Q URIs have the form: <code>Q:&lt;basename&gt;[,&lt;cryptoKey&gt;][&lt;path&gt;]</code>
<br>
<br>
Some examples:
<ul>
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
no cryptoKey)</li>
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
a cryptoKey)</li>
</td>
</tr>
<tr>
<td><code>basename</code></td>
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
URI calculation procedures</td>
</tr>
<tr>
<td><code>cryptoKey</code></td>
<td>An optional session encryption key for the stored data, encoded as base64.
This affords some protection to server node operators, and gives them a level
of plausible deniability for whatever gets stored in their server's
datastore without their direct human awareness.</td>
</tr>
<tr>
<td><code>path</code></td>
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
component in (for example) standard HTTP URLs
<code>http://&lt;domainname&gt;[:&lt;port&gt;][&lt;path&gt;]</code>, such as
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
is <code>/faq/editorial.shtml</code>).<br>
<br>
Paths, if not empty, should contain a leading slash ("/").
If an application specifies a non-empty <code>path</code> that doesn't begin with a
leading '/', a '/' will be automatically prepended by the receiving node.
</td>
</tr>
<tr>
<td><code>plain hash</code></td>
<td>A mode of inserting items, whereby the security of the resulting URI comes from
computing the URI from a hash of the item's data and metadata (and imposing a
mathematical barrier against spoofing content under a given URI). Corresponds to
Freenet's <code>CHK@</code> keys.</td>
</tr>
<tr>
<td><code>secure space</code></td>
<td>A mode of inserting items where the security of the URI is based not on a hash of the
item's data and metadata (as with <code>plain hash</code> mode),
but on the <code>privateKey</code> provided by the
application, and a content signature created from that private key.
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
can insert any number of items under different pseudo-pathnames (as is the case
with Freenet SSK keys).
</li>
</table>
<br><br>
<hr>
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
As the heading suggests, this is a list of metadata keys which should be inserted by a
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>title</code></td>
<td>String</td>
<td>Optional but strongly recommended. A free-text short description of the item,
should be less than 80 characters. The idea is that applications should
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
titles expressed as valid filename syntax has been abandoned).
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional but strongly recommended.
A virtual 'pathname' for the item, which should be in valid *nix
absolute pathname syntax (beginning with '/', containing no '//', consisting
only of alphanumerics, '-', '_', '.' and '/'.<br>
<br>
In Q web interfaces, the <code>filename</code> component of this path will
serve as the recommended filename when downloading/saving the item.<br>
<br>
If the application also provides a
<code>privateKey</code> key, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
under which the item can be retrieved by others.<br>
<br>
Refer also to <code>mimetype</code> below.
</td>
</tr>
<tr>
<td><code>encrypt</code></td>
<td>String</td>
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
this indicates that the application wishes the data to be stored on servers in
encrypted form.<br>
<br>
If this key is present and set to a positive value, the Q node, on receiving the
<code>putItem</code> RPC, will:
<ol>
<li>Generate a random symmetric encryption key</li>
<li>Encrypt the item's data using this encryption key</li>
<li>Delete the <code>encrypt</code> key from the metadata</li>
<li>Enclose a base64 representation of this encryption key in the RPC response
it sends back to the application (embedded in the <code>uri</code></li>
</ol>
</td>
</tr>
<tr>
<td><code>type</code></td>
<td>String</td>
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
audio video archive other</code></td>
</tr>
<tr>
<td><code>mimetype</code></td>
<td>String</td>
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
a mometype from the value of the <code>path</code> key. If this attempt fails, then
this key will be set to <code>application/x-octet-stream</code> by the node receiving
the <code>putItem</code> RPC.</td>
</tr>
<tr>
<td><code>keywords</code></td>
<td>String</td>
<td>Optional but moderately recommended.
A set of keywords, under which the inserting app would like this item to be
discoverable. Keywords should be entirely lower case and comma-separated. Content
inserts should consider keywords carefully, and only use space characters inside
keywords when necessary (eg, for flagging a distinctive phrase containing very
common words).</td>
<tr>
<td><code>privateKey</code></td>
<td>String</td>
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
<code>path</code>, indicating a 'path' within the signed space. If 'path'
is not given, it will default to '/'.<br>
<br>
Either way, when a node receives a
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
it removes this key and replaces it with <code>publicKey</code> and
<code>signature</code>.
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional. The virtual pathname, within signed space, under which to store the item.
This gets ignored and deleted unless the application also provides a
<code>privateKey</code> as well. But if the private key is given, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below).<br>
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
and preferably ending in a meaningful file extension such as <code>.html</code>
</td>
</tr>
<tr>
<td><code>expiry</code></td>
<td>int</td>
<td>Unixtime at which the inserted item should expire. When this expiry time
is reached, the item won't necessarily be deleted straight away, but may
be deleted whenever a node's data store is full.<br>
<br>
If this is not provided, it will default to a given duration according to
the client node's configuration.<br>
<br>
If it is provided, by an application, then the client node will transparently
generate the required 'rent payment' before caching the data item and uploading
it to servers.
</td>
</tr>
</table>
<br><br>
<hr>
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>size</code></td>
<td>Integer</td>
<td>Size of the data to be inserted, in bytes.</td>
</tr>
<tr>
<td><code>dataHash</code></td>
<td>String</td>
<td>base64-encoded SHA256 hash of data.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>String</td>
<td>This depends on whether the item is being inserted in <i>plain</i> or
<i>signed space</i> mode.<br>
<br>
If inserting in <i>plain</i> mode, then the uri is in the form
<code>Q:somebase64hash</code>, where the hash is computed according to
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
<br>
If inserting in <i>signed space</i> mode, then the uri will be in the form
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
the <a href="#signedhash">signed space hash calculation procedure</a>, and
the <code>/path.ext</code> is the verbatim value of the app-supplied
<code>path</code> key.
</td>
</tr>
<tr>
<td><code>publicKey</code></td>
<td>String</td>
<td>Base64-encoded signing public key. In cases where app provides
<code>privateKey</code>,
a node will derive the signing public key from the private key,
delete the private key from the metadata, and replace it with its corresponding
public key
key.</td>
</tr>
<tr>
<td><code>signature</code></td>
<td>String</td>
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
the app-provided <code>privateKey</code>.</td>
</tr>
<tr>
<td><code>rent</code></td>
<td>String</td>
<td>A rent payment for the data's accommodation on the server.<br>
Intention is to support a variety of payment tokens. Initially, the
only acceptable form of payment will be a hashcash-like token,
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
prefix indicates that this payment is in hashcash currency, in which case
the <code>base64String</code> should decode to a 16-byte string whose
SHA256 hash partially collides with <code>dataHash</code>.
The greater the number of bits in the collision,
the longer the data's accommodation will be 'paid up for'.<br>
<br>
If this key is already present, a Q node will verify the hashcash,
and adjust the <code>expiry</code> key value to the time the item's accommodation
is paid up till.<br>
<br>
If the key is not present:
<ul>
<li>A client node will generate a value for this key with enough collision bits
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
key to the end of the free accommodation period.</li>
</ul>
</td>
</tr>
</table>
<br><br>
<a name="plainhash"/>
<hr>
<h2>3. URI Determination Procedures</h2>
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
When items are inserted in <code>plain</code> mode, the final URI is determined from
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
<br>
Specifically, the recipe for calculating plain hash URIs is:
<ol>
<li>If the key <code>size</code> is missing, set this to the size of the data,
in bytes</li>
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
SHA256(data)</li>
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
where each line contains a metadata <code>key</code> and its <code>value</code>, and
is terminated by an ASCII linefeed (\n, 0x10).</li>
<li>Ensure that key <code>uri</code> is omitted</li>
<li>Sort the strings into ascending ASCII sort order</li>
<li>Concatenate the strings together into one big string</li>
<li>Calculate the SHA256 hash of this string</li>
<li>Encode the hash into Base64</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
<a name="signedhash"/>
<hr>
<h3>3.2. Signed Space URI Calculation Procedure</h3>
This is much simpler than determining plain hash URI, since the security of the URI
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
given by the application.<br>
<br>
Calculation recipe for Signed Space URIs is:
<ol>
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
if not empty, must begin with a '/')</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
<hr>
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
<!-- hhmts start -->
Last modified: Wed Apr 6 00:36:37 NZST 2005
<!-- hhmts end -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1 +0,0 @@
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Some files were not shown because too many files have changed in this diff Show More