aboutsummaryrefslogtreecommitdiffstats
path: root/mail2news.py
diff options
context:
space:
mode:
Diffstat (limited to 'mail2news.py')
-rw-r--r--mail2news.py374
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)