Error not catched when email not sent
I have set up an external SMTP email provider for sending confirmation emails for users upon registration (verify_emails set to True) and for some reasons, the provider detected the emails as spam. The emails were not sent, but in the current code, CTFd cannot detect anything and assumes the email has been sent.
The main reason is that the return value for the function responsible for sending the email is not checked.
The return value happens in my case in the sendmail email function here:
https://github.com/CTFd/CTFd/blob/master/CTFd/utils/email/providers/smtp.py#L65
class SMTPEmailProvider(EmailProvider):
@staticmethod
def sendmail(addr, text, subject):
try:
(...)
return True, "Email sent"
except smtplib.SMTPException as e:
return False, str(e)
except timeout:
return False, "SMTP server connection timed out"
except Exception as e:
return False, str(e) # <=== return here
and should be checked in auth.py here in the /register endpoint:
https://github.com/CTFd/CTFd/blob/master/CTFd/auth.py#L338
@auth.route("/register", methods=["POST", "GET"])
def register():
(...)
if request.method == "POST":
(...)
if len(errors) > 0:
(...)
else:
with app.app_context():
(...)
if config.can_send_mail() and get_config("verify_emails"):
(...)
email.verify_email_address(user.email) # <== Return value not checked here
Environment:
- CTFd Version/Commit: https://github.com/CTFd/CTFd/releases/tag/3.5.1
- Operating System: any
- Web Browser and Version: any
What happened? Nothing, email not sent with no error.
What did you expect to happen? An error stating that the email has not been sent, and a message saying why.
How to reproduce your issue Hard to reproduce as it depends from an error of an external provider.
Any associated stack traces or error logs
To produce the stack trace, I removed the try/except located here https://github.com/CTFd/CTFd/blob/master/CTFd/utils/email/providers/smtp.py#L38 to make the error apparent.
ERROR [CTFd] Exception on /register [POST]
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.9/site-packages/flask_restx/api.py", line 672, in error_router
return original_handler(e)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.9/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/opt/CTFd/CTFd/utils/decorators/visibility.py", line 119, in _check_registration_visibility
return f(*args, **kwargs)
File "/opt/CTFd/CTFd/utils/decorators/__init__.py", line 187, in ratelimit_function
return f(*args, **kwargs)
File "/opt/CTFd/CTFd/auth.py", line 358, in register
ret = email.verify_email_address(user.email)
File "/opt/CTFd/CTFd/utils/email/__init__.py", line 99, in verify_email_address
return sendmail(addr=addr, text=text, subject=subject)
File "/opt/CTFd/CTFd/utils/email/__init__.py", line 51, in sendmail
return EmailProvider.sendmail(addr, text, subject)
File "/opt/CTFd/CTFd/utils/email/providers/smtp.py", line 54, in sendmail
smtp.send_message(msg)
File "/usr/local/lib/python3.9/smtplib.py", line 986, in send_message
return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
File "/usr/local/lib/python3.9/smtplib.py", line 908, in sendmail
raise SMTPDataError(code, resp)
smtplib.SMTPDataError: (550, b'5.7.1 Reject for policy reason RULE3_2: Spam detected')
I think the code here is designed in a way so that we can capture the error and decide what to do with it. Some places in CTFd will surface the error and some places won't. I would accept a PR that made the error more noticeable for users if you can create one.