Errors when using SMTPLIB SSL email with a 365 email address - python

context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.office365.com", 587, context=context) as server:
(587) When I run this I get an SSL error: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1056).
(465) I get a timeout error.
I tried using ports 465 and 587. I get different errors when I use different ports. I did try 995 just for the heck of it and still no luck. If I use my gmail account, I have no issues.
Is there something I need to do to my email account so it works. I also tried .SMTP() and still no luck.
smtp = smtplib.SMTP("smtp.office365.com",587)
context = ssl.create_default_context()
with smtp.starttls(context=context) as server:
server.login(from_address, password)
for i, r in newhire[mask].iterrows():
server.sendmail(
from_address,
r["Email"],
message.format(Employee=r["Employee Name"],
StartDate=r["StartDate"],
PC=r["PC"],
Title=r["Title"],
Email=r["Email"],
)
)

From the documentation of SMTP_SSL:
SMTP_SSL should be used for situations where SSL is required from the beginning of the connection and using starttls() is not appropriate.
Thus, SMTP_SSL is for implicit SMTP and the common port for this is 465. Port 587 is instead used for explicit SMTP where a plain connect is done and later an upgrade to SSL with the STARTTLS command.
What happens here is that the client tries to speak SSL/TLS to a server which does not expect SSL/TLS at this stage and thus replies with non-TLS data. These get interpreted as TlS nonetheless which results in this strange [SSL: WRONG_VERSION_NUMBER].
To fix this either use port 465 (and not 587) with SMTP_SSL (not supported by Office365) or use port 587 but with starttls:
with smtplib.SMTP("smtp.office365.com", 587) as server:
server.starttls(context=context)

Related

Why am I getting this error: "TimeoutError: [Errno 110] Connection timed out" using python code in Pycharm?

import smtplib
my_email = "*******#gmail.com"
password = "***********"
with smtplib.SMTP("smtp.gmail.com") as connection:
connection.starttls()
connection.login(user=my_email, password=password)
connection.sendmail(from_addr=my_email, to_addrs="*******#yahoo.com",
msg="Subject:Hello\n\n This is the body of my email.")
In gmail the "less secure apps" feature was enabled. Conversely, in Yahoo, the "Generate App Password" feature was used and still got the same response for both instances.
There can be many reasons for this error - typically it means something has gone wrong in setting up the smtplib connection and your email hasn't set.
To get a more descriptive and helpful error message, you need to enable debugging.
To do that add, add this line of code:
connection.set_debuglevel(1)
That will print out much more descriptive debugger messages to the console.
try to set a port like 465 for gmail, smptlib.SMTP("smtp.gmail.com", 465)
if none of that work try to change your transfer security from starttls() to SMTP_SSL, so your code look like this:
import smtplib, ssl #import ssl module
port = 465 # For SSL
# Create a secure SSL context
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
server.login(my_email, password)
server.sendmail(from_addr=my_email, to_addrs=reciver_email, msg=message)

Python: I cannot send an email, whatever I try

So I tried to send E-Mails via Python from on of my E-Mail Accounts to another, and no matter what i try, i get different Error messages, and googling them won't help me. All suggested fixes don't work for me and im just exhausted. I tried it on GMail and on web.de, both doesnt work.
Error messages (depend on encrpyption (tls/ssl) or provider (web.de/gmail)):
Username and Password not accepted.
Transaction failed Unauthorized sender address.
SSL: WRONG_VERSION_NUMBER
My Code in a nutshell:
context = ssl.create_default_context()
with smtplib.SMTP_SSL('smtp.web.de', port, context=context) as server:
server.login(sender, password)
server.sendmail(sender, reciever, message)
Try to set the correct ssl.SSLContext for your server.
For example:
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
or
context = ssl.SSLContext(ssl.PROTOCOL_SSLv2)
https://docs.python.org/3.0/library/ssl.html
with smtplib.SMTP_SSL('smtp.web.de', port, context=context) as server:
It is unclear which port you are using but you like you the wrong one or you should not use smtplib.SMTP_SSL. SMTP_SSL is for connections where TLS is spoken from start (implicit TLS). This is true for port 465 (smtps).
smtp.web.de does not offer access with port 465 in the first place. This means that underlying TCP connection would already fail which would result in a different error.
Therefore it is likely that you've used port 25 (smtp) or port 587 (submission). Both of these ports require explicit TLS though, i.e. they start with a plain connection and upgrade to TLS only after an explicit STARTTLS command. For these ports the code should look something like this instead:
context = ssl.create_default_context()
port = 587
with smtplib.SMTP('smtp.web.de', port) as server:
server.starttls(context=context)
server.login(sender, password)
...
Connecting with implicit TLS to a port which does not use implicit TLS can result in very strange error messages since the TLS stack tries to interpret the response from the server as TLS even though it is not TLS (in this case the plain welcome message from the SMTP server, i.e. 220 ... Service ready). Thus the WRONG_VERSION_NUMBER you see does not actually indicate the wrong version of the response but only that it tried to interpret a specific part of the plain response as the TLS version number and got unexpected results from this.

