diff options
-rw-r--r-- | src/mail2news.py | 98 | ||||
-rw-r--r-- | src/news2mail.py | 118 | ||||
-rwxr-xr-x[-rw-r--r--] | test/test_pyg.py | 50 |
3 files changed, 234 insertions, 32 deletions
diff --git a/src/mail2news.py b/src/mail2news.py index 6e8fe53..dc05c1d 100644 --- a/src/mail2news.py +++ b/src/mail2news.py @@ -14,6 +14,7 @@ Gets news email and sends it via SMTP. class mail2news is hopefully conform to rfc850. """ +import argparse import io from collections import OrderedDict import email @@ -31,7 +32,7 @@ import tempfile # This is the single source of Truth # Yes, it is awkward to have it assymetrically here # and not in news2mail as well. -__version__ = '0.10.3' +__version__ = '0.11.0' __description__ = 'The Python Gateway Script: news2mail mail2news gateway' @@ -55,9 +56,14 @@ class mail2news(object): self.hostname = gethostbyaddr(gethostname())[0] self.heads_dict, self.smtpheads, self.nntpheads = {}, {}, {} - self.message = self.__readfile(options) + if options.input == '': + self.message = self.__readfile(options, sys.stdin) + else: + with open(options.input, 'r') as inp_stream: + self.message = self.__readfile(options, inp_stream) - self.message['X-Gateway'] = 'pyg {0} {1}'.format(VERSION, DESC) + self.message['X-Gateway'] = 'pyg {0} {1}'.format(__version__, + __description__) def __add_header(self, header, value, msg=None): if msg is None: @@ -65,8 +71,8 @@ class mail2news(object): if value: msg[header] = value.strip() - def __readfile(self, opt): - message = email.message_from_file(sys.stdin, policy=email.policy.SMTP) + def __readfile(self, opt, input_stream): + message = email.message_from_file(input_stream, policy=email.policy.SMTP) if (len(message) == 0) \ and message.get_payload().startswith('/'): @@ -193,3 +199,85 @@ class mail2news(object): logging.exception("Failed to convert message!") server.quit() + +def parse_cmdline(args): + parser = argparse.ArgumentParser( + description='%s version %s - Copyright 2000 Cosimo Alfarano\n%s' % + ('pyg', __version__, __description__)) + + parser.add_argument('-s', '--newsserver', default='') + parser.add_argument('-a', '--approver', default='', + help="address of moderator/approver") + parser.add_argument('-n', '--newsgroup', default='', + help='newsgroup[s] (specified as comma separated ' + + 'without spaces list)', required=True) + parser.add_argument('-u', '--user', default='', + help='NNTP server user (for authentication)') + parser.add_argument('-p', '--password', default='', + help='NNTP server password (for authentication)') + parser.add_argument('-P', '--port', default='') + parser.add_argument('-e', '--envelope', default='') + parser.add_argument('-i', '--input', default='') + parser.add_argument('-l', '--logfile') + + parser.add_argument('-T', '--test', action='store_true', + help='test mode (not send article via NNTP)') + parser.add_argument('-v', '--verbose', action='store_true', + help='verbose output ' + + '(usefull with -T option for debugging)') + + args = parser.parse_args(args) + + if not args.newsgroup: + raise argparse.ArgumentError('Error: Missing Newsgroups\n') + + return args + + +def main(args_in=None): + """main is structured in 4 phases: + 1) check and set pyg's internal variables + 2) check whitelist for users' permission + 3) format rfc 822 headers from input article + 4) open smtp connection and send e-mail + """ + out = '' + + try: + """phase 1: + check and set pyg's internal variables + """ + if args_in is None: + args_in = sys.argv[1:] + opt = parse_cmdline(args_in) + + m2n = mail2news(opt) + owner = None + + """phase 3: + format rfc 822 headers from input article + """ + m2n.renameheads() # rename useless heads + m2n.removeheads() # remove other heads + + m2n.sortheads() # sort remaining heads :) + + if opt.verbose: + out += m2n.message.as_string() + '\n' + + logging.debug('m2n.payload = len %d', len(m2n.message.get_payload())) + if len(m2n.message.get_payload()) > 0: + # wl.logmsg(m2n.heads_dict,wl.ACCEPT,owner) + if not opt.test: + try: + resp = m2n.sendemail() + except nntplib.NNTPError as ex: + logging.exception(ex) + except KeyboardInterrupt: + logging.error('Keyboard Interrupt') + sys.exit(0) + + if opt.input == '': + print(out) + else: + return out diff --git a/src/news2mail.py b/src/news2mail.py index 33d2590..f99b0d7 100644 --- a/src/news2mail.py +++ b/src/news2mail.py @@ -22,8 +22,7 @@ normal (what pygs does) operations flow is: Date:, normal headers ending with X-* and Resent-* headers. """ -from __future__ import absolute_import -from __future__ import print_function +import argparse from collections import OrderedDict import email import email.policy @@ -31,7 +30,7 @@ import smtplib from socket import gethostbyaddr, gethostname import sys import time -from mail2news import VERSION, DESC +import mail2news # logging.basicConfig(level=logging.DEBUG) @@ -60,7 +59,8 @@ class news2mail(object): """add new header like X-Gateway: Received: """ - msg['X-Gateway'] = 'pyg {0} {1}'.format(VERSION, DESC) + msg['X-Gateway'] = 'pyg {0} {1}'.format(mail2news.__version__, + mail2news.__description__) # to make Received: header t = time.ctime(time.time()) @@ -172,3 +172,113 @@ class news2mail(object): s.quit() +def parse_cmdline(a_in): + """ + set a dictionary with smtp new header in gw parameter (gw.smtpheads) + return (test,verbose) boolean tuple + """ + parser = argparse.ArgumentParser( + description='pyg version %s - Copyright 2000 Cosimo Alfarano\n%s' % + (mail2news.__version__, mail2news.__description__)) + + parser.add_argument('-H', '--smtpserver', default='') + parser.add_argument('-s', '--sender', required=True, default='') + parser.add_argument('-e', '--envelope', default='') + parser.add_argument('-t', '--to', dest='rcpt', required=True) + parser.add_argument('-w', '--wlfile') + parser.add_argument('-i', '--input', default='') + parser.add_argument('-l', '--logfile') + + parser.add_argument('-T', '--test', + help='test mode (not send article via SMTP)', + action='store_true') + parser.add_argument('-v', '--verbose', help='verbose output', + action='store_true') + + opts = parser.parse_args(a_in) + +# By rfc822 [Resent-]Sender: should be ever set, unless == From: +# (not this case). Should be a human, while [Resent-]From: may be a program. + + if opts.rcpt == '' or opts.sender == '': + raise argparse.ArgumentError('missing command line option') + + if opts.envelope == '' and opts.sender != '': + opts.envelope = opts.sender + + return opts + +def main(args_in=None): + """main is structured in 4 phases: + 1) check and set pyg's internal variables + 2) check whitelist for users' permission + 3) format rfc 822 headers from input article + 4) open smtp connection and send e-mail + """ + + """phase 1: + check and set pyg's internal variables + """ + out = '' + + # it returns only test, other parms are set directly in the actual + # parameter + if args_in is None: + args_in = sys.argv[1:] + args = parse_cmdline(args_in) + + n2m = news2mail(args) + owner = None + + # check if n2m has some file prefercences set on commandline + if args.wlfile is None: + wl = os.path.expanduser(os.path.join(os.path.dirname(__file__), 'pyg.whitelist')) + else: + wl = args.wlfile + + if args.logfile is None: + log = os.path.expanduser(os.path.join(os.path.dirname(__file__), 'pyg.log')) + else: + log = args.logfile + + wl = whitelist.whitelist(wl, log) + + """phase 2: + check whitelist for user's permission + """ + + # make a first check of From: address + owner = wl.checkfrom(n2m.message['From']) + if owner is None: + if sys.stdin.isatty() == 1 or args.test: + out += str('"%s" is not in whitelist!' % (n2m.message['From'][:-1])) + '\n' + else: + wl.logmsg(n2m.nntpheads, wl.DENY) + + # if verbose, I want to print out headers, so I can't + # exit now. + if not args.verbose: + sys.exit(1) + + # Reformat the message + n2m.process_message() + + # prints formatted email message only (without send) if user wants + if args.verbose: + out += n2m.message.as_string() + '\n' + + if owner is None: + sys.exit(1) + + """phase 4: + open smtp connection and send e-mail + """ + + wl.logmsg(n2m.heads_dict, wl.ACCEPT, owner) + if not args.test: + n2m.sendarticle() + + if args.input == '': + print(out) + else: + return out diff --git a/test/test_pyg.py b/test/test_pyg.py index 6044520..6777bd2 100644..100755 --- a/test/test_pyg.py +++ b/test/test_pyg.py @@ -1,12 +1,10 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import import re import subprocess import unittest import mail2news +import news2mail @unittest.skip('not ready') @@ -26,15 +24,18 @@ X-Gateway: pyg %s %s one line test -""" % (mail2news.VERSION, mail2news.DESC) +""" % (mail2news.__version__, mail2news.__description__) def test_m2n(self): - with open('examples/mail') as in_mail: - pid = subprocess.Popen([sys.executable, 'src/mail2news.py', '-Tv', '-n', 'pyg.test'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - universal_newlines=True) - out, _ = pid.communicate(in_mail.read()) + # with open('examples/mail') as in_mail: + # pid = subprocess.Popen([sys.executable, + # 'src/mail2news.py', '-Tv', '-n', 'pyg.test'], + # stdin=subprocess.PIPE, + # stdout=subprocess.PIPE, + # universal_newlines=True) + # out, _ = pid.communicate(in_mail.read()) + out = mail2news.main(['-T', '-v', + '-i', 'examples/mail', '-n', 'pyg.test']) self.assertEqual(out, self.expected_output) @@ -57,26 +58,29 @@ X-Gateway: pyg %s %s X-NNTP-Posting-Host: pyg.server.tld Resent-From: sender@example.com Resent-Sender: sender@example.com -""" % (mail2news.VERSION, mail2news.DESC) +""" % (mail2news.__version__, mail2news.__description__) def test_n2m(self): - with open('examples/articletest.accepted') as in_mail: - pid = subprocess.Popen([sys.executable, 'src/news2mail.py', '-Tvt', 'test@example.com', - '-s', 'sender@example.com', - '-w', 'examples/whitelist.example'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - universal_newlines=True) - in_message = in_mail.read().replace('pyg@pyg.server.tld', - 'kame@inwind.it') - out, err = pid.communicate(in_message) - out = re.sub(r'^Message-Id:.*$', '', out) + # with open('examples/articletest.accepted') as in_mail: + # pid = subprocess.Popen([sys.executable, 'src/news2mail.py', '-Tvt', 'test@example.com', + # '-s', 'sender@example.com', + # '-w', 'examples/whitelist.example'], + # stdin=subprocess.PIPE, + # stdout=subprocess.PIPE, + # universal_newlines=True) + # in_message = in_mail.read().replace('pyg@pyg.server.tld', + # 'kame@inwind.it') + # out, err = pid.communicate(in_message) + out = news2mail.main(['-T', '-v', '-t', 'test@example.com', + '-s', 'sender@example.com', + '-w', 'examples/whitelist.example']) + out = re.sub(r'^Message-Id:.*$', '', out) # Not sure how to compare two email mesages (with different # times, etc.) so for now just to make sure the script doesn’t # blow up and gives some output # otherwise it would be # self.assertEqual(out, expected_output) - self.assertEqual(pid.returncode, 0) + # self.assertEqual(pid.returncode, 0) self.assertGreater(len(out), 0) |