Commits

George Notaras committed 59a772d

Initial code import

Comments (0)

Files changed (14)

+George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+
+simsmtp Bugs
+------------------------------------------------------------------------
+
+You can view/search existing tickets or submit new ones at:
+
+	https://www.codetrax.org/projects/simsmtp/report
+
+Bugs reported by means other than the ticket system are silently ignored.
+
+simsmtp Changelog
+------------------------------------------------------------------------
+
+0.0.0 -> 0.1.0
+
+* Initial public alpha release
+
+simsmtp Installation Instructions
+------------------------------------------------------------------------
+
+To install simsmtp run:
+
+  python setup.py install
+
+For installation to a custom location you can use something like the following:
+
+  python setup.py install --prefix=/usr --root=/tmp
+
+For more information about the 'install' command, study the help message:
+
+  python setup.py --help install
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+include AUTHORS
+include BUGS
+include ChangeLog
+include INSTALL
+include LICENSE
+include MANIFEST.in
+include NOTICE
+include README
+include setup.cfg
+include setup.py
+include scripts/simsmtp
+recursive-include simsmtp *.py
+This product includes software developed at:
+	- CodeTRAX (http://www.codetrax.org/)
+	- G-Loaded (http://www.g-loaded.eu/)
+
+Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+
+Trade names include, but are not limited to:
+
+    * "simsmtp"
+    * "TRAX"
+    * "codeTRAX"
+    * "G-Loaded"
+
+The aforementioned names must not appear in the names of products derived from
+this software without prior written permission of George Notaras.
+
+For license information, please read the LICENSE file.
+
+simsmtp
+------------------------------------------------------------------------
+
+simsmtp is a ...
+
+... blah blah ...
+
+Project Development Site and Downloads:
+	https://www.codetrax.org/projects/simsmtp
+
+Project Summary :
+	N/A
+
+Project News:
+	N/A
+
+Community Support:
+	N/A
+
+
+Requirements
+============
+
+In order to run simsmtp you will need:
+
+- blah version (TODO)
+
+
+Installation
+============
+
+For installation instructions please read the INSTALL file.
+
+
+Configuration
+=============
+
+TODO
+
+
+Bugs
+====
+
+You can view existing tickets or submit new ones (requires registration) at:
+
+	https://www.codetrax.org/projects/simsmtp/report/1
+
+Bugs reported by other means than the ticket system are silently ignored.
+
+
+Support
+=======
+
+CodeTRAX does not provide support for simsmtp.
+You can still get community support at the Community Support Forums:
+
+	N/A
+
+
+Copyright
+=========
+
+Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+
+
+License
+=======
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+A copy of the License exists in the product distribution; the LICENSE file.
+For copyright notes please read the NOTICE file.
+
+
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  This file is part of simsmtp.
+#
+#  simsmtp - 
+#
+#  Project: https://www.codetrax.org/projects/simsmtp
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+# The following makes it possible to run the script from
+# the current location during development.
+#import sys
+#sys.path = ['../'] + sys.path
+
+from simsmtp.main import main
+
+main()
+[install]
+optimize = 1
+
+[bdist_rpm]
+doc_files = AUTHORS BUGS ChangeLog LICENSE NOTICE README
+vendor = CodeTRAX <http://www.codetrax.org/>
+#group = Applications/Communications
+
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  This file is part of simsmtp.
+#
+#  simsmtp - 
+#
+#  Project: https://www.codetrax.org/projects/simsmtp
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  NOTES
+#
+#  Create source distribution tarball:
+#    python setup.py sdist --formats=gztar
+#
+#  Create binary distribution rpm:
+#    python setup.py bdist --formats=rpm
+#
+#  Create binary distribution rpm with being able to change an option:
+#    python setup.py bdist_rpm --release 7
+#
+#  Test installation:
+#    python setup.py install --prefix=/usr --root=/tmp
+#
+#  Install:
+#    python setup.py install
+#  Or:
+#    python setup.py install --prefix=/usr
+#
+
+
+from distutils.core import setup
+#from setuptools import setup
+from simsmtp import info
+
+if __name__=='__main__':
+	setup(
+		name = info.name,
+		version = info.version,
+		description = info.description,
+		long_description = info.long_description,
+		author = info.author,
+		author_email = info.author_email,
+		url = info.url,
+		download_url = info.download_url,
+		license = info.license,
+		classifiers = info.classifiers,
+		packages = [
+				'simsmtp',
+				],
+		scripts = ['scripts/simsmtp']
+		)

src/simsmtp/__init__.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of simsmtp.
+#
+#  simsmtp - 
+#
+#  Project: https://www.codetrax.org/projects/simsmtp
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#

src/simsmtp/info.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of simsmtp.
+#
+#  simsmtp - 
+#
+#  Project: https://www.codetrax.org/projects/simsmtp
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+version = '0.1.0'
+status = 'alpha'
+name = 'simsmtp'
+description = """simsmtp is a ..."""
+long_description = """simsmtp is a ..."""
+author = 'George Notaras'
+author_email = 'gnot [at] g-loaded.eu'
+url = 'http://www.codetrax.org/projects/simsmtp'
+download_url = "http://www.codetrax.org/downloads/projects/" + name + "/" + name + "-" + version + ".tar.gz"
+license = "Apache License version 2"
+
+# Automate the development status for classifiers
+devel_status = ''
+if status == 'pre-alpha':
+	devel_status = 'Development Status :: 2 - Pre-Alpha'
+if status == 'alpha':
+	devel_status = 'Development Status :: 3 - Alpha'
+if status == 'beta':
+	devel_status = 'Development Status :: 4 - Beta'
+if status == 'stable':
+	devel_status = 'Development Status :: 5 - Production/Stable'
+
+# For a list of classifiers check: http://www.python.org/pypi/
+# (http://pypi.python.org/pypi?:action=list_classifiers)
+
+classifiers = [
+	devel_status,
+	'Environment :: Console',
+	'Intended Audience :: System Administrators',
+	'License :: OSI Approved :: Apache Software License',
+	'Natural Language :: English',
+	'Operating System :: OS Independent',
+	'Programming Language :: Python',
+	'Topic :: Utilities',
+	]
+
+def get_version():
+	return name + ' v' + version + '/' + status
+

src/simsmtp/main.py

+# -*- coding: utf-8 -*-
+#
+#  This file is part of simsmtp.
+#
+#  simsmtp - 
+#
+#  Project: https://www.codetrax.org/projects/simsmtp
+#
+#  Copyright 2009 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+
+import sys
+import os
+import smtplib
+import logging
+import socket
+import threading
+import time
+import random
+from optparse import OptionParser
+
+import mimetypes
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.MIMEText import MIMEText
+from email import Encoders
+
+import info
+
+__version__ = info.version
+__program__ = info.name
+#__program__ = os.path.basename(sys.argv[0])
+
+
+class SMTPClient(threading.Thread):
+	
+	def __init__(self, host='localhost', port=25, sender_pool=[], recipient_pool=[], msg_body_pool=[], attachment_pool=[], nrmsg=1):
+		threading.Thread.__init__(self)
+		self.host = host
+		self.port = port
+		self.sender_pool = sender_pool
+		self.recipient_pool = recipient_pool
+		self.msg_body_pool = msg_body_pool
+		self.attachment_pool = attachment_pool
+		self.nrmsg = nrmsg	# number of messages to send
+		
+		# Statistics
+		self.successful = 0		# counter
+		self.longest_send = 0	# gauge
+	
+	def run(self):
+		# If more than one thread are used and if delay has been specified with the "min,max"
+		# format (which implies randomness in the delay), delay here, so that not all clients
+		# start sending at the same moment. If a static delay has been set, then there is no point
+		# in delaying here, as all clients will start sending simultaneusly anyway.
+		if options.count > 1 and len(options.delay) > 1:
+			delay(self.name)
+			
+		for n in range(self.nrmsg):
+			try:
+				t_start = time.time()
+				self.sendmail()
+				t_end = time.time()
+			except socket.error, e:
+				logger.critical( '%s ABORT ALL SENDS! %s' % (self.name, e) )
+				return
+			except smtplib.SMTPException, e:
+				logger.error( '%s failed to send email #%d %s' % (self.name, n+1, e) )
+			else:
+				self.set_stats(t_start, t_end, success=True)
+				logger.info( '%s sent email #%d of %d' % (self.name, n+1, self.nrmsg) )
+			if n+1 != self.nrmsg:	# do not delay after sending the last message
+				delay(self.name)
+		logger.info( '%s finished' % self.name )
+	
+	def set_stats(self, t_start, t_end, success=True):
+		if success:
+			self.successful += 1
+			t_send = t_end - t_start
+			if t_send > self.longest_send:
+				self.longest_send = t_send
+			
+	def sendmail(self):
+		from_addr = random.choice(self.sender_pool)
+		to_addr = random.choice(self.recipient_pool)
+		# Ensure from_addr != to_addr
+		#if len(self.recipient_pool) > 1:	# TODO: this is probably incomplete. An endless loop might be hiding in here...
+		#	# Loop until recipient is different from sender in the case the same file is used for senders and recipients
+		#	while from_addr == to_addr:
+		#		to_addr = random.choice(self.recipient_pool)
+		msg_txt = self.get_email_message(from_addr, to_addr)
+		server = smtplib.SMTP(self.host, self.port)
+		if options.localhostname:
+			server.helo(options.localhostname)
+		server.sendmail(from_addr, to_addr, msg_txt)
+		server.quit()
+	
+	def get_email_message(self, from_addr, to_addr):
+		"""
+		Returns the message object as string
+		"""
+		if not options.subject:
+			options.subject = 'Stress Test'
+		subject = '[%s][%s] -- %s' % (PID, self.name, options.subject)
+		
+		if self.attachment_pool:
+			# Create the container email message.
+			msg_obj = MIMEMultipart()
+			msg_obj["From"] = from_addr
+			msg_obj["To"] = to_addr
+			msg_obj["Subject"] = subject
+			msg_obj.preamble = ''
+			# Guarantees the message ends in a newline
+			msg_obj.epilogue = ''
+			
+			# TEXT PART
+			body, filename = self.get_content_from_random_file(self.msg_body_pool)
+			msg_text = MIMEText(body, "plain", "utf-8")
+			msg_obj.attach(msg_text)
+			
+			# ATTACHMENT PART
+			file_data, filename = self.get_content_from_random_file(self.attachment_pool.keys())
+			mime_type, mime_subtype = self.attachment_pool[filename]
+			msg_atch = MIMEBase(mime_type, mime_subtype)
+			msg_atch.set_payload(file_data)
+			# Encode the payload using Base64
+			Encoders.encode_base64(msg_atch)
+			# Set the filename parameter
+			msg_atch.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
+			msg_obj.attach(msg_atch)
+		else:
+			# Simple text message
+			body, filename = self.get_content_from_random_file(self.msg_body_pool)
+			msg_obj = MIMEText(body, 'plain', 'utf-8')
+			msg_obj['From'] = from_addr
+			msg_obj['To'] = to_addr
+			msg_obj['Subject'] = subject
+		return msg_obj.as_string()
+	
+	def get_content_from_random_file(self, filename_pool):
+		contents = ''
+		if filename_pool:
+			fpath = random.choice(filename_pool)
+			f = open(fpath, 'rb')
+			contents = f.read()
+			f.close()
+		return contents, fpath
+
+
+def parseargs():
+	parser = OptionParser()
+	parser.add_option('-f', '--sender', dest='sender', help='The sender\'s email address. A path to a file containing email addresses (one per line) can be used. In that case, a random sender will be chosen for each email.', metavar='ADDRESS or PATH')
+	parser.add_option('-r', '--recipient', dest='recipient', help='The recipient\'s email address. A path to a file containing email addresses (one per line) can be used. In that case, a random recipient will be chosen for each email.', metavar='ADDRESS or PATH')
+	parser.add_option('-m', '--message', dest='message', help='Path to a text file which will be used as the message body. If the provided path is a directory, a random choice of the files inside the directory will be made. No check will be performed whether the file is a text file or not!!!', metavar='PATH')
+	parser.add_option('-a', '--attachment', dest='attachment', default='', help='Path to a file which will be attached to the emails. If the provided path is a directory, a random choice of the files inside the directory will be made and one of them will be finally attached to the email. No kinds of checks will be performed on the file(s)!!!', metavar='PATH')
+	parser.add_option('-s', '--subject', dest='subject', help='A subject that will be set to the email messages. Enclose in quotes if it contains spaces -- very likely.', metavar='"SUBJECT TEXT"')
+	parser.add_option('-k', '--host', dest='host', default='localhost', help='The server\'s hostname or IP address.', metavar='HOSTNAME')
+	parser.add_option('-p', '--port', dest='port', type="int", default=25, help='The SMTP server\'s port', metavar='PORT')
+	parser.add_option('-c', '--count', dest='count', type="int", default=1, help='The number of threads to start.', metavar='NUMBER')
+	parser.add_option('-n', '--number-of-msgs', dest='nrmsg', type="int", default=1, help='The number of messages each thread will send.', metavar='NUMBER')
+	parser.add_option('-d', '--delay', dest='delay', default='1', help='The number of seconds to wait between sends. The delay can be specified as a "min,max" delay (no spaces). In that case, a random delay between min & max will be used.', metavar='SECONDS')
+	parser.add_option('-l', '--local-hostname', dest='localhostname', default='localhost', help='If specified, this hostname will be sent to the remote server in the HELO command.', metavar='HOSTNAME')
+	options, args = parser.parse_args()
+	if args:
+		parser.error('Wrong number of arguments')
+	elif not options.sender:
+		parser.error('Sender is required')
+	elif not options.recipient:
+		parser.error('Recipient is required')
+	elif not options.message:
+		parser.error('Path to message or path to dir with messages is required')
+	elif options.delay:
+		# Check if specified delays are integers and create a list
+		try:
+			options.delay = [int(i) for i in options.delay.split(',')]
+		except ValueError:
+			parser.error('Invalid delay')
+		else:
+			if len(options.delay) not in (1, 2):
+				parser.error('Invalid delay')
+	return options
+
+
+def get_logger():
+	logger = logging.getLogger()
+	logger.setLevel(logging.DEBUG)
+	stderr_handler = logging.StreamHandler(sys.stderr)
+	stderr_formatter = logging.Formatter('%(asctime)s -- %(message)s', '%Y-%m-%d %H:%M:%S')
+	stderr_handler.setFormatter(stderr_formatter)
+	logger.addHandler(stderr_handler)
+	return logger
+
+
+def get_pool_from_file(option):
+	path = os.path.abspath(option)
+	file_pool = []
+	if os.path.isfile(path):
+		f = open(path, 'rb')
+		for line in f:
+			line = line.strip()
+			if line:
+				file_pool.append(line)
+		f.close()
+	else:
+		file_pool.append(option)
+	return file_pool
+
+
+def get_msg_body_pool():
+	path = os.path.abspath(options.message)
+	msg_body_pool = []
+	if os.path.isfile(path):
+		msg_body_pool.append(path)
+	elif os.path.isdir(path):
+		for fname in os.listdir(path):
+			fname_path = os.path.join(path, fname)
+			if os.path.isfile(fname_path):
+				msg_body_pool.append(fname_path)
+	return msg_body_pool
+
+
+def get_attachment_pool():
+	"""
+	Attachment pool is a dictionary. Format:
+	{ '<path_to_file>' : <mimetype as tuple (type, subtype)>, }
+	
+	Example:
+	{ '/tmp/readme.txt' : ('text', 'plain'), }
+	"""
+	attachment_pool = {}
+	if not options.attachment:
+		return attachment_pool
+	
+	def add_to_pool(path):
+		mime_type, encoding = mimetypes.guess_type(path)
+		attachment_pool[path] = mime_type.split('/')
+	
+	path = os.path.abspath(options.attachment)
+	if os.path.isfile(path):
+		add_to_pool(path)
+	elif os.path.isdir(path):
+		for fname in os.listdir(path):
+			fname_path = os.path.join(path, fname)
+			if os.path.isfile(fname_path):
+				add_to_pool(fname_path)
+	return attachment_pool
+
+
+def delay(name):
+	# options.delay has been transformed to a list in parseargs()
+	if len(options.delay) > 1:
+		n = random.randint(options.delay[0], options.delay[-1])
+	else:
+		n = options.delay[0]
+	logger.debug('%s sleeping for %s second(s)' % (name, n))
+	time.sleep(n)
+
+
+def stress():
+	pool = {}	# smtp client pool
+	sender_pool = get_pool_from_file(options.sender)
+	recipient_pool = get_pool_from_file(options.recipient)
+	msg_body_pool = get_msg_body_pool()
+	attachment_pool = get_attachment_pool()
+	# print sender_pool
+	# print recipient_pool
+	# print msg_body_pool
+	# print attachment_pool
+	for n in range(options.count):
+		name = 'smtp-%s' % str(n+1).zfill(len(str(options.count)))
+		pool[name] = SMTPClient(
+			host = options.host,
+			port = options.port,
+			sender_pool = sender_pool,
+			recipient_pool = recipient_pool,
+			msg_body_pool = msg_body_pool,
+			attachment_pool = attachment_pool,
+			nrmsg=options.nrmsg
+		)
+		pool[name].name = name
+		pool[name].start()
+
+	for k in pool.keys():
+		pool[k].join()
+	
+	summary(pool)
+
+
+def summary(pool):
+	print
+	print 'Results'
+	print '-'*40
+	tot_success = 0
+	longest_send = 0
+	for k in pool.keys():
+		tot_success += pool[k].successful
+		if pool[k].longest_send > longest_send:
+			longest_send = pool[k].longest_send
+		print '%s %s/%s' % (pool[k].name.ljust(10), pool[k].successful, options.nrmsg)
+	print '-'*40
+	print 'Total success: %s/%s' % (tot_success, options.count * options.nrmsg)
+	print 'Longest send : %.3f sec' % longest_send
+
+
+def main():
+	t_start = time.time()
+	print
+	print 'Using %s v%s' % (__program__, __version__)
+	print 'Attempting a total of %d messages using %d threads' % (options.count * options.nrmsg, options.count)
+	print
+	stress()
+	t_finish = time.time()
+	print 'Elapsed %.3f seconds' % (t_finish - t_start)
+
+
+options = parseargs()
+logger = get_logger()
+PID = os.getpid()	# for use in the subject
+
+if __name__ == '__main__':
+	main()
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.