[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Check in some long-pending tweaks from my laptop. Impl...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv18251/lib/mixminion
Modified Files:
BuildMessage.py ClientDirectory.py ClientMain.py Common.py
Config.py MMTPClient.py Packet.py ServerInfo.py test.py
Added Files:
NetUtils.py
Log Message:
Check in some long-pending tweaks from my laptop. Implements DNS, refactors.
NetUtils (New File):
- Added new module to hold general-purpose networking code. Currently
includes gethostname/getaddrinfo wrappers; timeout helper code; and
IPv6-support testing.
BuildMessage:
- Finally remove the old build*Message calls and switch to the build*Packet
messages. The difference is that the old ones combined payload encoding
with packet building, while the new ones take a complete (~28K) payload
generated by encodeMessage.
ClientDirectory:
- Debug lots of new code.
- Turn timeout from global to argument
- Use new NetUtils timeout logic.
ClientDirectory,ClientMain:
- Make DROP packets work again.
Common, MMTPClient:
- Move TimeoutError into Common
Common:
- Add function to detect strings that look like hostnames
- Debug TimeoutQueue
Config:
- Add _parseHost and _parseIP6
MMTPClient:
- Handle DNS lookup for MMTPHostInfo code and (possible) IPv6 result.
- Use new NetUtils timeout code
Packet:
- Don't allow MMTPHostInfo objects when hostname is not a possible hostname
- Force hostnames to lower case
ServerInfo, ServerKeys:
- Rename Packet-Formats to Packet-Versions
- Deprecate IP, add Hostname.
- Deprecate Key-Digest.
- Add getIP, getHostname
- Make getKeyDigest compute digest of identity key; we don't need Key-Digest
any more.
- Refactor protocol matchup code
- Add canStartAt function to tell whether we can connect directly to a given
server.
test:
- Add test for new packet types and config types
- Change code to use new BuildMessage interfaces
DNSFarm:
- Handle panding requests correctly.
- Move getIP functions to NetUtils.
MMTPServer,ServerMain:
- Implement DNS farm hookup for MMTPHostInfo routing.
ServerConfig:
- Add hostnames to configuration.
--- NEW FILE: NetUtils.py ---
# Copyright 2002-2003 Nick Mathewson. See LICENSE for licensing information.
# Id: ClientMain.py,v 1.89 2003/06/05 18:41:40 nickm Exp $
"""mixminion.NetUtils
This module holds helper code for network-related operations."""
__all__ = [ ]
import errno
import select
import signal
import socket
import time
from mixminion.Common import LOG, TimeoutError
#======================================================================
PREFER_INET4 = 1
AF_INET = socket.AF_INET
try:
AF_INET6 = socket.AF_INET6
except AttributeError:
AF_INET6 = "<Sorry, no IP6>"
# For windows -- list of errno values that we can expect when blocking IO
# blocks on a connect.
IN_PROGRESS_ERRNOS = [ getattr(errno, ename)
for ename in [ "EINPROGRESS", "WSAEWOULDBLOCK"]
if hasattr(errno,ename) ]
del ename
#======================================================================
if hasattr(socket, 'getaddrinfo'):
def getIPs(name):
"""DOCDOC"""
r = []
ai = socket.getaddrinfo(name,None)
now = time.time()
for family, _, _, _, addr in ai:
if family not in (AF_INET, AF_INET6):
continue
r.append((family, addr[0], now))
return r
else:
def getIPs(name):
addr = socket.gethostbyname(name)
return [ (AF_INET, addr, time.time()) ]
def getIP(name, preferIP4=PREFER_INET4):
try:
r = getIPs(name)
inet4 = [ addr for addr in r if addr[0] == AF_INET ]
inet6 = [ addr for addr in r if addr[0] == AF_INET6 ]
if not (inet4 or inet6):
LOG.error("getIP returned no inet addresses!")
return ("NOENT", "No inet addresses returned", time.time())
if inet4: best4=inet4[0]
if inet6: best6=inet6[0]
if preferIP4:
res = best4 or best6
else:
res = best6 or best4
protoname = (res[0] == AF_INET) and "inet" or "inet6"
LOG.trace("Result for getIP(%r): %s:%s (%d others dropped)",
name,protoname,res[1],len(r)-1)
return res
except socket.error, e:
LOG.trace("Result for getIP(%r): error:%r",name,e)
if len(e.args) == 2:
return ("NOENT", str(e[1]), time.time())
else:
return ("NOENT", str(e), time.time())
#----------------------------------------------------------------------
_SOCKETS_SUPPORT_TIMEOUT = hasattr(socket.SocketType, "settimeout")
def connectWithTimeout(sock,dest,timeout=None):
"""DOCDOC; sock must be blocking."""
if timeout is None:
return sock.connect(dest)
elif _SOCKETS_SUPPORT_TIMEOUT:
t = sock.gettimeout()
try:
sock.settimeout(timeout)
try:
return sock.connect(dest)
except socket.error, e:
if e[0] in IN_PROGRESS_ERRNOS:
raise TimeoutError()
else:
raise
finally:
sock.settimeout(t)
else:
sock.setblocking(0)
try:
try:
sock.connect(dest)
except socket.error, e:
if e[0] not in IN_PROGRESS_ERRNOS:
raise
fd = sock.fileno()
try:
_,wfds,_ = select.select([],[fd],[], timeout)
except select.error, e:
raise
if not wfds:
raise TimeoutError()
## try:
## sock.connect(dest)
## except select.error, e:
## if e[0] != errno.EISCONN:
## raise
finally:
sock.setblocking(1)
#----------------------------------------------------------------------
_PREV_DEFAULT_TIMEOUT = None
def setAlarmTimeout(timeout):
if hasattr(signal, 'alarm'):
def sigalrmHandler(sig,_): pass
signal.signal(signal.SIGALRM, sigalrmHandler)
signal.alarm(timeout)
def clearAlarmTimeout(timeout):
if hasattr(signal, 'alarm'):
signal.alarm(0)
def setGlobalTimeout(timeout,noalarm=0):
global _PREV_DEFAULT_TIMEOUT
assert timeout > 0
if _SOCKETS_SUPPORT_TIMEOUT:
_PREV_DEFAULT_TIMEOUT = socket.getdefaulttimeout()
socket.setdefaultimeout(timeout)
elif not noalarm:
setAlarmTimeout(timeout)
def exceptionIsTimeout(ex):
if isinstance(ex, socket.error):
if ex[0] in IN_PROGRESS_ERRNOS:
return 1
elif ex[0] == errno.EINTR and not _SOCKETS_SUPPORT_TIMEOUT:
return 1
return 0
def unsetGlobalTimeout(noalarm=0):
global _PREV_DEFAULT_TIMEOUT
if _SOCKETS_SUPPORT_TIMEOUT:
socket.setdefaulttimeout(_PREV_DEFAULT_TIMEOUT)
elif hasattr(signal, 'alarm') and not noalarm:
signal.alarm(0)
#----------------------------------------------------------------------
_PROTOCOL_SUPPORT = None
def getProtocolSupport():
"""DOCDOC"""
global _PROTOCOL_SUPPORT
if _PROTOCOL_SUPPORT is not None:
return _PROTOCOL_SUPPORT
res = [0,0]
for pos, familyname, loopback in ((0, "AF_INET", "127.0.0.1"),
(1, "AF_INET6", "::1")):
family = getattr(socket, familyname)
if family is None: continue
s = None
try:
s = socket.socket(family, socket.SOCK_DGRAM)
s.connect((loopback, 9)) # discard port
res[pos] = 1 # Everything worked, so we must have IP(foo) support.
except socket.error:
pass
if s is not None:
s.close()
_PROTOCOL_SUPPORT = res
return res
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.59
retrieving revision 1.60
diff -u -d -r1.59 -r1.60
--- BuildMessage.py 9 Oct 2003 15:26:15 -0000 1.59
+++ BuildMessage.py 19 Oct 2003 03:12:01 -0000 1.60
@@ -20,8 +20,8 @@
if sys.version_info[:3] < (2,2,0):
import mixminion._zlibutil as zlibutil
-__all__ = ['buildForwardMessage', 'buildEncryptedMessage',
- 'buildReplyMessage', 'buildReplyBlock', 'checkPathLength',
+__all__ = ['buildForwardPacket', 'buildEncryptedForwardPacket',
+ 'buildReplyPacket', 'buildReplyBlock', 'checkPathLength',
'encodeMessage', 'decodePayload' ]
def encodeMessage(message, overhead, uncompressedFragmentPrefix="",
@@ -82,26 +82,13 @@
rawFragments[i] = None
return fragments
-#XXXX006 Most of the build*Message functions here should be 'build*Packet'.
-#XXXX006 All of the build*Message functions should be replaced with their
-#XXXX006 _build*Message variants.
-def buildForwardMessage(payload, exitType, exitInfo, path1, path2,
- paddingPRNG=None):
- # Compress, pad, and checksum the payload.
- if payload is not None and exitType != DROP_TYPE:
- payloads = encodeMessage(payload, 0, "", paddingPRNG)
- if len(payloads) != 1:
- raise MixError("buildForwardMessage does not support fragmented payloads")
- payload = payloads[0]
- LOG.debug("Encoding forward message for %s-byte payload",len(payload))
- else:
- payload = (paddingPRNG or Crypto.getCommonPRNG()).getBytes(PAYLOAD_LEN)
- LOG.debug("Generating DROP message with %s bytes", PAYLOAD_LEN)
-
- return _buildForwardMessage(payload, exitType, exitInfo, path1, path2,
- paddingPRNG)
+def buildRandomPayload(paddingPRNG=None):
+ """DOCDOC"""
+ if not paddingPRNG:
+ paddingPRNG = Crypto.getCommonPRNG()
+ return paddingPRNG.getBytes(PAYLOAD_LEN)
-def _buildForwardMessage(payload, exitType, exitInfo, path1, path2,
+def buildForwardPacket(payload, exitType, exitInfo, path1, path2,
paddingPRNG=None):
"""Construct a forward message.
payload: The payload to deliver. Must be exactly 28K. If the
@@ -138,19 +125,11 @@
if not suppressTag:
tag = _getRandomTag(paddingPRNG)
exitInfo = tag + exitInfo
- return _buildMessage(payload, exitType, exitInfo, path1, path2,
- paddingPRNG)
+ return _buildPacket(payload, exitType, exitInfo, path1, path2,
+ paddingPRNG)
-def buildEncryptedForwardMessage(payload, exitType, exitInfo, path1, path2,
- key, paddingPRNG=None, secretRNG=None):
- payloads = encodeMessage(payload, ENC_FWD_OVERHEAD, "", paddingPRNG)
- if len(payloads) != 1:
- raise UIError("No support yet for fragmented encrypted messages")
- return _buildEncryptedForwardMessage(payloads[0], exitType, exitInfo,
- path1, path2, key, paddingPRNG,
- secretRNG)
-def _buildEncryptedForwardMessage(payload, exitType, exitInfo, path1, path2,
+def buildEncryptedForwardPacket(payload, exitType, exitInfo, path1, path2,
key, paddingPRNG=None, secretRNG=None):
"""Construct a forward message encrypted with the public key of a
given user.
@@ -208,15 +187,9 @@
assert len(payload) == 28*1024
# And now, we can finally build the message.
- return _buildMessage(payload, exitType, exitInfo, path1, path2,paddingPRNG)
-
-def buildReplyMessage(payload, path1, replyBlock, paddingPRNG=None):
- payloads = encodeMessage(payload, 0, "", paddingPRNG)
- if len(payloads) != 1:
- raise UIError("No support yet for fragmented reply messages")
- return _buildReplyMessage(payloads[0], path1, replyBlock, paddingPRNG)
+ return _buildPacket(payload, exitType, exitInfo, path1, path2,paddingPRNG)
-def _buildReplyMessage(payload, path1, replyBlock, paddingPRNG=None):
+def buildReplyPacket(payload, path1, replyBlock, paddingPRNG=None):
"""Build a message using a reply block. 'path1' is a sequence of
ServerInfo for the nodes on the first leg of the path. 'payload'
must be exactly 28K long.
@@ -237,7 +210,7 @@
Crypto.PAYLOAD_ENCRYPT_MODE)
payload = Crypto.lioness_decrypt(payload, k)
- return _buildMessage(payload, None, None,
+ return _buildPacket(payload, None, None,
path1=path1, path2=replyBlock)
def _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime=0,
@@ -285,9 +258,10 @@
header = _buildHeader(path, headerSecrets, exitType, tag+exitInfo,
paddingPRNG=Crypto.getCommonPRNG())
+ # XXXX007 switch to Host info.
return ReplyBlock(header, expiryTime,
SWAP_FWD_IPV4_TYPE,
- path[0].getRoutingInfo().pack(), sharedKey), secrets, tag
+ path[0].getIPV4Info().pack(), sharedKey), secrets, tag
# Maybe we shouldn't even allow this to be called with userKey==None.
def buildReplyBlock(path, exitType, exitInfo, userKey,
@@ -482,8 +456,8 @@
return _decodeReplyPayload(payload, secrets, check=1)
#----------------------------------------------------------------------
-def _buildMessage(payload, exitType, exitInfo,
- path1, path2, paddingPRNG=None, paranoia=0):
+def _buildPacket(payload, exitType, exitInfo,
+ path1, path2, paddingPRNG=None, paranoia=0):
"""Helper method to create a message.
The following fields must be set:
Index: ClientDirectory.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientDirectory.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- ClientDirectory.py 17 Oct 2003 17:23:24 -0000 1.6
+++ ClientDirectory.py 19 Oct 2003 03:12:01 -0000 1.7
@@ -15,7 +15,6 @@
import operator
import os
import re
-import signal
import socket
import stat
import time
@@ -25,6 +24,7 @@
import mixminion.ClientMain #XXXX
import mixminion.Config
import mixminion.Crypto
+import mixminion.NetUtils
import mixminion.Packet
import mixminion.ServerInfo
@@ -54,7 +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.
+ # byKeyID: Map from desc.getKeyDigest() to list of ServerInfo.
# byCapability: Map from capability ('mbox'/'smtp'/'relay'/None) to
# list of (ServerInfo, source) tuples.
# allServers: Same as byCapability[None]
@@ -101,22 +101,15 @@
self.downloadDirectory()
else:
LOG.debug("Directory is up to date.")
- def downloadDirectory(self):
+ def downloadDirectory(self, timeout=15):
"""Download a new directory from the network, validate it, and
rescan its servers."""
- # FFFF Make configurable
- DIRECTORY_TIMEOUT = 15
# Start downloading the directory.
url = MIXMINION_DIRECTORY_URL
LOG.info("Downloading directory from %s", url)
# XXXX Refactor download logic.
-
- if hasattr(signal, 'alarm'):
- def sigalrmHandler(sig, _):
- pass
- signal.signal(signal.SIGALRM, sigalrmHandler)
- signal.alarm(DIRECTORY_TIMEOUT)
+ if timeout: mixminion.NetUtils.setGlobalTimeout(timeout)
try:
try:
infile = urllib2.urlopen(url)
@@ -125,14 +118,13 @@
("Couldn't connect to directory server: %s.\n"
"Try '-D no' to run without downloading a directory.")%e)
except socket.error, e:
- if getattr(e,"errno",-1) == errno.EINTR:
+ if mixminion.NetUtils.exceptionIsTimeout(e):
raise UIError("Connection to directory server timed out")
else:
raise UIError("Error connecting: %s"%e)
- raise UIError
finally:
- if hasattr(signal, 'alarm'):
- signal.alarm(0)
+ if timeout:
+ mixminion.NetUtils.unsetGlobalTimeout()
# Open a temporary output file.
if url.endswith(".gz"):
@@ -366,7 +358,7 @@
for info, where in self.serverList:
nn = info.getNickname().lower()
lists = [ self.allServers, self.byNickname.setdefault(nn, []),
- self.byKeyID.setdefault(info.getKeyID(), []) ]
+ self.byKeyID.setdefault(info.getKeyDigest(), []) ]
for c in info.getCaps():
lists.append( self.byCapability[c] )
for lst in lists:
@@ -658,7 +650,8 @@
for c in relays:
if ((prev and c.hasSameNicknameAs(prev)) or
(next and c.hasSameNicknameAs(next)) or
- (prev and not prev.canRelayTo(c)) or
+ (prev and not prev.canRelayTo(c)) or
+ ((not prev) and not c.canStartAt()) or
(next and not c.canRelayTo(next))):
continue
candidates.append(c)
@@ -698,6 +691,13 @@
for e in p:
e.validate(self, startAt, endAt)
+ #XXXX006 make sure p can never be empty!
+
+ # If there is a 1st element, make sure we can route to it.
+ fixed = p[0].getFixedServer(self, startAt, endAt)
+ if fixed and not fixed.canStartAt():
+ raise UIError("Cannot relay messages to %s"%fixed.getNickname())
+
# When there are 2 elements in a row, make sure each can route to
# the next.
prevFixed = None
@@ -796,6 +796,8 @@
def setFragmented(self, isSSFragmented, nFragments):
self.isSSFragmented = isSSFragmented
self.nFragments = nFragments
+ def hasPayload(self):
+ return self.exitType not in ('drop', DROP_TYPE)
def setExitSize(self, exitSize):
self.exitSize = exitSize
def setHeaders(self, headers):
Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.121
retrieving revision 1.122
diff -u -d -r1.121 -r1.122
--- ClientMain.py 14 Oct 2003 00:08:27 -0000 1.121
+++ ClientMain.py 19 Oct 2003 03:12:01 -0000 1.122
@@ -7,7 +7,6 @@
"""
__all__ = [ 'Address', 'ClientKeyring', 'MixminionClient' ]
-
import getopt
import os
@@ -223,7 +222,7 @@
fails."""
assert not (forceQueue and forceNoQueue)
- allPackets = self.generateForwardPayloads(
+ allPackets = self.generateForwardPackets(
directory, address, pathSpec, message, startAt, endAt)
for routing, packets in self._sortPackets(allPackets):
@@ -273,7 +272,7 @@
return block
- def generateForwardPayloads(self, directory, address, pathSpec, message,
+ def generateForwardPackets(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
@@ -289,11 +288,15 @@
fragmentedMessagePrefix = address.getFragmentedMessagePrefix()
LOG.info("Generating payload(s)...")
r = []
- payloads = mixminion.BuildMessage.encodeMessage(message, 0,
- fragmentedMessagePrefix)
- if len(payloads) > 1:
- address.setFragmented(1,len(payloads))
+ if address.hasPayload():
+ payloads = mixminion.BuildMessage.encodeMessage(message, 0,
+ fragmentedMessagePrefix)
+ if len(payloads) > 1:
+ address.setFragmented(1,len(payloads))
+ else:
+ address.setFragmented(0,1)
else:
+ payloads = [ mixminion.BuildMessage.buildRandomPayload() ]
address.setFragmented(0,1)
routingType, routingInfo, _ = address.getRouting()
@@ -302,7 +305,7 @@
for p, (path1,path2) in zip(payloads, directory.generatePaths(
len(payloads), pathSpec, address, startAt, endAt)):
- msg = mixminion.BuildMessage._buildForwardMessage(
+ msg = mixminion.BuildMessage.buildForwardPacket(
p, routingType, routingInfo, path1, path2,
self.prng)
r.append( (msg, path1[0]) )
@@ -341,7 +344,7 @@
startAt,endAt)):
assert path1 and not path2
LOG.info("Generating packet...")
- msg = mixminion.BuildMessage.buildReplyMessage(
+ msg = mixminion.BuildMessage.buildReplyPacket(
payload, path1, surb, self.prng)
surbLog.markSURBUsed(surb)
@@ -1028,11 +1031,11 @@
# Read the message.
# XXXX Clean up this ugly control structure.
- if address and inFile is None and address.getRouting()[0] == DROP_TYPE:
+ if address and inFile is None and not address.hasPayload():
message = None
LOG.info("Sending dummy message")
else:
- if address and address.getRouting()[0] == DROP_TYPE:
+ if address and not address.hasPayload():
raise UIError("Cannot send a message in a DROP packet")
if inFile is None:
@@ -1052,7 +1055,6 @@
message = "%s%s" % (headerStr, message)
address.setExitSize(len(message))
-
if parser.exitAddress.isReply:
client.sendReplyMessage(
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.112
retrieving revision 1.113
diff -u -d -r1.112 -r1.113
--- Common.py 13 Oct 2003 17:31:22 -0000 1.112
+++ Common.py 19 Oct 2003 03:12:01 -0000 1.113
@@ -71,6 +71,10 @@
"""Exception class for MMTP protocol violations"""
pass
+class TimeoutError(MixProtocolError):
+ """Exception raised for protocol timeout."""
+ pass
+
class MixProtocolReject(MixProtocolError):
"""Exception class for server-rejected packets."""
pass
@@ -216,6 +220,19 @@
else:
return "%s%s%s %s" % (punc.join(lst[0:-1]), punc, compound, lst[-1])
+_HOST_CHARS = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
+ "abcdefghijklmnopqrstuvwxyz"+
+ "0123456789.")
+def isPlausibleHostname(s):
+ """DOCDOC"""
+ if not s:
+ return 0
+ if s.translate(_ALLCHARS, _HOST_CHARS):
+ return 0
+ if stringContains(s, "..") or s.startswith("."):
+ return 0
+ return 1
+
#----------------------------------------------------------------------
# Functions to generate and parse OpenPGP-style ASCII armor
@@ -1565,7 +1582,7 @@
if timeout is None:
return MessageQueue.get(self, blocking)
- # Adapted from 'Condition
+ # Adapted from 'Condition'.
_time = time.time
_sleep = time.sleep
deadline = timeout+_time()
@@ -1574,7 +1591,7 @@
try:
return MessageQueue.get(self,0)
except QueueEmpty:
- remaining = endTime-_time()
+ remaining = deadline-_time()
if remaining <= 0:
raise
delay = min(delay*2,remaining,0.2)
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.60
retrieving revision 1.61
diff -u -d -r1.60 -r1.61
--- Config.py 9 Oct 2003 15:26:16 -0000 1.60
+++ Config.py 19 Oct 2003 03:12:02 -0000 1.61
@@ -56,6 +56,7 @@
import os
import re
import socket # for inet_aton and error
+import string # for atoi
try:
import pwd
except ImportError:
@@ -213,6 +214,61 @@
return i
+_IP6_CHARS="01233456789ABCDEFabcdef:."
+
+def _parseIP6(ip6):
+ """DOCDOC"""
+ ip = ip6.strip()
+ bad = ip6.translate(mixminion.Common._ALLCHARS, _IP6_CHARS)
+ if bad:
+ raise ConfigError("Invalid characters %r in address %r"%(bad,ip))
+ if len(ip) < 2:
+ raise ConfigError("IPv6 address %r is too short"%ip)
+
+ items = ip.split(":")
+ if not items:
+ raise ConfigError("Empty IPv6 address")
+ if items[:2] == ["",""]:
+ del items[0]
+ if items[-2:] == ["",""]:
+ del items[-1]
+ foundNils = 0
+ foundWords = 0 # 16-bit words
+
+ for item in items:
+ if item == "":
+ foundNils += 1
+ elif '.' in item:
+ _parseIP(item)
+ if item is not items[-1]:
+ raise ConfigError("Embedded IPv4 address %r must appear at end of IPv6 address %r"%(item,ip))
+ foundWords += 2
+ else:
+ try:
+ val = string.atoi(item,16)
+ except ValueError:
+ raise ConfigError("IPv6 word %r did not parse"%item)
+ if not (0 <= val <= 0xFFFF):
+ raise ConfigError("IPv6 word %r out of range"%item)
+ foundWords += 1
+
+ if foundNils > 1:
+ raise ConfigError("Too many ::'s in IPv6 address %r"%ip)
+ elif foundNils == 0 and foundWords < 8:
+ raise ConfigError("IPv6 address %r is too short"%ip)
+ elif foundWords > 8:
+ raise ConfigError("IPv6 address %r is too long"%ip)
+
+ return ip
+
+
+def _parseHost(host):
+ """DOCDOC"""
+ host = host.strip()
+ if not mixminion.Common.isPlausibleHostname(host):
+ raise ConfigError("%r doesn't look like a valid hostname",host)
+ return host
+
# Regular expression to match 'address sets' as used in Allow/Deny
# configuration lines. General format is "<IP|*> ['/'MASK] [PORT['-'PORT]]"
_address_set_re = re.compile(r'''^(\d+\.\d+\.\d+\.\d+|\*)
Index: MMTPClient.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/MMTPClient.py,v
retrieving revision 1.38
retrieving revision 1.39
diff -u -d -r1.38 -r1.39
--- MMTPClient.py 24 Sep 2003 01:03:14 -0000 1.38
+++ MMTPClient.py 19 Oct 2003 03:12:02 -0000 1.39
@@ -15,17 +15,13 @@
__all__ = [ "BlockingClientConnection", "sendMessages" ]
-import errno
-import signal
import socket
import mixminion._minionlib as _ml
+import mixminion.NetUtils
from mixminion.Crypto import sha1, getCommonPRNG
from mixminion.Common import MixProtocolError, MixProtocolReject, \
- MixProtocolBadAuth, LOG, MixError, formatBase64
-
-class TimeoutError(MixProtocolError):
- """Exception raised for protocol timeout."""
- pass
+ MixProtocolBadAuth, LOG, MixError, formatBase64, TimeoutError
+from mixminion.Packet import IPV4Info, MMTPHostInfo
class BlockingClientConnection:
"""A BlockingClientConnection represents a MMTP connection to a single
@@ -44,9 +40,10 @@
# PROTOCOL_VERSIONS: (static) a list of protocol versions we allow,
# in decreasing order of preference.
PROTOCOL_VERSIONS = ['0.3']
- def __init__(self, targetIP, targetPort, targetKeyID):
+ def __init__(self, targetFamily, targetAddr, targetPort, targetKeyID):
"""Open a new connection."""
- self.targetIP = targetIP
+ self.targetFamily = targetFamily
+ self.targetAddr = targetAddr
self.targetPort = targetPort
if targetKeyID != '\x00' *20:
self.targetKeyID = targetKeyID
@@ -83,50 +80,29 @@
else:
tp = str(type(err))
e = MixProtocolError("%s error while %s to %s:%s: %s" %(
- tp, action, self.targetIP, self.targetPort, err))
+ tp, action, self.targetAddr, self.targetPort, err))
e.base = err
raise e
def _connect(self, connectTimeout=None):
"""Helper method; implements _connect."""
- # FFFF There should be a way to specify timeout for communication.
- if not hasattr(signal, 'alarm'): #WWWW
- connectTimeout = None
-
- def sigalarmHandler(sig, _):
- assert sig == signal.SIGALRM
- if connectTimeout:
- signal.signal(signal.SIGALRM, sigalarmHandler)
-
# Connect to the server
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock = socket.socket(self.targetFamily, socket.SOCK_STREAM)
self.sock.setblocking(1)
- LOG.debug("Connecting to %s:%s", self.targetIP, self.targetPort)
+ LOG.debug("Connecting to %s:%s", self.targetAddr, self.targetPort)
# Do the TLS handshaking
- if connectTimeout:
- signal.alarm(connectTimeout)
- try:
- try:
- self.sock.connect((self.targetIP,self.targetPort))
- except socket.error, e:
- if e[0] == errno.EINTR:
- raise TimeoutError("Connection timed out")
- else:
- raise MixProtocolError("Error connecting: %s" % e)
- finally:
- if connectTimeout:
- signal.alarm(0)
-
- LOG.debug("Handshaking with %s:%s",self.targetIP, self.targetPort)
+ mixminion.NetUtils.connectWithTimeout(
+ self.sock, (self.targetAddr, self.targetPort), connectTimeout)
+
+ LOG.debug("Handshaking with %s:%s",self.targetAddr, self.targetPort)
self.tls = self.context.sock(self.sock.fileno())
self.tls.connect()
LOG.debug("Connected.")
-
# Check the public key of the server to prevent man-in-the-middle
# attacks.
self.certCache.check(self.tls, self.targetKeyID,
- "%s:%s"%(self.targetIP,self.targetPort))
+ "%s:%s"%(self.targetAddr,self.targetPort))
####
# Protocol negotiation
@@ -207,7 +183,7 @@
def shutdown(self):
"""Close this connection."""
LOG.debug("Shutting down connection to %s:%s",
- self.targetIP, self.targetPort)
+ self.targetAddr, self.targetPort)
try:
if self.tls is not None:
self.tls.shutdown()
@@ -221,7 +197,8 @@
"""Sends a list of messages to a server. Raise MixProtocolError on
failure.
- routing -- an instance of mixminion.Packet.IPV4Info.
+ routing -- an instance of mixminion.Packet.IPV4Info or
+ mixminion.Packet.MMTPHostInfo.
If routing.keyinfo == '\000'*20, we ignore the server's
keyid.
packetList -- a list of 32KB packets and control strings. Control
@@ -242,7 +219,17 @@
else:
packets.append(("MSG", p))
- con = BlockingClientConnection(routing.ip,routing.port,routing.keyinfo)
+ if isinstance(routing, IPV4Info):
+ family, addr = socket.AF_INET, routing.ip
+ else:
+ assert isinstance(routing, MMTPHostInfo)
+ LOG.trace("Looking up %s...",routing.hostname)
+ family, addr, _ = mixminion.NetUtils.getIP(routing.hostname)
+ if family == "NOENT":
+ raise MixProtocolError("Couldn't resolve hostname %s: %s",
+ routing.hostname, addr)
+
+ con = BlockingClientConnection(family,addr,routing.port,routing.keyinfo)
try:
con.connect(connectTimeout=connectTimeout)
for idx in xrange(len(packets)):
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.62
retrieving revision 1.63
diff -u -d -r1.62 -r1.63
--- Packet.py 13 Oct 2003 17:32:04 -0000 1.62
+++ Packet.py 19 Oct 2003 03:12:02 -0000 1.63
@@ -16,12 +16,12 @@
'FRAGMENT_MESSAGEID_LEN', 'FRAGMENT_TYPE',
'HEADER_LEN', 'IPV4Info', 'MAJOR_NO', 'MBOXInfo',
'MBOX_TYPE', 'MINOR_NO', 'MIN_EXIT_TYPE',
- 'MIN_SUBHEADER_LEN', 'Packet',
+ 'MIN_SUBHEADER_LEN', 'MMTPHostInfo', 'Packet',
'OAEP_OVERHEAD', 'PAYLOAD_LEN', 'ParseError', 'ReplyBlock',
'ReplyBlock', 'SECRET_LEN', 'SINGLETON_PAYLOAD_OVERHEAD',
'SMTPInfo', 'SMTP_TYPE', 'SWAP_FWD_IPV4_TYPE', 'SingletonPayload',
'Subheader', 'TAG_LEN', 'TextEncodedMessage',
- 'parseHeader', 'parseIPV4Info',
+ 'parseHeader', 'parseIPV4Info', 'parseMMTPHostInfo',
'parseMBOXInfo', 'parsePacket', 'parseMessageAndHeaders',
'parsePayload', 'parseReplyBlock',
'parseReplyBlocks', 'parseSMTPInfo', 'parseSubheader',
@@ -37,7 +37,8 @@
import zlib
from socket import inet_ntoa, inet_aton
from mixminion.Common import MixError, MixFatalError, encodeBase64, \
- floorDiv, formatTime, isSMTPMailbox, LOG, armorText, unarmorText
+ floorDiv, formatTime, isSMTPMailbox, LOG, armorText, unarmorText, \
+ isPlausibleHostname
from mixminion.Crypto import sha1
if sys.version_info[:3] < (2,2,0):
@@ -636,14 +637,16 @@
port, keyinfo = struct.unpack(MMTP_HOST_PAT, s[:2+DIGEST_LEN])
except struct.error:
raise ParseError("Misformatted routing info")
+ host = s[2+DIGEST_LEN:]
+ if not isPlausibleHostname(host):
+ raise ParseError("Nonsensical hostname")
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.hostname = hostname.lower()
self.port = port
self.keyinfo = keyinfo
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.58
retrieving revision 1.59
diff -u -d -r1.58 -r1.59
--- ServerInfo.py 13 Oct 2003 17:30:24 -0000 1.58
+++ ServerInfo.py 19 Oct 2003 03:12:02 -0000 1.59
@@ -15,12 +15,13 @@
import mixminion.Config
import mixminion.Crypto
+import mixminion.MMTPClient
import mixminion.Packet
from mixminion.Common import IntervalSet, LOG, MixError, createPrivateDir, \
formatBase64, formatDate, formatTime, readPossiblyGzippedFile
from mixminion.Config import ConfigError
-from mixminion.Packet import IPV4Info
+from mixminion.Packet import IPV4Info, MMTPHostInfo
from mixminion.Crypto import CryptoError, DIGEST_LEN, pk_check_signature
# Longest allowed Contact email
@@ -64,16 +65,18 @@
"Packet-Key": ("REQUIRE", C._parsePublicKey, None),
"Contact-Fingerprint": ("ALLOW", None, None),
# XXXX010 change these next few to "REQUIRE".
- "Packet-Formats": ("ALLOW", None, None),
+ "Packet-Formats": ("ALLOW", None, None),#XXXX007 remove
+ "Packet-Versions": ("ALLOW", None, None),
"Software": ("ALLOW", None, None),
"Secure-Configuration": ("ALLOW", C._parseBoolean, None),
"Why-Insecure": ("ALLOW", None, None),
},
"Incoming/MMTP" : {
"Version": ("REQUIRE", None, None),
- "IP": ("REQUIRE", C._parseIP, None),
+ "IP": ("ALLOW", C._parseIP, None),#XXXX007 remove
+ "Hostname": ("ALLOW", C._parseHost, None),#XXXX008 require
"Port": ("REQUIRE", C._parseInt, None),
- "Key-Digest": ("REQUIRE", C._parseBase64, None),
+ "Key-Digest": ("ALLOW", C._parseBase64, None),#XXXX007 rmv
"Protocols": ("REQUIRE", None, None),
"Allow": ("ALLOW*", C._parseAddressSet_allow, None),
"Deny": ("ALLOW*", C._parseAddressSet_deny, None),
@@ -221,6 +224,8 @@
if len(inMMTP['Key-Digest']) != DIGEST_LEN:
raise ConfigError("Invalid key digest %s"%
formatBase64(inMMTP['Key-Digest']))
+ if not inMMTP['IP'] and not inMMTP['Hostname']:
+ raise ConfigError("Incoming/MMTP section has neither IP nor hostname")
## Outgoing/MMTP section
outMMTP = self['Outgoing/MMTP']
@@ -243,9 +248,13 @@
descriptor."""
return self['Server']['Digest']
- def getAddr(self):
+ def getIP(self):
"""Returns this server's IP address"""
- return self['Incoming/MMTP']['IP']
+ return self['Incoming/MMTP'].get('IP')
+
+ def getHostname(self):
+ """DOCDOC"""
+ return self['Incoming/MMTP'].get("Hostname")
def getPort(self):
"""Returns this server's IP port"""
@@ -255,44 +264,72 @@
"""Returns the RSA key this server uses to decrypt messages"""
return self['Server']['Packet-Key']
- def getKeyID(self):
+ def getKeyDigest(self):
"""Returns a hash of this server's MMTP key"""
- return self['Incoming/MMTP']['Key-Digest']
+ return mixminion.Crypto.sha1(
+ mixminion.Crypto.pk_encode_public_key(self['Server']['Identity']))
+ #return self['Incoming/MMTP']['Key-Digest']
- def getRoutingInfo(self):
+ def getIPV4Info(self):
"""Returns a mixminion.Packet.IPV4Info object for routing messages
to this server."""
- return IPV4Info(self.getAddr(), self.getPort(), self.getKeyID())
+ return IPV4Info(self.getIP(), self.getPort(), self.getKeyDigest())
+
+ def getMMTPHostInfo(self):
+ """DOCDOC"""
+ return MMTPHostInfo(get.getHostname(), self.getPort(), self.getKeyDigest())
+
+ def getRoutingInfo(self):
+ return self.getIPV4Info()
def getIdentity(self):
return self['Server']['Identity']
+ def getIncomingMMTPProtocols(self):
+ inc = self['Incoming/MMTP']
+ if not inc.get("Version"):
+ return []
+ return [ s.strip() for s in inc["Protocols"].split(",") ]
+
+ def getOutgoingMMTPProtocols(self):
+ inc = self['Outgoing/MMTP']
+ if not inc.get("Version"):
+ return []
+ return [ s.strip() for s in inc["Protocols"].split(",") ]
+
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"])]
+ myOutProtocols = self.getOutgoingMMTPProtocols()
+ otherInProtocols = otherDesc.getIncomingMMTPProtocols()
for out in myOutProtocols:
if out in otherInProtocols:
return 1
return 0
+ def canStartAt(self):
+ """DOCDOC"""
+ myInProtocols = self.getIncomingMMTPProtocols()
+ for out in mixminion.MMTPClient.BlockingClientConnection.PROTOCOL_VERSIONS:
+ if out in myInProtocols:
+ return 1
+ return 0
+
def getRoutingFor(self, otherDesc, swap=0):
"""DOCDOC"""
- #XXXX006 use this
+ #XXXX006 use this!
assert self.canRelayTo(otherDesc)
- if swap:
- rt = mixminion.Packet.SWAP_FWD_IPV4_TYPE
+ assert 0 <= swap <= 1
+ if self.getHostname() and otherDesc.getHostname():
+ ri = otherDesc.getMMTPHostInfo().pack()
+ rt = [mixminion.Packet.FWD_HOST_TYPE,
+ mixminion.Packet.SWAP_FWD_HOST_TYPE][swap]
else:
- rt = mixminion.Packet.FWD_IPV4_TYPE
- ri = otherDesc.getRoutingInfo().pack()
+ ri = otherDesc.getIPV4Info().pack()
+ rt = [mixminion.Packet.FWD_IPV4_TYPE,
+ mixminion.Packet.SWAP_FWD_IPV4_TYPE][swap]
+
return rt, ri
def getCaps(self):
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.157
retrieving revision 1.158
diff -u -d -r1.157 -r1.158
--- test.py 9 Oct 2003 15:26:16 -0000 1.157
+++ test.py 19 Oct 2003 03:12:02 -0000 1.158
@@ -251,7 +251,7 @@
self.assertEquals(parse(s), t)
if not last:
continue
-
+
if c == 'EX':
self.assertRaises(ValueError, cmp, last, t)
elif c == '<':
@@ -1336,6 +1336,23 @@
self.failUnlessRaises(ParseError, parseIPV4Info, ri[:-1])
self.failUnlessRaises(ParseError, parseIPV4Info, ri+"x")
+ def test_mmtphostinfo(self):
+ keyid = "zbcd"*5
+ ri = "\x30\x55"+keyid+"the.hostname.is.here"
+ inf = parseMMTPHostInfo(ri)
+ self.assertEquals(inf.hostname, "the.hostname.is.here")
+ self.assertEquals(inf.port, 0x3055)
+ self.assertEquals(inf.keyinfo, keyid)
+ self.assertEquals(inf.pack(), ri)
+ self.assertEquals(MMTPHostInfo("the.hostname.is.here", 0x3055,
+ keyid).pack(), ri)
+
+ self.failUnlessRaises(ParseError, parseMMTPHostInfo, "z")
+ self.failUnlessRaises(ParseError, parseMMTPHostInfo, "\x30\x55"+keyid)
+ self.failUnlessRaises(ParseError, parseMMTPHostInfo,
+ "\x30\x55"+keyid+"_.com")
+
+
def test_replyblock(self):
# Try parsing an example 'reply block' object
key = "\x99"*16
@@ -1657,15 +1674,15 @@
self.keyid = keyid
def getNickname(self): return "N(%s:%s)"%(self.addr,self.port)
- def getAddr(self): return self.addr
+ def getIP(self): return self.addr
def getPort(self): return self.port
def getPacketKey(self): return self.key
- def getKeyID(self): return self.keyid
+ def getKeyDigest(self): return self.keyid
def getRoutingInfo(self):
- """Returns a mixminion.Packet.IPV4Info object for routing messages
- to this server."""
return IPV4Info(self.addr, self.port, self.keyid)
+ def getIPV4Info(self):
+ return self.getRoutingInfo()
class BuildMessageTests(TestCase):
def setUp(self):
@@ -1999,11 +2016,11 @@
def test_build_fwd_message(self):
- bfm = BuildMessage.buildForwardMessage
- befm = BuildMessage.buildEncryptedForwardMessage
- payload = "Hello!!!!"
+ bfm = BuildMessage.buildForwardPacket
+ befm = BuildMessage.buildEncryptedForwardPacket
+ payloadF = BuildMessage.encodeMessage("Hello!!!!",0)[0]
- m = bfm(payload, 500, "Goodbye",
+ m = bfm(payloadF, 500, "Goodbye",
[self.server1, self.server2],
[self.server3, self.server2])
@@ -2017,7 +2034,7 @@
(self.server2.getRoutingInfo().pack(),
"Goodbye") ),
"Hello!!!!")
- m = bfm(payload, 500, "Goodbye", [self.server1], [self.server3])
+ m = bfm(payloadF, 500, "Goodbye", [self.server1], [self.server3])
messages = {}
@@ -2036,7 +2053,8 @@
decoder=decoder0)
# Drop message gets no tag, random payload
- m = bfm(payload, DROP_TYPE, "", [self.server1], [self.server3])
+ m = bfm(BuildMessage.buildRandomPayload(),
+ DROP_TYPE, "", [self.server1], [self.server3])
def decoderDrop(p,t,self=self):
self.assertEquals(None, t)
@@ -2053,12 +2071,12 @@
"",
decoder=decoderDrop)
-
# Encrypted forward message
rsa1, rsa2 = self.pk1, self.pk512
- payload = "<<<<Hello>>>>" * 100
+ payloadE = BuildMessage.encodeMessage(
+ "<<<<Hello>>>>"*100,ENC_FWD_OVERHEAD)[0]
for rsakey in rsa1,rsa2:
- m = befm(payload, 500, "Phello",
+ m = befm(payloadE, 500, "Phello",
[self.server1, self.server2],
[self.server3, self.server2],
rsakey)
@@ -2075,7 +2093,7 @@
(FWD_IPV4_TYPE, 500),
(self.server2.getRoutingInfo().pack(),
"Phello") ),
- payload,
+ "<<<<Hello>>>>"*100,
decoder=decoder)
# Now do more tests on final messages: is the format as expected?
@@ -2097,7 +2115,7 @@
ks = Keyset(sessionkey)
msg = rsa_rest + lioness_decrypt(mrest,
ks.getLionessKeys("END-TO-END ENCRYPT"))
- comp = compressData(payload)
+ comp = compressData("<<<<Hello>>>>"*100)
self.assert_(len(comp), ord(msg[0])*256 + ord(msg[1]))
self.assertEquals(sha1(msg[22:]), msg[2:22])
self.assertStartsWith(msg[22:], comp)
@@ -2105,7 +2123,7 @@
def test_buildreply(self):
brbi = BuildMessage._buildReplyBlockImpl
brb = BuildMessage.buildReplyBlock
- brm = BuildMessage.buildReplyMessage
+ brm = BuildMessage.buildReplyPacket
## Stateful reply blocks.
reply, secrets_1, tag_1 = \
@@ -2124,7 +2142,7 @@
self.assert_(reply.routingInfo == self.server3.getRoutingInfo().pack())
- m = brm("Information???",
+ m = brm(BuildMessage.encodeMessage("Information???",0)[0],
[self.server3, self.server1],
reply)
@@ -2196,12 +2214,13 @@
self.assertEquals(sec, [ prng.getBytes(16) for _ in range(len(sec)) ])
# _Gravity's Rainbow_, page 258.
- payload = '''
+ message = '''
"...Is it any wonder the world's gone insane, with information
come to the be the only medium of exchange?"
"I thought it was cigarettes."
"You dream."
-- Gravity's Rainbow, p.258 ''' # " <- for emacs python-mode
+ payload = BuildMessage.encodeMessage(message,0)[0]
m = brm(payload,
[self.server3, self.server1],
reply)
@@ -2220,7 +2239,7 @@
(pks_1, None,
(FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,FWD_IPV4_TYPE,MBOX_TYPE),
infos+("fred",)),
- payload,
+ message,
decoder=decoder2)
# Now test format of generated messages.
@@ -2243,7 +2262,7 @@
ks = Keyset(s)
p = lioness_encrypt(p, ks.getLionessKeys(
Crypto.PAYLOAD_ENCRYPT_MODE))
- comp = compressData(payload)
+ comp = compressData(message)
self.assertEquals(len(comp), ord(p[0])*256 +ord(p[1]))
self.assertStartsWith(p[22:], comp)
self.assertEquals(sha1(p[22:]), p[2:22])
@@ -2446,10 +2465,11 @@
return res
def test_successful(self):
- bfm = BuildMessage.buildForwardMessage
+ bfm = BuildMessage.buildForwardPacket
# A two-hop/one-hop message.
p = "Now is the time for all good men to come to the aid"
- m = bfm("\n"+p, SMTP_TYPE, "nobody@invalid",
+ m = bfm(BuildMessage.encodeMessage("\n"+p,0)[0],
+ SMTP_TYPE, "nobody@invalid",
[self.server1, self.server2], [self.server3])
self.do_test_chain(m,
@@ -2461,7 +2481,7 @@
p)
# A one-hop/one-hop message.
- m = bfm("\n"+p,
+ m = bfm(BuildMessage.encodeMessage("\n"+p,0)[0],
SMTP_TYPE, "nobody@invalid", [self.server1], [self.server3])
self.do_test_chain(m,
@@ -2472,7 +2492,7 @@
p)
# Try servers with multiple keys
- m = bfm("\n"+p,
+ m = bfm(BuildMessage.encodeMessage("\n"+p,0)[0],
SMTP_TYPE, "nobody@invalid", [self.server2], [self.server3])
self.do_test_chain(m, [self.sp2_3, self.sp2_3], [FWD_IPV4_TYPE, SMTP_TYPE],
[self.server3.getRoutingInfo().pack(),
@@ -2481,7 +2501,8 @@
# A 3/3 message with a long exit header.
for i in (100,300):
longemail = "f"*i+"@invalid"
- m = bfm("\n"+p, SMTP_TYPE, longemail,
+ m = bfm(BuildMessage.encodeMessage("\n"+p,0)[0],
+ SMTP_TYPE, longemail,
[self.server1, self.server2, self.server1],
[self.server3, self.server1, self.server2])
@@ -2500,12 +2521,13 @@
def test_deliverypacket(self):
# Test out DeliveryPacket.*: with a plaintext ascii packet.
- bfm = BuildMessage.buildForwardMessage
- befm = BuildMessage.buildEncryptedForwardMessage
+ bfm = BuildMessage.buildForwardPacket
+ befm = BuildMessage.buildEncryptedForwardPacket
h = "SUBJECT:cooper\n\n"
p = "That gum you like, it's coming back in style."
- m = bfm(h+p, SMTP_TYPE, "nobody@invalid", [self.server1],
+ payload = BuildMessage.encodeMessage(h+p,0)[0]
+ m = bfm(payload, SMTP_TYPE, "nobody@invalid", [self.server1],
[self.server3])
pkt = self.do_test_chain(m,
@@ -2530,7 +2552,8 @@
self.assertEquals({"SUBJECT":"cooper"}, pkt.getHeaders())
# with a plaintext, nonascii packet.
pbin = hexread("0123456789ABCDEFFEDCBA9876543210")
- m = bfm("\n"+pbin, SMTP_TYPE, "nobody@invalid",
+ m = bfm(BuildMessage.encodeMessage("\n"+pbin,0)[0],
+ SMTP_TYPE, "nobody@invalid",
[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
@@ -2546,7 +2569,8 @@
# with an overcompressed content
pcomp = " "*4096
# (forward, overcompressed)
- m = bfm(pcomp, SMTP_TYPE, "nobody@invalid",
+ m = bfm(BuildMessage.encodeMessage(pcomp,0)[0],
+ SMTP_TYPE, "nobody@invalid",
[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
@@ -2560,7 +2584,8 @@
encodeBase64(compressData(pcomp)))
# (enc-forward, overcompressed)
- m = befm(p, SMTP_TYPE, "nobody@invalid", [self.server1],
+ m = befm(BuildMessage.encodeMessage(pcomp,ENC_FWD_OVERHEAD)[0],
+ SMTP_TYPE, "nobody@invalid", [self.server1],
[self.server3], getRSAKey(0,1024))
pkt = self.do_test_chain(m,
[self.sp1,self.sp3],
@@ -2579,7 +2604,8 @@
# (ASCII msg with headers)
h = {"FROM":'fred@foo', "SUBJECT":'Stuff'}
p = encodeMessageHeaders(h) + "This is the message.\n"
- m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
+ m = bfm(BuildMessage.encodeMessage(p,0)[0],
+ SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1, self.sp3],
[FWD_IPV4_TYPE, SMTP_TYPE],
@@ -2594,7 +2620,8 @@
# (binary msg with headers.)
body = "\x01\x02\x03\x04"*10
p = encodeMessageHeaders(h) + body
- m = bfm(p, SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
+ m = bfm(BuildMessage.encodeMessage(p,0)[0],
+ SMTP_TYPE, "nobody@invalid",[self.server1], [self.server3])
pkt = self.do_test_chain(m,
[self.sp1, self.sp3],
[FWD_IPV4_TYPE, SMTP_TYPE],
@@ -2608,8 +2635,8 @@
def test_rejected(self):
- bfm = BuildMessage.buildForwardMessage
- brm = BuildMessage.buildReplyMessage
+ bfm = BuildMessage.buildForwardPacket
+ brm = BuildMessage.buildReplyPacket
brbi = BuildMessage._buildReplyBlockImpl
# A long intermediate header needs to fail.
@@ -2618,21 +2645,23 @@
def pack(self): return "x"*200
server1X.getRoutingInfo = lambda _packable=_packable: _packable()
- m = bfm("Z", MBOX_TYPE, "hello\000bye",
+ zPayload = BuildMessage.encodeMessage("Z",0)[0]
+ m = bfm(zPayload, MBOX_TYPE, "hello\000bye",
[self.server2, server1X, self.server3],
[server1X, self.server2, self.server3])
self.failUnlessRaises(ParseError, self.sp2.processMessage, m)
# Duplicate messages need to fail.
- m = bfm("Z", SMTP_TYPE, "nobody@invalid",
+ m = bfm(zPayload, SMTP_TYPE, "nobody@invalid",
[self.server1, self.server2], [self.server3])
self.sp1.processMessage(m)
self.failUnlessRaises(ContentError, self.sp1.processMessage, m)
# Duplicate reply blocks need to fail
reply,s,tag = brbi([self.server3], SMTP_TYPE, "fred@invalid")
- m = brm("Y", [self.server2], reply)
- m2 = brm("Y", [self.server1], reply)
+ yPayload = BuildMessage.encodeMessage("Y",0)[0]
+ m = brm(yPayload, [self.server2], reply)
+ m2 = brm(yPayload, [self.server1], reply)
m = self.sp2.processMessage(m).getPacket()
self.sp3.processMessage(m)
m2 = self.sp1.processMessage(m2).getPacket()
@@ -2643,21 +2672,21 @@
reply1,s,t = brbi([self.server1], SMTP_TYPE, "fred@invalid",0,prng)
prng = AESCounterPRNG(" "*16)
reply2,s,t = brbi([self.server2], MBOX_TYPE, "foo",0,prng)
- m = brm("Y", [self.server3], reply1)
- m2 = brm("Y", [self.server3], reply2)
+ m = brm(yPayload, [self.server3], reply1)
+ m2 = brm(yPayload, [self.server3], reply2)
m = self.sp3.processMessage(m).getPacket()
self.sp1.processMessage(m)
m2 = self.sp3.processMessage(m2).getPacket()
self.failUnlessRaises(ContentError, self.sp2.processMessage, m2)
# Drop gets dropped.
- m = bfm("Z", DROP_TYPE, "", [self.server2], [self.server2])
+ m = bfm(zPayload, DROP_TYPE, "", [self.server2], [self.server2])
m = self.sp2.processMessage(m).getPacket()
res = self.sp2.processMessage(m)
self.assertEquals(res,None)
# Wrong server.
- m = bfm("Z", DROP_TYPE, "", [self.server1], [self.server2])
+ m = bfm(zPayload, DROP_TYPE, "", [self.server1], [self.server2])
self.failUnlessRaises(CryptoError, self.sp2.processMessage, m)
self.failUnlessRaises(CryptoError, self.sp2_3.processMessage, m)
@@ -2676,7 +2705,7 @@
# constant.)
save = mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE
mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE = 50
- m_x = bfm("Z", 500, "", [self.server1], [self.server2])
+ m_x = bfm(zPayload, 500, "", [self.server1], [self.server2])
finally:
mixminion.BuildMessage.SWAP_FWD_IPV4_TYPE = save
self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
@@ -2710,7 +2739,7 @@
self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
# Corrupt payload
- m = bfm("Z", MBOX_TYPE, "Z", [self.server1, self.server2],
+ m = bfm(zPayload, MBOX_TYPE, "Z", [self.server1, self.server2],
[self.server3])
m_x = m[:-30] + " "*30
assert len(m_x) == len(m)
@@ -3520,8 +3549,8 @@
return con
if minimal:
conFactory = conFactoryMin
- listener = mixminion.server.MMTPServer.ListenConnection("127.0.0.1",
- port, 5, conFactory)
+ listener = mixminion.server.MMTPServer.ListenConnection(
+ socket.AF_INET, "127.0.0.1", port, 5, conFactory)
listener.register(server)
keyid = _getTLSContextKeyID()
@@ -3734,7 +3763,7 @@
targetKeyID, msgFast, msgSlow):
try:
con = mixminion.MMTPClient.BlockingClientConnection(
- targetIP,targetPort,targetKeyID)
+ socket.AF_INET,targetIP,targetPort,targetKeyID)
con.connect()
con.sendPacket(msgFast)
while pausing[0] > 0:
@@ -4044,6 +4073,19 @@
self.assertEquals(C._parseInt("99"), 99)
# IP
self.assertEquals(C._parseIP("192.168.0.1"), "192.168.0.1")
+ # IP6
+ self.assertEquals(C._parseIP6("::"), "::")
+ self.assertEquals(C._parseIP6("A::"), "A::")
+ self.assertEquals(C._parseIP6("::A"), "::A")
+ self.assertEquals(C._parseIP6("FE0F::A"), "FE0F::A")
+ self.assertEquals(C._parseIP6("1:2:3:4:5:6:7:8"), "1:2:3:4:5:6:7:8")
+ self.assertEquals(C._parseIP6("1:2:3::5:6:7:8"), "1:2:3::5:6:7:8")
+ self.assertEquals(C._parseIP6("1:2:3:4:5:6:1.2.3.4"),
+ "1:2:3:4:5:6:1.2.3.4")
+ self.assertEquals(C._parseIP6("FFFF::1.2.3.4"),"FFFF::1.2.3.4")
+ self.assertEquals(C._parseIP6("::000:1.2.3.4"),"::000:1.2.3.4")
+ # Host
+ self.assertEquals(C._parseHost("foo.bar.baz "), "foo.bar.baz")
# AddressSet
pa = C._parseAddressSet_allow
self.assertEquals(pa("*"), ("0.0.0.0", "0.0.0.0", 48099, 48099))
@@ -4136,6 +4178,15 @@
fails(C._parseIP, "192.0.0")
fails(C._parseIP, "192.0.0.0.0")
fails(C._parseIP, "A.0.0.0")
+ fails(C._parseIP6, "100::200::300")
+ fails(C._parseIP6, "10000::")
+ fails(C._parseIP6, ":::")
+ fails(C._parseIP6, "1:2:3:4:5:6:7:8:9")
+ fails(C._parseIP6, "F:E:D:B:C:A:7:1.2.3.4")
+ fails(C._parseIP6, ":")
+ fails(C._parseIP6, "")
+ fails(C._parseHost, ".")
+ fails(C._parseHost, "foo..bar")
fails(pa, "1/1")
fails(pa, "192.168.0.1 50-40")
fails(pa, "192.168.0.1 50-9999999")
@@ -4224,6 +4275,7 @@
[Incoming/MMTP]
Enabled = yes
IP: 192.168.0.1
+Hostname: Theserver
Allow: 192.168.0.16 1-1024
Deny: 192.168.0.16
Allow: *
@@ -4275,6 +4327,7 @@
eq = self.assertEquals
eq(info['Server']['Descriptor-Version'], "0.2")
eq(info['Incoming/MMTP']['IP'], "192.168.0.1")
+ eq(info['Incoming/MMTP']['Hostname'], "Theserver")
eq(info['Server']['Nickname'], "The_Server")
self.failUnless(0 <= time.time()-info['Server']['Published'] <= 120)
self.failUnless(0 <= time.time()-info['Server']['Valid-After']
@@ -4283,7 +4336,8 @@
10*24*60*60)
eq(info['Server']['Contact'], "a@b.c")
eq(info['Server']['Software'], "Mixminion %s"%mixminion.__version__)
- eq(info['Server']['Packet-Formats'], "0.3")
+ eq(info['Server']['Packet-Formats'], None)
+ eq(info['Server']['Packet-Versions'], "0.3")
eq(info['Server']['Comments'],
"This is a test of the emergency broadcast system")
@@ -4347,6 +4401,9 @@
self.assertUnorderedEq(info.getCaps(), ["relay", "frag"])
+ self.assertEquals(info.getIncomingMMTPProtocols(), ["0.3"])
+ self.assertEquals(info.getOutgoingMMTPProtocols(), ["0.3"])
+
# Now check whether we still validate the same after some corruption
self.assertStartsWith(inf, "[Server]\n")
self.assertEndsWith(inf, "\n")
@@ -4407,6 +4464,7 @@
conf = mixminion.server.ServerConfig.ServerConfig(string=(SERVER_CONFIG_SHORT%mix_mktemp())+
"""[Incoming/MMTP]
Enabled: yes
+Hostname: Theserver2
IP: 192.168.0.99
""")
finally:
@@ -4465,6 +4523,7 @@
string=(SERVER_CONFIG_SHORT%mix_mktemp())+
"""[Incoming/MMTP]
Enabled: yes
+Hostname: Theserver3
IP: 192.168.100.3
[Delivery/SMTP]
Enabled: yes
@@ -4474,6 +4533,7 @@
string=(SERVER_CONFIG_SHORT%mix_mktemp())+
"""[Incoming/MMTP]
Enabled: yes
+Hostname: Theserver4
IP: 192.168.100.4
""")
finally:
@@ -4493,11 +4553,13 @@
key2.getMMTPKey().get_public_key())
self.assertEquals(key3.getPacketKey().get_public_key(),
key2.getPacketKey().get_public_key())
+ eq(info3['Incoming/MMTP']['Hostname'], "Theserver3")
eq(info3['Incoming/MMTP']['IP'], "192.168.100.3")
self.assert_('smtp' in info3.getCaps())
key3.regenerateServerDescriptor(conf2, identity)
info3 = key3.getServerDescriptor()
+ eq(info3['Incoming/MMTP']['Hostname'], "Theserver4")
eq(info3['Incoming/MMTP']['IP'], "192.168.100.4")
def test_directory(self):
@@ -4863,6 +4925,7 @@
Foo: 99
[Incoming/MMTP]
Enabled: yes
+Hostname: Theserver5
IP: 1.0.0.1
""" % (mod_dir)
@@ -5577,6 +5640,7 @@
Nickname: mac-the-knife
[Incoming/MMTP]
Enabled: yes
+Hostname: Theserver5
IP: 10.0.0.1
"""
@@ -5785,6 +5849,7 @@
Nickname: %(nickname)s
[Incoming/MMTP]
Enabled: yes
+Hostname: %(nickname)s
IP: %(ip)s
[Outgoing/MMTP]
Enabled: yes
@@ -6514,8 +6579,8 @@
pathSpec1 = parsePath(usercfg, "lola,joe:alice,joe")
## Test generateForwardMessage.
- # We replace 'buildForwardMessage' to make this easier to test.
- replaceFunction(mixminion.BuildMessage, "buildForwardMessage",
+ # We replace 'buildForwardPacket' to make this easier to test.
+ replaceFunction(mixminion.BuildMessage, "buildForwardPacket",
lambda *a, **k:"X")
try:
getCalls = getReplacedFunctionCallLog
@@ -6523,12 +6588,12 @@
# 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.generateForwardPayloads(
+ client.generateForwardPackets(
directory,
parseAddress("joe@cledonism.net"),
pathSpec1,
payload, time.time(), time.time()+200)
- client.generateForwardPayloads(
+ client.generateForwardPackets(
directory,
parseAddress("smtp:joe@cledonism.net"),
pathSpec1,
@@ -6536,7 +6601,7 @@
time.time(), time.time()+200)
for fn, args, kwargs in getCalls():
- self.assertEquals(fn, "buildForwardMessage")
+ self.assertEquals(fn, "buildForwardPacket")
self.assertEquals(args[1:3],
(SMTP_TYPE, "joe@cledonism.net"))
self.assert_(len(args[3]) == len(args[4]) == 2)
@@ -6546,13 +6611,13 @@
# Now try an mbox message, with an explicit last hop.
payload = "Hey, Lo', where you goin' with that pun in your hand?"
- client.generateForwardPayloads(
+ client.generateForwardPackets(
directory,
parseAddress("mbox:granola"),
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.generateForwardPayloads(
+ client.generateForwardPackets(
directory,
parseAddress("mbox:granola@Lola"),
parsePath(usercfg, "Lola,Joe:Alice"),
@@ -6560,9 +6625,11 @@
for fn, args, kwargs in getCalls():
- self.assertEquals(fn, "buildForwardMessage")
- self.assertEquals(args[0:3],
- (payload, MBOX_TYPE, "granola"))
+ self.assertEquals(fn, "buildForwardPacket")
+ self.assertEquals(args[1:3],
+ (MBOX_TYPE, "granola"))
+ self.assertEquals(payload,
+ BuildMessage.decodePayload(args[0],"Z"*20).getUncompressedContents())
self.assert_(len(args[3]) == len(args[4]) == 2)
self.assertEquals(["Lola", "Joe", "Alice", "Lola"],
[x.getNickname() for x in args[3]+args[4]])
@@ -6576,9 +6643,11 @@
# Temporarily replace BlockingClientConnection so we can try the client
# without hitting the network.
class FakeBCC:
- def __init__(self, addr, port, keyid):
+ PROTOCOL_VERSIONS=["0.3"]
+ def __init__(self, family, addr, port, keyid):
global BCC_INSTANCE
BCC_INSTANCE = self
+ self.family = family
self.addr = addr
self.port = port
self.keyid = keyid
@@ -6797,6 +6866,32 @@
pool.close()
#----------------------------------------------------------------------
+
+def initializeGlobals():
+ init_crypto()
+
+ mixminion.ClientMain.configureClientLock(mix_mktemp())
+
+ # Suppress 'files-can't-be-securely-deleted' message while testing
+ LOG.setMinSeverity("FATAL")
+ mixminion.Common.secureDelete([],1)
+
+ # Don't complain about owner on /tmp, no matter who it is.
+ mixminion.Common._VALID_DIRECTORIES["/tmp"] = 1
+ mixminion.Common._VALID_DIRECTORIES["/var/tmp"] = 1
+
+ # Disable TRACE and DEBUG log messages, unless somebody overrides from
+ # the environment.
+ LOG.setMinSeverity(os.environ.get('MM_TEST_LOGLEVEL', "WARN"))
+ #LOG.setMinSeverity(os.environ.get('MM_TEST_LOGLEVEL', "TRACE"))
+
+ # Suppress DNS resolution on fake hostnames.
+ klh = mixminion.server.ServerKeys._KNOWN_LOCAL_HOSTNAMES
+ for name in [ 'Theserver', 'Theserver2', 'Theserver3', 'Theserver4',
+ 'Theserver5', 'Alice', 'Bob', 'Fred', 'Lola', 'Joe',
+ 'Lisa' ]:
+ klh[name]=1
+
def testSuite():
"""Return a PyUnit test suite containing all the unit test cases."""
suite = unittest.TestSuite()
@@ -6804,7 +6899,7 @@
tc = loader.loadTestsFromTestCase
if 0:
- suite.addTest(tc(ClientDirectoryTests))
+ suite.addTest(tc(PacketHandlerTests))
return suite
testClasses = [MiscTests,
MinionlibCryptoTests,
@@ -6840,20 +6935,5 @@
return suite
def testAll(name, args):
- init_crypto()
- mixminion.ClientMain.configureClientLock(mix_mktemp())
-
- # Suppress 'files-can't-be-securely-deleted' message while testing
- LOG.setMinSeverity("FATAL")
- mixminion.Common.secureDelete([],1)
-
- # Don't complain about owner on /tmp, no matter who it is.
- mixminion.Common._VALID_DIRECTORIES["/tmp"] = 1
- mixminion.Common._VALID_DIRECTORIES["/var/tmp"] = 1
-
- # Disable TRACE and DEBUG log messages, unless somebody overrides from
- # the environment.
- LOG.setMinSeverity(os.environ.get('MM_TEST_LOGLEVEL', "WARN"))
- #LOG.setMinSeverity(os.environ.get('MM_TEST_LOGLEVEL', "TRACE"))
-
+ initializeGlobals()
unittest.TextTestRunner(verbosity=1).run(testSuite())