indentation 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
38def main ():
39
40 txt = sys.stdin.read()
41
42 msg = email.parser.Parser().parsestr(txt)
43
44 print("From: " + msg['From'])
45 print("Subject: " + msg['Subject'])
46 print()
47
48 msg_walk(msg)
49
50
51def msg_walk (msg):
52
53 for part in msg.walk():
8bb4b0d5 54
0bec96d6
AE
55 if part.get_content_type() == 'multipart':
56 continue
0bec96d6 57
8bb4b0d5
AE
58 filename = part.get_filename()
59 conttype = part.get_content_type()
60 descrip_p = part.get_params(header='content-description')
0bec96d6 61
8bb4b0d5
AE
62 charset = part.get_content_charset()
63 payload_b = part.get_payload(decode=True)
0bec96d6 64
8bb4b0d5
AE
65 if payload_b == None:
66 continue
0bec96d6 67
8bb4b0d5
AE
68 if descrip_p == None:
69 decript = ""
70 else:
71 descript = descrip_p[0][0]
0bec96d6 72
8bb4b0d5
AE
73 if charset == None:
74 charset = 'utf-8'
0bec96d6 75
8bb4b0d5 76 payload = payload_b.decode(charset)
0bec96d6 77
8bb4b0d5
AE
78 if conttype == "application/pgp-encrypted":
79 if descript == 'PGP/MIME version identification':
80 if payload.strip() != "Version: 1":
81 print("*** Warning... unknown pgp/mime version: " \
82 + payload.strip())
83 continue
84
85 if (filename == "encrypted.asc") or (conttype == "pgp/mime"):
86 dec_msg = decrypt_payload(payload)
87 print_decrypted(dec_msg)
0bec96d6 88
8bb4b0d5
AE
89 elif conttype == "text/plain":
90 print(payload)
0bec96d6 91
8bb4b0d5
AE
92 else:
93 print(payload)
0bec96d6
AE
94
95
96def print_decrypted (message):
97
98 if message == None:
99 return
100
101 for chunk in message:
102 msg = email.parser.Parser().parsestr(chunk[0])
103 sigs = chunk[1]
104
105 msg_walk(msg)
106 for sig in sigs:
107 print_sig(sig)
108
109def print_sig (sig):
110
8bb4b0d5
AE
111 fprint = sig.fpr
112 fprint_short = re.search("[0-9A-Fa-f]{32}([0-9A-Fa-f]{8})", fprint).groups()[0]
9eb78301 113
8bb4b0d5
AE
114 timestamp = time.localtime(sig.timestamp)
115 date = time.strftime("%a %d %b %Y %I:%M:%S %p %Z", timestamp)
0bec96d6 116
8bb4b0d5
AE
117 g = gpgme.Context()
118 key = g.get_key(fprint)
119
120 # right now i'm just choosing the first user id, even if that id isn't
121 # signed by the user yet another is. if a user id is printed, it should
122 # at least be one that is signed, and/or correspond to the From:
123 # field's email address and full name.
124
125 name = key.uids[0].name
126 e_addr = key.uids[0].email
127 comment = key.uids[0].comment
128
129 # this section needs some work. signature summary, validity, status,
130 # and wrong_key_usage all complicate the picture. their enum/#define
131 # values overlap, which makes things more complicated.
132
133 validity = sig.validity
134 if validity == gpgme.VALIDITY_ULTIMATE \
135 or validity == gpgme.VALIDITY_FULL:
136 status = "Good Signature "
137 elif validity == gpgme.VALIDITY_MARGINAL:
138 status = "Marginal Signature "
139 else:
140 status = "BAD Signature "
16da8026 141
9eb78301 142
8bb4b0d5
AE
143 print("Signature Made " + date + " using key ID " + fprint_short)
144 print(status + "from " + name + " (" + comment + ") <" \
145 + e_addr + ">")
0bec96d6
AE
146
147
148def decrypt_payload (payload):
149
150 blocks = split_message(payload)
151 message = decrypt_blocks(blocks)
152
153 return message
154
155
156def split_message (text):
157
158 pgp_matches = re.search( \
159 '(-----BEGIN PGP MESSAGE-----' \
160 '.*' \
161 '-----END PGP MESSAGE-----)', \
162 text, \
163 re.DOTALL)
164
165 if pgp_matches == None:
166 return None
167 else:
168 return pgp_matches.groups()
169
170
171def decrypt_blocks (blocks):
172
173 if blocks == None:
174 return None
175
176 message = []
177 for block in blocks:
178 plain, sigs = decrypt_block(block)
179
180 message = message + [(plain, sigs)]
181
182 return message
183
184
185def decrypt_block (block):
186
187 block_b = io.BytesIO(block.encode('ASCII'))
188 plain_b = io.BytesIO()
189
190 g = gpgme.Context()
191 sigs = g.decrypt_verify(block_b, plain_b)
192
193 plain = plain_b.getvalue().decode('ASCII')
194 return (plain, sigs)
195
196
197main()
198