I started out by not really being that familiar with Python, but I will always contend that knowing how to program is independent of the choice of language, so I wasn’t worried.
Fortunately (?), the very old version of Python on my shared hosting meant that I didn’t need to concern myself with the Python 2 vs Python 3 debate1, or which library to choose for some functions (no choice) so I could just get on with it.
Since I wanted to make this a general-purpose tool, I spent some extra time getting SSL on ports 465 and 587 working as well as non-encrypted on port 25. My laptop has the latest version of Python 2.7, so I could test these even if I couldn’t use them on TSOHost.
Here is the script:
#!/usr/bin/env python # # Simple smtp mailer program. Designed to work on old versions of Python (2.4). # Has lots of command line parameters, but intended to run from shell scripts # so doesn't matter. Tested on ports 25, 465 (encrypted) and 587 (encrypted). # Only supports plain text auth via username & password. # The encrypted modes require 2.6 or later as smtplib.SMTP_SSL is not present # in earlier versions. # import smtplib import datetime, sys, getopt, re #import email.utils not on tsohost #from email.mime.text import MIMEText not on tsohost #SMTP_SSL not in tohost so port 465 and 587 won't work port = '' host = '' mfrom = '' mto = '' msubj = '' username = '' password = '' force_encrypt = False quiet = False def usage(progName = ''): print("Usage: %s -p port -o host -f mailFrom -t mailTo -s subject [opts] 'messageBody'" \ % progName) print("Other options:") print("-u username -w password : Where login is required") print("-e : Force encryption - prevent plaintext username/password if not encypted") print("-q : Quiet") print("-h : Help") print("messageBody will be taken from stdin if parameter not present") #get command line options try: myopts, args = getopt.getopt(sys.argv[1:],"p:o:f:t:s:u:w:eqh") except getopt.error,e: print(str(e)) usage(sys.argv) sys.exit(2) for o, a in myopts: if o == '-p': port = a elif o == '-o': host = a elif o == '-f': mfrom = a elif o == '-t': mto = a elif o == '-s': msubj = a elif o == '-u': username = a elif o == '-w': password = a elif o == '-e': force_encrypt = True elif o == '-q': quiet = True elif o == '-h': usage() sys.exit() #check for minimal required args if(port == '' or host == '' or mfrom == '' or mto == '' or msubj == ''): print("Missing args:") usage(sys.argv) sys.exit(2) #read from stdin for message body if no further args if(len(args) == 0): mbody = sys.stdin.read() else: mbody = args #generate correct mail date format date = datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S%z") #generate message header and body msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" \ % (mfrom, mto, msubj, date, mbody) #set to 1 for lots of debug messages debuglevel = 0 #indicate encrypted encrypted = False try: if(port == '465'): smtp = smtplib.SMTP_SSL(host, port) encrypted = True else: smtp = smtplib.SMTP(host, port) smtp.set_debuglevel(debuglevel) smtp.ehlo() #print(smtp.ehlo_resp) if(re.search(r'^STARTTLS\b', smtp.ehlo_resp, re.M | re.I)): #print("STARTTLS accepted") smtp.starttls() smtp.ehlo() encrypted = True #print(smtp.ehlo_resp) #see if login required #(order of login & plain not specified and there can be other options) if(re.search(r'^AUTH\b.*(?:LOGIN\b.*PLAIN\b|PLAIN\b.*LOGIN\b)', \ smtp.ehlo_resp, re.M | re.I)): #print("Plain login accepted") if(force_encrypt and not encrypted): print("Error: Plain text login over non-encrypted connection not allowed.") smtp.quit() sys.exit(2) else: smtp.login(username, password) #send the mail! smtp.sendmail(mfrom, mto, msg) smtp.quit() if(not quiet): print("Mail sent successfully.") except Exception, e: print("Error: unable to send mail.") print(str(e)) sys.exit(2) #end
That’s all there is to it. The response from smtp.ehlo() is tested to see if STARTTLS is supported; if it is then encryption is kicked in before doing a plain text login provided AUTH PLAIN LOGIN is supported. If the port is 465 then the sequence is started in SSL mode instead by using smtplib.SMTP_SSL.
Usage looks like:
./send-smtp-mail.py -p 25 -o smtp.mail.host \ -f "A User <firstname.lastname@example.org>" \ -t "email@example.com" \ -s "Py Test Mail" \ "Test Message Here..."
Pretty simple really and I was pleased with how well the result worked. Python didn’t take much brainpower to get the basics going, at least at the noddy level needed to make something like this work.
It helped to be familiar with the workings of mailservers though. This is the legacy of getting my own home mailserver working using Dovecot & Postfix and having to get relaying through a port 465 tunnel to Virgin Media’s servers working when Postfix doesn’t support port 4652.
- Ever heard of backwards-compatibilty? Just sayin’…
- Mailservers on non-fixed IP addresses are routinely blacklisted, so relaying outgoing mail via an ISP or other mailhost is pretty much a requirement. Running your own mailserver is not very sensible these days, but I’ve been doing it since 2004 so it’s a habit.