Created
December 20, 2016 22:37
-
-
Save ageis/df12dd7c57bd3f414625173e416218f9 to your computer and use it in GitHub Desktop.
Mass-decrypt PGP messages in Thunderbird folders for CLI-based email searchability
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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