Index: postman/archives/README
===================================================================
--- postman/archives/README	(revision 2)
+++ postman/archives/README	(revision 2)
@@ -0,0 +1,1 @@
+Default archives location
Index: postman/config.py
===================================================================
--- postman/config.py	(revision 1)
+++ postman/config.py	(revision 2)
@@ -4,5 +4,8 @@
 
 # The default path for storage files
-storage_path = os.path.join(os.path.dirname(__file__), 'db')
+storage_path = os.path.join(os.path.dirname(__file__), 'storage')
+
+# The default path for the archives
+archive_path = os.path.join(os.path.dirname(__file__), 'archives')
 
 # Set to True to set that, by default, only emails from members
Index: postman/daemon.py
===================================================================
--- postman/daemon.py	(revision 2)
+++ postman/daemon.py	(revision 2)
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+
+import os
+
+from postman import config
+from postman.models import Member, MailingList
+from postman.storage import JsonStorage as Storage
+
+
+class Postman():
+
+    def __init__(self, configfile=None):
+        if not configfile:
+            # FIXME: This is not used right now, we are getting the stuff
+            # from postman.config, but we will move configurations to a
+            # external ini-style config file soon
+            configfile = os.path.join(os.path.dirname(__file__),
+                                      '../conf/postman.conf')
+        self.configfile = configfile
+
+        # lists were the currently managed mailing lists information is going
+        # to be saved
+        self.mailings = {}
+        self.mailings_addresses = []
+        
+        # the files were internal information (like active mailing lists,
+        # members, etc) is saved
+        self.dbs = {'mailings': Storage(os.path.join(config.storage_path,
+                                                     'mailings.json')),
+                    'members': Storage(os.path.join(config.storage_path,
+                                                    'members.json'))}
+
+    def save(self):
+        """
+        Save all the current managed data to disk
+        """
+        if self.mailings:
+            # Save the config file from where we can reload information about the
+            # mailing lists managed by this postman instance
+            self.dbs['mailings'].write(self.mailings_addresses)
+            # Save each mailing list data into its separated persistence file
+            for m in self.mailings.keys():            
+                self.mailings[m].save()
+
+    def load(self):
+        """
+        Load all data from the storage files
+        """
+        if self.dbs['mailings'].exists():
+            # load the list of managed mailing lists
+            # FIXME: This is quite naive, we do not perform any check here after
+            # loading the data from the json file, which can be modified by
+            # untrustred users.
+            self.mailings_addresses = self.dbs['mailings'].read()
+
+            # now load all the mailing objects:
+            for address in self.mailings_addresses:
+                mailing = MailingList(address, address)
+                mailing.load()
+                self.mailings[address] = mailing                
+            
+
+    def add_mailing_list(self, list_info={}):
+        """
+        Add a new mailing list to this postman instance. expects one parameter,
+        list_info, which is a dictionary that should contain, at least, the
+        following keys:
+
+         - name: (string) the name we will give to the list
+         - address: (string) the email address of the list
+         - members: (list) a list of email adddress of the list members
+         
+        """
+        if not isinstance(list_info, dict):
+            raise TypeError(list_info, ' is not a valid dictionary')
+
+        if 'name' not in list_info.keys() or \
+           'address' not in list_info.keys() or \
+           'members' not in list_info.keys():
+            raise ValueError(list_info, ' does not seem to be a valid configuration')
+        
+        if list_info['address'] in self.mailings_addresses:
+            raise IndexError(list_info['address'],
+                             ' has been already added to postman')
+
+        mailing = MailingList(list_info['name'], list_info['address'],
+                              list_info['members'])        
+        self.mailings[mailing.address] = mailing
+        self.mailings_addresses.append(mailing.address)
+        # After adding new mailings, save them to disk
+        self.save()
+        
+    def add_mailing_member(self, member_addr=None, list_addr=None):
+        """
+        Add a new member for the mailing list represented by list_addr (a string
+        containing the email address of any mailing list managed by this postman
+        instance). member_addr is a string representing the email address of the
+        new member
+        """
+
+        if not member_addr:
+            raise ValueError(member_addr, 'missing member address')
+
+        if not list_addr:
+            raise ValueError(list_addr, 'missing list address')
+
+        if list_addr not in self.mailings_addresses:
+            # FIXME: Perhaps we should add it, perhaps not (mispelled address?)
+            raise IndexError(list_addr, ' is not a valid mailing list')
+
+        member = Member(member_addr)
+        
+        
+
+
+
+
+
+
Index: postman/models.py
===================================================================
--- postman/models.py	(revision 1)
+++ postman/models.py	(revision 2)
@@ -3,5 +3,5 @@
 import os
 from tools import validate_email_address
