diff options
Diffstat (limited to 'mail2news.py')
-rw-r--r-- | mail2news.py | 374 |
1 files changed, 254 insertions, 120 deletions
diff --git a/mail2news.py b/mail2news.py index 38ae507..f15ca4e 100644 --- a/mail2news.py +++ b/mail2news.py @@ -3,7 +3,7 @@ Author: Cosimo Alfarano Date: September 16 2000 -mail2news.py - (C) 2000 by Cosimo Alfarano <Alfarano@Students.CS.UniBo.It> +mail2news.py - Copyright 2000 by Cosimo Alfarano <Alfarano@Students.CS.UniBo.It> You can use this software under the terms of the GPL. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. @@ -14,70 +14,163 @@ Gets news email and sends it via SMTP. class mail2news is hopefully conform to rfc850. """ -from StringIO import StringIO -from collections import OrderedDict -import email -import logging -import nntplib -import os -from re import findall -from socket import gethostbyaddr, gethostname + import sys +from os import unlink, getpid +from socket import gethostbyaddr, gethostname +import string +from re import findall +import time +import nntplib +import pyginfo +import tempfile +class mail2news: + """news to mail gateway class""" -#logging.basicConfig(level=logging.DEBUG) -# This is the single source of Truth -# Yes, it is awkward to have it assymetrically here -# and not in news2mail as well. -VERSION = '0.9.12' -DESC = "The Python Gateway Script: news2mail mail2news gateway" + + reader = None # mode reader +# newsgroups = None # Newsgroups: local.test,local.moderated... +# approved = None # Approved: kame@aragorn.lorien.org + newsserver = 'localhost' # no comment :) + port = 119 + user = None + password = None + hostname = gethostbyaddr(gethostname())[0] -class mail2news(object): - """news to mail gateway class""" + heads_dict, smtpheads, nntpheads = {}, {}, {} + email, headers, body = [], [], [] - def __init__(self, options): - # newsgroups = None # Newsgroups: local.test,local.moderated... - # approved = None # Approved: kame@aragorn.lorien.org - if 'NNTPHOST' in os.environ: - self.newsserver = os.environ['NNTPHOST'] - else: - self.newsserver = 'localhost' - self.port = 119 - self.user = None - self.password = None - self.verbose = options.verbose - logging.debug('self.verbose = %s', self.verbose) + def readfile(self): - self.hostname = gethostbyaddr(gethostname())[0] + for line in sys.stdin.readlines(): + self.email.append(line) + + if(len(self.email) == 1 and self.email[0][0] == '/'): + file = self.email[0][:-1] + del self.email[0] + for line in open(file,'r').readlines(): + self.email.append(line) + + return 1 - self.heads_dict, self.smtpheads, self.nntpheads = {}, {}, {} - self.message = self.__readfile(options) - self.message['X-Gateway'] = 'pyg {0} {1}'.format(VERSION, DESC) + def parseemail(self): + """get news email from file or stdin and separate heads from body - def __add_header(self, header, value, msg=None): - if msg is None: - msg = self.message - if value: - msg[header] = value.strip() + REMEBER: headers value has '\n' as last char. + Use string[:-1] to ignore newline. + """ - def __readfile(self, opt): - message = email.message_from_file(sys.stdin) + try: + body = 0 # are we in body or in headers? + + for line in self.email: + if not body and len(line) == 1: + body = 1 # starts email body section + + if not body: + try: + # if it is a multi-line header like Received: + if not line[0] in [' ','\t']: + try: + head, value = string.split(line, ' ', 1) + except string.index_error: + value = '' + self.smtpheads[head] = value + else: + self.smtpheads[head] = '%s%s' % \ + (self.smtpheads[head], line) + except (string.index_error), message: + print 'line: %s' % line + print '(probably missing couple "Header: value" in %s)' % line + sys.exit(1) + + elif len(line) > 0 and body: + self.body.append(line) + + except (string.index_error), message: + print message + sys.exit(1) + + return self.smtpheads, self.body + + def puthead(self, dict, list, key): + """private, transform dict entries in list entries + Appends key of dict to list, deleting it from dict. + """ - if (len(message) == 0) \ - and message.get_payload().startswith('/'): - msg_file_name = message.get_payload().strip() - del message - with open(msg_file_name, 'r') as msg_file: - message = email.message_from_file(msg_file) + if dict.has_key(key): + list.append(key + ' ' + dict.get(key)) + del dict[key] + else: + return 0 + return 1 - # introduce nntpheads - self.__add_header('Newsgroups', opt.newsgroup, message) - self.__add_header('Approved', opt.approver, message) + def sortheads(self): + """make list sorted by heads: From: To: Subject: first, others, X-*, X-Resent-* last""" + + set = ('Newsgroups:','From:','To:','X-To:','Cc:','Subject:','Date:','Approved:','References:','Message-Id:') # put at top + + for k in set: + self.puthead(self.heads_dict,self.headers,k) + + for k in self.heads_dict.keys(): + if k[:2] != 'X-' and k[:9] != 'X-Resent-' and k not in set: + self.puthead(self.heads_dict,self.headers,k) + + for k in self.heads_dict.keys(): + if k[:2] == 'X-': + self.puthead(self.heads_dict,self.headers,k) + + for k in self.heads_dict.keys(): + if k[:9] == 'X-Resent-': + self.puthead(self.heads_dict,self.headers,k) + + return self.headers + + + def mergeheads(self): + """make a unique headers dictionary from NNTP and SMTP + single headers dictionaries.""" + + self.heads_dict = {} - return message + try: + for header in self.smtpheads.keys(): # fill it w/ smtp old heads + self.heads_dict[header] = self.smtpheads[header] + + for header in self.nntpheads.keys(): # and replace them w/ nntp new heads + self.heads_dict[header] = self.nntpheads[header] + + except KeyError, message: + print message + + return self.heads_dict + + def addheads(self): + """add new header like X-Gateway: + """ + + info = pyginfo.pygsinfo() + + try: + self.heads_dict['X-Gateway:'] = info.PROGNAME + ' ' + \ + info.PROGDESC + ' - Mail to News\n' + +# it is nntpheads stuff +# if(self.newsgroups): +# self.heads_dict['Newsgroups:'] = self.newsgroups + +# if(self.approved): +# self.heads_dict['Approved:'] = self.approved + + except KeyError, message: + print message + + return self.heads_dict def renameheads(self): """rename headers such as Resent-*: to X-Resent-*: @@ -87,99 +180,140 @@ class mail2news(object): """ try: - for key in self.message.keys(): - if key.startswith('Resent-'): - if ('X-' + key) in self.message: - self.message['X-Original-' + key] = \ - self.message['X-' + key] - self.message['X-' + key] = self.message[key] - del self.message[key] - +### test +# if(post): +# if(self.heads_dict.has_key(post)): +# self.heads_dict['X-Original-' + post] = self.heads_dict[post] +# +# self.heads_dict[post] = self.heads_dict[pre] +# del(self.heads_dict[pre]) +# +# else: +# if(pre[0:2] == 'X-' and self.heads_dict.has_key(pre)): +# self.heads_dict['X-Original-' + pre] = self.heads_dict[pre] +# elif(not pre[0:2] == 'X-' and self.heads_dict.has_key('X-' + pre)): +# self.heads_dict['X-' + pre] = self.heads_dict[pre] +# del(self.heads_dict[pre]) +### end test + + for key in self.heads_dict.keys(): + if(key[:7] in ['Resent-']): + if(self.heads_dict.has_key('X-' + key)): + self.heads_dict[ 'X-Original-' + key ] = self.heads_dict['X-' + key] + self.heads_dict[ 'X-' + key ] = self.heads_dict[key] + del self.heads_dict[key] + + # In rfc822 References: is considered, but many MUA doen't put it. - if ('References' not in self.message) and \ - ('In-Reply-To' in self.message): - print self.message['In-Reply-To'] + if(not self.heads_dict.has_key('References:') and self.heads_dict.has_key('In-Reply-To:')): + print self.heads_dict['In-Reply-To:'] # some MUA uses msgid without '<' '>' # ref = findall('([^\s<>\']+@[^\s<>;:\']+)', \ # but I prefer use RFC standards - ref = findall('(<[^<>]+@[^<>]+>)', - self.message['In-Reply-To']) + ref = findall('(<[^<>]+@[^<>]+>)', \ + self.heads_dict['In-Reply-To:']) # if found, keep first element that seems a Msg-ID. if(ref and len(ref)): - self.message['References'] = '%s\n' % ref[0] + self.heads_dict['References:'] = '%s\n' % ref[0] + +# if(self.heads_dict.has_key('To:')): +# self.heads_dict['X-To:'] = self.heads_dict['To:'] +# del self.heads_dict['To:'] except KeyError, message: print message - def removeheads(self, heads=None): + return self.heads_dict + + + def removeheads(self, heads = None): """remove headers like Xref: Path: Lines: """ try: # removing some others useless headers .... (From is not From:) - rmheads = ['Received', 'From ', 'NNTP-Posting-Host', - 'X-Trace', 'X-Compliants-To', 'NNTP-Posting-Date'] - if heads: + rmheads = ['Received:','From','NNTP-Posting-Host:','X-Trace'] + if(heads): rmheads.append(heads) - + for head in rmheads: - if head in self.message: - del self.message[head] - - if 'Message-Id' in self.message: - msgid = self.message['Message-Id'] - del self.message['Message-Id'] - self.message['Message-Id'] = msgid - else: - msgid = '<pyg.%d@tuchailepuppapera.org>\n' % (os.getpid()) - self.message['Message-Id'] = msgid + if self.heads_dict.has_key(head): + del self.heads_dict[head] + +# if self.heads_dict.has_key('From'): # neither 'From ' nor 'From:' +# del self.heads_dict['From'] + +# if self.heads_dict.has_key('NNTP-Posting-Host:'): # neither 'From ' nor 'From:' +# del self.heads_dict[''] + + +# if self.heads_dict.has_key('Lines:'): +# del self.heads_dict['Lines:'] + + # it is usually set by INN, if ng is moderated... +# if self.heads_dict.has_key('Sender:'): +# del self.heads_dict['Sender:'] + + + if self.heads_dict.has_key('Message-id:'): + self.heads_dict['Message-Id:'] = self.heads_dict['Message-id:'] + del(self.heads_dict['Message-id:']) + + if self.heads_dict.has_key('Message-ID:'): + self.heads_dict['Message-Id:'] = self.heads_dict['Message-ID:'] + del(self.heads_dict['Message-ID:']) + + # If message-id is not present, I generate it + if not self.heads_dict.has_key('Message-Id:'): + msgid = '<pyg.%d@tuchailepuppapera.org>\n' % (getpid()) + self.heads_dict['Message-Id:'] = msgid except KeyError, message: print message - def sortheads(self): - """make list sorted by heads: From: To: Subject: first, - others, X-*, X-Resent-* last""" - - heads_dict = OrderedDict(self.message) - for hdr in self.message.keys(): - del self.message[hdr] - - # put at top - head_set = ('Newsgroups', 'From', 'To', 'X-To', 'Cc', 'Subject', - 'Date', 'Approved', 'References', 'Message-Id') - - logging.debug('heads_dict = %s', heads_dict) - for k in head_set: - if k in heads_dict: - self.__add_header(k, heads_dict[k]) - - for k in heads_dict: - if not k.startswith('X-') and not k.startswith('X-Resent-') \ - and k not in head_set: - self.__add_header(k, heads_dict[k]) - - for k in heads_dict: - if k.startswith('X-'): - self.__add_header(k, heads_dict[k]) - - for k in heads_dict: - if k.startswith('X-Resent-'): - self.__add_header(k, heads_dict[k]) + return self.heads_dict def sendemail(self): - "Talk to NNTP server and try to send email." - # readermode must be True, otherwise we don't have POST command. - server = nntplib.NNTP(self.newsserver, self.port, self.user, - self.password, readermode=True) - - logging.debug('self.verbose = %s', self.verbose) - if self.verbose: - server.set_debuglevel(2) - - server.post(StringIO(self.message.as_string())) - - server.quit() + """Talk to NNTP server and try to send email.""" + try: + msglist = [] + + n = nntplib.NNTP(self.newsserver, self.port, self.user, self.password) + + if(self.reader): + n.putline('mode reader') + resp = n.getline() + print resp + + resp = n.shortcmd('POST') + + # sett RFC977 2.4.2 + if resp[0] <> '3': + raise n.error_reply, str(resp) + + for line in self.headers: + if not line: + break + if line[-1] == '\n': + line = line[:-1] + if line[:1] == '.': + line = '.' + line + n.putline(line) + + for line in self.body: + if not line: + break + if line[-1] == '\n': + line = line[:-1] + if line[:1] == '.': + line = '.' + line + n.putline(line) + + n.putline('.') + n.quit() + return None + except (nntplib.error_reply, nntplib.error_temp, nntplib.error_perm, nntplib.error_proto, nntplib.error_data), message: + return 'NNTP: ' + str(message) |