[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs] Finish implementing 0.0.1 client CLI; debug.



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv8695/lib/mixminion

Modified Files:
	ClientMain.py 
Log Message:
Finish implementing 0.0.1 client CLI; debug.

ClientMain changes
	- Implement CLI
	- Implement 'parseAddress'
	- Simplify and debug getPath, getServerInfo
	- Debug MixminionClient
	- Add stopgap code to make sure servers support the requested 
	  delivery methods.
	- Remove obsolete 'sendTestMessage' function
	- Clarify XXXX comments
	- Normalize whitespace


Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- ClientMain.py	22 Nov 2002 21:05:22 -0000	1.7
+++ ClientMain.py	2 Dec 2002 03:19:44 -0000	1.8
@@ -5,11 +5,12 @@
 
    Code for Mixminion command-line client.
 
-   XXXX THIS ISN'T IMPLEMENTED YET!  This file is just a placeholder that
-   XXXX routes a testing message through a few servers so I can make sure that
-   XXXX at least one configuration works.
+   NOTE: THIS IS NOT THE FINAL VERSION OF THE CODE.  It needs to
+         support replies and end-to-end encryption.  It also needs to
+         support directories.
    """
 
+# (NOTE: The stuff in the next comment isn't implemented yet.)
 # The client needs to store:
 #      - config
 #      - keys for pending SURBs
@@ -18,7 +19,7 @@
 #           info.)
 #          (Don't *name* files in dir; or at least, don't make their names
 #           magic.  Files can be: ServerInfos, ServerDirectories, or 'fake'
-#           directories.  Each server can have any number of virtual or 
+#           directories.  Each server can have any number of virtual or
 #           official tags.  Users should use the CLI to add/remove entries from
 #           dir.)
 #      - Per-system directory location is a neat idea, but individual users
@@ -28,6 +29,7 @@
 import getopt
 import sys
 import time
+import types
 
 from mixminion.Common import getLog, floorDiv, createPrivateDir, MixError, \
      MixFatalError
@@ -36,46 +38,51 @@
 import mixminion.MMTPClient
 import mixminion.Modules
 from mixminion.ServerInfo import ServerInfo
-from mixminion.Config import ClientConfig
+from mixminion.Config import ClientConfig, ConfigError
+from mixminion.Packet import ParseError, parseMBOXInfo, parseSMTPInfo
+from mixminion.Modules import MBOX_TYPE, SMTP_TYPE, DROP_TYPE
 
-class TrivialDirectoryCache:
-    def __init__(self, directory):
+class TrivialKeystore:
+    '''This is a temporary keystore implementation until we get a working
+       directory server implementation.'''
+    def __init__(self, directory, now=None):
 	self.directory = directory
 	createPrivateDir(directory)
 	self.byNickname = {}
 	self.byFilename = {}
 
+	if now is None:
+	    now = time.time()
+
 	for f in os.listdir(self.directory):
 	    p = os.path.join(self.directory, f)
 	    try:
 		info = ServerInfo(fname=p, assumeValid=0)
-	    except ConfigError, e:
+	    except ConfigError, _:
 		getLog().warn("Invalid server descriptor %s", p)
 		continue
 
 	    serverSection = info['Server']
 	    nickname = serverSection['Nickname']
-	    
+
 	    if '.' in f:
 		f = f[:f.rindex('.')]
-	    
-	    now = time.time()
 
-	    if now < serverSec['Valid-After']:
-		getLog().warn("Ignoring future decriptor %s", p)
+	    if now < serverSection['Valid-After']:
+		getLog().info("Ignoring future decriptor %s", p)
 		continue
-	    if now >= serverSec['Valid-Until']:
-		getLog().warn("Ignoring expired decriptor %s", p)
+	    if now >= serverSection['Valid-Until']:
+		getLog().info("Ignoring expired decriptor %s", p)
 		continue
-	    if now + 3*60*60 >= serverSec['Valid-Until']:
-		getLog().warn("Ignoring soon-to-expire decriptor %s", p)
+	    if now + 3*60*60 >= serverSection['Valid-Until']:
+		getLog().info("Ignoring soon-to-expire decriptor %s", p)
 		continue
 	    if self.byNickname.has_key(nickname):
 		getLog().warn(
 		    "Ignoring descriptor %s with duplicate nickname %s",
 		    p, nickname)
 		continue
-	    if self.byFilename.has_key(fname):
+	    if self.byFilename.has_key(f):
 		getLog().warn(
 		    "Ignoring descriptor %s with duplicate prefix %s",
 		    p, f)
@@ -87,18 +94,18 @@
 	if isinstance(name, ServerInfo):
 	    return name
 	if self.byNickname.has_key(name):
-	    return self.byNickname(name)
+	    return self.byNickname[name]
 	if self.byFilename.has_key(name):
-	    return self.byFilename(name)
+	    return self.byFilename[name]
 	return None
 
-    def getPath(self, minLength, serverList):
+    def getPath(self, serverList):
 	path = []
 	for s in serverList:
 	    if isinstance(s, ServerInfo):
 		path.append(s)
 	    elif isinstance(s, types.StringType):
-		server = self.dirCache.getServer(s)
+		server = self.getServerInfo(s)
 		if server is not None:
 		    path.append(server)
 		elif os.path.exists(s):
@@ -106,16 +113,13 @@
 			server = ServerInfo(fname=s, assumeValid=0)
 			path.append(server)
 		    except OSError, e:
-			getLog().error("Couldn't read descriptor %s: %s", 
-				       s, e)
-			sys.exit(1)
+			raise MixError("Couldn't read descriptor %s: %s" %
+				       (s, e))
 		    except ConfigError, e:
-			getLog().error("Couldn't parse descriptor %s: %s", 
-				       s, e)
-			sys.exit(1)
+			raise MixError("Couldn't parse descriptor %s: %s" %
+				       (s, e))
 		else:
-		    getLog().error("Couldn't find descriptor %s")
-		    sys.exit(1)
+		    raise MixError("Couldn't find descriptor %s" % s)
 	return path
 
     def getRandomServers(self, prng, n):
@@ -123,13 +127,13 @@
 	if len(vals) < n:
 	    raise MixFatalError("Not enough servers (%s requested)", n)
 	return prng.shuffle(vals, n)
-	
+
 def installDefaultConfig(fname):
     """Create a default, 'fail-safe' configuration in a given file"""
     getLog().warn("No configuration file found. Installing default file in %s",
 		  fname)
     f = open(os.path.expanduser(fname), 'w')
-    f.write("""\ 
+    f.write("""\
 # This file contains your options for the mixminion client.
 [Host]
 ## Use this option to specify a 'secure remove' command.
@@ -156,29 +160,17 @@
     f.close()
 
 class MixminionClient:
-    def __init__(self, conf=None):
-	if conf is None:
-	    conf = os.environ.get("MINIONRC", None)
-	    if conf is None: 
-		conf = "~/.minionrc"
-		if not os.path.exists(conf):
-		    installDefaultConfig(conf)
-	conf = os.path.expanduser(conf)
-	self.config = ClientConfig(fname=conf)
-
-	getLog().configure(self.config)
-	getLog().debug("Configuring client")
-	mixminion.Common.configureShredCommand(self.config)
-	mixminion.Crypto.init_crypto(self.config)
+    def __init__(self, conf):
+	self.config = conf
 
 	# Make directories
-	userdir = self.config['User']['UserDir']
+	userdir = os.path.expanduser(self.config['User']['UserDir'])
 	createPrivateDir(userdir)
 	createPrivateDir(os.path.join(userdir, 'surbs'))
 	createPrivateDir(os.path.join(userdir, 'servers'))
 
 	# Get directory cache
-	self.dirCache = TrivialDirectoryCache(
+	self.keystore = TrivialKeystore(
 	    os.path.join(userdir,"servers"))
 
 	# Initialize PRNG
@@ -191,13 +183,23 @@
 	self.sendMessages([message], firstHop)
 
     def generateForwardMessage(self, address, payload, path1, path2):
-	servers1 = self.dirCache.getPath(path1)
-	servers2 = self.dirCache.getPath(path2)
-	# XXXXencode payloadXXXX
-	
+	servers1 = self.keystore.getPath(path1)
+	servers2 = self.keystore.getPath(path2)
+
 	routingType, routingInfo, lastHop = address.getRouting()
-	if lastHop != None:
-	    servers2.append(self.dirCache.getServerInfo(lastHop))
+	if lastHop is None:
+	    # FFFF This is only a temporary solution.  It needs to get
+	    # FFFF rethought, or refactored into ServerInfo, or something.
+	    if routingType == SMTP_TYPE:
+		ok = path2[-1]['Delivery/SMTP'].get('Version',None)
+		if not ok:
+		    raise MixError("Last hop doesn't support SMTP")
+	    elif routingType == MBOX_TYPE:
+		ok = path2[-1]['Delivery/MBOX'].get('Version',None)
+		if not ok:
+		    raise MixError("Last hop doesn't support MBOX")
+	else:
+	    servers2.append(self.keystore.getServerInfo(lastHop))
 	msg = buildForwardMessage(payload,
 				  routingType, routingInfo,
 				  servers1, servers2)
@@ -214,7 +216,43 @@
 	finally:
 	    con.shutdown()
 
-def parseAddress():
+def parseAddress(s):
+    """DOCDOC 
+           format is mbox:name@server OR [smtp:]mailbox OR drop OR test:rinfo 
+           or 0xABCD:address """
+    # DOCDOC
+    # ???? Should this should get refactored into clientmodules, or someplace?
+    if s.lower() == 'drop':
+	return Address(DROP_TYPE, None, None)
+    elif s.lower() == 'test':
+	return Address(0xFFFE, "", None)
+    elif ':' not in s:
+	try:
+	    return Address(SMTP_TYPE, parseSMTPInfo(s).pack(), None)
+	except ParseError, _:
+	    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 "@" not in val:
+	    raise ParseError("No server for mailbox %s" % s)
+	mbox, server = val.split("@",1)
+	return Address(MBOX_TYPE, parseMBOXInfo(mbox).pack(), server)
+    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:
     def __init__(self, exitType, exitAddress, lastHop=None):
@@ -224,26 +262,6 @@
     def getRouting(self):
 	return self.exitType, self.exitAddress, self.lastHop
 
-def sendTestMessage(servers1, servers2):
-    assert len(servers1)
-    assert len(servers2)
-    payload = """ 
-           Insert
-	   Example
-	   Message
-	   Here.
-	   """
-    rt, ri = 0xFFFE, "deliver"
-    m = buildForwardMessage(payload, rt, ri, servers1, servers2)
-			    
-    firstHop = servers1[0]
-    b = mixminion.MMTPClient.BlockingClientConnection(firstHop.getAddr(),
-						      firstHop.getPort(),
-						      firstHop.getKeyID())
-    b.connect()
-    b.sendPacket(m)
-    b.shutdown()
-
 def readConfigFile(configFile):
     try:
 	return ClientConfig(fname=configFile)
@@ -251,31 +269,84 @@
 	print >>sys.stderr, "Error reading configuration file %r:"%configFile
 	print >>sys.stderr, "   ", str(e)
 	sys.exit(1)
-    except mixminion.Config.ConfigError, e:
+    except ConfigError, e:
 	print >>sys.stderr, "Error in configuration file %r"%configFile
 	print >>sys.stderr, str(e)
 	sys.exit(1)
+    return None #suppress pychecker warning
 
-# XXXX This isn't anything LIKE the final client interface: for now, I'm
-# XXXX just testing the server.
+# NOTE: This isn't anything LIKE the final client interface.  Many or all
+#       options will change between now and 1.0.0
 def runClient(cmd, args):
-    options, args = getopt.getopt(args, "hf:", ["help", "config="])
-    configFile = '~/.mixminion/mixminion.conf'
+    options, args = getopt.getopt(args, "hvf:i:t:",
+				  ["help", "verbose", "config=", "input=",
+				   "path1=", "path2=", "to="])
+    configFile = '~/.mixminionrc'
     usage = 0
+    inFile = "-"
+    verbose = 0
+    path1 = []
+    path2 = []
+    address = None
     for opt,val in options:
 	if opt in ('-h', '--help'):
 	    usage=1
 	elif opt in ('-f', '--config'):
 	    configFile = val
+	elif opt in ('-i', '--input'):
+	    inFile = val
+	elif opt in ('-v', '--verbose'):
+	    verbose = 1
+	elif opt == '--path1':
+	    path1.extend(val.split(","))
+	elif opt == '--path2':
+	    path2.extend(val.split(","))
+	elif opt in ('-t', '--to'):
+	    address = parseAddress(val)
+    if args:
+	print >>sys.stderr, "Unexpected options."
+	usage = 1
+    if not path1:
+	print >>sys.stderr, "First leg of path was not specified"
+	usage = 1
+    if not path2:
+	print >>sys.stderr, "Second leg of path was not specified"
+	usage = 1
+    if address is None:
+	print >>sys.stderr, "No recipient specified"
+	usage = 1
     if usage:
-	print >>sys.stderr, "Usage: %s [-h] [-f configfile] server1 server2..."%cmd
+	print >>sys.stderr, """\
+Usage: %s [-h] [-v] [-f configfile] [-i inputfile]
+          [--path1=server1,server2,...]
+          [--path2=server1,server2,...] [-t <address>]"""%cmd
 	sys.exit(1)
-    config = readConfigFile(os.path.expanduser(configFile))
 
+    if configFile is None:
+	configFile = os.environ.get("MIXMINIONRC", None)
+	if configFile is None:
+	    configFile = "~/.mixminionrc"
+
+    configFile = os.path.expanduser(configFile)
+    if not os.path.exists(configFile):
+	installDefaultConfig(configFile)
+    config = readConfigFile(configFile)
+
+    getLog().configure(config)
+    if verbose:
+	getLog().setMinSeverity("DEBUG")
+
+    getLog().debug("Configuring client")
+    mixminion.Common.configureShredCommand(config)
     mixminion.Crypto.init_crypto(config)
-    if len(args) < 2:
-	print >> sys.stderr, "I need at least 2 servers"
-    servers = [ ServerInfo(fn) for fn in args ]
-    idx = floorDiv(len(servers),2)
 
-    sendTestMessage(servers[:idx], servers[idx:])
+    client = MixminionClient(config)
+
+    if inFile == '-':
+	f = sys.stdin
+    else:
+	f = open(inFile, 'r')
+    payload = f.read()
+    f.close()
+
+    client.sendForwardMessage(address, payload, path1, path2)