def main(): parser = optparse.OptionParser(description=__doc__, usage="%prog [options] queue_path") parser.add_option( "-C", "--config", dest="config", default=None, help="Path to configuration file (defaults to $CWD/etc/karl.ini)", metavar="FILE", ) parser.add_option("--daemon", "-D", dest="daemon", action="store_true", default=False, help="Run in daemon mode.") parser.add_option( "--interval", "-i", dest="interval", type="int", default=6 * 3600, help="Interval, in seconds, between executions when in " "daemon mode.", ) parser.add_option( "--server", "-s", dest="hostname", default="localhost", help="SMTP server host name", metavar="HOST" ) parser.add_option("--port", "-P", dest="port", type="int", default=25, help="Port of SMTP server", metavar="PORT") parser.add_option("--username", "-u", dest="username", default=None, help="Username, if authentication is required") parser.add_option("--password", "-p", dest="password", default=None, help="Password, if authentication is required") parser.add_option( "--force-tls", "-f", dest="force_tls", action="store_true", default=False, help="Require that TLS be used." ) parser.add_option( "--no-tls", "-n", dest="no_tls", action="store_true", default=False, help="Require that TLS not be used." ) options, args = parser.parse_args() if not args: parser.error("Please specify queue path.") elif len(args) > 1: parser.error("Too many arguments.") queue_path = args[0] config = options.config if config is None: config = get_default_config() app = loadapp("config:%s" % config, "karl") set_subsystem("mailout") mailer = SMTPMailer( hostname=options.hostname, port=options.port, username=options.username, password=options.password, no_tls=options.no_tls, force_tls=options.force_tls, ) qp = QueueProcessor(mailer, queue_path) if options.daemon: run_daemon("digest", qp.send_messages, options.interval) else: qp.send_messages()
def main(): parser = optparse.OptionParser( description=__doc__, usage="%prog [options] queue_path", ) parser.add_option('-C', '--config', dest='config', default=None, help='Path to configuration file (defaults to $CWD/etc/karl.ini)', metavar='FILE') parser.add_option('--daemon', '-D', dest='daemon', action='store_true', default=False, help='Run in daemon mode.') parser.add_option('--interval', '-i', dest='interval', type='int', default=6*3600, help='Interval, in seconds, between executions when in ' 'daemon mode.') parser.add_option('--server', '-s', dest='hostname', default="localhost", help='SMTP server host name', metavar='HOST') parser.add_option('--port', '-P', dest='port', type='int', default=25, help='Port of SMTP server', metavar='PORT') parser.add_option('--username', '-u', dest='username', default=None, help='Username, if authentication is required') parser.add_option('--password', '-p', dest='password', default=None, help='Password, if authentication is required') parser.add_option('--force-tls', '-f', dest='force_tls', action='store_true', default=False, help='Require that TLS be used.') parser.add_option('--no-tls', '-n', dest='no_tls', action='store_true', default=False, help='Require that TLS not be used.') options, args = parser.parse_args() if not args: parser.error('Please specify queue path.') elif len(args) > 1: parser.error('Too many arguments.') queue_path = args[0] config = options.config if config is None: config = get_default_config() app = loadapp('config:%s' % config, 'karl') set_subsystem('mailout') mailer = SMTPMailer( hostname=options.hostname, port=options.port, username=options.username, password=options.password, no_tls=options.no_tls, force_tls=options.force_tls ) qp = QueueProcessor(mailer, queue_path) if options.daemon: run_daemon('digest', qp.send_messages, options.interval) else: qp.send_messages()
class TestQueuedMailDeliveryWithMaildir(unittest.TestCase): def setUp(self): import os import tempfile from repoze.sendmail.queue import QueueProcessor self.dir = tempfile.mkdtemp() self.maildir_path = os.path.join(self.dir, "Maildir") self.qp = QueueProcessor(_makeMailerStub(), self.maildir_path) def tearDown(self): import shutil shutil.rmtree(self.dir) def _getTargetClass(self): from repoze.sendmail.delivery import QueuedMailDelivery return QueuedMailDelivery def _makeOne(self, queuePath="/tmp"): return self._getTargetClass()(queuePath) def test_send_w_non_ASCII_addrs(self): import os from email.mime import base import transaction from repoze.sendmail._compat import b delivery = self._makeOne(self.maildir_path) non_ascii = b("LaPe\xc3\xb1a").decode("utf-8") fromaddr = non_ascii + " <*****@*****.**>" toaddrs = (non_ascii + " <*****@*****.**>",) message = base.MIMEBase("text", "plain") message["From"] = fromaddr message["To"] = ",".join(toaddrs) delivery.send(fromaddr, toaddrs, message) self.assertTrue(os.listdir(os.path.join(self.maildir_path, "tmp"))) self.assertFalse(os.listdir(os.path.join(self.maildir_path, "new"))) transaction.commit() self.assertFalse(os.listdir(os.path.join(self.maildir_path, "tmp"))) self.assertTrue(os.listdir(os.path.join(self.maildir_path, "new"))) self.qp.send_messages() self.assertTrue(len(self.qp.mailer.sent_messages), 1) queued_fromaddr, queued_toaddrs, queued_message = self.qp.mailer.sent_messages[0] self.assertEqual(queued_fromaddr, fromaddr) self.assertEqual(queued_toaddrs, toaddrs)
def main(args): queue_path = args.get_setting('mail_queue_path') set_subsystem('mailout') mailer = SMTPMailer( hostname=args.server, port=args.port, username=args.username, password=args.password, no_tls=args.no_tls, force_tls=args.force_tls ) qp = QueueProcessor(mailer, queue_path) qp.send_messages()
class TestQueuedMailDeliveryWithMaildir(unittest.TestCase): def setUp(self): import os import tempfile from repoze.sendmail.queue import QueueProcessor self.dir = tempfile.mkdtemp() self.maildir_path = os.path.join(self.dir, 'Maildir') self.qp = QueueProcessor(_makeMailerStub(), self.maildir_path) def tearDown(self): import shutil shutil.rmtree(self.dir) def _getTargetClass(self): from repoze.sendmail.delivery import QueuedMailDelivery return QueuedMailDelivery def _makeOne(self, queuePath='/tmp'): return self._getTargetClass()(queuePath) def test_send_w_non_ASCII_addrs(self): import os from email.mime import base import transaction from repoze.sendmail._compat import b delivery = self._makeOne(self.maildir_path) non_ascii = b('LaPe\xc3\xb1a').decode('utf-8') fromaddr = non_ascii+' <*****@*****.**>' toaddrs = (non_ascii+' <*****@*****.**>',) message = base.MIMEBase('text', 'plain') message['From'] = fromaddr message['To'] = ','.join(toaddrs) delivery.send(fromaddr, toaddrs, message) self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'tmp'))) self.assertFalse(os.listdir(os.path.join(self.maildir_path, 'new'))) transaction.commit() self.assertFalse(os.listdir(os.path.join(self.maildir_path, 'tmp'))) self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'new'))) self.qp.send_messages() self.assertTrue(len(self.qp.mailer.sent_messages), 1) queued_fromaddr, queued_toaddrs, queued_message = ( self.qp.mailer.sent_messages[0]) self.assertEqual(queued_fromaddr, fromaddr) self.assertEqual(queued_toaddrs, toaddrs)
class TestQueuedMailDeliveryWithMaildir(unittest.TestCase): def setUp(self): import os import tempfile from repoze.sendmail.queue import QueueProcessor self.dir = tempfile.mkdtemp() self.maildir_path = os.path.join(self.dir, 'Maildir') self.qp = QueueProcessor(_makeMailerStub(), self.maildir_path) def tearDown(self): import shutil shutil.rmtree(self.dir) def _getTargetClass(self): from repoze.sendmail.delivery import QueuedMailDelivery return QueuedMailDelivery def _makeOne(self, queuePath='/tmp'): return self._getTargetClass()(queuePath) def test_send_w_non_ASCII_addrs(self): import os from email.mime import base import transaction from repoze.sendmail._compat import b delivery = self._makeOne(self.maildir_path) non_ascii = b('LaPe\xc3\xb1a').decode('utf-8') fromaddr = non_ascii + ' <*****@*****.**>' toaddrs = (non_ascii + ' <*****@*****.**>',) message = base.MIMEBase('text', 'plain') message['From'] = fromaddr message['To'] = ','.join(toaddrs) delivery.send(fromaddr, toaddrs, message) self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'tmp'))) self.assertFalse(os.listdir(os.path.join(self.maildir_path, 'new'))) transaction.commit() self.assertFalse(os.listdir(os.path.join(self.maildir_path, 'tmp'))) self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'new'))) self.qp.send_messages() self.assertTrue(len(self.qp.mailer.sent_messages), 1) queued_fromaddr, queued_toaddrs, queued_message = ( self.qp.mailer.sent_messages[0]) self.assertEqual(queued_fromaddr, fromaddr) self.assertEqual(queued_toaddrs, toaddrs)
def mailout(args, env): registry = env['registry'] queue_path = registry.settings['mail_queue_path'] mailer = SMTPMailer( hostname=args.server, port=args.port, username=args.username, password=args.password, no_tls=args.no_tls, force_tls=args.force_tls ) qp = QueueProcessor(mailer, queue_path) qp.send_messages()
class TestQueueProcessor(TestCase): def setUp(self): from repoze.sendmail.queue import QueueProcessor self.qp = QueueProcessor(_makeMailerStub(), '/foo/bar/baz', MaildirStub) self.qp.log = LoggerStub() self.dir = mkdtemp() def tearDown(self): shutil.rmtree(self.dir) def test_parseMessage(self): hdr = ('X-Actually-From: [email protected]\n' 'X-Actually-To: [email protected], [email protected]\n') msg = ('Header: value\n' '\n' 'Body\n') f, t, m = self.qp._parseMessage(StringIO(u(hdr + msg))) self.assertEqual(f, '*****@*****.**') self.assertEqual(t, ('*****@*****.**', '*****@*****.**')) self.assertEqual(m.as_string(), msg) def test_delivery(self): self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n') + b('X-Actually-To: [email protected], [email protected]\n') + b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() sent_message = self.qp.mailer.sent_messages[0] self.assertEqual(sent_message[0], '*****@*****.**') self.assertEqual(sent_message[1], ('*****@*****.**', '*****@*****.**')) self.assertEqual(sent_message[2].as_string(), 'Header: value\n\nBody\n') self.assertFalse(os.path.exists(self.filename), 'File exists') self.assertEqual( self.qp.log.infos, [('Mail from %s to %s sent.', ('*****@*****.**', '[email protected], [email protected]'), {})]) def test_error_logging(self): self.qp.mailer = BrokenMailerStub() self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n') + b('X-Actually-To: [email protected], [email protected]\n') + b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() self.assertEqual( self.qp.log.errors, [('Error while sending mail from %s to %s.', ('*****@*****.**', '[email protected], [email protected]'), { 'exc_info': 1 })]) def test_error_logging_no_addrs(self): self.qp.mailer = BrokenMailerStub() self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() self.assertEqual(self.qp.log.errors, [('Error while sending mail : %s ', (self.filename, ), { 'exc_info': True })]) def test_smtp_response_error_transient(self): # Test a transient error self.qp.mailer = SMTPResponseExceptionMailerStub(451) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n') + b('X-Actually-To: [email protected], [email protected]\n') + b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() # File must remail were it was, so it will be retried self.assertTrue(os.path.exists(self.filename)) self.assertEqual( self.qp.log.errors, [('Error while sending mail from %s to %s.', ('*****@*****.**', '[email protected], [email protected]'), { 'exc_info': 1 })]) def test_smtp_response_error_permanent(self): # Test a permanent error self.qp.mailer = SMTPResponseExceptionMailerStub(550) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n') + b('X-Actually-To: [email protected], [email protected]\n') + b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() # File must be moved aside self.assertFalse(os.path.exists(self.filename)) self.assertTrue( os.path.exists(os.path.join(self.dir, '.rejected-message'))) self.assertEqual( self.qp.log.errors, [('Discarding email from %s to %s due to a ' 'permanent error: %s', ('*****@*****.**', '[email protected], [email protected]', (550, 'Serious Error')), {})]) def test_concurrent_delivery(self): # Attempt to send message self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n') + b('X-Actually-To: [email protected], [email protected]\n') + b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) # Trick processor into thinking message is being delivered by # another process. head, tail = os.path.split(self.filename) tmp_filename = os.path.join(head, '.sending-' + tail) queue._os_link(self.filename, tmp_filename) try: self.qp.send_messages() self.assertEqual(self.qp.mailer.sent_messages, []) self.assertTrue(os.path.exists(self.filename), 'File does not exist') self.assertEqual(self.qp.log.infos, []) finally: os.unlink(tmp_filename) def test_concurrent_delivery_w_old_file(self): # Attempt to send message self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write( b('X-Actually-From: [email protected]\n' 'X-Actually-To: [email protected], [email protected]\n' 'Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) # Trick processor into thinking message is being delivered by # another process. head, tail = os.path.split(self.filename) tmp_filename = os.path.join(head, '.sending-' + tail) queue._os_link(self.filename, tmp_filename) os.utime(tmp_filename, (1, 1)) #mtime/utime 1970-01-01T00:00:01Z self.qp.send_messages() sent_message = self.qp.mailer.sent_messages[0] self.assertEqual(sent_message[0], '*****@*****.**') self.assertEqual(sent_message[1], ('*****@*****.**', '*****@*****.**')) self.assertEqual(sent_message[2].as_string(), 'Header: value\n\nBody\n') self.assertFalse(os.path.exists(self.filename), 'File still exists') self.assertEqual( self.qp.log.infos, [('Mail from %s to %s sent.', ('*****@*****.**', '[email protected], [email protected]'), {})]) self.assertFalse(os.path.exists(tmp_filename))
class TestQueueProcessor(TestCase): def setUp(self): from repoze.sendmail.queue import QueueProcessor self.qp = QueueProcessor(_makeMailerStub(), '/foo/bar/baz', MaildirStub) self.qp.log = LoggerStub() self.dir = mkdtemp() def tearDown(self): shutil.rmtree(self.dir) def test_parseMessage(self): hdr = ('X-Actually-From: [email protected]\n' 'X-Actually-To: [email protected], [email protected]\n') msg = ('Header: value\n' '\n' 'Body\n') f, t, m = self.qp._parseMessage(StringIO(u(hdr + msg))) self.assertEqual(f, '*****@*****.**') self.assertEqual(t, ('*****@*****.**', '*****@*****.**')) self.assertEqual(m.as_string(), msg) def test_delivery(self): self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n')+ b('X-Actually-To: [email protected], [email protected]\n')+ b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() sent_message = self.qp.mailer.sent_messages[0] self.assertEqual(sent_message[0], '*****@*****.**') self.assertEqual(sent_message[1], ('*****@*****.**', '*****@*****.**')) self.assertEqual(sent_message[2].as_string(), 'Header: value\n\nBody\n') self.assertFalse(os.path.exists(self.filename), 'File exists') self.assertEqual(self.qp.log.infos, [('Mail from %s to %s sent.', ('*****@*****.**', '[email protected], [email protected]'), {})]) def test_error_logging(self): self.qp.mailer = BrokenMailerStub() self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n')+ b('X-Actually-To: [email protected], [email protected]\n')+ b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() self.assertEqual(self.qp.log.errors, [('Error while sending mail from %s to %s.', ('*****@*****.**', '[email protected], [email protected]'), {'exc_info': 1})]) def test_error_logging_no_addrs(self): self.qp.mailer = BrokenMailerStub() self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() self.assertEqual(self.qp.log.errors, [('Error while sending mail : %s ', (self.filename,), {'exc_info': True})]) def test_smtp_response_error_transient(self): # Test a transient error self.qp.mailer = SMTPResponseExceptionMailerStub(451) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n')+ b('X-Actually-To: [email protected], [email protected]\n')+ b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() # File must remail were it was, so it will be retried self.assertTrue(os.path.exists(self.filename)) self.assertEqual(self.qp.log.errors, [('Error while sending mail from %s to %s.', ('*****@*****.**', '[email protected], [email protected]'), {'exc_info': 1})]) def test_smtp_response_error_permanent(self): # Test a permanent error self.qp.mailer = SMTPResponseExceptionMailerStub(550) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n')+ b('X-Actually-To: [email protected], [email protected]\n')+ b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) self.qp.send_messages() # File must be moved aside self.assertFalse(os.path.exists(self.filename)) self.assertTrue(os.path.exists(os.path.join(self.dir, '.rejected-message'))) self.assertEqual(self.qp.log.errors, [('Discarding email from %s to %s due to a ' 'permanent error: %s', ('*****@*****.**', '[email protected], [email protected]', (550, 'Serious Error')), {})]) def test_concurrent_delivery(self): # Attempt to send message self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n')+ b('X-Actually-To: [email protected], [email protected]\n')+ b('Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) # Trick processor into thinking message is being delivered by # another process. head, tail = os.path.split(self.filename) tmp_filename = os.path.join(head, '.sending-' + tail) queue._os_link(self.filename, tmp_filename) try: self.qp.send_messages() self.assertEqual(self.qp.mailer.sent_messages, []) self.assertTrue(os.path.exists(self.filename), 'File does not exist') self.assertEqual(self.qp.log.infos, []) finally: os.unlink(tmp_filename) def test_concurrent_delivery_w_old_file(self): # Attempt to send message self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write(b('X-Actually-From: [email protected]\n' 'X-Actually-To: [email protected], [email protected]\n' 'Header: value\n\nBody\n')) temp.close() self.qp.maildir.files.append(self.filename) # Trick processor into thinking message is being delivered by # another process. head, tail = os.path.split(self.filename) tmp_filename = os.path.join(head, '.sending-' + tail) queue._os_link(self.filename, tmp_filename) os.utime(tmp_filename, (1,1)) #mtime/utime 1970-01-01T00:00:01Z self.qp.send_messages() sent_message = self.qp.mailer.sent_messages[0] self.assertEqual(sent_message[0], '*****@*****.**') self.assertEqual(sent_message[1], ('*****@*****.**', '*****@*****.**')) self.assertEqual(sent_message[2].as_string(), 'Header: value\n\nBody\n') self.assertFalse(os.path.exists(self.filename), 'File still exists') self.assertEqual(self.qp.log.infos, [('Mail from %s to %s sent.', ('*****@*****.**', '[email protected], [email protected]'), {})]) self.assertFalse(os.path.exists(tmp_filename))