import io
import time
+
def main ():
handle_args()
- txt = sys.stdin.read()
- msg = email.parser.Parser().parsestr(txt)
+ email_text = sys.stdin.read()
+ message, keys = decode_simplify_email(email_text)
- print("From: " + msg['From'])
- print("Subject: " + msg['Subject'])
- print()
+ print(message)
- msg_walk(msg)
+ for key in keys:
+ print(key.subkeys[0].fpr)
-def msg_walk (msg):
+def decode_simplify_email (email_text):
- for part in msg.walk():
+ body, keys = email_struct_decode_flatten(email_text)
+ email_from, email_subject = get_from_subject(email_text)
- if part.get_content_type() == 'multipart':
- continue
+ message = ""
+ message += "From: " + email_from + "\n"
+ message += "Subject: " + email_subject + "\n\n"
+ message += body
- charset = part.get_content_charset()
- payload_b = part.get_payload(decode=True)
+ return message, keys
- filename = part.get_filename()
- conttype = part.get_content_type()
- descrip_p = part.get_params(header='content-description')
- if charset == None:
- charset = 'utf-8'
+def email_struct_decode_flatten (email_text):
- if payload_b == None:
- continue
- else:
- payload = payload_b.decode(charset)
+ body = ""
+ keys = []
+ email_struct = email.parser.Parser().parsestr(email_text)
- if descrip_p == None:
- decript = ""
- else:
- descript = descrip_p[0][0]
+ for subpart in email_struct.walk():
+
+ payload, description, filename, content_type \
+ = get_email_subpart_info(subpart)
+ if payload == "":
+ continue
+
+ if content_type == "multipart":
+ continue
- if conttype == "application/pgp-encrypted":
- if descript == 'PGP/MIME version identification':
+ if content_type == "application/pgp-encrypted":
+ if description == "PGP/MIME version identification":
if payload.strip() != "Version: 1":
- print(progname + ": Warning: unknown " + descript + ": " \
+ print(progname + ": Warning: unknown " \
+ + description + ": " \
+ payload.strip(), file=sys.stderr)
continue
- elif (filename == "encrypted.asc") or (conttype == "pgp/mime"):
- dec_msg = decrypt_payload(payload)
- print_decrypted(dec_msg)
- elif conttype == "text/plain":
- print(payload)
+ if (filename == "encrypted.asc") or (content_type == "pgp/mime"):
+ plaintext, more_keys = decrypt_text(payload)
+
+ body += plaintext
+ keys += more_keys
+
+ elif content_type == "text/plain":
+ body += payload + "\n"
else:
- print(payload)
+ body += payload + "\n"
+ return body, keys
-def print_decrypted (message):
- if message == None:
- return
+def get_from_subject (email_text):
- for chunk in message:
- msg = email.parser.Parser().parsestr(chunk[0])
- sigs = chunk[1]
+ email_struct = email.parser.Parser().parsestr(email_text)
- msg_walk(msg)
- for sig in sigs:
- print_sig(sig)
+ email_from = email_struct['From']
+ email_subject = email_struct['Subject']
-def print_sig (sig):
+ return email_from, email_subject
- fprint = sig.fpr
- fprint_short = re.search("[0-9A-Fa-f]{32}([0-9A-Fa-f]{8})", fprint).groups()[0]
- timestamp = time.localtime(sig.timestamp)
- date = time.strftime("%a %d %b %Y %I:%M:%S %p %Z", timestamp)
+def get_email_subpart_info (part):
- g = gpgme.Context()
- key = g.get_key(fprint)
+ charset = part.get_content_charset()
+ payload_bytes = part.get_payload(decode=True)
- # right now i'm just choosing the first user id, even if that id isn't
- # signed by the user yet another is. if a user id is printed, it should
- # at least be one that is signed, and/or correspond to the From:
- # field's email address and full name.
+ filename = part.get_filename()
+ content_type = part.get_content_type()
+ description_list = part.get_params(header='content-description')
- name = key.uids[0].name
- e_addr = key.uids[0].email
- comment = key.uids[0].comment
+ if charset == None:
+ charset = 'utf-8'
- # this section needs some work. signature summary, validity, status,
- # and wrong_key_usage all complicate the picture. their enum/#define
- # values overlap, which makes things more complicated.
+ if payload_bytes != None:
+ payload = payload_bytes.decode(charset)
+ else:
+ payload = ""
- validity = sig.validity
- if validity == gpgme.VALIDITY_ULTIMATE \
- or validity == gpgme.VALIDITY_FULL:
- status = "Good Signature "
- elif validity == gpgme.VALIDITY_MARGINAL:
- status = "Marginal Signature "
+ if description_list != None:
+ description = description_list[0][0]
else:
- status = "BAD Signature "
+ description = ""
+
+ return payload, description, filename, content_type
+
+
+def decrypt_text (gpg_text):
+
+ body = ""
+ keys = []
+
+ gpg_chunks = split_message(gpg_text)
+
+ plaintext_and_sigs_chunks = decrypt_chunks(gpg_chunks)
+
+ for chunk in plaintext_and_sigs_chunks:
+ plaintext = chunk[0]
+ sigs = chunk[1]
+
+ for sig in sigs:
+ key = get_pub_key(sig)
+ keys += [key]
+
+ # recursive for nested layers of mime and/or gpg
+ plaintext, more_keys = email_struct_decode_flatten(plaintext)
+
+ body += plaintext
+ keys += more_keys
+ return body, keys
- print("Signature Made " + date + " using key ID " + fprint_short)
- print(status + "from " + name + " (" + comment + ") <" \
- + e_addr + ">")
+def get_pub_key (sig):
-def decrypt_payload (payload):
+ gpgme_ctx = gpgme.Context()
- blocks = split_message(payload)
- message = decrypt_blocks(blocks)
+ finger_print = sig.fpr
+ key = gpgme_ctx.get_key(finger_print)
- return message
+ return key
def split_message (text):
pgp_matches = re.search( \
- '(-----BEGIN PGP MESSAGE-----' \
- '.*' \
+ '(-----BEGIN PGP MESSAGE-----' + \
+ '.*' + \
'-----END PGP MESSAGE-----)', \
text, \
re.DOTALL)
if pgp_matches == None:
- return None
+ return ()
else:
return pgp_matches.groups()
-def decrypt_blocks (blocks):
+def decrypt_chunks (gpg_chunks):
- if blocks == None:
- return None
+ plaintext_and_sigs_chunks = []
- message = []
- for block in blocks:
- plain, sigs = decrypt_block(block)
+ for gpg_chunk in gpg_chunks:
+ plaintext_and_sigs_chunks += [decrypt_chunk(gpg_chunk)]
- message = message + [(plain, sigs)]
+ return plaintext_and_sigs_chunks
- return message
+def decrypt_chunk (gpg_chunk):
-def decrypt_block (block):
+ gpgme_ctx = gpgme.Context()
- block_b = io.BytesIO(block.encode('ASCII'))
+ chunk_b = io.BytesIO(gpg_chunk.encode('ASCII'))
plain_b = io.BytesIO()
- g = gpgme.Context()
- sigs = g.decrypt_verify(block_b, plain_b)
+ sigs = gpgme_ctx.decrypt_verify(chunk_b, plain_b)
- plain = plain_b.getvalue().decode('ASCII')
- return (plain, sigs)
+ plaintext = plain_b.getvalue().decode('ASCII')
+ return (plaintext, sigs)
def handle_args ():