[Nym3-commit] r307 - in trunk: . nymbaron nymbaron/Client
nymbaron/Server
laurent at conuropsis.org
laurent at conuropsis.org
Sat Sep 17 15:18:49 CEST 2005
Author: laurent
Date: 2005-09-17 15:18:46 +0200 (Sat, 17 Sep 2005)
New Revision: 307
Added:
trunk/nymbaron/
trunk/nymbaron/Client/Account.py
trunk/nymbaron/Client/Keyring.py
trunk/nymbaron/Client/Main.py
trunk/nymbaron/Mail.py
trunk/nymbaron/Message.py
trunk/nymbaron/Server/Main.py
trunk/nymbaron/Server/User.py
Removed:
trunk/nym3/
trunk/nymbaron/Client/Account.py
trunk/nymbaron/Client/Keyring.py
trunk/nymbaron/Client/Main.py
trunk/nymbaron/Mail.py
trunk/nymbaron/Message.py
trunk/nymbaron/Server/Main.py
trunk/nymbaron/Server/User.py
Log:
Complete the move nym3 -> nymbaron.
Copied: trunk/nymbaron (from rev 303, trunk/nym3)
Deleted: trunk/nymbaron/Client/Account.py
===================================================================
--- trunk/nym3/Client/Account.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Client/Account.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,495 +0,0 @@
-# $Id$
-# -*- coding: iso-8859-1 -*-
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Client.Acount
-
- This package contains user account information representation on the client
- side and ways to create, access to and modify them."""
-
-import copy
-import os
-import nym3.Client.Config as Config
-import nym3.Client.Keyring as Keyring
-import nym3.Mail as Mail
-import mixminion.Common
-import nym3.Common as Common
-import mixminion.Crypto as _cr
-import nym3.Message as Message
-import nym3.Crypto as Crypto
-#import mixminion.ClientAPI as mapi
-import pickle
-import tempfile
-import time
-import re
-#TODO debugging cruft associated to seqno display
-import binascii
-
-SEQNO_LEN = 20
-SURB_LEN = 2104
-
-class NoSuchAccount(Exception): pass
-"""Exception thrown when a user identified by her nym can't be found"""
-
-class AlreadySuchAccount(Exception): pass
-"""Exception thrown when a user identified by her nym already exists"""
-
-class TagMap:
- """Contains mapping between idTag and nickname"""
- def __init__(self, filename):
- self.lock = None
- self.id2nick = None
- self.nick2id = None
- self.filename = filename
-
- def __del__(self):
- if self.lock: self._release()
-
- def _lock(self):
- """Lock the tagmap file"""
- self.lock = mixminion.Common.Lockfile(self.filename + '.lck')
- self.lock.acquire()
-
- def _release(self):
- self.lock.release()
- self.lock = None
-
- def _tagfile(self):
- return self.filename
-
- def _load(self):
- tag = self._tagfile()
- try:
- f = open(tag, 'r')
- self.id2nick = pickle.load(f)
- f.close()
- except IOError:
- self.id2nick = {}
- self.nick2id = {}
- for i in self.id2nick.keys():
- self.nick2id[self.id2nick[i]] = i
-
- def _loadifneeded(self):
- if self.id2nick == None:
- self._lock()
- self._load()
- self._release()
-
- def _save(self):
- if self.id2nick == None: return
- tagmap = self._tagfile()
- f = open(tagmap, 'w')
- pickle.dump(self.id2nick, f)
- f.close()
-
- def nickFromId(self, idTag):
- self._loadifneeded()
- try:
- return self.id2nick[idTag]
- except KeyError:
- raise NoSuchAccount()
-
- def idFromNick(self, nick):
- self._loadifneeded()
- try:
- return self.nick2id[nick]
- except KeyError:
- raise NoSuchAccount()
-
- def getnewId(self, nick):
- self._lock()
- self._load()
- if self.nick2id.has_key(nick):
- self._release()
- raise AlreadySuchAccount()
- while True:
- tag = Mail.genMid(8)
- rtag = Mail.mid2filename(tag)
- if not self.id2nick.has_key(rtag): break
- self.nick2id[nick] = rtag
- self.id2nick[rtag] = nick
- self._save()
- self._release()
- return rtag
-
-class Account:
- """Hold account data. Specifically, this means:
-
- - idTag is the identity string used to generate SURBs for this
- account. It is therefore unique to this account. Additionnaly
- it is used to name the subdirectory holding this account's data
- - 'handshake' tells how far we are in the account creation handshake,
- it is one of 'initiated', 'got_challenge', 'answered_challenge' or
- 'completed'.
- - misc parameters that needs further documentation.
- - data is the hash that contains all of this
- - datafile() is the file where we pickle from and to the data hash
- - synbox is the synopsis box, stored in synboxfile()
- It is a hashtable that associates to a XNymSeq a t-uple
- (mid, flag_is_present_on server, synopsis)
- it may be represented by a string : the encrypted pickled hashtable
- (status = 'encrypted') or a hashtable (status = decrypted).
- When saved, it is put in the encrypted form.
- - mbox is the couple of a hash mid to message and a list of the keys of
- the hash to order these keys, stored in mboxfile()
- - a journal which is a hash of seqno -> encrypted (message, time sent) and is pickled from/to journalfile()
- - the keys used by the account. The actual keys are stored in the
- Keyring, we only store the handles in the account. An account can
- have at most two identity keys at the same time, one active and one
- pending acknowledgment. The active key is named idKey (string), and
- the pending one is name pendingKey (string * seqno) and holds the
- sequence number of the message whose ack by the server would turn
- into the main idKey. The number of encryption keys is not limited,
- they are stored in the encKeys list, last generated first."""
-
- def __init__(self, config, nickname, create = False):
- """Load from an existing account, or create a new, or fail"""
- # Some data related to the account are loaded only on demand,
- # this includes the journal, the mailbox, the synbox.
- self.lock = None
- self.succeeded = True
- self.config = config
- # If home directory does not exist, create it:
- try:
- os.stat(config.path)
- except OSError:
- os.makedirs(config.path)
- tagmap = TagMap(config.path + os.sep + 'tagmap')
- if create:
- self.data = {}
- self.succeeded = False
- self['idTag'] = tagmap.getnewId(nickname)
- # If the nick already existed, we're out of this because of
- # a thrown AlreadySuchAccount.
- # Create the dir that holds this account data
- self.base_path = config.path + os.sep + self['idTag']
- os.mkdir(self.base_path)
- self.data_status = 'dirty'
- self.journal = {}
- self.journal_status = 'dirty'
- self.mbox = {}, []
- self.mbox_status = 'dirty'
- self.synbox = {}
- self.synbox_status = 'dirty'
- self.synbox_enc_status = 'decrypted'
- self['encKeys'] = []
- self.idKey = None
- self.admKey = None
- self.pendingKey = None
- else:
- self.data_status = 'unloaded'
- self.journal_status = 'unloaded'
- self.mbox_status = 'unloaded'
- self.synbox_status = 'unloaded'
- idTag = tagmap.idFromNick(nickname)
- self.base_path = config.path + os.sep + idTag
- self._load_data()
- self.journal = None
- self.mbox = None
- self.synbox = None
- self.synbox_enc_status = None
- self.succeeded = True
- self._lock()
-
- def generateSurbs(self, n):
- """Generate surbs"""
- # TODO : this should be done via ClientAPI.
- surbdir = tempfile.mkdtemp()
- surbspath = surbdir + os.sep + "surbs"
- mixcall = "mixminion generate-surb -t %s -b -n %d -o %s --identity='%s'"
- mixcall = mixcall % (self['returnaddress'], n, surbspath, self['idTag'])
- os.system(mixcall)
- f = open(surbspath, "r")
- data = f.read()
- f.close()
- os.unlink(surbspath)
- os.rmdir(surbdir)
- return data
-
- def __getitem__(self, key):
- """Gets user account field as in a hashtable"""
- if self.data_status == 'unloaded': self._load_data()
- return self.data[key]
-
- def __setitem__(self, key, value):
- """Gets user account field as in a hashtable"""
- self.data[key] = value
- self.data_status = 'dirty'
-
- def send(self, body):
- # TODO : this should be done via ClientAPI.
- (f, msgpath) = tempfile.mkstemp()
- os.close(f)
- f = open(msgpath, "w")
- f.write(body)
- f.close()
- mixcall = "mixminion send -t %s -i %s" % \
- (self['servername'], msgpath)
- os.system(mixcall)
- # TODO : debugging cruft
- #os.unlink(msgpath)
- print "Raw control message left in " + msgpath
-
- def mboxfile(self):
- """Gets the path of the mailbox"""
- return self.base_path + os.sep + 'mbox'
-
- def datafile(self):
- """Gets the path of the datafile"""
- return self.base_path + os.sep + 'data'
-
- def synboxfile(self):
- """Gets the path of the synbox"""
- return self.base_path + os.sep + 'synbox'
-
- def journalfile(self):
- """Gets the path of the journal"""
- return self.base_path + os.sep + 'journal'
-
- def _lock(self):
- """Locks the user. For well behaved functions."""
- self.lock = mixminion.Common.Lockfile(self.config.path + os.sep +
- self['idTag'] + '.lck')
- self.lock.acquire()
-
- def _release(self):
- """Releases the lock"""
- self.lock.release()
- self.lock = None
-
- def homeDir(self):
- """Gets the home directory of this account"""
- return self.base_path
-
- def sendControl(self, l, idKey):
- """Sends a list of command messages to the nymserver"""
- msg = Message.buildMessage(l)
- seqno = self.get_seqno()
- #TODO debugging cruft
- print "send message: %s" % binascii.hexlify(seqno)
- hdr = self.buildHeader(msg, seqno, idKey)
- self.record(seqno, hdr + msg)
- self.send(hdr + msg)
-
- def buildHeader(self, msg, seqno, idKey):
- """Generates a header for a message using the identification key"""
- h = Message.Header()
- sig = _cr.pk_sign(_cr.sha1(chr(len(self['username'])) +
- self['username'] + seqno + msg), idKey)
- h.fromData(self.data['username'], seqno, sig)
- return str(h)
-
- def get_admPubKey(self):
- pubring = Keyring.Keyring(self.config.pubring_path, create = False)
- pubring.decrypt("nym3")
- return pubring.get_key(self['admKey'])
-
- def record(self, seqno, msg):
- """Store a control message in the journal"""
- if self.journal_status == 'unloaded': self._load_journal()
- clear = pickle.dumps((msg, int(time.time())))
- key = self.get_admPubKey()
- self.journal[seqno] = Crypto.nym_encrypt(clear, key)
- self.journal_status = 'dirty'
-
- def acknowledge(self, seqno_list):
- if seqno_list == None or seqno_list == []: return
- if self.journal_status == 'unloaded': self._load_journal()
- for el in seqno_list:
- if self.journal.has_key(el):
- del self.journal[el]
- #TODO debugging cruft
- print "acknowledged message : %s" % binascii.hexlify(el)
- self.journal_status = 'dirty'
-
- def get_seqno(self):
- """Return a previously unused sequence number"""
- if self.journal_status == 'unloaded': self._load_journal()
- ret = Mail.genMid(SEQNO_LEN)
- while self.journal.has_key(ret):
- ret = Mail.genMid(SEQNO_LEN)
- return ret
-
- def _save_data(self):
- """Flushes the data to the disk"""
- if not self.data_status == 'dirty': return
- data = self.datafile()
- f = open(data, 'w')
- pickle.dump(self.data, f)
- f.close()
- self.data_status = 'ok'
-
- def _save_mbox(self):
- """Flushes the mbox to the disk"""
- if not self.mbox_status == 'dirty': return
- mbox = self.mboxfile()
- f = open(mbox, 'w')
- pickle.dump(self.mbox, f)
- f.close()
- self.mbox_status = 'ok'
-
- def _save_synbox(self):
- """Flushes the synbox to the disk"""
- if not self.synbox_status == 'dirty': return
- synbox = self.synboxfile()
- f = open(synbox, 'w')
- if self.synbox_enc_status == 'decrypted':
- key = self.get_admPubKey()
- s = Crypto.nym_encrypt(pickle.dumps(self.synbox), key)
- else:
- assert self.synbox_enc_status == 'encrypted'
- s = self.synbox
- f.write(s)
- f.close()
- self.synbox_status = 'ok'
-
- def _save_journal(self):
- """Flushes the journal to the disk"""
- if not self.journal_status == 'dirty': return
- journal = self.journalfile()
- f = open(journal, 'w')
- pickle.dump(self.journal, f)
- f.close()
- self.journal_status = 'ok'
-
- def __del__(self):
- """Flushes the user account to the disk"""
- if not self.succeeded: return
- self._save_synbox()
- self._save_mbox()
- self._save_journal()
- self._save_data()
- if self.lock: self._release()
-
- def _load_mbox(self):
- """Loads the mailbox from the disk"""
- if not self.mbox_status == 'unloaded': return
- mbox = self.mboxfile()
- try:
- f = open(mbox, 'r')
- self.mbox = pickle.load(f)
- f.close()
- except IOError:
- self.mbox = {}
- self.mbox_status = 'ok'
-
- def _load_data(self):
- """Loads the datafile from the disk"""
- if not self.data_status == 'unloaded': return
- datafile = self.datafile()
- try:
- f = open(datafile, 'r')
- self.data = pickle.load(f)
- f.close()
- except IOError:
- self.data = {}
- self.data_status = 'ok'
-
- def _load_synbox(self):
- """Loads the synbox from the disk"""
- if not self.synbox_status == 'unloaded': return
- synbox = self.synboxfile()
- try:
- f = open(synbox, 'r')
- self.synbox = f.read()
- self.synbox_enc_status = 'encrypted'
- f.close()
- except IOError:
- self.synbox = {}
- self.synbox_enc_status = 'decrypted'
- self.synbox_status = 'ok'
-
- def _load_journal(self):
- """Loads the journal from the disk"""
- if not self.journal_status == 'unloaded': return
- journal = self.journalfile()
- try:
- f = open(journal, 'r')
- self.journal = pickle.load(f)
- f.close()
- except IOError:
- self.journal = {}
- self.journal_status = 'ok'
-
- def _decrypt_synbox(self, secring):
- """After a call to this method synbox is loaded and decrypted.
- secring contains the private admKey"""
- self._load_synbox()
- if self.synbox_enc_status == 'decrypted': return
- assert self.synbox_enc_status == 'encrypted'
- key = secring.get_key(self['admKey'])
- self.synbox = pickle.loads(Crypto.nym_decrypt(self.synbox, key))
- self.synbox_enc_status = 'decrypted'
-
- def get_synbox(self, secring):
- """return a copy of the synbox, trying to decrypt it if it is encrypted
- """
- self._decrypt_synbox(secring)
- return copy.deepcopy(self.synbox)
-
- def get_mbox(self):
- """return a copy of the mbox"""
- self._load_mbox()
- return copy.deepcopy(self.mbox)
-
- def get_journal(self):
- """return a copy of the journal"""
- self._load_journal()
- return copy.deepcopy(self.journal)
-
- def add_syn(self, secring, xnymseq, mid, flag, syn):
- """add a set of syn to the"""
- self._decrypt_synbox(secring)
- self.synbox[str(xnymseq)] = (mid, flag, syn)
- self.synbox_status = "dirty"
-
- def delete_syn(self, secring, mid):
- self._decrypt_synbox(secring)
- l = []
- for i, (m, flag, syn) in self.synbox.iteritems():
- if m == mid:
- l.append(i)
- if len(l) > 0:
- self.synbox_status = "dirty"
- for i in l:
- del(self.synbox[i])
-
- def add_msg(self, mid, msg):
- self._load_mbox()
- #this ensures there is no more than one occurence of a mid in
- #the list
- if self.mbox[0].has_key(mid):
- self.mbox[1].remove(mid)
- self.mbox[0][mid] = msg
- self.mbox[1].append(mid)
- self.mbox_status = "dirty"
-
- def delete_msg(self, mid):
- self._load_mbox()
- if self.mbox[0].has_key(mid):
- del(self.mbox[0][mid])
- self.mbox[1].remove(mid)
- self.mbox_status = "dirty"
-
- def add_enckey(self, key):
- self['encKeys'].insert(0, key)
Copied: trunk/nymbaron/Client/Account.py (from rev 306, trunk/nym3/Client/Account.py)
Deleted: trunk/nymbaron/Client/Keyring.py
===================================================================
--- trunk/nym3/Client/Keyring.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Client/Keyring.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,108 +0,0 @@
-# $Id$
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Client.KeyRing
-
- This package contains the implementation of a Keyring designed to store
- keys in files protected by a password and to easily access those keys."""
-
-import pickle
-import random
-import nym3.Mail as Mail
-from mixminion.Crypto import sha1, ctr_crypt, AES_KEY_LEN, DIGEST_LEN
-
-SALT_LEN = 8
-
-class NewKeyring(Exception): pass
-
-class Keyring:
- """Class that holds a user keyring.
- Keys are stored in a file and are accessed via an abstract
- handle (a string)."""
- # TODO : this would need locking. Somewhere.
-
- def __init__(self, keyfile, create = False):
- self.keyfile = keyfile
- self.status = 'encrypted'
- try:
- f = open(keyfile, 'r')
- self.datastring = f.read()
- f.close()
- except IOError:
- if create:
- f = open(keyfile, "w")
- f.close()
- self.data = {}
- self.status = "clear"
- else: raise NewKeyring()
-
- def _get_unused_handle(self):
- handle = "42"
- while self.data.has_key(handle):
- handle = Mail.genMid(8)
- return handle
-
- def store(self, key):
- handle = self._get_unused_handle()
- self.data[handle] = key
- return handle
-
- def update_key(self, handle, key):
- self.data[handle] = key
-
- def get_key(self, handle):
- return self.data[handle]
-
- def save(self, passphrase):
- """Save the current keyring to file"""
- salt = ""
- for i in range(0, SALT_LEN):
- salt = salt + chr(random.randint(0, 255))
- key = sha1(salt + passphrase + salt)[:AES_KEY_LEN]
- clear = pickle.dumps(self.data)
- digest = sha1(clear + salt)
- encrypted = ctr_crypt(clear + digest, key)
- try:
- f = open(self.keyfile, 'w')
- f.write(salt + encrypted)
- f.close
- except IOError:
- raise "Duh"
-
- def decrypt(self, passphrase):
- """Decrypt the keyring"""
- salt = self.datastring[:SALT_LEN]
- key = sha1(salt + passphrase + salt)
- key = key[:AES_KEY_LEN]
- clear = ctr_crypt(self.datastring[SALT_LEN:], key)
- digest = clear[-DIGEST_LEN:]
- clear = clear[:-DIGEST_LEN]
- if sha1(clear + salt) != digest:
- return False
- self.data = pickle.loads(clear)
- self.status = 'clear'
- self.passphrase = passphrase
- return True
-
Copied: trunk/nymbaron/Client/Keyring.py (from rev 306, trunk/nym3/Client/Keyring.py)
Deleted: trunk/nymbaron/Client/Main.py
===================================================================
--- trunk/nym3/Client/Main.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Client/Main.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,856 +0,0 @@
-# $Id$
-# -*- coding: iso-8859-1 -*-
-#
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Client.Main
-
- This package contains the implementation of a nym3 command line client."""
-
-import sys
-import os
-import string
-import binascii
-import termios
-from optparse import OptionParser, make_option
-from nym3.Message import SToCCODE
-import nym3.Client.Account as Account
-import nym3.Client.Config as Config
-import nym3.Message as Message
-import nym3.Common as Common
-import nym3.Mail as Mail
-import nym3.Crypto as Crypto
-import nym3.Client.Keyring as Keyring
-import mixminion.Crypto as _cr
-import mixminion.ClientDirectory as _cl
-
-usage_string = """Usage: nym3 <command> [arguments]
-where <command> is one of:
- \tcreate\t\tCreate a new account
- \tsend\t\tSend an anonymous mail with your nym
- \tsummarize\tRetrieve email summaries
- \tdelete\t\tRequest email deletion
- \tget\t\tRetrieve email
- \tprocess\t\tProcess returning server messages
- \tlist-syn\tList already fetched summaries
- \tdump-syn\tDump already fetched summaries
- \tlist-mbox\tList already fetched emails
- \tlist-journal\tList commands recorded in the journal (sent but not
- \t\t\tacknowledged yet)
- \tsend-surb\tSend SURBs
- \texport\t\tExport already fetched emails to a file
- \tresend-command\tResend a command that has been sent but not acknowledged
- \t\t\tyet
- \tldelete\tDelete messages from the local mbox
- \tldelete-syn\tDelete synopses from the local synbox"""
-
-class CLI:
- def __init__(self):
- pass
-
- def prompt(self, s):
- try:
- return raw_input(s + ": ")
- except EOFError:
- return ""
-
- def promptmultiline(self, s):
- res = []
- while True:
- if len(res) == 0:
- nym = self.prompt(s)
- else:
- nym = raw_input()
- if nym == "":
- break
- else:
- res.append(nym)
- return res
-
- def prompthidden(self, s):
- fd = sys.stdin.fileno()
- old = termios.tcgetattr(fd)
- new = termios.tcgetattr(fd)
- new[3] = new[3] & ~termios.ECHO # lflags
- try:
- termios.tcsetattr(fd, termios.TCSADRAIN, new)
- passwd = self.prompt(s)
- finally:
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
- print ''
- return passwd
-
- def display(self, s):
- print s
-
- def promptblock(self, s):
- print s
- return sys.stdin.read()
-
-def decode_secring(config, ui):
- secring = Keyring.Keyring(config.secring_path)
- ui.display("You need to provide your passphrase to unlock your keyring")
- while True:
- passphrase1 = ui.prompthidden("Passphrase")
- if secring.decrypt(passphrase1): break
- ui.display("wrong passphrase")
- return secring
-
-def decipher_string(string, keyring, key_handles):
- """Wrapper around Crypto.nym_decrypt which tries every available
- keys in keyring[key_handles] until one is able to decrypt"""
- clear = None
- for k in key_handles:
- enckey = keyring.get_key(k)
- try:
- clear = Crypto.nym_decrypt(string, enckey)
- break
- except: pass
- return clear
-
-def get_account_from_nickname(ui, config, nickname, fail_none):
- """Test if the nickname is different from None. If not display fail_none
- on the ui and exit. Then try to get an existing Account for this nickname.
- If none can be found, display a message on the ui and exit,
- else return it"""
- if not nickname:
- ui.display(fail_none)
- sys.exit(1)
- try:
- return Account.Account(config, nickname)
- except Account.NoSuchAccount:
- ui.display("No account relative to the provided nickname, abort")
- sys.exit(1)
-
-def build_syn_index(ui, config, account):
- """Builds the index of synopses of the given account, using ui to get
- the password to decrypt the keyring. Returns a hash containing the
- correspondance index -> mid, where index is a XNymSeq as a str"""
- secring = decode_secring(config, ui)
- synbox = account.get_synbox(secring)
- index = {}
- for xnymseq, (mid, flag, syn) in synbox.iteritems():
- index[xnymseq] = mid
- return index
-
-def build_mbox_index(account):
- """Builds the index of the mbox of the given account. Returns a hash
- containing the correspondance index -> mid, index as a str"""
- mbox, index = account.get_mbox()
- return dict([(str(i), j) for (i, j) in enumerate(index)])
-
-def build_journal_index(ui, config, account):
- """Builds the index of the journal of the given account. Returns a hash
- containing the correspondance index -> seqno, index as a str"""
- journal = account.get_journal()
- secring = decode_secring(config, ui)
- key = secring.get_key(account['admKey'])
- l = []
- for seqno in journal.keys():
- clear = Crypto.nym_decrypt(journal[seqno], key)
- m, t = pickle.loads(clear)
- l.append((seqno, m, t))
- l.sort(journal_time_cmp)
- return dict([(str(i), s) for (i, (s, m, t)) in enumerate(l)])
-
-def is_hex_mid(s):
- try:
- ds = binascii.unhexlify(s)
- except TypeError:
- return False
- return len(ds) == Common.midLength
-
-class DecodeException(Exception):
- """Exception raised by a decoding function in case of error"""
- pass
-
-def decode_message_references(refs, keys, h):
- """Generate a list of mids from a list of message references, refs,
- a list of keys and a hashtable.
- Return the list of mid if all the mids can be decoded.
- Throw a DecodeException with as argument a string describing the cause of
- error.
-
- A reference is either a mid or a string of the format <key>:<val>,
- where key is in keys and does not contain the caracter ':'.
- h is a hashtable: h[<key>] contains a 2-uple (f, a)
- where f is a function to call with the arguments in the tuple a to
- get a hash h'. h'[s] contains the mid corresponding to the string s
- for the key <key>."""
-
- #we chose this signature so that we can have lazy loading of the
- #correspondance table.
- l = []
- loaded_h = {}
- if refs == None:
- return []
- for e in refs:
- if ':' not in e:
- if is_hex_mid(e):
- l.append(binascii.unhexlify(e))
- else:
- raise DecodeException("%s: not a valid mid" % e)
- else:
- s = e.split(':', 1)
- if s[0] not in keys:
- raise DecodeException("%s: not a valid index key" % s[0])
- try:
- if not loaded_h.has_key(s[0]):
- if not h.has_key(s[0]):
- raise DecodeException("%s: no correspondance table " +
- "callback provided for this key" % s[0])
- f, a = h[s[0]]
- try:
- loaded_h[s[0]] = f(*a)
- except:
- raise DecodeException("%s: defective callback " +
- "provided for this key" % s[0])
- e2 = loaded_h[s[0]][s[1]]
- if len(e2) == Common.midLength:
- l.append(e2)
- else:
- raise DecodeException("%s: dereferenced value not " +
- "a valid mid" % e)
- except KeyError:
- raise DecodeException("%s: not a valid index for key %s" %
- (s[1], s[0]))
- return l
-
-def processMessage(msg, config, ui, nickname):
- """process incoming control message"""
-
- sr = Message.StrReader(msg)
- comList = sr.readCommandSToCList()
- try:
- account = Account.Account(config, nickname)
- except Account.NoSuchAccount:
- ui.display("%s: no account corresponding to this nickname" % nickname)
- sys.exit(1)
- for com in comList:
- if (com.ct() == SToCCODE['Created']):
- # No challenge/response scheme decided yet:
- account['handshake'] = 'completed'
- account['username'] = com.nym
- ui.display("Account " + nickname + " successfully registered"
- " with username " + com.nym)
- elif (com.ct() == SToCCODE['Status']):
- #update the fields
- account['nMsg'] = com.nMsg
- account['nSurb'] = com.nSurb
- account['quota'] = com.quota
- account['used'] = com.used
- #remove from the journal the acknowledged messages
- account.acknowledge(com.acks)
- # TODO raise some warnings, exec some automatic actions and warn
- # the user
- elif (com.ct() == SToCCODE['Summary']):
- # We need to decrypt the set of synopsis to retrieve the MIDs
- try:
- secring = decode_secring(config, ui)
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- clear = decipher_string(com.synSet, secring, account['encKeys'])
- if not clear:
- ui.display("Could not decrypt synopsis")
- sys.exit(1)
- index = 0
- bflist = Mail.bf2list(com.bf)
- while len(clear) > 0:
- mid = clear[:20]
- synlen = Message.strToIntBE(clear[20:22])
- syn = clear[22:22 + synlen]
- clear = clear[22 + synlen:]
- try:
- xnymseq = Mail.XNymSeq(syn)
- except:
- #there was a problem determining the XNymSeq: bug
- #TODO is it the right play?
- raise Exception("unable to determine XNymSeq")
- account.add_syn(secring, xnymseq, mid, index in bflist, syn)
- index += 1
- elif (com.ct() == SToCCODE['Msg']):
- account.add_msg(com.mid, com.msg)
- elif (com.ct() == SToCCODE['Dropped']):
- pass
- elif (com.ct() == SToCCODE['Error']):
- pass
-
-def setupAccount(config, ui, serverName = None, usernamelist = None,
- emailAddress = None, nickname = None):
- """Tries to setup a new account on the given server"""
- if not nickname:
- nickname = ui.prompt("How do you want to name this account")
- while True: # chose a suitable account name
- account = None
- try:
- account = Account.Account(config, nickname, create = True)
- except Account.AlreadySuchAccount:
- ui.display("This account name is already in use")
- nickname = ui.prompt("New account name")
- if account: break
- account['nickname'] = nickname
- if not serverName:
- serverName = ui.prompt("At which server do you want to register"
- " this account")
- account['servername'] = serverName
- if not usernamelist:
- ui.display("You need a username for this account.")
- usernamelist = ui.promptmultiline("Enter several choices, one per line,"
- " finish by a blank line")
- account['username'] = "" # Not yet created
- if not emailAddress:
- emailAddress = ui.prompt("What is the default email address for "
- "returning messages")
- account['returnaddress'] = emailAddress
- ui.display("Please wait, generating keys for this account...")
- idKey = _cr.pk_generate(bits=config.idkey_length)
- encKey = _cr.pk_generate(bits=config.enckey_length)
- admKey = _cr.pk_generate()
- # We have gathered the relevant information for this account, except for
- # the policy which we don't let the user change at this point for the sake
- # of simplicity. So, let's store all of that in the account and prepare the
- # message for the server.
- pubring = None
- secring = None
- try:
- pubring = Keyring.Keyring(config.pubring_path, create = True)
- pubring.decrypt("nym3")
- except: pass
- try:
- secring = Keyring.Keyring(config.secring_path)
- ui.display("You need to provide your passphrase to unlock your keyring")
- while True:
- passphrase1 = ui.prompthidden("Passphrase")
- if secring.decrypt(passphrase1): break
- ui.display("wrong passphrase")
- except Keyring.NewKeyring:
- # The Keyring is new. We need to ask the user for a password.
- # Twice.
- while True:
- passphrase1 = ui.prompthidden("Please enter a passphrase to"
- " protect your secret keyring")
- passphrase2 = ui.prompthidden("Again")
- if passphrase1 == passphrase2: break
- # TODO : warn for an empty passphrase.
- ui.display("Passphrases do not match.")
- secring = Keyring.Keyring(config.secring_path, create = True)
-
- idtag = secring.store(_cr.pk_encode_private_key(idKey))
- enctag = secring.store(_cr.pk_encode_private_key(encKey))
- admtag = secring.store(_cr.pk_encode_private_key(admKey))
- account.add_enckey(enctag)
- account['idKey'] = idtag
- account['admKey'] = admtag
- pubring.update_key(idtag, _cr.pk_encode_public_key(idKey))
- pubring.update_key(enctag, _cr.pk_encode_public_key(encKey))
- pubring.update_key(admtag, _cr.pk_encode_public_key(admKey))
- secring.save(passphrase1)
- pubring.save("nym3")
- createc = Message.Create()
- createc.fromData(usernamelist, "")
- newpkc = Message.Newpk()
- newpkc.fromData(idKey, encKey)
- surbc = Message.Surb()
- surbc.fromData(account.generateSurbs(7))
- account.sendControl([createc, newpkc, surbc], idKey)
- account['handshake'] = "initiated"
-
-def relayMessage(ui, config, nickname = None, to = None, input = None):
- body = None
- exAdr = None
- if not to:
- ui.display("No destination given, abort")
- ui.display("Use -t <dest>")
- sys.exit(1)
- try:
- exAdr = _cl.parseAddress(to)
- except _cl.ParseError:
- ui.display("Address format unknown")
- #TODO add here the supported formats?
- sys.exit(1)
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- if input:
- try:
- fd = open(input)
- body = fd.read()
- fd.close()
- except IOError:
- ui.display("Unable to read message from input file")
- sys.exit(1)
- else:
- try:
- body = ui.promptblock("Enter your message:")
- except IOError:
- ui.display("Unable to read message from stdin")
- sys.exit(1)
- rt, ri, lh = exAdr.getRouting()
- relayc = Message.Relay()
- relayc.fromData(Message.intToStrBE(rt, 2), ri, body)
- try:
- secring = decode_secring(config, ui)
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- idKey = _cr.pk_decode_private_key(secring.get_key(account['idKey']))
- account.sendControl([relayc], idKey)
-
-def Summarize(ui, config, nickname = None, max = None, older = None):
- if not max: max = config.max_syn
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- if older:
- if is_hex_mid(older):
- older = binascii.unhexlify(older)
- else:
- try:
- l = decode_message_references([older], ['syn'],
- {'syn': (build_syn_index, (ui, config, account))})
- except DecodeException, inst:
- ui.display(str(inst))
- sys.exit(1)
- if len(l) == 1:
- older = l[0]
- else:
- #The passed reference didn't correspond to a valid mid, abort
- ui.display('the reference passed did not correspond to a ' +
- 'valid mid, abort')
- sys.exit(1)
- else:
- #no older given, defaults to the oldest mid
- older = chr(0) * 20
- summ = Message.Summarize()
- summ.fromData(max, older)
- try:
- secring = decode_secring(config, ui)
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- idKey = _cr.pk_decode_private_key(secring.get_key(account['idKey']))
- account.sendControl([summ], idKey)
-
-def Delete(ui, config, nickname = None, midlist = None):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- delmsg = Message.Delete()
- try:
- midlist = decode_message_references(midlist, ['syn'],
- {'syn': (build_syn_index, (ui, config, account))})
- except DecodeException, inst:
- ui.display(str(inst))
- sys.exit(1)
- delmsg.fromData(midlist)
- try:
- secring = Keyring.Keyring(config.secring_path)
- ui.display("You need to provide your passphrase to unlock your keyring")
- while True:
- passphrase1 = ui.prompthidden("Passphrase")
- if secring.decrypt(passphrase1): break
- ui.display("wrong passphrase")
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- idKey = _cr.pk_decode_private_key(secring.get_key(account['idKey']))
- account.sendControl([delmsg], idKey)
-
-def Get(ui, config, nickname = None, midlist = None):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- getmsg = Message.Get()
- try:
- midlist = decode_message_references(midlist, ['syn'],
- {'syn': (build_syn_index, (ui, config, account))})
- except DecodeException, inst:
- ui.display(str(inst))
- sys.exit(1)
- getmsg.fromData(midlist)
- try:
- secring = Keyring.Keyring(config.secring_path)
- ui.display("You need to provide your passphrase to unlock your keyring")
- while True:
- passphrase1 = ui.prompthidden("Passphrase")
- if secring.decrypt(passphrase1): break
- ui.display("wrong passphrase")
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- idKey = _cr.pk_decode_private_key(secring.get_key(account['idKey']))
- account.sendControl([getmsg], idKey)
-
-def resend_command(ui, config, nickname = None, seqnolist = None):
- #TODO avoid the double input of passwd (in build_journal_index and
- #decode_secring)
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- try:
- #TODO ! this works only because seqNoLength == midLength
- #modify the sugnature of decode message to take the length of
- #the items to decode as an argument?
- seqnolist = decode_message_references(seqnolist, ['journal'],
- {'journal': (build_journal_index, (ui, config, account))})
- except DecodeException, inst:
- ui.display(str(inst))
- sys.exit(1)
- secring = decode_secring(config, ui)
- key = secring.get_key(account['admKey'])
- for seqno in seqnolist:
- clear = Crypto.nym_decrypt(journal[seqno], key)
- m, t = pickle.loads(clear)
- account.send(m)
- account.record(seqno, m)
- ui.display("resend message seqno: %s" % binascii.hexlify(seqno))
-
-def SendSurb(ui, config, nickname = None, number = 42):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- try:
- secring = decode_secring(config, ui)
- except Keyring.NewKeyring:
- # The Keyring is new. That shouldn't happen
- raise Exception('Bug keyring?')
- idKey = _cr.pk_decode_private_key(secring.get_key(account['idKey']))
- surbc = Message.Surb()
- surbc.fromData(account.generateSurbs(number))
- account.sendControl([surbc], idKey)
-
-def list_syn(ui, config, nickname = None, summarize = True):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- secring = decode_secring(config, ui)
- synbox = account.get_synbox(secring)
- mbox = account.get_mbox()[0]
- for xnymseq, (mid, flag, syn) in synbox.iteritems():
- if mbox.has_key(mid):
- avail = "email available"
- elif flag:
- avail = "email available on server"
- else:
- avail = "email not available"
- ui.display("%s %s %s" % (xnymseq, binascii.hexlify(mid), avail))
- if summarize:
- ui.display(Mail.syn_summary(syn))
- else:
- ui.display(syn)
-
-def list_mbox(ui, config, nickname = None):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- mbox, mids = account.get_mbox()
- secring = decode_secring(config, ui)
- for i, mid in enumerate(mids):
- clear = decipher_string(mbox[mid], secring, account['encKeys'])
- ui.display("%d %s" % (i, binascii.hexlify(mid)))
- ui.display(Mail.syn_summary(clear))
-
-def journal_time_cmp(a, b):
- seqnoa, msga, ta = a
- seqnob, msgb, tb = b
- return ta < tb
-
-def list_journal(ui, config, nickname = None):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- journal = account.get_journal()
- secring = decode_secring(config, ui)
- key = secring.get_key(account['admKey'])
- l = []
- for seqno in journal.keys():
- clear = Crypto.nym_decrypt(journal[seqno], key)
- m, t = pickle.loads(clear)
- l.append((seqno, m, t))
- l.sort(journal_time_cmp)
- for i, (s, m, t) in enumerate(l):
- ui.display("%d %s %s" % (i, binascii.hexlify(s), str(t)))
- #TODO display the content of the command
- #ui.display()
-
-def export(ui, config, nickname, output = None, args = []):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- output_f = sys.stdout
- secring = decode_secring(config, ui)
- mbox = account.get_mbox()[0]
- args = decode_message_references(args, ['mbox'],
- {'mbox': (build_mbox_index, (account,))})
- if output != None:
- try:
- output_f = open(output, "w")
- except IOError:
- ui.display("Unable to write in output file, abort")
- sys.exit(1)
- for mid in args:
- if mbox.has_key(mid):
- clear = decipher_string(mbox[mid], secring, account['encKeys'])
- output_f.write(clear)
- else:
- ui.display("%s: no email relative to that mid" %
- binascii.hexlify(mid))
- if output_f != sys.stdout:
- output_f.close()
- else:
- sys.stdout.flush()
-
-def ldelete(ui, config, nickname, args = []):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- mids = decode_message_references(args, ['mbox'],
- {'mbox': (build_mbox_index, (account,))})
- for mid in mids:
- account.delete_msg(mid)
-
-def ldelete_syn(ui, config, nickname, args = []):
- account = get_account_from_nickname(ui, config, nickname,
- "No nickname given, abort\nUse -n <nickname>")
- mids = decode_message_references(args, ['syn'],
- {'syn': (build_syn_index, (ui, config, account))})
- secring = decode_secring(config, ui)
- for mid in mids:
- account.delete_syn(secring, mid)
-
-def main(args):
- if len(args) < 2:
- print usage_string
- sys.exit(1)
- if args[1] == "create":
- parser = OptionParser()
- parser.add_option("-s", "--server", action = "store", dest = "server",
- help = "Specify the server")
- parser.add_option("-u", "--username", action = "store",
- dest = "username",
- help = "Specify usernames to attempt to register in"
- " the form user1:user2:user3")
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "How to refer to this "
- "account locally")
- parser.add_option("-e", "--email", help = "Specify the destination "
- "email address for returning surbs", action = "store",
- dest = "email")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- myusernamelist = []
- if options.username:
- myusernamelist = string.split(options.username, ":")
- config = Config.Config() # TODO load from file
- setupAccount(config, ui, serverName = options.server,
- usernamelist = myusernamelist,
- emailAddress = options.email, nickname = options.nickname)
- sys.exit(0)
-
- if args[1] == "send":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname refering "
- "to the account used to send a message")
- parser.add_option("-t", "--to", help = "Specify the recipient's "
- "address", action = "store", dest = "to")
- parser.add_option("-i", "--input", action = "store",
- dest = "input", help = "The file to read the message "
- "from (defaults to stdin)")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- relayMessage(ui, config, options.nickname, options.to, options.input)
- sys.exit(0)
-
- if args[1] == "summarize":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose summaries to request")
- parser.add_option("-m", "--max", action = "store",
- dest = "max", help = "Maximum number of summaries"
- " to request", type = "int")
- parser.add_option("-o", "--older", action = "store",
- dest = "older", help = "Retrieve only summaries "
- "older than msgid")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- Summarize(ui, config, options.nickname, options.max, options.older)
- sys.exit(0)
-
- if args[1] == "delete":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request deletion")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- Delete(ui, config, options.nickname, args)
- sys.exit(0)
-
- if args[1] == "get":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- Get(ui, config, options.nickname, args)
- sys.exit(0)
-
- if args[1] == "send-surb":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose surbs to replenish")
- parser.add_option("-N", "--number", action = "store",
- dest = "number", help = "The number "
- "of SURBs to send", type = "int", default = 42)
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- SendSurb(ui, config, options.nickname, options.number)
- sys.exit(0)
-
- if args[1] == "process":
- parser = OptionParser()
- parser.add_option("-i", "--idtag", action = "store",
- dest = "idtag", help = "The idTag contained in the"
- "enclosing SURB")
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname of the"
- "account to use")
- parser.add_option("-f", "--file", action = "store",
- dest = "file", help = "The file to read the message"
- " from, or stdin if omitted")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- if (not options.idtag) and (not options.nickname):
- ui.display("Must provide an idTag or a nickname")
- sys.exit(1)
- config = Config.Config() # TODO load from file
- if options.nickname: nick= options.nickname
- else:
- tm = Account.TagMap(config.path + os.sep + 'tagmap')
- nick = tm.nickFromId(options.idtag)
- if options.file:
- try:
- f = open(options.file, "r")
- msg = f.read()
- f.close()
- except IOError:
- ui.display("Can't read requested file")
- sys.exit(1)
- else: msg = sys.stdin.read()
- processMessage(msg, config, ui, nick)
- sys.exit(0)
-
- if args[1] == "list-syn":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose synopsis to list")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- list_syn(ui, config, options.nickname)
- sys.exit(0)
-
- if args[1] == "dump-syn":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose synopsis to dump")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- list_syn(ui, config, options.nickname, summarize = False)
- sys.exit(0)
-
- if args[1] == "list-mbox":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- list_mbox(ui, config, options.nickname)
- sys.exit(0)
-
- if args[1] == "list-journal":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose journal to list")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- list_journal(ui, config, options.nickname)
- sys.exit(0)
-
- if args[1] == "resend-command":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- resend_command(ui, config, options.nickname, args)
- sys.exit(0)
-
- if args[1] == "export":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- parser.add_option("-o", "--output", action = "store",
- dest = "output", help = "The file to export the " +
- "message to, stdout if omitted")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- export(ui, config, options.nickname, options.output, args)
- sys.exit(0)
-
- if args[1] == "ldelete":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- ldelete(ui, config, options.nickname, args)
- sys.exit(0)
-
- if args[1] == "ldelete-syn":
- parser = OptionParser()
- parser.add_option("-n", "--nickname", action = "store",
- dest = "nickname", help = "The nickname "
- "of the account whose emails to request")
- (options, args) = parser.parse_args(args[2:])
- ui = CLI()
- config = Config.Config()
- ldelete_syn(ui, config, options.nickname, args)
- sys.exit(0)
-
-
-if __name__ == '__main__':
- main(sys.argv)
- sys.exit(0)
Copied: trunk/nymbaron/Client/Main.py (from rev 306, trunk/nym3/Client/Main.py)
Deleted: trunk/nymbaron/Mail.py
===================================================================
--- trunk/nym3/Mail.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Mail.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,208 +0,0 @@
-# $Id$
-# -*- coding: utf-8 -*-
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Mail
-
- This package contains utilities to process mails."""
-
-import re
-import os
-import tempfile
-import random
-import base64
-import string
-import email.Parser
-import nym3.Common as Common
-
-slen = 128
-"""The length of a summary in bytes"""
-
-midLen = Common.midLength
-"""The length of a mid in bytes"""
-
-random.seed(None)
-
-oldestMid = chr(0) * midLen
-"""Special value of a mid, older than any othany other mid"""
-
-forbidden_headers = ['from', 'received', 'sender', 'reply-to', 'return-path',
- 'user-agent']
-"""The list of headers that are removed from a message before it is relayed"""
-#TODO complete this list, how do we process msgid? remove and add another one?
-
-class SeqNotFound(Exception): pass
-
-def synopsize(msg, seq):
- """Synopsizes a message : keeps a set of headers and the beginning of
- the body of the mail
- seq is the sequence number of the generated synopsis"""
- #TODO assert type(seq) == int?
- #TODO: this is not RFC2822-compliant
- vheaders = [ 'Cc', 'From', 'Date', 'In-Reply-To', 'Sender',
- 'Message-Id', 'References', 'Return-Path', 'Subject',
- 'To', 'X-Anonymous', 'X-Spam-Level']
- par = email.Parser.HeaderParser()
- message = par.parsestr(msg)
- res = ''
- print message.keys()
- for i in vheaders:
- if i in message:
- res = res + i + ': ' + message[i][0:80] + "\n"
- l = message.get_all('Received')
- if l:
- res = res + 'Received: ' + l[0][0:80] + "\n"
- res = res + 'X-Octets: ' + str(len(msg)) + "\n"
- res = res + 'X-Nym-Sequence: ' + str(seq) + "\n"
- res = res + "\n"
- print res
- if not message.is_multipart():
- res = res + message.get_payload()[0:slen]
- else:
- n = 0
- p = message.get_payload()
- j = 0
- while n < slen and j < len(p):
- s = p[j].as_string(True)[0:slen - n]
- res = res + s
- n = n + len(s)
- j = j + 1
- return res
-
-def syn_summary(synopsis):
- """returns a summary for the provided synopsis.
- """
- par = email.Parser.HeaderParser()
- syn = par.parsestr(synopsis)
- from_f = syn['From']
- subj_f = syn['Subject']
- if from_f == None:
- from_f = ""
- if subj_f == None:
- subj_f = ""
- return "From: %s\nSubject: %s" % (from_f, subj_f)
-
-def XNymSeq(synopsis):
- """returns an integer, the sequence number of the provided synopsis.
- if it is absent throws a SeqNotFound exception, ValueError if not an
- integer string """
- par = email.Parser.HeaderParser()
- syn = par.parsestr(synopsis)
- seq = syn['X-Nym-Sequence']
- if seq == None:
- raise SeqNotFound()
- return int(seq)
-
-def tmpFileMsg(body):
- """write body in a temp file name and return a file name of this file
- """
- (fd, fname) = tempfile.mkstemp()
- os.close(fd)
- f = open(fname, "w")
- f.write(body)
- f.close()
- return fname
-
-
-def bf(l):
- """Generate 2 octets bitfield from boolean list (hasmail values). LSB
- corresponds to first value in list"""
- assert len(l) <= 16
- r1 = 0
- offset = 0
- for i in l[:8]:
- if i: r1 |= (1 << offset)
- offset += 1
- r2 = 0
- offset = 0
- for i in l[8:]:
- if i: r2 |= (1 << offset)
- offset += 1
- return chr(r2) + chr(r1)
-
-def bf2list(bf):
- """take a 2 octets big endian string and output the associated
- list of indexes"""
- assert (len(bf) == 2 and type(bf) == str)
- l = []
- for i in range(2):
- for j in range(8):
- if ord(bf[i]) & (1 << j):
- l.append((1 - i) * 8 + j)
- return l
-
-def b2s(c):
- """return a string of length 2 representing the byte c in hex"""
- i = ord(c)
- (q, r) = divmod(i, 16)
- return hex(q)[2:] + hex(r)[2:]
-
-def filter_header(body):
- par = email.Parser.HeaderParser()
- message = par.parsestr(msg)
- for h in forbidden_headers:
- if message.has_key(h):
- del message[h]
- return message.as_string()
-
-def relay(nym, rt, ri, sname, body):
- """relay the e-mail in body according to the routing type rt
- and the routing info ri for the nymholder of nym"""
- body = filter_header(body)
- b = 'From: ' + nym + '@' + sname + "\n\n" + body
- fname = tmpFileMsg(b) #TODO we can avoid the tempfile with some fd manipultation : exec mixminion, write b in its stdin
- mixcmd = "mixminion send -t 0x" + b2s(rt[0]) + b2s(rt[1]) + ":" + ri + " -i " + fname
- print mixcmd
- ec = os.system(mixcmd)
- # TODO : debugging cruft
- #os.unlink(fname)
- print "Raw relayed message left in " + fname
- return ec
-
-def genMid(length = midLen):
- """Randomly generates a mid of given length different from oldestMid"""
- ret = oldestMid
- while ret == oldestMid:
- res = ""
- for i in range(0, length): res = res + chr(random.randint(0, 255))
- ret = res
- return ret
-
-def mid2filename(mid):
- """Converts a mid in a file name"""
- res = base64.encodestring(mid)
- res = res.replace('/', '_')
- res = string.strip(res)
- return res
-
-def filename2mid(mid):
- """Converts a file name in a mid"""
- res = mid
- res = res.replace('_', '/')
- res = base64.decodestring(mid)
- return res
-
-if __name__ == '__main__':
- import sys
- print synopsize(sys.stdin.read())
- print "And now, a random encoded mid: " + mid2filename(genMid(20))
Copied: trunk/nymbaron/Mail.py (from rev 306, trunk/nym3/Mail.py)
Deleted: trunk/nymbaron/Message.py
===================================================================
--- trunk/nym3/Message.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Message.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,1114 +0,0 @@
-#!/usr/bin/python
-# -*- coding: iso-8859-1 -*-
-# $Id$
-#
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Message
-
- This module contains representations of the message of the nym protocol
- and methods to parse and generate messages from these representations."""
-
-import types
-import nym3.Common as Common
-import mixminion.Crypto as _cr
-
-# Every command type has a numerical code as described in nym-spec.
-CToSCODE = { 'Create': 0, 'Create2': 1, 'Surb': 2, 'Newpk': 3,
- 'Relay': 4, 'Get': 5, 'Summarize': 6, 'Delete': 7,
- 'Policy': 8 }
-# nym-spec says MSGS and later MSG. use Msg for consistency.
-SToCCODE = { 'Created': 0, 'Status': 1, 'Summary': 2, 'Msg': 3,
- 'Dropped': 4, 'Error': 5 }
-
-class ParseError(Exception):
- """ParseError : error raised if anything goes wrong during parsing"""
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return repr(self.value)
-
-class BadArgument(Exception):
- """BadArgument : error raised if a function is given an irrelevant argument"""
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return repr(self.value)
-
-def strToIntBE(s):
- """return the Int coded by the string s with the big endian convention"""
- def aux(s, ac):
- if(s == ""):
- return ac
- else:
- return aux(s[1:], 256 * ac + ord(s[0]))
- return aux(s, 0)
-
-def intToStrBE(n,mod):
- """return a string coding the n modulo 256^mod in big endian"""
- def aux(n, ac, mod):
- if(mod == 0):
- return ac
- else:
- return aux(n / 256, chr(n % 256) + ac, mod - 1)
- return aux(n, "", mod)
-
-def nbBits(n):
- """return the number of bits of a strictly positive integer,
- 0 otherwise"""
- res = 0
- p = 1
- while(n >= p):
- p = 2 * p
- res = res + 1
- return res
-
-def isMidList(l, n):
- """true if l is a list of strings of length n"""
- for i in l:
- if ((len(i) != n) or (type(i) != types.StringType)): return False
- return True
-
-sigLength = Common.sigLength
-"""The length of signatures in bytes"""
-
-seqNoLength = Common.seqNoLength
-"""The length of sequence numbers in bytes"""
-
-midLength = Common.midLength
-"""The length of message ID in bytes"""
-
-class StrReader:
- """wraps a string and gives method to read it chunk by chunk"""
- def __init__(self, s):
- """initialize by wrapping a string"""
- self.s = s
- self.a = 0
- self.b = 0
-
- def isEnd(self):
- """True if we have read the whole string"""
- return (self.b == len(self.s))
-
- def next(self, n):
- """return the next n characters"""
- if n < 0: raise BadArgument("StrReader.next : n < 0")
- if self.b + n > len(self.s):
- raise IndexError("StrReader.next : n too long")
- self.a = self.b
- self.b = self.b + n
- return self.s[self.a:self.b]
-
- def readHeader(self):
- """Read next header from the string"""
- h = Header()
- h.fromStrReader(self)
- return h
-
- def readCommandCToS(self):
- """Read next CommandCToS from string"""
- try:
- ct = ord(self.next(1))
- cs = strToIntBE(self.next(3))
- if (ct == CToSCODE['Create']): a = Create()
- elif (ct == CToSCODE['Create2']): a = Create2()
- elif (ct == CToSCODE['Surb']): a = Surb()
- elif (ct == CToSCODE['Newpk']): a = Newpk()
- elif (ct == CToSCODE['Relay']): a = Relay()
- elif (ct == CToSCODE['Get']): a = Get()
- elif (ct == CToSCODE['Summarize']): a = Summarize()
- elif (ct == CToSCODE['Delete']): a = Delete()
- elif (ct == CToSCODE['Policy']): a = Policy()
- else:
- raise ParseError("Undefined Command Type")
- a.fromStrReader(self, cs)
- return a
- except IndexError: #is it really necessary? is it not considered in the various fromStrReader ?
- raise ParseError("Bad Formed Command")
-
- def readCommandCToSList(self):
- """Read a list of CommandSToC from string, until
- the string is completely read"""
- l = []
- while (not self.isEnd()):
- l.append(self.readCommandCToS())
- return l
-
- def readCommandSToC(self):
- """Read next CommandSToC from string"""
- try:
- ct = ord(self.next(1))
- cs = strToIntBE(self.next(3))
- if (ct == SToCCODE['Created']): a = Created()
- elif (ct == SToCCODE['Status']): a = Status()
- elif (ct == SToCCODE['Summary']): a = Summary()
- elif (ct == SToCCODE['Msg']): a = Msg()
- elif (ct == SToCCODE['Dropped']): a = Dropped()
- elif (ct == SToCCODE['Error']): a = Error()
- else:
- print "Got ct %u" % ct
- raise ParseError("Undefined Command Type")
- a.fromStrReader(self, cs)
- return a
- except IndexError:
- # is it really necessary? is it not considered in the various
- # fromStrReader ? it should, if not consider it a bug
- raise ParseError("Bad Formed Command")
-
- def readCommandSToCList(self):
- """Read a list of CommandSToC from string, until
- the string is completely read"""
- l = []
- while (not self.isEnd()):
- l.append(self.readCommandSToC())
- return l
-
- def readNym(self, start, cs):
- """Read a Nym length + a nym
- Raise ParseError if it takes more characters from the string that previously advertised"""
- nl = ord(self.next(1))
- if(self.b + nl - start > cs):
- raise ParseError("Bad Formed Command")
- return self.next(nl)
-
- def readNymList(self, start, cs):
- """Read a list of (Nym length + a nym)
- Raise ParseError if it takes more characters from the string that previously advertised"""
- try:
- l = []
- while(self.b < start + cs):
- l.append(self.readNym(start, cs))
- return l
- except ParseError: #is it really necessary?
- raise
-
- def readMid(self, length, start, cs):
- """Read a str of len = length
- Raise ParseError if it takes more characters from the string that previously advertised, in that case, doesn't read the StrReader"""
- if(self.b + length - start > cs):
- raise ParseError("Bad Formed Command")
- return self.next(length)
-
- def readMidList(self, length, start, cs):
- """Read a list of str of len length
- Raise ParseError if it takes more characters from the string that previously advertised"""
- try:
- l = []
- while(self.b < start + cs):
- l.append(self.readMid(length, start, cs))
- return l
- except ParseError: #is it really necessary?
- raise
-
-class Header:
- """Header Object
- sig : signature
- nl : nym length
- nym : nym
- seqNo : sequence number which identifies the message
- length : header length"""
- def __init__(self):
- """Build a Header empty object"""
- pass
-
- def fromData(self, nym, seqNo, sig = ""):
- """Fill a Header Object from a nym and sequence number and sig
- if provided.
- sig : signature
- nl : nym length
- nym : nym
- seqNo : sequence number which identifies the message
- length : header length"""
- if(len(seqNo) != seqNoLength):
- raise BadArgument("Buildheader.__init__ : seqNo length doesn't match seqNoLength")
- if(sig == ""): self.sig = chr(0) * sigLength
- else:
- if(len(sig) != sigLength):
- self.sig = chr(0) * (sigLength - len(sig)) + sig
- else: self.sig = sig
- self.nl = len(nym)
- self.nym = nym
- self.seqNo = seqNo
- #self.length = sigLength + 1 + self.nl + seqNoLength
-
- def __str__(self):
- """Give the string of the header that would be sent in a control message"""
- return self.sig + chr(self.nl) + self.nym + self.seqNo
-
- def fromStrReader(self,sr):
- """Fill a Header from a StrReader"""
- try:
- self.sig = sr.next(sigLength)
- self.nl = ord(sr.next(1))
- self.nym = sr.next(self.nl)
- self.seqNo = sr.next(seqNoLength)
- except IndexError: raise ParseError("Header too short")
-
-
-## class ReadHeader(Header):
-## def __init__(self,sr):
-## """builds a header from a StrReader
-## sig : signature
-## nl : nym length
-## nym : nym
-## seqNo : sequence number which identifies the message
-## length : header length"""
-## try:
-## self.sig = sr.next(sigLength)
-## self.nl = ord(sr.next(1))
-## self.nym = sr.next(nl)
-## self.seqNo = sr.next(seqNoLength)
-## self.length = sr.b
-## except IndexError:
-## raise ParseError("Parse Error : Header too short")
-
-## class BuiltHeader(Header):
-## def __init__(self,nym,seqNo):
-## """Builds a header from a nym and and sequence number
-## sig : signature
-## nl : nym length
-## nym : nym
-## seqNo : sequence number which identifies the message
-## length : header length"""
-## if(len(seqNo) != seqNoLength):
-## raise BadArgument("Buildheader.__init__ : seqNo length doesn't match seqNoLength")
-## self.sig = chr(0) * sigLength
-## self.nl = len(nym)
-## self.nym = nym
-## self.seqNo = seqNo
-## self.length = sigLength + 1 + self.nl + seqNoLength
-
-class CommandCToS:
- """CommandCToS astract class
- the CommandCToS derive from this class"""
- pass
-
-class Create(CommandCToS):
- """Create command
- self.list : list of nym (string list)
- self.pw : proof of work"""
- def __init__(self):
- """Build a Create empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Create']
-
- def fromData(self, l, pw = ""):
- """Fill a Create Object from a list of nyms and a proof of work"""
- if(len(l) > 255):
- raise BadArgument("Create.fromData : len(l) too big")
- for i in range(len(l)):
- if(len(l[i]) > 255):
- raise BadArgument("Create.fromData : nym too long")
- self.list = l
- self.pw = pw
- #if len(pw) isn't too big, we are sure that the command size is small enough because of the size and the number of nyms
-
- def fromStrReader(self, sr, cs):
- """Fill a Create command from a StrReader
- raise ParseError if it is malformed"""
- start = sr.b
- try:
- nNym = ord(sr.next(1))
- self.pw = "" #what is a proof of work?
- self.list = sr.readNymList(start, cs)
- except (ParseError, IndexError):
- raise ParseError("Bad Formed Command : Create")
- #nNym is redundant, we use it to make sure the message is well formed
- if(nNym != len(self.list)):
- raise ParseError("Bad Formed Command : Create")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = chr(len(self.list)) + self.pw
- for i in range(len(self.list)):
- s = s + chr(len(self.list[i])) + self.list[i]
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Create2(CommandCToS):
- """Create2 command
- self.cr : challenge response (str)"""
- def __init__(self):
- """Build a Create2 empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Create2']
-
- def fromData(self, cr):
- """Fill Create2 from a string containing the response to a challenge"""
- self.cr = cr
- #if cr is small enough we are sure that the command size is small enough
-
- def fromStrReader(self, sr, cs):
- """Fill a Create2 command from a StrReader
- raise ParseError if it is malformed"""
- try:
- self.cr = sr.next(cs)
- except IndexError:
- raise ParseError("Bad Formed Command : Create2")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = self.cr
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Surb(CommandCToS):
- """Surb command
- self.surbs : set of surbs (str)"""
- def __init__(self):
- """Build a Surb empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Surb']
-
- def fromData(self, s):
- """Fill a Surb Object from a string containing Surbs"""
- if len(s) >= pow(256, 3):
- raise BadArgument("Surb.fromData : command body too long")
- self.surbs = s
-
- def fromStrReader(self, sr, cs):
- """Fill a Surb Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- self.surbs = sr.next(cs)
- except IndexError:
- raise ParseError("Bad Formed Command : Surb")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = self.surbs
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Newpk(CommandCToS):
- """Newpk command
- self.ekid : ASN.1 encoding of the identity key
- self.ekenc : ASN.1 encoding of the encryption key"""
-
- def __init__(self):
- """Build a Newpk empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Newpk']
-
- def fromData(self, sid, senc):
- """Fill a Newpk Object from 2 keys"""
- if sid == None or senc == None:
- raise BadArgument("Newpk.fromData : sid and senc mustn't be None")
- id_l = nbBits(_cr.pk_get_modulus(sid))
- id_l2 = nbBits(_cr.pk_get_modulus(senc))
- if( ((id_l != 1024) and (id_l != 2048)) or ((id_l2 != 1024) and (id_l2 != 2048))):
- raise BadArgument("Newpk.fromData : sid or senc as not a valid size")
- self.ekid = _cr.pk_encode_public_key(sid)
- self.ekenc = _cr.pk_encode_public_key(senc)
- #command body is small enough
-
- def check_key(self, ekey):
- key = _cr.pk_decode_public_key(ekey)
- mod = _cr.pk_get_modulus(key)
- nb = nbBits(mod)
- return (nb == 1024 or nb == 2048) and _cr.pk_same_public_key(key, _cr.pk_from_modulus(mod))
-
- def fromStrReader(self, sr, cs):
- """Fill a Newpk Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- id_l = strToIntBE(sr.next(2))
- id_l2 = cs-2 - id_l
-
- self.ekid = sr.next(id_l)
- self.ekenc = sr.next(id_l2)
- if not (self.check_key(self.ekid) and self.check_key(self.ekenc)):
- raise BadArgument("Illegal keys : Newpk")
-
- except IndexError:
- raise ParseError("Bad Formed Command : Newpk")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s=intToStrBE(len(self.ekid), 2) + self.ekid + self.ekenc
- if(len(s)>=pow(256,3)): #should not be possible, otherwise bug
- raise BadArgument("Newpk.__str__ : command body too long")
- return chr(self.ct()) + intToStrBE(len(s),3) + s
-
-
-class Relay(CommandCToS):
- """Relay command
- self.rs : routing info size (int)
- self.rt : routing type (str of length 2)
- self.ri : routing info (str)
- self.body : body of the message (str)"""
- def __init__(self):
- """Build a Relay empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Relay']
-
- def fromData(self, rt, ri, body):
- """Fill a Relay Object from a 3 strings : routing type(2 octets) routing info and body"""
- if len(rt) != 2:
- raise BadArgument("Relay.fromData : RT size isn't 2 bytes")
- self.rt = rt
- self.rs = len(ri)
- if self.rs >= 256 * 256:
- raise BadArgument("Relay.fromData : RS size isn't 2 bytes")
- self.ri = ri
- self.body = body
- if(4 + self.rs + len(body) >= pow(256, 3)):
- raise BadArgument("Relay.fromData : Command body too long")
-
- def fromStrReader(self, sr, cs):
- """Fill a Relay Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- self.rs = strToIntBE(sr.next(2))
- self.rt = sr.next(2)
- self.ri = sr.next(self.rs)
- self.body = sr.next(cs - 4 - self.rs)
- except (BadArgument, IndexError):
- raise ParseError("Bad Formed Command : Relay")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = intToStrBE(self.rs, 2) + self.rt + self.ri + self.body
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Get(CommandCToS):
- """Get command
- self.l : list of mid (list of str of len midLength)"""
- def __init__(self):
- """Build a Get empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Get']
-
- def fromData(self, li):
- """Fill a Get Object from a list of message id (=string of len 20)"""
- #check that each element of the list has 20 bytes
- if not isMidList(li, midLength):
- raise BadArgument("Get.fromData : li is not a list of message id")
- self.l = li
- if midLength * len(self.l) >= 256 ** 3:
- raise BadArgument("Get.fromData : Command body too long")
-
- def fromStrReader(self, sr, cs):
- """Fill a Get Object from a StrReader
- raise ParseError if it is malformed"""
- start = sr.b
- try:
- self.l = sr.readMidList(midLength, start, cs)
- except (ParseError, IndexError):
- raise ParseError("Bad Formed Command : Get")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = ""
- for i in range(len(self.l)):
- s = s + self.l[i]
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Summarize(CommandCToS):
- """Summarize command
- self.num : maximum number of synopsis to retrieve (int)
- self.after : mid older than the synopsese retrieved"""
- def __init__(self):
- """Build a Summarize empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Summarize']
-
- def fromData(self, n, m):
- """Fill a Summarize Object from the max number of synopsis to retrieve (int) and the message id of a message older than the messages we want the synopsis from (string of len 20)
- the int is considered modulo 256^2"""
- n_mod = n % (256 * 256)
- self.num = n_mod
- if len(m) != midLength: # or m is not a str
- raise BadArgument(" Summarize.fromData : m is not a valid message ID")
- self.after = m
- #command body is small enough
-
- def fromStrReader(self, sr, cs):
- """Fill a Summarize Object from a StrReader
- raise ParseError if it is malformed"""
- if cs != 2 + midLength:
- raise ParseError("Bad Formed Command : Summarize")
- try:
- self.num = strToIntBE(sr.next(2))
- self.after = sr.next(midLength)
- except IndexError:
- raise ParseError("Bad Formed Command : Summarize")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = intToStrBE(self.num, 2) + self.after
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Delete(CommandCToS):
- """Delete command
- self.l : list of mid (list of str of len midLength)"""
- def __init__(self):
- """Build a Delete empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Delete']
-
- def fromData(self, li):
- """Fill a Delete Object from a list of message ID (string of length midLength"""
- #check that each element of the list has 20 bytes
- if not isMidList(li, midLength):
- raise BadArgument("Delete.fromData : li is not a list of message id")
- if midLength * len(li) >= pow(256, 3):
- raise BadArgument("Delete.fromData : Command body too long")
- self.l = li
-
- def fromStrReader(self, sr, cs):
- """Fill a Delete Object from a StrReader
- raise ParseError if it is malformed"""
- start = sr.b
- try:
- self.l = sr.readMidList(midLength, start, cs)
- except (ParseError, IndexError):
- raise ParseError("Bad Formed Command : Delete")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = ""
- for i in range(len(self.l)):
- s = s + self.l[i]
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Policy(CommandCToS):
- """Policy command
- self.opt : option name (str)
- self.val : option value (str)"""
- def __init__(self):
- """Build a Policy empty object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return CToSCODE['Policy']
-
- def fromData(self, opt, val):
- """Fill a Policy Object from 1 str : the option name
- and its value (either a str or a int depending on the option
- """
- if len(opt) > 255:
- raise BadArgument("Policy.fromData : Option name length too big")
- if opt in Common.userPolicy:
- self.opt = opt
- if opt == "SendMsgAfter":
- if val > pow(256, 2):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
- elif (opt == "SendSummaryAfter"):
- if val > pow(256, 2):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
- elif (opt == "EncryptSummaryAfter"):
- if val > pow(256, 2):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
- elif (opt == "MaxSURBsPerDay"):
- if val > pow(256, 2):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
- elif (opt == "HoldSURBs"):
- if val > pow(256, 2):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
- elif (opt == "HoldUntilAck"):
- if val in [ 'never', 'quota', 'always' ]:
- self.val = val
- elif (opt == "ShutdownPhrase"):
- if len(val) + 1 + len(opt) > pow(256, 3):
- raise BadArgument("Policy.fromData : Option value too long")
- self.val = val
-
- def fromStrReader(self, sr, cs):
- """Fill a Policy Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- le = ord(sr.next(1))
- self.opt = sr.next(le)
- inter = sr.next(cs - 1 - le)
- if(self.opt == "SendMsgAfter"):
- if len(inter) != 2:
- raise ParseError("Bad Formed Command : Policy")
- self.val = strToIntBE(inter)
- elif (self.opt == "SendSummaryAfter"):
- if len(inter) != 2:
- raise ParseError("Bad Formed Command : Policy")
- self.val = strToIntBE(inter)
- elif (self.opt == "EncryptSummaryAfter"):
- if len(inter) != 2:
- raise ParseError("Bad Formed Command : Policy")
- self.val = strToIntBE(inter)
- elif (self.opt == "MaxSURBsPerDay"):
- if len(inter) != 2:
- raise ParseError("Bad Formed Command : Policy")
- self.val = strToIntBE(inter)
- elif (self.opt == "HoldSURBs"):
- if len(inter) != 2:
- raise ParseError("Bad Formed Command : Policy")
- self.val = strToIntBE(inter)
- elif (self.opt == "HoldUntilAck"):
- if inter in [ 'never', 'quota', 'always']:
- self.val = inter
- elif (self.opt == "ShutdownPhrase"):
- self.val = inter
- except (IndexError, BadArgument):
- raise ParseError("Bad Formed Command : Policy")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = chr(len(self.opt)) + self.opt
- if self.opt in Common.userPolicy[:5]:
- s = s + intToStrBE(self.val, 2)
- if self.opt in Common.userPolicy[5:]:
- s = s + self.val
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-
-class CommandSToC:
- """CommandSToC astract class
- the CommandSToC derive from this class"""
- pass
-
-class Created(CommandSToC):
- """command Created
- self.nym : nym of the created account (str)
- self.challenge : challenge (str)"""
- def __init__(self):
- """Build a empty Created object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Created']
-
- def fromData(self, nym, ch):
- """Fill a Created Object from a nym(str) and a challenge(str)"""
- if len(nym) > 255:
- raise BadArgument("Created.fromData : nym too long")
- if 1 + len(nym) + len(ch) >= pow(256, 3):
- raise BadArgument("Created.fromData : challenge too long")
- self.nym = nym
- self.challenge = ch
-
- def fromStrReader(self, sr, cs):
- """Fill a Created Object from a StrReader
- raise ParseError if it is malformed"""
- start = sr.b
- try:
- self.nym = sr.readNym(start, cs)
- self.challenge = sr.next(cs + start - sr.b)
- except (IndexError, ParseError, BadArgument):
- raise ParseError("Bad Formed Command : Created")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = chr(len(self.nym)) + self.nym + self.challenge
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Status(CommandSToC):
- """command Status
- self.nMsg : number of email waiting (int)
- self.nSurb : number of surbs left (int)
- self.quota : maximum storage on nymserver in byte (int)
- self.used : used storage in byte (int)
- self.acks : list of seqno (list of (str of length seqNoLength))"""
- def __init__(self):
- """Build a empty Status object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Status']
-
- def fromData(self, nM, nS, q, u, l):
- """Fill a Status Object from a number of message(int), a number of surbs(int), a quota(int), a used(int) and a sequence of seqno(list of (str of size seqNoLength)"""
- #TODO replace the error by the taking of the maximal value?
- if nM >= pow(256, 4):
- raise BadArgument("Status.fromData : number of waiting e-mail too large")
- if nM >= pow(256, 2):
- raise BadArgument("Status.fromData : number of available surbs too large")
- if q >= pow(256, 4):
- raise BadArgument("Status.fromData : quota too large")
- if u >= pow(256, 4):
- raise BadArgument("Status.fromData : used space too large")
- if not isMidList(l, seqNoLength):
- raise BadArgument("Status.fromData : l is not a list of Sequence Number")
- if 14 + seqNoLength * len(l) >= pow(256, 3):
- # avoid numeric literals where possible TODO
- raise BadArgument("Status.fromData : l is too long")
- self.nMsg = nM
- self.nSurb = nS
- self.quota = q
- self.used = u
- self.acks = l
-
- def fromStrReader(self, sr, cs):
- """Fill a Status Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- start = sr.b
- self.nMsg = strToIntBE(sr.next(4))
- self.nSurb = strToIntBE(sr.next(2))
- self.quota = strToIntBE(sr.next(4))
- self.used = strToIntBE(sr.next(4))
- self.acks = sr.readMidList(seqNoLength, start, cs)
- except (IndexError, ParseError):
- raise ParseError("Bad Formed Command : Status")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = intToStrBE(self.nMsg, 4) + intToStrBE(self.nSurb, 2) +\
- intToStrBE(self.quota, 4) + intToStrBE(self.used, 4)
- for i in range(len(self.acks)):
- s = s + self.acks[i]
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Summary(CommandSToC):
- """command Summary
- self.bf : bit field indicating which one of the synopsis as a mail
- self.synSet : Encrypted synopses set
- """
- def __init__(self):
- """Build a empty Summary object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Summary']
-
- def fromData(self, bf, ess):
- """Fill a Object from bit field (str of len 2) and an encrypted set of synopses(str)"""
- if len(bf) != 2:
- raise BadArgument("Summary.fromData : bit field has not the good size, and size DOES matter")
- if 2 + len(ess) >= pow(256, 3):
- raise BadArgument("Summary.fromData : Encrypted set of synopses too long")
- self.bf = bf
- self.synSet = ess
-
- def fromStrReader(self, sr, cs):
- """Fill a Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- self.bf = sr.next(2)
- self.synSet = sr.next(cs - 2)
- except IndexError:
- ParseError("Bad Formed Command : Summary")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = self.bf + self.synSet
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Msg(CommandSToC):
- """command Msg
- self.mid : message ID (str of length midLength)
- self.msg : encrypted email (str)
- """
- def __init__(self):
- """Build a empty Msg object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Msg']
-
- def fromData(self, mid, msg):
- """Fill a Msg Object from a Mid (str of length midLength) and an encrypted msg (str)"""
- if len(mid) != midLength:
- raise BadArgument("Msg.fromData : mid has not the good length")
- if midLength + len(msg) >= pow(256, 3):
- raise BadArgument("Msg.fromData : msg too long")
- self.mid = mid
- self.msg = msg
-
- def fromStrReader(self, sr, cs):
- """Fill a Msg Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- start = sr.b
- self.mid = sr.readMid(midLength, start, cs)
- self.msg = sr.next(cs - midLength)
- except (IndexError, ParseError):
- raise ParseError("Bad Formed Command : Msg")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = self.mid + self.msg
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Dropped(CommandSToC):
- """command Dropped
- self.list : list of mid (list of (str of size midLength))
- """
- def __init__(self):
- """Build a empty Dropped object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Dropped']
-
- def fromData(self, l):
- """Fill a Dropped Object from a list of mid"""
- if( not isMidList(l, midLength)):
- raise BadArgument("Dropped.fromData : l is not a list of message id")
- if midLength * len(l) >= pow(256, 3):
- raise BadArgument("Dropped.fromData : Command body too long")
- self.list = l
-
- def fromStrReader(self, sr, cs):
- """Fill a Dropped Object from a StrReader
- raise ParseError if it is malformed"""
- start = sr.b
- try:
- self.list = sr.readMidList(midLength, start, cs)
- except (ParseError, IndexError):
- raise ParseError("Bad Formed Command : Dropped")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = ""
- for i in range(len(self.list)):
- s = s + self.list[i]
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-class Error(CommandSToC):
- """command Error
- self.nonce : nonce from client message (str of size seqNo)
- self.error : english langage error description (str)
- """
- def __init__(self):
- """Build a empty Error object"""
- pass
-
- def ct(self):
- """return a int for the code of the command"""
- return SToCCODE['Error']
-
- def fromData(self, seqNo, error):
- """Fill a Object from a seqno (str of len seqNoLength) and an error message(str)"""
- if len(seqNo) != seqNoLength:
- raise BadArgument("Error.fromData : seqNo has not the good length")
- if seqNoLength + len(error) >= pow(256, 3):
- raise BadArgument("Error.fromData : msg too long")
- self.nonce = seqNo
- self.error = error
-
- def fromStrReader(self, sr, cs):
- """Fill a Object from a StrReader
- raise ParseError if it is malformed"""
- try:
- start = sr.b
- self.mid = sr.readMid(seqNoLength, start, cs)
- self.msg = sr.next(cs - seqNoLength)
- except (IndexError, ParseError):
- raise ParseError("Bad Formed Command : Error")
-
- def __str__(self):
- """Give the string of the Command that would be sent in a control message"""
- s = self.nonce + self.error
- assert len(s) < pow(256, 3)
- return chr(self.ct()) + intToStrBE(len(s), 3) + s
-
-def buildMessage(comList):
- """Turn a list of Command into a sendable message"""
- s = ""
- for c in comList:
- s = s + str(c)
- return s
-
-if (__name__ == '__main__'):
- import Mail
- l = []
- for i in range(3):
- l.append(Mail.genMid(length = 20))
- H = Header()
- H.fromData("JR", "01234567890123456789")
- print H
- del H
- print "test Create"
- C = Create()
- C.fromData(["Lolo_", "Marsux", "Azatoth_", "Keeh"])
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Create2"
- C = Create2()
- C.fromData("azertyuiop42")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Surb"
- C = Surb()
- C.fromData("-" * 2104)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Newpk"
- C = Newpk()
- k1 = _cr.pk_generate()
- k2 = _cr.pk_generate()
- C.fromData(k1, k2)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Relay"
- C = Relay()
- C.fromData("ab", "too lazy to make real ones here", "this is the body of the email. It sholud contain some kind of header, but for testing the parser, who cares?")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Get"
- C = Get()
- C.fromData(l)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Summarize"
- C = Summarize()
- C.fromData(10, l[0])
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Delete"
- C = Delete()
- C.fromData(l)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
- print "test Policy"
- C = Policy()
- C.fromData("HoldUntilAck", "never")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandCToS()
- s2 = str(D)
- assert s1 == s2
-
- print "test Created"
- C = Created()
- C.fromData("Marsux", "what is the answer to universe life and everything")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
- print "test Status"
- C = Status()
- C.fromData(5, 10, 424242, 212121, l)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
- print "test Summary"
- C = Summary()
- C.fromData("ab","this should be an encrypted set of synopses")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
- print "test Msg"
- C = Msg()
- C.fromData(l[0], "Say something stupid, like, I am wearing female's underwear")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
- print "test Dropped"
- C = Dropped()
- C.fromData(l)
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
- print "test Error"
- C = Msg()
- C.fromData(l[0], "Help! Help! I am being repressed!")
- s1 = str(C)
- S = StrReader(s1)
- D = S.readCommandSToC()
- s2 = str(D)
- assert s1 == s2
-
Copied: trunk/nymbaron/Message.py (from rev 306, trunk/nym3/Message.py)
Deleted: trunk/nymbaron/Server/Main.py
===================================================================
--- trunk/nym3/Server/Main.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Server/Main.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,238 +0,0 @@
-#!/usr/bin/python
-# -*- coding: iso-8859-1 -*-
-# $Id$
-#
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Server.Main
-
- This package contains an implementation of a nym3 server."""
-
-import sys
-import os
-import getopt
-import nym3.Server.User as User
-import nym3.Server.Config as Config
-import nym3.Message as Message
-import nym3.Common as Common
-import nym3.Mail as Mail
-
-lifeCycle = User.lifeCycle
-"""The life cycle of a mail received by the server for a nym"""
-
-def processIncoming(localpart, msg):
- """Process incoming mail from the MTA
- - identifies the nym the mail is addressed to
- - checks whether the new mail violates its quota
- - finally stores the message """
- try:
- nymuser = User.User(localpart)
- except User.NoSuchUser:
- print "No such user " + localpart
- sys.exit(73)
- # TODO : we should honor the "Quota" sending policy.
- if nymuser.usage() + len(msg) > nymuser.quota():
- print "Quota exceeded."
- print repr(nymuser.usage()) + " > " + repr(nymuser.quota())
- sys.exit(75)
- # Valid user, not over quota. Filtering policy can wait.
- nymuser.store(msg)
- sys.exit(0)
-
-def processMessage(msg):
- """process incoming control message from the Mixminion serveur
- - verifies signature
- - parse the message into header + sequence on control commands
- - run appropriate actions"""
-
- class MyException(Exception): pass
-
- sr = Message.StrReader(msg)
- try:
- h = sr.readHeader()
- if (h.nym == ""):
- comList = sr.readCommandCToSList()
- try:
- if (len(comList) != 3):
- raise MyException()
- #this may be an account setup message
- #we suppose there is a exactly 1 Create Command in the message,
- # 1 Newpk, and 1 surb. more will raise an error
-
- nymUser = None
- #phase 1 we look for the command create
- for idx, com in enumerate(comList):
- if(com.ct() == 0): # TODO : evil numeric litteral
- #the Create command
- for pnym in com.list:
- try:
- nymUser = User.User(pnym,1)
- except User.AlreadySuchUser: pass
- if(nymUser != None):
- break
- if (nymUser == None):
- #TODO send an Error message to the client when surbs become available?
- #for the time being just ignore
- print "All nyms proposed in the list were already attributed"
- raise MyException()
- del(comList[idx])
- break
- if(nymUser == None):
- #TODO send an Error message to the client when surbs become available?
- #for the time being just ignore
- print "No Create Command"
- raise MyException()
-
- #phase 2 we look for the command surb
- for idx, com in enumerate(comList):
- if(com.ct()==2): # TODO : evil numeric litteral
- nymUser.addSurbs(com.surbs)
- del (comList[idx])
- break
- if(len(comList) != 1):
- nymUser.abort()
- raise MyException()
- #phase 3 the last command should be a Newpk
- com = comList[0]
- if(com.ct() != 3): # TODO : evil numeric litteral
- nymUser.abort()
- raise MyException()
- nymUser.setKeys(com.ekid, com.ekenc)
- if(not nymUser.checkMessageSign(msg[Message.sigLength:],h.sig)):
- nymUser.abort()
- raise MyException()
- else: # Valid account creation request. Send CREATED
- nymUser.received(h.seqNo)
- created = Message.Created()
- created.fromData(nymUser.username, "")
- nymUser.setUp()
- nymUser.advanced_send(Message.buildMessage([created]))
- except MyException:
- #if you come here something went wrong during the account
- #initialization
- print "Bad formed account creation message"
- sys.exit(2) #TODO smart error code
- else: # NYM is not empty
- try:
- nymUser = User.User(h.nym)
- except User.NoSuchUser:
- print "No such user %s" % h.nym
- sys.exit(73) #TODO is it the smart error code
- if(not nymUser.checkMessageSign(msg[Message.sigLength:],h.sig)):
- return
- #The message comes from a known nick and is authenticated
- #register the seqNo as received
- #TODO is it ok?
- nymUser.received(h.seqNo)
- comList = sr.readCommandCToSList()
- for com in comList:
- if (com.ct() == Message.CToSCODE['Create']):
- #we ignore the Create command if it comes from the nymholder of an account, should we rise an error?
- pass
- #if the account is not initialized, or if it is already up we ignore Create2 messages
- elif (com.ct() == Message.CToSCODE['Create2']):
- if (nymUser.isInitialized() and (not nymUser.isUp())):
- if(nymUser.checkChallenge(com.cr)):
- nymUser.setUp()
- #other commands are only taken into account if the account is up
- elif (nymUser.isUp()):
- if (com.ct() == Message.CToSCODE['Surb']):
- if (len(com.surbs) == 0):
- nymUser.delSurbs()
- else:
- nymUser.addSurbs(com.surbs)
- elif (com.ct() == Message.CToSCODE['Newpk']):
- nymUser.setKeys(com.kid,com.kenc)
- elif (com.ct() == Message.CToSCODE['Relay']):
- ec = Mail.relay(h.nym, com.rt, com.ri,
- Config.serverName, com.body)
- if (ec != 0):
- print "mixminion exited abnormally with error code %d" % ec
- sys.exit(2)
-
- elif (com.ct() == Message.CToSCODE['Get']):
- msgList = []
- sendList = []
- for m in com.l:
- if nymUser.hasMail(m):
- msgCom = Message.Msg()
- msgCom.fromData(m,nymUser.getMail(m))
- msgList.append(msgCom)
- sendList.append(m)
- ec = nymUser.advanced_send(
- Message.buildMessage(msgList))
- print "EC is " + str(ec)
- if (ec == 0):
- nymUser.markMid(sendList,lifeCycle['sent-in-full'])
- if(nymUser['HoldUntilAck'] == 'never'):
- map(nymUser.delete_msg,sendList)
-
- else:
- print "mixminion exited abnormally with error code %d" % ec
- sys.exit(2)
-
- elif (com.ct() == Message.CToSCODE['Summarize']):
- comList = []
- sendList = nymUser.prepareSummary(com.num, com.after)
- mList = []
- print str(sendList)
- for (ml, bf, blob) in sendList:
- sumCom = Message.Summary()
- sumCom.fromData(bf, blob)
- comList.append(sumCom)
- mList = mList + ml
- ec = nymUser.send(Message.buildMessage(comList))
- if (ec == 0):
- nymUser.markMid(mList, lifeCycle['synopsis-sent'])
- else:
- print "mixminion exited abnormally with error code %d" % ec
- sys.exit(2)
-
- elif (com.ct() == Message.CToSCODE['Delete']):
- for mid in com.l:
- nymUser.delete_msg(mid)
- elif (com.ct() == Message.CToSCODE['Policy']):
- if(com.opt in Common.userPolicy):
- nymUser[com.opt] = com.val
- else:
- pass
- except Message.ParseError, inst:
- print inst
- sys.exit(2) #TODO error code
-
-def main(argv):
- optlist, pholder = getopt.getopt(argv[1:], 'D:d:m')
- for o, a in optlist:
- if o == "-D":
- Config.DEBUG = True
-
- for o, a in optlist:
- if o == "-d": # mail delivery
- processIncoming(a, sys.stdin.read())
- sys.exit(0)
- if o == "-m":
- processMessage(sys.stdin.read())
- sys.exit(0)
-
-if __name__ == '__main__':
- main(sys.argv)
Copied: trunk/nymbaron/Server/Main.py (from rev 306, trunk/nym3/Server/Main.py)
Deleted: trunk/nymbaron/Server/User.py
===================================================================
--- trunk/nym3/Server/User.py 2005-07-12 19:53:38 UTC (rev 303)
+++ trunk/nymbaron/Server/User.py 2005-09-17 13:18:46 UTC (rev 307)
@@ -1,602 +0,0 @@
-# $Id$
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2004,2005 Jean-René Reinhard <jr at komite.net>
-# and Laurent Fousse <laurent at komite.net>.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to
-# do so, provided that the above copyright notice(s) and this permission
-# notice appear in all copies of the Software and that both the above
-# copyright notice(s) and this permission notice appear in supporting
-# documentation.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
-# LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR
-# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""nym3.Server.User
-
- This module contains methods of storing, accessing and editing user account
- information."""
-
-import os
-import nym3.Server.Config as Config
-import nym3.Common as Common
-import nym3.Mail as Mail
-import nym3.Crypto as Crypto
-import nym3.Message as Message
-import pickle
-import string
-import time
-import mixminion.Common
-from nym3.Message import intToStrBE
-import mixminion.Crypto as _cr
-from mixminion.BuildMessage import getNPacketsToEncode
-from mixminion.Packet import parseReplyBlocks, ParseError
-
-lifeCycle = { 'nothing-sent' : 0, 'synopsis-sent' : 1,
- 'sent-in-full' : 2, 'deleted' : 3 }
-"""The life cycle of a mail received by a nym"""
-
-class NoSuchUser(Exception): pass
-"""Exception thrown when a user account identified by its nym can't be found
-"""
-
-class AlreadySuchUser(Exception): pass
-"""Exception thrown when a user account identified by its nym already exists
-"""
-
-def binsearch(l, prop):
- """Returns the first index i of l for which l[i] is true,
- assuming prop is increasing over l"""
- min = 0
- max = len(l) - 1
- if not prop(l[max]): return IndexError
- if prop(l[min]): return min
-
- current = (max + min) / 2
- while max - min > 1:
- if prop(l[current]):
- max = current
- current = (max + min) / 2
- else:
- min = current
- current = (max + min) / 2
- return max
-
-class User:
- """Hold user data"""
- def __init__(self, username, create = 0):
- """0 : load user data throw an error if the user doesn't exist
- 1 : create user data throw an error if the user exists
- _ : load a user data, if it doesn't exist create a new user silently
- """
- self.datafile = Config.path + os.sep + username + '.dat'
- self.username = username
- self.index = None
- self.mbox = None
- self.syn = None
- self.data = None
- self._abort = False
- self._lock()
- try:
- f = open(self.datafile, 'r')
- if (create == 1):
- f.close()
- raise AlreadySuchUser
- self.data = pickle.load(f)
- f.close()
- except IOError:
- if create == 0: raise NoSuchUser()
- self.data = Config.default_settings
- self.data['username'] = username
- self.data['received'] = []
- self.data['nSurbs'] = 0
- self.data['seqNo'] = 0
-
- def __del__(self):
- """Flush the information contained in this User to the disk
- """
- if (not self._abort):
- self._save_index()
- self._save_synbox()
- self._save_mbox()
- self._save_data()
- self._release()
-
- def __getitem__(self, key):
- """Gets the fields of this Object as if a hashtable
- """
- return self.data[key]
-
- def __setitem__(self, key, value):
- """Sets the fields of this Object as if a hashtable"""
- self.data[key] = value
-
- def _lock(self):
- """Lock the user. For well behaved functions."""
- self.lock = mixminion.Common.Lockfile(Config.path + os.sep +
- self.username + '.lck')
- self.lock.acquire(blocking = 1)
-
- def _release(self):
- """Releases the lock"""
- self.lock.release()
-
- def abort(self):
- """permit to destroy a User object without saving its data
- Can only be used if the object is being created for the
- first time. If username.dat exist, it is ignored
- """
- try:
- os.stat(self.datafile)
- except:
- self._abort = True
-
- def timecmp(self, a, b):
- return cmp(self.index[a]['time'], self.index[b]['time'])
-
- def quota(self):
- """Gets the quota, the maximum data size authorized"""
- return self.data['quota']
-
- def usage(self):
- """Gets the usage, the actual data size used"""
- sum = 0
- try:
- sum = os.stat(self.surbfile())[6]
- except: pass
- try:
- sum = sum + os.stat(self.indexfile())[6]
- except: pass
- try:
- sum = sum + os.stat(self.synboxfile())[6]
- except: pass
- try:
- sum = sum + os.stat(self.mboxfile())[6]
- except: pass
- self.data['usage'] = sum
- return sum
-
- def received(self, seqNo):
- self['received'].append(seqNo)
-
- def idKey(self):
- """Gets the user public key used for identification"""
- return self.data['idKey']
-
- def encKey(self):
- """Gets the user public key used for encryption"""
- return self.data['encKey']
-
- def blobify(self, l):
- """encrypts a set of synopses
- l is a list of tuples (mid, status, synopsis) """
- if len(l) == 0: return None
- s = ""
- m = []
- for mid, status, syn in l:
- assert status == "clear"
- assert len(mid) == 1
- m = m + mid
- #print "mid = %s, status = %s, syn = %s\n" % (str(mid), str(status), str(syn))
- s = s + mid[0] + intToStrBE(len(syn), 2) + syn
- return (m, 'encrypted', Crypto.nym_encrypt(s, self.encKey()))
-
- def getSyn(self, mid):
- """Retrieve a blurb consisting of the synopsis of the
- requested synopsis mid. May contain unwanted synopsis
- too."""
- self.load_synbox()
- for i, (midlist, status, synblob) in enumerate(self.syn):
- if mid in midlist: return i, (midlist, status, synblob)
- raise ValueError()
-
- def getMail(self, mid):
- """Retrieve an encrypted mail with a given mid from the mbox
- raise ValueError if the mail cannot be found"""
- self.load_mbox()
- try:
- return self.mbox[mid]
- except:
- raise ValueError()
-
- def midAfter(self, mid):
- """Retrieve mids of messages that came strictly after message `mid'
- the elements of the output are ordered by ascending
- order of arrival time"""
- self.load_index()
-
- ret = []
- #TODO if the mid can't be found in the index tab, what do we do?
- if mid == Mail.oldestMid: ret = self.index.keys()
- else:
- midtime = self.index[mid]['time']
- for msg in self.index.keys():
- if self.index[msg]['time'] > midtime: ret.append(msg)
- ret.sort(self.timecmp)
- return ret
-
- def _save_data(self):
- """Flush to the disk the non message related data contained by this User object
- """
- if self.data == None: return
- f = open(self.datafile, 'w')
- pickle.dump(self.data, f)
- f.close()
-
- def surbfile(self):
- """Gets the path to file containing the surbs to send messages to the nymholder
- """
- return Config.path + os.sep + self.data['username'] + '.surbs'
-
- def advanced_send(self, msg, add_status = True):
- """Sends a message to the nymholder through the mixminion network,
- using the surbs provided by the nymholder. Adds a status control
- message if the add_status argument is True. Also updates the number
- of surbs"""
- #TODO add the filling of the remaining place available in the the
- #message by syns and msgs here
- statusc = Message.Status()
- nMsg = 0
- usage = 0
- cmsg = msg
- if add_status:
- #determine the status elements, the number of surbs is assumed
- #to stay equal to its value
- self.load_index()
- for mid, v in self.index.iteritems():
- if v['status'] in [lifeCycle['nothing-sent'], lifeCycle['synopsis-sent']]:
- nMsg += 1
- usage = self.usage()
- statusc.fromData(nMsg, 0, self.quota(), usage, self['received'])
- cmsg += str(statusc)
- nb_req_surb = getNPacketsToEncode(cmsg, 0)
- if add_status:
- while True:
- statusc.fromData(nMsg, self['nSurbs'] - nb_req_surb,
- self.quota(), usage, self['received'])
- cmsg = msg + str(statusc)
- act_nb = getNPacketsToEncode(cmsg, 0)
- old_nb_req_surb = nb_req_surb
- nb_req_surb = act_nb
- if nb_req_surb <= old_nb_req_surb:
- break
- if nb_req_surb > self['nSurbs']:
- #TODO
- # - send an Error message "low on surbs" if a surb is available
- # - store the message we couldn't send for later
- return 1
- #TODO debbuging cruft
- print "%d surbs used to send control messages" % nb_req_surb
- self['nSurbs'] -= nb_req_surb
- self['received'] = []
- return self.send(cmsg)
-
-
- def send(self, msg):
- """Sends a message to the nymholder through the mixminion network,
- using the surbs provided by the nymholder"""
- if Config.DEBUG:
- print msg
- return 0
- else:
- fname = Mail.tmpFileMsg(msg)
- ec = os.system("mixminion send -R " + self.surbfile() + " -i " + fname)
- # TODO : debugging cruft
- #os.unlink(fname)
- print "Message file left in " + fname
- return ec
-
- def clean_surbs(self):
- """Inspect the surbs and delete the used/outdated"""
- pass
- # TODO : this function relied on the false assumption that
- # SURBs have fixed length. A rewrite is needed.
- fname = self.surbfile()
- f = open(fname, "r")
- buffer = f.read()
- f.close()
- surbs = parseReplyBlocks(buffer)
- goods = []
- for surb in surbs:
- fname = '/tmp/' + Mail.mid2filename(Mail.genMid())
- fname = string.strip(fname)
- f = open(fname, 'w')
- f.write(surb)
- f.close()
- ec = os.system("mixminion inspect-surb " + fname +
- " |grep 'Used: no'")
- os.unlink(fname)
- if ec == 0: goods.append(surb)
- self.data['nSurbs'] = 0
- f = open(self.surbfile(), "w")
- for surb in goods:
- f.write(surb)
- self.data['nSurbs'] = self.data['nSurbs'] + 1
- f.close()
-
- def addSurbs(self, surbs):
- """Adds surbs to the store of surbs"""
- try:
- #TODO debbuging cruft to remove
- nb = len(parseReplyBlocks(surbs))
- self['nSurbs'] += nb
- except ParseError:
- #the provided binary data isn't a succession of binary surbs
- return
- fname = self.surbfile()
- f = open(fname, "a")
- f.write(surbs)
- f.close()
- print "%d surbs added" % nb
-
- def delSurbs(self):
- """Deletes all the surbs of the surb store"""
- fname = self.surbfile()
- os.unlink(fname)
- self.data['nSurbs'] = 0
-
- def load_mbox(self):
- """Loads the mailbox from the disk"""
- if not self.mbox == None: return
- mbox = self.mboxfile()
- try:
- f = open(mbox, 'r')
- self.mbox = pickle.load(f)
- f.close()
- except IOError:
- self.mbox = {}
-
- def mboxfile(self):
- """Gets the mailbox file name"""
- return Config.path + os.sep + self.data['username'] + '.mbox'
-
- def _save_mbox(self):
- """Flushs the mailbox to the disk"""
- if self.mbox == None: return
- mbox = self.mboxfile()
- f = open(mbox, 'w')
- pickle.dump(self.mbox, f)
- f.close()
-
- def synboxfile(self):
- """Gets the synbox file name"""
- return Config.path + os.sep + self.data['username'] + '.syn'
-
- def indexfile(self):
- """Gets the index filename"""
- return Config.path + os.sep + self.data['username'] + '.idx'
-
- def load_synbox(self):
- """Loads the synbox, structure containing synopses, from the disk"""
- if not self.syn == None: return
- synbox = self.synboxfile()
- try:
- f = open(synbox, 'r')
- self.syn = pickle.load(f)
- f.cl