diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java index ca2d6f878a67c2a5e871c26df871176e1370f680..cd4ccfdcb46d9bb2a31da9f5e904d47c552735b4 100644 --- a/apps/BOB/src/net/i2p/BOB/BOB.java +++ b/apps/BOB/src/net/i2p/BOB/BOB.java @@ -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(); } diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java new file mode 100644 index 0000000000000000000000000000000000000000..f309bcb96d9135cbf726dc39025e02e2adece8d0 --- /dev/null +++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java @@ -0,0 +1,1349 @@ +/** + * 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 = "-C"; + public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT; + private Socket server; + private Properties props; + private NamedDB database; + private String line; + private Destination d; + private ByteArrayOutputStream prikey; + private boolean dk, ns, ip, op; + private NamedDB 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"; + + /* 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, NamedDB database, Log _log) { + this.server = server; + this.props = new Properties(props); + this.database = database; + this._log = _log; + } + + private void rlock() throws Exception { + rlock(nickinfo); + } + + private void rlock(NamedDB Arg) throws Exception { + database.getReadLock(); + Arg.getReadLock(); + } + + private void runlock() throws Exception { + runlock(nickinfo); + } + + private void runlock(NamedDB Arg) throws Exception { + Arg.releaseReadLock(); + database.releaseReadLock(); + } + + private void wlock() throws Exception { + wlock(nickinfo); + } + + private void wlock(NamedDB Arg) throws Exception { + database.getWriteLock(); + Arg.getWriteLock(); + } + + private void wunlock() throws Exception { + wunlock(nickinfo); + } + + private void wunlock(NamedDB Arg) throws Exception { + Arg.releaseWriteLock(); + database.releaseWriteLock(); + } + + /** + * Try to print info from the database + * + * @param out + * @param info + * @param key + * @throws Exception + */ + private void trypnt(PrintStream out, NamedDB info, Object key) throws Exception { + try { + rlock(info); + } catch(Exception e) { + throw new Exception(e); + } + try { + out.print(" " + key + ": "); + if(info.exists(key)) { + out.print(info.get(key)); + } else { + out.print("not_set"); + } + } catch(Exception e) { + runlock(info); + throw new Exception(e); + } + runlock(info); + } + + /** + * Print true or false if an object exists + * + * @param out + * @param info + * @param key + * @throws Exception + */ + private void tfpnt(PrintStream out, NamedDB info, Object key) throws Exception { + try { + rlock(info); + } catch(Exception e) { + throw new Exception(e); + } + try { + out.print(" " + key + ": "); + out.print(info.exists(key)); + } catch(Exception e) { + runlock(info); + throw new Exception(e); + } + runlock(info); + } + + /** + * Print an error message + * + * @param out + */ + private void nns(PrintStream out) throws IOException { + out.println("ERROR no nickname has been set"); + } + + /** + * Dump various information from the database + * + * @param out + * @param info + * @throws Exception + */ + private void nickprint(PrintStream out, NamedDB info) throws Exception { + try { + rlock(info); + } catch(Exception e) { + throw new Exception(e); + } + try { + + 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); + out.println(); + } catch(Exception e) { + runlock(info); + throw new Exception(e); + } + + runlock(info); + } + + /** + * Print information on a specific record, indicated by NamedDB + * @param out + * @param Arg + * @throws Exception + */ + private void ttlpnt(PrintStream out, Object Arg) throws Exception { + try { + database.getReadLock(); + } catch(Exception e) { + throw new Exception(e); + } + + try { + if(database.exists(Arg)) { + out.print("DATA"); + nickprint(out, (NamedDB)database.get(Arg)); + } + } catch(Exception e) { + database.releaseReadLock(); + throw new Exception(e); + } + + + database.releaseReadLock(); + } + + /** + * Is this NamedDB's tunnel active? + * + * @param Arg + * @return true if the tunnel is active + */ + private boolean tunnelactive(NamedDB Arg) throws Exception { + boolean retval; + try { + rlock(Arg); + } catch(Exception e) { + throw new Exception(e); + } + + try { + retval = (Arg.get(P_STARTING).equals(Boolean.TRUE) || + Arg.get(P_STOPPING).equals(Boolean.TRUE) || + Arg.get(P_RUNNING).equals(Boolean.TRUE)); + } catch(Exception e) { + runlock(); + throw new Exception(e); + } + + 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()); +quit: { +die: { + 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 = ""; + NamedDB 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) { + try { + rlock(); + } catch(Exception ex) { + break die; + } + + try { + out.println("OK " + nickinfo.get(P_DEST)); + } catch(Exception e) { + try { + runlock(); + } catch(Exception ex) { + break die; + } + break die; + } + + try { + runlock(); + } catch(Exception ex) { + break die; + } + + } 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 = (NamedDB)database.getnext(i); + out.print("DATA"); + } catch(Exception e) { + database.releaseReadLock(); + break die; + } + + try { + info.getReadLock(); + } catch(Exception ex) { + break die; + } + try { + nickprint(out, info); + } catch(Exception e) { + try { + info.releaseReadLock(); + database.releaseReadLock(); + } catch(Exception ex) { + break die; + } + break die; + } + + try { + info.releaseReadLock(); + } catch(Exception ex) { + break die; + } + } + + try { + database.releaseReadLock(); + } catch(Exception ex) { + break die; + } + out.println("OK Listing done"); + } else if(Command.equals(C_quit)) { + // End the command session + break quit; + } else if(Command.equals(C_newkeys)) { + if(ns) { + try { + 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); + try { + wlock(); + } catch(Exception e) { + break die; + } + + try { + nickinfo.add(P_KEYS, prikey.toByteArray()); + nickinfo.add(P_DEST, d.toBase64()); + } catch(Exception e) { + try { + wunlock(); + } catch(Exception ex) { + break die; + } + break die; + } + + dk = true; + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + try { + rlock(); + } catch(Exception ex) { + break die; + } + + try { + out.println("OK " + nickinfo.get(P_DEST)); + } catch(Exception e) { + runlock(); + break die; + } + + try { + runlock(); + } catch(Exception ex) { + break die; + } + } catch(I2PException ipe) { + BOB.error("Error generating keys" + ipe); + out.println("ERROR generating keys"); + } + + } + } catch(Exception e) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_getkeys)) { + // Return public key + if(dk) { + prikey = new ByteArrayOutputStream(); + try { + rlock(); + } catch(Exception e) { + break die; + } + try { + prikey.write(((byte[])nickinfo.get(P_KEYS))); + } catch(Exception ex) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + runlock(); + } catch(Exception e) { + break die; + } + + 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) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + try { + wlock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo.add(P_QUIET, new Boolean(Boolean.parseBoolean(Arg) == true)); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + out.println("OK Quiet set"); + } + + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_setkeys)) { + // Set the NamedDB to a privatekey in BASE64 format + if(ns) { + try { + 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)) { + try { + wlock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo.add(P_KEYS, prikey.toByteArray()); + nickinfo.add(P_DEST, d.toBase64()); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + dk = true; + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + try { + rlock(); + } catch(Exception ex) { + break die; + } + + try { + out.println("OK " + nickinfo.get(P_DEST)); + } catch(Exception e) { + try { + runlock(); + } catch(Exception ex) { + break die; + } + break die; + } + + try { + runlock(); + } catch(Exception ex) { + break die; + } + } else { + out.println("ERROR not in BASE64 format"); + } + + } + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_setnick)) { + ns = dk = ip = op = false; + try { + database.getReadLock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo = (NamedDB)database.get(Arg); + if(!tunnelactive(nickinfo)) { + nickinfo = null; + ns = + true; + } + + } catch(Exception b) { + nickinfo = null; + ns = + true; + } + + try { + database.releaseReadLock(); + } catch(Exception ex) { + break die; + } + // Clears and Sets the initial NamedDB structure to work with + if(ns) { + nickinfo = new NamedDB(); + try { + wlock(); + } catch(Exception e) { + break die; + } + + try { + 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); + } catch(Exception e) { + try { + wunlock(); + break die; + } catch(Exception ee) { + break die; + } + + } + try { + wunlock(); + } catch(Exception e) { + break die; + } + + out.println("OK Nickname set to " + Arg); + } else { + out.println("ERROR tunnel is active"); + } + + } else if(Command.equals(C_option)) { + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + StringTokenizer otoken = new StringTokenizer(Arg, "="); // use an equal sign as a delimiter + if(otoken.countTokens() != 2) { + out.println("ERROR to many or no options."); + } else { + String pname = otoken.nextToken(); + String pval = otoken.nextToken(); + try { + rlock(); + } catch(Exception ex) { + break die; + } + + Properties Q = (Properties)nickinfo.get(P_PROPERTIES); + try { + runlock(); + } catch(Exception ex) { + break die; + } + + Q.setProperty(pname, pval); + try { + wlock(); + } catch(Exception ex) { + break die; + } + + try { + nickinfo.add(P_PROPERTIES, Q); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + out.println("OK " + pname + " set to " + pval); + } + + } + } catch(Exception ex) { + break die; + } + + } else { + nns(out); + } + + } else if(Command.equals(C_getnick)) { + // Get the NamedDB to work with... + try { + database.getReadLock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo = (NamedDB)database.get(Arg); + ns = true; + } catch(RuntimeException b) { + try { + nns(out); + } catch(Exception ex) { + try { + database.releaseReadLock(); + } catch(Exception ee) { + break die; + } + break die; + } + } + + database.releaseReadLock(); + if(ns) { + try { + rlock(); + } catch(Exception e) { + break die; + } + try { + dk = nickinfo.exists(P_KEYS); + ip = nickinfo.exists(P_INPORT); + op = nickinfo.exists(P_OUTPORT); + } catch(Exception ex) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + runlock(); + } catch(Exception e) { + break die; + } +// Finally say OK. + out.println("OK Nickname set to " + Arg); + } + + } else if(Command.equals(C_inport)) { + // Set the NamedDB inbound TO the router port + // app --> BOB + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + int prt; + try { + wlock(); + } catch(Exception ex) { + break die; + } + + try { + nickinfo.kill(P_INPORT); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + + break die; + } + try { + prt = Integer.parseInt(Arg); + if(prt > 1 && prt < 65536) { + try { + nickinfo.add(P_INPORT, new Integer(prt)); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + + break die; + } + } + + } catch(NumberFormatException nfe) { + out.println("ERROR not a number"); + } + + try { + wunlock(); + } catch(Exception ex) { + break die; + } + try { + rlock(); + } catch(Exception ex) { + break die; + } + + try { + ip = nickinfo.exists(P_INPORT); + } catch(Exception ex) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + runlock(); + } catch(Exception ex) { + break die; + } + + if(ip) { + out.println("OK inbound port set"); + } else { + out.println("ERROR port out of range"); + } + + } + } catch(Exception ex) { + break die; + } + + } else { + nns(out); + } + + } else if(Command.equals(C_outport)) { + // Set the NamedDB outbound FROM the router port + // BOB --> app + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + int prt; + try { + wlock(); + } catch(Exception ex) { + break die; + } + + try { + nickinfo.kill(P_OUTPORT); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + prt = Integer.parseInt(Arg); + if(prt > 1 && prt < 65536) { + try { + nickinfo.add(P_OUTPORT, new Integer(prt)); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + } + + } catch(NumberFormatException nfe) { + out.println("ERROR not a number"); + } + + try { + wunlock(); + } catch(Exception ex) { + break die; + } + try { + rlock(); + } catch(Exception ex) { + break die; + } + + try { + ip = nickinfo.exists(P_OUTPORT); + } catch(Exception ex) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + runlock(); + } catch(Exception ex) { + break die; + } + + if(ip) { + out.println("OK outbound port set"); + } else { + out.println("ERROR port out of range"); + } + + } + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_inhost)) { + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + try { + wlock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo.add(P_INHOST, Arg); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + out.println("OK inhost set"); + } + + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_outhost)) { + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + try { + wlock(); + } catch(Exception ex) { + break die; + } + try { + nickinfo.add(P_OUTHOST, Arg); + } catch(Exception ex) { + try { + wunlock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + wunlock(); + } catch(Exception ex) { + break die; + } + + out.println("OK outhost set"); + } + + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception ex) { + break die; + } + } + + } else if(Command.equals(C_show)) { + // Get the current NamedDB properties + if(ns) { + out.print("OK"); + try { + rlock(); + } catch(Exception e) { + break die; + } + + try { + nickprint(out, nickinfo); + } catch(Exception e) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + + out.println(); // this will cause an IOE if IOE + break die; + } + + try { + runlock(); + } catch(Exception e) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception e) { + break die; + } + } + + } else if(Command.equals(C_start)) { + // Start the tunnel, if we have all the information + if(ns && dk && (ip || op)) { + try { + 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); + } + + } + } catch(Exception ex) { + break die; + } + + } else { + out.println("ERROR tunnel settings incomplete"); + } + + } else if(Command.equals(C_stop)) { + // Stop the tunnel, if it is running + if(ns) { + try { + rlock(); + } catch(Exception e) { + break die; + } + + try { + if(nickinfo.get(P_RUNNING).equals(Boolean.TRUE) && nickinfo.get(P_STOPPING).equals(Boolean.FALSE) && nickinfo.get(P_STARTING).equals(Boolean.FALSE)) { + try { + runlock(); + } catch(Exception e) { + break die; + } + + try { + wlock(); + } catch(Exception e) { + break die; + } + + nickinfo.add(P_STOPPING, Boolean.TRUE); + try { + wunlock(); + + } catch(Exception e) { + break die; + } + + out.println("OK tunnel stopping"); + } else { + try { + runlock(); + } catch(Exception e) { + break die; + } + + out.println("ERROR tunnel is inactive"); + } + } catch(Exception e) { + try { + runlock(); + } catch(Exception ee) { + break die; + } + break die; + } + + } else { + try { + nns(out); + } catch(Exception e) { + break die; + } + } + + } else if(Command.equals(C_clear)) { + // Clear use of the NamedDB if stopped + if(ns) { + try { + if(tunnelactive(nickinfo)) { + out.println("ERROR tunnel is active"); + } else { + try { + database.getWriteLock(); + } catch(Exception e) { + break die; + } + try { + database.kill(nickinfo.get(P_NICKNAME)); + } catch(Exception e) { + try { + database.releaseWriteLock(); + } catch(Exception ee) { + break die; + } + break die; + } + try { + database.releaseWriteLock(); + } catch(Exception e) { + break die; + } + dk = ns = ip = op = false; + out.println("OK cleared"); + } + + } catch(Exception ex) { + break die; + } + + } else { + try { + nns(out); + } catch(Exception e) { + break die; + } + } + + } else if(Command.equals(C_status)) { + try { + if(database.exists(Arg)) { + // Show status of a NamedDB + out.print("OK "); + try { + ttlpnt(out, Arg); + } catch(Exception e) { + out.println(); // this will cause an IOE if IOE + break die; + } + + } else { + try { + nns(out); + } catch(Exception e) { + break die; + } + } + } catch(Exception e) { + break die; + } + + } else { + out.println("ERROR UNKNOWN COMMAND! Try help"); + } + + } + } + } // die + out.print("ERROR A really bad error just happened, "); + } // quit +// Say goodbye. + + out.println("OK Bye!"); + + server.close(); + } catch(IOException ioe) { + BOB.warn("IOException on socket listen: " + ioe); + ioe.printStackTrace(); + } + } +} diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java index c306c0ebf2de803e9ee2b8c355e829999ea53a35..c30e751f5c0456c91eadaecad9fa16ce675eb288 100644 --- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java +++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java @@ -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; diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java index 4d5f239c6cff48723e27c7ab12ba6d05919d792d..dae9f3e0ff2392fd9b48c51a2dbc990924eaa114 100644 --- a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java +++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java @@ -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; } } } diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index 08b75efbb1ff24699a73abb4976f15274f820039..a16c830b1c0f011dcf2b2cef58a3e1b6a346650c 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -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; @@ -90,7 +91,7 @@ public class MUXlisten implements Runnable { if(this.come_in) { this.listener = new ServerSocket(port, backlog, host); } - + // Everything is OK as far as we can tell. this.database.getWriteLock(); this.info.getWriteLock(); @@ -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(); - } - - if(come_in) { - // TCP -> I2P - TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log); - Thread q = new Thread(tg, conn, "BOBTCPlistener" + N); - q.start(); + wlock(); + try { + info.add("RUNNING", Boolean.TRUE); + info.add("STARTING", Boolean.FALSE); + } catch(Exception e) { + wunlock(); + return; } + } catch(Exception e) { + return; + } + try { + wunlock(); + } catch(Exception e) { + return; + } - boolean spin = true; - while(spin) { - try { - Thread.sleep(1000); //sleep for 1000 ms (One second) - } catch(InterruptedException e) { - // nop +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 + } } - - this.database.getReadLock(); - this.info.getReadLock(); - spin = info.get("STOPPING").equals(Boolean.FALSE); - this.database.releaseReadLock(); - this.info.releaseReadLock(); + tg.destroy(); + // Zap reference to the ThreadGroup so the JVM can GC it. + tg = null; + } catch(Exception e) { + break quit; } - - 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 - } + } // quit + socketManager.destroySocketManager(); + // zero out everything, just incase. + try { + wlock(); + try { + info.add("STARTING", Boolean.FALSE); + info.add("STOPPING", Boolean.FALSE); + info.add("RUNNING", Boolean.FALSE); + } catch(Exception e) { + wunlock(); + return; } - tg.destroy(); - // Zap reference to the ThreadGroup so the JVM can GC it. - tg = null; + wunlock(); } catch(Exception e) { + return; } - - 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(); } } diff --git a/apps/BOB/src/net/i2p/BOB/nickname.java b/apps/BOB/src/net/i2p/BOB/NamedDB.java similarity index 95% rename from apps/BOB/src/net/i2p/BOB/nickname.java rename to apps/BOB/src/net/i2p/BOB/NamedDB.java index 37f16bb18df22b371470aa480637f52dfef1ecbf..8b0ba35df6a1079c53609b73135c5fd8f58d30da 100644 --- a/apps/BOB/src/net/i2p/BOB/nickname.java +++ b/apps/BOB/src/net/i2p/BOB/NamedDB.java @@ -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; - } /** diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index f6286f3290ca5420c06a1cbb227d5176fe9bb87f..e9024105afa93b67440ed73f19855d470a22408a 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -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; } } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java index 9134c0a47fa5c147a27d5a3db445066b144306a1..72b34eac86d014688b1edac109805f8f691ec946 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java +++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java @@ -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; diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java index dd199ecb7cbe925e68c4f562aa20bb9183a47b2c..3323d0450b1cd5491f74d71a4cce5fd812a86af3 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java +++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java @@ -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; diff --git a/apps/BOB/src/net/i2p/BOB/UDPIOthread.java b/apps/BOB/src/net/i2p/BOB/UDPIOthread.java index 0dce4a0c271c8740d96214d52259d414eadae69e..687fe80893cf6e94e599953f8816b754115ed9a7 100644 --- a/apps/BOB/src/net/i2p/BOB/UDPIOthread.java +++ b/apps/BOB/src/net/i2p/BOB/UDPIOthread.java @@ -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; diff --git a/apps/BOB/src/net/i2p/BOB/doCMDS.java b/apps/BOB/src/net/i2p/BOB/doCMDS.java deleted file mode 100644 index e87a91b62c98fd7bba4e16740adc158e314b4b14..0000000000000000000000000000000000000000 --- a/apps/BOB/src/net/i2p/BOB/doCMDS.java +++ /dev/null @@ -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(); - } - } -}