Refactored code.

Piles of pedantic lock checks to ensure we don't get deadlocked, it's ugly.
This commit is contained in:
sponge
2008-10-31 21:11:03 +00:00
parent 7f3f6dfde3
commit 0b599c45ec
11 changed files with 1576 additions and 866 deletions

View File

@ -113,7 +113,7 @@ public class BOB {
public final static String PROP_BOB_PORT = "BOB.port";
public final static String PROP_BOB_HOST = "BOB.host";
private static int maxConnections = 0;
private static nickname database;
private static NamedDB database;
/**
* Log a warning
@ -141,7 +141,7 @@ public class BOB {
* @param args
*/
public static void main(String[] args) {
database = new nickname();
database = new NamedDB();
int i = 0;
boolean save = false;
// Set up all defaults to be passed forward to other threads.
@ -212,10 +212,10 @@ public class BOB {
Socket server;
while((i++ < maxConnections) || (maxConnections == 0)) {
//doCMDS connection;
//DoCMDS connection;
server = listener.accept();
doCMDS conn_c = new doCMDS(server, props, database, _log);
DoCMDS conn_c = new DoCMDS(server, props, database, _log);
Thread t = new Thread(conn_c);
t.start();
}

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@ import net.i2p.util.Log;
*/
public class I2Plistener implements Runnable {
private nickname info, database;
private NamedDB info, database;
private Log _log;
private int tgwatch;
public I2PSocketManager socketManager;
@ -53,7 +53,7 @@ public class I2Plistener implements Runnable {
* @param database
* @param _log
*/
I2Plistener(I2PSocketManager S, nickname info, nickname database, Log _log) {
I2Plistener(I2PSocketManager S, NamedDB info, NamedDB database, Log _log) {
this.database = database;
this.info = info;
this._log = _log;

View File

@ -36,7 +36,7 @@ import net.i2p.client.streaming.I2PSocket;
public class I2PtoTCP implements Runnable {
private I2PSocket I2P;
private nickname info, database;
private NamedDB info, database;
private Socket sock;
/**
@ -46,65 +46,93 @@ public class I2PtoTCP implements Runnable {
* @param info
* @param database
*/
I2PtoTCP(I2PSocket I2Psock, nickname info, nickname database) {
I2PtoTCP(I2PSocket I2Psock, NamedDB info, NamedDB database) {
this.I2P = I2Psock;
this.info = info;
this.database = database;
}
private void rlock() throws Exception {
database.getReadLock();
info.getReadLock();
}
private void runlock() throws Exception {
database.releaseReadLock();
info.releaseReadLock();
}
/**
* I2P stream to TCP stream thread starter
*
*/
public void run() {
try {
database.getReadLock();
info.getReadLock();
String host = info.get("OUTHOST").toString();
int port = Integer.parseInt(info.get("OUTPORT").toString());
boolean tell = info.get("QUIET").equals(Boolean.FALSE);
info.releaseReadLock();
database.releaseReadLock();
sock = new Socket(host, port);
// make readers/writers
InputStream in = sock.getInputStream();
OutputStream out = sock.getOutputStream();
InputStream Iin = I2P.getInputStream();
OutputStream Iout = I2P.getOutputStream();
I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
if(tell) {
// tell who is connecting
out.write(I2P.getPeerDestination().toBase64().getBytes());
out.write(10); // nl
out.flush(); // not really needed, but...
}
// setup to cross the streams
TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P
TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app
Thread t = new Thread(conn_c, "TCPioA");
Thread q = new Thread(conn_a, "TCPioB");
// Fire!
t.start();
q.start();
while(t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
String host;
int port;
boolean tell;
die: {
try {
try {
Thread.sleep(10); //sleep for 10 ms
} catch(InterruptedException e) {
// nop
rlock();
} catch(Exception e) {
break die;
}
}
try {
host = info.get("OUTHOST").toString();
port = Integer.parseInt(info.get("OUTPORT").toString());
tell = info.get("QUIET").equals(Boolean.FALSE);
} catch(Exception e) {
runlock();
break die;
}
try {
runlock();
} catch(Exception e) {
break die;
}
sock = new Socket(host, port);
// make readers/writers
InputStream in = sock.getInputStream();
OutputStream out = sock.getOutputStream();
InputStream Iin = I2P.getInputStream();
OutputStream Iout = I2P.getOutputStream();
I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
} catch(Exception e) {
}
if(tell) {
// tell who is connecting
out.write(I2P.getPeerDestination().toBase64().getBytes());
out.write(10); // nl
out.flush(); // not really needed, but...
}
// setup to cross the streams
TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P
TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app
Thread t = new Thread(conn_c, "TCPioA");
Thread q = new Thread(conn_a, "TCPioB");
// Fire!
t.start();
q.start();
while(t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
try {
Thread.sleep(10); //sleep for 10 ms
} catch(InterruptedException e) {
// nop
}
}
} catch(Exception e) {
break die;
}
} // die
try {
I2P.close();
} catch(Exception e) {
tell = false;
}
try {
sock.close();
} catch(Exception e) {
tell = false;
}
}
}

View File

@ -41,7 +41,7 @@ import net.i2p.util.Log;
*/
public class MUXlisten implements Runnable {
private nickname database, info;
private NamedDB database, info;
private Log _log;
private I2PSocketManager socketManager;
private ByteArrayInputStream prikey;
@ -51,6 +51,7 @@ public class MUXlisten implements Runnable {
private int backlog = 50; // should this be more? less?
boolean go_out;
boolean come_in;
/**
* Constructor Will fail if INPORT is occupied.
*
@ -60,7 +61,7 @@ public class MUXlisten implements Runnable {
* @throws net.i2p.I2PException
* @throws java.io.IOException
*/
MUXlisten(nickname database, nickname info, Log _log) throws I2PException, IOException {
MUXlisten(NamedDB database, NamedDB info, Log _log) throws I2PException, IOException, RuntimeException {
int port = 0;
InetAddress host = null;
this.database = database;
@ -99,82 +100,143 @@ public class MUXlisten implements Runnable {
this.database.releaseWriteLock();
}
private void rlock() throws Exception {
database.getReadLock();
info.getReadLock();
}
private void runlock() throws Exception {
database.releaseReadLock();
info.releaseReadLock();
}
private void wlock() throws Exception {
database.getWriteLock();
info.getWriteLock();
}
private void wunlock() throws Exception {
info.releaseWriteLock();
database.releaseWriteLock();
}
/**
* MUX sockets, fire off a thread to connect, get destination info, and do I/O
*
*/
public void run() {
this.database.getWriteLock();
this.info.getWriteLock();
info.add("RUNNING", Boolean.TRUE);
info.add("STARTING", Boolean.FALSE);
this.info.releaseWriteLock();
this.database.releaseWriteLock();
try {
tg = new ThreadGroup(N);
// toss the connections to a new threads.
// will wrap with TCP and UDP when UDP works
if(go_out) {
// I2P -> TCP
I2Plistener conn = new I2Plistener(socketManager, info, database, _log);
Thread t = new Thread(tg, conn, "BOBI2Plistener " + N);
t.start();
wlock();
try {
info.add("RUNNING", Boolean.TRUE);
info.add("STARTING", Boolean.FALSE);
} catch(Exception e) {
wunlock();
return;
}
if(come_in) {
// TCP -> I2P
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log);
Thread q = new Thread(tg, conn, "BOBTCPlistener" + N);
q.start();
}
boolean spin = true;
while(spin) {
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(InterruptedException e) {
// nop
}
this.database.getReadLock();
this.info.getReadLock();
spin = info.get("STOPPING").equals(Boolean.FALSE);
this.database.releaseReadLock();
this.info.releaseReadLock();
}
this.database.getWriteLock();
this.info.getWriteLock();
info.add("RUNNING", Boolean.FALSE);
this.info.releaseWriteLock();
this.database.releaseWriteLock();
// wait for child threads and thread groups to die
while(tg.activeCount() + tg.activeGroupCount() != 0) {
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(InterruptedException ex) {
// nop
}
}
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
} catch(Exception e) {
return;
}
try {
wunlock();
} catch(Exception e) {
return;
}
quit: {
try {
tg = new ThreadGroup(N);
die: {
// toss the connections to a new threads.
// will wrap with TCP and UDP when UDP works
if(go_out) {
// I2P -> TCP
I2Plistener conn = new I2Plistener(socketManager, info, database, _log);
Thread t = new Thread(tg, conn, "BOBI2Plistener " + N);
t.start();
}
if(come_in) {
// TCP -> I2P
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log);
Thread q = new Thread(tg, conn, "BOBTCPlistener" + N);
q.start();
}
boolean spin = true;
while(spin) {
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(InterruptedException e) {
// nop
}
try {
rlock();
try {
spin = info.get("STOPPING").equals(Boolean.FALSE);
} catch(Exception e) {
runlock();
break die;
}
} catch(Exception e) {
break die;
}
try {
runlock();
} catch(Exception e) {
break die;
}
}
try {
wlock();
try {
info.add("RUNNING", Boolean.FALSE);
} catch(Exception e) {
wunlock();
break die;
}
} catch(Exception e) {
break die;
}
try {
wunlock();
} catch(Exception e) {
break die;
}
} // die
// wait for child threads and thread groups to die
while(tg.activeCount() + tg.activeGroupCount() != 0) {
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(InterruptedException ex) {
// nop
}
}
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
} catch(Exception e) {
break quit;
}
} // quit
socketManager.destroySocketManager();
// zero out everything, just incase.
this.database.getWriteLock();
this.info.getWriteLock();
info.add("STARTING", Boolean.FALSE);
info.add("STOPPING", Boolean.FALSE);
info.add("RUNNING", Boolean.FALSE);
this.info.releaseWriteLock();
this.database.releaseWriteLock();
try {
wlock();
try {
info.add("STARTING", Boolean.FALSE);
info.add("STOPPING", Boolean.FALSE);
info.add("RUNNING", Boolean.FALSE);
} catch(Exception e) {
wunlock();
return;
}
wunlock();
} catch(Exception e) {
return;
}
}
}

View File

@ -28,7 +28,7 @@ package net.i2p.BOB;
*
* @author sponge
*/
public class nickname {
public class NamedDB {
private volatile Object[][] data;
private volatile int index, writersWaiting, readers;
@ -37,7 +37,7 @@ public class nickname {
* make initial NULL object
*
*/
public nickname() {
public NamedDB() {
this.data = new Object[1][2];
this.index = this.writersWaiting = this.readers = 0;
}
@ -76,8 +76,9 @@ public class nickname {
* Find objects in the array, returns it's index or throws exception
* @param key
* @return an objects index
* @throws ArrayIndexOutOfBoundsException when key does not exist
*/
public int idx(Object key) {
public int idx(Object key) throws ArrayIndexOutOfBoundsException {
for(int i = 0; i < index; i++) {
if(key.equals(data[i][0])) {
return i;
@ -115,7 +116,6 @@ public class nickname {
}
index -= didsomething;
data = olddata;
}
/**

View File

@ -36,7 +36,7 @@ public class TCPio implements Runnable {
private InputStream Ain;
private OutputStream Aout;
private nickname info, database;
private NamedDB info, database;
/**
* Constructor
@ -46,16 +46,13 @@ public class TCPio implements Runnable {
* @param info
* @param database
*/
TCPio(InputStream Ain, OutputStream Aout, nickname info, nickname database) {
TCPio(InputStream Ain, OutputStream Aout, NamedDB info, NamedDB database) {
this.Ain = Ain;
this.Aout = Aout;
this.info = info;
this.database = database;
}
/**
* kill off the streams, to hopefully cause an IOException in the thread in order to kill it.
*/
/**
* Copy from source to destination...
* and yes, we are totally OK to block here on writes,
@ -73,17 +70,18 @@ public class TCPio implements Runnable {
spin = info.get("RUNNING").equals(Boolean.TRUE);
info.releaseReadLock();
database.releaseReadLock();
b = Ain.read(a, 0, 1);
// System.out.println(info.get("NICKNAME").toString() + " " + b);
if(b > 0) {
Aout.write(a, 0, 1);
// Aout.flush(); too slow!
Aout.write(a, 0, b);
} else if(b == 0) {
try {
// Thread.yield();
Thread.sleep(10);
} catch(InterruptedException ex) {
Thread.yield(); // this should act like a mini sleep.
if(Ain.available() == 0) {
try {
// Thread.yield();
Thread.sleep(10);
} catch(InterruptedException ex) {
}
}
} else {
/* according to the specs:
@ -97,6 +95,8 @@ public class TCPio implements Runnable {
}
}
} catch(Exception e) {
// Eject!!! Eject!!!
return;
}
}
}

View File

@ -40,7 +40,7 @@ import net.i2p.util.Log;
*/
public class TCPlistener implements Runnable {
private nickname info, database;
private NamedDB info, database;
private Log _log;
private int tgwatch;
public I2PSocketManager socketManager;
@ -54,7 +54,7 @@ public class TCPlistener implements Runnable {
* @param database
* @param _log
*/
TCPlistener(ServerSocket listener, I2PSocketManager S, nickname info, nickname database, Log _log) {
TCPlistener(ServerSocket listener, I2PSocketManager S, NamedDB info, NamedDB database, Log _log) {
this.database = database;
this.info = info;
this._log = _log;

View File

@ -45,7 +45,7 @@ import net.i2p.i2ptunnel.I2PTunnel;
public class TCPtoI2P implements Runnable {
private I2PSocket I2P;
private nickname info, database;
private NamedDB info, database;
private Socket sock;
private I2PSocketManager socketManager;
@ -57,7 +57,7 @@ public class TCPtoI2P implements Runnable {
* @return line of text as a String
* @throws Exception
*/
public static String Lread(InputStream in) throws Exception {
private static String lnRead(InputStream in) throws Exception {
String S;
int b;
char c;
@ -87,7 +87,7 @@ public class TCPtoI2P implements Runnable {
* @param info
* @param database
*/
TCPtoI2P(I2PSocketManager i2p, Socket socket, nickname info, nickname database) {
TCPtoI2P(I2PSocketManager i2p, Socket socket, NamedDB info, NamedDB database) {
this.sock = socket;
this.info = info;
this.database = database;
@ -119,7 +119,7 @@ public class TCPtoI2P implements Runnable {
InputStream in = sock.getInputStream();
OutputStream out = sock.getOutputStream();
try {
line = Lread(in);
line = lnRead(in);
input = line.toLowerCase();
Destination dest = null;

View File

@ -45,7 +45,7 @@ import net.i2p.util.Log;
*/
public class UDPIOthread implements I2PSessionListener, Runnable {
private nickname info;
private NamedDB info;
private Log _log;
private Socket socket;
private DataInputStream in;
@ -61,7 +61,7 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
* @param socket
* @param _session
*/
UDPIOthread(nickname info, Log _log, Socket socket, I2PSession _session) {
UDPIOthread(NamedDB info, Log _log, Socket socket, I2PSession _session) {
this.info = info;
this._log = _log;
this.socket = socket;

View File

@ -1,729 +0,0 @@
/**
* 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.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.I2PClientFactory;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Simplistic command parser for BOB
*
* @author sponge
*
*/
public class doCMDS implements Runnable {
// FIX ME
// I need a better way to do versioning, but this will do for now.
public static final String BMAJ = "00", BMIN = "00", BREV = "01", BEXT = "-B";
public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
private Socket server;
private Properties props;
private nickname database;
private String line;
private Destination d;
private ByteArrayOutputStream prikey;
private boolean dk, ns, ip, op;
private nickname nickinfo;
private Log _log;
/* database strings */
private static final String P_DEST = "DESTINATION";
private static final String P_INHOST = "INHOST";
private static final String P_INPORT = "INPORT";
private static final String P_KEYS = "KEYS";
private static final String P_NICKNAME = "NICKNAME";
private static final String P_OUTHOST = "OUTHOST";
private static final String P_OUTPORT = "OUTPORT";
private static final String P_PROPERTIES = "PROPERTIES";
private static final String P_QUIET = "QUIET";
private static final String P_RUNNING = "RUNNING";
private static final String P_STARTING = "STARTING";
private static final String P_STOPPING = "STOPPING";
// private static final String P_INSTATE = "INSTATE";
// private static final String P_OUTSTATE = "OUTSTATE";
/* command strings */
private static final String C_help = "help";
private static final String C_clear = "clear";
private static final String C_getdest = "getdest";
private static final String C_getkeys = "getkeys";
private static final String C_getnick = "getnick";
private static final String C_inhost = "inhost";
private static final String C_inport = "inport";
private static final String C_list = "list";
private static final String C_newkeys = "newkeys";
private static final String C_option = "option";
private static final String C_outhost = "outhost";
private static final String C_outport = "outport";
private static final String C_quiet = "quiet";
private static final String C_quit = "quit";
private static final String C_setkeys = "setkeys";
private static final String C_setnick = "setnick";
private static final String C_show = "show";
private static final String C_start = "start";
private static final String C_status = "status";
private static final String C_stop = "stop";
/* all the coomands available, plus description */
private static final String C_ALL[][] = {
{C_help, C_help + " <command> * Get help on a command."},
{C_clear, C_clear + " * Clear the current nickname out of the list."},
{C_getdest, C_getdest + " * Return the destination for the current nickname."},
{C_getkeys, C_getkeys + " * Return the keypair for the current nickname."},
{C_getnick, C_getnick + " tunnelname * Set the nickname from the database."},
{C_inhost, C_inhost + " hostname | IP * Set the inbound hostname or IP."},
{C_inport, C_inport + " port_number * Set the inbound port number nickname listens on."},
{C_list, C_list + " * List all tunnels."},
{C_newkeys, C_newkeys + " * Generate a new keypair for the current nickname."},
{C_option, C_option + " I2CPoption=something * Set an I2CP option. NOTE: Don't use any spaces."},
{C_outhost, C_outhost + " hostname | IP * Set the outbound hostname or IP."},
{C_outport, C_outport + " port_number * Set the outbound port that nickname contacts."},
{C_quiet, C_quiet + " True | False * Don't send to the application the incoming destination."},
{C_quit, C_quit + " * Quits this session with BOB."},
{C_setkeys, C_setkeys + " BASE64_keypair * Sets the keypair for the current nickname."},
{C_setnick, C_setnick + " nickname * Create a new nickname."},
{C_show, C_show + " * Display the status of the current nickname."},
{C_start, C_start + " * Start the current nickname tunnel."},
{C_status, C_status + " nickname * Display status of a nicknamed tunnel."},
{C_stop, C_stop + " * Stops the current nicknamed tunnel."},
{"", "COMMANDS: " + // this is ugly, but...
C_help + " " +
C_clear + " " +
C_getdest + " " +
C_getkeys + " " +
C_getnick + " " +
C_inhost + " " +
C_inport + " " +
C_list + " " +
C_newkeys + " " +
C_option + " " +
C_outhost + " " +
C_outport + " " +
C_quiet + " " +
C_quit + " " +
C_setkeys + " " +
C_setnick + " " +
C_show + " " +
C_start + " " +
C_status + " " +
C_stop
},
{" ", " "} // end of list
};
/**
*
* @param server
* @param props
* @param database
* @param _log
*/
doCMDS(Socket server, Properties props, nickname database, Log _log) {
this.server = server;
this.props = new Properties(props);
this.database = database;
this._log = _log;
}
private void rlock() {
rlock(nickinfo);
}
private void rlock(nickname Arg) {
database.getReadLock();
Arg.getReadLock();
}
private void runlock() {
runlock(nickinfo);
}
private void runlock(nickname Arg) {
Arg.releaseReadLock();
database.releaseReadLock();
}
private void wlock() {
wlock(nickinfo);
}
private void wlock(nickname Arg) {
database.getWriteLock();
Arg.getWriteLock();
}
private void wunlock() {
wunlock(nickinfo);
}
private void wunlock(nickname Arg) {
Arg.releaseWriteLock();
database.releaseWriteLock();
}
/**
* Try to print info from the database
*
* @param out
* @param info
* @param key
*/
public void trypnt(PrintStream out, nickname info, Object key) {
rlock(info);
try {
out.print(" " + key + ": ");
if(info.exists(key)) {
out.print(info.get(key));
} else {
out.print("not_set");
}
} catch(Exception e) {
}
runlock(info);
}
/**
* Print true or false if an object exists
*
* @param out
* @param info
* @param key
*/
public void tfpnt(PrintStream out, nickname info, Object key) {
rlock(info);
try {
out.print(" " + key + ": ");
out.print(info.exists(key));
} catch(Exception e) {
}
runlock(info);
}
/**
* Print an error message
*
* @param out
*/
public void nns(PrintStream out) {
out.println("ERROR no nickname has been set");
}
/**
* Dump various information from the database
*
* @param out
* @param info
*/
public void nickprint(PrintStream out, nickname info) {
rlock(info);
trypnt(out, info, P_NICKNAME);
trypnt(out, info, P_STARTING);
trypnt(out, info, P_RUNNING);
trypnt(out, info, P_STOPPING);
tfpnt(out, info, P_KEYS);
trypnt(out, info, P_QUIET);
trypnt(out, info, P_INPORT);
trypnt(out, info, P_INHOST);
trypnt(out, info, P_OUTPORT);
trypnt(out, info, P_OUTHOST);
try {
out.println();
} catch(Exception e) {
}
runlock(info);
}
/**
* Print information on a specific record, indicated by nickname
* @param out
* @param Arg
*/
public void ttlpnt(PrintStream out, Object Arg) {
database.getReadLock();
if(database.exists(Arg)) {
try {
out.print("DATA");
} catch(Exception e) {
}
nickprint(out, (nickname)database.get(Arg));
}
database.releaseReadLock();
}
/**
* Is this nickname's tunnel active?
*
* @param Arg
* @return true if the tunnel is active
*/
public boolean tunnelactive(nickname Arg) {
rlock(Arg);
boolean retval = (Arg.get(P_STARTING).equals(Boolean.TRUE) ||
Arg.get(P_STOPPING).equals(Boolean.TRUE) ||
Arg.get(P_RUNNING).equals(Boolean.TRUE));
runlock(Arg);
return retval;
}
/**
* Does the base64 information look OK
*
* @param data
* @return
*/
private boolean is64ok(String data) {
String dest = new String(data);
if(dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0) {
return true;
}
return false;
}
/**
* The actual parser.
* It probabbly needs a rewrite into functions, but I kind-of like inline code.
*
*/
public void run() {
dk = ns = ip = op = false;
try {
// Get input from the client
BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream()));
PrintStream out = new PrintStream(server.getOutputStream());
prikey = new ByteArrayOutputStream();
out.println("BOB " + BOBversion);
out.println("OK");
while((line = in.readLine()) != null) {
StringTokenizer token = new StringTokenizer(line, " "); // use a space as a delimiter
String Command = "";
String Arg = "";
nickname info;
if(token.countTokens() != 0) {
Command = token.nextToken();
Command = Command.toLowerCase();
if(token.countTokens() != 0) {
Arg = token.nextToken();
} else {
Arg = "";
}
// The rest of the tokens are considered junk,
// and discarded without any warnings.
if(Command.equals(C_help)) {
for(int i = 0; !C_ALL[i][0].equals(" "); i++) {
if(C_ALL[i][0].equalsIgnoreCase(Arg)) {
out.println("OK " + C_ALL[i][1]);
}
}
} else if(Command.equals(C_getdest)) {
if(ns) {
if(dk) {
rlock();
try {
out.println("OK " + nickinfo.get(P_DEST));
} catch(Exception e) {
}
runlock();
} else {
out.println("ERROR keys not set.");
}
} else {
nns(out);
}
} else if(Command.equals(C_list)) {
// Produce a formatted list of all nicknames
database.getReadLock();
for(int i = 0; i < database.getcount(); i++) {
try {
info = (nickname)database.getnext(i);
} catch(Exception b) {
break; // something bad happened.
}
try {
out.print("DATA");
} catch(Exception e) {
}
info.getReadLock();
nickprint(out, info);
info.releaseReadLock();
}
database.releaseReadLock();
out.println("OK Listing done");
} else if(Command.equals(C_quit)) {
// End the command session
break;
} else if(Command.equals(C_newkeys)) {
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
try {
// Make a new PublicKey and PrivateKey
prikey = new ByteArrayOutputStream();
d = I2PClientFactory.createClient().createDestination(prikey);
wlock();
nickinfo.add(P_KEYS, prikey.toByteArray());
nickinfo.add(P_DEST, d.toBase64());
dk = true;
wunlock();
rlock();
try {
out.println("OK " + nickinfo.get(P_DEST));
} catch(Exception e) {
}
runlock();
} catch(IOException ioe) {
BOB.error("Error generating keys" + ioe);
out.println("ERROR generating keys");
} catch(I2PException ipe) {
BOB.error("Error generating keys" + ipe);
out.println("ERROR generating keys");
}
}
} else {
nns(out);
}
} else if(Command.equals(C_getkeys)) {
// Return public key
if(dk) {
prikey = new ByteArrayOutputStream();
rlock();
prikey.write(((byte[])nickinfo.get(P_KEYS)));
runlock();
out.println("OK " + net.i2p.data.Base64.encode(prikey.toByteArray()));
} else {
out.println("ERROR no public key has been set");
}
} else if(Command.equals(C_quiet)) {
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
wlock();
nickinfo.add(P_QUIET, new Boolean(Boolean.parseBoolean(Arg) == true));
wunlock();
out.println("OK Quiet set");
}
} else {
nns(out);
}
} else if(Command.equals(C_setkeys)) {
// Set the nickname to a privatekey in BASE64 format
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
try {
prikey = new ByteArrayOutputStream();
prikey.write(net.i2p.data.Base64.decode(Arg));
d.fromBase64(Arg);
} catch(Exception ex) {
Arg = "";
}
if((Arg.length() == 884) && is64ok(Arg)) {
wlock();
nickinfo.add(P_KEYS, prikey.toByteArray());
nickinfo.add(P_DEST, d.toBase64());
dk = true;
wunlock();
rlock();
try {
out.println("OK " + nickinfo.get(P_DEST));
} catch(Exception e) {
}
runlock();
} else {
out.println("ERROR not in BASE64 format");
}
}
} else {
nns(out);
}
} else if(Command.equals(C_setnick)) {
ns = dk = ip = op = false;
database.getReadLock();
try {
nickinfo = (nickname)database.get(Arg);
if(!tunnelactive(nickinfo)) {
nickinfo = null;
ns = true;
}
} catch(Exception b) {
nickinfo = null;
ns = true;
}
database.releaseReadLock();
// Clears and Sets the initial nickname structure to work with
if(ns) {
nickinfo = new nickname();
wlock();
database.add(Arg, nickinfo);
nickinfo.add(P_NICKNAME, Arg);
nickinfo.add(P_STARTING, Boolean.FALSE);
nickinfo.add(P_RUNNING, Boolean.FALSE);
nickinfo.add(P_STOPPING, Boolean.FALSE);
nickinfo.add(P_QUIET, Boolean.FALSE);
nickinfo.add(P_INHOST, "localhost");
nickinfo.add(P_OUTHOST, "localhost");
Properties Q = new Properties(props);
Q.setProperty("inbound.nickname", Arg);
Q.setProperty("outbound.nickname", Arg);
nickinfo.add(P_PROPERTIES, Q);
wunlock();
out.println("OK Nickname set to " + Arg);
} else {
out.println("ERROR tunnel is active");
}
} else if(Command.equals(C_option)) {
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
StringTokenizer otoken = new StringTokenizer(Arg, "="); // use a space as a delimiter
if(otoken.countTokens() != 2) {
out.println("ERROR to many or no options.");
} else {
String pname = otoken.nextToken();
String pval = otoken.nextToken();
rlock();
Properties Q = (Properties)nickinfo.get(P_PROPERTIES);
runlock();
Q.setProperty(pname, pval);
wlock();
nickinfo.add(P_PROPERTIES, Q);
wunlock();
out.println("OK " + pname + " set to " + pval);
}
}
} else {
nns(out);
}
} else if(Command.equals(C_getnick)) {
// Get the nickname to work with...
database.getReadLock();
try {
nickinfo = (nickname)database.get(Arg);
ns = true;
} catch(RuntimeException b) {
nns(out);
}
database.releaseReadLock();
if(ns) {
rlock();
dk = nickinfo.exists(P_KEYS);
ip = nickinfo.exists(P_INPORT);
op = nickinfo.exists(P_OUTPORT);
runlock();
// Finally say OK.
out.println("OK Nickname set to " + Arg);
}
} else if(Command.equals(C_inport)) {
// Set the nickname inbound TO the router port
// app --> BOB
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
int prt;
wlock();
nickinfo.kill(P_INPORT);
try {
prt = Integer.parseInt(Arg);
if(prt > 1 && prt < 65536) {
nickinfo.add(P_INPORT, new Integer(prt));
}
} catch(NumberFormatException nfe) {
out.println("ERROR not a number");
}
wunlock();
rlock();
ip = nickinfo.exists(P_INPORT);
runlock();
if(ip) {
out.println("OK inbound port set");
} else {
out.println("ERROR port out of range");
}
}
} else {
nns(out);
}
} else if(Command.equals(C_outport)) {
// Set the nickname outbound FROM the router port
// BOB --> app
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
int prt;
wlock();
nickinfo.kill(P_OUTPORT);
try {
prt = Integer.parseInt(Arg);
if(prt > 1 && prt < 65536) {
nickinfo.add(P_OUTPORT, new Integer(prt));
}
} catch(NumberFormatException nfe) {
out.println("ERROR not a number");
}
wunlock();
rlock();
ip = nickinfo.exists(P_OUTPORT);
runlock();
if(ip) {
out.println("OK outbound port set");
} else {
out.println("ERROR port out of range");
}
}
} else {
nns(out);
}
} else if(Command.equals(C_inhost)) {
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
wlock();
nickinfo.add(P_INHOST, Arg);
wunlock();
out.println("OK inhost set");
}
} else {
nns(out);
}
} else if(Command.equals(C_outhost)) {
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
wlock();
nickinfo.add(P_OUTHOST, Arg);
wunlock();
out.println("OK outhost set");
}
} else {
nns(out);
}
} else if(Command.equals(C_show)) {
// Get the current nickname properties
if(ns) {
out.print("OK");
rlock();
nickprint(out, nickinfo);
runlock();
} else {
nns(out);
}
} else if(Command.equals(C_start)) {
// Start the tunnel, if we have all the information
if(ns && dk && (ip || op)) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
MUXlisten tunnel;
try {
tunnel = new MUXlisten(database, nickinfo, _log);
Thread t = new Thread(tunnel);
t.start();
out.println("OK tunnel starting");
} catch(I2PException e) {
out.println("ERROR starting tunnel: " + e);
} catch(IOException e) {
out.println("ERROR starting tunnel: " + e);
}
}
} else {
out.println("ERROR tunnel settings incomplete");
}
} else if(Command.equals(C_stop)) {
// Stop the tunnel, if it is running
if(ns) {
rlock();
if(nickinfo.get(P_RUNNING).equals(Boolean.TRUE) && nickinfo.get(P_STOPPING).equals(Boolean.FALSE) && nickinfo.get(P_STARTING).equals(Boolean.FALSE)) {
runlock();
wlock();
nickinfo.add(P_STOPPING, Boolean.TRUE);
wunlock();
out.println("OK tunnel stopping");
} else {
runlock();
out.println("ERROR tunnel is inactive");
}
} else {
nns(out);
}
} else if(Command.equals(C_clear)) {
// Clear use of the nickname if stopped
if(ns) {
if(tunnelactive(nickinfo)) {
out.println("ERROR tunnel is active");
} else {
database.getWriteLock();
database.kill(nickinfo.get(P_NICKNAME));
database.releaseWriteLock();
dk = ns = ip = op = false;
out.println("OK cleared");
}
} else {
nns(out);
}
} else if(Command.equals(C_status)) {
if(database.exists(Arg)) {
// Show status of a nickname
out.print("OK ");
ttlpnt(out, Arg);
} else {
nns(out);
}
} else {
out.println("ERROR UNKNOWN COMMAND! Try help");
}
}
}
// Say goodbye.
out.println("OK Bye!");
server.close();
} catch(IOException ioe) {
BOB.warn("IOException on socket listen: " + ioe);
ioe.printStackTrace();
}
}
}