Skip to content

Instantly share code, notes, and snippets.

@ageis
Created December 20, 2016 22:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ageis/df12dd7c57bd3f414625173e416218f9 to your computer and use it in GitHub Desktop.
Save ageis/df12dd7c57bd3f414625173e416218f9 to your computer and use it in GitHub Desktop.
Mass-decrypt PGP messages in Thunderbird folders for CLI-based email searchability
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import subprocess
import argparse
import re
import mailbox
import email.utils
import os
import os.path
import configparser
from os.path import expanduser
from pick import pick
homedir = expanduser("~")
def find_mailboxes():
config = configparser.ConfigParser()
config.read(homedir + '/.thunderbird/profiles.ini')
tbird_profile = config['Profile0']['Path']
tbird_imap_path = homedir + '/.thunderbird/' + tbird_profile + '/ImapMail/'
imap_servers = next(os.walk(tbird_imap_path))[1]
mailboxes = {}
for x in imap_servers:
mailboxes[x] = []
imap_folder = tbird_imap_path + x
for f in os.listdir(imap_folder):
file_path = tbird_imap_path + x + '/' + f
if not os.path.isfile(file_path):
continue
if not os.path.splitext(file_path)[1]:
mailboxes[x].append(file_path)
return mailboxes
def select_folder(mailboxes):
prompt = 'Please select an email account: '
accounts = list(mailboxes.keys())
account, index = pick(accounts, prompt)
prompt = 'Please select which folder to search: '
folders = mailboxes[account]
folder, index = pick(folders, prompt)
mbox = mailbox.mbox(folder)
return folder
def parse_cmd_args():
description = """
Allows one to extract, decrypt and search through OpenPGP messages scattered among regular unencrypted e-mails.
Decrypted messages are simply printed to standard output so you can use GNU coreutils, etc. to locate information.
Examples:
# Prompt for IMAP folder selection and decrypt all armored PGP message blocks in a user's local Thunderbird profile
python3 pgpgrep.py thunderbird
"""
parser = argparse.ArgumentParser(description=description,
formatter_class=argparse.RawTextHelpFormatter)
subparsers = parser.add_subparsers(help='Subcommands', dest='subcommand')
thunderbird_parser = subparsers.add_parser('thunderbird', help='Extract OpenPGP armored message blocks from a Thunderbird profile')
return parser.parse_args()
def extract(data):
pgp_msgs = re.findall('-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----', data, flags=re.DOTALL)
pgp_signed_msgs = re.findall('-----BEGIN PGP MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----', data, flags=re.DOTALL)
return list(filter(None,pgp_msgs + pgp_signed_msgs))
def decrypt(ciphertext):
gpg = subprocess.Popen(['gpg2', '--decrypt', '--quiet'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
gpg.stdin.write(ciphertext.encode('utf-8'))
plaintext = gpg.communicate()[0]
gpg.stdin.close()
return plaintext.decode('utf-8')
def main():
args = parse_cmd_args()
if args.subcommand == 'thunderbird':
mailboxes = find_mailboxes()
pgp_msgs = []
folder = select_folder(mailboxes)
if not (os.access(folder, os.R_OK)):
sys.stderr.write('Please confirm that ' + os.path.abspath(folder) + ' exists and is readable.\n')
sys.exit(1)
mbox = mailbox.mbox(folder)
print('Reading messages from: ' + folder)
for message in mbox:
try:
body = message.as_string()
except:
continue
pgp_content = extract(body)
if pgp_content:
pgp_msgs.append('\n'.join(pgp_content))
print('PGP messages found: ' + str(len(pgp_msgs)))
for pgp_msg in pgp_msgs:
plaintext = decrypt(pgp_msg)
print(plaintext)
sys.exit(0)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment