diff options
author | Peter Palfrader <peter@palfrader.org> | 2003-10-19 15:08:35 +0000 |
---|---|---|
committer | Peter Palfrader <peter@palfrader.org> | 2003-10-19 15:08:35 +0000 |
commit | a4c0d3d6d878da55435cb9d9cc8cff6199644199 (patch) | |
tree | e066aabfb7a143cd39c3740b4aedd701df5ac758 /src | |
parent | 46a03a3ea3c26a65e4428bc9de036a01487aeda3 (diff) |
Support random path creation
Diffstat (limited to 'src')
18 files changed, 773 insertions, 133 deletions
diff --git a/src/Main.java b/src/Main.java index da89a50..58ef46b 100644 --- a/src/Main.java +++ b/src/Main.java @@ -15,8 +15,7 @@ public class Main { DirectoryParser parser = new DirectoryParser(new DirectoryLexer(new FileReader(argv[0]))); DirectoryMessage dm = (DirectoryMessage)parser.parse().value; Directory dir = new Directory(dm, false); - PathSpec path = new PathSpec(dir,"test1 : tonga", false); - + PathSpec path = new PathSpec(dir,"*4,~1", false); RoutingSMTP destination = new RoutingSMTP("peter@palfrader.org"); String body = "FROM:Peter\n" + "SUBJECT:test fancydress\n" + diff --git a/src/org/noreply/fancydress/crypto/CryptoPrimitives.java b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java index 820dbcb..d9b9884 100644 --- a/src/org/noreply/fancydress/crypto/CryptoPrimitives.java +++ b/src/org/noreply/fancydress/crypto/CryptoPrimitives.java @@ -66,6 +66,25 @@ public class CryptoPrimitives { return(result); } /** + * Get a random value from the normal distrubution with mean <code>u</code> and standard deviation <code>s</code> + * + * @param u mean or the normal distribution + * @param s standard deviation of the normal distribution + * @return a random value from given the normal distrubution + */ + public static double normal(double m, double s) { + return secureRandom.nextGaussian() * s + m; + } + /** + * Return a uniformly distributed int value between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be positive. + * @return a pseudorandom, uniformly distributed int value between 0 (inclusive) and n (exclusive). + */ + public static int randInt(int n) { + return secureRandom.nextInt(n); + } + /** * Get <code>n</code> octets of zeroes. * * @param n number of octets diff --git a/src/org/noreply/fancydress/directory/Directory.java b/src/org/noreply/fancydress/directory/Directory.java index f2eda8c..ff0cfdf 100644 --- a/src/org/noreply/fancydress/directory/Directory.java +++ b/src/org/noreply/fancydress/directory/Directory.java @@ -25,6 +25,12 @@ import java.util.*; */ public class Directory { /** + * directory version we understand. + */ + + private static final String DIRECTORY_VERSION = "0.2"; + + /** * Hash holding all Servers. * * This hash holds all Servers. Since nickname are to be treaded case @@ -37,35 +43,27 @@ public class Directory { private Hashtable byName; /** - * Possibly add a new server descriptor to the directory. + * Useable servers. * - * This method adds a server desccriptor to the directory if it - * - conforms to the syntax specified. - * - the server is not yet known or it's identity key matches the key of the already known ServerDescriptor. + * A server is useable if it understands PacketVersions that we understand + * and has a serverdescriptor that is useable right now. + */ + private Server[] useableServers; + + /** + * Recommended servers. + */ + private Server[] recommendedServers; + + /* + * matrix of friends. * - * @param server a DirectoryMessage consisting of one of more sections. At 'Server' section needs to be present. - * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored. - * @throws Mix3BadServerSignatureException if the server descriptor's signature is invalid. + * (a, b) are friends, if a can send messages to b. */ - private void addServerDescriptor(DirectoryMessage server) throws Mix3BadServerFormatException, Mix3BadServerSignatureException { - try { - ServerDescriptor sd = new ServerDescriptor(server); - String key = sd.getNickname().toLowerCase(); - if (byName.containsKey(key)) { - Server s = (Server) byName.get(key); - try { - s.addDescriptor(sd); - } catch (Mix3BadServerFormatException e) { - System.err.println("Ignoring Descriptor with different identity for "+key); - } - } else { - Server s = new Server(sd); - byName.put(key, s); - } - } catch (Mix3BadServerUnrecognizedVersionException e) { - System.err.println("Ignoring unregonized version"); - } - } + /* + private boolean[][] friends; + */ + /** * Create a directory from an entire DirectoryMessage. @@ -82,11 +80,87 @@ public class Directory { */ public Directory(DirectoryMessage m, boolean checkDirectorySignature) throws Mix3BadDirectorySignatureException, + Mix3BadDirectoryFormatException, Mix3BadServerFormatException, Mix3BadServerSignatureException { byName = new Hashtable(); + + parseDirectory(m, checkDirectorySignature); + + + Collection all = byName.values(); + ArrayList useable = new ArrayList(); + for (Iterator i = all.iterator(); i.hasNext(); ) { + Server s = (Server) i.next(); + if (s.isUseable()) + useable.add(s); + } + useableServers = (Server[]) useable.toArray( new Server[useable.size()] ); + + + ArrayList recommended = new ArrayList(); + for (int i=0; i<useableServers.length; i++) + if (useableServers[i].isRecommended()) + recommended.add(useableServers[i]); + recommendedServers = (Server[]) recommended.toArray( new Server[recommended.size()] ); + } + + + + /** + * Possibly add a new server descriptor to the directory. + * + * This method adds a server desccriptor to the directory if it + * - conforms to the syntax specified. + * - the server is not yet known or it's identity key matches the key of the already known ServerDescriptor. + * + * @param server a DirectoryMessage consisting of one of more sections. At 'Server' section needs to be present. + * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored. + * @throws Mix3BadServerSignatureException if the server descriptor's signature is invalid. + */ + private void addServerDescriptor(DirectoryMessage server) throws Mix3BadServerFormatException, Mix3BadServerSignatureException { + ServerDescriptor sd; + try { + sd = new ServerDescriptor(server); + } catch (Mix3BadServerUnrecognizedVersionException e) { + System.err.println("Ignoring unregonized version"); + return; + } + String key = sd.getNickname().toLowerCase(); + if (byName.containsKey(key)) { + Server s = (Server) byName.get(key); + try { + s.addDescriptor(sd); + } catch (Mix3BadServerFormatException e) { + System.err.println("Ignoring Descriptor with different identity for "+key); + } + } else { + Server s = new Server(sd); + byName.put(key, s); + } + } + + + private void parseDirectory(DirectoryMessage m, boolean checkDirectorySignature) throws + Mix3BadDirectorySignatureException, + Mix3BadDirectoryFormatException, + Mix3BadServerFormatException, + Mix3BadServerSignatureException + { + DirectorySection directorySection = m.getSection("Directory"); + if (directorySection == null) + throw new Mix3BadDirectoryFormatException("No Directory section found."); + + DirectoryEntry versionEntry = directorySection.getEntry("Version"); + if (versionEntry == null) + throw new Mix3BadDirectoryFormatException("No Version in Directory section found."); + if (! versionEntry.getValue().equals(DIRECTORY_VERSION)) + throw new Mix3BadDirectoryFormatException("Directory Version "+versionEntry+" not recognized."); + + /* FIXME: check validity (date range) */ + if (checkDirectorySignature) checkSignature(m); DirectoryMessage server = null; @@ -102,27 +176,38 @@ public class Directory { } if (server != null) addServerDescriptor(server); + + DirectoryEntry recommendedServersEntry = directorySection.getEntry("Recommended-Servers"); + if (recommendedServersEntry == null) + throw new Mix3BadDirectorySignatureException("No Recommended-Servers in Directory section found."); + String[] recommendedServers = Util.tokenize( recommendedServersEntry.getValue(), ','); + for (int i=0; i<recommendedServers.length; i++) { + Server s = getServer(recommendedServers[i]); + if (s == null) + throw new Mix3BadDirectoryFormatException("Unkown nickname '"+recommendedServers[i]+"'in Recommended-Servers"); + s.setRecommended(); + } } /* FIXME: known directory servers should be passed as arguments */ /** * Check the signature of a type III directory. */ - private void checkSignature(DirectoryMessage m) throws Mix3BadDirectorySignatureException { + private void checkSignature(DirectoryMessage m) throws Mix3BadDirectorySignatureException, Mix3BadDirectoryFormatException { /* FIXME: handle more than one signature block, */ DirectorySection signatureSection = m.getSection("Signature"); if (signatureSection == null) - throw new Mix3BadDirectorySignatureException("No Signature section found."); + throw new Mix3BadDirectoryFormatException("No Signature section found."); DirectoryEntry signatureEntry = signatureSection.getEntry("DirectorySignature"); if (signatureEntry == null) - throw new Mix3BadDirectorySignatureException("No DirectorySignature in Signature section found."); + throw new Mix3BadDirectoryFormatException("No DirectorySignature in Signature section found."); DirectoryEntry identityEntry = signatureSection.getEntry("DirectoryIdentity"); if (identityEntry == null) - throw new Mix3BadDirectorySignatureException("No DirectoryIdentity in Signature section found."); + throw new Mix3BadDirectoryFormatException("No DirectoryIdentity in Signature section found."); byte[] signature = Base64.decode(signatureEntry.getValue()); byte[] identity = Base64.decode(identityEntry.getValue()); @@ -131,7 +216,6 @@ public class Directory { cleanedDirectory.blindValue("Signature","DirectoryDigest"); cleanedDirectory.blindValue("Signature","DirectorySignature"); - /* FIXME: what if identity is no valid key? */ RSAPublicKey identityKey = new RSAPublicKey(identity); boolean verifies; try { @@ -145,6 +229,9 @@ public class Directory { /** * Get a server by nickname. + * + * @param name nickname of the requested server + * @return server specified by the nickname or null if no such server exists */ public Server getServer(String name) { String key = name.toLowerCase(); @@ -152,4 +239,85 @@ public class Directory { return (Server) byName.get(key); return null; } + + /* + * Get current server descriptor of useable servers. + * + * A server is useable if it understands PacketVersions that we understand + * and has a serverdescriptor that is useable right now. + * + * @return server descriptor of useable servers + */ + /* + private Server[] getUseableServers() { + return useableServers; + } + */ + + /** + * Get server descriptor of all recommended servers that are useable. + * + * @return server descriptor recommended servers + * @see #getUseableServers + */ + public Server[] getRecommendedServers() { + return recommendedServers; + } + + /** + * Check if server a can talk to server b. + * + * A server can talk to another one if they have MMTP/Outgoing and + * MMTP/Incoming sections respectively and speak a common MMTP version. + * + * FIXME: check Allow/Deny ACLs + * + * @param a server a + * @param b server b + * @return whether a can send messages to b + */ + public boolean areFriends(Server a, Server b) { + ServerDescriptor aa,bb; + + try { + aa = a.getDescriptor(); + } catch (Mix3NoServerDescriptorException e) { + throw new Error(a.getNickname()+" should have a valid ServerDescriptor.", e); + } + try { + bb = b.getDescriptor(); + } catch (Mix3NoServerDescriptorException e) { + throw new Error(b.getNickname()+" should have a valid ServerDescriptor.", e); + } + + OutgoingMMTPSection out = aa.getOutgoingMMTPSection(); + IncomingMMTPSection in = bb.getIncomingMMTPSection(); + + if (out == null || in == null) + return false; + String[] inP = in.getProtocols(); + String[] outP = out.getProtocols(); + for (int i=0; i<inP.length; i++) + for (int o=0; o<outP.length; o++) + if (inP[i].equals(outP[o])) + return true; + return false; + } + + /* + * Build a matrix of friends. + * + * (a, b) are friends, if a can send messages to b. + */ + /* + private void buildFriendsMatrix() { + friends = new boolean[useableServers.length][useableServers.length]; + for (int i=0; i<useableServers.length; i++) + for (int j=0; j<useableServers.length; j++) + if (i==j) + friends[i][j] = true; + else + friends[i][j] = areFriends(useableServers[i], useableServers[j]); + } + */ } diff --git a/src/org/noreply/fancydress/directory/IncomingMMTPSection.java b/src/org/noreply/fancydress/directory/IncomingMMTPSection.java index 92e17d7..bf9682f 100644 --- a/src/org/noreply/fancydress/directory/IncomingMMTPSection.java +++ b/src/org/noreply/fancydress/directory/IncomingMMTPSection.java @@ -19,6 +19,11 @@ import java.net.UnknownHostException; * @see ServerDescriptor */ public class IncomingMMTPSection { + /* +ปญญญญญญญ * version we understand. +ปญญญญญญญ */ + private static final String VERSION = "0.1"; + private String name; /* Required */ @@ -26,7 +31,7 @@ public class IncomingMMTPSection { private InetAddress ip; private String hostname; private int port; - private String protocols; + private String[] protocols; /** * Construct an Incoming/MMTP section. @@ -53,7 +58,7 @@ public class IncomingMMTPSection { if (entryVersion == null) throw new Mix3BadServerFormatException("Version not in " + name + " section"); version = entryVersion.getValue(); - if (! version.equals("0.1")) + if (! version.equals(VERSION)) /* We have to ignore unknown Versions */ throw new Mix3BadServerUnrecognizedVersionException("Unrecognized " + name + " Version "+version); @@ -98,7 +103,7 @@ public class IncomingMMTPSection { if (port < 0 || port > 65535) throw new Mix3BadServerFormatException("Port is not in TCP/IP port range in " + name + " section"); - protocols = entryProtocols.getValue(); + protocols = Util.tokenize(entryProtocols.getValue(), ','); } public InetAddress getIP() { @@ -110,4 +115,7 @@ public class IncomingMMTPSection { public int getPort() { return port; } + public String[] getProtocols() { + return protocols; + } } diff --git a/src/org/noreply/fancydress/directory/Server.java b/src/org/noreply/fancydress/directory/Server.java index c4052d9..45ca720 100644 --- a/src/org/noreply/fancydress/directory/Server.java +++ b/src/org/noreply/fancydress/directory/Server.java @@ -35,6 +35,11 @@ public class Server { ArrayList descriptors; /** + * whether or not this node is recommended. + */ + boolean recommended; + + /** * Construct a Server from a given ServerDescriptor. * * @param descriptor a ServerDescriptor. @@ -46,6 +51,51 @@ public class Server { descriptors = new ArrayList(); descriptors.add(descriptor); + recommended = false; + } + + /** + * Set this node recommended. + * + * This is done by the Directory according to the recommended-servers list. + * + * @see Directory + */ + public void setRecommended() { + recommended = true; + } + + /** + * Get whether or not this node recommended. + * + * @return whether or not this node is recommended + */ + public boolean isRecommended() { + return recommended; + } + + /** + * Get whether or not this node is useable. + * + * A server is useable if it understands PacketVersions that we speak, + * has a serverdescriptor that is useable right now, and this SD has + * an Incoming/MMTP section. + * + * @return whether or not this node is useable + */ + public boolean isUseable() { + ServerDescriptor sd; + try { + sd = getDescriptor(); + } catch (Mix3NoServerDescriptorException e) { + return false; + } + String[] pv = sd.getPacketVersions(); /* getPacketVersions only returns packet versions that we understand */ + if (pv.length == 0) + return false; + if (sd.getIncomingMMTPSection() == null) + return false; + return true; } /** @@ -77,11 +127,21 @@ public class Server { } /** + * Get the nickname of this server. + * + * @return nickname + */ + public String getNickname() { + return nickname; + } + + /** * get the the currently valid server descriptor. * * @return current server descriptor + * @throws Mix3NoServerDescriptorException if there is no valid server descriptor */ - public ServerDescriptor getDescriptor() throws Mix3Exception { + public ServerDescriptor getDescriptor() throws Mix3NoServerDescriptorException { ServerDescriptor result = null; Date now = new Date(); @@ -93,7 +153,7 @@ public class Server { result = desc; } if (result == null) - throw new Mix3Exception("No valid server descriptor found."); + throw new Mix3NoServerDescriptorException("No valid server descriptor found."); return result; } } diff --git a/src/org/noreply/fancydress/directory/ServerDescriptor.java b/src/org/noreply/fancydress/directory/ServerDescriptor.java index 020d31b..4c0cba1 100644 --- a/src/org/noreply/fancydress/directory/ServerDescriptor.java +++ b/src/org/noreply/fancydress/directory/ServerDescriptor.java @@ -23,6 +23,11 @@ import java.text.ParseException; * @see Server */ public class ServerDescriptor { + /* +ปญญญญญญญ * descriptor version we understand. +ปญญญญญญญ */ + private static final String DESCRIPTOR_VERSION = "0.2"; + /* Required */ private String descriptorVersion; private String nickname; @@ -46,7 +51,7 @@ public class ServerDescriptor { private String whyInsecure; private IncomingMMTPSection incomingMMTPSection; - private Object outgoingMMTPSection; + private OutgoingMMTPSection outgoingMMTPSection; private DeliveryMBOXSection deliveryMBOXSection; private DeliverySMTPSection deliverySMTPSection; private Object deliveryFragmentedSection; @@ -82,6 +87,11 @@ public class ServerDescriptor { incomingMMTPSection = section == null ? null : new IncomingMMTPSection(section); } catch (Mix3BadServerFormatException e) { System.err.println(e); }; + section = m.getSection("Outgoing/MMTP"); + try { + outgoingMMTPSection = section == null ? null : new OutgoingMMTPSection(section); + } catch (Mix3BadServerFormatException e) { System.err.println(e); }; + section = m.getSection("Delivery/MBOX"); try { deliveryMBOXSection = section == null ? null : new DeliveryMBOXSection(section); @@ -120,46 +130,18 @@ public class ServerDescriptor { * Whitespace is trimmed off each Packet-Version. * * @param s the comma separated list of Packet-Versions - * @param onlySupported only include Packet-Versions that we can handle * @return array of Packet-Versions. */ - public static String[] parsePacketVersions(String s, boolean onlySupported) { + public static String[] parsePacketVersions(String s) { ArrayList versions = new ArrayList(); int indexFrom = 0; int indexOf; - while ((indexOf = s.indexOf(',', indexFrom)) != -1) { - String v = s.substring(indexFrom, indexOf).trim(); - if (!v.equals("") && (Packet.isPacketVersionSupported(v) || !onlySupported )) - versions.add( v ); - indexFrom = indexOf + 1; - } - String v = s.substring(indexFrom).trim(); - if (!v.equals("") && (Packet.isPacketVersionSupported(v) || !onlySupported )) - versions.add( v ); - - String[] result = new String[versions.size()]; - for (int i=0; i<result.length; i++) - result[i] = (String) versions.get(i); - - return result; - } - /** - * Tokenize a Packet-Versions string into single Packet-Versions. - * - * The Packet-Versions is a comma separated list of Packet-Versions. - * This method parses a Packet-Versions value and returns an array of - * Strings, holding one Packet-Version each. - * - * Whitespace is trimmed off each Packet-Version. - * - * Only Packet-Versions that we know to handle are included n the result. - * - * @param s the comma separated list of Packet-Versions - * @return array of Packet-Versions. - */ - public static String[] parsePacketVersions(String s) { - return parsePacketVersions(s, true); + String[] tokens = Util.tokenize(s, ','); + for (int i=0; i<tokens.length; i++) + if (Packet.isPacketVersionSupported(tokens[i])) + versions.add( tokens[i] ); + return (String[]) versions.toArray( new String[versions.size()] ); } /** @@ -173,7 +155,7 @@ public class ServerDescriptor { if (entryDescriptorVersion == null) throw new Mix3BadServerFormatException("Descriptor-Version not in Server Descriptor"); descriptorVersion = entryDescriptorVersion.getValue(); - if (! descriptorVersion.equals("0.2")) + if (! descriptorVersion.equals(DESCRIPTOR_VERSION)) /* We have to ignore unknown Descriptor-Versions */ throw new Mix3BadServerUnrecognizedVersionException("Unrecognized Descriptor-Version "+descriptorVersion); @@ -319,6 +301,15 @@ public class ServerDescriptor { } /** + * Get this server descriptor's supported packet versions. + * + * @return supported packet versions + */ + public String[] getPacketVersions() { + return packetVersions; + } + + /** * Get the date after which this server descriptor is valid. * * @return date after which this server descriptor is valid @@ -354,4 +345,13 @@ public class ServerDescriptor { return incomingMMTPSection; } + /** + * Get the Outgoing/MMTP section. + * + * @return this server descriptor's Outgoing/MMTP section + */ + public OutgoingMMTPSection getOutgoingMMTPSection() { + return outgoingMMTPSection; + } + } diff --git a/src/org/noreply/fancydress/misc/Util.java b/src/org/noreply/fancydress/misc/Util.java index a111b88..d9b46da 100644 --- a/src/org/noreply/fancydress/misc/Util.java +++ b/src/org/noreply/fancydress/misc/Util.java @@ -234,4 +234,30 @@ public class Util { throw new ParseException("Cannot parse boolean expression "+s,0); } } + + /** + * Tokenize comma separated lists into single tokens. + * + * Single tokens are trimmed of whitespace. + * + * @param s string of comma separated items. + * @param separator a token separator + * @return an array of single tokens. + */ + public static String[] tokenize(String s, char separator) { + ArrayList list = new ArrayList(); + int indexFrom = 0; + int indexOf; + + do { + indexOf = s.indexOf(separator, indexFrom); + String v = (indexOf >= 0) ? + s.substring(indexFrom, indexOf).trim() : + s.substring(indexFrom).trim(); + list.add( v ); + indexFrom = indexOf + 1; + } while (indexOf >= 0); + + return (String[]) list.toArray(new String[list.size()]); + } } diff --git a/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java b/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java index 2260934..5bd3e5a 100644 --- a/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java +++ b/src/org/noreply/fancydress/status/Mix3BadDirectorySignatureException.java @@ -2,8 +2,7 @@ package org.noreply.fancydress.status; /** - * The operation failed because the directory was not correctly signed, - * or no signature was found. + * The operation failed because the directory was not correctly signed. */ public class Mix3BadDirectorySignatureException extends Mix3BadSignatureException { public Mix3BadDirectorySignatureException(String s) { diff --git a/src/org/noreply/fancydress/type3/HalfPath.java b/src/org/noreply/fancydress/type3/HalfPath.java index 73dfb84..fdde951 100644 --- a/src/org/noreply/fancydress/type3/HalfPath.java +++ b/src/org/noreply/fancydress/type3/HalfPath.java @@ -9,5 +9,11 @@ public class HalfPath { public Hop[] getHops() { return hops; } + public String asString() { + String result = null; + for (int i=0; i<hops.length; i++) + result = (result == null) ? hops[i].getNickname() : result + "," + hops[i].getNickname(); + return result; + } } diff --git a/src/org/noreply/fancydress/type3/Hop.java b/src/org/noreply/fancydress/type3/Hop.java index 181d2e8..9c61b2d 100644 --- a/src/org/noreply/fancydress/type3/Hop.java +++ b/src/org/noreply/fancydress/type3/Hop.java @@ -3,14 +3,40 @@ package org.noreply.fancydress.type3; import org.noreply.fancydress.type3.routing.*; import org.noreply.fancydress.crypto.*; +import org.noreply.fancydress.status.*; +import org.noreply.fancydress.directory.*; public class Hop { private Routing routing; private RSAPublicKey pubKey; + private String[] packetVersions; + private String nickname; - public Hop(Routing routing, RSAPublicKey pubKey) { + public Hop(Routing routing, RSAPublicKey pubKey, String[] packetVersions, String nickname) { this.routing = routing; this.pubKey = pubKey; + this.packetVersions = packetVersions; + this.nickname = nickname; + } + + public Hop(Server server) throws Mix3PathProblemException { + if (!server.isUseable()) + throw new Mix3PathProblemException("Invalid path: '"+server.getNickname()+"' is not useable"); + ServerDescriptor desc; + try { + desc = server.getDescriptor(); + } catch (Mix3NoServerDescriptorException e) { + throw new Error ("We should have a server descriptor at that point"); + } + IncomingMMTPSection incoming = desc.getIncomingMMTPSection(); + + if (incoming.getHostname() != null) + this.routing = new RoutingHOST(incoming.getHostname(), incoming.getPort(), server.getKeyID()); + else + this.routing = new RoutingIP4(incoming.getIP(), incoming.getPort(), server.getKeyID()); /* FIXME */ + this.pubKey = desc.getPacketKey(); + this.packetVersions = desc.getPacketVersions(); + this.nickname = server.getNickname(); } public Routing getRouting() { @@ -20,4 +46,15 @@ public class Hop { public RSAPublicKey getPubKey() { return pubKey; } + + public boolean supportsPacketVersion(String v) { + for (int i=0; i<packetVersions.length; i++) + if (v.equals(packetVersions[i])) + return true; + return false; + } + + public String getNickname() { + return nickname; + }; } diff --git a/src/org/noreply/fancydress/type3/Message.java b/src/org/noreply/fancydress/type3/Message.java index 4861c85..0925a6c 100644 --- a/src/org/noreply/fancydress/type3/Message.java +++ b/src/org/noreply/fancydress/type3/Message.java @@ -15,9 +15,11 @@ public class Message { public Message(PathSpec path, RoutingDestination destination, String body) throws Mix3Exception { Payload payload = new Payload(destination, body); int numberOfPackets = payload.numPackets(); + Path[] paths = path.getPath(payload); packets = new Packet[numberOfPackets]; for (int i=0; i<numberOfPackets; i++) { - packets[i] = new Packet(path.getPath(), payload.getRoute(i), payload.getPayload(i)); +System.err.println("Path: "+paths[i].asString()); + packets[i] = new Packet(paths[i], payload.getRoute(i), payload.getPayload(i)); } } diff --git a/src/org/noreply/fancydress/type3/Path.java b/src/org/noreply/fancydress/type3/Path.java index 8373990..d9b8950 100644 --- a/src/org/noreply/fancydress/type3/Path.java +++ b/src/org/noreply/fancydress/type3/Path.java @@ -23,5 +23,9 @@ public class Path { public HalfPath getSecondHalf() { return second; } + + public String asString() { + return (first.asString() + ":" + second.asString()); + } } diff --git a/src/org/noreply/fancydress/type3/PathSpec.java b/src/org/noreply/fancydress/type3/PathSpec.java index cbc608f..9e975c6 100644 --- a/src/org/noreply/fancydress/type3/PathSpec.java +++ b/src/org/noreply/fancydress/type3/PathSpec.java @@ -11,53 +11,125 @@ import java.util.*; public class PathSpec { - String pathSpec; - Path singlePath; + Directory dir; + PathComponent[] pathComponents; + boolean singleLeg; - private String[] tokenize(String path) throws Mix3Exception { - ArrayList nicks = new ArrayList(); - int indexFrom = 0; - int indexOf; - - while ((indexOf = path.indexOf(',', indexFrom)) != -1) { - String v = path.substring(indexFrom, indexOf).trim(); - if (v.equals("")) - throw new Mix3Exception("Invalid path."); - nicks.add( v ); - indexFrom = indexOf + 1; + private class PathComponent { + public final static double GAUSS_STD_DEV = 1.5; + + public final static int TYPE_NICKNAME = 1; + public final static int TYPE_RANDOM = 2; + public final static int TYPE_RANDOM_GAUSS = 3; + public final static int TYPE_CROSSOVER = 4; + int type; + Server byNickname; + int numRandoms; + + public PathComponent(Directory dir, String component) throws Mix3BadArgumentsIllegalPathSpecException { + String c = component.trim(); + if (c.equals("")) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: empty path component"); + char b = c.charAt(0); + + Integer integer; + switch (b) { + case '*' : + try { + integer = new Integer(c.substring(1)); + } catch (NumberFormatException e) { + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: illegal path component '"+c+"'"); + } + type = TYPE_RANDOM; + numRandoms = integer.intValue(); + break; + case '~' : + try { + integer = new Integer(c.substring(1)); + } catch (NumberFormatException e) { + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: illegal path component '"+c+"'"); + } + type = TYPE_RANDOM_GAUSS; + numRandoms = integer.intValue(); + break; + case '?' : + if (c.length() > 1) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: illegal path component '"+c+"'"); + type = TYPE_RANDOM; + numRandoms = 1; + break; + case ':' : + if (c.length() > 1) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: illegal path component '"+c+"'"); + type = TYPE_CROSSOVER; + break; + default : + Server server = dir.getServer(c); + if (server == null) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: Nickname '"+c+"' not found"); + if (!server.isUseable()) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: Nickname '"+c+"' is not useable"); + byNickname = server; + type = TYPE_NICKNAME; + break; + } } - String v = path.substring(indexFrom).trim(); - if (v.equals("")) - throw new Mix3Exception("Invalid path."); - nicks.add( v ); - String[] result = new String[nicks.size()]; - for (int i=0; i<result.length; i++) - result[i] = (String) nicks.get(i); + public boolean isCrossover() { + return (type == TYPE_CROSSOVER); + } - return result; + public Server[] getHops() { + Server[] result; + switch (type) { + case TYPE_NICKNAME : + result = new Server[1]; + result[0] = byNickname; + break; + case TYPE_RANDOM : + result = new Server[numRandoms]; + break; + case TYPE_RANDOM_GAUSS : + double d = CryptoPrimitives.normal(numRandoms, GAUSS_STD_DEV); + int num = (int) (d+0.5); + result = new Server[ num<0 ? 0 : num ]; + break; + default : + throw new Error("Unkown type "+type); + } + return result; + } } - private Hop[] parseHalfPath(Directory dir, String path) throws Mix3Exception { - String[] nicks = tokenize(path); - Hop[] hops = new Hop[nicks.length]; - - for (int i=0; i<hops.length; i++) { - Server server = dir.getServer(nicks[i]); - if (server == null) - throw new Mix3Exception("Invalid path: "+nicks[i]+" not found"); - ServerDescriptor desc = server.getDescriptor(); - IncomingMMTPSection incoming = desc.getIncomingMMTPSection(); - Routing routing; - - if (incoming.getHostname() != null) { /* FIXME */ - routing = new RoutingHOST(incoming.getHostname(), incoming.getPort(), server.getKeyID()); - } else { - routing = new RoutingIP4(incoming.getIP(), incoming.getPort(), server.getKeyID()); - } - hops[i] = new Hop(routing, desc.getPacketKey()); + private void splitCrossover(ArrayList tokens, String s) throws Mix3BadArgumentsIllegalPathSpecException { + int indexOf = s.indexOf(':'); + if (indexOf != -1) { + String s1 = s.substring(0, indexOf).trim(); + String s2 = s.substring(indexOf+1).trim(); + if (s1.equals("")) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: Empty hop before crossover point."); + if (s2.equals("")) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: Empty hop after crossover point."); + tokens.add(s1.trim()); + tokens.add(s2.trim()); + } else { + if (s.equals("")) + throw new Mix3BadArgumentsIllegalPathSpecException("Invalid path: Empty hop in path."); + tokens.add(s); } - return hops; + } + + private PathComponent[] parsePath(Directory dir, String path) throws Mix3BadArgumentsIllegalPathSpecException { + ArrayList nicks = new ArrayList(); + String[] tokens = Util.tokenize(path, ','); + for (int i=0; i<tokens.length; i++) + splitCrossover( nicks, tokens[i] ); + + PathComponent[] components = new PathComponent[nicks.size()]; + for (int i=0; i<nicks.size(); i++) + components[i] = new PathComponent(dir, (String) nicks.get(i)); + + return components; } @@ -70,26 +142,212 @@ public class PathSpec { * * @param path given path */ - public PathSpec(Directory dir, String pathSpec, boolean singleLeg) throws Mix3Exception { - this.pathSpec = pathSpec; + public PathSpec(Directory dir, String pathSpec, boolean singleLeg) throws Mix3BadArgumentsIllegalPathSpecException { + this.dir = dir; + this.singleLeg = singleLeg; + + if (singleLeg) { + /* single legs do not have a crossover point */ + int crossover = pathSpec.indexOf(':'); + if (crossover >= 0) + throw new Mix3BadArgumentsIllegalPathSpecException("Path is not a valid path: crossover point specified in single leg path."); + } else { + /* full paths may have up to one specified crossover point */ + int crossover = pathSpec.indexOf(':'); + if (crossover >= 0) + if (pathSpec.indexOf(':', crossover+1) >= 0) + throw new Mix3BadArgumentsIllegalPathSpecException("Path is not a valid path: more than one crossover points specified."); + } + + this.pathComponents = parsePath(dir, pathSpec); + } + + private class ServerWithCrossover { + public Server[] servers; + public int crossoverPoint; + + public Server getLastServer() { + return (servers[servers.length - 1]); + } + + public void setLastServer(Server s) { + servers[servers.length - 1] = s; + } + + public Server getSecondToLastServer() { + return (servers[servers.length - 2]); + } + + private void fillInRandoms() throws Mix3PathProblemException { + Server[] recommended = dir.getRecommendedServers(); + for (int i=servers.length-1; i>=0 ; i--) { + if (servers[i] != null) + continue; + ArrayList s = new ArrayList(); + for (int r=0; r<recommended.length; r++) + if ( + /* last hop or */ + ((i == servers.length-1) || ( + /* next hop is different */ + (recommended[r] != servers[i+1]) && + /* and can talk to next hop */ + dir.areFriends(recommended[r], servers[i+1]) )) && + /* first hop, or previos hop is random, or */ + ((i == 0) || (servers[i-1] == null) || ( + /* previous hop is different */ + (recommended[r] != servers[i-1]) && + /* and prev hop can talk to this */ + dir.areFriends(servers[i-1], recommended[r]) )) + ) + s.add(recommended[r]); + if (s.size() == 0) + throw new Mix3PathProblemException("Cannot find useable servers for random hop."); + servers[i] = (Server) s.get(CryptoPrimitives.randInt(s.size())); + } + } + + public Path getPath() throws Mix3PathProblemException { + fillInRandoms(); + Hop[] hops = new Hop[servers.length]; + for (int i=0; i<hops.length ; i++) + hops[i] = new Hop(servers[i]); + + if (crossoverPoint == -1) + crossoverPoint = (hops.length+1)/2; + int len1 = crossoverPoint; + int len2 = hops.length - len1; + Hop[] l1 = new Hop[len1]; + Hop[] l2 = new Hop[len2]; + System.arraycopy(hops, 0, l1, 0, len1); + System.arraycopy(hops, len1, l2, 0, len2); + return new Path(l1, l2); + } + } + + /** + * Concat path components, getting random amount of hops where requested. + */ + private ServerWithCrossover concatComponents() throws Mix3PathProblemException { + Server[][] components = new Server[pathComponents.length][]; + int length = 0; + int crossoverBefore = -1; + for (int i=0; i<pathComponents.length ; i++) { + if (pathComponents[i].isCrossover()) + crossoverBefore = length; + else { + components[i] = pathComponents[i].getHops(); + length += components[i].length; + } + } + Server[] servers = new Server[length]; + int c = 0; + for (int i=0; i<pathComponents.length ; i++) { + if (components[i] != null) { + System.arraycopy(components[i], 0, servers, c, components[i].length); + c += components[i].length; + } + } + if (c != length) + throw new Error ("Did not fill in length hops ("+c+" vs "+length+")"); + + ServerWithCrossover result = new ServerWithCrossover(); + result.servers = servers; + result.crossoverPoint = crossoverBefore; + return result; + } + + /** + * get one instance of this path spec. + * + * Also we filter acceptable exit nodes according to the payload's + * constraints. + * + * @return one path constructed from the PathSpec + */ + /* + private Object getPathInstance() throws Mix3PathProblemException { - int crossover = pathSpec.indexOf(':'); - if (crossover < 0) - throw new Mix3Exception("Path is not a valid path: no crossover point specified."); + ServerWithCrossover path = concatComponents(); + fillInRandoms(path.components); - Hop[] leg1 = parseHalfPath(dir, pathSpec.substring(0, crossover)); - Hop[] leg2 = parseHalfPath(dir, pathSpec.substring(crossover+1)); - singlePath = new Path(leg1, leg2); + if (singleLeg) + throw new Error("not implemented yet"); // FIXME + else + return path.getPath(); } + */ /** - * Return a path constructed from this PathSpec + * Return paths constructed from this PathSpec. + * + * Build paths from this pathspec that are able to deliver the payload + * in <code>payload</code>. * - * @param dir a directory - * @return path + * We build as many paths as <code>payload.numPackets()</code> requires. + * + * Also we filter acceptable exit nodes according to the payload's + * constraints. + * + * @param payload the payload + * @return paths constructed from the PathSpec */ - public Path getPath() throws Mix3Exception { - return singlePath; + public Path[] getPath(Payload payload) throws Mix3PathProblemException { + if (singleLeg) + throw new IllegalArgumentException("getPath() may not be called for single leg path specs."); + + + ServerWithCrossover[] paths = new ServerWithCrossover[payload.numPackets()]; + for (int i=0; i<paths.length; i++) { + paths[i] = concatComponents(); + if (paths[i].servers.length < 2) + throw new Mix3PathProblemException("Path too short."); + } + + Server lastHop = paths[0].getLastServer(); + for (int i=1; i<paths.length; i++) + if (lastHop != paths[i].getLastServer()) + throw new Mix3PathProblemException("Exit hop must be the same in all paths (random or fixed)"); + + Server[] possibleExitHops = payload.filterExithops( dir.getRecommendedServers() ); + if (lastHop == null) { + boolean[] unuseable = new boolean[possibleExitHops.length]; + for (int i=0; i<paths.length; i++) { + Server stls = paths[i].getSecondToLastServer(); + if (stls == null) + continue; + for (int j=0; j<unuseable.length; j++) + if (unuseable[j]) + continue; + else + if (! dir.areFriends(stls, possibleExitHops[j] )) + unuseable[j] = true; + } + ArrayList list = new ArrayList(); + for (int j=0; j<unuseable.length; j++) + if (! unuseable[j]) + list.add(possibleExitHops[j]); + + if (list.size() == 0) + throw new Mix3PathProblemException("No useable exit hops found"); + + lastHop = (Server) list.get( CryptoPrimitives.randInt(list.size()) ); + for (int i=0; i<paths.length; i++) + paths[i].setLastServer(lastHop); + } else { + boolean exitHopOK = false; + for (int j=0; j<possibleExitHops.length; j++) + if (possibleExitHops[j] == lastHop) { + exitHopOK = true; + break; + } + if (! exitHopOK) + throw new Mix3PathProblemException("Exit hop "+lastHop.getNickname()+" does not meet constraints."); + } + + Path[] result = new Path[paths.length]; + for (int i=0; i<paths.length; i++) + result[i] = paths[i].getPath(); + + return result; } } - diff --git a/src/org/noreply/fancydress/type3/Payload.java b/src/org/noreply/fancydress/type3/Payload.java index 73027cd..0294537 100644 --- a/src/org/noreply/fancydress/type3/Payload.java +++ b/src/org/noreply/fancydress/type3/Payload.java @@ -4,6 +4,7 @@ package org.noreply.fancydress.type3; import java.io.*; import java.util.zip.*; import org.noreply.fancydress.crypto.*; +import org.noreply.fancydress.directory.*; import org.noreply.fancydress.misc.Util; import org.noreply.fancydress.type3.routing.*; @@ -77,5 +78,9 @@ public class Payload { public RoutingDestination getRoute(int i) { return route[i]; } + + public Server[] filterExithops(Server[] s) { + return s; + } } diff --git a/src/org/noreply/fancydress/type3/mmtp/MMTP.java b/src/org/noreply/fancydress/type3/mmtp/MMTP.java index 63f1f6f..3695c52 100644 --- a/src/org/noreply/fancydress/type3/mmtp/MMTP.java +++ b/src/org/noreply/fancydress/type3/mmtp/MMTP.java @@ -29,7 +29,9 @@ public class MMTP { context.init(null, trustManagers, null); SSLSocketFactory socketFactory = context.getSocketFactory(); - SSLSocket socket = (SSLSocket) socketFactory.createSocket("127.0.0.1", 48099); + RoutingForward route = packet.getRoute(); +System.err.println("Connecting to "+route.getHostname()+":"+route.getPort()); + SSLSocket socket = (SSLSocket) socketFactory.createSocket(route.getHostname(), route.getPort()); String[] supportedProtocols = socket.getSupportedProtocols(); @@ -49,11 +51,13 @@ public class MMTP { socket.setEnabledProtocols( new String[] { TLS_PROTO } ); socket.setEnabledCipherSuites( new String[] { TLS_DHE_RSA_WITH_AES_128_CBC_SHA } ); acceptableFound = true; +System.err.println("Using TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); break; } else if (haveSSLv3 && supportedCipherSuits[i].equals(SSL3_RSA_DES_192_CBC3_SHA)) { socket.setEnabledProtocols( new String[] { SSL_PROTO } ); socket.setEnabledCipherSuites( new String[] { SSL3_RSA_DES_192_CBC3_SHA } ); acceptableFound = true; +System.err.println("Using SSL3_RSA_DES_192_CBC3_SHA"); break; } } diff --git a/src/org/noreply/fancydress/type3/routing/RoutingForward.java b/src/org/noreply/fancydress/type3/routing/RoutingForward.java index 183696a..7c05eed 100644 --- a/src/org/noreply/fancydress/type3/routing/RoutingForward.java +++ b/src/org/noreply/fancydress/type3/routing/RoutingForward.java @@ -38,5 +38,18 @@ public abstract class RoutingForward extends Routing { public byte[] getKeyID() { return keyid; } + + /** + * Get the hostname needed for connectig to the node. + * + * @return hostname. + */ + public abstract String getHostname(); + /** + * Get the port needed for connectig to the node. + * + * @return port + */ + public abstract int getPort(); } diff --git a/src/org/noreply/fancydress/type3/routing/RoutingHOST.java b/src/org/noreply/fancydress/type3/routing/RoutingHOST.java index 6138c2e..be568b4 100644 --- a/src/org/noreply/fancydress/type3/routing/RoutingHOST.java +++ b/src/org/noreply/fancydress/type3/routing/RoutingHOST.java @@ -94,5 +94,21 @@ public class RoutingHOST extends RoutingForward { return result; } + /** + * Get the hostname needed for connectig to the node. + * + * @return hostname. + */ + public String getHostname() { + return hostname; + } + /** + * Get the port needed for connectig to the node. + * + * @return port + */ + public int getPort() { + return port; + } } diff --git a/src/org/noreply/fancydress/type3/routing/RoutingIP4.java b/src/org/noreply/fancydress/type3/routing/RoutingIP4.java index e0527a0..9db61dd 100644 --- a/src/org/noreply/fancydress/type3/routing/RoutingIP4.java +++ b/src/org/noreply/fancydress/type3/routing/RoutingIP4.java @@ -102,5 +102,21 @@ public class RoutingIP4 extends RoutingForward { return result; } + /** + * Get the hostname needed for connectig to the node. + * + * @return hostname. + */ + public String getHostname() { + return ip.getHostAddress(); + } + /** + * Get the port needed for connectig to the node. + * + * @return port + */ + public int getPort() { + return port; + } } |