diff options
-rw-r--r-- | Makefile | 35 | ||||
-rw-r--r-- | emailcanary/__init__.py | 0 | ||||
-rw-r--r-- | emailcanary/canary.py | 33 | ||||
-rw-r--r-- | emailcanary/canarydb.py | 41 | ||||
-rw-r--r-- | emailcanary/email-digest-sender.py | 38 | ||||
-rw-r--r-- | emailcanary/emailutils.py | 19 | ||||
-rw-r--r-- | requirements.txt | 3 | ||||
-rw-r--r-- | setup.py | 22 | ||||
-rw-r--r-- | tests/__init__.py | 0 | ||||
-rw-r--r-- | tests/test_canarydb.py | 66 |
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) |