def try_auto_create_catch_all_domain(address: str) -> Optional[Alias]: """Try to create an alias with catch-all domain""" # try to create alias on-the-fly with custom-domain catch-all feature # check if alias is custom-domain alias and if the custom-domain has catch-all enabled alias_domain = get_email_domain_part(address) custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain) if not custom_domain: return None # custom_domain exists if not custom_domain.catch_all: return None # custom_domain has catch-all enabled domain_user: User = custom_domain.user if not domain_user.can_create_new_alias(): send_cannot_create_domain_alias(domain_user, address, alias_domain) return None try: LOG.d("create alias %s for domain %s", address, custom_domain) mailboxes = custom_domain.mailboxes alias = Alias.create( email=address, user_id=custom_domain.user_id, custom_domain_id=custom_domain.id, automatic_creation=True, mailbox_id=mailboxes[0].id, ) db.session.flush() for i in range(1, len(mailboxes)): AliasMailbox.create( alias_id=alias.id, mailbox_id=mailboxes[i].id, ) db.session.commit() return alias except AliasInTrashError: LOG.warning( "Alias %s was deleted before, cannot auto-create using domain catch-all %s, user %s", address, custom_domain, domain_user, ) return None except IntegrityError: LOG.warning("Alias %s already exists", address) db.session.rollback() alias = Alias.get_by(email=address) return alias except DataError: LOG.warning("Cannot create alias %s", address) db.session.rollback() return None
def try_auto_create_catch_all_domain(address: str) -> Optional[Alias]: """Try to create an alias with catch-all domain""" # try to create alias on-the-fly with custom-domain catch-all feature # check if alias is custom-domain alias and if the custom-domain has catch-all enabled alias_domain = get_email_domain_part(address) custom_domain = CustomDomain.get_by(domain=alias_domain) if not custom_domain: return None # custom_domain exists if not custom_domain.catch_all: return None # custom_domain has catch-all enabled domain_user: User = custom_domain.user if not domain_user.can_create_new_alias(): send_cannot_create_domain_alias(domain_user, address, alias_domain) return None # if alias has been deleted before, do not auto-create it if DeletedAlias.get_by(email=address): LOG.warning( "Alias %s was deleted before, cannot auto-create using domain catch-all %s, user %s", address, custom_domain, domain_user, ) return None LOG.d("create alias %s for domain %s", address, custom_domain) alias = Alias.create( email=address, user_id=custom_domain.user_id, custom_domain_id=custom_domain.id, automatic_creation=True, mailbox_id=domain_user.default_mailbox_id, ) db.session.commit() return alias
def handle_forward(self, envelope, smtp: SMTP, msg: Message) -> str: """return *status_code message*""" alias = envelope.rcpt_tos[0].lower() # alias@SL gen_email = GenEmail.get_by(email=alias) if not gen_email: LOG.d( "alias %s not exist. Try to see if it can be created on the fly", alias) # try to see if alias could be created on-the-fly on_the_fly = False # check if alias belongs to a directory, ie having directory/anything@EMAIL_DOMAIN format if email_belongs_to_alias_domains(alias): if "/" in alias or "+" in alias or "#" in alias: if "/" in alias: sep = "/" elif "+" in alias: sep = "+" else: sep = "#" directory_name = alias[:alias.find(sep)] LOG.d("directory_name %s", directory_name) directory = Directory.get_by(name=directory_name) # Only premium user can use the directory feature if directory: dir_user = directory.user if dir_user.is_premium(): LOG.d("create alias %s for directory %s", alias, directory) on_the_fly = True gen_email = GenEmail.create( email=alias, user_id=directory.user_id, directory_id=directory.id, ) db.session.commit() else: LOG.error( "User %s is not premium anymore and cannot create alias with directory", dir_user, ) send_cannot_create_directory_alias( dir_user, alias, directory_name) # try to create alias on-the-fly with custom-domain catch-all feature # check if alias is custom-domain alias and if the custom-domain has catch-all enabled if not on_the_fly: alias_domain = get_email_domain_part(alias) custom_domain = CustomDomain.get_by(domain=alias_domain) # Only premium user can continue using the catch-all feature if custom_domain and custom_domain.catch_all: domain_user = custom_domain.user if domain_user.is_premium(): LOG.d("create alias %s for domain %s", alias, custom_domain) on_the_fly = True gen_email = GenEmail.create( email=alias, user_id=custom_domain.user_id, custom_domain_id=custom_domain.id, automatic_creation=True, ) db.session.commit() else: LOG.error( "User %s is not premium anymore and cannot create alias with domain %s", domain_user, alias_domain, ) send_cannot_create_domain_alias( domain_user, alias, alias_domain) if not on_the_fly: LOG.d("alias %s cannot be created on-the-fly, return 510", alias) return "510 Email not exist" user_email = gen_email.user.email website_email = get_email_part(msg["From"]) forward_email = ForwardEmail.get_by(gen_email_id=gen_email.id, website_email=website_email) if not forward_email: LOG.debug( "create forward email for alias %s and website email %s", alias, website_email, ) # generate a reply_email, make sure it is unique # not use while to avoid infinite loop for _ in range(1000): reply_email = f"reply+{random_string(30)}@{EMAIL_DOMAIN}" if not ForwardEmail.get_by(reply_email=reply_email): break forward_email = ForwardEmail.create( gen_email_id=gen_email.id, website_email=website_email, website_from=msg["From"], reply_email=reply_email, ) db.session.commit() forward_log = ForwardEmailLog.create(forward_id=forward_email.id) if gen_email.enabled: # add custom header add_or_replace_header(msg, "X-SimpleLogin-Type", "Forward") # remove reply-to header if present delete_header(msg, "Reply-To") # change the from header so the sender comes from @SL # so it can pass DMARC check # replace the email part in from: header from_header = (get_email_name(msg["From"]) + " - " + website_email.replace("@", " at ") + f" <{forward_email.reply_email}>") msg.replace_header("From", from_header) LOG.d("new from header:%s", from_header) # add List-Unsubscribe header unsubscribe_link = f"{URL}/dashboard/unsubscribe/{gen_email.id}" add_or_replace_header(msg, "List-Unsubscribe", f"<{unsubscribe_link}>") add_or_replace_header(msg, "List-Unsubscribe-Post", "List-Unsubscribe=One-Click") add_dkim_signature(msg, EMAIL_DOMAIN) LOG.d( "Forward mail from %s to %s, mail_options %s, rcpt_options %s ", website_email, user_email, envelope.mail_options, envelope.rcpt_options, ) # smtp.send_message has UnicodeEncodeErroremail issue # encode message raw directly instead msg_raw = msg.as_string().encode() smtp.sendmail( forward_email.reply_email, user_email, msg_raw, envelope.mail_options, envelope.rcpt_options, ) else: LOG.d("%s is disabled, do not forward", gen_email) forward_log.blocked = True db.session.commit() return "250 Message accepted for delivery"
def try_auto_create_via_domain(address: str) -> Optional[Alias]: """Try to create an alias with catch-all or auto-create rules on custom domain""" # try to create alias on-the-fly with custom-domain catch-all feature # check if alias is custom-domain alias and if the custom-domain has catch-all enabled alias_domain = get_email_domain_part(address) custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain) if not custom_domain: return None if not custom_domain.catch_all and len( custom_domain.auto_create_rules) == 0: return None elif not custom_domain.catch_all and len( custom_domain.auto_create_rules) > 0: local = get_email_local_part(address) for rule in custom_domain.auto_create_rules: if regex_match(rule.regex, local): LOG.d( "%s passes %s on %s", address, rule.regex, custom_domain, ) alias_note = f"Created by rule {rule.order} with regex {rule.regex}" mailboxes = rule.mailboxes break else: # no rule passes LOG.d("no rule passed to create %s", local) return else: # catch-all is enabled mailboxes = custom_domain.mailboxes alias_note = "Created by catch-all option" domain_user: User = custom_domain.user if not domain_user.can_create_new_alias(): send_cannot_create_domain_alias(domain_user, address, alias_domain) return None # a rule can have 0 mailboxes. Happened when a mailbox is deleted if not mailboxes: LOG.d("use %s default mailbox for %s %s", domain_user, address, custom_domain) mailboxes = [domain_user.default_mailbox] try: LOG.d("create alias %s for domain %s", address, custom_domain) alias = Alias.create( email=address, user_id=custom_domain.user_id, custom_domain_id=custom_domain.id, automatic_creation=True, mailbox_id=mailboxes[0].id, ) if not custom_domain.user.disable_automatic_alias_note: alias.note = alias_note Session.flush() for i in range(1, len(mailboxes)): AliasMailbox.create( alias_id=alias.id, mailbox_id=mailboxes[i].id, ) Session.commit() return alias except AliasInTrashError: LOG.w( "Alias %s was deleted before, cannot auto-create using domain catch-all %s, user %s", address, custom_domain, domain_user, ) return None except IntegrityError: LOG.w("Alias %s already exists", address) Session.rollback() alias = Alias.get_by(email=address) return alias except DataError: LOG.w("Cannot create alias %s", address) Session.rollback() return None