-from storage import JsonStorage
+from storage import JsonStorage as Storage
 import config
 
@@ -38,6 +38,13 @@
         self.members = members
         self.config = config
-        self.storage = JsonStorage(os.path.join(config['storage'],
-                                                self.address))
+        self._validate_config() # validate the config parameters
+        self.storage = Storage(os.path.join(self.config['storage'],
+                                            self.address))
+        self.archive = Storage(os.path.join(self.config['archive'],
+                                            self.address))
+        # try to load data from the storage
+        loaded = self.load()
+        # FIXME: if loaded is False, the storage does not exist, perhaps
+        # this would be a good place to create it for the first time
 
     def __repr__(self):
@@ -50,21 +57,20 @@
         if not 'storage' in self.config.keys():
             self.config['storage'] = os.path.join(config.storage_path,
-                                                  self.address)
+                                                  'mailings/')
         if not 'archive' in self.config.keys():
-            self.config['archive'] = os.path.join(self.config['storage'],
-                                                  'archive')
+            self.config['archive'] = config.archive_path
         if not 'private' in self.config.keys():
-            self.config['private'] = config.private
+            self.config['private'] = config.private_mailing
         
-    def _validate_member_object(self, member):
+    def _validate_member_object(self, member=None):
         if not isinstance(member, Member):
             raise TypeError(member, ' is not a valid Member instance')
         return member
 
-    def _validate_member(self, member):
+    def _validate_member(self, member=None):
         member = self._validate_member_object(member)
         return member.address in self.members_addresses()
 
-    def _validate_member_by_address(self, address):
+    def _validate_member_by_address(self, address=None):
         if not validate_email_address(address):
             raise ValueError(address, ' is not a valid email address')
@@ -81,5 +87,5 @@
         return True
 
-    def add_member_by_address(self, address):
+    def add_member_by_address(self, address=None):
         if self._validate_member_by_address(address):
             return False
@@ -88,5 +94,5 @@
         return True
                 
-    def delete_member(self, member):
+    def delete_member(self, member=None):
         member = self._validate_member_object(member)
         if not self._validate_member(member):
@@ -101,4 +107,6 @@
             self.address = data.address
             self.members = data.members
+            return True
+        return False
     
     def save(self):
Index: postman/mta.py
===================================================================
--- postman/mta.py	(revision 1)
+++ postman/mta.py	(revision 2)
@@ -8,42 +8,51 @@
 class Sendmail():
     
-    def __init__(self, mailing_list):
+    def __init__(self, mailing_list=None):
         if not isinstance(mailing_list, MailingList):
             raise ValueError(mailing_list, ' is not a valid mailing list')
         self.mailing_list = mailing_list
         self.suscriptors = self.mailing_list.members_addresses
-        self.reply_address = self.mailing_list.address
+        self.reply_to = self.mailing_list.address
+        self.raw_email = None
         self.queue = []
         self.archive = self.mailing_list.config.get('archive',
-                                                    config.storage_path)
+                                                    config.archive_path)
         
-    def get_raw_email():
+    def get_raw_email(self):
         try:        
-            raw_email = sys.stdin.read()
+            self.raw_email = sys.stdin.read()
         except:
             raise IOError('Can not get a valid email from stdin')
-        return raw_email
+        return self.raw_email
 
