summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2015-10-24 13:28:24 -0700
committerJesse Morgan <jesse@jesterpm.net>2015-10-24 13:28:24 -0700
commit95ee2046715024e7eedf663e09161933689a7aef (patch)
tree9a0bf8dfaa91ef97f24671a53d51d0829ce2a48f
Initial commit.
-rw-r--r--Makefile35
-rw-r--r--emailcanary/__init__.py0
-rw-r--r--emailcanary/canary.py33
-rw-r--r--emailcanary/canarydb.py41
-rw-r--r--emailcanary/email-digest-sender.py38
-rw-r--r--emailcanary/emailutils.py19
-rw-r--r--requirements.txt3
-rw-r--r--setup.py22
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_canarydb.py66
10 files changed, 257 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e365f62
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+PYTHON=`which python`
+NAME=`python setup.py --name`
+
+all: check test source deb
+
+init:
+ pip install -r requirements.txt --use-mirrors
+
+dist: source deb
+
+source:
+ $(PYTHON) setup.py sdist
+
+deb:
+ $(PYTHON) setup.py --command-packages=stdeb.command bdist_deb
+
+rpm:
+ $(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall
+
+test:
+ unit2 discover -s tests -t .
+ python -mpytest weasyprint
+
+check:
+ find . -name \*.py | grep -v "^test_" | xargs pylint --errors-only --reports=n
+ # pep8
+ # pyntch
+ # pyflakes
+ # pychecker
+ # pymetrics
+
+clean:
+ $(PYTHON) setup.py clean
+ rm -rf build/ MANIFEST dist build my_program.egg-info deb_dist
+ find . -name '*.pyc' -delete \ No newline at end of file
diff --git a/emailcanary/__init__.py b/emailcanary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emailcanary/__init__.py
diff --git a/emailcanary/canary.py b/emailcanary/canary.py
new file mode 100644
index 0000000..d07e2e9
--- /dev/null
+++ b/emailcanary/canary.py
@@ -0,0 +1,33 @@
+import uuid, datetime, time
+import email
+import re
+
+class Canary:
+ def __init__(self, db, smtp, fromaddress):
+ self.db = db
+ self.smtp = smtp
+ self.fromaddress = fromaddress
+
+ def chirp(self, list, expectedreceipients):
+ uuid = uuid.uuid4()
+ now = datetime.datetime.now()
+
+ self.send(list, now, uuid)
+ for dest in expectedreceipients:
+ self.db.ping(dest, now, uuid)
+
+ def echo(self, receipient, msg):
+ uuid = re.match('Canary Email (.+)', msg['Subject']).group(1)
+ now = datetime.datetime.now()
+
+ self.db.pong(receipient, now, uuid)
+
+
+ def send(self, dest, date, uuid):
+ msg = email.message.Message()
+ msg['From'] = self.fromaddress
+ msg['To'] = dest
+ msg['Subject'] = "Canary Email " + str(uuid)
+ msg['Date'] = email.utils.formatdate(time.mktime(date.timetuple()))
+
+ self.smtp.sendmail(self.fromaddress, dest, msg.as_string()) \ No newline at end of file
diff --git a/emailcanary/canarydb.py b/emailcanary/canarydb.py
new file mode 100644
index 0000000..112b8f5
--- /dev/null
+++ b/emailcanary/canarydb.py
@@ -0,0 +1,41 @@
+import sqlite3
+from datetime import datetime
+
+class CanaryDB:
+ def __init__(self, filename):
+ self.conn = sqlite3.connect(filename, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
+
+ # Create Tables if necessary
+ cursor = self.conn.cursor()
+ cursor.execute("CREATE TABLE IF NOT EXISTS pings (address, uuid, timesent timestamp, timereceived timestamp)")
+
+ def close(self):
+ self.conn.close()
+
+ def ping(self, address, time, uuid):
+ cursor = self.conn.cursor()
+ cursor.execute("INSERT INTO pings (address, timesent, uuid) VALUES (?, ?, ?)", \
+ (address, time, uuid))
+ self.conn.commit()
+
+ def pong(self, address, time, uuid):
+ cursor = self.conn.cursor()
+ cursor.execute("UPDATE pings SET timereceived=? WHERE address=? AND uuid=?", \
+ (time, address, uuid))
+ self.conn.commit()
+
+ def get_missing_pongs(self):
+ '''Return a list of tupls of missing pongs.
+ Each tupl contains (uuid, address, time since send)'''
+ cursor = self.conn.cursor()
+ cursor.execute("SELECT uuid, address, timesent FROM pings WHERE timereceived IS NULL");
+
+ now = datetime.now()
+ results = list()
+ for row in cursor:
+ uuid = row[0]
+ address = row[1]
+ delta = now - row[2]
+ results.append((uuid, address, delta))
+
+ return results
diff --git a/emailcanary/email-digest-sender.py b/emailcanary/email-digest-sender.py
new file mode 100644
index 0000000..f1583c0
--- /dev/null
+++ b/emailcanary/email-digest-sender.py
@@ -0,0 +1,38 @@
+import emailutils
+import smtplib
+from email import message
+
+PASSWORD="secret"
+ACCOUNTS = [('mail.example.com', 'email@example.com', PASSWORD)]
+DESTINATION="other@example.com"
+
+youve_got_mail = False
+all_subjects = {}
+
+for account in ACCOUNTS:
+ mail = emailutils.get_imap(account)
+ these_subjects = []
+ for uid in emailutils.get_mail_uids(mail):
+ message = emailutils.get_message(mail, uid)
+ these_subjects.append(message['subject'])
+ youve_got_mail = True
+ all_subjects[account[1]] = these_subjects
+
+
+if youve_got_mail:
+ msg = ""
+ for account in all_subjects:
+ msg = msg + "# Messages for %s\n" % account
+ for subject in all_subjects[account]:
+ msg = msg + " * %s\n" % subject
+ msg = msg + "\n"
+
+ digest_message = message.Message()
+ digest_message.set_payload(msg)
+ digest_message['From'] = DESTINATION
+ digest_message['To'] = DESTINATION
+ digest_message['Subject'] = "Email Digest"
+
+ s = smtplib.SMTP_SSL('localhost', 2465)
+ s.sendmail(DESTINATION, DESTINATION, digest_message.as_string())
+ s.quit() \ No newline at end of file
diff --git a/emailcanary/emailutils.py b/emailcanary/emailutils.py
new file mode 100644
index 0000000..6646c1e
--- /dev/null
+++ b/emailcanary/emailutils.py
@@ -0,0 +1,19 @@
+import imaplib, email
+
+def get_imap(account):
+ '''Connect and login via IMAP'''
+ mail = imaplib.IMAP4_SSL(account[0])
+ mail.login(account[1], account[2])
+ return mail
+
+def get_mail_uids(mail):
+ '''Return a list of message UIDs in the inbox'''
+ mail.select("inbox") # connect to inbox.
+ result, data = mail.uid('search', None, "ALL") # search and return uids instead
+ return data[0].split()
+
+def get_message(mail, uid):
+ '''Get a single email message object by UID'''
+ result, data = mail.uid('fetch', uid, '(RFC822)')
+ raw_email = data[0][1]
+ return email.message_from_string(raw_email) \ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e51ea6d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+setuptools
+pylint
+unittest2 \ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..bc53c06
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+from setuptools import setup, find_packages
+
+setup(name='emailcanary',
+ version='0.1',
+ author='Jesse Morgan',
+ author_email='jesse@jesterpm.net',
+ url='https://jesterpm.net',
+ download_url='http://www.my_program.org/files/',
+ description='Email Canary sends emails to a distribution list and checks for proper distribution.',
+
+ packages = find_packages(),
+ include_package_data = True,
+ exclude_package_data = { '': ['README.txt'] },
+
+ scripts = ['bin/emailcanary'],
+
+ license='MIT',
+
+ #setup_requires = ['python-stdeb', 'fakeroot', 'python-all'],
+ install_requires = ['setuptools'],
+ ) \ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_canarydb.py b/tests/test_canarydb.py
new file mode 100644
index 0000000..5a0fc23
--- /dev/null
+++ b/tests/test_canarydb.py
@@ -0,0 +1,66 @@
+import unittest
+import tempfile, shutil
+import datetime
+
+from emailcanary import canarydb
+
+class TestCanaryDB(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ self.db = canarydb.CanaryDB(self.tempdir + "canary.db")
+
+ def tearDown(self):
+ self.db.close()
+ shutil.rmtree(self.tempdir)
+
+ def testPingCheckPong(self):
+ address = "test@example.com"
+ time = datetime.datetime(2015, 10, 24, 9, 00)
+ uuid = "1234"
+ expectedDelta = datetime.datetime.now() - time
+
+ # Record a Ping
+ self.db.ping(address, time, uuid)
+
+ # Check for missing pongs
+ missing = self.db.get_missing_pongs()
+
+ self.assertEqual(1, len(missing))
+ firstMissing = missing[0]
+ self.assertEqual(3, len(firstMissing))
+ self.assertEqual(uuid, firstMissing[0])
+ self.assertEqual(address, firstMissing[1])
+ delta = firstMissing[2].total_seconds() - expectedDelta.total_seconds()
+ self.assertTrue(delta <= 10)
+
+ # Record a pong
+ pongtime = datetime.datetime(2015, 10, 24, 9, 05)
+ self.db.pong(address, pongtime, uuid)
+
+ # Check for missing pongs
+ missing = self.db.get_missing_pongs()
+ self.assertEqual(0, len(missing))
+
+ def testCloseReopen(self):
+ address = "test@example.com"
+ time = datetime.datetime(2015, 10, 24, 9, 00)
+ uuid = "1234"
+ expectedDelta = datetime.datetime.now() - time
+
+ # Record a Ping
+ self.db.ping(address, time, uuid)
+
+ # Close, Reopen
+ self.db.close()
+ self.db = canarydb.CanaryDB(self.tempdir + "canary.db")
+
+ # Check for missing pongs
+ missing = self.db.get_missing_pongs()
+
+ self.assertEqual(1, len(missing))
+ firstMissing = missing[0]
+ self.assertEqual(3, len(firstMissing))
+ self.assertEqual(uuid, firstMissing[0])
+ self.assertEqual(address, firstMissing[1])
+ delta = firstMissing[2].total_seconds() - expectedDelta.total_seconds()
+ self.assertTrue(delta <= 10)