the referenced issue is resolved
[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
adcef2f7
AE
51langs = ["an", "de", "el", "en", "fr", "ja", "pt-br", "ro", "ru", "tr"]
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)
129543c3 293
8f61c66a 294 elif piece.piece_type == "pubkey":
f8ee6bd3 295 key_fps = add_gpg_key(piece.string, gpgme_ctx)
8f61c66a 296
f8ee6bd3 297 if key_fps != []:
8f61c66a 298 piece.gpg_data = GPGData()
f8ee6bd3 299 piece.gpg_data.keys = key_fps
129543c3
AE
300
301 elif piece.piece_type == "clearsign":
f8ee6bd3 302 (plaintext, sig_fps) = verify_clear_signature(piece.string, gpgme_ctx)
129543c3 303
f8ee6bd3 304 if sig_fps != []:
129543c3 305 piece.gpg_data = GPGData()
f8ee6bd3 306 piece.gpg_data.sigs = sig_fps
129543c3
AE
307 piece.gpg_data.plainobj = parse_pgp_mime(plaintext, gpgme_ctx)
308
101d54a8
AE
309 elif piece.piece_type == "detachedsig":
310 for prev in prev_parts:
311 payload_bytes = prev.payload_bytes
6d240f68 312 sig_fps = verify_detached_signature(piece.string, payload_bytes, gpgme_ctx)
101d54a8 313
6d240f68
AE
314 if sig_fps != []:
315 piece.gpg_data = GPGData()
316 piece.gpg_data.sigs = sig_fps
317 piece.gpg_data.plainobj = prev
318 break
7e18f14d 319
38738401
AE
320 else:
321 pass
322
323
928e3819 324def prepare_for_reply (eddymsg_obj, replyinfo_obj):
dd11a483 325
928e3819 326 do_to_eddys_pieces(prepare_for_reply_pieces, eddymsg_obj, replyinfo_obj)
56578eaf 327
928e3819 328def prepare_for_reply_pieces (eddymsg_obj, replyinfo_obj):
56578eaf 329
928e3819 330 for piece in eddymsg_obj.payload_pieces:
38738401 331 if piece.piece_type == "text":
d873ff48
AE
332 # don't quote the plaintext part.
333 pass
334
38738401 335 elif piece.piece_type == "message":
05683768 336 prepare_for_reply_message(piece, replyinfo_obj)
d873ff48 337
05683768
AE
338 elif piece.piece_type == "pubkey":
339 prepare_for_reply_pubkey(piece, replyinfo_obj)
6906d46d 340
05683768
AE
341 elif (piece.piece_type == "clearsign") \
342 or (piece.piece_type == "detachedsig"):
343 prepare_for_reply_sig(piece, replyinfo_obj)
d873ff48 344
d873ff48 345
05683768 346def prepare_for_reply_message (piece, replyinfo_obj):
44fe39b7 347
05683768
AE
348 if piece.gpg_data == None:
349 replyinfo_obj.failed_decrypt = True
350 return
351
352 if piece.gpg_data.decrypted == False:
353 prepare_for_reply_sig(piece, replyinfo_obj)
354 return
355 else:
356 replyinfo_obj.success_decrypt = True
357
358 # we already have a key (and a message)
359 if replyinfo_obj.target_key != None:
360 return
361
362 if piece.gpg_data.sigs != []:
363 replyinfo_obj.target_key = piece.gpg_data.sigs[0]
364 get_signed_part = False
365 else:
366 # only include a signed message in the reply.
367 get_signed_part = True
368
369 replyinfo_obj.msg_to_quote = flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part)
370
371 # to catch public keys in encrypted blocks
372 prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
373
374
375def prepare_for_reply_pubkey (piece, replyinfo_obj):
376
377 if piece.gpg_data == None or piece.gpg_data.keys == []:
378 replyinfo_obj.no_public_key = True
379 else:
380 replyinfo_obj.public_key_received = True
381
382 if replyinfo_obj.fallback_target_key == None:
383 replyinfo_obj.fallback_target_key = piece.gpg_data.keys[0]
384
385
386def prepare_for_reply_sig (piece, replyinfo_obj):
387
388 if piece.gpg_data == None or piece.gpg_data.sigs == []:
389 replyinfo_obj.sig_failure = True
390 else:
391 replyinfo_obj.sig_success = True
392
393 if replyinfo_obj.fallback_target_key == None:
394 replyinfo_obj.fallback_target_key = piece.gpg_data.sigs[0]
6906d46d
AE
395
396
d873ff48 397
0402995a 398def flatten_decrypted_payloads (eddymsg_obj, get_signed_part):
d873ff48
AE
399
400 flat_string = ""
401
0aa88c27
AE
402 if eddymsg_obj == None:
403 return ""
404
0402995a 405 # recurse on multi-part mime
928e3819
AE
406 if eddymsg_obj.multipart == True:
407 for sub in eddymsg_obj.subparts:
0402995a 408 flat_string += flatten_decrypted_payloads (sub)
d873ff48
AE
409
410 return flat_string
411
928e3819 412 for piece in eddymsg_obj.payload_pieces:
d873ff48
AE
413 if piece.piece_type == "text":
414 flat_string += piece.string
0aa88c27 415
0402995a
AE
416 if (get_signed_part):
417 # don't include nested encryption
418 elif (piece.piece_type == "message") \
419 and (piece.gpg_data != None) \
420 and (piece.gpg_data.decrypted == False):
421 flat_string += flatten_decrypted_payloads(piece.gpg_data.plainobj, get_signed_part)
422
423 elif ((piece.piece_type == "clearsign") \
424 or (piece.piece_type == "detachedsig")) \
425 and (piece.gpg_data != None):
426 # FIXME: the key used to sign this message needs to be the one that is used for the encrypted reply.
427 flat_string += flatten_decrypted_payloads (piece.gpg_data.plainobj, get_signed_part)
d873ff48
AE
428
429 return flat_string
430
431
013f7cb8
AE
432def get_key_from_fp (replyinfo_obj, gpgme_ctx):
433
dc24daf1
AE
434 if replyinfo_obj.target_key == None:
435 replyinfo_obj.target_key = replyinfo_obj.fallback_target_key
436
013f7cb8
AE
437 if replyinfo_obj.target_key != None:
438 try:
439 encrypt_to_key = gpgme_ctx.get_key(replyinfo_obj.target_key)
440 return encrypt_to_key
441
442 except:
443 pass
444
445 # no available key to use
446 replyinfo_obj.target_key = None
447 replyinfo_obj.fallback_target_key = None
448
449 replyinfo_obj.no_public_key = True
450 replyinfo_obj.public_key_received = False
451
452 return None
453
454
d873ff48
AE
455def write_reply (replyinfo_obj):
456
457 reply_plain = ""
458
459 if replyinfo_obj.success_decrypt == True:
d873ff48 460 reply_plain += replyinfo_obj.replies['success_decrypt']
2694709a
AE
461
462 if replyinfo_obj.no_public_key == False:
463 quoted_text = email_quote_text(replyinfo_obj.msg_to_quote)
464 reply_plain += quoted_text
d873ff48
AE
465
466 elif replyinfo_obj.failed_decrypt == True:
467 reply_plain += replyinfo_obj.replies['failed_decrypt']
468
469
470 if replyinfo_obj.sig_success == True:
471 reply_plain += "\n\n"
472 reply_plain += replyinfo_obj.replies['sig_success']
473
474 elif replyinfo_obj.sig_failure == True:
475 reply_plain += "\n\n"
476 reply_plain += replyinfo_obj.replies['sig_failure']
477
478
479 if replyinfo_obj.public_key_received == True:
480 reply_plain += "\n\n"
481 reply_plain += replyinfo_obj.replies['public_key_received']
482
483 elif replyinfo_obj.no_public_key == True:
484 reply_plain += "\n\n"
485 reply_plain += replyinfo_obj.replies['no_public_key']
486
487
488 reply_plain += "\n\n"
489 reply_plain += replyinfo_obj.replies['signature']
56578eaf 490
d873ff48 491 return reply_plain
56578eaf
AE
492
493
8f61c66a 494def add_gpg_key (key_block, gpgme_ctx):
c267c233 495
8f61c66a 496 fp = io.BytesIO(key_block.encode('ascii'))
c267c233 497
8f61c66a
AE
498 result = gpgme_ctx.import_(fp)
499 imports = result.imports
c267c233 500
f8ee6bd3 501 key_fingerprints = []
c267c233 502
8f61c66a
AE
503 if imports != []:
504 for import_ in imports:
505 fingerprint = import_[0]
f8ee6bd3 506 key_fingerprints += [fingerprint]
c267c233 507
e49673aa 508 debug("added gpg key: " + fingerprint)
ec1e779a 509
f8ee6bd3 510 return key_fingerprints
ec1e779a
AE
511
512
129543c3 513def verify_clear_signature (sig_block, gpgme_ctx):
cf75de65 514
129543c3
AE
515 # FIXME: this might require the un-decoded bytes
516 # or the correct re-encoding with the carset of the mime part.
517 msg_fp = io.BytesIO(sig_block.encode('utf-8'))
518 ptxt_fp = io.BytesIO()
cf75de65 519
129543c3 520 result = gpgme_ctx.verify(msg_fp, None, ptxt_fp)
cf75de65 521
129543c3
AE
522 # FIXME: this might require using the charset of the mime part.
523 plaintext = ptxt_fp.getvalue().decode('utf-8')
cf75de65 524
f8ee6bd3 525 sig_fingerprints = []
129543c3 526 for res_ in result:
f8ee6bd3 527 sig_fingerprints += [res_.fpr]
cf75de65 528
f8ee6bd3 529 return plaintext, sig_fingerprints
cf75de65
AE
530
531
101d54a8
AE
532def verify_detached_signature (detached_sig, plaintext_bytes, gpgme_ctx):
533
534 detached_sig_fp = io.BytesIO(detached_sig.encode('ascii'))
535 plaintext_fp = io.BytesIO(plaintext_bytes)
536 ptxt_fp = io.BytesIO()
537
538 result = gpgme_ctx.verify(detached_sig_fp, plaintext_fp, None)
539
540 sig_fingerprints = []
541 for res_ in result:
542 sig_fingerprints += [res_.fpr]
543
544 return sig_fingerprints
545
546
5b3053c1 547def decrypt_block (msg_block, gpgme_ctx):
0bec96d6 548
5b3053c1 549 block_b = io.BytesIO(msg_block.encode('ascii'))
0bec96d6
AE
550 plain_b = io.BytesIO()
551
afc1f64c
AE
552 try:
553 sigs = gpgme_ctx.decrypt_verify(block_b, plain_b)
554 except:
555 return ("",[])
0bec96d6 556
6aa41372 557 plaintext = plain_b.getvalue().decode('utf-8')
cbdf22c1
AE
558
559 fingerprints = []
560 for sig in sigs:
561 fingerprints += [sig.fpr]
562 return (plaintext, fingerprints)
0bec96d6
AE
563
564
d0489345 565def choose_reply_encryption_key (gpgme_ctx, fingerprints):
fafa21c3
AE
566
567 reply_key = None
d0489345
AE
568 for fp in fingerprints:
569 try:
570 key = gpgme_ctx.get_key(fp)
571
572 if (key.can_encrypt == True):
573 reply_key = key
574 break
575 except:
576 continue
577
fafa21c3 578
216708e9 579 return reply_key
fafa21c3
AE
580
581
d65993b8
AE
582def email_to_from_subject (email_text):
583
584 email_struct = email.parser.Parser().parsestr(email_text)
585
586 email_to = email_struct['To']
587 email_from = email_struct['From']
588 email_subject = email_struct['Subject']
589
590 return email_to, email_from, email_subject
591
592
adcef2f7
AE
593def import_lang(email_to):
594
5250b3b8
AE
595 if email_to != None:
596 for lang in langs:
597 if "edward-" + lang in email_to:
598 lang = "lang." + re.sub('-', '_', lang)
599 language = importlib.import_module(lang)
adcef2f7 600
5250b3b8 601 return language
adcef2f7
AE
602
603 return importlib.import_module("lang.en")
604
605
bf79a93e 606def generate_encrypted_mime (plaintext, email_from, email_subject, encrypt_to_key,
0a064403 607 gpgme_ctx):
1da9b527 608
2007103e
AE
609 # quoted printable encoding lets most ascii characters look normal
610 # before the decrypted mime message is decoded.
611 char_set = email.charset.Charset("utf-8")
612 char_set.body_encoding = email.charset.QP
8bdfb6d4 613
2007103e
AE
614 # MIMEText doesn't allow setting the text encoding
615 # so we use MIMENonMultipart.
616 plaintext_mime = MIMENonMultipart('text', 'plain')
617 plaintext_mime.set_payload(plaintext, charset=char_set)
216708e9
AE
618
619 if (encrypt_to_key != None):
8bdfb6d4
AE
620
621 encrypted_text = encrypt_sign_message(plaintext_mime.as_string(),
622 encrypt_to_key,
40c37ab3 623 gpgme_ctx)
8bdfb6d4
AE
624
625 control_mime = MIMEApplication("Version: 1",
626 _subtype='pgp-encrypted',
627 _encoder=email.encoders.encode_7or8bit)
628 control_mime['Content-Description'] = 'PGP/MIME version identification'
629 control_mime.set_charset('us-ascii')
630
631 encoded_mime = MIMEApplication(encrypted_text,
632 _subtype='octet-stream; name="encrypted.asc"',
633 _encoder=email.encoders.encode_7or8bit)
634 encoded_mime['Content-Description'] = 'OpenPGP encrypted message'
635 encoded_mime['Content-Disposition'] = 'inline; filename="encrypted.asc"'
636 encoded_mime.set_charset('us-ascii')
637
638 message_mime = MIMEMultipart(_subtype="encrypted", protocol="application/pgp-encrypted")
639 message_mime.attach(control_mime)
640 message_mime.attach(encoded_mime)
641 message_mime['Content-Disposition'] = 'inline'
216708e9 642
216708e9 643 else:
2007103e
AE
644 message_mime = plaintext_mime
645
646 message_mime['To'] = email_from
647 message_mime['Subject'] = email_subject
648
649 reply = message_mime.as_string()
1da9b527
AE
650
651 return reply
652
653
f87041f8
AE
654def email_quote_text (text):
655
656 quoted_message = re.sub(r'^', r'> ', text, flags=re.MULTILINE)
657
658 return quoted_message
659
660
0a064403 661def encrypt_sign_message (plaintext, encrypt_to_key, gpgme_ctx):
897cbaf6 662
6aa41372 663 plaintext_bytes = io.BytesIO(plaintext.encode('ascii'))
1da9b527
AE
664 encrypted_bytes = io.BytesIO()
665
897cbaf6 666 gpgme_ctx.encrypt_sign([encrypt_to_key], gpgme.ENCRYPT_ALWAYS_TRUST,
1da9b527
AE
667 plaintext_bytes, encrypted_bytes)
668
6aa41372 669 encrypted_txt = encrypted_bytes.getvalue().decode('ascii')
1da9b527
AE
670 return encrypted_txt
671
672
0a064403
AE
673def error (error_msg):
674
e4fb2ab2 675 sys.stderr.write(progname + ": " + str(error_msg) + "\n")
0a064403
AE
676
677
5e8f9094
AE
678def debug (debug_msg):
679
680 if edward_config.debug == True:
0a064403 681 error(debug_msg)
5e8f9094
AE
682
683
20f6e7c5
AE
684def handle_args ():
685 if __name__ == "__main__":
686
687 global progname
688 progname = sys.argv[0]
689
690 if len(sys.argv) > 1:
691 print(progname + ": error, this program doesn't " \
692 "need any arguments.", file=sys.stderr)
693 exit(1)
694
695
0bec96d6
AE
696main()
697