def get_missed_message_token_from_address(address: str) -> str: msg_string = get_email_gateway_message_string_from_address(address) if not is_mm_32_format(msg_string): raise ZulipEmailForwardError("Could not parse missed message address") return msg_string
def find_emailgateway_recipient(message: EmailMessage) -> str: # We can't use Delivered-To; if there is a X-Gm-Original-To # it is more accurate, so try to find the most-accurate # recipient list in descending priority order recipient_headers = [ "X-Gm-Original-To", "Delivered-To", "Envelope-To", "Resent-To", "Resent-CC", "To", "CC", ] pattern_parts = [ re.escape(part) for part in settings.EMAIL_GATEWAY_PATTERN.split("%s") ] match_email_re = re.compile(".*?".join(pattern_parts)) for header_name in recipient_headers: for header_value in message.get_all(header_name, []): if isinstance(header_value, AddressHeader): emails = [addr.addr_spec for addr in header_value.addresses] else: emails = [str(header_value)] for email in emails: if match_email_re.match(email): return email raise ZulipEmailForwardError("Missing recipient in mirror email")
def get_usable_missed_message_address(address: str) -> MissedMessageEmailAddress: token = get_missed_message_token_from_address(address) try: mm_address = MissedMessageEmailAddress.objects.select_related().get( email_token=token, timestamp__gt=timezone_now() - timedelta(seconds=MissedMessageEmailAddress.EXPIRY_SECONDS) ) except MissedMessageEmailAddress.DoesNotExist: raise ZulipEmailForwardError("Missed message address expired or doesn't exist.") if not mm_address.is_usable(): # Technical, this also checks whether the event is expired, # but that case is excluded by the logic above. raise ZulipEmailForwardError("Missed message address out of uses.") return mm_address
def mirror_email_message(data: Dict[str, str]) -> Dict[str, str]: rcpt_to = data['recipient'] if is_missed_message_address(rcpt_to): try: mm_address = get_missed_message_address(rcpt_to) if not mm_address.is_usable(): raise ZulipEmailForwardError("Missed message address out of uses.") except ZulipEmailForwardError: return { "status": "error", "msg": "5.1.1 Bad destination mailbox address: " "Bad or expired missed message address." } else: try: extract_and_validate(rcpt_to) except ZulipEmailForwardError: return { "status": "error", "msg": "5.1.1 Bad destination mailbox address: " "Please use the address specified in your Streams page." } queue_json_publish( "email_mirror", { "message": data['msg_text'], "rcpt_to": rcpt_to } ) return {"status": "success"}
def get_missed_message_token_from_address(address: str) -> str: msg_string = get_email_gateway_message_string_from_address(address) if not is_mm_32_format(msg_string): raise ZulipEmailForwardError('Could not parse missed message address') # strip off the 'mm' before returning the redis key return msg_string[2:]
def extract_and_validate(email: str) -> Tuple[Stream, Dict[str, bool]]: token, options = decode_email_address(email) try: stream = Stream.objects.get(email_token=token) except Stream.DoesNotExist: raise ZulipEmailForwardError("Bad stream token from email recipient " + email) return stream, options
def get_missed_message_address(address: str) -> MissedMessageEmailAddress: token = get_missed_message_token_from_address(address) try: return MissedMessageEmailAddress.objects.select_related().get( email_token=token, timestamp__gt=timezone_now() - timedelta(seconds=MissedMessageEmailAddress.EXPIRY_SECONDS) ) except MissedMessageEmailAddress.DoesNotExist: raise ZulipEmailForwardError("Missed message address expired or doesn't exist")
def mark_missed_message_address_as_used(address: str) -> None: token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) with redis_client.pipeline() as pipeline: pipeline.hincrby(key, 'uses_left', -1) pipeline.expire(key, 60 * 60 * 24 * 5) new_value = pipeline.execute()[0] if new_value < 0: redis_client.delete(key) raise ZulipEmailForwardError('Missed message address has already been used')
def process_missed_message(to: str, message: message.Message) -> None: mm_address = get_missed_message_address(to) if not mm_address.is_usable(): raise ZulipEmailForwardError("Missed message address out of uses.") mm_address.increment_times_used() user_profile = mm_address.user_profile topic = mm_address.message.topic_name() if mm_address.message.recipient.type == Recipient.PERSONAL: # We need to reply to the sender so look up their personal recipient_id recipient = mm_address.message.sender.recipient else: recipient = mm_address.message.recipient if not is_user_active(user_profile): logger.warning("Sending user is not active. Ignoring this missed message email.") return body = construct_zulip_body(message, user_profile.realm) if recipient.type == Recipient.STREAM: stream = get_stream_by_id_in_realm(recipient.type_id, user_profile.realm) internal_send_stream_message( user_profile.realm, user_profile, stream, topic, body ) recipient_str = stream.name elif recipient.type == Recipient.PERSONAL: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) recipient_str = display_recipient[0]['email'] recipient_user = get_user(recipient_str, user_profile.realm) internal_send_private_message(user_profile.realm, user_profile, recipient_user, body) elif recipient.type == Recipient.HUDDLE: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) emails = [user_dict['email'] for user_dict in display_recipient] recipient_str = ', '.join(emails) internal_send_huddle_message(user_profile.realm, user_profile, emails, body) else: raise AssertionError("Invalid recipient type!") logger.info("Successfully processed email from user %s to %s" % ( user_profile.id, recipient_str))
def send_to_missed_message_address(address: str, message: message.Message) -> None: token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject') if not all(val is not None for val in result): raise ZulipEmailForwardError('Missing missed message address data') user_profile_id, recipient_id, subject_b = result # type: (bytes, bytes, bytes) user_profile = get_user_profile_by_id(user_profile_id) if not is_user_active(user_profile): logger.warning( "Sending user is not active. Ignoring this missed message email.") return recipient = Recipient.objects.get(id=recipient_id) body = construct_zulip_body(message, user_profile.realm) if recipient.type == Recipient.STREAM: stream = get_stream_by_id_in_realm(recipient.type_id, user_profile.realm) internal_send_stream_message(user_profile.realm, user_profile, stream, subject_b.decode('utf-8'), body) recipient_str = stream.name elif recipient.type == Recipient.PERSONAL: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) recipient_str = display_recipient[0]['email'] recipient_user = get_user(recipient_str, user_profile.realm) internal_send_private_message(user_profile.realm, user_profile, recipient_user, body) elif recipient.type == Recipient.HUDDLE: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) emails = [user_dict['email'] for user_dict in display_recipient] recipient_str = ', '.join(emails) internal_send_huddle_message(user_profile.realm, user_profile, emails, body) else: raise AssertionError("Invalid recipient type!") logger.info("Successfully processed email from user %s to %s" % (user_profile.id, recipient_str))
def find_emailgateway_recipient(message: message.Message) -> str: # We can't use Delivered-To; if there is a X-Gm-Original-To # it is more accurate, so try to find the most-accurate # recipient list in descending priority order recipient_headers = ["X-Gm-Original-To", "Delivered-To", "Resent-To", "Resent-CC", "To", "CC"] pattern_parts = [re.escape(part) for part in settings.EMAIL_GATEWAY_PATTERN.split('%s')] match_email_re = re.compile(".*?".join(pattern_parts)) header_addresses = [str(addr) for recipient_header in recipient_headers for addr in message.get_all(recipient_header, [])] for addr_tuple in getaddresses(header_addresses): if match_email_re.match(addr_tuple[1]): return addr_tuple[1] raise ZulipEmailForwardError("Missing recipient in mirror email")