-    def save_raw_email():
+    def save_raw_email(self):
+        if not self.raw_email:
+            # FIXME: perhaps a while loop here, with some maximum recursion
+            # check, would be nice here
+            self.get_raw_email
         filename = os.path.join(self.archive,
                                 datetime.today().strftime('%Y%d%m%H%M%S%f'))
         tmpfile = file(filename, 'w')
-        tmpfile.write(raw_email)
+        tmpfile.write(self.raw_email)
         tmpfile.close()
         self.queue.append(filename)
 
-    def send_email():
-        if not self.queue:
-            raise ValueError('The emails queue is empty')
-        next_email = self.queue.pop()
-        email_file = file(next_email, 'r')
-        email_data = email.message_from_file(email_file)
-        email_file.close()
+    def send_email(self):
+        if self.queue:
+            next_email = self.queue.pop()
+            email_file = file(next_email, 'r')
+            email_data = email.message_from_file(email_file)
+            email_file.close()
 
-        email_data['Reply-to'] = self.reply_address
+            email_data['Reply-to'] = self.reply_to
 
-        smtp_conn = smtplib.SMTP()
-        smtp_conn.connect()
-        smtp_conn.sendmail(email_data['From'], self.suscriptors,
-                           email_data.as_string())
-        smtp_conn.close()        
+            smtp_conn = smtplib.SMTP()
+            smtp_conn.connect()
+            smtp_conn.sendmail(email_data['From'], self.suscriptors,
+                               email_data.as_string())
+            smtp_conn.close()
+
+    def run(self):
+        self.get_raw_email()
+        self.save_raw_email()
+        self.send_email()
Index: postman/storage/README
===================================================================
--- postman/storage/README	(revision 2)
+++ postman/storage/README	(revision 2)
@@ -0,0 +1,1 @@
+Default storage location
Index: postman/storage/mailings/README
===================================================================
--- postman/storage/mailings/README	(revision 2)
+++ postman/storage/mailings/README	(revision 2)
@@ -0,0 +1,1 @@
+Default mailing list storage location
Index: postman/tests/__init__.py
===================================================================
--- postman/tests/__init__.py	(revision 1)
+++ postman/tests/__init__.py	(revision 2)
@@ -2,4 +2,4 @@
 
 from models import *
+from mta import *
 