Get a server certificate despite handshake failure in Python

I am writing a tool to monitor server certificate expiration. I'm using python3 ssl and socket modules to get the server cert using a pretty basic method of creating a default context, disabling hostname validation and certificate verification, calling SSLSocket.connect(), then SSLSocket.getpeercert(), with the sole purpose of grabbing the server certificate, and that is all.
This is all within a private network and I am not concerned with validation.
I have some devices that require client certs signed by a private CA (which my tool doesn't have), so the handshake fails on SSLSocket.connect(), making SSLSocket.getpeercert() impossible.
I know that the server certificate is indeed being provided to my client (along with that pesky Certificate Request) during the handshake. I can see it in a packet capture, as well as just using the openssl s_client command line.
Here is my code.
def get_cert(self, host, port):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
s.settimeout(10)
s.connect((host, port))
binary_cert = s.getpeercert(True)
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, binary_cert)
pem_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode()
return pem_cert
Is there any way to get a little lower into the handshake messages to get the server cert, even though the handshake ultimately fails?
My current solution is to just run openssl s_client -connect host:port using subprocess.run() in the event of a ssl.SSLError.
You may catch exception that do_handshake() produced and then continue to process server certificate.
import OpenSSL
import socket
dst = ('10.10.10.10', 443)
sock = socket.create_connection(dst)
context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
connection = OpenSSL.SSL.Connection(context, sock)
connection.set_connect_state()
try:
connection.do_handshake()
except:
print(connection.get_peer_cert_chain())
Tested on python 2.7.17 and 3.8.5
It looks like there's unfortunately no way to do it with python's ssl module in versions < 3.10. In those versions, the only way to get the peer certificate that I can see is through the low-level _ssl.SSLSocket.getpeercert() method and that immediately throws exception if the handshake is not complete.
Since python 3.10, there's a new _ssl.SSLSocket.get_unverified_chain() method that does not do the handshake check, so perhaps something like this abomination could work?
ssock = context.wrap_socket(sock, do_handshake_on_connect=False)
try:
ssock.do_handshake()
except ssl.SSLError as e:
pass
certs = ssock._sslobj._sslobj.get_unverified_chain()
... but I have not tested it.

Using SMTP_SSL on bluehost.com results in ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host

