more name changes; other minor changes
[edward.git] / edward-bot
CommitLineData
0bec96d6
AE
1#! /usr/bin/env python3
2
3"""*********************************************************************
4* Edward is free software: you can redistribute it and/or modify *
5* it under the terms of the GNU Affero Public License as published by *
6* the Free Software Foundation, either version 3 of the License, or *
7* (at your option) any later version. *
8* *
9* Edward is distributed in the hope that it will be useful, *
10* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12* GNU Affero Public License for more details. *
13* *
14* You should have received a copy of the GNU Affero Public License *
15* along with Edward. If not, see <http://www.gnu.org/licenses/>. *
16* *
17* Copyright (C) 2014-2015 Andrew Engelbrecht (AGPLv3+) *
18* Copyright (C) 2014 Josh Drake (AGPLv3+) *
19* Copyright (C) 2014 Lisa Marie Maginnis (AGPLv3+) *
20* *
21* Special thanks to Josh Drake for writing the original edward bot! :) *
22* *
23************************************************************************
24
25Code used from:
26
27* http://agpl.fsf.org/emailselfdefense.fsf.org/edward/CURRENT/edward.tar.gz
28
29"""
30
31import sys
32import email.parser
33import gpgme
34import re
35import io
36import time
37
c96f3837 38
0bec96d6
AE
39def main ():
40
20f6e7c5 41 handle_args()
0bec96d6 42
c96f3837 43 email_text = sys.stdin.read()
394a1476 44 message, keys = decode_simplify_email(email_text)
c96f3837 45
394a1476 46 print(message)
c96f3837 47
394a1476
AE
48 for key in keys:
49 print(key.subkeys[0].fpr)
c96f3837 50
0bec96d6 51
394a1476
AE
52def decode_simplify_email (email_text):
53
7b06b980
AE
54 plaintext, keys = email_decode_flatten(email_text)
55
394a1476 56 email_from, email_subject = get_from_subject(email_text)
0bec96d6 57
7b06b980
AE
58 simplified_msg = "From: " + email_from + "\n"
59 simplified_msg += "Subject: " + email_subject + "\n\n"
60 simplified_msg += plaintext
86663388 61
7b06b980 62 return simplified_msg, keys
0bec96d6
AE
63
64
7b06b980 65def email_decode_flatten (email_text):
0bec96d6 66
86663388 67 body = ""
394a1476
AE
68 keys = []
69
70 email_struct = email.parser.Parser().parsestr(email_text)
71
72 for subpart in email_struct.walk():
8bb4b0d5 73
394a1476
AE
74 payload, description, filename, content_type \
75 = get_email_subpart_info(subpart)
0bec96d6 76
394a1476 77 if payload == "":
8bb4b0d5 78 continue
0bec96d6 79
394a1476 80 if content_type == "multipart":
d437f8b2 81 continue
0bec96d6 82
394a1476
AE
83 if content_type == "application/pgp-encrypted":
84 if description == "PGP/MIME version identification":
8bb4b0d5 85 if payload.strip() != "Version: 1":
394a1476
AE
86 print(progname + ": Warning: unknown " \
87 + description + ": " \
20f6e7c5 88 + payload.strip(), file=sys.stderr)
8bb4b0d5
AE
89 continue
90
d437f8b2 91
394a1476
AE
92 if (filename == "encrypted.asc") or (content_type == "pgp/mime"):
93 plaintext, more_keys = decrypt_text(payload)
0bec96d6 94
394a1476
AE
95 body += plaintext
96 keys += more_keys
97
98 elif content_type == "text/plain":
86663388 99 body += payload + "\n"
0bec96d6 100
8bb4b0d5 101 else:
86663388
AE
102 body += payload + "\n"
103
394a1476 104 return body, keys
86663388 105
c96f3837 106
394a1476 107def get_from_subject (email_text):
d437f8b2 108
394a1476 109 email_struct = email.parser.Parser().parsestr(email_text)
d437f8b2 110
394a1476
AE
111 email_from = email_struct['From']
112 email_subject = email_struct['Subject']
d437f8b2 113
394a1476 114 return email_from, email_subject
d437f8b2 115
d437f8b2 116
394a1476 117def get_email_subpart_info (part):
d437f8b2 118
394a1476
AE
119 charset = part.get_content_charset()
120 payload_bytes = part.get_payload(decode=True)
d437f8b2 121
394a1476
AE
122 filename = part.get_filename()
123 content_type = part.get_content_type()
124 description_list = part.get_params(header='content-description')
d437f8b2 125
394a1476
AE
126 if charset == None:
127 charset = 'utf-8'
0bec96d6 128
394a1476
AE
129 if payload_bytes != None:
130 payload = payload_bytes.decode(charset)
131 else:
132 payload = ""
0bec96d6 133
394a1476
AE
134 if description_list != None:
135 description = description_list[0][0]
136 else:
137 description = ""
0bec96d6 138
394a1476 139 return payload, description, filename, content_type
0bec96d6 140
0bec96d6 141
394a1476 142def decrypt_text (gpg_text):
86663388 143
394a1476
AE
144 body = ""
145 keys = []
0bec96d6 146
394a1476 147 gpg_chunks = split_message(gpg_text)
86663388 148
394a1476 149 plaintext_and_sigs_chunks = decrypt_chunks(gpg_chunks)
0bec96d6 150
394a1476
AE
151 for chunk in plaintext_and_sigs_chunks:
152 plaintext = chunk[0]
153 sigs = chunk[1]
9eb78301 154
394a1476
AE
155 for sig in sigs:
156 key = get_pub_key(sig)
157 keys += [key]
0bec96d6 158
394a1476 159 # recursive for nested layers of mime and/or gpg
7b06b980 160 plaintext, more_keys = email_decode_flatten(plaintext)
8bb4b0d5 161
394a1476
AE
162 body += plaintext
163 keys += more_keys
8bb4b0d5 164
394a1476 165 return body, keys
8bb4b0d5 166
8bb4b0d5 167
394a1476 168def get_pub_key (sig):
16da8026 169
394a1476 170 gpgme_ctx = gpgme.Context()
9eb78301 171
7b06b980
AE
172 fingerprint = sig.fpr
173 key = gpgme_ctx.get_key(fingerprint)
0bec96d6 174
394a1476 175 return key
0bec96d6
AE
176
177
178def split_message (text):
179
7b06b980 180 gpg_matches = re.search( \
394a1476
AE
181 '(-----BEGIN PGP MESSAGE-----' + \
182 '.*' + \
0bec96d6
AE
183 '-----END PGP MESSAGE-----)', \
184 text, \
185 re.DOTALL)
186
7b06b980
AE
187 if gpg_matches != None:
188 gpg_chunks = gpg_matches.groups()
0bec96d6 189 else:
7b06b980
AE
190 gpg_chunks = ()
191
192 return gpg_chunks
0bec96d6
AE
193
194
394a1476 195def decrypt_chunks (gpg_chunks):
0bec96d6 196
394a1476 197 plaintext_and_sigs_chunks = []
0bec96d6 198
394a1476
AE
199 for gpg_chunk in gpg_chunks:
200 plaintext_and_sigs_chunks += [decrypt_chunk(gpg_chunk)]
0bec96d6 201
394a1476 202 return plaintext_and_sigs_chunks
0bec96d6 203
0bec96d6 204
394a1476 205def decrypt_chunk (gpg_chunk):
0bec96d6 206
394a1476 207 gpgme_ctx = gpgme.Context()
0bec96d6 208
394a1476 209 chunk_b = io.BytesIO(gpg_chunk.encode('ASCII'))
0bec96d6
AE
210 plain_b = io.BytesIO()
211
394a1476 212 sigs = gpgme_ctx.decrypt_verify(chunk_b, plain_b)
0bec96d6 213
394a1476
AE
214 plaintext = plain_b.getvalue().decode('ASCII')
215 return (plaintext, sigs)
0bec96d6
AE
216
217
20f6e7c5
AE
218def handle_args ():
219 if __name__ == "__main__":
220
221 global progname
222 progname = sys.argv[0]
223
224 if len(sys.argv) > 1:
225 print(progname + ": error, this program doesn't " \
226 "need any arguments.", file=sys.stderr)
227 exit(1)
228
229
0bec96d6
AE
230main()
231