created a function for choosing the encryption key
[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()
c96f3837 44
65ed3800
AE
45 plaintext, keys = email_decode_flatten (email_text)
46 email_from, email_subject = email_from_subject(email_text)
47
fafa21c3
AE
48 reply_encrypt_to_key = choose_reply_encryption_key(keys)
49
65ed3800
AE
50 print("From: " + email_from)
51 print("Subject: " + email_subject)
52 print(plaintext)
c96f3837 53
fafa21c3 54 print(reply_encrypt_to_key.subkeys[0].fpr)
c96f3837 55
0bec96d6 56
7b06b980 57def email_decode_flatten (email_text):
0bec96d6 58
86663388 59 body = ""
394a1476
AE
60 keys = []
61
62 email_struct = email.parser.Parser().parsestr(email_text)
63
64 for subpart in email_struct.walk():
8bb4b0d5 65
394a1476
AE
66 payload, description, filename, content_type \
67 = get_email_subpart_info(subpart)
0bec96d6 68
394a1476 69 if payload == "":
8bb4b0d5 70 continue
0bec96d6 71
394a1476 72 if content_type == "multipart":
d437f8b2 73 continue
0bec96d6 74
394a1476
AE
75 if content_type == "application/pgp-encrypted":
76 if description == "PGP/MIME version identification":
8bb4b0d5 77 if payload.strip() != "Version: 1":
394a1476
AE
78 print(progname + ": Warning: unknown " \
79 + description + ": " \
20f6e7c5 80 + payload.strip(), file=sys.stderr)
8bb4b0d5
AE
81 continue
82
d437f8b2 83
394a1476
AE
84 if (filename == "encrypted.asc") or (content_type == "pgp/mime"):
85 plaintext, more_keys = decrypt_text(payload)
0bec96d6 86
394a1476
AE
87 body += plaintext
88 keys += more_keys
89
90 elif content_type == "text/plain":
86663388 91 body += payload + "\n"
0bec96d6 92
8bb4b0d5 93 else:
86663388
AE
94 body += payload + "\n"
95
394a1476 96 return body, keys
86663388 97
c96f3837 98
65ed3800 99def email_from_subject (email_text):
d437f8b2 100
394a1476 101 email_struct = email.parser.Parser().parsestr(email_text)
d437f8b2 102
394a1476
AE
103 email_from = email_struct['From']
104 email_subject = email_struct['Subject']
d437f8b2 105
394a1476 106 return email_from, email_subject
d437f8b2 107
d437f8b2 108
394a1476 109def get_email_subpart_info (part):
d437f8b2 110
394a1476
AE
111 charset = part.get_content_charset()
112 payload_bytes = part.get_payload(decode=True)
d437f8b2 113
394a1476
AE
114 filename = part.get_filename()
115 content_type = part.get_content_type()
116 description_list = part.get_params(header='content-description')
d437f8b2 117
394a1476
AE
118 if charset == None:
119 charset = 'utf-8'
0bec96d6 120
394a1476
AE
121 if payload_bytes != None:
122 payload = payload_bytes.decode(charset)
123 else:
124 payload = ""
0bec96d6 125
394a1476
AE
126 if description_list != None:
127 description = description_list[0][0]
128 else:
129 description = ""
0bec96d6 130
394a1476 131 return payload, description, filename, content_type
0bec96d6 132
0bec96d6 133
394a1476 134def decrypt_text (gpg_text):
86663388 135
394a1476
AE
136 body = ""
137 keys = []
0bec96d6 138
394a1476 139 gpg_chunks = split_message(gpg_text)
86663388 140
394a1476 141 plaintext_and_sigs_chunks = decrypt_chunks(gpg_chunks)
0bec96d6 142
394a1476
AE
143 for chunk in plaintext_and_sigs_chunks:
144 plaintext = chunk[0]
145 sigs = chunk[1]
9eb78301 146
394a1476
AE
147 for sig in sigs:
148 key = get_pub_key(sig)
149 keys += [key]
0bec96d6 150
394a1476 151 # recursive for nested layers of mime and/or gpg
7b06b980 152 plaintext, more_keys = email_decode_flatten(plaintext)
8bb4b0d5 153
394a1476
AE
154 body += plaintext
155 keys += more_keys
8bb4b0d5 156
394a1476 157 return body, keys
8bb4b0d5 158
8bb4b0d5 159
394a1476 160def get_pub_key (sig):
16da8026 161
394a1476 162 gpgme_ctx = gpgme.Context()
9eb78301 163
7b06b980
AE
164 fingerprint = sig.fpr
165 key = gpgme_ctx.get_key(fingerprint)
0bec96d6 166
394a1476 167 return key
0bec96d6
AE
168
169
170def split_message (text):
171
7b06b980 172 gpg_matches = re.search( \
394a1476
AE
173 '(-----BEGIN PGP MESSAGE-----' + \
174 '.*' + \
0bec96d6
AE
175 '-----END PGP MESSAGE-----)', \
176 text, \
177 re.DOTALL)
178
7b06b980
AE
179 if gpg_matches != None:
180 gpg_chunks = gpg_matches.groups()
0bec96d6 181 else:
7b06b980
AE
182 gpg_chunks = ()
183
184 return gpg_chunks
0bec96d6
AE
185
186
394a1476 187def decrypt_chunks (gpg_chunks):
0bec96d6 188
394a1476 189 plaintext_and_sigs_chunks = []
0bec96d6 190
394a1476
AE
191 for gpg_chunk in gpg_chunks:
192 plaintext_and_sigs_chunks += [decrypt_chunk(gpg_chunk)]
0bec96d6 193
394a1476 194 return plaintext_and_sigs_chunks
0bec96d6 195
0bec96d6 196
394a1476 197def decrypt_chunk (gpg_chunk):
0bec96d6 198
394a1476 199 gpgme_ctx = gpgme.Context()
0bec96d6 200
394a1476 201 chunk_b = io.BytesIO(gpg_chunk.encode('ASCII'))
0bec96d6
AE
202 plain_b = io.BytesIO()
203
394a1476 204 sigs = gpgme_ctx.decrypt_verify(chunk_b, plain_b)
0bec96d6 205
394a1476
AE
206 plaintext = plain_b.getvalue().decode('ASCII')
207 return (plaintext, sigs)
0bec96d6
AE
208
209
fafa21c3
AE
210def choose_reply_encryption_key (keys):
211
212 reply_key = None
213 for key in keys:
214 if (key.can_encrypt == True):
215 reply_key = key
216 break
217
218 return key
219
220
20f6e7c5
AE
221def handle_args ():
222 if __name__ == "__main__":
223
224 global progname
225 progname = sys.argv[0]
226
227 if len(sys.argv) > 1:
228 print(progname + ": error, this program doesn't " \
229 "need any arguments.", file=sys.stderr)
230 exit(1)
231
232
0bec96d6
AE
233main()
234