I recently registered a domain on bluehost.com. For now, I want to send simple 'reset password' emails. Using the "Standard (without SSL/TLS) Settings" works fine.
However, the bluehost documentation suggests using Secure SSL/TLS Settings, so I want to give that a try:
Username: Your email address: john#example.com
Password: The password for that email account.
Outgoing Server: mail.example.com
Outgoing Port: 465 (SMTP)
Here is my code (anonymized parameters):
import smtplib, ssl, os
# Params
port = 465
smtp_server = "mail.mydomain.com"
sender_email = "sender#mydomain.com"
receiver_email = "receiver#gmail.com"
password = os.environ.get("SMTP_PASSWORD")
# As documented in https://docs.python.org/3/library/smtplib.html
""" Deprecated since version 3.6: keyfile and certfile are deprecated
in favor of context. Please use ssl.SSLContext.load_cert_chain() instead,
or let ssl.create_default_context() select the system’s trusted
CA certificates for you. """
context = ssl.create_default_context()
# Create the connection and authenticate
with smtplib.SMTP_SSL(smtp_server, port, context=context) as s:
s.set_debuglevel(1)
s.login(sender_email, password)
s.quit()
The code fails on:
...Python37-32\lib\ssl.py", line 1117, in do_handshake
self._sslobj.do_handshake()
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
I contacted bluehost tech support, who clarified no certificates were missing or anything. Their answer was unsatisfying in the sense that they just said "SSL sometimes doesn't work. Reset your password and try again" (the latter didn't work).
I'm hoping I'm simply overlooking a mistake in my code. Can someone point me in the right direction? I've run out of Google.

Sending email from Python using STARTTLS

I want to send emails with a Python script by using Python's smtplib.
The script should only send the email, if an encrypted connection to the server can be established.
To encrypt the connection to port 587 I want to use STARTTLS.
Using some examples I have written the following code:
smtp_server = smtplib.SMTP(host, port=port)
context = ssl.create_default_context()
smtp_server.starttls(context)
smtp_server.login(user, password)
smtp_server.send_message(msg)
msg, host, port, user, password are variables in my script.
I have two questions:
Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).
Should I use the ehlo() method of the SMTP object? In some examples it is called explicitly before and after calling starttls(). On the other side in the documentation of smptlib it is written, that sendmail() will call it, if it is necessary.
[Edit]
#tintin explained, that ssl.create_default_context() can possibly lead to insecure connections. Thus I have changed the code using some examples in the following way:
_DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
'!eNULL:!MD5')
smtp_server = smtplib.SMTP(host, port=port)
# only TLSv1 or higher
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.set_ciphers(_DEFAULT_CIPHERS)
context.set_default_verify_paths()
context.verify_mode = ssl.CERT_REQUIRED
if smtp_server.starttls(context=context)[0] != 220:
return False # cancel if connection is not encrypted
smtp_server.login(user, password)
For the cipher setting I used some code of a recent version of ssl.create_default_context(). Are these settings appropriate?
Note: In the code of my original question is one mistake. Here is the correct version of the concerned line:
smtp_server.starttls(context=context)
[\Edit]
Is the connection always encrypted or is it vulnerable to the STRIPTLS attack (https://en.wikipedia.org/wiki/STARTTLS).
long story short: starttls can be stripped from smtplib <=py3.5.1rc1 <=py2.7.10 if you do not check response codes for .starttls()
explicitly calling .starttls() on smtp servers supporting it with a malicious MitM stripping your STARTTLS command and forging a non 220 response will NOT negotiate ssl, nor raise an exception and therefore leave your communication unencrypted - ergo it is vulnerable to striptls unless you manually verify that the response to .starttls()[0]==220 or the internal .sock got ssl wrapped.
Here's a python 2.7.9 smtplib communication with an example similar to yours that failed to negotiate starttls by having the server or a MitM reply 999 NOSTARTTLS instead of the 200. No explicit check for the 200 response code in the client script, no exception due to a failed starttls attempt therefore mail transport not encrypted:
220 xx ESMTP
250-xx
250-SIZE 20480000
250-AUTH LOGIN
250-STARTTLS
250 HELP
STARTTLS
999 NOSTARTTLS
mail FROM:<a#b.com> size=686
250 OK
rcpt TO:<a#b.com>
250 OK
data
explicitly calling .starttls() on smtp servers not supporting STARTTLS - or a MitM stripping this capability from the servers response - will raise SMTPNotSupportedError. see code below.
general note: encryption also depends on the configured cipherspec i.e. your SSLContext which in your case is created by ssl.create_default_context(). Note that it is totally valid to configure your SSLContext to allow cipherspecs that authenticate but do not encrypt (if offered/allowed by both server and client). E.g. TLS_RSA_WITH_NULL_SHA256.
NULL-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=None Mac=SHA256
According to this answer python pre 2.7.9/3.4.3 does NOT attempt to enforce certificate validation for the default ssl context and therefore is vulnerable to ssl interception. Starting with Python 2.7.9/3.4.3 certificate validation is enforced for the default context. This also means, that you'll have to manually enable certificate validation for pre 2.7.9/3.4.3 (by creating a custom sslcontext) otherwise any untrusted certificate might be accepted.
Should I use the ehlo() method of the SMTP object? In some examples it is called explicitly before and after calling starttls(). On the other side in the documentation of smptlib it is written, that sendmail() will call it, if it is necessary.
.sendmail(), .send_message and .starttls() will implicitly call .ehlo_or_helo_if_needed() therefore there is no need to explicitly call it again. This is also
see source::smtplib::starttls (cpython, inofficial github) below:
def starttls(self, keyfile=None, certfile=None, context=None):
"""Puts the connection to the SMTP server into TLS mode.
If there has been no previous EHLO or HELO command this session, this
method tries ESMTP EHLO first.
If the server supports TLS, this will encrypt the rest of the SMTP
session. If you provide the keyfile and certfile parameters,
the identity of the SMTP server and client can be checked. This,
however, depends on whether the socket module really checks the
certificates.
This method may raise the following exceptions:
SMTPHeloError The server didn't reply properly to
the helo greeting.
"""
self.ehlo_or_helo_if_needed()
if not self.has_extn("starttls"):
raise SMTPNotSupportedError(
"STARTTLS extension not supported by server.")
(resp, reply) = self.docmd("STARTTLS")
if resp == 220:
if not _have_ssl:
raise RuntimeError("No SSL support included in this Python")
if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
if context is None:
context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.sock = context.wrap_socket(self.sock,
server_hostname=self._host)
self.file = None
# RFC 3207:
# The client MUST discard any knowledge obtained from
# the server, such as the list of SMTP service extensions,
# which was not obtained from the TLS negotiation itself.
self.helo_resp = None
self.ehlo_resp = None
self.esmtp_features = {}
self.does_esmtp = 0
return (resp, reply)

Categories

Resources