From 6c74a71455efddf4ca3c8e744b8d12a61ede7b4e Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Thu, 27 Aug 2015 12:39:30 -0700 Subject: [PATCH 1/7] Whitespace fixes Switched the mixed indentation to spaces with a tabstop of 4, as per PEP 0008. Also aligned line continuations better in some places. --- putmail.py | 482 ++++++++++++++++++++++----------------------- putmail_dequeue.py | 60 +++--- putmail_enqueue.py | 48 ++--- 3 files changed, 295 insertions(+), 295 deletions(-) diff --git a/putmail.py b/putmail.py index 8e6076a..abead3d 100755 --- a/putmail.py +++ b/putmail.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# putmail.py Send mail read from standard input. +# putmail.py Send mail read from standard input. # # Copyright 2007 Ricardo Garcia Gonzalez: http://sourceforge.net/users/rg3/ # @@ -99,53 +99,53 @@ ### Some key variables in the program ### -theEMailAddress = None # Envelope From address -theSMTPServer = None # SMTP server to use -thePort = None # The SMTP port in the server -theMessage = None # The E-Mail message -theRecipients = [] # The recipients of the E-Mail message -theConfigFilename = None # The configuration file name -theTLSFlag = False # Use TLS or not -theAuthenticateFlag = False # Use SMTP authentication or not -theSMTPLogin = None # The login name to use with the server -theSMTPPassword = None # The corresponding password -theRcpsFromMailFlag = False # Take recipients from message or not -theQuietFlag = False # Supress program output or not -theLogFile = None # The log file name +theEMailAddress = None # Envelope From address +theSMTPServer = None # SMTP server to use +thePort = None # The SMTP port in the server +theMessage = None # The E-Mail message +theRecipients = [] # The recipients of the E-Mail message +theConfigFilename = None # The configuration file name +theTLSFlag = False # Use TLS or not +theAuthenticateFlag = False # Use SMTP authentication or not +theSMTPLogin = None # The login name to use with the server +theSMTPPassword = None # The corresponding password +theRcpsFromMailFlag = False # Take recipients from message or not +theQuietFlag = False # Supress program output or not +theLogFile = None # The log file name ### A few auxiliary functions ### def handle_error(str, stderr_output, exit_program): - fullstr = str + "\n" - try: - file(theLogFile, "a").write("%s: %s" % - (datetime.datetime.ctime(datetime.datetime.now()), fullstr)) - except (IOError, OSError): - sys.stderr.write(WARNING_LOGFILE + "\n") - - if stderr_output: - sys.stderr.write(fullstr) - if exit_program: - sys.exit(EXIT_FAILURE) + fullstr = str + "\n" + try: + file(theLogFile, "a").write("%s: %s" % ( + datetime.datetime.ctime(datetime.datetime.now()), fullstr)) + except (IOError, OSError): + sys.stderr.write(WARNING_LOGFILE + "\n") + + if stderr_output: + sys.stderr.write(fullstr) + if exit_program: + sys.exit(EXIT_FAILURE) def exit_forcing_print(str): - handle_error(str, True, True) + handle_error(str, True, True) def exit_conditional_print(str): - handle_error(str, not theQuietFlag, True) + handle_error(str, not theQuietFlag, True) def conditional_print(str): - handle_error(str, not theQuietFlag, False) + handle_error(str, not theQuietFlag, False) def force_print(str): - handle_error(str, True, False) + handle_error(str, True, False) def check_status((code, message)): - if code >= FIRST_ERROR_CODE: - exit_conditional_print(ERROR_OTHER % (code, message)) + if code >= FIRST_ERROR_CODE: + exit_conditional_print(ERROR_OTHER % (code, message)) def keychain(keychainType): - if keychainType == 'osx': - return osxkeychain + if keychainType == 'osx': + return osxkeychain def osxkeychain(service, type="internet"): cmd = """/usr/bin/security find-%s-password -gs %s""" % (type, service) @@ -158,9 +158,9 @@ def osxkeychain(service, type="internet"): ### Check for HOME present (needed later, checking now saves a lot of work) ### if not os.environ.has_key(HOME_EV): - # Note: I still can't use exit_forcing_print() at this point, the log - # filename is not set. - sys.exit(ERROR_HOME_UNSET + "\n") + # Note: I still can't use exit_forcing_print() at this point, the log + # filename is not set. + sys.exit(ERROR_HOME_UNSET + "\n") ### Build the log filename now ### theLogFile = os.path.join(os.environ[HOME_EV], CONFIG_DIRECTORY, LOG_FILE) @@ -169,305 +169,305 @@ def osxkeychain(service, type="internet"): # First step: Parse the command line options. # ############################################### -### This is the new manual option parser to fully comply with ### -### the braindead traditional sendmail options format >:( ### +### This is the new manual option parser to fully comply with ### +### the braindead traditional sendmail options format >:( ### try: - program_args = sys.argv[1:] # Program arguments - print_info = False # prints program information - direct_exit = False # Exit directly? - - ### The lists below contain IGNORED OPTIONS ONLY, see the loop. ### - ### Option groups ending with _exit make the program exit ### - ### directly at the end of the parsing loop ### - - single_options = ['-Ac', '-Am', '-ba', '-bm', '-bs', '-bt', - '-bv', '-G', '-i', '-n', '-v'] - - single_options_exit = ['-bd', '-bD', '-bh', '-bH', '-bp', '-bP', '-qf', - '-bi' ] - - one_argument_options = [ '-B', '-C', '-D', '-d', '-F', '-h', '-L', '-N', - '-O', '-p', '-R', '-r', '-V', '-X' ] - - one_argument_options_exit = ['-qG', '-qI', '-qQ', '-qR', '-qS', '-q!I', - '-q!Q', '-q!R', '-q!S' ] - - optional_extra_chars_options_exit = [ '-q', '-qp', '-Q' ] - - # Eat arguments until there's none left - while (len(program_args) > 0): - if program_args[0] == '--': # Remaining options are recipients - theRecipients.extend(program_args[1:]) - break - elif program_args[0] == '--version': - print_info = True - direct_exit = True - break - elif program_args[0] == '-t': # Take recipients from message - theRcpsFromMailFlag = True - del program_args[0] - elif program_args[0] in single_options: - del program_args[0] - elif program_args[0] in single_options_exit: - del program_args[0] - direct_exit = True - elif program_args[0] == '-f': # Address in next argument - theEMailAddress = program_args[1] - del program_args[0] - del program_args[0] - elif program_args[0][0:2] == '-f': # Address in this argument - theEMailAddress = program_args[0][2:] - del program_args[0] - elif program_args[0] in one_argument_options: - del program_args[0] - del program_args[0] - elif program_args[0][0:2] in one_argument_options: - del program_args[0] - elif program_args[0] in one_argument_options_exit: - del program_args[0] - del program_args[0] - direct_exit = True - elif (sum([program_args[0].startswith(x) # First chars match - for x in one_argument_options_exit]) > 0): - del program_args[0] - direct_exit = True - elif (sum([program_args[0].startswith(x) # First chars match - for x in optional_extra_chars_options_exit]) > 0): - del program_args[0] - direct_exit = True - elif program_args[0].startswith('-o'): # Weird case - if program_args[0] == '-o': - exit_forcing_print(ERROR_O_OPTION) - del program_args[0] - del program_args[0] - else: - if program_args[0].startswith('-'): - exit_forcing_print(ERROR_UNKNOWN_OPTION % - program_args[0]) - theRecipients.append(program_args[0]) - del program_args[0] - # End of parsing loop + program_args = sys.argv[1:] # Program arguments + print_info = False # prints program information + direct_exit = False # Exit directly? + + ### The lists below contain IGNORED OPTIONS ONLY, see the loop. ### + ### Option groups ending with _exit make the program exit ### + ### directly at the end of the parsing loop ### + + single_options = ['-Ac', '-Am', '-ba', '-bm', '-bs', '-bt', + '-bv', '-G', '-i', '-n', '-v'] + + single_options_exit = ['-bd', '-bD', '-bh', '-bH', '-bp', '-bP', '-qf', + '-bi' ] + + one_argument_options = [ '-B', '-C', '-D', '-d', '-F', '-h', '-L', '-N', + '-O', '-p', '-R', '-r', '-V', '-X' ] + + one_argument_options_exit = ['-qG', '-qI', '-qQ', '-qR', '-qS', '-q!I', + '-q!Q', '-q!R', '-q!S' ] + + optional_extra_chars_options_exit = [ '-q', '-qp', '-Q' ] + + # Eat arguments until there's none left + while (len(program_args) > 0): + if program_args[0] == '--': # Remaining options are recipients + theRecipients.extend(program_args[1:]) + break + elif program_args[0] == '--version': + print_info = True + direct_exit = True + break + elif program_args[0] == '-t': # Take recipients from message + theRcpsFromMailFlag = True + del program_args[0] + elif program_args[0] in single_options: + del program_args[0] + elif program_args[0] in single_options_exit: + del program_args[0] + direct_exit = True + elif program_args[0] == '-f': # Address in next argument + theEMailAddress = program_args[1] + del program_args[0] + del program_args[0] + elif program_args[0][0:2] == '-f': # Address in this argument + theEMailAddress = program_args[0][2:] + del program_args[0] + elif program_args[0] in one_argument_options: + del program_args[0] + del program_args[0] + elif program_args[0][0:2] in one_argument_options: + del program_args[0] + elif program_args[0] in one_argument_options_exit: + del program_args[0] + del program_args[0] + direct_exit = True + elif (sum([program_args[0].startswith(x) # First chars match + for x in one_argument_options_exit]) > 0): + del program_args[0] + direct_exit = True + elif (sum([program_args[0].startswith(x) # First chars match + for x in optional_extra_chars_options_exit]) > 0): + del program_args[0] + direct_exit = True + elif program_args[0].startswith('-o'): # Weird case + if program_args[0] == '-o': + exit_forcing_print(ERROR_O_OPTION) + del program_args[0] + del program_args[0] + else: + if program_args[0].startswith('-'): + exit_forcing_print(ERROR_UNKNOWN_OPTION % + program_args[0]) + theRecipients.append(program_args[0]) + del program_args[0] + # End of parsing loop # Problem in some option argument except IndexError: - exit_forcing_print(ERROR_OPTION_ARGS) + exit_forcing_print(ERROR_OPTION_ARGS) # print program info if print_info: - programName = os.path.basename(sys.argv[0]) - version = "%s %s" % (programName, __version__) - print version - print " type `man %s` for more information" % programName + programName = os.path.basename(sys.argv[0]) + version = "%s %s" % (programName, __version__) + print version + print " type `man %s` for more information" % programName # Options indicated direct exit if direct_exit: - sys.exit() + sys.exit() # No addresses found? if len(theRecipients) == 0 and not theRcpsFromMailFlag: - exit_forcing_print(ERROR_NO_RECIPIENTS) + exit_forcing_print(ERROR_NO_RECIPIENTS) ###################################################### # Second step: Read the message from standard input. # ###################################################### try: - theMessage = email.message_from_file(sys.stdin) + theMessage = email.message_from_file(sys.stdin) except email.Errors.MessageError: - exit_forcing_print(ERROR_READ_MAIL) + exit_forcing_print(ERROR_READ_MAIL) ############################################ # Third step: Read the configuration file. # ############################################ -### Try to find the apropiate configuration file or ### -### fall back to CONFIG_NAME. ### +### Try to find the apropiate configuration file or ### +### fall back to CONFIG_NAME. ### configPath = os.path.join(os.environ[HOME_EV], CONFIG_DIRECTORY) # temporally theConfigFilename = CONFIG_NAME if theMessage.has_key(FROM_HEADER): - try: - fromaddr = email.Utils.getaddresses( - theMessage.get_all(FROM_HEADER))[-1][1] - tmpcfgpath = os.path.join(configPath, fromaddr) - if os.path.isfile(tmpcfgpath): - if os.access(tmpcfgpath, os.R_OK): - theConfigFilename = fromaddr - else: - force_print(WARNING_CONFIG_UNREADABLE %fromaddr) - - except IndexError: - pass + try: + fromaddr = email.Utils.getaddresses( + theMessage.get_all(FROM_HEADER))[-1][1] + tmpcfgpath = os.path.join(configPath, fromaddr) + if os.path.isfile(tmpcfgpath): + if os.access(tmpcfgpath, os.R_OK): + theConfigFilename = fromaddr + else: + force_print(WARNING_CONFIG_UNREADABLE %fromaddr) + + except IndexError: + pass configPath = os.path.join(configPath, theConfigFilename) # finally if not os.path.exists(configPath): - # Config file not present, try to create one and exit - force_print(ERROR_CONFIG_NONEXISTANT % configPath) - force_print(WARNING_SAMPLE_CONFIG) + # Config file not present, try to create one and exit + force_print(ERROR_CONFIG_NONEXISTANT % configPath) + force_print(WARNING_SAMPLE_CONFIG) - try: - dirname = os.path.dirname(configPath) - if not os.path.isdir(dirname): - os.makedirs(dirname) - file(configPath, "w").write(DEFAULT_CONFIG) - except: - exit_forcing_print(ERROR_CONFIG_CREATE) + try: + dirname = os.path.dirname(configPath) + if not os.path.isdir(dirname): + os.makedirs(dirname) + file(configPath, "w").write(DEFAULT_CONFIG) + except: + exit_forcing_print(ERROR_CONFIG_CREATE) - sys.exit(EXIT_FAILURE) + sys.exit(EXIT_FAILURE) # Last check. If we cannot read this we cannot proceed. if not os.access(configPath, os.R_OK): - exit_forcing_print(ERROR_CONFIG_UNREADABLE) + exit_forcing_print(ERROR_CONFIG_UNREADABLE) ### Read the file ### config = ConfigParser.ConfigParser() try: - config.read([configPath]) + config.read([configPath]) except: - exit_forcing_print(ERROR_CONFIG_PARSE) + exit_forcing_print(ERROR_CONFIG_PARSE) ### Check conditions for bad configurations ### if (not config.has_section(CONFIG_SECTION) or - not config.has_option(CONFIG_SECTION, OPTION_SERVER) or - not config.has_option(CONFIG_SECTION, OPTION_EMAIL) or - (config.has_option(CONFIG_SECTION, OPTION_LOGIN) and - not (config.has_option(CONFIG_SECTION, OPTION_PASSWORD) or - config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN))) or - ((config.has_option(CONFIG_SECTION, OPTION_PASSWORD) or - config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN)) and not - config.has_option(CONFIG_SECTION, OPTION_LOGIN))): - exit_forcing_print(ERROR_CONFIG_PARSE) - -### Extract the necessary configuration parameters ## + not config.has_option(CONFIG_SECTION, OPTION_SERVER) or + not config.has_option(CONFIG_SECTION, OPTION_EMAIL) or + (config.has_option(CONFIG_SECTION, OPTION_LOGIN) and + not (config.has_option(CONFIG_SECTION, OPTION_PASSWORD) or + config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN))) or + ((config.has_option(CONFIG_SECTION, OPTION_PASSWORD) or + config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN)) and not + config.has_option(CONFIG_SECTION, OPTION_LOGIN))): + exit_forcing_print(ERROR_CONFIG_PARSE) + +### Extract the necessary configuration parameters ### theSMTPServer = config.get(CONFIG_SECTION, OPTION_SERVER) -if theEMailAddress is None: # "Envelope from" if -f was not present - theEMailAddress = config.get(CONFIG_SECTION, OPTION_EMAIL) +if theEMailAddress is None: # "Envelope from" if -f was not present + theEMailAddress = config.get(CONFIG_SECTION, OPTION_EMAIL) -try: # TLS - if (config.has_option(CONFIG_SECTION, OPTION_TLS) and - config.getboolean(CONFIG_SECTION, OPTION_TLS)): - theTLSFlag = True +try: # TLS + if (config.has_option(CONFIG_SECTION, OPTION_TLS) and + config.getboolean(CONFIG_SECTION, OPTION_TLS)): + theTLSFlag = True except ValueError: - exit_forcing_print(ERROR_TLS) + exit_forcing_print(ERROR_TLS) -try: # Quiet - if (config.has_option(CONFIG_SECTION, OPTION_QUIET) and - config.getboolean(CONFIG_SECTION, OPTION_QUIET)): - theQuietFlag = True +try: # Quiet + if (config.has_option(CONFIG_SECTION, OPTION_QUIET) and + config.getboolean(CONFIG_SECTION, OPTION_QUIET)): + theQuietFlag = True except ValueError: - exit_forcing_print(ERROR_QUIET) - -if config.has_option(CONFIG_SECTION, OPTION_LOGIN): # Login/password - theSMTPLogin = config.get(CONFIG_SECTION, OPTION_LOGIN) - try: - # if config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN): - keychainType = config.get(CONFIG_SECTION, OPTION_KEYCHAIN) - keychain_func = keychain(keychainType) - theSMTPPassword = keychain_func(theSMTPServer) - except TypeError: - exit_forcing_print(ERROR_CONFIG_KEYCHAIN) - except ConfigParser.NoOptionError: - theSMTPPassword = config.get(CONFIG_SECTION, OPTION_PASSWORD) - theAuthenticateFlag = True - -try: # Port - if config.has_option(CONFIG_SECTION, OPTION_PORT): - thePort = config.getint(CONFIG_SECTION, OPTION_PORT) - if thePort < 0 or thePort > HIGHEST_PORT: - raise ValueError - else: - thePort = DEFAULT_PORT + exit_forcing_print(ERROR_QUIET) + +if config.has_option(CONFIG_SECTION, OPTION_LOGIN): # Login/password + theSMTPLogin = config.get(CONFIG_SECTION, OPTION_LOGIN) + try: + # if config.has_option(CONFIG_SECTION, OPTION_KEYCHAIN): + keychainType = config.get(CONFIG_SECTION, OPTION_KEYCHAIN) + keychain_func = keychain(keychainType) + theSMTPPassword = keychain_func(theSMTPServer) + except TypeError: + exit_forcing_print(ERROR_CONFIG_KEYCHAIN) + except ConfigParser.NoOptionError: + theSMTPPassword = config.get(CONFIG_SECTION, OPTION_PASSWORD) + theAuthenticateFlag = True + +try: # Port + if config.has_option(CONFIG_SECTION, OPTION_PORT): + thePort = config.getint(CONFIG_SECTION, OPTION_PORT) + if thePort < 0 or thePort > HIGHEST_PORT: + raise ValueError + else: + thePort = DEFAULT_PORT except ValueError: - exit_forcing_print(ERROR_PORT) + exit_forcing_print(ERROR_PORT) ########################################################################## -# Fourth step: Extract information from important headers (like To) and # -# remove the Bcc header from the message. # +# Fourth step: Extract information from important headers (like To) and # +# remove the Bcc header from the message. # ########################################################################## # If we are told to take the addresses from the message itself... if theRcpsFromMailFlag: - if theMessage.has_key(TO_HEADER): - theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( - theMessage.get_all(TO_HEADER))] - ) - if theMessage.has_key(CC_HEADER): - theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( - theMessage.get_all(CC_HEADER))] - ) - if theMessage.has_key(BCC_HEADER): - theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( - theMessage.get_all(BCC_HEADER))] - ) + if theMessage.has_key(TO_HEADER): + theRecipients.extend( + [x[1] for x in email.Utils.getaddresses( + theMessage.get_all(TO_HEADER))] + ) + if theMessage.has_key(CC_HEADER): + theRecipients.extend( + [x[1] for x in email.Utils.getaddresses( + theMessage.get_all(CC_HEADER))] + ) + if theMessage.has_key(BCC_HEADER): + theRecipients.extend( + [x[1] for x in email.Utils.getaddresses( + theMessage.get_all(BCC_HEADER))] + ) # Delete Bcc header if it exists if theMessage.has_key(BCC_HEADER): - del theMessage[BCC_HEADER] + del theMessage[BCC_HEADER] # Still no addresses found? if len(theRecipients) == 0: - exit_forcing_print(ERROR_NO_RECIPIENTS) + exit_forcing_print(ERROR_NO_RECIPIENTS) #################################################### # Fifth step: Send the damned message finally \o/. # #################################################### try: - server = smtplib.SMTP() - #server.set_debuglevel(True) - check_status(server.connect(theSMTPServer, thePort)) - check_status(server.ehlo()) - if theTLSFlag: - check_status(server.starttls()) - check_status(server.ehlo()) # Repeat EHLO after starting TLS - if theAuthenticateFlag: - check_status(server.login(theSMTPLogin, theSMTPPassword)) - rejected = server.sendmail(theEMailAddress, theRecipients, - theMessage.as_string()) + server = smtplib.SMTP() + #server.set_debuglevel(True) + check_status(server.connect(theSMTPServer, thePort)) + check_status(server.ehlo()) + if theTLSFlag: + check_status(server.starttls()) + check_status(server.ehlo()) # Repeat EHLO after starting TLS + if theAuthenticateFlag: + check_status(server.login(theSMTPLogin, theSMTPPassword)) + rejected = server.sendmail(theEMailAddress, theRecipients, + theMessage.as_string()) except smtplib.SMTPServerDisconnected: - exit_conditional_print(ERROR_DISCONNECTED) + exit_conditional_print(ERROR_DISCONNECTED) except smtplib.SMTPSenderRefused: - exit_conditional_print(ERROR_SENDER_REFUSED) + exit_conditional_print(ERROR_SENDER_REFUSED) except smtplib.SMTPRecipientsRefused: - exit_conditional_print(ERROR_REFUSED) + exit_conditional_print(ERROR_REFUSED) except smtplib.SMTPDataError: - exit_conditional_print(ERROR_DATA) + exit_conditional_print(ERROR_DATA) except smtplib.SMTPConnectError: - exit_conditional_print(ERROR_CONNECT) + exit_conditional_print(ERROR_CONNECT) except smtplib.SMTPHeloError: - exit_conditional_print(ERROR_HELO) + exit_conditional_print(ERROR_HELO) except smtplib.SMTPAuthenticationError: - exit_conditional_print(ERROR_AUTH) + exit_conditional_print(ERROR_AUTH) except smtplib.SMTPResponseException, err: - exit_conditional_print(ERROR_OTHER % (err.smtp_code, err.smtp_error)) + exit_conditional_print(ERROR_OTHER % (err.smtp_code, err.smtp_error)) except (socket.error, socket.herror, socket.gaierror), err: - exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err[1])) + exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err[1])) except socket.timeout, err: - exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err)) + exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err)) except smtplib.SMTPException: - exit_conditional_print(ERROR_UNKNOWN) + exit_conditional_print(ERROR_UNKNOWN) try: - server.quit() + server.quit() except: - conditional_print(WARNING_QUIT) + conditional_print(WARNING_QUIT) # Check for rejected recipients if len(rejected) > 0: - conditional_print(WARNING_REJECTED) - conditional_print('\n'.join(['\t' + email for email in rejected])) + conditional_print(WARNING_REJECTED) + conditional_print('\n'.join(['\t' + email for email in rejected])) # Good enough sys.exit() -# vim: set ft=python noet sts=4 sw=4 ts=4 : +# vim: set ft=python noet sts=4 sw=4 ts=4 : diff --git a/putmail_dequeue.py b/putmail_dequeue.py index dca0643..c30343f 100755 --- a/putmail_dequeue.py +++ b/putmail_dequeue.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# putmail_dequeue.py Read parameters and messages from a queue and pass -# them as arguments and standard input data to putmail.py. +# putmail_dequeue.py Read parameters and messages from a queue and pass +# them as arguments and standard input data to putmail.py. # -# (c) Ricardo García González -# sarbalap-sourceforge _at_ yahoo _dot_ es +# (c) Ricardo García González +# sarbalap-sourceforge _at_ yahoo _dot_ es # # This tiny script is distributed under the X Consortium License. See # LICENSE file for more details. @@ -20,9 +20,9 @@ ### Initialize ### try: - gettext.install("putmail_dequeue.py") # Always before using _() + gettext.install("putmail_dequeue.py") # Always before using _() except: - pass + pass ### Constants ### PUTMAIL_DIR = ".putmail" @@ -33,7 +33,7 @@ ### Main program ### if not os.environ.has_key(HOME_EV): - sys.exit(ERROR_HOME_UNSET) + sys.exit(ERROR_HOME_UNSET) # Get message file names pattern = os.path.join(os.getenv(HOME_EV), PUTMAIL_DIR, QUEUE_SUBDIR, "*") @@ -42,36 +42,36 @@ # Try to send each message (total, sent, deleted) = (0, 0, 0) for msgfn in files: - msgbn = os.path.basename(msgfn) - print _("[%s] Sending message.") % msgbn + msgbn = os.path.basename(msgfn) + print _("[%s] Sending message.") % msgbn - try: - (params, text) = cPickle.load(open(msgfn)) + try: + (params, text) = cPickle.load(open(msgfn)) - # Launch putmail.py and write the message to its stdin - cmd = "%s %s" % (PUTMAIL_PY, " ".join(params[1:])) - child_stdin = os.popen(cmd, "w") - child_stdin.write(text) + # Launch putmail.py and write the message to its stdin + cmd = "%s %s" % (PUTMAIL_PY, " ".join(params[1:])) + child_stdin = os.popen(cmd, "w") + child_stdin.write(text) - exit_status = child_stdin.close() - if not exit_status is None: - raise Exception - print _("[%s] Message sent.") % msgbn - sent += 1 + exit_status = child_stdin.close() + if not exit_status is None: + raise Exception + print _("[%s] Message sent.") % msgbn + sent += 1 - try: - print _("[%s] Deleting message file.") % msgbn - os.unlink(msgfn) - print _("[%s] Message deleted.") % msgbn - deleted += 1 + try: + print _("[%s] Deleting message file.") % msgbn + os.unlink(msgfn) + print _("[%s] Message deleted.") % msgbn + deleted += 1 - except (IOError, OSError): - print _("[%s] Message NOT deleted! Fix queue!") % msgbn + except (IOError, OSError): + print _("[%s] Message NOT deleted! Fix queue!") % msgbn - except (IOError, OSError): - print _("[%s] Message NOT sent.") % msgbn + except (IOError, OSError): + print _("[%s] Message NOT sent.") % msgbn - total += 1 + total += 1 # End of for loop print _("Total: %s\nSent: %s\nDeleted: %s") % (total, sent, deleted) diff --git a/putmail_enqueue.py b/putmail_enqueue.py index 6267678..2d2bbed 100755 --- a/putmail_enqueue.py +++ b/putmail_enqueue.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# putmail_enqueue.py Read mail from standard input and save message and -# program arguments to a mail queue. +# putmail_enqueue.py Read mail from standard input and save message and +# program arguments to a mail queue. # -# (c) Ricardo García González -# sarbalap-sourceforge _at_ yahoo _dot_ es +# (c) Ricardo García González +# sarbalap-sourceforge _at_ yahoo _dot_ es # # This tiny script is distributed under the X Consortium License. See # LICENSE file for more details. @@ -20,9 +20,9 @@ ### Initialize ### try: - gettext.install("putmail_enqueue.py") # Always before using _() + gettext.install("putmail_enqueue.py") # Always before using _() except: - pass + pass ### Constants ### PUTMAIL_DIR = ".putmail" @@ -35,39 +35,39 @@ ### Main program ### if not os.environ.has_key(HOME_EV): - sys.exit(ERROR_HOME_UNSET) + sys.exit(ERROR_HOME_UNSET) queue_dir = os.path.join(os.getenv(HOME_EV), PUTMAIL_DIR, QUEUE_SUBDIR) # Create message file try: - (msgfd, msgfname) = tempfile.mkstemp("", "", queue_dir) - msgfile = os.fdopen(msgfd, "w") + (msgfd, msgfname) = tempfile.mkstemp("", "", queue_dir) + msgfile = os.fdopen(msgfd, "w") except (IOError, OSError): - sys.exit(ERROR_CREATE_TEMPFILE) + sys.exit(ERROR_CREATE_TEMPFILE) # Read parameters and message contents params = sys.argv try: - message = sys.stdin.read() + message = sys.stdin.read() except IOError: - msgfile.close() - os.unlink(msgfname) - sys.exit(ERROR_MESSAGE_STDIN) + msgfile.close() + os.unlink(msgfname) + sys.exit(ERROR_MESSAGE_STDIN) # Write data try: - cPickle.dump((params, message), msgfile) + cPickle.dump((params, message), msgfile) except IOError: - try: - msgfile.close() - except: - pass - try: - os.unlink(msgfname) - except: - pass - sys.exit(ERROR_DATA_OUTPUT) + try: + msgfile.close() + except: + pass + try: + os.unlink(msgfname) + except: + pass + sys.exit(ERROR_DATA_OUTPUT) # Close file and exit msgfile.close() From 79e630c27b997a1f059af6f70578233e1d99f0e7 Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Fri, 28 Aug 2015 08:46:07 -0700 Subject: [PATCH 2/7] Added native TLS support; switched to Python 3; cleaned up code some - Renamed theTLSFlag to theTLSMode and converted it from a boolean to a constant-or-none variable (TLS_MODE_NATIVE|TLS_MODE_STARTTLS|None). - Added the tls_versions option and related constants. - Made check_status() take two separate arguments instead of unpacking a tuple. - Caught OSError instead of socket.error and reordered that suite last (smtplib.SMTPException is a subclass of OSError). - Tweaked the code in various ways (using with statements for files, eliminating bare `except`s, using containers' truthiness instead of len(), renaming to avoid shadowing builtins, etc). --- putmail.py | 180 ++++++++++++++++++++++++++++----------------- putmail_dequeue.py | 30 ++++---- putmail_enqueue.py | 46 +++++------- 3 files changed, 143 insertions(+), 113 deletions(-) diff --git a/putmail.py b/putmail.py index abead3d..49efd42 100755 --- a/putmail.py +++ b/putmail.py @@ -20,6 +20,7 @@ import os import sys import socket +import ssl import datetime import subprocess as sb @@ -42,9 +43,10 @@ OPTION_SERVER = 'server' OPTION_EMAIL = 'email' OPTION_TLS = 'tls' +OPTION_TLS_VERSIONS = 'tls_versions' OPTION_LOGIN = 'username' OPTION_PASSWORD = 'password' -OPTION_KEYCHAIN= 'keychain' +OPTION_KEYCHAIN = 'keychain' OPTION_PORT = 'port' OPTION_QUIET = 'quiet' @@ -59,6 +61,23 @@ email = your.email@example.com """ +TLS_MODE_NATIVE = 'native' +TLS_MODE_STARTTLS = 'starttls' + +NO_TLS_VERSION_FLAGS = {} +for config_option, attribute in { + 'ssl2': 'OP_NO_SSLv2', + 'ssl3': 'OP_NO_SSLv3', + 'tls1': 'OP_NO_TLSv1', + 'tls1_1': 'OP_NO_TLSv1_1', + 'tls1_2': 'OP_NO_TLSv1_2', + 'tls1_3': 'OP_NO_TLSv1_3', # FutureProofing[tm] + }.items(): + try: + NO_TLS_VERSION_FLAGS[config_option] = getattr(ssl, attribute) + except AttributeError: + pass + # Internal errors ERROR_HOME_UNSET = 'Error: %s environment variable not set' % HOME_EV ERROR_CONFIG_NONEXISTANT = 'Error: config file %s not present' @@ -69,11 +88,13 @@ ERROR_READ_MAIL = 'Error: parse error reading the mail message' ERROR_NO_RECIPIENTS = 'Error: no recipients for the message' ERROR_TLS = 'Error: malformed option "%s"' % OPTION_TLS +ERROR_TLS_VERSION = 'Error: malformed option "%s"' % OPTION_TLS_VERSIONS ERROR_PORT = 'Error: malformed option "%s"' % OPTION_PORT ERROR_QUIET = 'Error: malformed option "%s"' % OPTION_QUIET ERROR_O_OPTION = 'Error: missing option for -o' ERROR_OPTION_ARGS = 'Error: missing arguments for last option' ERROR_UNKNOWN_OPTION = 'Error: unknown option "%s"' +ERROR_OLD_OPENSSL = 'Error: openssl is too far out of date. Please update to at least 0.9.8m' # SMTP and network errors ERROR_REFUSED = 'Error: all recipients rejected by server' @@ -91,8 +112,12 @@ WARNING_REJECTED = 'Warning: the following recipients were rejected:' WARNING_QUIT = 'Warning: problem disconnecting but message probably sent' WARNING_LOGFILE = 'Warning: unable to write to log file' -WARNING_CONFIG_UNREADABLE = 'Warning: config file for %s present but no read access, falling back to default config file' -WARNING_SAMPLE_CONFIG = 'Warning: trying to create sample config file (read manpage and check permissions' +WARNING_CONFIG_UNREADABLE = ( + 'Warning: config file for %s present but no read access, ' + 'falling back to default config file') +WARNING_SAMPLE_CONFIG = ( + 'Warning: trying to create sample config file ' + '(read manpage and check permissions)') # Codes EXIT_FAILURE = 1 @@ -105,41 +130,42 @@ theMessage = None # The E-Mail message theRecipients = [] # The recipients of the E-Mail message theConfigFilename = None # The configuration file name -theTLSFlag = False # Use TLS or not +theTLSMode = None # Use native TLS, STARTTLS, or neither +theTLSContext = ssl.create_default_context() theAuthenticateFlag = False # Use SMTP authentication or not theSMTPLogin = None # The login name to use with the server theSMTPPassword = None # The corresponding password theRcpsFromMailFlag = False # Take recipients from message or not theQuietFlag = False # Supress program output or not -theLogFile = None # The log file name +theLogPath = None # The log file name ### A few auxiliary functions ### -def handle_error(str, stderr_output, exit_program): - fullstr = str + "\n" +def handle_error(error_string, stderr_output, exit_program): try: - file(theLogFile, "a").write("%s: %s" % ( - datetime.datetime.ctime(datetime.datetime.now()), fullstr)) + with open(theLogPath, "a") as theLogFile: + theLogFile.write("%s: %s\n" % ( + datetime.datetime.now().ctime(), error_string)) except (IOError, OSError): - sys.stderr.write(WARNING_LOGFILE + "\n") + print(WARNING_LOGFILE, file=sys.stderr) if stderr_output: - sys.stderr.write(fullstr) + print(error_string, file=sys.stderr) if exit_program: sys.exit(EXIT_FAILURE) -def exit_forcing_print(str): - handle_error(str, True, True) +def exit_forcing_print(string): + handle_error(string, True, True) -def exit_conditional_print(str): - handle_error(str, not theQuietFlag, True) +def exit_conditional_print(string): + handle_error(string, not theQuietFlag, True) -def conditional_print(str): - handle_error(str, not theQuietFlag, False) +def conditional_print(string): + handle_error(string, not theQuietFlag, False) -def force_print(str): - handle_error(str, True, False) +def force_print(string): + handle_error(string, True, False) -def check_status((code, message)): +def check_status(code, message): if code >= FIRST_ERROR_CODE: exit_conditional_print(ERROR_OTHER % (code, message)) @@ -147,8 +173,8 @@ def keychain(keychainType): if keychainType == 'osx': return osxkeychain -def osxkeychain(service, type="internet"): - cmd = """/usr/bin/security find-%s-password -gs %s""" % (type, service) +def osxkeychain(service, passwordType="internet"): + cmd = """/usr/bin/security find-%s-password -gs %s""" % (passwordType, service) args = shlex.split(cmd) t = sb.check_output(args, stderr=sb.STDOUT) lines = t.split('\n') @@ -157,13 +183,13 @@ def osxkeychain(service, type="internet"): return passwd ### Check for HOME present (needed later, checking now saves a lot of work) ### -if not os.environ.has_key(HOME_EV): +if not HOME_EV in os.environ: # Note: I still can't use exit_forcing_print() at this point, the log # filename is not set. sys.exit(ERROR_HOME_UNSET + "\n") ### Build the log filename now ### -theLogFile = os.path.join(os.environ[HOME_EV], CONFIG_DIRECTORY, LOG_FILE) +theLogPath = os.path.join(os.environ[HOME_EV], CONFIG_DIRECTORY, LOG_FILE) ############################################### # First step: Parse the command line options. # @@ -186,18 +212,18 @@ def osxkeychain(service, type="internet"): '-bv', '-G', '-i', '-n', '-v'] single_options_exit = ['-bd', '-bD', '-bh', '-bH', '-bp', '-bP', '-qf', - '-bi' ] + '-bi'] - one_argument_options = [ '-B', '-C', '-D', '-d', '-F', '-h', '-L', '-N', - '-O', '-p', '-R', '-r', '-V', '-X' ] + one_argument_options = ['-B', '-C', '-D', '-d', '-F', '-h', '-L', '-N', + '-O', '-p', '-R', '-r', '-V', '-X'] one_argument_options_exit = ['-qG', '-qI', '-qQ', '-qR', '-qS', '-q!I', - '-q!Q', '-q!R', '-q!S' ] + '-q!Q', '-q!R', '-q!S'] - optional_extra_chars_options_exit = [ '-q', '-qp', '-Q' ] + optional_extra_chars_options_exit = ['-q', '-qp', '-Q'] # Eat arguments until there's none left - while (len(program_args) > 0): + while program_args: if program_args[0] == '--': # Remaining options are recipients theRecipients.extend(program_args[1:]) break @@ -229,12 +255,12 @@ def osxkeychain(service, type="internet"): del program_args[0] del program_args[0] direct_exit = True - elif (sum([program_args[0].startswith(x) # First chars match - for x in one_argument_options_exit]) > 0): + elif any(program_args[0].startswith(x) + for x in one_argument_options_exit): del program_args[0] direct_exit = True - elif (sum([program_args[0].startswith(x) # First chars match - for x in optional_extra_chars_options_exit]) > 0): + elif any(program_args[0].startswith(x) + for x in optional_extra_chars_options_exit): del program_args[0] direct_exit = True elif program_args[0].startswith('-o'): # Weird case @@ -258,15 +284,15 @@ def osxkeychain(service, type="internet"): if print_info: programName = os.path.basename(sys.argv[0]) version = "%s %s" % (programName, __version__) - print version - print " type `man %s` for more information" % programName + print(version) + print(" type `man %s` for more information" % programName) # Options indicated direct exit if direct_exit: sys.exit() # No addresses found? -if len(theRecipients) == 0 and not theRcpsFromMailFlag: +if not theRecipients and not theRcpsFromMailFlag: exit_forcing_print(ERROR_NO_RECIPIENTS) ###################################################### @@ -313,8 +339,9 @@ def osxkeychain(service, type="internet"): dirname = os.path.dirname(configPath) if not os.path.isdir(dirname): os.makedirs(dirname) - file(configPath, "w").write(DEFAULT_CONFIG) - except: + with open(configPath, "w") as configFile: + configFile.write(DEFAULT_CONFIG) + except OSError: exit_forcing_print(ERROR_CONFIG_CREATE) sys.exit(EXIT_FAILURE) @@ -328,7 +355,7 @@ def osxkeychain(service, type="internet"): config = ConfigParser.ConfigParser() try: config.read([configPath]) -except: +except OSError: exit_forcing_print(ERROR_CONFIG_PARSE) ### Check conditions for bad configurations ### @@ -351,12 +378,28 @@ def osxkeychain(service, type="internet"): if theEMailAddress is None: # "Envelope from" if -f was not present theEMailAddress = config.get(CONFIG_SECTION, OPTION_EMAIL) -try: # TLS - if (config.has_option(CONFIG_SECTION, OPTION_TLS) and - config.getboolean(CONFIG_SECTION, OPTION_TLS)): - theTLSFlag = True -except ValueError: - exit_forcing_print(ERROR_TLS) +if config.has_option(CONFIG_SECTION, OPTION_TLS): # TLS + theTLSMode = config.get(CONFIG_SECTION, OPTION_TLS) + try: + if not config.getboolean(CONFIG_SECTION, OPTION_TLS): + theTLSMode = None + except ValueError: + pass + if theTLSMode not in {TLS_MODE_NATIVE, TLS_MODE_STARTTLS, None}: + exit_forcing_print(ERROR_TLS) + +if config.has_option(CONFIG_SECTION, OPTION_TLS_VERSIONS): # TLS versions + for flag in NO_TLS_VERSION_FLAGS.values(): + theTLSContext.options |= flag + for version in config.get(CONFIG_SECTION, OPTION_TLS_VERSIONS).split(): + try: + theTLSContext.options &= ~NO_TLS_VERSION_FLAGS[version] + except KeyError: + exit_forcing_print(ERROR_TLS_VERSION) + # openssls older than 0.9.8m will cause a ValueError + # to be raised when we try to unset an option + except ValueError: + exit_forcing_print(ERROR_OLD_OPENSSL) try: # Quiet if (config.has_option(CONFIG_SECTION, OPTION_QUIET) and @@ -382,7 +425,7 @@ def osxkeychain(service, type="internet"): if config.has_option(CONFIG_SECTION, OPTION_PORT): thePort = config.getint(CONFIG_SECTION, OPTION_PORT) if thePort < 0 or thePort > HIGHEST_PORT: - raise ValueError + raise ValueError() else: thePort = DEFAULT_PORT except ValueError: @@ -416,7 +459,7 @@ def osxkeychain(service, type="internet"): del theMessage[BCC_HEADER] # Still no addresses found? -if len(theRecipients) == 0: +if not theRecipients: exit_forcing_print(ERROR_NO_RECIPIENTS) #################################################### @@ -424,17 +467,22 @@ def osxkeychain(service, type="internet"): #################################################### try: - server = smtplib.SMTP() + if theTLSMode == TLS_MODE_NATIVE: + server = smtplib.SMTP_SSL(context=theTLSContext) + else: + server = smtplib.SMTP() #server.set_debuglevel(True) - check_status(server.connect(theSMTPServer, thePort)) - check_status(server.ehlo()) - if theTLSFlag: - check_status(server.starttls()) - check_status(server.ehlo()) # Repeat EHLO after starting TLS + check_status(*server.connect(theSMTPServer, thePort)) + check_status(*server.ehlo()) + if theTLSMode == TLS_MODE_STARTTLS: + check_status(*server.starttls(context=theTLSContext)) + # SMTP.starttls() discards all knowledge obtained from the server + # as per RFC 3207. This means we need to EHLO again. + check_status(*server.ehlo()) if theAuthenticateFlag: - check_status(server.login(theSMTPLogin, theSMTPPassword)) - rejected = server.sendmail(theEMailAddress, theRecipients, - theMessage.as_string()) + check_status(*server.login(theSMTPLogin, theSMTPPassword)) + rejectedRecipients = server.sendmail(theEMailAddress, theRecipients, + theMessage.as_string()) except smtplib.SMTPServerDisconnected: exit_conditional_print(ERROR_DISCONNECTED) except smtplib.SMTPSenderRefused: @@ -449,25 +497,21 @@ def osxkeychain(service, type="internet"): exit_conditional_print(ERROR_HELO) except smtplib.SMTPAuthenticationError: exit_conditional_print(ERROR_AUTH) -except smtplib.SMTPResponseException, err: +except smtplib.SMTPResponseException as err: exit_conditional_print(ERROR_OTHER % (err.smtp_code, err.smtp_error)) -except (socket.error, socket.herror, socket.gaierror), err: - exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err[1])) -except socket.timeout, err: +except socket.timeout as err: exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err)) except smtplib.SMTPException: exit_conditional_print(ERROR_UNKNOWN) +except (socket.herror, socket.gaierror, OSError) as err: + exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err[1])) try: server.quit() -except: +except smtplib.SMTPException: conditional_print(WARNING_QUIT) # Check for rejected recipients -if len(rejected) > 0: +if rejectedRecipients: conditional_print(WARNING_REJECTED) - conditional_print('\n'.join(['\t' + email for email in rejected])) - -# Good enough -sys.exit() -# vim: set ft=python noet sts=4 sw=4 ts=4 : + conditional_print('\n'.join(['\t' + email for email in rejectedRecipients])) diff --git a/putmail_dequeue.py b/putmail_dequeue.py index c30343f..4a968ea 100755 --- a/putmail_dequeue.py +++ b/putmail_dequeue.py @@ -16,13 +16,13 @@ import os.path import glob import gettext -import cPickle +import pickle ### Initialize ### try: gettext.install("putmail_dequeue.py") # Always before using _() -except: - pass +except Exception: + _ = lambda s: s ### Constants ### PUTMAIL_DIR = ".putmail" @@ -32,7 +32,7 @@ ERROR_HOME_UNSET = _("Error: %s environment variable not set") % HOME_EV ### Main program ### -if not os.environ.has_key(HOME_EV): +if not HOME_EV in os.environ: sys.exit(ERROR_HOME_UNSET) # Get message file names @@ -43,36 +43,34 @@ (total, sent, deleted) = (0, 0, 0) for msgfn in files: msgbn = os.path.basename(msgfn) - print _("[%s] Sending message.") % msgbn + print(_("[%s] Sending message.") % msgbn) try: - (params, text) = cPickle.load(open(msgfn)) + (params, text) = pickle.load(open(msgfn)) # Launch putmail.py and write the message to its stdin cmd = "%s %s" % (PUTMAIL_PY, " ".join(params[1:])) - child_stdin = os.popen(cmd, "w") + child_stdin = os.popen(cmd, "wb") child_stdin.write(text) exit_status = child_stdin.close() if not exit_status is None: - raise Exception - print _("[%s] Message sent.") % msgbn + raise Exception() + print(_("[%s] Message sent.") % msgbn) sent += 1 try: - print _("[%s] Deleting message file.") % msgbn + print(_("[%s] Deleting message file.") % msgbn) os.unlink(msgfn) - print _("[%s] Message deleted.") % msgbn + print(_("[%s] Message deleted.") % msgbn) deleted += 1 except (IOError, OSError): - print _("[%s] Message NOT deleted! Fix queue!") % msgbn + print(_("[%s] Message NOT deleted! Fix queue!") % msgbn) except (IOError, OSError): - print _("[%s] Message NOT sent.") % msgbn + print(_("[%s] Message NOT sent.") % msgbn) total += 1 -# End of for loop -print _("Total: %s\nSent: %s\nDeleted: %s") % (total, sent, deleted) -sys.exit() +print(_("Total: %s\nSent: %s\nDeleted: %s") % (total, sent, deleted)) diff --git a/putmail_enqueue.py b/putmail_enqueue.py index 2d2bbed..267ffbe 100755 --- a/putmail_enqueue.py +++ b/putmail_enqueue.py @@ -16,13 +16,13 @@ import os import os.path import gettext -import cPickle +import pickle ### Initialize ### try: gettext.install("putmail_enqueue.py") # Always before using _() -except: - pass +except Exception: + _ = lambda s: s ### Constants ### PUTMAIL_DIR = ".putmail" @@ -34,41 +34,29 @@ ERROR_DATA_OUTPUT = _("Error: unable to write data to queue file") ### Main program ### -if not os.environ.has_key(HOME_EV): +if not HOME_EV in os.environ: sys.exit(ERROR_HOME_UNSET) queue_dir = os.path.join(os.getenv(HOME_EV), PUTMAIL_DIR, QUEUE_SUBDIR) -# Create message file try: (msgfd, msgfname) = tempfile.mkstemp("", "", queue_dir) - msgfile = os.fdopen(msgfd, "w") -except (IOError, OSError): +except OSError: sys.exit(ERROR_CREATE_TEMPFILE) -# Read parameters and message contents -params = sys.argv try: - message = sys.stdin.read() -except IOError: - msgfile.close() - os.unlink(msgfname) - sys.exit(ERROR_MESSAGE_STDIN) - -# Write data -try: - cPickle.dump((params, message), msgfile) -except IOError: - try: - msgfile.close() - except: - pass + with os.fdopen(msgfd, "wb") as msgfile: + try: + message = sys.stdin.read() + except IOError: + sys.exit(ERROR_MESSAGE_STDIN) + try: + pickle.dump((sys.argv, message), msgfile) + except IOError: + sys.exit(ERROR_DATA_OUTPUT) +except SystemExit: try: os.unlink(msgfname) - except: + except OSError: pass - sys.exit(ERROR_DATA_OUTPUT) - -# Close file and exit -msgfile.close() -sys.exit() + raise From 0a20b0877ffb4e7311318170d1cf212bf64f5d66 Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Mon, 31 Aug 2015 18:49:03 -0700 Subject: [PATCH 3/7] Changed the shebangs from python to python3 --- putmail.py | 2 +- putmail_dequeue.py | 2 +- putmail_enqueue.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/putmail.py b/putmail.py index 49efd42..cd55328 100755 --- a/putmail.py +++ b/putmail.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # putmail.py Send mail read from standard input. diff --git a/putmail_dequeue.py b/putmail_dequeue.py index 4a968ea..76227ce 100755 --- a/putmail_dequeue.py +++ b/putmail_dequeue.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # putmail_dequeue.py Read parameters and messages from a queue and pass diff --git a/putmail_enqueue.py b/putmail_enqueue.py index 267ffbe..f467c2d 100755 --- a/putmail_enqueue.py +++ b/putmail_enqueue.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # putmail_enqueue.py Read mail from standard input and save message and From 918dd755afd9a8be0b23985cd88bbda2254195c8 Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Thu, 1 Oct 2015 15:34:10 -0700 Subject: [PATCH 4/7] Updated manual page to reflect TLS updates Documentation for the new `tls_versions` option was added; the documentation for the `tls` configuration option was updated to reflect the new choices. --- man/man1/putmail.py.1 | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/man/man1/putmail.py.1 b/man/man1/putmail.py.1 index 286f286..4a70047 100644 --- a/man/man1/putmail.py.1 +++ b/man/man1/putmail.py.1 @@ -99,21 +99,34 @@ The password used when authenticating to the SMTP server. The server port. The default value is 25. .TP \fItls\fR -Boolean option to enable or disable TLS. It is disabled by -default or when its value is \fIno\fR, \fI0\fR, \fIoff\fR -or \fIfalse\fR. Set it to \fIyes\fR, \fI1\fR, \fIon\fR -or \fItrue\fR to enable TLS. +The TLS mode to use, disabled by default or when set to +\fIoff\fR. Set it to \fInative\fR or \fIstarttls\fR to use TLS. +When active, \fBputmail.py\fR will refuse to authenticate or +send mail without TLS. +.TP +\fItls_versions\fR +A whitespace-separated list of TLS versions to use. Ignored +when \fItls\fR is inactive. Known values: \fIssl2\fR, +\fIssl3\fR, \fItls1\fR, \fItls1_1\fR, \fItls1_2\fR. Note that +\fBputmail.py\fR cannot use a TLS version unless it is also +supported by the underlying Python implementation (and, by +extension, the underlying OpenSSL implementation). The default +depends on the Python implementation; at the time of +publication, it is recommended that this option be set +manually and include only TLS 1.1 and newer. .TP \fIquiet\fR Boolean option, disabled by default. When active, errors and warnings related to communication with the SMTP server -will not be printed on screen. See the \fItls\fR option -for possible values. Use it with care and once you know -your configuration works. In most cases this will not be -needed. However, sometimes warnings about improper -connection shutdown can get really annoying. Errors -related to malformed configuration files, absence of -message recipients and others will still be printed. +will not be printed on screen. It is disabled by default or +when its value is \fIno\fR, \fI0\fR, \fIoff\fR or \fIfalse\fR. +Set it to \fIyes\fR, \fI1\fR, \fIon\fR or \fItrue\fR to enable +TLS. Use it with care and once you know your configuration +works. In most cases this will not be needed. However, +sometimes warnings about improper connection shutdown can get +really annoying. Errors related to malformed configuration +files, absence of message recipients and others will still be +printed. .SH "COMMAND LINE OPTIONS" .LP .TP From 8840366a1bf5c97115766c1dfb4376c398f48632 Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Thu, 4 Feb 2016 06:00:15 -0800 Subject: [PATCH 5/7] Always give our address as 0.0.0.0 in the EHLO I'd rather my hostname not show up in the headers of mail I send. That's probably just me, though. Maybe this should be an option or something. --- putmail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/putmail.py b/putmail.py index cd55328..13b8a05 100755 --- a/putmail.py +++ b/putmail.py @@ -473,12 +473,12 @@ def osxkeychain(service, passwordType="internet"): server = smtplib.SMTP() #server.set_debuglevel(True) check_status(*server.connect(theSMTPServer, thePort)) - check_status(*server.ehlo()) + check_status(*server.ehlo('0.0.0.0')) if theTLSMode == TLS_MODE_STARTTLS: check_status(*server.starttls(context=theTLSContext)) # SMTP.starttls() discards all knowledge obtained from the server # as per RFC 3207. This means we need to EHLO again. - check_status(*server.ehlo()) + check_status(*server.ehlo('0.0.0.0')) if theAuthenticateFlag: check_status(*server.login(theSMTPLogin, theSMTPPassword)) rejectedRecipients = server.sendmail(theEMailAddress, theRecipients, From 1e6820b31e96e582a0c8eea726e5714b38453364 Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Mon, 3 Oct 2016 03:44:18 -0700 Subject: [PATCH 6/7] Tweaked code to actually work under Python 3 dict.has_key(key) -> key in dict exception[1] -> str(exception) ConfigParser -> configparser email.Utils -> email.utils Also stopped shadowing the `email` module in that one comprehension on line 517 --- putmail.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/putmail.py b/putmail.py index 13b8a05..404a9bd 100755 --- a/putmail.py +++ b/putmail.py @@ -14,7 +14,7 @@ __version_info__ = (1, 4, 1) __version__ = '.'.join([str(i) for i in __version_info__]) -import ConfigParser +import configparser import smtplib import email import os @@ -314,9 +314,9 @@ def osxkeychain(service, passwordType="internet"): configPath = os.path.join(os.environ[HOME_EV], CONFIG_DIRECTORY) # temporally theConfigFilename = CONFIG_NAME -if theMessage.has_key(FROM_HEADER): +if FROM_HEADER in theMessage: try: - fromaddr = email.Utils.getaddresses( + fromaddr = email.utils.getaddresses( theMessage.get_all(FROM_HEADER))[-1][1] tmpcfgpath = os.path.join(configPath, fromaddr) if os.path.isfile(tmpcfgpath): @@ -352,7 +352,7 @@ def osxkeychain(service, passwordType="internet"): ### Read the file ### -config = ConfigParser.ConfigParser() +config = configparser.ConfigParser() try: config.read([configPath]) except OSError: @@ -417,7 +417,7 @@ def osxkeychain(service, passwordType="internet"): theSMTPPassword = keychain_func(theSMTPServer) except TypeError: exit_forcing_print(ERROR_CONFIG_KEYCHAIN) - except ConfigParser.NoOptionError: + except configparser.NoOptionError: theSMTPPassword = config.get(CONFIG_SECTION, OPTION_PASSWORD) theAuthenticateFlag = True @@ -438,24 +438,24 @@ def osxkeychain(service, passwordType="internet"): # If we are told to take the addresses from the message itself... if theRcpsFromMailFlag: - if theMessage.has_key(TO_HEADER): + if TO_HEADER in theMessage: theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( + [x[1] for x in email.utils.getaddresses( theMessage.get_all(TO_HEADER))] ) - if theMessage.has_key(CC_HEADER): + if CC_HEADER in theMessage: theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( + [x[1] for x in email.utils.getaddresses( theMessage.get_all(CC_HEADER))] ) - if theMessage.has_key(BCC_HEADER): + if BCC_HEADER in theMessage: theRecipients.extend( - [x[1] for x in email.Utils.getaddresses( + [x[1] for x in email.utils.getaddresses( theMessage.get_all(BCC_HEADER))] ) # Delete Bcc header if it exists -if theMessage.has_key(BCC_HEADER): +if BCC_HEADER in theMessage: del theMessage[BCC_HEADER] # Still no addresses found? @@ -504,7 +504,7 @@ def osxkeychain(service, passwordType="internet"): except smtplib.SMTPException: exit_conditional_print(ERROR_UNKNOWN) except (socket.herror, socket.gaierror, OSError) as err: - exit_conditional_print(ERROR_NETWORK % (theSMTPServer, err[1])) + exit_conditional_print(ERROR_NETWORK % (theSMTPServer, str(err))) try: server.quit() @@ -514,4 +514,4 @@ def osxkeychain(service, passwordType="internet"): # Check for rejected recipients if rejectedRecipients: conditional_print(WARNING_REJECTED) - conditional_print('\n'.join(['\t' + email for email in rejectedRecipients])) + conditional_print('\n'.join(['\t' + recipient for recipient in rejectedRecipients])) From 26763d4d10cbfade785eda0ab5d1131e8754536d Mon Sep 17 00:00:00 2001 From: Blacklight Shining Date: Mon, 3 Oct 2016 03:45:00 -0700 Subject: [PATCH 7/7] Moved SMTP.connect() call to `SMTP` initialization This means that errors in SMTP.connect() will cause putmail to print specific errors of its own, rather than ERROR_OTHER and a copy of the server's error response. (Probably. I actually made this change months ago and I'll admit to not remembering why.) --- putmail.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/putmail.py b/putmail.py index 404a9bd..664529a 100755 --- a/putmail.py +++ b/putmail.py @@ -468,11 +468,11 @@ def osxkeychain(service, passwordType="internet"): try: if theTLSMode == TLS_MODE_NATIVE: - server = smtplib.SMTP_SSL(context=theTLSContext) + server = smtplib.SMTP_SSL(theSMTPServer, thePort, + context=theTLSContext) else: - server = smtplib.SMTP() + server = smtplib.SMTP(theSMTPServer, thePort) #server.set_debuglevel(True) - check_status(*server.connect(theSMTPServer, thePort)) check_status(*server.ehlo('0.0.0.0')) if theTLSMode == TLS_MODE_STARTTLS: check_status(*server.starttls(context=theTLSContext))