[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Add first draft for Python client API. This has been s...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv16659/lib/mixminion
Added Files:
ClientAPI.py
Log Message:
Add first draft for Python client API. This has been sitting half-finished on my laptop for way to long.
--- NEW FILE: ClientAPI.py ---
# Copyright 2004 Nick Mathewson. See LICENSE for licensing information.
# Id: ClientMain.py,v 1.89 2003/06/05 18:41:40 nickm Exp $
"""mixminion.ClientAPI
This is a front-end for programs to use client-side Mixminion functionality.
This includes:
- Encoding messages into packets
- Sending packets
- Parsing paths
- Etc, etc DOCDOC
This module is intended to present a semi-stable front-end to the
other Mixminion client modules. Client implementors shouldn't need to
use functions from any other modules.
"""
## XXXX THIS IS AN UNIMPLEMENTED DRAFT API!
# ============================================================
__all__ = [ "MixError", "ClientEnv", "Mix", "PathSpec", "Path",
"PacketBody", "Packet", "PacketDest", "MsgDest",
"AddrMsgDest", "SURBMsgDest", "SURB", "ReceivedPacket",
]
# The operations in this file raise 'MixError' on failure.
from mixminion.Common import MixError
class ClientEnv:
"""A ClientEnv is an interface to a Mixminion client environment. Once
you allocate a ClientEnv, you should release it with .close() before
exiting, to make sure all resources are flushed to disk.
In general, all managed resources are lazy-loaded automatically from
disk as needed, but you can override the defaults with the
configuration functions below.
A ClientEnv manages the following resources:
- Mixminion configuration (set manually with loadConfig/setConfig, or
lazy-loaded automatically based on current environment variables).
- A source of server descriptors (defaults to an automatically
downloaded, cached, and refreshed server directory).
- A log of which SURBs have already been used (defaults to a
disk-backed DB).
- A 'password manager' that asks the user for passwords as needed to
decrypt resources, and remembers passwords that have already been
asked (defaults to a terminal-based implementation).
- A 'keyring' storing SURB keys (defaults to a password-encrypted
file on disk).
- A 'queue' of pending packets that have been generated but not yet
sent into the mixnet (defaults to a directory on disk).
- A 'fragment pool' of fragments for messages that are not yet
complete enough to reassemble.
- One or more callbacks to receive log messages.
Additionally, a ClientAPI provides interfaces to use these managed
resources in order to send and receive Type III packets, by:
- Querying information about known mixes.
- Generating paths through the mixnet.
- Encoding messages into packet bodies.
- Generating SURBs.
- Generating Type III packets for transmission into the network.
- Sending packets into the network and queueing undeliverable
packets.
- Managing queued packets (retrying, and deleting old ones)
- Decoding/decrypting received packets.
- Reassembling fragmented messages.
"""
def __init__(self):
pass
# ------------------------------------------------------------
# Configuration functions
def loadConfig(self, location=None):
"""Read the configuration file for this client. If 'location'
is provided, read the configuration stored in the provided
file. Otherwise, load the configuration file from the default
location.
You don't need to invoke this funtion; if you don't call it,
the default configuration file will be lazy-loaded on demand.
"""
pass
# Advanced:
def setConfig(self, config):
"""Set the configuration object for thie ClientEnv to 'config'.
'Config' should be one of: a dict mapping section names to dicts
mapping key names to values; or a string containing the contents
of a configuration file; or an object conforming to the
interface of mixminion.Config.ClientConfig.
"""
pass
#------------------------------------------------------------
# Functions to change the underlying pluggable helper objects.
# You don't need to fool with these if you want default functionality.
# Advanced:
def setDescriptorSource(self, descriptorSource):
"""Override the implementation of the Mix directory with another
source for server descriptor objects. Any new descriptor source
must conform to the interface of
mixminion.ServerInfo.DescriptorSource
"""
pass
def setSURBLog(self, surbLog):
"""Override the implementation of the database used to log
which SURBs we've used, and when. Must conform to
mixminion.ClientUtils.SURBLog."""
pass
def setPasswordManager(self, passwordManager):
"""Override the object used to ask for passwords from the user
and remember passwords that we've received. Must conform to
mixminion.ClientUtils.PasswordManager.
"""
pass
def setKeyring(self, keyring):
"""Override the object used to store SURB keys, encrypted with the
user's password. Must conform to mixminion.ClientUtils.Keyring.
"""
pass
def setQueue(self, queue):
"""Override the object used to store pending packets. Must conform
to mixminion.ClientUtils.ClientQueue.
"""
pass
def setFragmentPool(self, pool):
"""Override the object used to reassemble fragmented messages. Must
conform to mixminion.ClientUtils.ClientFragmentPool.
"""
pass
def addLogHandler(self, func):
"""Add a callback to receive log messages. The argument
'func' will be called with a severity (one of
"TRACE", "DEBUG", "INFO", "WARN", or "ERROR"); a time (in seconds
since the epoch), and a message (a string) whenever a log event
occurs.
"""
pass
def addStatusLogHandler(self, func):
"""Add a callback to receive --status-fd messages. The argument
'func' will be called once for every line that would be sent to
the status fd, with the line as an argument.
"""
pass
# ------------------------------------------------------------
# Internal helpers. You don't need to call these.
def _getConfig(self):
pass
def _getClientDirectory(self):
pass
def _getSURBLog(self):
pass
def _getPasswordManager(self):
pass
def _getKeyring(self):
pass
def _getQueue(self):
pass
def _getFragmentPool(self):
pass
# ------------------------------------------------------------
# Directory-related functions
def getAllMixNames(self):
"""Return a list of the names of all the mixes we know about.
Does not force a directory download, even if the directory is
stale.
"""
pass
def getRecommendedMixNames(self):
"""Return a list of the names of all the recommended mixes we know
about. Does not force a directory download, even if the directory
is stale.
"""
pass
def getMixByName(self, name):
"""Return a Mix object for a given (case-insensitive) Mix nickname.
Raise KeyError is no such Mix is known. Does not force a directory
download, even if the directory is stale.
"""
pass
def updateDirectory(self, force=0):
"""If the directory is stale, or if 'force' is true, download
a fresh directory.
"""
pass
def checkPathSpec(self, pathSpec, messageDest):
"""Given a PathSpec object and a MsgDest object, raise MixError if
no corresponding valid paths could be generated.
"""
pass
def generatePaths(self, pathSpec, messageDest, n=1):
"""Given a PathSpec object and a MsgDest object, return a list of
'n' Path objects conforming to chosen path spec and dest.
"""
pass
# ------------------------------------------------------------
# Generating packets and SURBs
def getNFragments(self, message, messageDest, headers=None):
"""Given a message (type string), a MsgDest object, and an optional
EmailHeaders object, return the number of fragments that
encodeAndSplit will return for the given message.
"""
pass
def encodeAndSplit(self, message, messageDest, headers=None):
"""Given a message (type string), a MsgDest object, and an optional
EmailHeaders object, return a list of PacketBody objects for this
message.
"""
pass
def generateSURBs(self, n, pathSpec=None, messageDest=None, identity=None,
surbKey=None):
"""Return a list of 'n' SURBs that will traverse paths according
to pathSpec (defaults to SURBPath in config file), and deliver
messages to messageDest (defaults to SURBAddress in config file;
must be an AddrMsgDest).
By default, uses the default identity in the keyring; you can
set a different identity from the keyring with 'identity', or
specify a key directly with 'surbKey'.
(High-level interface. You can generate SURBs more directly
using ClientEnv.generatePaths and generateSURB.)
"""
pass
def encryptPackets(self, bodies, messageDest, pathSpec=None,
surbList=None):
"""Given a sequence of PacketBody objects and a MsgDest object, generate
corresponding Packet objects. If 'pathSpec' is not provided,
default ForwardPath or ReplyPath in the config file. If
'messageDest' indicates a reply, then SURBList should be a
sequence of SURB objects.
(High-level interface. You can encrypt packets more directly
using ClientEnv.generatePaths and encryptPacket.)
"""
pass
# ------------------------------------------------------------
# Sending and/or queueing packets
def sendOrQueuePackets(self, packets, queueOnFail=1):
"""Given a sequence of Packet objects, send as many as possible. If
'queueOnFail' is true, then add any packets that fail to the queue.
Returns a list of the packets that were not successfully delivered.
"""
pass
def queuePackets(self, packets):
"""Given a sequence of Packet objects, add them all to the queue."""
pass
# ------------------------------------------------------------
# Manipulating the queue
def flushQueue(self, serverNames=None, maxCount=None):
"""Try to deliver packets from the queue. If 'serverNames' is provided,
only send packets whose first hop is in serverNames. If 'maxCount'
is provided, send at most 'maxCount' packets."""
pass
def cleanQueue(self, serverNames=None, maxAge=30):
"""Delete old packets from the queue. If 'serverNames' is provided,
only delete packets whose first hop is in serverNames. If 'maxAge'
is provided, only delete packets older than maxAge days.
"""
pass
def inspectQueue(self):
"""Return a list of 3-tuples for the packets in the queue, where
each tuple contains: the PacketDest that will receive the packet;
the time when the packet was queuee; and an opaque handle.
"""
pass
# ------------------------------------------------------------
# Decoding/decrypting packets
def decodeArmoredPackets(self, contents):
"""Given a string containing zero or more ASCII-armored Type III
packets (-----BEGIN TYPE III MESSAGE-----), return a sequence
of ReceivedPacket objects, decrypting them as necessary.
May use the configured PasswordManager to extract keys from the
keyring.
"""
pass
# ------------------------------------------------------------
# Manipulating the fragment pool
def addFragment(self, fragment):
"""Given a ReceivedFragment, add it to the fragment pool.
"""
pass
def getFramentedMessageStatus(self, messageID):
"""Return the status of fragmented message as a 3-tuple of:
(is-ready, number-of-fragments-in-pool,
minimum-number-of-additional-fragments needed.) Return None is no
such message exists.
"""
pass
def reassembleFragmentedMessage(self, messageID):
"""Given the messageID of a fragmented message, return the string
value of the original message. Raise MixError if the message
doesn't exist, or isn't ready to be reassembled.
"""
pass
def removeFragmentedMessage(self, messageID):
"""Remove the message 'messageID' from the fragment pool."""
pass
def listFragmentedMesasges(self):
"""Return a list of messageIDs for all messages in the fragment pool."""
pass
# ------------------------------------------------------------
# Housekeeping
def clean(self):
"""Clean up all underlying containers and storage."""
pass
# ------------------------------------------------------------
def close(self):
"""Free all resources held by this ClientEnv, and flush all
changes to disk.
"""
pass
# ============================================================
class Mix:
"""A Mix object represents a single Type III remailer."""
def __init__(self):
pass
def getName(self):
"""Return this remailer's nickname."""
pass
def getFeature(self, feature):
"""Return the value of a given feature for this remailer."""
pass
def _getServerInfoList(self):
"""Helper: return a list of the server descriptors we have for
this remailer."""
pass
# ============================================================
def readArmored(file):
"""Read lines from the file-like object 'file', and return a list of
armored _Encodeable objects from the file.
"""
pass
class _Encodeable:
"""An _Encodeable object is one that can be conveniently written to disk.
All _Encodeable objects support the Python pickle protocol, and also
can be read and written with ----BEGIN---- and ----END---- lines
using readArmored and writeArmored.
"""
def armored(self):
"""Return an ASCII-armored representation of this object surrounded
with -----BEGIN----- and -----END----- lines.
"""
pass
def writeArmored(self, file):
"""Write the armored version of this object to the file-like object
'file'.
"""
file.write(self.armored())
class PathSpec(_Encodeable):
"""A PathSpec is a description of a class of paths, as describe in
MIX3:path (path-spec.txt). Examples include:
'*3' (a path containing 3 hops)
'~3' (a path containing approximately 3 hops)
'foo,?,?,bar' (a path starting with foo, followed by 2 hops,
ending in bar.)
"""
def __init__(self, string):
pass
class Path(_Encodeable):
"""A Path is a sequence of names for mixes in the mix-net, and a message
destination. Don't create paths directly; instead, generate them with
clientEnv.generatePaths().
"""
def __init__(self, path1, path2, msgDest):
pass
class PacketBody(_Encodeable):
"""A PacketBody is the encoded contents of a single Type III packet,
before it is encryped and the Type III headers are added for it to
be transmitted through the network.
(Use ClientEnv.encodeAndSplit to encode a message and break it into
PacketBody objects.)
"""
def __init__(self, contents):
pass
class Packet(_Encodeable):
"""A Packet is a single Type III packet, and the address of its first
hop, ready to be transmitted into the mix network.
(Use ClientEnv.encryptPackets to encrypt a set of PacketBody objects
into Packet objects.)"""
def __init__(self, contents, packetDest):
pass
class PacketDest(_Encodeable):
"""A PacketDest is an address where a Mix receives packets; we use it
to describe a packet's first hop.
"""
def __init__(self, routingType, routingInfo):
pass
class MsgDest(_Encodeable):
"""A MsgDest is the address for a mssage. It may be either a SURBMsgDest,
to indate a message that we will send with one or more SURBs, or an
AddrMsgDest, to indicate an email address, mbox address, or other
Type III exit address.
"""
def __init__(self):
pass
def isSURB(self):
pass
def parseMessageAddress(string):
"""Parse an address given as a string into an AddrMsgDest. 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>
OR 0x<routing type>
"""
pass
class AddrMsgDest(MsgDest):
"""Indicates the final Type III exit address of a message. Generate these
by calling parseMessageAddress."""
def __init__(self, routingType, routingInfo, pkey=None):
pass
class SURBMsgDest(MsgDest):
"""Indicates that a Type III message will be delivered by one or more
SURBs."""
def __init__(self):
pass
class SURB(_Encodeable):
"""A single-use reply block, and _possibly_ the decoding handle that
will be used when the corresponding packet is received.
(The decodingHandle is only set when the SURB is first generated;
is is sensitive, and so is *not* encoded along with the SURB.)"""
def __init__(self, replyBlock, decodingHandle=None):
pass
def getDecodingHandle(self):
pass
class ReceivedPacket(_Encodeable):
"""The (decrypted) contents of a single received Type III packet.
"""
def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
pass
def isFragment(self):
pass
def isReply(self):
pass
def getSURBIdentity(self):
pass
class ReceivedFragment(ReceivedPacket):
"""The (decrypted) contents of a single packet containing a fragment
of a larger message.
"""
def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
pass
def getMessageID(self):
pass
class ReceivedSingleton(ReceivedPacket, ReceivedMessage):
"""The (decrypted) contents of a single packet containing an entire
message.
"""
def __init__(self, contents, decodingHandle, isReply=0, replyIdentity=None):
pass
def getContents(self):
pass
class EmailHeaders:
"""A set of email headers to be prepended to a message before delivery."""
def __init__(self):
pass
def setFrom(self, val):
pass
def setSubject(self, val):
pass
def setInReplyTo(self, val):
pass
def setReferences(self, val):
pass