X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fmailgw.py;h=8b4f95e6de567160cee5a5f0b85788fb3f455a9e;hb=715f6d151e0a42146aeb9c64af875cae6bb946e6;hp=b5d802d150b6b2af0db135c5cd50c5c3536c30be;hpb=369fb117ee3618ba00b5386cbaa4b4f19c2db4a4;p=roundup.git diff --git a/roundup/mailgw.py b/roundup/mailgw.py index b5d802d..8b4f95e 100644 --- a/roundup/mailgw.py +++ b/roundup/mailgw.py @@ -16,7 +16,7 @@ # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -__doc__ = ''' +''' An e-mail gateway for Roundup. Incoming messages are examined for multiple parts: @@ -73,12 +73,11 @@ are calling the create() method to create a new node). If an auditor raises an exception, the original message is bounced back to the sender with the explanatory message given in the exception. -$Id: mailgw.py,v 1.77 2002-07-18 11:17:31 gmcm Exp $ +$Id: mailgw.py,v 1.99 2002-11-05 22:59:46 richard Exp $ ''' - import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri -import time, random +import time, random, sys import traceback, MimeWriter import hyperdb, date, password @@ -93,9 +92,21 @@ class MailUsageError(ValueError): class MailUsageHelp(Exception): pass -class UnAuthorized(Exception): +class Unauthorized(Exception): """ Access denied """ +def initialiseSecurity(security): + ''' Create some Permissions and Roles on the security object + + This function is directly invoked by security.Security.__init__() + as a part of the Security object instantiation. + ''' + security.addPermission(name="Email Registration", + description="Anonymous may register through e-mail") + p = security.addPermission(name="Email Access", + description="User may use the email interface") + security.addPermissionToRole('Admin', p) + class Message(mimetools.Message): ''' subclass mimetools.Message so we can retrieve the parts of the message... @@ -119,9 +130,9 @@ class Message(mimetools.Message): s.seek(0) return Message(s) -subject_re = re.compile(r'(?P\s*\W?\s*(fwd|re|aw)\s*\W?\s*)*' - r'\s*(\[(?P[^\d\s]+)(?P\d+)?\])?' - r'\s*(?P[^[]+)?(\[(?P<args>.+?)\])?', re.I) +subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re|aw)\W\s*)*' + r'\s*(?P<quote>")?(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?' + r'\s*(?P<title>[^[]+)?"?(\[(?P<args>.+?)\])?', re.I) class MailGW: def __init__(self, instance, db): @@ -132,6 +143,87 @@ class MailGW: # (for testing) self.trapExceptions = 1 + def do_pipe(self): + ''' Read a message from standard input and pass it to the mail handler. + + Read into an internal structure that we can seek on (in case + there's an error). + + XXX: we may want to read this into a temporary file instead... + ''' + s = cStringIO.StringIO() + s.write(sys.stdin.read()) + s.seek(0) + self.main(s) + return 0 + + def do_mailbox(self, filename): + ''' Read a series of messages from the specified unix mailbox file and + pass each to the mail handler. + ''' + # open the spool file and lock it + import fcntl, FCNTL + f = open(filename, 'r+') + fcntl.flock(f.fileno(), FCNTL.LOCK_EX) + + # handle and clear the mailbox + try: + from mailbox import UnixMailbox + mailbox = UnixMailbox(f, factory=Message) + # grab one message + message = mailbox.next() + while message: + # handle this message + self.handle_Message(message) + message = mailbox.next() + # nuke the file contents + os.ftruncate(f.fileno(), 0) + except: + import traceback + traceback.print_exc() + return 1 + fcntl.flock(f.fileno(), FCNTL.LOCK_UN) + return 0 + + def do_pop(self, server, user='', password=''): + '''Read a series of messages from the specified POP server. + ''' + import getpass, poplib, socket + try: + if not user: + user = raw_input(_('User: ')) + if not password: + password = getpass.getpass() + except (KeyboardInterrupt, EOFError): + # Ctrl C or D maybe also Ctrl Z under Windows. + print "\nAborted by user." + return 1 + + # open a connection to the server and retrieve all messages + try: + server = poplib.POP3(server) + except socket.error, message: + print "POP server error:", message + return 1 + server.user(user) + server.pass_(password) + numMessages = len(server.list()[1]) + for i in range(1, numMessages+1): + # retr: returns + # [ pop response e.g. '+OK 459 octets', + # [ array of message lines ], + # number of octets ] + lines = server.retr(i)[1] + s = cStringIO.StringIO('\n'.join(lines)) + s.seek(0) + self.handle_Message(Message(s)) + # delete the message + server.dele(i) + + # quit the server to commit changes. + server.quit() + return 0 + def main(self, fp): ''' fp - the file from which to read the Message. ''' @@ -172,7 +264,7 @@ class MailGW: m.append('\n\nMail Gateway Help\n=================') m.append(fulldoc) m = self.bounce_message(message, sendto, m) - except UnAuthorized, value: + except Unauthorized, value: # just inform the user that he is not authorized sendto = [sendto[0][1]] m = [''] @@ -180,7 +272,7 @@ class MailGW: m = self.bounce_message(message, sendto, m) except: # bounce the message back to the sender with the error message - sendto = [sendto[0][1], self.instance.ADMIN_EMAIL] + sendto = [sendto[0][1], self.instance.config.ADMIN_EMAIL] m = [''] m.append('An unexpected error occurred during the processing') m.append('of your message. The tracker administrator is being') @@ -193,7 +285,7 @@ class MailGW: m = self.bounce_message(message, sendto, m) else: # very bad-looking message - we don't even know who sent it - sendto = [self.instance.ADMIN_EMAIL] + sendto = [self.instance.config.ADMIN_EMAIL] m = ['Subject: badly formed message from mail gateway'] m.append('') m.append('The mail gateway retrieved a message which has no From:') @@ -206,11 +298,13 @@ class MailGW: # now send the message if SENDMAILDEBUG: open(SENDMAILDEBUG, 'w').write('From: %s\nTo: %s\n%s\n'%( - self.instance.ADMIN_EMAIL, ', '.join(sendto), m.getvalue())) + self.instance.config.ADMIN_EMAIL, ', '.join(sendto), + m.getvalue())) else: try: - smtp = smtplib.SMTP(self.instance.MAILHOST) - smtp.sendmail(self.instance.ADMIN_EMAIL, sendto, m.getvalue()) + smtp = smtplib.SMTP(self.instance.config.MAILHOST) + smtp.sendmail(self.instance.config.ADMIN_EMAIL, sendto, + m.getvalue()) except socket.error, value: raise MailGWError, "Couldn't send error email: "\ "mailhost %s"%value @@ -226,8 +320,8 @@ class MailGW: msg = cStringIO.StringIO() writer = MimeWriter.MimeWriter(msg) writer.addheader('Subject', subject) - writer.addheader('From', '%s <%s>'% (self.instance.INSTANCE_NAME, - self.instance.ISSUE_TRACKER_EMAIL)) + writer.addheader('From', '%s <%s>'% (self.instance.config.TRACKER_NAME, + self.instance.config.TRACKER_EMAIL)) writer.addheader('To', ','.join(sendto)) writer.addheader('MIME-Version', '1.0') part = writer.startmultipartbody('mixed') @@ -235,35 +329,21 @@ class MailGW: body = part.startbody('text/plain') body.write('\n'.join(error)) - # reconstruct the original message - m = cStringIO.StringIO() - w = MimeWriter.MimeWriter(m) - # default the content_type, just in case... - content_type = 'text/plain' - # add the headers except the content-type + # attach the original message to the returned message + part = writer.nextpart() + part.addheader('Content-Disposition','attachment') + part.addheader('Content-Description','Message you sent') + body = part.startbody('text/plain') for header in message.headers: - header_name = header.split(':')[0] - if header_name.lower() == 'content-type': - content_type = message.getheader(header_name) - elif message.getheader(header_name): - w.addheader(header_name, message.getheader(header_name)) - # now attach the message body - body = w.startbody(content_type) + body.write(header) + body.write('\n') try: message.rewindbody() - except IOError: - body.write("*** couldn't include message body: read from pipe ***") + except IOError, message: + body.write("*** couldn't include message body: %s ***"%message) else: body.write(message.fp.read()) - # attach the original message to the returned message - part = writer.nextpart() - part.addheader('Content-Disposition','attachment') - part.addheader('Content-Description','Message you sent') - part.addheader('Content-Transfer-Encoding', '7bit') - body = part.startbody('message/rfc822') - body.write(m.getvalue()) - writer.lastpart() return msg @@ -305,9 +385,9 @@ class MailGW: classname = m.group('classname') if classname is None: # no classname, fallback on the default - if hasattr(self.instance, 'MAIL_DEFAULT_CLASS') and \ - self.instance.MAIL_DEFAULT_CLASS: - classname = self.instance.MAIL_DEFAULT_CLASS + if hasattr(self.instance.config, 'MAIL_DEFAULT_CLASS') and \ + self.instance.config.MAIL_DEFAULT_CLASS: + classname = self.instance.config.MAIL_DEFAULT_CLASS else: # fail m = None @@ -349,6 +429,11 @@ Subject was: "%s" else: title = '' + # strip off the quotes that dumb emailers put around the subject, like + # Re: "[issue1] bla blah" + if m.group('quote') and title.endswith('"'): + title = title[:-1] + # but we do need either a title or a nodeid... if nodeid is None and not title: raise MailUsageError, ''' @@ -378,6 +463,71 @@ does not exist. Subject was: "%s" '''%(nodeid, subject) + # + # handle the users + # + # Don't create users if anonymous isn't allowed to register + create = 1 + anonid = self.db.user.lookup('anonymous') + if not self.db.security.hasPermission('Email Registration', anonid): + create = 0 + + # ok, now figure out who the author is - create a new user if the + # "create" flag is true + author = uidFromAddress(self.db, message.getaddrlist('from')[0], + create=create) + + # if we're not recognised, and we don't get added as a user, then we + # must be anonymous + if not author: + author = anonid + + # make sure the author has permission to use the email interface + if not self.db.security.hasPermission('Email Access', author): + if author == anonid: + # we're anonymous and we need to be a registered user + raise Unauthorized, ''' +You are not a registered user. + +Unknown address: %s +'''%message.getaddrlist('from')[0][1] + else: + # we're registered and we're _still_ not allowed access + raise Unauthorized, 'You are not permitted to access '\ + 'this tracker.' + + # make sure they're allowed to edit this class of information + if not self.db.security.hasPermission('Edit', author, classname): + raise Unauthorized, 'You are not permitted to edit %s.'%classname + + # the author may have been created - make sure the change is + # committed before we reopen the database + self.db.commit() + + # reopen the database as the author + username = self.db.user.get(author, 'username') + self.db.close() + self.db = self.instance.open(username) + + # re-get the class with the new database connection + cl = self.db.getclass(classname) + + # now update the recipients list + recipients = [] + tracker_email = self.instance.config.TRACKER_EMAIL.lower() + for recipient in message.getaddrlist('to') + message.getaddrlist('cc'): + r = recipient[1].strip().lower() + if r == tracker_email or not r: + continue + + # look up the recipient - create if necessary (and we're + # allowed to) + recipient = uidFromAddress(self.db, recipient, create) + + # if all's well, add the recipient to the list + if recipient: + recipients.append(recipient) + # # extract the args # @@ -508,55 +658,6 @@ There were problems handling your subject line argument list: Subject was: "%s" '''%(errors, subject) - # - # handle the users - # - - # Don't create users if ANONYMOUS_REGISTER_MAIL is denied - # ... fall back on ANONYMOUS_REGISTER if the other doesn't exist - create = 1 - if hasattr(self.instance, 'ANONYMOUS_REGISTER_MAIL'): - if self.instance.ANONYMOUS_REGISTER_MAIL == 'deny': - create = 0 - elif self.instance.ANONYMOUS_REGISTER == 'deny': - create = 0 - - author = self.db.uidFromAddress(message.getaddrlist('from')[0], - create=create) - if not author: - raise UnAuthorized, ''' -You are not a registered user. - -Unknown address: %s -'''%message.getaddrlist('from')[0][1] - - # the author may have been created - make sure the change is - # committed before we reopen the database - self.db.commit() - - # reopen the database as the author - username = self.db.user.get(author, 'username') - self.db = self.instance.open(username) - - # re-get the class with the new database connection - cl = self.db.getclass(classname) - - # now update the recipients list - recipients = [] - tracker_email = self.instance.ISSUE_TRACKER_EMAIL.lower() - for recipient in message.getaddrlist('to') + message.getaddrlist('cc'): - r = recipient[1].strip().lower() - if r == tracker_email or not r: - continue - - # look up the recipient - create if necessary (and we're - # allowed to) - recipient = self.db.uidFromAddress(recipient, create) - - # if all's well, add the recipient to the list - if recipient: - recipients.append(recipient) - # # handle message-id and in-reply-to # @@ -565,7 +666,7 @@ Unknown address: %s # generate a messageid if there isn't one if not messageid: messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(), - classname, nodeid, self.instance.MAIL_DOMAIN) + classname, nodeid, self.instance.config.MAIL_DOMAIN) # # now handle the body - find the message @@ -721,15 +822,65 @@ There was a problem with the message you sent: return nodeid +def extractUserFromList(userClass, users): + '''Given a list of users, try to extract the first non-anonymous user + and return that user, otherwise return None + ''' + if len(users) > 1: + for user in users: + # make sure we don't match the anonymous or admin user + if userClass.get(user, 'username') in ('admin', 'anonymous'): + continue + # first valid match will do + return user + # well, I guess we have no choice + return user[0] + elif users: + return users[0] + return None + +def uidFromAddress(db, address, create=1): + ''' address is from the rfc822 module, and therefore is (name, addr) + + user is created if they don't exist in the db already + ''' + (realname, address) = address + + # try a straight match of the address + user = extractUserFromList(db.user, db.user.stringFind(address=address)) + if user is not None: return user + + # try the user alternate addresses if possible + props = db.user.getprops() + if props.has_key('alternate_addresses'): + users = db.user.filter(None, {'alternate_addresses': address}) + user = extractUserFromList(db.user, users) + if user is not None: return user + + # try to match the username to the address (for local + # submissions where the address is empty) + user = extractUserFromList(db.user, db.user.stringFind(username=address)) + + # couldn't match address or username, so create a new user + if create: + return db.user.create(username=address, address=address, + realname=realname, roles=db.config.NEW_EMAIL_USER_ROLES) + else: + return 0 + def parseContent(content, keep_citations, keep_body, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), eol=re.compile(r'[\r\n]+'), signature=re.compile(r'^[>|\s]*[-_]+\s*$'), original_message=re.compile(r'^[>|\s]*-----Original Message-----$')): ''' The message body is divided into sections by blank lines. - Sections where the second and all subsequent lines begin with a ">" or "|" - character are considered "quoting sections". The first line of the first - non-quoting section becomes the summary of the message. + Sections where the second and all subsequent lines begin with a ">" + or "|" character are considered "quoting sections". The first line of + the first non-quoting section becomes the summary of the message. + + If keep_citations is true, then we keep the "quoting sections" in the + content. + If keep_body is true, we even keep the signature sections. ''' # strip off leading carriage-returns / newlines i = 0 @@ -754,7 +905,7 @@ def parseContent(content, keep_citations, keep_body, # see if there's a response somewhere inside this section (ie. # no blank line between quoted message and response) for line in lines[1:]: - if line[0] not in '>|': + if line and line[0] not in '>|': break else: # we keep quoted bits if specified in the config @@ -762,17 +913,16 @@ def parseContent(content, keep_citations, keep_body, l.append(section) continue # keep this section - it has reponse stuff in it - if not summary: - # and while we're at it, use the first non-quoted bit as - # our summary - summary = line lines = lines[lines.index(line):] section = '\n'.join(lines) + # and while we're at it, use the first non-quoted bit as + # our summary + summary = section if not summary: # if we don't have our summary yet use the first line of this # section - summary = lines[0] + summary = section elif signature.match(lines[0]) and 2 <= len(lines) <= 10: # lose any signature break @@ -782,373 +932,22 @@ def parseContent(content, keep_citations, keep_body, # and add the section to the output l.append(section) - # we only set content for those who want to delete cruft from the - # message body, otherwise the body is left untouched. + + # figure the summary - find the first sentence-ending punctuation or the + # first whole line, whichever is longest + sentence = re.search(r'^([^!?\.]+[!?\.])', summary) + if sentence: + sentence = sentence.group(1) + else: + sentence = '' + first = eol.split(summary)[0] + summary = max(sentence, first) + + # Now reconstitute the message content minus the bits we don't care + # about. if not keep_body: content = '\n\n'.join(l) + return summary, content -# -# $Log: not supported by cvs2svn $ -# Revision 1.76 2002/07/10 06:39:37 richard -# . made mailgw handle set and modify operations on multilinks (bug #579094) -# -# Revision 1.75 2002/07/09 01:21:24 richard -# Added ability for unit tests to turn off exception handling in mailgw so -# that exceptions are reported earlier (and hence make sense). -# -# Revision 1.74 2002/05/29 01:16:17 richard -# Sorry about this huge checkin! It's fixing a lot of related stuff in one go -# though. -# -# . #541941 ] changing multilink properties by mail -# . #526730 ] search for messages capability -# . #505180 ] split MailGW.handle_Message -# - also changed cgi client since it was duplicating the functionality -# . build htmlbase if tests are run using CVS checkout (removed note from -# installation.txt) -# . don't create an empty message on email issue creation if the email is empty -# -# Revision 1.73 2002/05/22 04:12:05 richard -# . applied patch #558876 ] cgi client customization -# ... with significant additions and modifications ;) -# - extended handling of ML assignedto to all places it's handled -# - added more NotFound info -# -# Revision 1.72 2002/05/22 01:24:51 richard -# Added note to MIGRATION about new config vars. Also made us more resilient -# for upgraders. Reinstated list header style (oops) -# -# Revision 1.71 2002/05/08 02:40:55 richard -# grr -# -# Revision 1.70 2002/05/06 23:40:07 richard -# hrm -# -# Revision 1.69 2002/05/06 23:37:21 richard -# Tweaking the signature deletion from mail messages. -# Added nuking of the "-----Original Message-----" crap from Outlook. -# -# Revision 1.68 2002/05/02 07:56:34 richard -# . added option to automatically add the authors and recipients of messages -# to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and -# ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current -# behaviour. Setting them to 'yes' will add the author/recipients to the nosy -# on messages that create issues and followup messages. -# . added missing documentation for a few of the config option values -# -# Revision 1.67 2002/04/23 15:46:49 rochecompaan -# . stripping of the email message body can now be controlled through -# the config variables EMAIL_KEEP_QUOTED_TEST and -# EMAIL_LEAVE_BODY_UNCHANGED. -# -# Revision 1.66 2002/03/14 23:59:24 richard -# . #517734 ] web header customisation is obscure -# -# Revision 1.65 2002/02/15 00:13:38 richard -# . #503204 ] mailgw needs a default class -# - partially done - the setting of additional properties can wait for a -# better configuration system. -# -# Revision 1.64 2002/02/14 23:46:02 richard -# . #516883 ] mail interface + ANONYMOUS_REGISTER -# -# Revision 1.63 2002/02/12 08:08:55 grubert -# . Clean up mail handling, multipart handling. -# -# Revision 1.62 2002/02/05 14:15:29 grubert -# . respect encodings in non multipart messages. -# -# Revision 1.61 2002/02/04 09:40:21 grubert -# . add test for multipart messages with first part being encoded. -# -# Revision 1.60 2002/02/01 07:43:12 grubert -# . mailgw checks encoding on first part too. -# -# Revision 1.59 2002/01/23 21:43:23 richard -# tabnuke -# -# Revision 1.58 2002/01/23 21:41:56 richard -# . mailgw failures (unexpected ones) are forwarded to the roundup admin -# -# Revision 1.57 2002/01/22 22:27:43 richard -# . handle stripping of "AW:" from subject line -# -# Revision 1.56 2002/01/22 11:54:45 rochecompaan -# Fixed status change in mail gateway. -# -# Revision 1.55 2002/01/21 10:05:47 rochecompaan -# Feature: -# . the mail gateway now responds with an error message when invalid -# values for arguments are specified for link or multilink properties -# . modified unit test to check nosy and assignedto when specified as -# arguments -# -# Fixed: -# . fixed setting nosy as argument in subject line -# -# Revision 1.54 2002/01/16 09:14:45 grubert -# . if the attachment has no name, name it unnamed, happens with tnefs. -# -# Revision 1.53 2002/01/16 07:20:54 richard -# simple help command for mailgw -# -# Revision 1.52 2002/01/15 00:12:40 richard -# #503340 ] creating issue with [asignedto=p.ohly] -# -# Revision 1.51 2002/01/14 02:20:15 richard -# . changed all config accesses so they access either the instance or the -# config attriubute on the db. This means that all config is obtained from -# instance_config instead of the mish-mash of classes. This will make -# switching to a ConfigParser setup easier too, I hope. -# -# At a minimum, this makes migration a _little_ easier (a lot easier in the -# 0.5.0 switch, I hope!) -# -# Revision 1.50 2002/01/11 22:59:01 richard -# . #502342 ] pipe interface -# -# Revision 1.49 2002/01/10 06:19:18 richard -# followup lines directly after a quoted section were being eaten. -# -# Revision 1.48 2002/01/08 04:12:05 richard -# Changed message-id format to "<%s.%s.%s%s@%s>" so it complies with RFC822 -# -# Revision 1.47 2002/01/02 02:32:38 richard -# ANONYMOUS_ACCESS -> ANONYMOUS_REGISTER -# -# Revision 1.46 2002/01/02 02:31:38 richard -# Sorry for the huge checkin message - I was only intending to implement #496356 -# but I found a number of places where things had been broken by transactions: -# . modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename -# for _all_ roundup-generated smtp messages to be sent to. -# . the transaction cache had broken the roundupdb.Class set() reactors -# . newly-created author users in the mailgw weren't being committed to the db -# -# Stuff that made it into CHANGES.txt (ie. the stuff I was actually working -# on when I found that stuff :): -# . #496356 ] Use threading in messages -# . detectors were being registered multiple times -# . added tests for mailgw -# . much better attaching of erroneous messages in the mail gateway -# -# Revision 1.45 2001/12/20 15:43:01 rochecompaan -# Features added: -# . Multilink properties are now displayed as comma separated values in -# a textbox -# . The add user link is now only visible to the admin user -# . Modified the mail gateway to reject submissions from unknown -# addresses if ANONYMOUS_ACCESS is denied -# -# Revision 1.44 2001/12/18 15:30:34 rochecompaan -# Fixed bugs: -# . Fixed file creation and retrieval in same transaction in anydbm -# backend -# . Cgi interface now renders new issue after issue creation -# . Could not set issue status to resolved through cgi interface -# . Mail gateway was changing status back to 'chatting' if status was -# omitted as an argument -# -# Revision 1.43 2001/12/15 19:39:01 rochecompaan -# Oops. -# -# Revision 1.42 2001/12/15 19:24:39 rochecompaan -# . Modified cgi interface to change properties only once all changes are -# collected, files created and messages generated. -# . Moved generation of change note to nosyreactors. -# . We now check for changes to "assignedto" to ensure it's added to the -# nosy list. -# -# Revision 1.41 2001/12/10 00:57:38 richard -# From CHANGES: -# . Added the "display" command to the admin tool - displays a node's values -# . #489760 ] [issue] only subject -# . fixed the doc/index.html to include the quoting in the mail alias. -# -# Also: -# . fixed roundup-admin so it works with transactions -# . disabled the back_anydbm module if anydbm tries to use dumbdbm -# -# Revision 1.40 2001/12/05 14:26:44 rochecompaan -# Removed generation of change note from "sendmessage" in roundupdb.py. -# The change note is now generated when the message is created. -# -# Revision 1.39 2001/12/02 05:06:16 richard -# . We now use weakrefs in the Classes to keep the database reference, so -# the close() method on the database is no longer needed. -# I bumped the minimum python requirement up to 2.1 accordingly. -# . #487480 ] roundup-server -# . #487476 ] INSTALL.txt -# -# I also cleaned up the change message / post-edit stuff in the cgi client. -# There's now a clearly marked "TODO: append the change note" where I believe -# the change note should be added there. The "changes" list will obviously -# have to be modified to be a dict of the changes, or somesuch. -# -# More testing needed. -# -# Revision 1.38 2001/12/01 07:17:50 richard -# . We now have basic transaction support! Information is only written to -# the database when the commit() method is called. Only the anydbm -# backend is modified in this way - neither of the bsddb backends have been. -# The mail, admin and cgi interfaces all use commit (except the admin tool -# doesn't have a commit command, so interactive users can't commit...) -# . Fixed login/registration forwarding the user to the right page (or not, -# on a failure) -# -# Revision 1.37 2001/11/28 21:55:35 richard -# . login_action and newuser_action return values were being ignored -# . Woohoo! Found that bloody re-login bug that was killing the mail -# gateway. -# (also a minor cleanup in hyperdb) -# -# Revision 1.36 2001/11/26 22:55:56 richard -# Feature: -# . Added INSTANCE_NAME to configuration - used in web and email to identify -# the instance. -# . Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup -# signature info in e-mails. -# . Some more flexibility in the mail gateway and more error handling. -# . Login now takes you to the page you back to the were denied access to. -# -# Fixed: -# . Lots of bugs, thanks Roché and others on the devel mailing list! -# -# Revision 1.35 2001/11/22 15:46:42 jhermann -# Added module docstrings to all modules. -# -# Revision 1.34 2001/11/15 10:24:27 richard -# handle the case where there is no file attached -# -# Revision 1.33 2001/11/13 21:44:44 richard -# . re-open the database as the author in mail handling -# -# Revision 1.32 2001/11/12 22:04:29 richard -# oops, left debug in there -# -# Revision 1.31 2001/11/12 22:01:06 richard -# Fixed issues with nosy reaction and author copies. -# -# Revision 1.30 2001/11/09 22:33:28 richard -# More error handling fixes. -# -# Revision 1.29 2001/11/07 05:29:26 richard -# Modified roundup-mailgw so it can read e-mails from a local mail spool -# file. Truncates the spool file after parsing. -# Fixed a couple of small bugs introduced in roundup.mailgw when I started -# the popgw. -# -# Revision 1.28 2001/11/01 22:04:37 richard -# Started work on supporting a pop3-fetching server -# Fixed bugs: -# . bug #477104 ] HTML tag error in roundup-server -# . bug #477107 ] HTTP header problem -# -# Revision 1.27 2001/10/30 11:26:10 richard -# Case-insensitive match for ISSUE_TRACKER_EMAIL in address in e-mail. -# -# Revision 1.26 2001/10/30 00:54:45 richard -# Features: -# . #467129 ] Lossage when username=e-mail-address -# . #473123 ] Change message generation for author -# . MailGW now moves 'resolved' to 'chatting' on receiving e-mail for an issue. -# -# Revision 1.25 2001/10/28 23:22:28 richard -# fixed bug #474749 ] Indentations lost -# -# Revision 1.24 2001/10/23 22:57:52 richard -# Fix unread->chatting auto transition, thanks Roch'e -# -# Revision 1.23 2001/10/21 04:00:20 richard -# MailGW now moves 'unread' to 'chatting' on receiving e-mail for an issue. -# -# Revision 1.22 2001/10/21 03:35:13 richard -# bug #473125: Paragraph in e-mails -# -# Revision 1.21 2001/10/21 00:53:42 richard -# bug #473130: Nosy list not set correctly -# -# Revision 1.20 2001/10/17 23:13:19 richard -# Did a fair bit of work on the admin tool. Now has an extra command "table" -# which displays node information in a tabular format. Also fixed import and -# export so they work. Removed freshen. -# Fixed quopri usage in mailgw from bug reports. -# -# Revision 1.19 2001/10/11 23:43:04 richard -# Implemented the comma-separated printing option in the admin tool. -# Fixed a typo (more of a vim-o actually :) in mailgw. -# -# Revision 1.18 2001/10/11 06:38:57 richard -# Initial cut at trying to handle people responding to CC'ed messages that -# create an issue. -# -# Revision 1.17 2001/10/09 07:25:59 richard -# Added the Password property type. See "pydoc roundup.password" for -# implementation details. Have updated some of the documentation too. -# -# Revision 1.16 2001/10/05 02:23:24 richard -# . roundup-admin create now prompts for property info if none is supplied -# on the command-line. -# . hyperdb Class getprops() method may now return only the mutable -# properties. -# . Login now uses cookies, which makes it a whole lot more flexible. We can -# now support anonymous user access (read-only, unless there's an -# "anonymous" user, in which case write access is permitted). Login -# handling has been moved into cgi_client.Client.main() -# . The "extended" schema is now the default in roundup init. -# . The schemas have had their page headings modified to cope with the new -# login handling. Existing installations should copy the interfaces.py -# file from the roundup lib directory to their instance home. -# . Incorrectly had a Bizar Software copyright on the cgitb.py module from -# Ping - has been removed. -# . Fixed a whole bunch of places in the CGI interface where we should have -# been returning Not Found instead of throwing an exception. -# . Fixed a deviation from the spec: trying to modify the 'id' property of -# an item now throws an exception. -# -# Revision 1.15 2001/08/30 06:01:17 richard -# Fixed missing import in mailgw :( -# -# Revision 1.14 2001/08/13 23:02:54 richard -# Make the mail parser a little more robust. -# -# Revision 1.13 2001/08/12 06:32:36 richard -# using isinstance(blah, Foo) now instead of isFooType -# -# Revision 1.12 2001/08/08 01:27:00 richard -# Added better error handling to mailgw. -# -# Revision 1.11 2001/08/08 00:08:03 richard -# oops ;) -# -# Revision 1.10 2001/08/07 00:24:42 richard -# stupid typo -# -# Revision 1.9 2001/08/07 00:15:51 richard -# Added the copyright/license notice to (nearly) all files at request of -# Bizar Software. -# -# Revision 1.8 2001/08/05 07:06:07 richard -# removed some print statements -# -# Revision 1.7 2001/08/03 07:18:22 richard -# Implemented correct mail splitting (was taking a shortcut). Added unit -# tests. Also snips signatures now too. -# -# Revision 1.6 2001/08/01 04:24:21 richard -# mailgw was assuming certain properties existed on the issues being created. -# -# Revision 1.5 2001/07/29 07:01:39 richard -# Added vim command to all source so that we don't get no steenkin' tabs :) -# -# Revision 1.4 2001/07/28 06:43:02 richard -# Multipart message class has the getPart method now. Added some tests for it. -# -# Revision 1.3 2001/07/28 00:34:34 richard -# Fixed some non-string node ids. -# -# Revision 1.2 2001/07/22 12:09:32 richard -# Final commit of Grande Splite -# -# # vim: set filetype=python ts=4 sw=4 et si