don't use the 'an' language
[edward.git] / edward
CommitLineData
0bec96d6 1#! /usr/bin/env python3
ff4136c7 2# -*- coding: utf-8 -*-
0bec96d6
AE
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+) *
8bdfb6d4
AE
20* Copyright (C) 2009-2015 Tails developers <tails@boum.org> ( GPLv3+) *
21* Copyright (C) 2009 W. Trevor King <wking@drexel.edu> ( GPLv2+) *
0bec96d6
AE
22* *
23* Special thanks to Josh Drake for writing the original edward bot! :) *
24* *
25************************************************************************
26
a5385c04 27Code sourced from these projects:
0bec96d6 28
8bdfb6d4
AE
29 * http://agpl.fsf.org/emailselfdefense.fsf.org/edward/CURRENT/edward.tar.gz
30 * https://git-tails.immerda.ch/whisperback/tree/whisperBack/encryption.py?h=feature/python3
31 * http://www.physics.drexel.edu/~wking/code/python/send_pgp_mime
0bec96d6
AE
32"""
33
34import sys
0bec96d6
AE
35import gpgme
36import re
37import io
40c37ab3 38import os
adcef2f7 39import importlib
0bec96d6 40
8bdfb6d4
AE
41import email.parser
42import email.message
43import email.encoders
44
45from email.mime.multipart import MIMEMultipart
46from email.mime.application import MIMEApplication
47from email.mime.nonmultipart import MIMENonMultipart
48
40c37ab3 49import edward_config
c96f3837 50
5b0e37d3 51langs = ["de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"]
adcef2f7 52
38738401
AE
53match_types = [('clearsign',
54 '-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----'),
55 ('message',
56578eaf
AE
56 '-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----'),
57 ('pubkey',
58 '-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----'),
59 ('detachedsig',
38738401 60 '-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----')]
56578eaf
AE
61
62
63class EddyMsg (object):
64 def __init__(self):
65 self.multipart = False
66 self.subparts = []
67
68 self.charset = None
69 self.payload_bytes = None
70 self.payload_pieces = []
71
72 self.filename = None
73 self.content_type = None
74 self.description_list = None
75
76
77class PayloadPiece (object):
78 def __init__(self):
79 self.piece_type = None
80 self.string = None
81 self.gpg_data = None
82
83
38738401
AE
84class GPGData (object):
85 def __init__(self):
86 self.decrypted = False
87
88 self.plainobj = None
89 self.sigs = []
90 self.keys = []
91
d873ff48
AE
92class ReplyInfo (object):
93 def __init__(self):
94 self.replies = None
6906d46d
AE
95
96 self.target_key = None
97 self.fallback_target_key = None
d873ff48
AE
98 self.msg_to_quote = ""
99
100 self.success_decrypt = False
101 self.failed_decrypt = False
102 self.public_key_received = False
103 self.no_public_key = False
104 self.sig_success = False
105 self.sig_failure = False
106
38738401 107
0bec96d6
AE
108def main ():
109
20f6e7c5 110 handle_args()
0bec96d6 111
0a064403
AE
112 gpgme_ctx = get_gpg_context(edward_config.gnupghome,
113 edward_config.sign_with_key)
114
c96f3837 115 email_text = sys.stdin.read()
adcef2f7
AE
116 email_struct = parse_pgp_mime(email_text, gpgme_ctx)
117
118 email_to, email_from, email_subject = email_to_from_subject(email_text)
119 lang = import_lang(email_to)
fafa21c3 120
d873ff48
AE
121 replyinfo_obj = ReplyInfo()
122 replyinfo_obj.replies = lang.replies
123
124 prepare_for_reply(email_struct, replyinfo_obj)
013f7cb8 125 encrypt_to_key = get_key_from_fp(replyinfo_obj, gpgme_ctx)
d873ff48 126 reply_plaintext = write_reply(replyinfo_obj)
adcef2f7 127
2007103e
AE
128 reply_mime = generate_encrypted_mime(reply_plaintext, email_from, \
129 email_subject, encrypt_to_key,
130 gpgme_ctx)
1fccb295 131
2007103e 132 print(reply_mime)
c96f3837 133
0bec96d6 134
0a064403
AE
135def get_gpg_context (gnupghome, sign_with_key_fp):
136
137 os.environ['GNUPGHOME'] = gnupghome
138
139 gpgme_ctx = gpgme.Context()
140 gpgme_ctx.armor = True
141
142 try:
143 sign_with_key = gpgme_ctx.get_key(sign_with_key_fp)
144 except:
145 error("unable to load signing key. is the gnupghome "
146 + "and signing key properly set in the edward_config.py?")
147 exit(1)
148
149 gpgme_ctx.signers = [sign_with_key]
150
151 return gpgme_ctx
152
153
38738401 154def parse_pgp_mime (email_text, gpgme_ctx):
394a1476
AE
155
156 email_struct = email.parser.Parser().parsestr(email_text)
157
928e3819
AE
158 eddymsg_obj = parse_mime(email_struct)
159 split_payloads(eddymsg_obj)
160 gpg_on_payloads(eddymsg_obj, gpgme_ctx)
8bb4b0d5 161
928e3819 162 return eddymsg_obj
0bec96d6 163
0bec96d6 164
56578eaf 165def parse_mime(msg_struct):
0bec96d6 166
928e3819 167 eddymsg_obj = EddyMsg()
8bb4b0d5 168
56578eaf
AE
169 if msg_struct.is_multipart() == True:
170 payloads = msg_struct.get_payload()
0bec96d6 171
928e3819
AE
172 eddymsg_obj.multipart = True
173 eddymsg_obj.subparts = list(map(parse_mime, payloads))
dd11a483 174
56578eaf 175 else:
928e3819 176 eddymsg_obj = get_subpart_data(msg_struct)
394a1476 177
928e3819 178 return eddymsg_obj
c267c233 179
80119cab 180
56578eaf 181def scan_and_split (payload_piece, match_type, pattern):
cf75de65 182
a5d37d44
AE
183 # don't try to re-split pieces containing gpg data
184 if payload_piece.piece_type != "text":
185 return [payload_piece]
186
56578eaf
AE
187 flags = re.DOTALL | re.MULTILINE
188 matches = re.search("(?P<beginning>.*?)(?P<match>" + pattern +
189 ")(?P<rest>.*)", payload_piece.string, flags=flags)
86663388 190
56578eaf
AE
191 if matches == None:
192 pieces = [payload_piece]
c96f3837 193
56578eaf 194 else:
d437f8b2 195
56578eaf
AE
196 beginning = PayloadPiece()
197 beginning.string = matches.group('beginning')
198 beginning.piece_type = payload_piece.piece_type
d437f8b2 199
56578eaf
AE
200 match = PayloadPiece()
201 match.string = matches.group('match')
202 match.piece_type = match_type
d437f8b2 203
56578eaf
AE
204 rest = PayloadPiece()
205 rest.string = matches.group('rest')
206 rest.piece_type = payload_piece.piece_type
d437f8b2 207
56578eaf 208 more_pieces = scan_and_split(rest, match_type, pattern)
4615b156 209 pieces = [beginning, match ] + more_pieces
d437f8b2 210
56578eaf 211 return pieces
d437f8b2 212
d437f8b2 213
56578eaf 214def get_subpart_data (part):
0bec96d6 215
56578eaf 216 obj = EddyMsg()
0bec96d6 217
56578eaf
AE
218 obj.charset = part.get_content_charset()
219 obj.payload_bytes = part.get_payload(decode=True)
220
221 obj.filename = part.get_filename()
222 obj.content_type = part.get_content_type()
223 obj.description_list = part['content-description']
224
225 # your guess is as good as a-myy-ee-ine...
226 if obj.charset == None:
227 obj.charset = 'utf-8'
228
229 if obj.payload_bytes != None:
0eb75d9c
AE
230 try:
231 payload = PayloadPiece()
232 payload.string = obj.payload_bytes.decode(obj.charset)
233 payload.piece_type = 'text'
234
235 obj.payload_pieces = [payload]
236 except UnicodeDecodeError:
237 pass
56578eaf
AE
238
239 return obj
240
241
928e3819 242def do_to_eddys_pieces (function_to_do, eddymsg_obj, data):
56578eaf 243
928e3819
AE
244 if eddymsg_obj.multipart == True:
245 for sub in eddymsg_obj.subparts:
d873ff48 246 do_to_eddys_pieces(function_to_do, sub, data)
394a1476 247 else:
928e3819 248 function_to_do(eddymsg_obj, data)
dd11a483
AE
249
250
928e3819 251def split_payloads (eddymsg_obj):
a5d37d44
AE
252
253 for match_type in match_types:
928e3819 254 do_to_eddys_pieces(split_payload_pieces, eddymsg_obj, match_type)
a5d37d44 255
a5d37d44 256
928e3819 257def split_payload_pieces (eddymsg_obj, match_type):
a5d37d44
AE
258
259 (match_name, pattern) = match_type
260
261 new_pieces_list = []
928e3819 262 for piece in eddymsg_obj.payload_pieces:
a5d37d44
AE
263 new_pieces_list += scan_and_split(piece, match_name, pattern)
264
928e3819 265 eddymsg_obj.payload_pieces = new_pieces_list
a5d37d44
AE
266
267
928e3819 268def gpg_on_payloads (eddymsg_obj, gpgme_ctx, prev_parts=[]):
38738401 269
928e3819 270 if eddymsg_obj.multipart == True:
101d54a8 271 prev_parts=[]
928e3819 272 for sub in eddymsg_obj.subparts:
101d54a8
AE
273 gpg_on_payloads (sub, gpgme_ctx, prev_parts)
274 prev_parts += [sub]
38738401 275
d873ff48 276 return
38738401 277
928e3819 278 for piece in eddymsg_obj.payload_pieces:
38738401
AE
279
280 if piece.piece_type == "text":
281 # don't transform the plaintext.
282 pass
283
284 elif piece.piece_type == "message":
7e18f14d 285 (plaintext, sigs) = decrypt_block(piece.string, gpgme_ctx)
38738401
AE
286
287 if plaintext:
288 piece.gpg_data = GPGData()
b23e171d 289 piece.gpg_data.decrypted = True
38738401
AE
290 piece.gpg_data.sigs = sigs
291 # recurse!
292 piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx)
3a753fd0
AE
293 continue
294
295 # if not encrypted, check to see if this is an armored signature.
296 (plaintext, sigs) = verify_sig_message(piece.string, gpgme_ctx)
297
298 if plaintext:
299 piece.piece_type = "signature"
300 piece.gpg_data = GPGData()
301 piece.gpg_data.sigs = sigs
302 # recurse!
303 piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx)
129543c3 304
8f61c66a 305 elif piece.piece_type == "pubkey":
f8ee6bd3 306 key_fps = add_gpg_key(piece.string, gpgme_ctx)
8f61c66a 307
f8ee6bd3 308 if key_fps != []:
8f61c66a 309 piece.gpg_data = GPGData()
f8ee6bd3 310 piece.gpg_data.keys = key_fps
129543c3
AE
311
312 elif piece.piece_type == "clearsign":
f8ee6bd3 313 (plaintext, sig_fps) = verify_clear_signature(piece.string, gpgme_ctx)
129543c3 314
f8ee6bd3 315 if sig_fps != []:
129543c3 316 piece.gpg_data = GPGData()
f8ee6bd3 317 piece.gpg_data.sigs = sig_fps
129543c3
AE
318 piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx)
319
101d54a8
AE
320 elif piece.piece_type == "detachedsig":
321 for prev in prev_parts:
322 payload_bytes = prev.payload_bytes
6d240f68 323 sig_fps = verify_detached_signature(piece.string, payload_bytes, gpgme_ctx)
101d54a8 324
6d240f68
AE
325 if sig_fps != []:
326 piece.gpg_data = GPGData()
327 piece.gpg_data.sigs = sig_fps
328 piece.gpg_data.plainobj = prev
329 break
7e18f14d 330
38738401
AE
331 else:
332 pass
333
334
928e3819 335def prepare_for_reply (eddymsg_obj, replyinfo_obj):
dd11a483 336
928e3819 337 do_to_eddys_pieces(prepare_for_reply_pieces, eddymsg_obj, replyinfo_obj)
56578eaf 338
928e3819 339def prepare_for_reply_pieces (eddymsg_obj, replyinfo_obj):
56578eaf 340
928e3819 341 for piece in eddymsg_obj.payload_pieces:
38738401 342 if piece.piece_type == "text":
d873ff48
AE
343 # don't quote the plaintext part.
344 pass
345
38738401 346 elif piece.piece_type == "message":
05683768 347 prepare_for_reply_message(piece, replyinfo_obj)
d873ff48 348
05683768
AE
349 elif piece.piece_type == "pubkey":
350 prepare_for_reply_pubkey(piece, replyinfo_obj)
6906d46d 351
05683768 352 elif (piece.piece_type == "clearsign") \
3a753fd0
AE
353 or (piece.piece_type == "detachedsig") \
354 or (piece.piece_type == "signature"):
05683768 355 prepare_for_reply_sig(piece, replyinfo_obj)
d873ff48 356
d873ff48 357
05683768 358def prepare_for_reply_message (piece, replyinfo_obj):
44fe39b7 359
05683768
AE
360 if piece.gpg_data == None:
361 replyinfo_obj.failed_decrypt = True
362 return
363
5c40e676 364 replyinfo_obj.success_decrypt = True
05683768
AE
365
366 # we already have a key (and a message)
367 if replyinfo_obj.target_key != None:
368 return
369
370 if piece.gpg_data.sigs != []:
371 replyinfo_obj.target_key = piece.gpg_data.sigs[0]
372 get_signed_part = False
373 else:
374 # only include a signed message in the reply.
375 get_signed_part = True
376
377 replyinfo_obj.msg_to_quote = flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part)
378
379 # to catch public keys in encrypted blocks
380 prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
381
382
383def prepare_for_reply_pubkey (piece, replyinfo_obj):
384
385 if piece.gpg_data == None or piece.gpg_data.keys == []:
386 replyinfo_obj.no_public_key = True
387 else:
388 replyinfo_obj.public_key_received = True
389
390 if replyinfo_obj.fallback_target_key == None:
391 replyinfo_obj.fallback_target_key = piece.gpg_data.keys[0]
392
393
394def prepare_for_reply_sig (piece, replyinfo_obj):
395
396 if piece.gpg_data == None or piece.gpg_data.sigs == []:
397 replyinfo_obj.sig_failure = True
398 else:
399 replyinfo_obj.sig_success = True
400
401 if replyinfo_obj.fallback_target_key == None:
402 replyinfo_obj.fallback_target_key = piece.gpg_data.sigs[0]
6906d46d
AE
403
404
d873ff48 405
0402995a 406def flatten_decrypted_payloads (eddymsg_obj, get_signed_part):
d873ff48
AE
407
408 flat_string = ""
409
0aa88c27
AE
410 if eddymsg_obj == None:
411 return ""
412
0402995a 413 # recurse on multi-part mime
928e3819
AE
414 if eddymsg_obj.multipart == True:
415 for sub in eddymsg_obj.subparts:
0d553ce9 416 flat_string += flatten_decrypted_payloads (sub, get_signed_part)
d873ff48
AE
417
418 return flat_string
419
928e3819 420 for piece in eddymsg_obj.payload_pieces:
d873ff48
AE
421 if piece.piece_type == "text":
422 flat_string += piece.string
0aa88c27 423
0402995a
AE
424 if (get_signed_part):
425 # don't include nested encryption
0d553ce9 426 if (piece.piece_type == "message") \
0402995a
AE
427 and (piece.gpg_data != None) \
428 and (piece.gpg_data.decrypted == False):
429 flat_string += flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part)
430
431 elif ((piece.piece_type == "clearsign") \
3a753fd0
AE
432 or (piece.piece_type == "detachedsig") \
433 or (piece.piece_type == "signature")) \
0402995a
AE
434 and (piece.gpg_data != None):
435 # FIXME: the key used to sign this message needs to be the one that is used for the encrypted reply.
436 flat_string += flatten_decrypted_payloads (piece.gpg_data.plainobj, get_signed_part)
d873ff48
AE
437
438 return flat_string
439
440
013f7cb8
AE
441def get_key_from_fp (replyinfo_obj, gpgme_ctx):
442
dc24daf1
AE
443 if replyinfo_obj.target_key == None:
444 replyinfo_obj.target_key = replyinfo_obj.fallback_target_key
445
013f7cb8
AE
446 if replyinfo_obj.target_key != None:
447 try:
448 encrypt_to_key = gpgme_ctx.get_key(replyinfo_obj.target_key)
449 return encrypt_to_key
450
451 except:
452 pass
453
454 # no available key to use
455 replyinfo_obj.target_key = None
456 replyinfo_obj.fallback_target_key = None
457
458 replyinfo_obj.no_public_key = True
459 replyinfo_obj.public_key_received = False
460
461 return None
462
463
d873ff48
AE
464def write_reply (replyinfo_obj):
465
466 reply_plain = ""
467
468 if replyinfo_obj.success_decrypt == True:
d873ff48 469 reply_plain += replyinfo_obj.replies['success_decrypt']
2694709a
AE
470
471 if replyinfo_obj.no_public_key == False:
472 quoted_text = email_quote_text(replyinfo_obj.msg_to_quote)
473 reply_plain += quoted_text
d873ff48
AE
474
475 elif replyinfo_obj.failed_decrypt == True:
476 reply_plain += replyinfo_obj.replies['failed_decrypt']
477
478
479 if replyinfo_obj.sig_success == True:
480 reply_plain += "\n\n"
481 reply_plain += replyinfo_obj.replies['sig_success']
482
483 elif replyinfo_obj.sig_failure == True:
484 reply_plain += "\n\n"
485 reply_plain += replyinfo_obj.replies['sig_failure']
486
487
488 if replyinfo_obj.public_key_received == True:
489 reply_plain += "\n\n"
490 reply_plain += replyinfo_obj.replies['public_key_received']
491
492 elif replyinfo_obj.no_public_key == True:
493 reply_plain += "\n\n"
494 reply_plain += replyinfo_obj.replies['no_public_key']
495
496
497 reply_plain += "\n\n"
498 reply_plain += replyinfo_obj.replies['signature']
56578eaf 499
d873ff48 500 return reply_plain
56578eaf
AE
501
502
8f61c66a 503def add_gpg_key (key_block, gpgme_ctx):
c267c233 504
8f61c66a 505 fp = io.BytesIO(key_block.encode('ascii'))
c267c233 506
8f61c66a
AE
507 result = gpgme_ctx.import_(fp)
508 imports = result.imports
c267c233 509
f8ee6bd3 510 key_fingerprints = []
c267c233 511
8f61c66a
AE
512 if imports != []:
513 for import_ in imports:
514 fingerprint = import_[0]
f8ee6bd3 515 key_fingerprints += [fingerprint]
c267c233 516
e49673aa 517 debug("added gpg key: " + fingerprint)
ec1e779a 518
f8ee6bd3 519 return key_fingerprints
ec1e779a
AE
520
521
3a753fd0
AE
522def verify_sig_message (msg_block, gpgme_ctx):
523
524 block_b = io.BytesIO(msg_block.encode('ascii'))
525 plain_b = io.BytesIO()
526
527 try:
528 sigs = gpgme_ctx.verify(block_b, None, plain_b)
529 except:
530 return ("",[])
531
532 plaintext = plain_b.getvalue().decode('utf-8')
533
534 fingerprints = []
535 for sig in sigs:
536 fingerprints += [sig.fpr]
537 return (plaintext, fingerprints)
538
539
129543c3 540def verify_clear_signature (sig_block, gpgme_ctx):
cf75de65 541
129543c3
AE
542 # FIXME: this might require the un-decoded bytes
543 # or the correct re-encoding with the carset of the mime part.
544 msg_fp = io.BytesIO(sig_block.encode('utf-8'))
545 ptxt_fp = io.BytesIO()
cf75de65 546
129543c3 547 result = gpgme_ctx.verify(msg_fp, None, ptxt_fp)
cf75de65 548
129543c3
AE
549 # FIXME: this might require using the charset of the mime part.
550 plaintext = ptxt_fp.getvalue().decode('utf-8')
cf75de65 551
f8ee6bd3 552 sig_fingerprints = []
129543c3 553 for res_ in result:
f8ee6bd3 554 sig_fingerprints += [res_.fpr]
cf75de65 555
f8ee6bd3 556 return plaintext, sig_fingerprints
cf75de65
AE
557
558
101d54a8
AE
559def verify_detached_signature (detached_sig, plaintext_bytes, gpgme_ctx):
560
561 detached_sig_fp = io.BytesIO(detached_sig.encode('ascii'))
562 plaintext_fp = io.BytesIO(plaintext_bytes)
563 ptxt_fp = io.BytesIO()
564
565 result = gpgme_ctx.verify(detached_sig_fp, plaintext_fp, None)
566
567 sig_fingerprints = []
568 for res_ in result:
569 sig_fingerprints += [res_.fpr]
570
571 return sig_fingerprints
572
573
5b3053c1 574def decrypt_block (msg_block, gpgme_ctx):
0bec96d6 575
5b3053c1 576 block_b = io.BytesIO(msg_block.encode('ascii'))
0bec96d6
AE
577 plain_b = io.BytesIO()
578
afc1f64c
AE
579 try:
580 sigs = gpgme_ctx.decrypt_verify(block_b, plain_b)
581 except:
582 return ("",[])
0bec96d6 583
6aa41372 584 plaintext = plain_b.getvalue().decode('utf-8')
cbdf22c1
AE
585
586 fingerprints = []
587 for sig in sigs:
588 fingerprints += [sig.fpr]
589 return (plaintext, fingerprints)
0bec96d6
AE
590
591
d0489345 592def choose_reply_encryption_key (gpgme_ctx, fingerprints):
fafa21c3
AE
593
594 reply_key = None
d0489345
AE
595 for fp in fingerprints:
596 try:
597 key = gpgme_ctx.get_key(fp)
598
599 if (key.can_encrypt == True):
600 reply_key = key
601 break
602 except:
603 continue
604
fafa21c3 605
216708e9 606 return reply_key
fafa21c3
AE
607
608
d65993b8
AE
609def email_to_from_subject (email_text):
610
611 email_struct = email.parser.Parser().parsestr(email_text)
612
613 email_to = email_struct['To']
614 email_from = email_struct['From']
615 email_subject = email_struct['Subject']
616
617 return email_to, email_from, email_subject
618
619
adcef2f7
AE
620def import_lang(email_to):
621
5250b3b8
AE
622 if email_to != None:
623 for lang in langs:
624 if "edward-" + lang in email_to:
625 lang = "lang." + re.sub('-', '_', lang)
626 language = importlib.import_module(lang)
adcef2f7 627
5250b3b8 628 return language
adcef2f7
AE
629
630 return importlib.import_module("lang.en")
631
632
bf79a93e 633def generate_encrypted_mime (plaintext, email_from, email_subject, encrypt_to_key,
0a064403 634 gpgme_ctx):
1da9b527 635
2007103e
AE
636 # quoted printable encoding lets most ascii characters look normal
637 # before the decrypted mime message is decoded.
638 char_set = email.charset.Charset("utf-8")
639 char_set.body_encoding = email.charset.QP
8bdfb6d4 640
2007103e
AE
641 # MIMEText doesn't allow setting the text encoding
642 # so we use MIMENonMultipart.
643 plaintext_mime = MIMENonMultipart('text', 'plain')
644 plaintext_mime.set_payload(plaintext, charset=char_set)
216708e9
AE
645
646 if (encrypt_to_key != None):
8bdfb6d4
AE
647
648 encrypted_text = encrypt_sign_message(plaintext_mime.as_string(),
649 encrypt_to_key,
40c37ab3 650 gpgme_ctx)
8bdfb6d4
AE
651
652 control_mime = MIMEApplication("Version: 1",
653 _subtype='pgp-encrypted',
654 _encoder=email.encoders.encode_7or8bit)
655 control_mime['Content-Description'] = 'PGP/MIME version identification'
656 control_mime.set_charset('us-ascii')
657
658 encoded_mime = MIMEApplication(encrypted_text,
659 _subtype='octet-stream; name="encrypted.asc"',
660 _encoder=email.encoders.encode_7or8bit)
661 encoded_mime['Content-Description'] = 'OpenPGP encrypted message'
662 encoded_mime['Content-Disposition'] = 'inline; filename="encrypted.asc"'
663 encoded_mime.set_charset('us-ascii')
664
665 message_mime = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted")
666 message_mime.attach(control_mime)
667 message_mime.attach(encoded_mime)
668 message_mime['Content-Disposition'] = 'inline'
216708e9 669
216708e9 670 else:
2007103e
AE
671 message_mime = plaintext_mime
672
673 message_mime['To'] = email_from
674 message_mime['Subject'] = email_subject
675
676 reply = message_mime.as_string()
1da9b527
AE
677
678 return reply
679
680
f87041f8
AE
681def email_quote_text (text):
682
683 quoted_message = re.sub(r'^', r'> ', text, flags=re.MULTILINE)
684
685 return quoted_message
686
687
0a064403 688def encrypt_sign_message (plaintext, encrypt_to_key, gpgme_ctx):
897cbaf6 689
6aa41372 690 plaintext_bytes = io.BytesIO(plaintext.encode('ascii'))
1da9b527
AE
691 encrypted_bytes = io.BytesIO()
692
897cbaf6 693 gpgme_ctx.encrypt_sign([encrypt_to_key], gpgme.ENCRYPT_ALWAYS_TRUST,
1da9b527
AE
694 plaintext_bytes, encrypted_bytes)
695
6aa41372 696 encrypted_txt = encrypted_bytes.getvalue().decode('ascii')
1da9b527
AE
697 return encrypted_txt
698
699
0a064403
AE
700def error (error_msg):
701
e4fb2ab2 702 sys.stderr.write(progname + ": " + str(error_msg) + "\n")
0a064403
AE
703
704
5e8f9094
AE
705def debug (debug_msg):
706
707 if edward_config.debug == True:
0a064403 708 error(debug_msg)
5e8f9094
AE
709
710
20f6e7c5
AE
711def handle_args ():
712 if __name__ == "__main__":
713
714 global progname
715 progname = sys.argv[0]
716
717 if len(sys.argv) > 1:
718 print(progname + ": error, this program doesn't " \
719 "need any arguments.", file=sys.stderr)
720 exit(1)
721
722
0bec96d6
AE
723main()
724