summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2015-11-24 11:12:49 -0800
committerJesse Morgan <jesse@jesterpm.net>2015-11-24 11:12:49 -0800
commit6259efbc44be3e21e42b29246941cd300e79200f (patch)
tree67532f37aeadc45e959fffd1d05ab45ecd72e114
parent51129fda2179286143921db55a69b3f4dba03c0e (diff)
Adding emailcanary script
Adding the main launch script. Adding the listAddress to the pings table. Fixing the Makefile
-rw-r--r--Makefile3
-rwxr-xr-xbin/emailcanary105
-rw-r--r--emailcanary/canary.py18
-rw-r--r--emailcanary/canarydb.py30
-rw-r--r--emailcanary/emailutils.py2
-rw-r--r--tests/test_canary.py6
-rw-r--r--tests/test_canarydb.py26
7 files changed, 155 insertions, 35 deletions
diff --git a/Makefile b/Makefile
index d2c655e..cae60cc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
PYTHON=`which python`
NAME=`python setup.py --name`
-all: check test source deb
+all: check test source
init:
pip install -r requirements.txt --use-mirrors
@@ -19,7 +19,6 @@ rpm:
test:
unit2 discover -s tests -t .
- python -mpytest weasyprint
check:
find . -name \*.py | grep -v "^test_" | xargs pylint --errors-only --reports=n
diff --git a/bin/emailcanary b/bin/emailcanary
new file mode 100755
index 0000000..2c3a630
--- /dev/null
+++ b/bin/emailcanary
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+import smtplib
+from emailcanary import canarydb
+from emailcanary import canary
+
+SUCCESS=0
+FAILURE=1
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-d', '--database', default='/etc/emailcanary.db', help='Specify the database to use.')
+ parser.add_argument('-s', '--smtp', default='localhost:25', help='SMTP Server to send chirps to.')
+ parser.add_argument('-f', '--from', dest='fromaddress', help='Specify the email address to send the ping from.')
+
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument('--chirp', metavar='listAddress', help='Send an email to the given canary list address.')
+ group.add_argument('--check', metavar='listAddress', help='Check the recepient addresses for the list address.')
+ group.add_argument('--add', nargs=4, metavar=('listAddress', 'address', 'imapserver', 'password'), help='Add a new recepient and list.')
+ group.add_argument('--remove', nargs='+', metavar=('listAddress', 'address'), help='Remove recepients from the list.')
+ group.add_argument('--list', action='store_true', help='List all configured addresses.')
+
+ args = parser.parse_args()
+ return args
+
+def get_smtp(address):
+ index = address.find(':')
+ if index == -1:
+ server = address
+ port = 465
+ else:
+ (server, port) = address.split(':')
+ return smtplib.SMTP_SSL(server, port)
+
+def list(db):
+ accounts = db.get_accounts()
+ if len(accounts) == 0:
+ print('No accounts configured.')
+ return SUCCESS
+ print "%-25s %-25s %-25s" % ('List Address', 'Recepient', 'IMAP Server')
+ print "-" * 80
+ for account in accounts:
+ print "%-25s %-25s %-25s" % (account[0], account[1], account[2])
+ return SUCCESS
+
+def add(db, listAddress, recepient, imapserver, password):
+ db.add_account(listAddress, recepient, imapserver, password)
+ return SUCCESS
+
+def remove(db, listAddress, recepients):
+ if len(recepients) == 0:
+ recepients = db.get_accounts(listAddress)
+ for address in recepients:
+ db.remove_account(listAddress, address)
+ return SUCCESS
+
+def check(db, birdie, listAddress):
+ missing = birdie.check(listAddress)
+ if len(missing) == 0:
+ return SUCCESS
+ print "list recepient uuid time"
+ for chirp in missing:
+ print "%s %s %s %d" % (chirp[0], chirp[1], chirp[2], chirp[3].total_seconds())
+ return FAILURE
+
+def main():
+ args = parse_args()
+ if not args:
+ return
+
+ smtp = None
+ db = None
+ try:
+ db = canarydb.CanaryDB(args.database)
+
+ if args.list:
+ return list(db)
+ elif args.add:
+ return add(db, args.add[0], args.add[1], args.add[2], args.add[3])
+ elif args.remove:
+ return remove(db, args.remove[0], args.remove[1:])
+ else:
+ smtp = get_smtp(args.smtp)
+ birdie = canary.Canary(db, smtp, args.fromaddress)
+ if args.chirp:
+ birdie.chirp(args.chirp)
+ return SUCCESS
+ elif args.check:
+ return check(db, birdie, args.check)
+ else:
+ raise Exception('Unknown action')
+
+ except Exception, e:
+ sys.stderr.write("Error: %s\n" % (str(e)))
+ return FAILURE
+ finally:
+ if smtp:
+ smtp.quit()
+ if db:
+ db.close()
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/emailcanary/canary.py b/emailcanary/canary.py
index 49972db..e020349 100644
--- a/emailcanary/canary.py
+++ b/emailcanary/canary.py
@@ -1,5 +1,5 @@
import uuid, datetime, time
-import email, emailutils
+import email.message, emailutils
import re
class Canary:
@@ -12,12 +12,20 @@ class Canary:
chirpUUID = str(uuid.uuid4())
now = datetime.datetime.now()
+ receipients = self.db.get_recipients_for_list(listAddress)
+ if len(receipients) == 0:
+ raise Exception("No receipients for listAddress '%s'", (listAddress,))
+
self.send(listAddress, now, chirpUUID)
- for dest in self.db.get_recipients_for_list(listAddress):
- self.db.ping(dest, now, chirpUUID)
+ for dest in receipients:
+ self.db.ping(listAddress, dest, now, chirpUUID)
def check(self, listAddress):
- for (listAddress, address, imapserver, password) in self.db.get_accounts(listAddress):
+ '''Check for messages from listAddress and return a list of missing chirps'''
+ accounts = self.db.get_accounts(listAddress)
+ if len(accounts) == 0:
+ raise Exception("No receipients for listAddress '%s'", (listAddress,))
+ for (listAddress, address, imapserver, password) in accounts:
mail = emailutils.get_imap(imapserver, address, password)
these_subjects = []
for uid in emailutils.get_mail_uids(mail):
@@ -25,6 +33,7 @@ class Canary:
if self.processMessage(address, message):
emailutils.delete_message(mail, uid)
emailutils.close(mail)
+ return self.db.get_missing_pongs(listAddress)
def processMessage(self, receipient, msg):
match = re.match('Canary Email (.+)', msg['Subject'])
@@ -35,7 +44,6 @@ class Canary:
return True
return False
-
def send(self, dest, date, chirpUUID):
msg = email.message.Message()
msg['From'] = self.fromaddress
diff --git a/emailcanary/canarydb.py b/emailcanary/canarydb.py
index 59a2ea2..980431b 100644
--- a/emailcanary/canarydb.py
+++ b/emailcanary/canarydb.py
@@ -7,7 +7,7 @@ class CanaryDB:
# Create Tables if necessary
cursor = self.conn.cursor()
- cursor.execute("CREATE TABLE IF NOT EXISTS pings (address, uuid, timesent timestamp, timereceived timestamp)")
+ cursor.execute("CREATE TABLE IF NOT EXISTS pings (listAddress, address, uuid, timesent timestamp, timereceived timestamp)")
cursor.execute("CREATE TABLE IF NOT EXISTS accounts (list, address, imapserver, password)")
def close(self):
@@ -19,9 +19,9 @@ class CanaryDB:
(listAddress, address, imapserver, password))
self.conn.commit()
- def remove_account(self, address):
+ def remove_account(self, listAddress, address):
cursor = self.conn.cursor()
- cursor.execute("DELETE FROM accounts WHERE address=?", (address,))
+ cursor.execute("DELETE FROM accounts WHERE list=? AND address=?", (listAddress, address))
self.conn.commit()
def get_accounts(self, listAddress = None):
@@ -47,10 +47,10 @@ class CanaryDB:
results.append(row[0])
return results
- def ping(self, address, time, uuid):
+ def ping(self, listAddress, address, time, uuid):
cursor = self.conn.cursor()
- cursor.execute("INSERT INTO pings (address, timesent, uuid) VALUES (?, ?, ?)", \
- (address, time, uuid))
+ cursor.execute("INSERT INTO pings (listAddress, address, timesent, uuid) VALUES (?, ?, ?, ?)", \
+ (listAddress, address, time, uuid))
self.conn.commit()
def pong(self, address, time, uuid):
@@ -59,16 +59,20 @@ class CanaryDB:
(time, address, uuid))
self.conn.commit()
- def get_missing_pongs(self):
+ def get_missing_pongs(self, listAddress = None):
'''Return a list of tupls of missing pongs.
- Each tupl contains (uuid, address, time since send)'''
+ Each tupl contains (listAddress, uuid, address, time since send)'''
cursor = self.conn.cursor()
- cursor.execute("SELECT uuid, address, timesent FROM pings WHERE timereceived IS NULL");
+ if listAddress:
+ cursor.execute("SELECT listAddress, uuid, address, timesent FROM pings WHERE timereceived IS NULL AND listAddress = ?", (listAddress,));
+ else:
+ cursor.execute("SELECT listAddress, 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))
+ listAddress = row[0]
+ uuid = row[1]
+ address = row[2]
+ delta = now - row[3]
+ results.append((listAddress, uuid, address, delta))
return results
diff --git a/emailcanary/emailutils.py b/emailcanary/emailutils.py
index b87f637..83c2f46 100644
--- a/emailcanary/emailutils.py
+++ b/emailcanary/emailutils.py
@@ -19,7 +19,7 @@ def get_message(mail, uid):
return email.message_from_string(raw_email)
def delete_message(mail, uid):
- result = mail.uid('store', uid, '+FLAGS', '\\Deleted')
+ result = mail.uid('store', uid, '+FLAGS', '(\Deleted)')
def close(mail):
mail.expunge()
diff --git a/tests/test_canary.py b/tests/test_canary.py
index ff104f6..d23f3fe 100644
--- a/tests/test_canary.py
+++ b/tests/test_canary.py
@@ -40,10 +40,10 @@ class TestCanary(unittest.TestCase):
# Assert DB updated
self.db.get_recipients_for_list.assert_called_with(LIST_ADDRESS)
self.db.ping.assert_has_calls( \
- [mock.call(USER_ADDRESS1, mock.ANY, mock.ANY), \
- mock.call(USER_ADDRESS2, mock.ANY, mock.ANY)])
+ [mock.call(LIST_ADDRESS, USER_ADDRESS1, mock.ANY, mock.ANY), \
+ mock.call(LIST_ADDRESS, USER_ADDRESS2, mock.ANY, mock.ANY)])
args = self.db.ping.call_args
- expectedSubject = "Canary Email " + args[0][2]
+ expectedSubject = "Canary Email " + args[0][3]
# Assert emails were sent
self.assertEqual(1, self.smtp.sendmail.call_count)
diff --git a/tests/test_canarydb.py b/tests/test_canarydb.py
index 8a29d23..a51afdc 100644
--- a/tests/test_canarydb.py
+++ b/tests/test_canarydb.py
@@ -14,23 +14,25 @@ class TestCanaryDB(unittest.TestCase):
shutil.rmtree(self.tempdir)
def testPingCheckPong(self):
+ listAddress = "list@example.com"
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)
+ self.db.ping(listAddress, 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.assertEqual(4, len(firstMissing))
+ self.assertEqual(listAddress, firstMissing[0])
+ self.assertEqual(uuid, firstMissing[1])
+ self.assertEqual(address, firstMissing[2])
+ delta = firstMissing[3].total_seconds() - expectedDelta.total_seconds()
self.assertTrue(delta <= 10)
# Record a pong
@@ -42,13 +44,14 @@ class TestCanaryDB(unittest.TestCase):
self.assertEqual(0, len(missing))
def testCloseReopen(self):
+ listAddress = "list@example.com"
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)
+ self.db.ping(listAddress, address, time, uuid)
# Close, Reopen
self.db.close()
@@ -59,10 +62,11 @@ class TestCanaryDB(unittest.TestCase):
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.assertEqual(4, len(firstMissing))
+ self.assertEqual(listAddress, firstMissing[0])
+ self.assertEqual(uuid, firstMissing[1])
+ self.assertEqual(address, firstMissing[2])
+ delta = firstMissing[3].total_seconds() - expectedDelta.total_seconds()
self.assertTrue(delta <= 10)
def testAccounts(self):
@@ -87,7 +91,7 @@ class TestCanaryDB(unittest.TestCase):
self.assertEqual(password, accounts[0][3])
# Remove the account
- self.db.remove_account(address)
+ self.db.remove_account(listAddress, address)
accounts = self.db.get_accounts()
self.assertEqual(0, len(accounts))