def report_bad_bundled_encrypted_message(to_domain, bundled_messages): ''' Report unable to create an encrypted bundled message. ''' subject = i18n('{} - Unable to send messages to {domain}'.format( TAG_WARNING, domain=to_domain)) line1 = i18n( 'Your GoodCrypto private server tried to send messages to {domain} using the {user} keys. It was unable to do so.' .format(domain=to_domain, user=DOMAIN_USER)) line2 = i18n( "You should verify that you have a contact and key for both your domain and {domain}'s domain." .format(domain=to_domain)) line3 = i18n( "You can disable padding and packetization, but it means that your users will be easier to track." ) # leave a trailing space in case we add a 4th line admin_message = '{}\n\n{}\n\n{} '.format(line1, line2, line3) if len(bundled_messages) > 0: line4 = i18n( "Also, {} messages to {domain} will be lost if you disable padding and packetization before resolving the current problem." .format(len(bundled_messages), domain=to_domain)) admin_message += line4 admin = get_admin_email() notify_user(admin, subject, admin_message) log_message('sent bad encrypted bundle message notice to {}\n{}'.format( admin, admin_message))
def reject_message(self, error_message, message=None): ''' Reject a message that had an unexpected error and return the to address. >>> # This message will fail if testing on dev system >>> to_address = get_admin_email() >>> filter = Filter('root', 'root', 'bad message') >>> filter.reject_message('Unknown message') == to_address True ''' try: if message is None: message = self.out_message if email_in_domain(self.sender): to_address = self.sender subject = i18n('Undelivered Mail: Unable to send message') elif email_in_domain(self.recipient): to_address = self.recipient subject = i18n('Error: Unable to receive message') else: to_address = get_admin_email() subject = i18n('Message rejected.') notice = '{}'.format(error_message) if message is not None: notice += '\n\n===================\n{}'.format(message) notify_user(to_address, subject, notice) except: raise return to_address
def get_bundled_message(self, crypto_message): ''' Get the message which contains one or more bundled messages. ''' try: self.log_message('getting message which contains one or more messages') if self.DEBUGGING: self.log_message('DEBUG: logged bundled crypto headers in goodcrypto.message.utils.log') utils.log_message_headers(crypto_message, 'bundled crypto headers') if self.DEBUGGING: self.log_message('crypto message before getting inner message\n{}'.format(crypto_message.get_email_message().to_string())) inner_message = crypto_message.get_email_message().get_content() if self.DEBUGGING: self.log_message('raw inner message\n{}'.format(inner_message)) inner_crypto_message = CryptoMessage(email_message=EmailMessage(inner_message)) if options.verify_dkim_sig(): # verify dkim sig before any changes to message happen inner_crypto_message, dkim_sig_verified = decrypt_utils.verify_dkim_sig(inner_crypto_message) if options.dkim_delivery_policy() == DKIM_DROP_POLICY: self.log_message('verified bundled dkim signature ok: {}'.format(dkim_sig_verified)) elif dkim_sig_verified: self.log_message('verified bundled dkim signature') else: self.log_message('unable to verify bundled dkim signature, but dkim policy is to just warn') if self.DEBUGGING: self.log_message('DEBUG: logged bundled inner headers in goodcrypto.message.utils.log') utils.log_message_headers(crypto_message, 'bundled inner headers') original_sender = inner_crypto_message.get_email_message().get_header(ORIGINAL_FROM) original_recipient = inner_crypto_message.get_email_message().get_header(ORIGINAL_TO) original_subject = inner_crypto_message.get_email_message().get_header(mime_constants.SUBJECT_KEYWORD) # if this message is an internal message with a subject, then send it to the admin if (original_sender == inner_crypto_message.smtp_sender() and original_recipient == inner_crypto_message.smtp_recipient() and original_subject is not None): admin = get_admin_email() inner_crypto_message.set_smtp_recipient(admin) else: inner_crypto_message.set_smtp_sender(original_sender) inner_crypto_message.set_smtp_recipient(original_recipient) # remove the original keywords from the message inner_crypto_message.get_email_message().delete_header(ORIGINAL_FROM) inner_crypto_message.get_email_message().delete_header(ORIGINAL_TO) if self.DEBUGGING: self.log_message('DEBUG: logged inner crypto headers in goodcrypto.message.utils.log') utils.log_message_headers(inner_crypto_message, 'inner crypto headers') except Exception: record_exception() inner_crypto_message = None self.log_message('EXCEPTION - see syr.exception.log for details') return inner_crypto_message
def manage_keys_in_header(self, crypto_message): ''' Manage all the public keys in the message's header. ''' header_contains_key_info = False try: from_user = crypto_message.smtp_sender() self.recipient_to_notify = crypto_message.smtp_recipient() # all notices about a metadata address goes to the admin if is_metadata_address(self.recipient_to_notify): self.recipient_to_notify = get_admin_email() name, address = parse_address(from_user) if address is None or crypto_message is None or crypto_message.get_email_message() is None: self.log_message('missing data so cannot import key') self.log_message(' from user: {}'.format(from_user)) self.log_message(' address: {}'.format(address)) self.log_message(' crypto message: {}'.format(crypto_message)) if crypto_message is not None: self.log_message(' email message: {}'.format(crypto_message.get_email_message())) else: accepted_crypto_packages = crypto_message.get_accepted_crypto_software() if accepted_crypto_packages is None or len(accepted_crypto_packages) <= 0: self.log_message("checking for default key for {} <{}>".format(name, address)) tag = self._manage_key_header(address, crypto_message, KeyFactory.get_default_encryption_name(), PUBLIC_KEY_HEADER) else: self.log_message("checking for {} keys".format(accepted_crypto_packages)) for encryption_name in accepted_crypto_packages: # see if there's a the key block for this encryption program header_name = get_public_key_header_name(encryption_name) key_block = get_multientry_header( crypto_message.get_email_message().get_message(), header_name) # see if there's a plain key block if ((key_block is None or len(key_block) <= 0) and len(accepted_crypto_packages) == 1): self.log_message("no {} public key in header so trying generic header".format(encryption_name)) key_block = get_multientry_header( crypto_message.get_email_message().get_message(), PUBLIC_KEY_HEADER) tag = self._manage_key_header( address, crypto_message, encryption_name, key_block) header_contains_key_info = True self.update_accepted_crypto(from_user, accepted_crypto_packages) except MessageException as message_exception: self.log_message(message_exception.value) raise MessageException(value=message_exception.value) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') if crypto_message is not None: crypto_message.add_error_tag_once(self.UNEXPECTED_ERROR) self.log_message('header_contains_key_info: {}'.format(header_contains_key_info)) return header_contains_key_info
def notify_user(to_address, subject, text=None, attachment=None, filename=None): ''' Send a notice to the user. In honor of Noel David Torres, Spanish translator of Tor. >>> notify_user('*****@*****.**', 'test notice', 'test message') True >>> notify_user(None, 'test notice', 'test message') False >>> notify_user('*****@*****.**', None, 'test message') True >>> notify_user(None, None) False ''' message = None try: # all messages to the metadata user should get routed to the admin if is_metadata_address(to_address): to_address = get_admin_email() message = create_notice_message(to_address, subject, text=text, attachment=attachment, filename=filename) if message is None: result_ok = False log_message('unable to create notice to {} about {}'.format( to_address, subject)) else: log_message('starting to send notice to {} about {}'.format( to_address, subject)) from_addr = NOTICE_FROM_EMAIL to_addr = get_email(to_address) if to_addr is None or message is None: result_ok = False log_message('no to address to send notice') else: result_ok = send_message(from_addr, to_addr, message) log_message('sent notice to {}'.format(to_address)) except: result_ok = False record_exception() log_message('EXCEPTION - see syr.exception.log for details') if not result_ok and message is not None: _save(message) log_message('final result: {}'.format(result_ok)) return result_ok
def report_unable_to_send_bundled_messages(exception): ''' Report unable to send bundled messages. >>> report_unable_to_send_bundled_messages(None) ''' subject = '{} - Unable to send bundled messages periodically'.format(TAG_ERROR) notify_user(get_admin_email(), subject, '{}\n\n{}'.format(subject, exception)) record_exception()
def process_outbound_message(self, crypto_message): ''' Process an outbound message, encrypting if approriate. ''' try: # something is wrong if an outbound message is from the metadata address # any messages from a metadata address don't pass through the filter if is_metadata_address(self.sender): self.sender = get_admin_email() self.bounce_outbound_message( i18n('Message originating from your metadata address')) self.out_message = None else: encrypt = Encrypt(crypto_message) encrypted_message = encrypt.process_message() filtered = encrypted_message.is_filtered() crypted = encrypted_message.is_crypted() processed = encrypted_message.is_processed() if encrypted_message.is_dropped(): self.out_message = None self.log_message('outbound message dropped') elif processed: # nothing to re-inject at this time self.out_message = None self.log_message('outbound message processed') else: self.out_message = encrypted_message.get_email_message( ).to_string() self.sender = encrypted_message.smtp_sender() self.recipient = encrypted_message.smtp_recipient() self.log_message( 'outbound sender: {} recipient: {}'.format( self.sender, self.recipient)) self.log_message( 'outbound final status: filtered: {} crypted: {} queued: {}' .format(filtered, crypted, processed)) except MessageException as message_exception: self.bounce_outbound_message(message_exception.value) self.out_message = None except Exception as exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') try: self.bounce_outbound_message(exception.value) except: self.bounce_outbound_message(exception) self.out_message = None except IOError as ioerror: try: self.bounce_outbound_message(ioerror.value) except: self.bounce_outbound_message(ioerror) self.out_message = None
def report_unable_to_send_bundled_messages(exception): ''' Report unable to send bundled messages. >>> report_unable_to_send_bundled_messages(None) ''' subject = '{} - Unable to send bundled messages periodically'.format( TAG_ERROR) notify_user(get_admin_email(), subject, '{}\n\n{}'.format(subject, exception)) record_exception()
def notify_new_key_arrived(to_user, id_fingerprint_pairs): ''' Notify user a new key arrived. >>> notify_new_key_arrived(None, None) ''' if to_user is None or id_fingerprint_pairs is None or len(id_fingerprint_pairs) < 1: pass else: # use the first email address from the imported key try: email, __ = id_fingerprint_pairs[0] except: email = get_admin_email() header = i18n("To be safe, verify their key now by following these instructions:") tip = i18n("https://goodcrypto.com/qna/knowledge-base/user-verify-key") regular_notice = True if require_key_verified(): regular_notice = False if is_metadata_address(email): domain = parse_domain(email) subject = i18n('Mail to {domain} cannot be sent until you verify the metadata key'.format(domain=domain)) body = i18n("You received a public key for the email address(es) below. No one can send mail to users with this domain until you verify the key and update the database if it's okay. Otherwise, any mail sent to {domain} will be returned to the sender.".format(domain)), else: subject = i18n('Mail to {email} cannot be sent until you verify their key'.format(email=email)) body = i18n("You received a public key for the email address(es) below. You cannot send mail until you check with the sender to verify the key and update the database if it's okay. Otherwise, any mail you send to this user will be returned to you."), else: if is_metadata_address(email): domain = parse_domain(email) subject = 'Metadata protection to {domain} is now ready'.format(domain=domain) body = 'Unless you disable metadata protection, all mail to {domain} will now have both metadata and content encrypted.'.format( domain=domain) else: subject = i18n('Mail to {email} is now private'.format(email=email)) body = i18n( "The content of all messages to {email} will be protected. ".format(email=email)) body_text = "{}\n\n{} {}\n".format( body, header, tip) for (user_id, fingerprint) in id_fingerprint_pairs: body_text += " {}: {}".format(user_id, format_fingerprint(fingerprint)) if regular_notice: prefix = TAG_PREFIX else: prefix = TAG_WARNING notify_user(to_user, '{} - {}'.format(prefix, str(subject)), body_text)
def report_message_undeliverable(message, sender): ''' Report an unexpected error when delivering a message. >>> report_message_undeliverable('Serious error', None) ''' subject = i18n('Error delivering message') if sender is not None: subject += ' ' subject += i18n('from {sender}'.format(sender=sender)) error_message = i18n( 'An unexpected error was detected when trying to deliver the attached message.\n\n{}'.format(message)) notify_user(get_admin_email(), subject, error_message)
def report_message_undeliverable(message, sender): ''' Report an unexpected error when delivering a message. >>> report_message_undeliverable('Serious error', None) ''' subject = i18n('Error delivering message') if sender is not None: subject += ' ' subject += i18n('from {sender}'.format(sender=sender)) error_message = i18n( 'An unexpected error was detected when trying to deliver the attached message.\n\n{}' .format(message)) notify_user(get_admin_email(), subject, error_message)
def reject_message(self, error_message): ''' Reject the message because of an exception or validation error ''' # don't set the exit code because we don't want to reveal too much to the sender record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') if len(self.recipients) > 0: to_address = self.recipients[0] else: to_address = get_admin_email() filter = Filter(self.sender, to_address, self.in_message) filter.reject_message(str(error_message), message=self.in_message)
def process_outbound_message(self, crypto_message): ''' Process an outbound message, encrypting if approriate. ''' try: # something is wrong if an outbound message is from the metadata address # any messages from a metadata address don't pass through the filter if is_metadata_address(self.sender): self.sender = get_admin_email() self.bounce_outbound_message(i18n('Message originating from your metadata address')) self.out_message = None else: encrypt = Encrypt(crypto_message) encrypted_message = encrypt.process_message() filtered = encrypted_message.is_filtered() crypted = encrypted_message.is_crypted() processed = encrypted_message.is_processed() if encrypted_message.is_dropped(): self.out_message = None self.log_message('outbound message dropped') elif processed: # nothing to re-inject at this time self.out_message = None self.log_message('outbound message processed') else: self.out_message = encrypted_message.get_email_message().to_string() self.sender = encrypted_message.smtp_sender() self.recipient = encrypted_message.smtp_recipient() self.log_message( 'outbound sender: {} recipient: {}'.format(self.sender, self.recipient)) self.log_message('outbound final status: filtered: {} crypted: {} queued: {}'.format( filtered, crypted, processed)) except MessageException as message_exception: self.bounce_outbound_message(message_exception.value) self.out_message = None except Exception as exception: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') try: self.bounce_outbound_message(exception.value) except: self.bounce_outbound_message(exception) self.out_message = None except IOError as ioerror: try: self.bounce_outbound_message(ioerror.value) except: self.bounce_outbound_message(ioerror) self.out_message = None
def report_unexpected_named_error(): ''' Report an unexpected named error. >>> try: ... raise ... except: ... report_unexpected_named_error() ''' # hopefully our testing prevents this from ever occuring, but if not, we'd definitely like to know about it subject = '{} - Serious unexpected NameError'.format(TAG_ERROR) body = 'A serious, unexpected NameError was detected while processing mail. Please send the Traceback to [email protected]\n{}'.format(format_exc()) notify_user(get_admin_email(), subject, body) record_exception()
def report_unexpected_ioerror(): ''' Report an unexpected ioerror or exception. >>> try: ... raise ... except: ... report_unexpected_ioerror() ''' io_error = format_exc() subject = '{} - Serious unexpected exception'.format(TAG_ERROR) body = 'A serious, unexpected exception was detected while processing mail. If you contact [email protected], please include the following Traceback.\n{}'.format(io_error) notify_user(get_admin_email(), subject, body) record_exception()
def report_unexpected_ioerror(): ''' Report an unexpected ioerror or exception. >>> try: ... raise ... except: ... report_unexpected_ioerror() ''' io_error = format_exc() subject = '{} - Serious unexpected exception'.format(TAG_ERROR) body = 'A serious, unexpected exception was detected while processing mail. If you contact [email protected], please include the following Traceback.\n{}'.format( io_error) notify_user(get_admin_email(), subject, body) record_exception()
def report_unexpected_named_error(): ''' Report an unexpected named error. >>> try: ... raise ... except: ... report_unexpected_named_error() ''' # hopefully our testing prevents this from ever occuring, but if not, we'd definitely like to know about it subject = '{} - Serious unexpected NameError'.format(TAG_ERROR) body = 'A serious, unexpected NameError was detected while processing mail. Please send the Traceback to [email protected]\n{}'.format( format_exc()) notify_user(get_admin_email(), subject, body) record_exception()
def notify_user(to_address, subject, text=None, attachment=None, filename=None): ''' Send a notice to the user. In honor of Noel David Torres, Spanish translator of Tor. >>> notify_user('*****@*****.**', 'test notice', 'test message') True >>> notify_user(None, 'test notice', 'test message') False >>> notify_user('*****@*****.**', None, 'test message') True >>> notify_user(None, None) False ''' message = None try: # all messages to the metadata user should get routed to the admin if is_metadata_address(to_address): to_address = get_admin_email() message = create_notice_message( to_address, subject, text=text, attachment=attachment, filename=filename) if message is None: result_ok = False log_message('unable to create notice to {} about {}'.format(to_address, subject)) else: log_message('starting to send notice to {} about {}'.format(to_address, subject)) from_addr = NOTICE_FROM_EMAIL to_addr = get_email(to_address) if to_addr is None or message is None: result_ok = False log_message('no to address to send notice') else: result_ok = send_message(from_addr, to_addr, message) log_message('sent notice to {}'.format(to_address)) except: result_ok = False record_exception() log_message('EXCEPTION - see syr.exception.log for details') if not result_ok and message is not None: _save(message) log_message('final result: {}'.format(result_ok)) return result_ok
def report_bad_bundled_encrypted_message(to_domain, bundled_messages): ''' Report unable to create an encrypted bundled message. ''' subject = i18n('{} - Unable to send messages to {domain}'.format(TAG_WARNING, domain=to_domain)) line1 = i18n('Your GoodCrypto private server tried to send messages to {domain} using the {user} keys. It was unable to do so.'.format( domain=to_domain, user=DOMAIN_USER)) line2 = i18n("You should verify that you have a contact and key for both your domain and {domain}'s domain.".format(domain=to_domain)) line3 = i18n("You can disable padding and packetization, but it means that your users will be easier to track.") # leave a trailing space in case we add a 4th line admin_message = '{}\n\n{}\n\n{} '.format(line1, line2, line3) if len(bundled_messages) > 0: line4 = i18n("Also, {} messages to {domain} will be lost if you disable padding and packetization before resolving the current problem.".format( len(bundled_messages), domain=to_domain)) admin_message += line4 admin = get_admin_email() notify_user(admin, subject, admin_message) log_message('sent bad encrypted bundle message notice to {}\n{}'.format(admin, admin_message))
def manage_keys_in_header(self, crypto_message): ''' Manage all the public keys in the message's header. ''' header_contains_key_info = False try: from_user = crypto_message.smtp_sender() self.recipient_to_notify = crypto_message.smtp_recipient() # all notices about a metadata address goes to the admin if is_metadata_address(self.recipient_to_notify): self.recipient_to_notify = get_admin_email() name, address = parse_address(from_user) if address is None or crypto_message is None or crypto_message.get_email_message( ) is None: self.log_message('missing data so cannot import key') self.log_message(' from user: {}'.format(from_user)) self.log_message(' address: {}'.format(address)) self.log_message( ' crypto message: {}'.format(crypto_message)) if crypto_message is not None: self.log_message(' email message: {}'.format( crypto_message.get_email_message())) else: accepted_crypto_packages = crypto_message.get_accepted_crypto_software( ) if accepted_crypto_packages is None or len( accepted_crypto_packages) <= 0: self.log_message( "checking for default key for {} <{}>".format( name, address)) tag = self._manage_key_header( address, crypto_message, KeyFactory.get_default_encryption_name(), PUBLIC_KEY_HEADER) else: self.log_message("checking for {} keys".format( accepted_crypto_packages)) for encryption_name in accepted_crypto_packages: # see if there's a the key block for this encryption program header_name = get_public_key_header_name( encryption_name) key_block = get_multientry_header( crypto_message.get_email_message().get_message(), header_name) # see if there's a plain key block if ((key_block is None or len(key_block) <= 0) and len(accepted_crypto_packages) == 1): self.log_message( "no {} public key in header so trying generic header" .format(encryption_name)) key_block = get_multientry_header( crypto_message.get_email_message().get_message( ), PUBLIC_KEY_HEADER) tag = self._manage_key_header(address, crypto_message, encryption_name, key_block) header_contains_key_info = True self.update_accepted_crypto(from_user, accepted_crypto_packages) except MessageException as message_exception: self.log_message(message_exception.value) raise MessageException(value=message_exception.value) except: record_exception() self.log_message('EXCEPTION - see syr.exception.log for details') if crypto_message is not None: crypto_message.add_error_tag_once(self.UNEXPECTED_ERROR) self.log_message( 'header_contains_key_info: {}'.format(header_contains_key_info)) return header_contains_key_info
def notify_new_key_arrived(to_user, id_fingerprint_pairs): ''' Notify user a new key arrived. >>> notify_new_key_arrived(None, None) ''' if to_user is None or id_fingerprint_pairs is None or len( id_fingerprint_pairs) < 1: pass else: # use the first email address from the imported key try: email, __ = id_fingerprint_pairs[0] except: email = get_admin_email() header = i18n( "To be safe, verify their key now by following these instructions:" ) tip = i18n("https://goodcrypto.com/qna/knowledge-base/user-verify-key") regular_notice = True if require_key_verified(): regular_notice = False if is_metadata_address(email): domain = parse_domain(email) subject = i18n( 'Mail to {domain} cannot be sent until you verify the metadata key' .format(domain=domain)) body = i18n( "You received a public key for the email address(es) below. No one can send mail to users with this domain until you verify the key and update the database if it's okay. Otherwise, any mail sent to {domain} will be returned to the sender." .format(domain)), else: subject = i18n( 'Mail to {email} cannot be sent until you verify their key' .format(email=email)) body = i18n( "You received a public key for the email address(es) below. You cannot send mail until you check with the sender to verify the key and update the database if it's okay. Otherwise, any mail you send to this user will be returned to you." ), else: if is_metadata_address(email): domain = parse_domain(email) subject = 'Metadata protection to {domain} is now ready'.format( domain=domain) body = 'Unless you disable metadata protection, all mail to {domain} will now have both metadata and content encrypted.'.format( domain=domain) else: subject = i18n( 'Mail to {email} is now private'.format(email=email)) body = i18n( "The content of all messages to {email} will be protected. " .format(email=email)) body_text = "{}\n\n{} {}\n".format(body, header, tip) for (user_id, fingerprint) in id_fingerprint_pairs: body_text += " {}: {}".format(user_id, format_fingerprint(fingerprint)) if regular_notice: prefix = TAG_PREFIX else: prefix = TAG_WARNING notify_user(to_user, '{} - {}'.format(prefix, str(subject)), body_text)