[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Integrate and new path selection logic; rename FWD to F...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv15175/src/minion/lib/mixminion
Modified Files:
BuildMessage.py ClientDirectory.py ClientMain.py
ClientUtils.py Config.py Packet.py ServerInfo.py test.py
Log Message:
Integrate and new path selection logic; rename FWD to FWD_IPv4; start
work on reverse directory lookup; clean dead code from directory; add
new MMTPHostInfo routing type; have ServerInfo decide whom it can
relay to and how.
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.58
retrieving revision 1.59
diff -u -d -r1.58 -r1.59
--- BuildMessage.py 31 Aug 2003 19:29:29 -0000 1.58
+++ BuildMessage.py 9 Oct 2003 15:26:15 -0000 1.59
@@ -286,7 +286,7 @@
paddingPRNG=Crypto.getCommonPRNG())
return ReplyBlock(header, expiryTime,
- SWAP_FWD_TYPE,
+ SWAP_FWD_IPV4_TYPE,
path[0].getRoutingInfo().pack(), sharedKey), secrets, tag
# Maybe we shouldn't even allow this to be called with userKey==None.
@@ -334,7 +334,7 @@
err = 0 # 0: no error. 1: 1st leg too big. 2: 1st leg okay, 2nd too big.
if path1 is not None:
try:
- _getRouting(path1, SWAP_FWD_TYPE, path2[0].getRoutingInfo().pack())
+ _getRouting(path1, SWAP_FWD_IPV4_TYPE, path2[0].getRoutingInfo().pack())
except MixError:
err = 1
# Add a dummy tag as needed to last exitinfo.
@@ -537,7 +537,7 @@
path1exittype = reply.routingType
path1exitinfo = reply.routingInfo
else:
- path1exittype = SWAP_FWD_TYPE
+ path1exittype = SWAP_FWD_IPV4_TYPE
path1exitinfo = path2[0].getRoutingInfo().pack()
# Generate secrets for path1.
@@ -726,7 +726,7 @@
Raises MixError if the routing info is too big to fit into a single
header. """
# Construct a list 'routing' of exitType, exitInfo.
- routing = [ (FWD_TYPE, node.getRoutingInfo().pack()) for
+ routing = [ (FWD_IPV4_TYPE, node.getRoutingInfo().pack()) for
node in path[1:] ]
routing.append((exitType, exitInfo))
Index: ClientDirectory.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientDirectory.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- ClientDirectory.py 7 Oct 2003 21:57:46 -0000 1.3
+++ ClientDirectory.py 9 Oct 2003 15:26:15 -0000 1.4
@@ -8,7 +8,7 @@
DOCDOC
"""
-__all__ = [ 'ClientDirectory', 'parsePath', 'parsePathLeg', 'parseAddress2' ]
+__all__ = [ 'ClientDirectory', 'parsePath', 'parseAddress' ]
import cPickle
import errno
@@ -25,13 +25,15 @@
import mixminion.ClientMain #XXXX
import mixminion.Config
import mixminion.Crypto
+import mixminion.Packet
import mixminion.ServerInfo
from mixminion.Common import LOG, MixError, MixFatalError, UIError, \
ceilDiv, createPrivateDir, formatDate, formatFnameTime, openUnique, \
previousMidnight, readPickled, readPossiblyGzippedFile, \
- replaceFile, tryUnlink, writePickled, floorDiv
-from mixminion.Packet import MBOX_TYPE, SMTP_TYPE, DROP_TYPE
+ replaceFile, tryUnlink, writePickled, floorDiv, isSMTPMailbox
+from mixminion.Packet import MBOX_TYPE, SMTP_TYPE, DROP_TYPE, FRAGMENT_TYPE, \
+ parseMBOXInfo, parseSMTPInfo, ParseError
# FFFF This should be made configurable and adjustable.
MIXMINION_DIRECTORY_URL = "http://mixminion.net/directory/Directory.gz"
@@ -52,6 +54,7 @@
# digestMap: Map of (Digest -> 'D'|'D-'|'I:filename').
# byNickname: Map from nickname.lower() to list of (ServerInfo, source)
# tuples.
+ # byKeyID: Map from desc.getKeyID() to list of ServerInfo.
# byCapability: Map from capability ('mbox'/'smtp'/'relay'/None) to
# list of (ServerInfo, source) tuples.
# allServers: Same as byCapability[None]
@@ -346,10 +349,12 @@
return n
def __rebuildTables(self):
- """Helper method. Reconstruct byNickname, allServers, and byCapability
- from the internal start of this object.
- """
+
+ """Helper method. Reconstruct byNickname, byKeyID,
+ allServers, and byCapability from the internal start of
+ this object. """
self.byNickname = {}
+ self.byKeyID = {}
self.allServers = []
self.byCapability = { 'mbox': [],
'smtp': [],
@@ -360,7 +365,9 @@
for info, where in self.serverList:
nn = info.getNickname().lower()
- lists = [ self.allServers, self.byNickname.setdefault(nn, []) ]
+ keyid = info.getKeyID()
+ lists = [ self.allServers, self.byNickname.setdefault(nn, []),
+ self.byKeyID.setdefault(info.getKeyID(), []) ]
for c in info.getCaps():
lists.append( self.byCapability[c] )
for lst in lists:
@@ -373,7 +380,6 @@
continue
self.byNickname.setdefault(nn, []).append((info, where))
-
def listServers(self):
"""Returns a linewise listing of the current servers and their caps.
This will go away or get refactored in future versions once we
@@ -403,18 +409,6 @@
lines.append(line)
return lines
- def __findOne(self, lst, startAt, endAt):
- """Helper method. Given a list of (ServerInfo, where), return a
- single element that is valid for all time between startAt and
- endAt.
-
- Watch out: this element is _not_ randomly chosen.
- """
- res = self.__find(lst, startAt, endAt)
- if res:
- return res[0]
- return None
-
def __find(self, lst, startAt, endAt):
"""Helper method. Given a list of (ServerInfo, where), return all
elements that are valid for all time between startAt and endAt.
@@ -439,6 +433,28 @@
return u.values()
+ def getNicknameByKeyID(self, keyid):
+ s = self.bykeyID.get(keyid)
+ if not s:
+ return None
+ r = []
+ for d in s:
+ if d.getNickname().lower() not in r:
+ r.append(d.getNickname())
+ return "/".join(r)
+
+ def getNameByRelay(self, routingType, routingInfo):
+ assert routingType in (mixminion.Packet.FWD_IPV4_TYPE,
+ mixminion.Packet.SWAP_FWD_IPV4_TYPE)
+ if type(routingInfo) == types.StringType:
+ routingInfo = mixminion.Packet.parseIPV4Info(routingInfo)
+ assert isinstance(routingInfo, mixminion.Packet.IPV4Info)
+ nn = self.getNicknameByKeyID(self, routingInfo.keyinfo)
+ if nn is None:
+ return "%s:%s"%(routingInfo.ip, routingInfo.port)
+ else:
+ return nn
+
def getLiveServers(self, startAt=None, endAt=None):
"""DOCDOC"""
if startAt is None:
@@ -447,24 +463,6 @@
endAt = time.time()+self.DEFAULT_REQUIRED_LIFETIME
return self.__find(self.serverList, startAt, endAt)
- def findByExitTypeAndSize(self, exitType, size, nPackets):
- #XXXX006 remove this method. It's not really a good interface,
- #XXXX006 and only gets used by the kludgy choose-a-new-last-hop logic
- """Return a server that supports exitType 'exittype' (currently must be
- SMTP_TYPE), and messages of size 'size' bytes."""
- assert exitType == SMTP_TYPE
- servers = self.__find(self.byCapability['smtp'], time.time(),
- time.time()+24*60*60)
- servers = servers[:]
- mixminion.Crypto.getCommonPRNG().shuffle(servers)
- for s in servers:
- maxSize = s['Delivery/SMTP']['Maximum-Size'] * 1024
- maxPackets = s['Delivery/Fragmented'].get('Maximum-Fragments',1)
- if maxSize >= size and maxPackets >= nPackets:
- return s
-
- return None
-
def clean(self, now=None):
"""Remove all expired or superseded descriptors from DIR/servers."""
@@ -524,13 +522,14 @@
LOG.error("Server is not currently valid")
elif self.byNickname.has_key(name.lower()):
# If it's a nickname, return a serverinfo with that name.
- s = self.__findOne(self.byNickname[name.lower()], startAt, endAt)
+ s = self.__find(self.byNickname[name.lower()], startAt, endAt)
if not s:
raise UIError(
"Couldn't find any currently live descriptor with name %s"
% name)
+ s = s[0]
if not self.goodServerNicknames.has_key(s.getNickname().lower()):
LOG.warn("Server %s is not recommended",name)
@@ -552,7 +551,8 @@
else:
return None
- def generatePaths(self, nPaths, pathSpec, exitAddress, startAt=None, endAt=None,
+ def generatePaths(self, nPaths, pathSpec, exitAddress,
+ startAt=None, endAt=None,
prng=None):
"""Return a list of pairs of lists of serverinfo DOCDOC."""
@@ -567,7 +567,7 @@
plausibleExits = exitAddress.getExitServers(self,startAt,endAt)
if exitAddress.isSSFragmented:
# We _must_ have a single common last hop.
- plauxibleExits = [ prng.pick(plausibleExits) ]
+ plausibleExits = [ prng.pick(plausibleExits) ]
for _ in xrange(nPaths):
p1 = []
@@ -590,12 +590,20 @@
else:
n1 = len(p1)
- path = self.getPath(None, p, startAt=startAt, endAt=endAt)
- paths.append( (path[:n1], path[n1:]) )
-
+ path = self.getPath(p, startAt=startAt, endAt=endAt)
+ path1,path2 = path[:n1], path[n1:]
+ paths.append( (path1,path2) )
+ if exitAddress.isReply or exitAddress.isSURB:
+ LOG.info("Selected path is %s",
+ ",".join([s.getNickname() for s in path]))
+ else:
+ LOG.info("Selected path is %s:%s",
+ ",".join([s.getNickname() for s in path1]),
+ ",".join([s.getNickname() for s in path2]))
+
return paths
- def getPath(self, endCap, template, startAt=None, endAt=None, prng=None):
+ def getPath(self, template, startAt=None, endAt=None, prng=None):
"""Workhorse method for path selection. Given a template, and
a capability that must be supported by the exit node, return
a list of serverinfos that 'matches' the template, and whose
@@ -606,9 +614,7 @@
getPath should select a corresponding server.
All servers are chosen to be valid continuously from
- startAt to endAt. The last server is not set) is selected
- to have 'endCap' (typically 'mbox' or 'smtp'). Set endCap
- to 'None' if you don't care.
+ startAt to endAt.
The path selection algorithm perfers to choose without
replacement it it can.
@@ -619,8 +625,9 @@
considered equivalent if their nicknames are the same,
ignoring case.
"""
- n = [ inf.getNickname().lower() for inf in s2 ]
- return [ inf for inf in s1 if inf.getNickname().lower() not in n]
+ n = [ inf.getNickname().lower() for inf in s2 if inf is not None ]
+ return [ inf for inf in s1
+ if inf is not None and inf.getNickname().lower() not in n]
# Fill in startAt, endAt, prng if not provided
if startAt is None:
@@ -638,33 +645,7 @@
else:
servers.append(self.getServerInfo(name, startAt, endAt, 1))
- # If we need to pick the last server, pick it first.
- if servers[-1] is None:
- # Who has the required exit capability....
- endCandidates = self.__find(self.byCapability[endCap],
- startAt,endAt)
- if not endCandidates:
- raise UIError("Can't build path: no %s servers known"%endCap)
- # ....that we haven't used yet?
- used = filter(None, servers)
- unusedEndCandidates = setSub(endCandidates, used)
- if unusedEndCandidates:
- # Somebody with the capability is unused
- endCandidates = unusedEndCandidates
- elif len(endCandidates) > 1 and servers[-2] is not None:
- # We can at least avoid of picking someone with the
- # capability who isn't the penultimate node.
- penultimate = servers[-2].getNickname().lower()
- endCandidates = setSub(endCandidates, [penultimate])
- else:
- # We're on our own.
- assert len(endCandidates)
-
- # Finally, fill in the last server.
- servers[-1] = prng.pick(endCandidates)
-
# Now figure out which relays we haven't used yet.
- used = filter(None, servers)
relays = self.__find(self.byCapability['relay'], startAt, endAt)
if not relays:
raise UIError("No relays known")
@@ -679,37 +660,78 @@
continue
# Find the servers adjacent to it, if any...
if i>0:
- abutters = filter(None,[ servers[i-1], servers[i+1]])
+ prev = servers[i-1]
else:
- abutters = filter(None,[ servers[i+1] ])
- # ...and see if there are any relays left that aren't adjacent.
- candidates = setSub(relays, abutters)
+ prev = None
+ if i+1<len(servers):
+ next = servers[i+1]
+ else:
+ next = None
+ # ...and see if there are any relays left that aren't adjacent?
+ candidates = []
+ for c in relays:
+ if ((prev and c.hasSameNicknameAs(prev)) or
+ (next and c.hasSameNicknameAs(next)) or
+ (prev and not prev.canRelayTo(c)) or
+ (next and not c.canRelayTo(next))):
+ continue
+ candidates.append(c)
if candidates:
# Good. There are.
servers[i] = prng.pick(candidates)
else:
- # Nope. Choose a random relay.
- servers[i] = prng.pick(relays)
+ # Nope. Can we duplicate a relay?
+ LOG.warn("Repeating a relay because of routing restrictions.")
+ if prev and next:
+ if prev.canRelayTo(next):
+ servers[i] = prev
+ elif next.canRelayTo(next):
+ servers[i] = next
+ else:
+ raise UIError("Can't generate path %s->???->%s"%(
+ prev.getNickname(),next.getNickname()))
+ elif prev and not next:
+ servers[i] = prev
+ elif next and not prev:
+ servers[i] = next
+ else:
+ raise UIError("No servers known.")
# FFFF We need to make sure that the path isn't totally junky.
return servers
- def validatePath2(self, pathSpec, exitAddress, startAt=None, endAt=None):
+ def validatePath(self, pathSpec, exitAddress, startAt=None, endAt=None):
"""DOCDOC
takes pathspec; raises UIError or does nothing."""
if startAt is None: startAt = time.time()
if endAt is None: endAt = startAt+self.DEFAULT_REQUIRED_LIFETIME
p = pathSpec.path1+pathSpec.path2
+ # Make sure all elements are valid.
for e in p:
e.validate(self, startAt, endAt)
+
+ # When there are 2 elements in a row, make sure each can route to
+ # the next.
+ prevFixed = None
+ for e in p:
+ fixed = e.getFixedServer(self, startAt, endAt)
+ if prevFixed and fixed and not prevFixed.canRelayTo(fixed):
+ raise UIError("Server %s can't relay to %s",
+ prevFixed.getNickname(), fixed.getNickname())
+ prevFixed = fixed
+
fs = p[-1].getFixedServer(self,startAt,endAt)
lh = exitAddress.getLastHop()
if lh is not None:
- fs = self.getServerInfo(lh, startAt, endAt)
- if fs is None:
+ lh_s = self.getServerInfo(lh, startAt, endAt)
+ if lh_s is None:
raise UIError("No known server descriptor named %s",lh)
+ if fs and not fs.canRelayTo(lh_s):
+ raise UIError("Server %s can't relay to %s",
+ fs.getNickname(), lh)
+ fs = lh_s
if fs is not None:
exitAddress.checkSupportedByServer(fs)
elif exitAddress.isServerRelative():
@@ -748,244 +770,6 @@
LOG.warn("This software is newer than any version "
"on the recommended list.")
-def parsePath(directory, config, path, address, nHops=None,
- startAt=None, endAt=None, halfPath=0,
- defaultNHops=None):
- """Wrap new path parsing methods to provide functionality of old parsePath
- method. The old methods has been (for now) renamed to 'parsePathOrig'.
- """
- isReply = halfPath and (address is None)
- isSURB = halfPath and (address is not None)
- if not isReply:
- rt, ri, lastHop = address.getRouting()
- exitAddress = ExitAddress(rt, ri, lastHop)
- else:
- exitAddress = ExitAddress(isReply=1)
- pathSpec = parsePath2(config, path, nHops=nHops, isReply=isReply,
- isSURB=isSURB, defaultNHops=defaultNHops)
- directory.validatePath2(pathSpec, exitAddress, startAt=startAt,endAt=endAt)
- paths = directory.generatePaths(1, pathSpec, exitAddress, startAt,endAt)
- assert len(paths) == 1
- return paths[0]
-
-def parsePathOrig(directory, config, path, address, nHops=None,
- startAt=None, endAt=None, halfPath=0,
- defaultNHops=None):
- """Resolve a path as specified on the command line. Returns a
- (path-leg-1, path-leg-2) tuple, where each leg is a list of ServerInfo.
-
- directory -- the ClientDirectory to use.
- config -- unused for now.
- path -- the path, in a format described below. If the path is
- None, all servers are chosen as if the path were '*'.
- address -- the address to deliver the message to; if it specifies
- an exit node, the exit node is appended to the second leg of the
- path and does not count against the number of hops. If 'address'
- is None, the exit node must support relay.
- nHops -- the number of hops to use. Defaults to defaultNHops.
- startAt/endAt -- A time range during which all servers must be valid.
- halfPath -- If true, we generate only the second leg of the path
- and leave the first leg empty.
- defaultNHops -- The default path length to use when we encounter a
- wildcard in the path. Defaults to 6.
-
- Paths are ordinarily comma-separated lists of server nicknames or
- server descriptor filenames, as in:
- 'foo,bar,./descriptors/baz,quux'.
-
- You can use a colon as a separator to divides the first leg of the path
- from the second:
- 'foo,bar:baz,quux'.
- If nSwap and a colon are both used, they must match, or MixError is
- raised.
-
- You can use a question mark to indicate a randomly chosen server:
- 'foo,bar,?,quux,?'.
- As an abbreviation, you can use star followed by a number to indicate
- that number of randomly chosen servers:
- 'foo,bar,*2,quux'.
- You can use a star without a number to specify a fill point
- where randomly-selected servers will be added:
- 'foo,bar,*,quux'.
- Finally, you can use a tilde followed by a number to specify an
- approximate number of servers to add. (The actual number will be
- chosen randomly, according to a normal distribution with standard
- deviation 1.5):
- 'foo,bar,~2,quux'
-
- The nHops argument must be consistent with the path, if both are
- specified. Specifically, if nHops is used _without_ a star on the
- path, nHops must equal the path length; and if nHops is used _with_ a
- star on the path, nHops must be >= the path length.
- """
- if not path:
- path = '*'
- # Break path into a list of entries of the form:
- # Nickname
- # or "<swap>"
- # or "?"
- p = []
- while path:
- if path[0] == "'":
- m = re.match(r"'([^']+|\\')*'", path)
- if not m:
- raise UIError("Mismatched quotes in path.")
- p.append(m.group(1).replace("\\'", "'"))
- path = path[m.end():]
- if path and path[0] not in ":,":
- raise UIError("Invalid quotes in path.")
- elif path[0] == '"':
- m = re.match(r'"([^"]+|\\")*"', path)
- if not m:
- raise UIError("Mismatched quotes in path.")
- p.append(m.group(1).replace('\\"', '"'))
- path = path[m.end():]
- if path and path[0] not in ":,":
- raise UIError("Invalid quotes in path.")
- else:
- m = re.match(r"[^,:]+",path)
- if not m:
- raise UIError("Invalid path")
- p.append(m.group(0))
- path = path[m.end():]
- if not path:
- break
- elif path[0] == ',':
- path = path[1:]
- elif path[0] == ':':
- path = path[1:]
- p.append("<swap>")
-
- path = []
- for ent in p:
- if re.match(r'\*(\d+)', ent):
- path.extend(["?"]*int(ent[1:]))
- elif re.match(r'\~(\d+)', ent):
- avg = int(ent[1:])
- n = int(mixminion.Crypto.getCommonPRNG().getNormal(avg, 1.5)+0.5)
- if n < 0: n = 0
- path.extend(['?']*n)
- else:
- path.append(ent)
-
- # set explicitSwap to true iff the user specified a swap point.
- explicitSwap = path.count("<swap>")
- # set colonPos to the index of the explicit swap point, if any.
- if path.count("<swap>") > 1:
- raise UIError("Can't specify swap point twice")
-
- # set starPos to the index of the var-length wildcard, if any.
- if path.count("*") > 1:
- raise UIError("Can't have two variable-length wildcards in a path")
- elif path.count("*") == 1:
- starPos = path.index("*")
- else:
- starPos = None
-
- # If there's a variable-length wildcard...
- if starPos is not None:
- # Find out how many hops we should have.
- myNHops = nHops or defaultNHops or 6
- # Figure out how many nodes we need to add.
- haveHops = len(path) - 1
- # A colon will throw the count off.
- if explicitSwap:
- haveHops -= 1
- path[starPos:starPos+1] = ["?"]*max(0,myNHops-haveHops)
-
- # Figure out how long the first leg should be.
- if explicitSwap:
- # Calculate colon position
- colonPos = path.index("<swap>")
- if halfPath:
- raise UIError("Can't specify swap point with replies")
- firstLegLen = colonPos
- del path[colonPos]
- elif halfPath:
- firstLegLen = 0
- else:
- firstLegLen = ceilDiv(len(path), 2)
-
- # Do we have the right # of hops?
- if nHops is not None and len(path) != nHops:
- raise UIError("Mismatch between specified path lengths")
-
- # Replace all '?'s in path with [None].
- for i in xrange(len(path)):
- if path[i] == '?': path[i] = None
-
- # Figure out what capability we need in our exit node, so that
- # we can tell the directory.
- if address is None:
- rt, ri, exitNode = None, None, None
- exitCap = 'relay'
- else:
- rt, ri, exitNode = address.getRouting()
- if rt == MBOX_TYPE:
- exitCap = 'mbox'
- elif rt == SMTP_TYPE:
- exitCap = 'smtp'
- else:
- exitCap = None
-
- # If we have an explicit exit node from the address, append it.
- if exitNode is not None:
- path.append(exitNode)
-
- # Get a list of serverinfo.
- path = directory.getPath(endCap=exitCap,
- template=path, startAt=startAt, endAt=endAt)
-
- # Now sanity-check the servers.
-
- # Make sure all relay servers support relaying.
- for server in path[:-1]:
- if "relay" not in server.getCaps():
- raise UIError("Server %s does not support relay"
- % server.getNickname())
-
- # Make sure the exit server can support the exit capability.
- if exitCap and exitCap not in path[-1].getCaps():
- raise UIError("Server %s does not support %s capability"
- % (path[-1].getNickname(), exitCap))
-
-
- # Split the path into 2 legs.
- path1, path2 = path[:firstLegLen], path[firstLegLen:]
- if not halfPath and len(path1)+len(path2) < 2:
- raise UIError("Path is too short")
- if not halfPath and (not path1 or not path2):
- raise UIError("Each leg of the path must have at least 1 hop")
-
- # Make sure the path can fit into the headers.
- mixminion.BuildMessage.checkPathLength(path1, path2,
- rt,ri,
- explicitSwap)
-
- # Return the two legs of the path.
- return path1, path2
-
-def parsePathLeg(directory, config, path, nHops, address=None,
- startAt=None, endAt=None, defaultNHops=None):
- """Parse a single leg of a path. Used for generating SURBs (second leg
- only) or reply messages (first leg only). Returns a list of
- ServerInfo.
-
- directory -- the ClientDirectory to use.
- config -- unused for now.
- path -- The path, as described in parsePath, except that ':' is not
- allowed.
- nHops -- the number of hops to use. Defaults to defaultNHops.
- startAt/endAt -- A time range during which all servers must be valid.
- defaultNHops -- The default path length to use when we encounter a
- wildcard in the path. Defaults to 6.
- """
- path1, path2 = parsePath(directory, config, path, address, nHops,
- startAt=startAt, endAt=endAt, halfPath=1,
- defaultNHops=defaultNHops)
- assert path1 == []
- return path2
-
#----------------------------------------------------------------------
KNOWN_STRING_EXIT_TYPES = [
@@ -995,7 +779,7 @@
class ExitAddress:
#FFFF Perhaps this crams too much into ExitAddress.
def __init__(self,exitType=None,exitAddress=None,lastHop=None,isReply=0,
- isSSFragmented=0):
+ isSURB=0,isSSFragmented=0):
if isReply:
assert exitType is None
assert exitAddress is None
@@ -1014,18 +798,22 @@
self.exitAddress = exitAddress
self.lastHop = lastHop
self.isReply = isReply
+ self.isSURB = isSURB
self.isSSFragmented = isSSFragmented #server-side frag reassembly only.
self.nFragments = self.exitSize = 0
self.headers = {}
+ def getFragmentedMessagePrefix(self):
+ routingType, routingInfo, _ = self.getRouting()
+ return mixminion.Packet.ServerSideFragmentedMessage(
+ routingType, routingInfo, "").pack()
+
def setFragmented(self, isSSFragmented, nFragments):
- self.isSSFragmented = isFragmented
+ self.isSSFragmented = isSSFragmented
self.nFragments = nFragments
def setExitSize(self, exitSize):
self.exitSize = exitSize
def setHeaders(self, headers):
self.headers = headers
- def isReply(self):
- return self.isReply
def getLastHop(self):
return self.lastHop
def isSupportedByServer(self, desc):
@@ -1038,6 +826,14 @@
if self.isReply:
return
nickname = desc.getNickname()
+
+ if self.headers:
+ #XXXX006 remove this eventually.
+ sware = desc['Server'].get("Software")
+ if (sware.startswith("Mixminion 0.0.4") or
+ sware.startswith("Mixminion 0.0.5alpha1")):
+ raise UIError("Server %s is running old software that doesn't support exit headers.", nickname)
+
if self.isSSFragmented:
dfsec = desc['Delivery/Fragmented']
if not dfsec.get("Version"):
@@ -1076,9 +872,9 @@
def getPrettyExitType(self):
if type(self.exitType) == types.IntType:
- prettyExit = "0x%04X"%self.exitType
+ return "0x%04X"%self.exitType
else:
- prettyExit = `self.exitType`
+ return self.exitType
def isServerRelative(self):
return self.exitType in ('mbox', MBOX_TYPE)
@@ -1092,12 +888,24 @@
if self.isSupportedByServer(desc) ]
return result
- def getRouting(self, desc):
+ def getRouting(self):
"""DOCDOC"""
- #XXXX
- assert 0
+ ri = self.exitAddress
+ if self.isSSFragmented:
+ rt = FRAGMENT_TYPE
+ ri = ""
+ elif self.exitType == 'smtp':
+ rt = SMTP_TYPE
+ elif self.exitType == 'drop':
+ rt = DROP_TYPE
+ elif self.exitType == 'mbox':
+ rt = MBOX_TYPE
+ else:
+ assert type(self.exitType) == types.IntType
+ rt = self.exitType
+ return rt, ri, self.lastHop
-def parseAddress2(s):
+def parseAddress(s):
"""Parse and validate an address; takes a string, and returns an
ExitAddress object.
@@ -1144,15 +952,13 @@
class PathElement:
def validate(self, directory, start, end):
- raise NotImplemented
- def getServers(self, directory, start, end):
- raise NotImplemented
+ raise NotImplemented()
def getFixedServer(self, directory, start, end):
- raise NotImplemented
+ raise NotImplemented()
def getServerNames(self):
- raise NotImplemented
+ raise NotImplemented()
def getMinLength(self):
- raise NotImplemented
+ raise NotImplemented()
class ServerPathElement(PathElement):
def __init__(self, nickname):
@@ -1226,6 +1032,7 @@
self.isReply=isReply
self.isSURB=isSURB
self.lateSplit=lateSplit
+
def getFixedLastServer(self,directory,startAt,endAt):
"""DOCDOC"""
if self.path2:
@@ -1234,9 +1041,57 @@
return None
#----------------------------------------------------------------------
-def parsePath2(config, path, nHops=None, isReply=0, isSURB=0,
- defaultNHops=None):
- """DOCDOC ; Returns a pathSpecifier.
+def parsePath(config, path, nHops=None, isReply=0, isSURB=0,
+ defaultNHops=None):
+ """DOCDOC ; Returns a pathSpecifier. This documentation is no longer
+ even vaguely accurate.
+
+ Resolve a path as specified on the command line. Returns a
+ (path-leg-1, path-leg-2) tuple, where each leg is a list of ServerInfo.
+
+ directory -- the ClientDirectory to use.
+ config -- unused for now.
+ path -- the path, in a format described below. If the path is
+ None, all servers are chosen as if the path were '*'.
+ address -- the address to deliver the message to; if it specifies
+ an exit node, the exit node is appended to the second leg of the
+ path and does not count against the number of hops. If 'address'
+ is None, the exit node must support relay.
+ nHops -- the number of hops to use. Defaults to defaultNHops.
+ startAt/endAt -- A time range during which all servers must be valid.
+ halfPath -- If true, we generate only the second leg of the path
+ and leave the first leg empty.
+ defaultNHops -- The default path length to use when we encounter a
+ wildcard in the path. Defaults to 6.
+
+ Paths are ordinarily comma-separated lists of server nicknames or
+ server descriptor filenames, as in:
+ 'foo,bar,./descriptors/baz,quux'.
+
+ You can use a colon as a separator to divides the first leg of the path
+ from the second:
+ 'foo,bar:baz,quux'.
+ If nSwap and a colon are both used, they must match, or MixError is
+ raised.
+
+ You can use a question mark to indicate a randomly chosen server:
+ 'foo,bar,?,quux,?'.
+ As an abbreviation, you can use star followed by a number to indicate
+ that number of randomly chosen servers:
+ 'foo,bar,*2,quux'.
+ You can use a star without a number to specify a fill point
+ where randomly-selected servers will be added:
+ 'foo,bar,*,quux'.
+ Finally, you can use a tilde followed by a number to specify an
+ approximate number of servers to add. (The actual number will be
+ chosen randomly, according to a normal distribution with standard
+ deviation 1.5):
+ 'foo,bar,~2,quux'
+
+ The nHops argument must be consistent with the path, if both are
+ specified. Specifically, if nHops is used _without_ a star on the
+ path, nHops must equal the path length; and if nHops is used _with_ a
+ star on the path, nHops must be >= the path length.
"""
halfPath = isReply or isSURB
if not path:
@@ -1300,7 +1155,7 @@
raise UIError("Only one '*' is permitted in a single path")
approxHops = reduce(operator.add,
[ ent.getMinLength() for ent in pathEntries
- if ent not in ("*", "<swap>") ])
+ if ent not in ("*", "<swap>") ], 0)
myNHops = nHops or defaultNHops or 6
extraHops = max(myNHops-approxHops, 0)
pathEntries[starPos:starPos+1] =[RandomServersPathElement(n=extraHops)]
@@ -1333,7 +1188,7 @@
raise UIError("Each leg of the path must have at least 1 hop")
else:
minLen = reduce(operator.add,
- [ ent.getMinLength() for ent in pathEntries ])
+ [ ent.getMinLength() for ent in pathEntries ], 0)
if halfPath and minLen < 1:
raise UIError("The path must have at least 1 hop")
if not halfPath and minLen < 2:
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.118
retrieving revision 1.119
diff -u -d -r1.118 -r1.119
--- ClientMain.py 7 Oct 2003 21:57:46 -0000 1.118
+++ ClientMain.py 9 Oct 2003 15:26:15 -0000 1.119
@@ -11,6 +11,7 @@
import getopt
import os
+import socket
import sys
import time
from types import ListType
@@ -26,12 +27,11 @@
from mixminion.Common import LOG, Lockfile, LockfileLocked, MixError, \
MixFatalError, MixProtocolBadAuth, MixProtocolError, UIError, \
UsageError, createPrivateDir, isPrintingAscii, isSMTPMailbox, readFile, \
- stringContains, succeedingMidnight, writeFile
+ stringContains, succeedingMidnight, writeFile, previousMidnight
from mixminion.Packet import encodeMailHeaders, ParseError, parseMBOXInfo, \
parseReplyBlocks, parseSMTPInfo, parseTextEncodedMessages, \
- parseTextReplyBlocks, ReplyBlock, MBOX_TYPE, SMTP_TYPE, DROP_TYPE
-from mixminion.ClientDirectory import ClientDirectory, parsePath, \
- parsePathLeg
+ parseTextReplyBlocks, ReplyBlock, MBOX_TYPE, SMTP_TYPE, DROP_TYPE, \
+ parseMessageAndHeaders
#----------------------------------------------------------------------
# Global variable; holds an instance of Common.Lockfile used to prevent
@@ -203,8 +203,16 @@
self.prng = mixminion.Crypto.getCommonPRNG()
self.queue = mixminion.ClientUtils.ClientQueue(os.path.join(userdir, "queue"))
- def sendForwardMessage(self, address, payload, servers1, servers2,
- forceQueue=0, forceNoQueue=0):
+ def _sortPackets(self, packets):
+ """[(packet,firstHop),...] -> [ (routing, [packet,...]), ...]"""
+ r = {}
+ for packet, firstHop in packets:
+ ri = firstHop.getRoutingInfo()
+ r.setdefault(ri,[]).append(packet)
+ return r.items()
+
+ def sendForwardMessage(self, directory, address, pathSpec, message,
+ startAt, endAt, forceQueue=0, forceNoQueue=0):
"""Generate and send a forward message.
address -- the results of a parseAddress call
payload -- the contents of the message to send
@@ -216,17 +224,17 @@
fails."""
assert not (forceQueue and forceNoQueue)
- for packet, firstHop in self.generateForwardMessage(
- address, payload, servers1, servers2):
-
- routing = firstHop.getRoutingInfo()
+ allPackets = self.generateForwardPayloads(
+ directory, address, pathSpec, message, startAt, endAt)
+ for routing, packets in self._sortPackets(allPackets):
if forceQueue:
- self.queueMessages([packet], routing)
+ self.queueMessages(packets, routing)
else:
- self.sendMessages([packet], routing, noQueue=forceNoQueue)
+ self.sendMessages(packets, routing, noQueue=forceNoQueue)
- def sendReplyMessage(self, payload, servers, surbList, forceQueue=0,
+ def sendReplyMessage(self, directory, address, pathSpec, surbList, message,
+ startAt, endAt, forceQueue=0,
forceNoQueue=0):
"""Generate and send a reply message.
payload -- the contents of the message to send
@@ -237,17 +245,18 @@
forceQueue -- if true, do not try to send the message; simply
queue it and exit.
forceNoQueue -- if true, do not queue the message even if delivery
- fails."""
- #XXXX write unit tests
- message, firstHop = \
- self.generateReplyMessage(payload, servers, surbList)
+ fails.
- routing = firstHop.getRoutingInfo()
+ DOCDOC args are wrong."""
+ #XXXX write unit tests
+ allPackets = self.generateReplyMessage(
+ directory, address, pathSpec, message, surbList, startAt, endAt)
- if forceQueue:
- self.queueMessages([message], routing)
- else:
- self.sendMessages([message], routing, noQueue=forceNoQueue)
+ for routing, packets in self._sortPackets(allPackets):
+ if forceQueue:
+ self.queueMessages(packets, routing)
+ else:
+ self.sendMessages(packets, routing, noQueue=forceNoQueue)
def generateReplyBlock(self, address, servers, name="", expiryTime=0):
"""Generate an return a new ReplyBlock object.
@@ -265,42 +274,44 @@
return block
- def generateForwardMessage(self, address, message, servers1, servers2):
+ def generateForwardPayloads(self, directory, address, pathSpec, message,
+ startAt, endAt):
"""Generate a forward message, but do not send it. Returns a
list of tuples of (the packet body, a ServerInfo for the
first hop.)
- address -- the results of a parseAddress call
- message -- the contents of the message to send (None for DROP
- messages)
- servers1,servers2 -- lists of ServerInfo.
+ DOCDOC
"""
- routingType, routingInfo, _ = address.getRouting()
+
+ #XXXX006 handle user-side fragmentation.
#XXXX006 we need to factor this long-message logic out to the
#XXXX006 common code. For now, this is a temporary measure.
- fragmentedMessagePrefix = mixminion.Packet.ServerSideFragmentedMessage(
- routingType, routingInfo, "").pack()
+ fragmentedMessagePrefix = address.getFragmentedMessagePrefix()
LOG.info("Generating payload(s)...")
r = []
payloads = mixminion.BuildMessage.encodeMessage(message, 0,
fragmentedMessagePrefix)
if len(payloads) > 1:
- routingType = mixminion.Packet.FRAGMENT_TYPE
- routingInfo = ""
- if servers2[-1]['Delivery/Fragmented'].get('Maximum-Fragments',1) < len(payloads):
- raise UIError("Oops; %s won't reassable a message this large."%
- servers2[-1].getNickname())
+ address.setFragmented(1,len(payloads))
+ else:
+ address.setFragmented(0,1)
+ routingType, routingInfo, _ = address.getRouting()
+
+ directory.validatePath(pathSpec, address, startAt, endAt)
+
+ for p, (path1,path2) in zip(payloads, directory.generatePaths(
+ len(payloads), pathSpec, address, startAt, endAt)):
- #XXXX006 don't use the same path for all the packets!
- for p in payloads:
msg = mixminion.BuildMessage._buildForwardMessage(
- p, routingType, routingInfo, servers1, servers2,
+ p, routingType, routingInfo, path1, path2,
self.prng)
- r.append( (msg, servers1[0]) )
+ r.append( (msg, path1[0]) )
+
return r
- def generateReplyMessage(self, payload, servers, surbList, now=None):
+ def generateReplyMessage(self, directory, address, pathSpec, message,
+ surbList, startAt, endAt):
"""Generate a forward message, but do not send it. Returns
a tuple of (the message body, a ServerInfo for the first hop.)
@@ -313,20 +324,30 @@
used, and mark it used.
"""
#XXXX write unit tests
- if now is None:
- now = time.time()
+ assert address.isReply
+
+ payloads = mixminion.BuildMessage.encodeMessage(message, 0, "")
+
surbLog = self.openSURBLog() # implies lock
+ result = []
try:
- surb = surbLog.findUnusedSURB(surbList, verbose=1, now=now)
- if surb is None:
- raise UIError("No usable reply blocks found; all were used or expired.")
-
- LOG.info("Generating packet...")
- msg = mixminion.BuildMessage.buildReplyMessage(
- payload, servers, surb, self.prng)
+ surbs = surbLog.findUnusedSURB(surbList, len(payloads),
+ verbose=1, now=startAt)
+ if len(surbs) <= len(payloads):
+ raise UIError("Not enough usable reply blocks found; all were used or expired.")
+
- surbLog.markSURBUsed(surb)
- return msg, servers[0]
+ for (surb,payload,(path1,path2)) in zip(surbs,payloads,
+ directory.generatePaths(len(payloads),pathSpec, address,
+ startAt,endAt)):
+ assert path1 and not path2
+ LOG.info("Generating packet...")
+ msg = mixminion.BuildMessage.buildReplyMessage(
+ payload, path1, surb, self.prng)
+
+ surbLog.markSURBUsed(surb)
+ result.append( (msg, path1[0]) )
+
finally:
surbLog.close() #implies unlock
@@ -365,6 +386,8 @@
If warnIfLost is true, log a warning if we fail to deliver
the message, and we don't queue it.
+
+ DOCDOC never raises
"""
#XXXX write unit tests
timeout = self.config['Network'].get('ConnectionTimeout')
@@ -392,6 +415,7 @@
timeout)
LOG.info("... %s sent", mword)
except:
+ e = sys.exc_info()[1]
if noQueue and warnIfLost:
LOG.error("Error with queueing disabled: %s lost", mword)
elif lazyQueue:
@@ -401,7 +425,8 @@
else:
LOG.info("Error while delivering %s; leaving in queue",
mword)
- raise
+
+ LOG.info("Error was: %s",e)
try:
clientLock()
for h in handles:
@@ -528,63 +553,6 @@
raise UIError("Not writing binary message to terminal: Use -F to do it anyway.")
return results
-def parseAddress(s):
- """Parse and validate an address; takes a string, and returns an Address
- object.
-
- Accepts strings of the format:
- mbox:<mailboxname>@<server>
- OR smtp:<email address>
- OR <email address> (smtp is implicit)
- OR drop
- OR 0x<routing type>:<routing info>
- """
- # ???? Should this should get refactored into clientmodules, or someplace?
- if s.lower() == 'drop':
- return Address(DROP_TYPE, "", None)
- elif s.lower() == 'test':
- return Address(0xFFFE, "", None)
- elif ':' not in s:
- if isSMTPMailbox(s):
- return Address(SMTP_TYPE, s, None)
- else:
- raise ParseError("Can't parse address %s"%s)
- tp,val = s.split(':', 1)
- tp = tp.lower()
- if tp.startswith("0x"):
- try:
- tp = int(tp[2:], 16)
- except ValueError:
- raise ParseError("Invalid hexidecimal value %s"%tp)
- if not (0x0000 <= tp <= 0xFFFF):
- raise ParseError("Invalid type: 0x%04x"%tp)
- return Address(tp, val, None)
- elif tp == 'mbox':
- if "@" in val:
- mbox, server = val.split("@",1)
- return Address(MBOX_TYPE, parseMBOXInfo(mbox).pack(), server)
- else:
- return Address(MBOX_TYPE, parseMBOXInfo(val).pack(), None)
- elif tp == 'smtp':
- # May raise ParseError
- return Address(SMTP_TYPE, parseSMTPInfo(val).pack(), None)
- elif tp == 'test':
- return Address(0xFFFE, val, None)
- else:
- raise ParseError("Unrecognized address type: %s"%s)
-
-class Address:
- """Represents the target address for a Mixminion message.
- Consists of the exitType for the final hop, the routingInfo for
- the last hop, and (optionally) a server to use as the last hop.
- """
- def __init__(self, exitType, exitAddress, lastHop=None):
- self.exitType = exitType
- self.exitAddress = exitAddress
- self.lastHop = lastHop
- def getRouting(self):
- return self.exitType, self.exitAddress, self.lastHop
-
def readConfigFile(configFile):
"""Given a configuration file (possibly none) as specified on the command
line, return a ClientConfig object.
@@ -713,7 +681,7 @@
self.path = None
self.nHops = None
- self.address = None
+ self.exitAddress = None
self.lifetime = None
self.replyBlockFiles = []
@@ -744,10 +712,10 @@
self.download = dl
elif o in ('-t', '--to'):
assert wantForwardPath or wantReplyPath
- if self.address is not None:
+ if self.exitAddress is not None:
raise UIError("Multiple addresses specified.")
try:
- self.address = parseAddress(v)
+ self.exitAddress = mixminion.ClientDirectory.parseAddress(v)
except ParseError, e:
raise UsageError(str(e))
elif o in ('-R', '--reply-block'):
@@ -818,7 +786,7 @@
if self.wantClientDirectory:
assert self.wantConfig
LOG.debug("Configuring server list")
- self.directory = ClientDirectory(userdir)
+ self.directory = mixminion.ClientDirectory.ClientDirectory(userdir)
if self.wantDownload:
assert self.wantClientDirectory
@@ -833,22 +801,20 @@
self.directory.checkClientVersion()
def parsePath(self):
- """Parse the path specified on the command line and generate a
- new list of servers to be retrieved by getForwardPath or
- getReplyPath."""
- if self.wantReplyPath and self.address is None:
+ # Sets: exitAddress, pathSpec.
+ if self.wantReplyPath and self.exitAddress is None:
address = self.config['Security'].get('SURBAddress')
if address is None:
raise UIError("No recipient specified; exiting. (Try "
"using -t <your-address>)")
try:
- self.address = parseAddress(address)
+ self.exitAddress = mixminion.ClientDirectory.parseAddress(address)
except ParseError, e:
raise UIError("Error in SURBAddress:"+str(e))
- elif self.address is None and self.replyBlockFiles == []:
+ elif self.exitAddress is None and self.replyBlockFiles == []:
raise UIError("No recipients specified; exiting. (Try using "
"-t <recipient-address>")
- elif self.address is not None and self.replyBlockFiles:
+ elif self.exitAddress is not None and self.replyBlockFiles:
raise UIError("Cannot use both a recipient and a reply block")
elif self.replyBlockFiles:
useRB = 1
@@ -866,66 +832,44 @@
surbs.extend(parseReplyBlocks(s))
except ParseError, e:
raise UIError("Error parsing %s: %s" % (fn, e))
+ self.surbList = surbs
else:
- assert self.address is not None
+ assert self.exitAddress is not None
useRB = 0
+ isSURB = isReply = 0
+ if self.wantReplyPath:
+ p = 'SURBPath'; isSURB = 1
+ defHops = self.config['Security'].get("SURBPathLength", 4)
+ elif useRB:
+ p = 'ReplyPath'; isReply = 1
+ defHops = self.config['Security'].get("PathLength", 6)
+ else:
+ p = 'ForwardPath'
+ defHops = self.config['Security'].get("PathLength", 6)
if self.path is None:
- if self.wantReplyPath:
- p = 'SURBPath'
- elif useRB:
- p = 'ReplyPath'
- else:
- p = 'ForwardPath'
self.path = self.config['Security'].get(p, "*")
- if self.wantReplyPath:
+ if isSURB:
if self.lifetime is not None:
duration = self.lifetime * 24*60*60
else:
duration = int(self.config['Security']['SURBLifetime'])
-
- self.endTime = succeedingMidnight(time.time() + duration)
-
- defHops = self.config['Security'].get("SURBPathLength", 4)
- self.path1 = parsePathLeg(self.directory, self.config, self.path,
- self.nHops, self.address,
- startAt=time.time(),
- endAt=self.endTime,
- defaultNHops=defHops)
- self.path2 = None
- LOG.info("Selected path is %s",
- ",".join([ s.getNickname() for s in self.path1 ]))
- elif useRB:
- assert self.wantForwardPath
- defHops = self.config['Security'].get("PathLength", 6)
- self.path1 = parsePathLeg(self.directory, self.config, self.path,
- self.nHops, defaultNHops=defHops)
- self.path2 = surbs
- self.usingSURBList = 1
- LOG.info("Selected path is %s:<reply block>",
- ",".join([ s.getNickname() for s in self.path1 ]))
else:
- assert self.wantForwardPath
- defHops = self.config['Security'].get("PathLength", 6)
- self.path1, self.path2 = \
- parsePath(self.directory, self.config, self.path,
- self.address, self.nHops,
- defaultNHops=defHops)
- self.usingSURBList = 0
- LOG.info("Selected path is %s:%s",
- ",".join([ s.getNickname() for s in self.path1 ]),
- ",".join([ s.getNickname() for s in self.path2 ]))
+ duration = 24*60*60
- def getForwardPath(self):
- """Return a 2-tuple of lists of ServerInfo for the most recently
- parsed forward path."""
- return self.path1, self.path2
+ self.startAt = time.time()
+ self.endAt = previousMidnight(self.startAt+duration)
- def getReplyPath(self):
- """Return a list of ServerInfo for the most recently parsed reply
- block path."""
- return self.path1
+ self.pathSpec = mixminion.ClientDirectory.parsePath(
+ self.config, self.path, self.nHops, isReply=isReply, isSURB=isSURB,
+ defaultNHops = defHops)
+ self.directory.validatePath2(self.pathSpec, self.exitAddress,
+ self.startAt, self.endAt)
+
+ def generatePaths(self, n):
+ return self.directory.generatePaths(n,self.pathSpec,self.exitAddress,
+ self.startAt,self.endAt)
_SEND_USAGE = """\
Usage: %(cmd)s [options] <-t address>|<--to=address>|
@@ -1067,35 +1011,12 @@
parser.init()
client = parser.client
-
- #XXXX006 the logic here is wrong for large messages. Instead of
- #XXXX006 [parse pathspec, parse address, generate path, read message,
- #XXXX006 encode message, build packets, send packets], it should be
- #XXXX006 [parse pathspec, parse address, check pathspec, read message,
- #XXXX006 encode message, generate paths, build packets, send packets].
parser.parsePath()
-
- path1, path2 = parser.getForwardPath()
- address = parser.address
-
- #XXXX006 remove these ad hoc checks
- if not parser.usingSURBList and len(headerStr) > 2:
- sware = path2[-1]['Server'].get('Software', "")
- if sware.startswith("Mixminion 0.0.4") or sware.startswith("Mixminion 0.0.5alpha1"):
- LOG.warn("Exit server %s is running old software that may not support headers correctly.", path2[-1].getNickname())
- elif not parser.usingSURBList and h_from:
- sware = path2[-1]['Server'].get('Software', "")
- if sware != 'Mixminion 0.0.5':
- bad = 0
- if address.getRouting()[0] == SMTP_TYPE and not path2[-1]['Delivery/SMTP'].get("Allow-From"):
- bad = 1
- elif address.getRouting()[0] == MBOX_TYPE and not path2[-1]['Delivery/MBOX'].get("Allow-From"):
- bad = 1
- if bad:
- LOG.warn("Exit server %s does not support user-supplied From addresses", path2[-1].getNickname())
+ address = parser.exitAddress
+ address.setHeaders(parseMessageAndHeaders(headerStr+"\n")[1])
# Get our surb, if any.
- if parser.usingSURBList and inFile in ('-', None):
+ if address.isReply and inFile in ('-', None):
# We check to make sure that we have a valid SURB before reading
# from stdin.
surblog = client.openSURBLog()
@@ -1106,6 +1027,7 @@
finally:
surblog.close()
+ # Read the message.
# XXXX Clean up this ugly control structure.
if address and inFile is None and address.getRouting()[0] == DROP_TYPE:
message = None
@@ -1128,44 +1050,22 @@
print "Interrupted. Message not sent."
sys.exit(1)
- message = "%s%s" % (headerStr, message)
+ message = "%s%s" % (headerStr, message)
- if parser.usingSURBList:
- assert isinstance(path2, ListType)
- client.sendReplyMessage(message, path1, path2,
- forceQueue, forceNoQueue)
+ address.setExitSize(len(message))
+
+
+ if parser.exitAddress.isReply:
+ client.sendReplyMessage(
+ parser.directory, parser.exitAddress, parser.pathSpec,
+ parser.surbList, message,
+ parser.startAt, parser.endAt, forceQueue, forceNoQueue)
else:
- # If our message is too large for the exit node to reconstruct,
- # either choose a new exit node (for SMTP) or bail (for MBOX or
- # other).
- #
- #XXXX006 This logic is wrong; when we refactor paths again, it'll
- #XXXX006 have to be fixed.
- if message:
- msgLen = len(message)
- if address.exitType == SMTP_TYPE:
- maxLen = path2[-1]["Delivery/SMTP"]["Maximum-Size"] * 1024
- if msgLen > maxLen:
- LOG.warn("Message is too long for server %s--looking for another..."
- % path2[-1].getNickname())
- LOG.warn("(This behavior is a hack, and will go away in 0.0.6.)")
- server = parser.directory.findByExitTypeAndSize(SMTP_TYPE, msgLen, 1)
- if not server:
- raise UIError("No such server found")
- LOG.warn("Replacing %s with %s",
- path2[-1].getNickname(),
- server.getNickname())
- path2[-1] = server
- elif address.exitType == MBOX_TYPE:
- maxLen = path2[-1]["Delivery/MBOX"]["Maximum-Size"] * 1024
- if msgLen > maxLen:
- raise UIError("Message is too long for MBOX server %s, and client-side reconstruction is not yet supported"
- % path2[-1].getNickname())
- elif msgLen > 32*1024:
- LOG.warn("Delivering long message via unrecognized delivery type")
-
- client.sendForwardMessage(address, message, path1, path2,
- forceQueue, forceNoQueue)
+ client.sendForwardMessage(
+ parser.directory, parser.exitAddress, parser.pathSpec,
+ message, parser.startAt, parser.endAt, forceQueue, forceNoQueue)
+
+
_PING_USAGE = """\
Usage: mixminion ping [options] serverName
@@ -1495,9 +1395,6 @@
parser.parsePath()
- path1 = parser.getReplyPath()
- address = parser.address
-
if outputFile == '-':
out = sys.stdout
elif binary:
@@ -1505,16 +1402,15 @@
else:
out = open(outputFile, 'w')
- for i in xrange(count):
- surb = client.generateReplyBlock(address, path1, name=identity,
- expiryTime=parser.endTime)
+ for path1,path2 in parser.generatePaths(count):
+ assert path2 and not path1
+ surb = client.generateReplyBlock(parser.exitAddress, path2,
+ name=identity,
+ expiryTime=parser.endAt)
if binary:
out.write(surb.pack())
else:
out.write(surb.packAsText())
- if i != count-1:
- parser.parsePath()
- path1 = parser.getReplyPath()
out.close()
Index: ClientUtils.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientUtils.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- ClientUtils.py 28 Sep 2003 05:27:56 -0000 1.2
+++ ClientUtils.py 9 Oct 2003 15:26:16 -0000 1.3
@@ -234,14 +234,14 @@
self.clean()
self.sync()
- def findUnusedSURB(self, surbList, verbose=0, now=None):
+ def findUnusedSURBs(self, surbList, nSURBs=1, verbose=0, now=None):
"""Given a list of ReplyBlock objects, find the first that is neither
expired, about to expire, or used in the past. Return None if
- no such reply block exists."""
+ no such reply block exists. DOCDOC returns list, nSurbs"""
if now is None:
now = time.time()
nUsed = nExpired = nShortlived = 0
- result = None
+ result = []
for surb in surbList:
expiry = surb.timestamp
timeLeft = expiry - now
@@ -252,8 +252,9 @@
elif timeLeft < 3*60*60:
nShortlived += 1
else:
- result = surb
- break
+ result.append(surb)
+ if len(result) >= nSURBs:
+ break
if verbose:
if nUsed:
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.59
retrieving revision 1.60
diff -u -d -r1.59 -r1.60
--- Config.py 6 Oct 2003 20:53:02 -0000 1.59
+++ Config.py 9 Oct 2003 15:26:16 -0000 1.60
@@ -67,7 +67,7 @@
import mixminion.Crypto
from mixminion.Common import MixError, LOG, ceilDiv, englishSequence, \
- isPrintingAscii, stripSpace, stringContains
+ isPrintingAscii, stripSpace, stringContains, UIError
class ConfigError(MixError):
"""Thrown when an error is found in a configuration file."""
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.60
retrieving revision 1.61
diff -u -d -r1.60 -r1.61
--- Packet.py 31 Aug 2003 19:29:29 -0000 1.60
+++ Packet.py 9 Oct 2003 15:26:16 -0000 1.61
@@ -12,14 +12,14 @@
__all__ = [ 'compressData', 'CompressedDataTooLong', 'DROP_TYPE',
'ENC_FWD_OVERHEAD', 'ENC_SUBHEADER_LEN',
'encodeMailHeaders', 'encodeMessageHeaders',
- 'FRAGMENT_PAYLOAD_OVERHEAD', 'FWD_TYPE', 'FragmentPayload',
+ 'FRAGMENT_PAYLOAD_OVERHEAD', 'FWD_IPV4_TYPE', 'FragmentPayload',
'FRAGMENT_MESSAGEID_LEN', 'FRAGMENT_TYPE',
'HEADER_LEN', 'IPV4Info', 'MAJOR_NO', 'MBOXInfo',
'MBOX_TYPE', 'MINOR_NO', 'MIN_EXIT_TYPE',
'MIN_SUBHEADER_LEN', 'Packet',
'OAEP_OVERHEAD', 'PAYLOAD_LEN', 'ParseError', 'ReplyBlock',
'ReplyBlock', 'SECRET_LEN', 'SINGLETON_PAYLOAD_OVERHEAD',
- 'SMTPInfo', 'SMTP_TYPE', 'SWAP_FWD_TYPE', 'SingletonPayload',
+ 'SMTPInfo', 'SMTP_TYPE', 'SWAP_FWD_IPV4_TYPE', 'SingletonPayload',
'Subheader', 'TAG_LEN', 'TextEncodedMessage',
'parseHeader', 'parseIPV4Info',
'parseMBOXInfo', 'parsePacket', 'parseMessageAndHeaders',
@@ -75,9 +75,11 @@
#----------------------------------------------------------------------
# Values for the 'Routing type' subheader field
# Mixminion types
-DROP_TYPE = 0x0000 # Drop the current message
-FWD_TYPE = 0x0001 # Forward the msg to an IPV4 addr via MMTP
-SWAP_FWD_TYPE = 0x0002 # SWAP, then forward the msg to an IPV4 addr via MMTP
+DROP_TYPE = 0x0000 # Drop the current message
+FWD_IPV4_TYPE = 0x0001 # Forward the msg to an IPV4 addr via MMTP
+SWAP_FWD_IPV4_TYPE = 0x0002 # SWAP, then FWD_IPV4
+FWD_HOST_TYPE = 0x0003 # Forward the msg to a hostname, via MMTP.
+SWAP_FWD_HOST_TYPE = 0x0004 # SWAP, then FWD_HOST
# Exit types
MIN_EXIT_TYPE = 0x0100 # The numerically first exit type.
@@ -93,6 +95,9 @@
# XXXX006 needless tag field to every fragment routing info.
_TYPES_WITHOUT_TAGS = { FRAGMENT_TYPE : 1 }
+def typeIsSwap(tp):
+ return tp in (SWAP_FWD_IPV4_TYPE,SWAP_FWD_HOST_TYPE)
+
class ParseError(MixError):
"""Thrown when a message or portion thereof is incorrectly formatted."""
pass
@@ -525,7 +530,7 @@
def format(self):
hash = binascii.b2a_hex(sha1(self.pack()))
expiry = formatTime(self.timestamp)
- if self.routingType == SWAP_FWD_TYPE:
+ if self.routingType == SWAP_FWD_IPV4_TYPE:
server = parseIPV4Info(self.routingInfo).format()
else:
server = "????"
@@ -598,6 +603,52 @@
r = cmp(type(self), type(other))
if r: return r
r = cmp(self.ip, other.ip)
+ if r: return r
+ r = cmp(self.port, other.port)
+ if r: return r
+ return cmp(self.keyinfo, other.keyinfo)
+
+MMTP_HOST_PAT = "!H%ds" % DIGEST_LEN
+
+def parseMMTPHostInfo(s):
+ """DOCDOC"""
+ if len(s) < 2+DIGEST_LEN+1:
+ raise ParseError("Routing information is too short.")
+ try:
+ port, keyinfo = struct.unpack(MMTP_HOST_PAT, s[:2+DIGEST_LEN])
+ except struct.error:
+ raise ParseError("Misformatted routing info")
+ return MMTPHostInfo(s[2+DIGEST_LEN:], port, keyinfo)
+
+class MMTPHostInfo:
+ """DOCDOC"""
+ def __init__(self, hostname, port, keyinfo):
+ """Construct a new IPV4Info"""
+ assert 0 <= port <= 65535
+ self.hostname = hostname
+ self.port = port
+ self.keyinfo = keyinfo
+
+ def format(self):
+ return "%s:%s (keyid=%s)"%(self.hostname, self.port,
+ binascii.b2a_hex(self.keyinfo))
+
+ def pack(self):
+ """Return the routing info for this address"""
+ assert len(self.keyinfo) == DIGEST_LEN
+ return struct.pack(MMTP_HOST_PAT,self.port,self.keyinfo)+self.hostname
+
+ def __repr__(self):
+ return "MMTPHostInfo(%r, %r, %r)"%(
+ self.hostname,self.port,self.keyinfo)
+
+ def __hash__(self):
+ return hash(self.pack())
+
+ def __cmp__(self, other):
+ r = cmp(type(self), type(other))
+ if r: return r
+ r = cmp(self.hostname, other.hostname)
if r: return r
r = cmp(self.port, other.port)
if r: return r
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.56
retrieving revision 1.57
diff -u -d -r1.56 -r1.57
--- ServerInfo.py 6 Oct 2003 20:55:06 -0000 1.56
+++ ServerInfo.py 9 Oct 2003 15:26:16 -0000 1.57
@@ -15,6 +15,7 @@
import mixminion.Config
import mixminion.Crypto
+import mixminion.Packet
from mixminion.Common import IntervalSet, LOG, MixError, createPrivateDir, \
formatBase64, formatDate, formatTime, readPossiblyGzippedFile
@@ -266,10 +267,34 @@
def getIdentity(self):
return self['Server']['Identity']
- def canDeliverTo(self, otherDesc):
- #DOCDOC
- return 1
+ def canRelayTo(self, otherDesc):
+ """DOCDOC"""
+ if self.hasSameNicknameAs(otherDesc):
+ return 1
+ myOut = self['Outgoing/MMTP']
+ if not myOut.get("Version"):
+ return 0
+ otherIn = otherDesc['Incoming/MMTP']
+ if not otherIn.get("Version"):
+ return 0
+ myOutProtocols = [ s.strip() for s in ",".split(myOut["Protocols"]) ]
+ otherInProtocols = [s.strip() for s in ",".split(otherIn["Protocols"])]
+ for out in myOutProtocols:
+ if out in otherInProtocols:
+ return 1
+ return 0
+ def getRoutingFor(self, otherDesc, swap=0):
+ """DOCDOC"""
+ #XXXX006 use this
+ assert self.canRelayTo(otherDesc)
+ if swap:
+ rt = mixminion.Packet.SWAP_FWD_IPV4_TYPE
+ else:
+ rt = mixminion.Packet.FWD_IPV4_TYPE
+ ri = other.getRoutingInfo().pack()
+ return rt, ri
+
def getCaps(self):
# FFFF refactor this once we have client addresses.
caps = []
@@ -285,6 +310,14 @@
if self['Delivery/Fragmented'].get('Version'):
caps.append('frag')
return caps
+
+ def isSameDescriptorAs(self, other):
+ """DOCDOC"""
+ return self.getDigest() == other.getDigest()
+
+ def hasSameNicknameAs(self, other):
+ """DOCDOC"""
+ return self.getNickname().lower() == other.getNickname().lower()
def isValidated(self):
"""Return true iff this ServerInfo has been validated"""
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.156
retrieving revision 1.157
diff -u -d -r1.156 -r1.157
--- test.py 7 Oct 2003 21:57:46 -0000 1.156
+++ test.py 9 Oct 2003 15:26:16 -0000 1.157
@@ -1754,7 +1754,7 @@
self.do_header_test(head,
(self.pk1, self.pk2),
["9"*16, "1"*16],
- (FWD_TYPE, 99),
+ (FWD_IPV4_TYPE, 99),
(ipv4("127.0.0.2",3,"Z"*20).pack(),
"Hi mom"))
@@ -1765,7 +1765,7 @@
head = bhead([self.server1, self.server2, self.server3], secrets,
99, "Hi mom", AESCounterPRNG())
pks = (self.pk1,self.pk2,self.pk3)
- rtypes = (FWD_TYPE, FWD_TYPE, 99)
+ rtypes = (FWD_IPV4_TYPE, FWD_IPV4_TYPE, 99)
rinfo = (mixminion.Packet.IPV4Info("127.0.0.2", 3, "Z"*20).pack(),
mixminion.Packet.IPV4Info("127.0.0.3", 5, "Q"*20).pack(),
"Hi mom")
@@ -1880,7 +1880,7 @@
longStr,
AESCounterPRNG())
pks = (self.pk2,self.pk1)
- rtypes = (FWD_TYPE,99)
+ rtypes = (FWD_IPV4_TYPE,99)
rinfo = (tag+longStr2,longStr)
self.do_header_test(head, pks, secrets, rtypes, rinfo)
@@ -2009,11 +2009,11 @@
self.do_message_test(m,
( (self.pk1, self.pk2), None,
- (FWD_TYPE, SWAP_FWD_TYPE),
+ (FWD_IPV4_TYPE, SWAP_FWD_IPV4_TYPE),
(self.server2.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack()) ),
( (self.pk3, self.pk2), None,
- (FWD_TYPE, 500),
+ (FWD_IPV4_TYPE, 500),
(self.server2.getRoutingInfo().pack(),
"Goodbye") ),
"Hello!!!!")
@@ -2027,7 +2027,7 @@
self.do_message_test(m,
( (self.pk1,), None,
- (SWAP_FWD_TYPE,),
+ (SWAP_FWD_IPV4_TYPE,),
(self.server3.getRoutingInfo().pack(),) ),
( (self.pk3,), None,
(500,),
@@ -2045,7 +2045,7 @@
self.do_message_test(m,
( (self.pk1,), None,
- (SWAP_FWD_TYPE,),
+ (SWAP_FWD_IPV4_TYPE,),
(self.server3.getRoutingInfo().pack(),) ),
( (self.pk3,), None,
(DROP_TYPE,),
@@ -2068,11 +2068,11 @@
return payload.getUncompressedContents()
self.do_message_test(m,
( (self.pk1, self.pk2), None,
- (FWD_TYPE, SWAP_FWD_TYPE),
+ (FWD_IPV4_TYPE, SWAP_FWD_IPV4_TYPE),
(self.server2.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack()) ),
( (self.pk3, self.pk2), None,
- (FWD_TYPE, 500),
+ (FWD_IPV4_TYPE, 500),
(self.server2.getRoutingInfo().pack(),
"Phello") ),
payload,
@@ -2136,11 +2136,11 @@
self.do_message_test(m,
((self.pk3, self.pk1), None,
- (FWD_TYPE,SWAP_FWD_TYPE),
+ (FWD_IPV4_TYPE,SWAP_FWD_IPV4_TYPE),
(self.server1.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack())),
(pks_1, hsecrets,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
+ (FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,SMTP_TYPE),
infos+("no-such-user@invalid",)),
"Information???",
decoder=decoder)
@@ -2150,14 +2150,14 @@
"fred", "Tyrone Slothrop", 3)
sec,(loc,), _ = self.do_header_test(reply.header, pks_1, None,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
+ (FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,MBOX_TYPE),
infos+(None,))
self.assertEquals(loc[20:], "fred")
# (Test reply block formats)
self.assertEquals(reply.timestamp, 3)
- self.assertEquals(reply.routingType, SWAP_FWD_TYPE)
+ self.assertEquals(reply.routingType, SWAP_FWD_IPV4_TYPE)
self.assertEquals(reply.routingInfo,
self.server3.getRoutingInfo().pack())
self.assertEquals(reply.pack(),
@@ -2214,11 +2214,11 @@
self.do_message_test(m,
((self.pk3, self.pk1), None,
- (FWD_TYPE,SWAP_FWD_TYPE),
+ (FWD_IPV4_TYPE,SWAP_FWD_IPV4_TYPE),
(self.server1.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack())),
(pks_1, None,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
+ (FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,MBOX_TYPE),
infos+("fred",)),
payload,
decoder=decoder2)
@@ -2430,7 +2430,7 @@
res = sp.processMessage(m)
self.assert_(isinstance(res, DeliveryPacket) or
isinstance(res, RelayedPacket))
- if rt in (FWD_TYPE, SWAP_FWD_TYPE):
+ if rt in (FWD_IPV4_TYPE, SWAP_FWD_IPV4_TYPE):
self.assert_(not res.isDelivery())
self.assertEquals(res.getAddress().pack(), ri)
m = res.getPacket()
@@ -2454,7 +2454,7 @@
self.do_test_chain(m,
[self.sp1,self.sp2,self.sp3],
- [FWD_TYPE, FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, FWD_IPV4_TYPE, SMTP_TYPE],
[self.server2.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
@@ -2466,7 +2466,7 @@
self.do_test_chain(m,
[self.sp1,self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
p)
@@ -2474,7 +2474,7 @@
# Try servers with multiple keys
m = bfm("\n"+p,
SMTP_TYPE, "nobody@invalid", [self.server2], [self.server3])
- self.do_test_chain(m, [self.sp2_3, self.sp2_3], [FWD_TYPE, SMTP_TYPE],
+ self.do_test_chain(m, [self.sp2_3, self.sp2_3], [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"], p)
@@ -2488,8 +2488,8 @@
self.do_test_chain(m,
[self.sp1,self.sp2,self.sp1,
self.sp3,self.sp1,self.sp2],
- [FWD_TYPE,FWD_TYPE,FWD_TYPE,
- FWD_TYPE,FWD_TYPE,SMTP_TYPE],
+ [FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,
+ FWD_IPV4_TYPE,FWD_IPV4_TYPE,SMTP_TYPE],
[self.server2.getRoutingInfo().pack(),
self.server1.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack(),
@@ -2510,7 +2510,7 @@
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
p)
@@ -2534,7 +2534,7 @@
[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
pbin)
@@ -2550,7 +2550,7 @@
[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
"")
@@ -2564,7 +2564,7 @@
[self.server3], getRSAKey(0,1024))
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
"")
@@ -2582,7 +2582,7 @@
m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1, self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
"")
@@ -2597,7 +2597,7 @@
m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1, self.sp3],
- [FWD_TYPE, SMTP_TYPE],
+ [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
"nobody@invalid"],
"")
@@ -2674,16 +2674,21 @@
# (We temporarily override the setting from 'BuildMessage',
# not Packet; BuildMessage has already imported a copy of this
# constant.)
- save = mixminion.BuildMessage.SWAP_FWD_TYPE
- mixminion.BuildMessage.SWAP_FWD_TYPE = 50
+ save = mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE
+ mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE = 50
m_x = bfm("Z", 500, "", [self.server1], [self.server2])
finally:
- mixminion.BuildMessage.SWAP_FWD_TYPE = save
+ mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE = save
self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
- # Subhead we can't parse
+ # Subhead with bad length
m_x = pk_encrypt("foo", self.pk1)+m[256:]
- self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+ # Subhead we can't parse.
+ m_x = pk_encrypt("f"*(256-42), self.pk1)+m[256:]
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
# Bad IPV4 info
subh_real = pk_decrypt(m[:256], self.pk1)
@@ -6046,26 +6051,25 @@
try:
### Try out getPath.
# 1. Fully-specified paths.
- p = ks.getPath(None, ['Joe', 'Lisa', 'Alice', 'Joe'])
+ p = ks.getPath(['Joe', 'Lisa', 'Alice', 'Joe'])
# 2. Partly-specified paths...
# 2a. With plenty of servers
- p = ks.getPath(None, [None, None])
+ p = ks.getPath([None, None])
eq(2, len(p))
neq(p[0].getNickname(), p[1].getNickname())
- p = ks.getPath(None, ["Joe", None, None])
+ p = ks.getPath(["Joe", None, None])
eq(3, len(p))
self.assertSameSD(p[0], joe[0])
neq(p[1].getNickname(), "Joe")
- neq(p[2].getNickname(), "Joe")
neq(p[1].getNickname(), p[2].getNickname())
- p = ks.getPath(None, [None, None, "Joe"])
+ p = ks.getPath([None, None, "Joe"])
eq(3, len(p))
self.assertSameSD(joe[0], p[2])
- p = ks.getPath(None, ["Alice", None, None, "Joe"])
+ p = ks.getPath(["Alice", None, None, "Joe"])
eq(4, len(p))
self.assertSameSD(alice[0], p[0])
self.assertSameSD(joe[0], p[3])
@@ -6074,7 +6078,7 @@
self.assert_(nicks.count("Joe")>=1)
neq(nicks[1],nicks[2])
- p = ks.getPath(None, ["Joe", None, "Alice", "Joe"])
+ p = ks.getPath(["Joe", None, "Alice", "Joe"])
eq(4, len(p))
self.assertSameSD(alice[0], p[2])
self.assertSameSD(joe[0], p[0])
@@ -6088,22 +6092,22 @@
ks2.importFromFile(os.path.join(impdirname, "Lisa1"))
ks2.importFromFile(os.path.join(impdirname, "Bob0"))
- p = ks2.getPath(None, [None]*9)
+ p = ks2.getPath([None]*9)
eq(9, len(p))
self.failIf(nRuns([s.getNickname() for s in p]))
- p = ks2.getPath(None, ["Joe"]+[None]*6+["Joe"])
+ p = ks2.getPath(["Joe"]+[None]*6+["Joe"])
self.failIf(nRuns([s.getNickname() for s in p]))
eq(8, len(p))
self.assertSameSD(joe[0], p[0])
self.assertSameSD(joe[0], p[-1])
- p = ks2.getPath(None, ["Joe"]+[None]*6)
+ p = ks2.getPath(["Joe"]+[None]*6)
self.failIf(nRuns([s.getNickname() for s in p]))
eq(7, len(p))
self.assertSameSD(joe[0], p[0])
- p = ks2.getPath(None, [None]*6+["Joe"])
+ p = ks2.getPath([None]*6+["Joe"])
self.failIf(nRuns([s.getNickname() for s in p]))
eq(7, len(p))
self.assertSameSD(joe[0], p[-1])
@@ -6111,56 +6115,49 @@
# 2c. With 2 servers
ks2.expungeByNickname("Alice")
ks2.expungeByNickname("Bob")
- p = ks2.getPath(None, [None]*4)
+ p = ks2.getPath([None]*4)
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
- p = ks2.getPath(None, ["Joe",None,None,None])
+ p = ks2.getPath(["Joe",None,None,None])
self.failIf(nRuns([s.getNickname() for s in p]) > 2)
- p = ks2.getPath(None, [None, None, None, "Joe"])
+ p = ks2.getPath([None, None, None, "Joe"])
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
- p = ks2.getPath(None, [None,None,None,None,None, "Joe"])
+ p = ks2.getPath([None,None,None,None,None, "Joe"])
self.failIf(nRuns([s.getNickname() for s in p]) > 1)
# 2d. With only 1.
ks2.expungeByNickname("Lisa")
- p = ks2.getPath(None,[None]*4)
+ p = ks2.getPath([None]*4)
eq(len(p), 4)
- p = ks2.getPath(None,["Joe",None,None,None])
+ p = ks2.getPath(["Joe",None,None,None])
eq(len(p), 4)
- p = ks2.getPath(None,[None,None,None,"Joe"])
+ p = ks2.getPath([None,None,None,"Joe"])
eq(len(p), 4)
# 2e. With 0
self.assertRaises(MixError, ks.getPath,
- None, [None]*4, startAt=now+100*oneDay)
+ [None]*4, startAt=now+100*oneDay)
finally:
s = resumeLog()
self.assertEquals(4, s.count("to avoid same-server hops"))
self.assertEquals(3, s.count("Only one relay known"))
- # 3. With capabilities.
- p = ks.getPath("smtp", [None]*5)
- eq(5, len(p))
- self.assertSameSD(p[-1], joe[0]) # Only Joe has SMTP
-
- p = ks.getPath("mbox", [None]*4)
- eq(4, len(p))
- self.assertSameSD(p[-1], lola[1]) # Only Lola has MBOX
-
- p = ks.getPath("mbox", ["Alice", None, None, None, None])
- eq(5, len(p))
- self.assertSameSD(p[-1], lola[1]) # Only Lola has MBOX
- self.assertSameSD(p[0], alice[0])
-
- p = ks.getPath("mbox", [None,None,None,None, "Alice"])
- eq(5, len(p))
- self.assertSameSD(p[-1], alice[0]) # We ignore endCap with endServers
-
- ### Now try parsePath. This should exercise resolvePath as well.
- ppath = mixminion.ClientDirectory.parsePath
- paddr = mixminion.ClientMain.parseAddress
+ # wrap path parsing and verification and generation.
+ def ppath(dir, cfg, path, addr, nHops=None, startAt=None, endAt=None,
+ halfPath=0, defaultNHops=None):
+ isReply = halfPath and (addr is None)
+ isSURB = halfPath and (addr is not None)
+ pathSpec = mixminion.ClientDirectory.parsePath(
+ cfg, path, nHops=nHops, isReply=isReply,
+ isSURB=isSURB, defaultNHops=defaultNHops)
+ dir.validatePath(pathSpec, addr, startAt=startAt, endAt=endAt)
+ paths = dir.generatePaths(1, pathSpec, addr, startAt,endAt)
+ assert len(paths) == 1
+ return paths[0]
+
+ paddr = mixminion.ClientDirectory.parseAddress
email = paddr("smtp:lloyd@dobler.com")
mboxWithServer = paddr("mbox:Granola@Lola")
mboxWithoutServer = paddr("mbox:Granola")
@@ -6369,7 +6366,7 @@
def testAddress(self):
def parseEq(s, tp, addr, server, eq=self.assertEquals):
"Helper: return true iff parseAddress(s).getRouting() == t,s,a."
- t, a, s = mixminion.ClientMain.parseAddress(s).getRouting()
+ t, a, s = mixminion.ClientDirectory.parseAddress(s).getRouting()
eq(t, tp)
eq(s, server)
eq(a, addr)
@@ -6391,7 +6388,7 @@
parseEq("0x999:", 0x999, "", None)
def parseFails(s, f=self.failUnlessRaises):
- f(ParseError, mixminion.ClientMain.parseAddress, s)
+ f(ParseError, mixminion.ClientDirectory.parseAddress, s)
# Check failing cases
parseFails("sxtp:foo@bar.com") # unknown module
@@ -6437,11 +6434,16 @@
s = SURBLog(fname)
self.assert_(s.isSURBUsed(surbs[0]))
self.assert_(not s.isSURBUsed(surbs[1]))
- self.assert_(s.findUnusedSURB(surbs) is surbs[1])
+ self.assert_(s.findUnusedSURBs(surbs)[0] is surbs[1])
+ one = s.findUnusedSURBs(surbs,1)
+ self.assertEquals(len(one),1)
+ two = s.findUnusedSURBs(surbs,2)
+ self.assert_(two[0] is surbs[1])
+ self.assert_(two[1] is surbs[2])
s.markSURBUsed(surbs[1])
- self.assert_(s.findUnusedSURB(surbs) is surbs[2])
+ self.assert_(s.findUnusedSURBs(surbs)[0] is surbs[2])
s.markSURBUsed(surbs[2])
- self.assert_(s.findUnusedSURB(surbs) is None)
+ self.assert_(s.findUnusedSURBs(surbs) == [])
finally:
s.close()
@@ -6478,22 +6480,39 @@
def testMixminionClient(self):
# Create and configure a MixminionClient object...
- parseAddress = mixminion.ClientMain.parseAddress
+ parseAddress = mixminion.ClientDirectory.parseAddress
+ parsePath = mixminion.ClientDirectory.parsePath
userdir = mix_mktemp()
usercfgstr = "[User]\nUserDir: %s\n[DirectoryServers]\n"%userdir
usercfg = mixminion.Config.ClientConfig(string=usercfgstr)
client = mixminion.ClientMain.MixminionClient(usercfg)
+ # Create a directory...
+ dirname = mix_mktemp()
+ directory = mixminion.ClientDirectory.ClientDirectory(dirname)
+
+ edesc = getExampleServerDescriptors()
+ fname = mix_mktemp()
+ for server, descriptors in edesc.items():
+ for d in descriptors:
+ writeFile(fname, d)
+ try:
+ directory.importFromFile(fname)
+ except UIError:
+ pass
+
# Now try with some servers...
edesc = getExampleServerDescriptors()
ServerInfo = mixminion.ServerInfo.ServerInfo
Lola = ServerInfo(string=edesc["Lola"][1], assumeValid=1)
Joe = ServerInfo(string=edesc["Joe"][0], assumeValid=1)
Alice = ServerInfo(string=edesc["Alice"][1], assumeValid=1)
-
+
# ... and for now, we need to restart the client.
client = mixminion.ClientMain.MixminionClient(usercfg)
+ pathSpec1 = parsePath(usercfg, "lola,joe:alice,joe")
+
## Test generateForwardMessage.
# We replace 'buildForwardMessage' to make this easier to test.
replaceFunction(mixminion.BuildMessage, "buildForwardMessage",
@@ -6504,14 +6523,17 @@
# First, two forward messages that end with 'joe' and go via
# SMTP
payload = "Hey Joe, where you goin' with that gun in your hand?"
- client.generateForwardMessage(
+ client.generateForwardPayloads(
+ directory,
parseAddress("joe@cledonism.net"),
- payload,
- servers1=[Lola, Joe], servers2=[Alice, Joe])
- client.generateForwardMessage(
+ pathSpec1,
+ payload, time.time(), time.time()+200)
+ client.generateForwardPayloads(
+ directory,
parseAddress("smtp:joe@cledonism.net"),
+ pathSpec1,
"Hey Joe, where you goin' with that gun in your hand?",
- servers1=[Lola, Joe], servers2=[Alice, Joe])
+ time.time(), time.time()+200)
for fn, args, kwargs in getCalls():
self.assertEquals(fn, "buildForwardMessage")
@@ -6524,15 +6546,18 @@
# Now try an mbox message, with an explicit last hop.
payload = "Hey, Lo', where you goin' with that pun in your hand?"
- client.generateForwardMessage(
+ client.generateForwardPayloads(
+ directory,
parseAddress("mbox:granola"),
- payload,
- servers1=[Lola, Joe], servers2=[Alice, Lola])
+ parsePath(usercfg, "lola,joe:alice,lola"),
+ payload, time.time(), time.time()+200)
# And an mbox message with a last hop implicit in the address
- client.generateForwardMessage(
+ client.generateForwardPayloads(
+ directory,
parseAddress("mbox:granola@Lola"),
- payload,
- servers1=[Lola, Joe], servers2=[Alice, Lola])
+ parsePath(usercfg, "Lola,Joe:Alice"),
+ payload, time.time(), time.time()+200)
+
for fn, args, kwargs in getCalls():
self.assertEquals(fn, "buildForwardMessage")
@@ -6547,11 +6572,6 @@
clearCalls()
### Now try some failing cases for generateForwardMessage:
- # Empty path...
- self.assertRaises(MixError,
- client.generateForwardMessage,
- parseAddress("0xFFFE:zed"),
- "Z", [], [Alice])
# Temporarily replace BlockingClientConnection so we can try the client
# without hitting the network.
@@ -6577,9 +6597,11 @@
FakeBCC)
try:
client.sendForwardMessage(
+ directory,
parseAddress("mbox:granola@Lola"),
+ parsePath(usercfg,"alice,lola,joe,alice:joe,alice"),
"You only give me your information.",
- [Alice, Lola, Joe, Alice], [Joe, Alice])
+ time.time(), time.time()+300)
bcc = BCC_INSTANCE
# first hop is alice
self.assertEquals(bcc.addr, "10.0.0.9")