+ Pre:
+ the piece.payload_piece value should be TxtType.message.
+
+ Post:
+ replyinfo_obj gets updated with decryption status, signing status, a
+ potential signing key, posession status of the public key for the
+ signature and encryption capability status if that key is missing.
+ """
+
+ if piece.gpg_data.plainobj == None:
+ replyinfo_obj.decrypt_failure = True
+ return
+
+ replyinfo_obj.decrypt_success = True
+
+ # we already have a key (and a message)
+ if replyinfo_obj.target_key != None:
+ return
+
+ if piece.gpg_data.sigs == []:
+ if piece.gpg_data.sigkey_missing == True:
+ replyinfo_obj.sigkey_missing = True
+
+ if piece.gpg_data.key_cannot_encrypt == True:
+ replyinfo_obj.key_cannot_encrypt = True
+
+ # only include a signed message in the reply.
+ get_signed_part = True
+
+ else:
+ replyinfo_obj.target_key = piece.gpg_data.sigs[0]
+ replyinfo_obj.sig_success = True
+ get_signed_part = False
+
+ flatten_decrypted_payloads(piece.gpg_data.plainobj, replyinfo_obj, get_signed_part)
+
+ # to catch public keys in encrypted blocks
+ prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
+
+
+def prepare_for_reply_pubkey (piece, replyinfo_obj):
+ """Helper function for prepare_for_reply(). Marks pubkey import status.
+
+ Marks replyinfo_obj with pub key import status.
+
+ Args:
+ piece: a PayloadPiece object
+ replyinfo_obj: a ReplyInfo object
+
+ Pre:
+ piece.piece_type should be set to TxtType.pubkey .
+
+ Post:
+ replyinfo_obj has its fields updated.
+ """
+
+ if piece.gpg_data.keys == []:
+ if piece.gpg_data.key_cannot_encrypt == True:
+ replyinfo_obj.key_cannot_encrypt = True
+ else:
+ replyinfo_obj.pubkey_success = True
+
+ # prefer public key as a fallback for the encrypted reply
+ replyinfo_obj.fallback_target_key = piece.gpg_data.keys[0]
+
+
+def prepare_for_reply_sig (piece, replyinfo_obj):
+ """Helper function for prepare_for_reply(). Marks sig verification status.
+
+ Marks replyinfo_obj with signature verification status.
+
+ Args:
+ piece: a PayloadPiece object
+ replyinfo_obj: a ReplyInfo object
+
+ Pre:
+ piece.piece_type should be set to TxtType.signature, or
+ TxtType.detachedsig .
+
+ Post:
+ replyinfo_obj has its fields updated.
+ """
+
+ if piece.gpg_data.sigs == []:
+ replyinfo_obj.sig_failure = True
+
+ if piece.gpg_data.sigkey_missing == True:
+ replyinfo_obj.sigkey_missing = True
+
+ if piece.gpg_data.key_cannot_encrypt == True:
+ replyinfo_obj.key_cannot_encrypt = True
+
+ else:
+ replyinfo_obj.sig_success = True
+
+ if replyinfo_obj.fallback_target_key == None:
+ replyinfo_obj.fallback_target_key = piece.gpg_data.sigs[0]
+
+ if (piece.piece_type == TxtType.signature):
+ # to catch public keys in signature blocks
+ prepare_for_reply(piece.gpg_data.plainobj, replyinfo_obj)
+
+
+def flatten_decrypted_payloads (eddymsg_obj, replyinfo_obj, get_signed_part):
+ """For creating a string representation of a signed, encrypted part.
+
+ When given a decrypted payload, it will add either the plaintext or signed
+ plaintext to the reply message, depeding on 'get_signed_part'. This is
+ useful for ensuring that the reply message only comes from a signed and
+ ecrypted GPG message. It also sets the target_key for encrypting the reply
+ if it's told to get signed text only.
+
+ Args:
+ eddymsg_obj: the message in EddyMsg format created by decrypting GPG
+ text
+ replyinfo_obj: a ReplyInfo object for holding the message to quote and
+ the target_key to encrypt to.
+ get_signed_part: True if we should only include text that contains a
+ further signature. If False, then include plain text.
+
+ Returns:
+ Nothing
+
+ Pre:
+ The EddyMsg instance passed in should be a piece.gpg_data.plainobj
+ which represents decrypted text. It may or may not be signed on that
+ level.
+
+ Post:
+ the ReplyInfo instance may have a new 'target_key' set and its
+ 'msg_to_quote' will be updated with (possibly signed) plaintext, if any
+ could be found.
+ """
+
+ if eddymsg_obj == None:
+ return
+
+ # recurse on multi-part mime
+ if eddymsg_obj.multipart == True:
+ for sub in eddymsg_obj.subparts:
+ flatten_decrypted_payloads(sub, replyinfo_obj, get_signed_part)
+
+ for piece in eddymsg_obj.payload_pieces:
+ if (get_signed_part):
+ if ((piece.piece_type == TxtType.detachedsig) \
+ or (piece.piece_type == TxtType.signature)) \
+ and (piece.gpg_data != None) \
+ and (piece.gpg_data.plainobj != None):
+ flatten_decrypted_payloads(piece.gpg_data.plainobj, replyinfo_obj, False)
+ replyinfo_obj.target_key = piece.gpg_data.sigs[0]
+ break
+ else:
+ if (eddymsg_obj.content_disposition == None \
+ or not eddymsg_obj.content_disposition.startswith("attachment")) \
+ and piece.piece_type == TxtType.text:
+ replyinfo_obj.msg_to_quote += piece.string
+
+
+def get_key_from_fp (replyinfo_obj, gpgme_ctx):
+ """Obtains a public key object from a key fingerprint
+
+ If the .target_key is not set, then we use .fallback_target_key, if
+ available.
+
+ Args:
+ replyinfo_obj: ReplyInfo instance
+ gpgme_ctx: the gpgme context
+
+ Return:
+ Nothing
+
+ Pre:
+ Loading a key requires that we have the public key imported. This
+ requires that they email contains the pub key block, or that it was
+ previously sent to edward.
+
+ Post:
+ If the key can be loaded, then replyinfo_obj.reply_to_key points to the
+ public key object. If the key cannot be loaded, then the replyinfo_obj
+ is marked as having no public key available. If the key is not capable
+ of encryption, it will not be used, and replyinfo_obj will be marked
+ accordingly.
+ """
+
+ for key in (replyinfo_obj.target_key, replyinfo_obj.fallback_target_key):
+ if key != None:
+ try:
+ encrypt_to_key = gpgme_ctx.get_key(key)
+
+ except gpgme.GpgmeError:
+ continue
+
+ if is_key_usable(encrypt_to_key):
+ replyinfo_obj.encrypt_to_key = encrypt_to_key
+ replyinfo_obj.have_reply_key = True
+ replyinfo_obj.key_can_encrypt = True
+ return
+
+ else:
+ replyinfo_obj.key_cannot_encrypt = True
+
+
+
+def write_reply (replyinfo_obj):
+ """Write the reply email body about the GPG successes/failures.
+
+ The reply is about whether decryption, sig verification and key
+ import/loading was successful or failed. If text was successfully decrypted
+ and verified, then the first instance of such text will be included in
+ quoted form.
+
+ Args:
+ replyinfo_obj: contains details of GPG processing status
+
+ Returns:
+ the plaintext message to be sent to the user
+
+ Pre:
+ replyinfo_obj should be populated with info about GPG processing status.
+ """
+
+ reply_plain = ""
+
+ if (replyinfo_obj.pubkey_success == True):
+ reply_plain += replyinfo_obj.replies['greeting']
+ reply_plain += "\n\n"
+
+
+ if replyinfo_obj.decrypt_success == True:
+ debug('decrypt success')
+ reply_plain += replyinfo_obj.replies['success_decrypt']
+ reply_plain += "\n\n"
+
+ elif replyinfo_obj.decrypt_failure == True:
+ debug('decrypt failure')
+ reply_plain += replyinfo_obj.replies['failed_decrypt']
+ reply_plain += "\n\n"
+
+
+ if replyinfo_obj.sig_success == True:
+ debug('signature success')
+ reply_plain += replyinfo_obj.replies['sig_success']
+ reply_plain += "\n\n"
+
+ elif replyinfo_obj.sig_failure == True:
+ debug('signature failure')
+ reply_plain += replyinfo_obj.replies['sig_failure']
+ reply_plain += "\n\n"
+
+
+ if (replyinfo_obj.pubkey_success == True):
+ debug('public key received')
+ reply_plain += replyinfo_obj.replies['public_key_received']
+ reply_plain += "\n\n"
+
+ elif (replyinfo_obj.sigkey_missing == True):
+ debug('no public key')
+ reply_plain += replyinfo_obj.replies['no_public_key']
+ reply_plain += "\n\n"
+
+ elif (replyinfo_obj.key_can_encrypt == False) \
+ and (replyinfo_obj.key_cannot_encrypt == True):
+ debug('bad public key')
+ reply_plain += replyinfo_obj.replies['no_public_key']
+ reply_plain += "\n\n"
+
+
+ if (replyinfo_obj.decrypt_success == True) \
+ and (replyinfo_obj.sig_success == True) \
+ and (replyinfo_obj.have_reply_key == True):
+ debug('message quoted')
+ reply_plain += replyinfo_obj.replies['quote_follows']
+ reply_plain += "\n\n"
+ quoted_text = email_quote_text(replyinfo_obj.msg_to_quote)
+ reply_plain += quoted_text
+ reply_plain += "\n\n"
+
+
+ if (reply_plain == ""):
+ debug('plaintext message')
+ reply_plain += replyinfo_obj.replies['failed_decrypt']
+ reply_plain += "\n\n"
+
+
+ reply_plain += replyinfo_obj.replies['signature']
+ reply_plain += "\n\n"
+
+ return reply_plain