1 #! /usr/bin/env python3
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. *
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. *
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/>. *
17 * Copyright (C) 2014-2015 Andrew Engelbrecht (AGPLv3+) *
18 * Copyright (C) 2014 Josh Drake (AGPLv3+) *
19 * Copyright (C) 2014 Lisa Marie Maginnis (AGPLv3+) *
21 * Special thanks to Josh Drake for writing the original edward bot! :) *
23 ************************************************************************
27 * http://agpl.fsf.org/emailselfdefense.fsf.org/edward/CURRENT/edward.tar.gz
42 txt
= sys
.stdin
.read()
43 msg
= email
.parser
.Parser().parsestr(txt
)
46 message
+= "From: " + msg
['From'] + "\n"
47 message
+= "Subject: " + msg
['Subject'] + "\n\n"
49 message
+= msg_walk(msg
)
57 for part
in msg
.walk():
59 payload
, descript
, filename
, conttype
= get_part_info(part
)
64 if conttype
== 'multipart':
67 if conttype
== "application/pgp-encrypted":
68 if descript
== 'PGP/MIME version identification':
69 if payload
.strip() != "Version: 1":
70 print(progname
+ ": Warning: unknown " + descript
+ ": " \
71 + payload
.strip(), file=sys
.stderr
)
75 if (filename
== "encrypted.asc") or (conttype
== "pgp/mime"):
76 payload_dec
= decrypt_payload(payload
)
79 elif conttype
== "text/plain":
80 body
+= payload
+ "\n"
83 body
+= payload
+ "\n"
87 def get_part_info (part
):
89 charset
= part
.get_content_charset()
90 payload_b
= part
.get_payload(decode
=True)
92 filename
= part
.get_filename()
93 conttype
= part
.get_content_type()
94 descrip_p
= part
.get_params(header
='content-description')
102 payload
= payload_b
.decode(charset
)
104 if descrip_p
== None:
107 descript
= descrip_p
[0][0]
110 return payload
, descript
, filename
, conttype
113 def decrypt_payload (payload
):
115 blocks
= split_message(payload
)
116 decrypted_tree
= decrypt_blocks(blocks
)
118 if decrypted_tree
== None:
122 for node
in decrypted_tree
:
123 msg
= email
.parser
.Parser().parsestr(node
[0])
126 body
+= msg_walk(msg
)
128 body
+= format_sig(sig
)
133 def format_sig (sig
):
136 fprint_short
= re
.search("[0-9A-Fa-f]{32}([0-9A-Fa-f]{8})", fprint
).groups()[0]
138 timestamp
= time
.localtime(sig
.timestamp
)
139 date
= time
.strftime("%a %d %b %Y %I:%M:%S %p %Z", timestamp
)
142 key
= g
.get_key(fprint
)
144 # right now i'm just choosing the first user id, even if that id isn't
145 # signed by the user yet another is. if a user id is printed, it should
146 # at least be one that is signed, and/or correspond to the From:
147 # field's email address and full name.
149 name
= key
.uids
[0].name
150 e_addr
= key
.uids
[0].email
151 comment
= key
.uids
[0].comment
153 # this section needs some work. signature summary, validity, status,
154 # and wrong_key_usage all complicate the picture. their enum/#define
155 # values overlap, which makes things more complicated.
157 validity
= sig
.validity
158 if validity
== gpgme
.VALIDITY_ULTIMATE \
159 or validity
== gpgme
.VALIDITY_FULL
:
160 status
= "MAYBE-Good Signature "
161 elif validity
== gpgme
.VALIDITY_MARGINAL
:
162 status
= "MAYBE-Marginal Signature "
164 status
= "MAYBE-BAD Signature "
167 sig_str
= "Signature Made " + date
+ " using key ID " + fprint_short
+ "\n"
168 sig_str
+= status
+ "from " + name
+ " (" + comment
+ ") <" + e_addr
+ ">"
173 def split_message (text
):
175 pgp_matches
= re
.search( \
176 '(-----BEGIN PGP MESSAGE-----' \
178 '-----END PGP MESSAGE-----)', \
182 if pgp_matches
== None:
185 return pgp_matches
.groups()
188 def decrypt_blocks (blocks
):
195 plain
, sigs
= decrypt_block(block
)
197 message
= message
+ [(plain
, sigs
)]
202 def decrypt_block (block
):
204 block_b
= io
.BytesIO(block
.encode('ASCII'))
205 plain_b
= io
.BytesIO()
208 sigs
= g
.decrypt_verify(block_b
, plain_b
)
210 plain
= plain_b
.getvalue().decode('ASCII')
215 if __name__
== "__main__":
218 progname
= sys
.argv
[0]
220 if len(sys
.argv
) > 1:
221 print(progname
+ ": error, this program doesn't " \
222 "need any arguments.", file=sys
.stderr
)