-
Index: postman/tests/mta.py
===================================================================
--- postman/tests/mta.py	(revision 2)
+++ postman/tests/mta.py	(revision 2)
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import os, sys
+from unittest import TestCase
+
+from postman.models import Member, MailingList
+from postman.mta import Sendmail
+
+
+class TestSendmail(TestCase):
+    def setUp(self):
+        config = {'private': False, 'archive': '/tmp/postman-tests/archive',
+                  'storage': '/tmp/postman-tests/storage'}
+        self.mailing_list = MailingList('test_list', 'test_list@example.com',
+                                        members={}, config=config)
+        self.member =  Member('test@example.com')
+        self.mta = Sendmail(self.mailing_list)
+        self.raw_email_file = os.path.join(os.path.dirname(__file__),
+                                           'sample_raw_email.txt')
+        tmp_to_read = open(self.raw_email_file, 'r')
+        self.raw_email = tmp_to_read.read()
+        tmp_to_read.close()
+        
+    def test___init__(self):
+        with self.assertRaises(ValueError):
+            mta = Sendmail('test_list@example.com')
+            mta = Sendmail(self.member)
+            mta = Sendmail(None)
+        mta = Sendmail(self.mailing_list)
+        self.assertTrue(isinstance(mta, Sendmail))
+        self.assertEqual(mta.mailing_list, self.mailing_list)
+        self.assertEqual(mta.suscriptors, self.mailing_list.members_addresses)
+        self.assertEqual(mta.reply_to, self.mailing_list.address)
+
+    def test_get_raw_email(self):
+        sys_stdin = sys.stdin
+        sys.stdin = open(self.raw_email_file, 'r')
+        self.assertEqual(self.mta.get_raw_email(),
+                         self.raw_email)
+        sys.stdin.close()
+        with self.assertRaises(IOError):
+            self.mta.get_raw_email()
+
+    #def save_raw_email(self):
+    #    sys_stdin = sys.stdin
+    #    sys.stdin = open(self.raw_email_file, 'r')
+    #    self.mta.save_raw_email.filename = '/tmp/postman-test-mta-save-raw-email'
+    
+        
+        
+        
+            
+            
+        
Index: postman/tests/sample_raw_email.txt
===================================================================
--- postman/tests/sample_raw_email.txt	(revision 2)
+++ postman/tests/sample_raw_email.txt	(revision 2)
@@ -0,0 +1,179 @@
+From root@fenrir.codigo23.lab Mon May 14 03:22:20 2012
+Return-Path: <root@fenrir.codigo23.lab>
+Received: from fenrir.codigo23.lab (localhost [127.0.0.1])
+	by fenrir.codigo23.lab (8.14.5/8.14.5) with ESMTP id q4D1MNxD038292
+	for <root@fenrir.codigo23.lab>; Sun, 13 May 2012 03:22:23 +0200 (CEST)
+	(envelope-from root@fenrir.codigo23.lab)
+Received: (from root@localhost)
+	by fenrir.codigo23.lab (8.14.5/8.14.5/Submit) id q4D1MNLp038270
+	for root; Sun, 13 May 2012 03:22:23 +0200 (CEST)
+	(envelope-from root)
+Date: Sun, 13 May 2012 03:22:23 +0200 (CEST)
+From: Charlie Root <root@fenrir.codigo23.lab>
+Message-Id: <201205130122.q4D1MNLp038270@fenrir.codigo23.lab>
+To: root@fenrir.codigo23.lab
+Subject: fenrir.codigo23.lab security run output
+
+
+Checking setuid files and devices:
+
+Checking for uids of 0:
+root 0
+toor 0
+
+Checking for passwordless accounts:
+
+Checking login.conf permissions:
+
+Checking for ports with mismatched checksums:
+
+fenrir.codigo23.lab kernel log messages:
++++ /tmp/security.94lpgJ1k	2012-05-13 03:22:14.000000000 +0200
++Accounting disabled
++Accounting enabled
++Accounting disabled
++Accounting enabled
+
+fenrir.codigo23.lab login failures:
+
+fenrir.codigo23.lab refused connections:
+
+Checking for a current audit database:
+
+Database created: Sat May 12 03:20:04 CEST 2012
+
+Checking for packages with security vulnerabilities:
+
+Affected package: nvidia-driver-285.05.09
+Type of problem: NVIDIA UNIX driver -- access to arbitrary system memory.
+Reference: http://portaudit.FreeBSD.org/b91234e7-9a8b-11e1-b666-001636d274f3.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/94c0ac4f-9388-11e1-b242-00262d5ed8ee.html
+
+Affected package: firefox-8.0,1
+Type of problem: mozilla -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/380e8c56-8e32-11e1-9580-4061862b8c22.html
+
+Affected package: png-1.4.8
+Type of problem: png -- memory corruption/possible remote code execution.
+Reference: http://portaudit.FreeBSD.org/262b92fe-81c8-11e1-8899-001ec9578670.html
+
+Affected package: freetype2-2.4.7
+Type of problem: freetype -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/462e2d6c-8017-11e1-a571-bcaec565249c.html
+
+Affected package: mutt-devel-1.5.21_3
+Type of problem: mutt-devel -- failure to check SMTP TLS server certificate.
+Reference: http://portaudit.FreeBSD.org/49314321-7fd4-11e1-9582-001b2134ef46.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/057130e6-7f61-11e1-8a43-00262d5ed8ee.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/b8f0a391-7910-11e1-8a43-00262d5ed8ee.html
+
+Affected package: raptor2-2.0.4_1
+Type of problem: raptor/raptor2 -- XXE in RDF/XML File Interpretation.
+Reference: http://portaudit.FreeBSD.org/60f81af3-7690-11e1-9423-00235a5f2c9a.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/330106da-7406-11e1-a1d7-00262d5ed8ee.html
+
+Affected package: libtasn1-2.11
+Type of problem: libtasn1 -- ASN.1 length decoding vulnerability.
+Reference: http://portaudit.FreeBSD.org/2e7e9072-73a0-11e1-a883-001cc0a36e12.html
+
+Affected package: gnutls-2.12.14
+Type of problem: libtasn1 -- ASN.1 length decoding vulnerability.
+Reference: http://portaudit.FreeBSD.org/2e7e9072-73a0-11e1-a883-001cc0a36e12.html
+
+Affected package: gnutls-2.12.14
+Type of problem: gnutls -- possible overflow/Denial of service vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/aecee357-739e-11e1-a883-001cc0a36e12.html
+
+Affected package: firefox-8.0,1
+Type of problem: mozilla -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/a1050b8b-6db3-11e1-8b37-0011856a6e37.html
+
+Affected package: portaudit-0.5.17
+Type of problem: portaudit -- auditfile remote code execution.
+Reference: http://portaudit.FreeBSD.org/6d329b64-6bbb-11e1-9166-001e4f0fb9b1.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- Errant plug-in load and GPU process memory corruption.
+Reference: http://portaudit.FreeBSD.org/ab1f515d-6b69-11e1-8288-00262d5ed8ee.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- cross-site scripting vulnerability.
+Reference: http://portaudit.FreeBSD.org/1015e1fe-69ce-11e1-8288-00262d5ed8ee.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/99aef698-66ed-11e1-8288-00262d5ed8ee.html
+
+Affected package: postgresql-client-8.4.10
+Type of problem: databases/postgresql*-client -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/174b8864-6237-11e1-be18-14dae938ec40.html
+
+Affected package: libxml2-2.7.8_1
+Type of problem: libxml2 -- heap buffer overflow.
+Reference: http://portaudit.FreeBSD.org/57f1a624-6197-11e1-b98c-bcaec565249c.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/2f5ff968-5829-11e1-8288-00262d5ed8ee.html
+
+Affected package: python24-2.4.5_8
+Type of problem: Python -- DoS via malformed XML-RPC / HTTP POST request.
+Reference: http://portaudit.FreeBSD.org/b4f8be9e-56b2-11e1-9fb7-003067b2972c.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/fe1976c2-5317-11e1-9e99-00262d5ed8ee.html
+
+Affected package: firefox-8.0,1
+Type of problem: mozilla -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/0a9e2b72-4cb7-11e1-9146-14dae9ebcf89.html
+
+Affected package: sudo-1.8.3_1
+Type of problem: sudo -- format string vulnerability.
+Reference: http://portaudit.FreeBSD.org/7c920bb7-4b5f-11e1-9f47-00e0815b8da8.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/33d73d59-4677-11e1-88cd-00262d5ed8ee.html
+
+Affected package: wireshark-1.6.2
+Type of problem: Wireshark -- Multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/3ebb2dc8-4609-11e1-9f47-00e0815b8da8.html
+
+Affected package: ffmpeg-0.7.8,1
+Type of problem: ffmpeg -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/ea2ddc49-3e8e-11e1-8095-5404a67eef98.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/1a1aef8e-3894-11e1-8b5c-00262d5ed8ee.html
+
+Affected package: firefox-8.0,1
+Type of problem: mozilla -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/e3ff776b-2ba6-11e1-93c6-0011856a6e37.html
+
+Affected package: chromium-15.0.874.121
+Type of problem: chromium -- multiple vulnerabilities.
+Reference: http://portaudit.FreeBSD.org/68ac6266-25c3-11e1-b63a-00262d5ed8ee.html
+
+Affected package: libXfont-1.4.4,1
+Type of problem: libXfont -- possible local privilege escalation.
+Reference: http://portaudit.FreeBSD.org/304409c3-c3ef-11e0-8aa5-485d60cb5385.html
+
+32 problem(s) in your installed packages found.
+
+You are advised to update or deinstall the affected package(s) immediately.
+
+-- End of security output --
