# Copyright (C) 1998 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. '''Mixin class for gatewaying mail to news, and news to mail.''' # All these things should already be imported, so might as well do them here # at the top level import os import string import re import time import mm_cfg # XXX: Bogus, but might as we do it `legally' QuickEscape = 'QuickEscape' class GatewayManager: def InitVars(self): # Configurable self.nntp_host = '' self.linked_newsgroup = '' self.gateway_to_news = 0 self.gateway_to_mail = 0 def GetConfigInfo(self): return [ 'Mail-to-News and News-to-Mail gateway services.', ('nntp_host', mm_cfg.String, 50, 0, 'The Internet address of the machine your News server ' 'is running on.', 'The News server is not part of Mailman proper. You have to ' 'already have access to a NNTP server, and that NNTP server ' 'has to recognize the machine this mailing list runs on as ' 'a machine capable of reading and posting news.'), ('linked_newsgroup', mm_cfg.String, 50, 0, 'The name of the Usenet group to gateway to and/or from.'), ('gateway_to_news', mm_cfg.Toggle, ('No', 'Yes'), 0, 'Should posts to the mailing list be resent to the ' 'newsgroup?'), ('gateway_to_mail', mm_cfg.Toggle, ('No', 'Yes'), 0, 'Should newsgroup posts not sent from the list be resent ' 'to the list?') ] # This function is called from cron/gate_news and assumes the following # have been asserted: # - that the list gates from news to mail # - that list has an nntp_host and linked_newsgroup # - that the connection has been opened # - that this method is run in a child process # - that the watermark is non-zero def PollNewsGroup(self, conn, wm, first, last): import nntplib # NEWNEWS is not portable and has synchronization issues... Use a # watermark system instead. for num in range(max(wm+1, first), last+1): try: headers = conn.head(`num`)[3] found_to = 0 for header in headers: i = string.find(header, ':') if i > 0 and string.lower(header[:i]) == 'to': found_to = 1 if header[:i] <> 'X-BeenThere': continue if header[i:] == ': %s' % self.GetListEmail(): raise QuickEscape body = conn.body(`num`)[3] # Create the pipe to the Mail posting script. Note that it is # not installed executable, so we'll tack on the path to # Python we discovered when we configured Mailman. The extra # argument to `post' informs the system that the message is # originating from Usenet and so should not get posted back to # Usenet. I think this is mostly redundent with the # X-BeenThere header, but I'm a little afraid to muck with # that. cmd = '%s %s %s fromusenet' % ( mm_cfg.PYTHON, os.path.join(mm_cfg.SCRIPTS_DIR, 'post'), self._internal_name) file = os.popen(cmd, 'w') # Usenet originated messages will not have a Unix envelope # (i.e. "From " header). This breaks Pipermail archiving, so # we will synthesize one. Be sure to use the format searched # for by mailbox.UnixMailbox._isrealfromline() timehdr = time.asctime(time.localtime(time.time())) envhdr = 'From ' + self.GetAdminEmail() + ' ' + timehdr file.write(envhdr + '\n') file.write(string.join(headers,'\n')) # If there wasn't already a TO: header, add one. if not found_to: file.write("\nTo: %s" % self.GetListEmail()) file.write('\n\n') file.write(string.join(body,'\n')) file.write('\n') file.close() except nntplib.error_temp: pass # Probably canceled, etc... except QuickEscape: pass # We gated this TO news, don't repost it! def SendMailToNewsGroup(self, mail_msg): import Message error = [] if not self.linked_newsgroup: error.append('no newsgroup') if not self.nntp_host: error.append('no NNTP host') if error: msg = 'NNTP gateway improperly configured: ' + \ string.join(error, ', ') self.LogMsg('error', msg) return if hasattr(mail_msg, 'fromusenet') and mail_msg.fromusenet: # This message originated on Usenet; don't re-post it. return # Fork in case the nntp connection hangs. if not os.fork(): # child import nntplib # Now make the news message... msg = Message.NewsMessage(mail_msg) # Ok, munge headers, etc. subj = msg.getheader('subject') if subj: subjpref = self.subject_prefix if not re.match('(re:? *)?' + re.escape(subjpref), subj, re.I): msg.SetHeader('Subject', '%s%s' % (subjpref, subj)) else: msg.SetHeader('Subject', '%s(no subject)' % subjpref) if self.reply_goes_to_list: del msg['reply-to'] msg.headers.append('Reply-To: %s\n' % self.GetListEmail()) # if we already have a sender header, don't add another one; use # the header that's already there. if not msg.getheader('sender'): msg.headers.append('Sender: %s\n' % self.GetAdminEmail()) msg.headers.append('Errors-To: %s\n' % self.GetAdminEmail()) msg.headers.append('X-BeenThere: %s\n' % self.GetListEmail()) ngheader = msg.getheader('newsgroups') if ngheader is not None: # see if the Newsgroups: header already contains our # linked_newsgroup. If so, don't add it again. If not, # append our linked_newsgroup to the end of the header list ngroups = map(string.strip, string.split(ngheader, ',')) if self.linked_newsgroup not in ngroups: ngroups.append(self.linked_newsgroup) ngheader = string.join(ngroups, ',') # subtitute our new header for the old one. XXX Message # class should have a __setitem__() del msg['newsgroups'] msg.headers.append('Newsgroups: %s\n' % ngroups) else: # Newsgroups: isn't in the message msg.headers.append('Newsgroups: %s\n' % self.linked_newsgroup) # Note: Need to be sure 2 messages aren't ever sent to the same # list in the same process, since message ID's need to be unique. # Could make the ID be mm.listname.postnum instead if that happens if msg.getheader('Message-ID') is None: msg.headers.append('Message-ID: \n' % (time.time(), os.getpid(), self.host_name)) if msg.getheader('Lines') is None: msg.headers.append('Lines: %s\n' % len(string.split(msg.body,"\n"))) del msg['received'] # TBD: Gross hack to ensure that we have only one # content-transfer-encoding header. More than one barfs NNTP. I # don't know why we sometimes have more than one such header, and # it probably isn't correct to take the value of just the first # one. What if there are conflicting headers??? # # This relies on the new interface for getaddrlist() returning # values for all present headers, and the fact that the legal # values are usually not parseable as addresses. Yes this is # another bogosity. cteheaders = msg.getaddrlist('content-transfer-encoding') if cteheaders: ctetuple = cteheaders[0] ctevalue = ctetuple[1] del msg['content-transfer-encoding'] msg['content-transfer-encoding'] = ctevalue # NNTP is strict about spaces after the colon in headers. for n in range(len(msg.headers)): line = msg.headers[n] i = string.find(line,":") if i <> -1 and line[i+1] <> ' ': msg.headers[n] = line[:i+1] + ' ' + line[i+1:] con = nntplib.NNTP(self.nntp_host) con.post(msg) con.quit() os._exit(0)