source: emailtotracscript/trunk/email2trac.py.in @ 97

Last change on this file since 97 was 97, checked in by bas, 18 years ago

EmailtoTracScript?:

email2trac.py.in:

  • Some control changes
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 20.8 KB
Line 
1#!@PYTHON@
2# Copyright (C) 2002
3#
4# This file is part of the email2trac utils
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any
9# later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19#
20# For vi/emacs or other use tabstop=4 (vi: set ts=4)
21#
22"""
23email2trac.py -- Email tickets to Trac.
24
25A simple MTA filter to create Trac tickets from inbound emails.
26
27Copyright 2005, Daniel Lundin <daniel@edgewall.com>
28Copyright 2005, Edgewall Software
29
30Changed By: Bas van der Vlies <basv@sara.nl>
31Date      : 13 September 2005
32Descr.    : Added config file and command line options, spam level
33            detection, reply address and mailto option. Unicode support
34
35Changed By: Walter de Jong <walter@sara.nl>
36Descr.    : multipart-message code and trac attachments
37
38
39The scripts reads emails from stdin and inserts directly into a Trac database.
40MIME headers are mapped as follows:
41
42        * From:      => Reporter
43                     => CC (Optional via reply_address option)
44        * Subject:   => Summary
45        * Body       => Description
46        * Component  => Can be set to SPAM via spam_level option
47
48How to use
49----------
50 * Create an config file:
51        [DEFAULT]                      # REQUIRED
52        project      : /data/trac/test # REQUIRED
53        debug        : 1               # OPTIONAL, if set print some DEBUG info
54        spam_level   : 4               # OPTIONAL, if set check for SPAM mail
55        reply_address: 1               # OPTIONAL, if set then fill in ticket CC field
56        umask        : 022             # OPTIONAL, if set then use this umask for creation of the attachments
57        mailto_link  : 1               # OPTIONAL, if set then [mailto:<>] in description
58        mailto_cc    : basv@sara.nl    # OPTIONAL, use this address as CC in mailto line
59        ticket_update: 1               # OPTIONAL, if set then check if this is an update for a ticket
60        trac_version : 0.8             # OPTIONAL, default is 0.9
61
62        [jouvin]                         # OPTIONAL project declaration, if set both fields necessary
63        project      : /data/trac/jouvin # use -p|--project jouvin. 
64       
65 * default config file is : /etc/email2trac.conf
66
67 * Commandline opions:
68                -h | --help
69                -c <value> | --component=<value>
70                -f <config file> | --file=<config file>
71                -p <project name> | --project=<project name>
72
73SVN Info:
74        $Id: email2trac.py.in 97 2006-07-13 06:43:44Z bas $
75"""
76import os
77import sys
78import string
79import getopt
80import stat
81import time
82import email
83import re
84import urllib
85import unicodedata
86import ConfigParser
87from email import Header
88from stat import *
89import mimetypes
90import syslog
91import traceback
92
93
94# Some global variables
95#
96trac_default_version = 0.9
97m = None
98
99
100class TicketEmailParser(object):
101        env = None
102        comment = '> '
103   
104        def __init__(self, env, parameters, version):
105                self.env = env
106
107                # Database connection
108                #
109                self.db = None
110
111                # Some useful mail constants
112                #
113                self.author = None
114                self.email_addr = None
115                self.email_field = None
116
117                self.VERSION = version
118                if self.VERSION == 0.8:
119                        self.get_config = self.env.get_config
120                else:
121                        self.get_config = self.env.config.get
122
123                if parameters.has_key('umask'):
124                        os.umask(int(parameters['umask'], 8))
125
126                if parameters.has_key('debug'):
127                        self.DEBUG = int(parameters['debug'])
128                else:
129                        self.DEBUG = 0
130
131                if parameters.has_key('mailto_link'):
132                        self.MAILTO = int(parameters['mailto_link'])
133                        if parameters.has_key('mailto_cc'):
134                                self.MAILTO_CC = parameters['mailto_cc']
135                        else:
136                                self.MAILTO_CC = ''
137                else:
138                        self.MAILTO = 0
139
140                if parameters.has_key('spam_level'):
141                        self.SPAM_LEVEL = int(parameters['spam_level'])
142                else:
143                        self.SPAM_LEVEL = 0
144
145                if parameters.has_key('email_comment'):
146                        self.comment = str(parameters['email_comment'])
147
148                if parameters.has_key('email_header'):
149                        self.EMAIL_HEADER = int(parameters['email_header'])
150                else:
151                        self.EMAIL_HEADER = 0
152
153                if parameters.has_key('alternate_notify_template'):
154                        self.notify_template = str(parameters['alternate_notify_template'])
155                else:
156                        self.notify_template = None
157
158                if parameters.has_key('reply_all'):
159                        self.REPLY_ALL = int(parameters['reply_all'])
160                else:
161                        self.REPLY_ALL = 0
162
163                if parameters.has_key('ticket_update'):
164                        self.TICKET_UPDATE = int(parameters['ticket_update'])
165                else:
166                        self.TICKET_UPDATE = 0
167
168
169        # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
170        # Note if Spam_level then '*' are included
171        def spam(self, message):
172                if message.has_key('X-Spam-Score'):
173                        spam_l = string.split(message['X-Spam-Score'])
174                        number = spam_l[0].count('*')
175
176                        if number >= self.SPAM_LEVEL:
177                                return 'Spam'
178
179                elif message.has_key('X-Virus-found'):                  # treat virus mails as spam
180                        return 'Spam'
181
182                return self.get_config('ticket', 'default_component')
183
184        def to_unicode(self, str):
185                """
186                Email has 7 bit ASCII code, convert it to unicode with the charset
187        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
188                understands it.
189                """
190                results =  Header.decode_header(str)
191                str = None
192                for text,format in results:
193                        if format:
194                                try:
195                                        temp = unicode(text, format)
196                                except (UnicodeError,LookupError):
197                                        # This always works
198                                        #
199                                        temp = unicode(text, 'iso-8859-15')
200                                       
201                                temp =  temp.encode('utf-8')
202                        else:
203                                temp = string.strip(text)
204                                temp = unicode(text, 'iso-8859-15')
205
206                        if str:
207                                str = u'%s %s' %(str, temp)
208                        else:
209                                str = u'%s' %temp
210
211                str = str.encode('utf-8')
212                return str
213
214        def debug_attachments(self, message):
215                n = 0
216                for part in message.walk():
217                        if part.get_content_maintype() == 'multipart':      # multipart/* is just a container
218                                print 'TD: multipart container'
219                                continue
220
221                        n = n + 1
222                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
223                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
224
225                        if part.is_multipart():
226                                print 'TD: this part is multipart'
227                                payload = part.get_payload(decode=1)
228                                print 'TD: payload:', payload
229                        else:
230                                print 'TD: this part is not multipart'
231
232                        part_file = '/var/tmp/part%d' % n
233                        print 'TD: writing part%d (%s)' % (n,part_file)
234                        fx = open(part_file, 'wb')
235                        text = part.get_payload(decode=1)
236                        if not text:
237                                text = '(None)'
238                        fx.write(text)
239                        fx.close()
240                        try:
241                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
242                        except OSError:
243                                pass
244
245        def email_header_txt(self, m):
246                """
247                Display To and CC addresses in description field
248                """
249                str = ''
250                if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
251                        str = "'''To:''' %s [[BR]]" %(m['To'])
252                if m['Cc'] and len(m['Cc']) > 0:
253                        str = "%s'''Cc:''' %s [[BR]]" % (str, m['Cc'])
254
255                return  self.to_unicode(str)
256
257        def set_owner(self, ticket):
258                """
259                Select default owner for ticket component
260                """
261                cursor = self.db.cursor()
262                sql = "SELECT owner FROM component WHERE name='%s'" % ticket['component']
263                cursor.execute(sql)
264                try:
265                        ticket['owner'] = cursor.fetchone()[0]
266                except TypeError, detail:
267                        ticket['owner'] = "UNKNOWN"
268
269        def get_author_emailaddrs(self, message):
270                """
271                Get the default author name and email address from the message
272                """
273                self.author, self.email_addr  = email.Utils.parseaddr(message['from'])
274
275                # Look for email address in registered trac users
276                #
277                if self.VERSION == 0.8:
278                        users = []
279                else:
280                        users = [ u for (u, n, e) in self.env.get_known_users(self.db)
281                                if e == self.email_addr ]
282
283                if len(users) == 1:
284                        self.email_field = users[0]
285                else:
286                        self.email_field =  self.to_unicode(message['from'])
287
288        def set_reply_fields(self, ticket, message):
289                """
290                Set all the right fields for a new ticket
291                """
292                ticket['reporter'] = self.email_field
293
294                # Put all CC-addresses in ticket CC field
295                #
296                if self.REPLY_ALL:
297                        #tos = message.get_all('to', [])
298                        ccs = message.get_all('cc', [])
299
300                        addrs = email.Utils.getaddresses(ccs)
301
302                        # Remove reporter email address if notification is
303                        # on
304                        #
305                        if self.notification:
306                                try:
307                                        addrs.remove((self.author, self.email_addr))
308                                except ValueError, detail:
309                                        pass
310
311                        for name,mail in addrs:
312                                        try:
313                                                mail_list = '%s,%s' %(mail_list, mail)
314                                        except:
315                                                mail_list = mail
316
317                        ticket['cc'] = self.to_unicode(mail_list)
318
319        def save_email_for_debug(self, message, tempfile=False):
320                if tempfile:
321                        import tempfile
322                        msg_file = tempfile.mktemp('.email2trac')
323                else:
324                        msg_file = '/var/tmp/msg.txt'
325                print 'TD: saving email to %s' % msg_file
326                fx = open(msg_file, 'wb')
327                fx.write('%s' % message)
328                fx.close()
329                try:
330                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
331                except OSError:
332                        pass
333
334        def ticket_update(self, m):
335                """
336                If the current email is a reply to an existing ticket, this function
337                will append the contents of this email to that ticket, instead of
338                creating a new one.
339                """
340                if not m['Subject']:
341                        return False
342                else:
343                        subject  = self.to_unicode(m['Subject'])
344
345                TICKET_RE = re.compile(r"""
346                                        (?P<ticketnr>[#][0-9]+:)
347                                        """, re.VERBOSE)
348
349                result =  TICKET_RE.search(subject)
350                if not result:
351                        return False
352
353                body_text = self.get_body_text(m)
354                body_text = '{{{\n%s\n}}}' %body_text
355
356                # Strip '#' and ':' from ticket_id
357                #
358                ticket_id = result.group('ticketnr')
359                ticket_id = int(ticket_id[1:-1])
360
361                # Get current time
362                #
363                when = int(time.time())
364
365                if self.VERSION  == 0.8:
366                        tkt = Ticket(self.db, ticket_id)
367                        tkt.save_changes(self.db, self.author, body_text, when)
368                else:
369                        tkt = Ticket(self.env, ticket_id, self.db)
370                        tkt.save_changes(self.author, body_text, when)
371                        tkt['id'] = ticket_id
372
373                self.attachments(m, tkt)
374
375                if self.notification:
376                        self.notify(tkt, False, when)
377
378                return True
379
380        def new_ticket(self, msg):
381                """
382                Create a new ticket
383                """
384                tkt = Ticket(self.env)
385                tkt['status'] = 'new'
386
387                # Some defaults
388                #
389                tkt['milestone'] = self.get_config('ticket', 'default_milestone')
390                tkt['priority'] = self.get_config('ticket', 'default_priority')
391                tkt['severity'] = self.get_config('ticket', 'default_severity')
392                tkt['version'] = self.get_config('ticket', 'default_version')
393
394                if not msg['Subject']:
395                        tkt['summary'] = u'(geen subject)'
396                else:
397                        tkt['summary'] = self.to_unicode(msg['Subject'])
398
399
400                if settings.has_key('component'):
401                        tkt['component'] = settings['component']
402                else:
403                        tkt['component'] = self.spam(msg)
404
405                # Must make this an option or so, discard SPAM messages or save then
406                # and delete later
407                #
408                #if self.SPAM_LEVEL and self.spam(msg):
409                #       print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
410                #       sys.exit(1)
411
412                # Set default owner for component
413                #
414                self.set_owner(tkt)
415                self.set_reply_fields(tkt, msg)
416
417                # produce e-mail like header
418                #
419                head = ''
420                if self.EMAIL_HEADER > 0:
421                        head = self.email_header_txt(msg)
422                       
423
424                body_text = self.get_body_text(msg)
425                body_text = ('\r\n'.join(body_text.splitlines()))
426
427                # A patch so that the web-interface will not update the description
428                # field of a ticket
429                #
430                tkt['description'] = u'email2trac:%s\r\n{{{\r\n%s\r\n}}}' \
431                        %(head, body_text)
432
433                when = int(time.time())
434                if self.VERSION == 0.8:
435                        ticket_id = tkt.insert(self.db)
436                else:
437                        ticket_id = tkt.insert()
438
439                changed = False
440                comment = ''
441
442                # Rewrite the description if we have mailto enabled
443                #
444                if self.MAILTO:
445                        mailto = self.html_mailto_link(self.to_unicode(msg['subject']), ticket_id, body_text)
446                        tkt['description'] = u'email2trac:%s%s\r\n{{{\r\n%s\r\n}}}' %(head, mailto, body_text)
447                        comment = '\nadded mailto line\n'
448                        changed = True
449
450                n =  self.attachments(msg, tkt)
451                if n:
452                        comment = '%s\nThis message has %d attachment(s)\n' %(comment, n)
453                        changed = True
454
455                if changed:
456                        if self.VERSION  == 0.8:
457                                tkt.save_changes(self.db, self.author, comment)
458                        else:
459                                tkt.save_changes(self.author, comment)
460
461                #print tkt.get_changelog(self.db, when)
462
463                if self.notification:
464                        self.notify(tkt, True)
465                        #self.notify(tkt, False)
466
467        def parse(self, fp):
468                global m
469
470                m = email.message_from_file(fp)
471                if not m:
472                        return
473
474                if self.DEBUG > 1:        # save the entire e-mail message text
475                        self.save_email_for_debug(m)
476                        self.debug_attachments(m)
477
478                self.db = self.env.get_db_cnx()
479                self.get_author_emailaddrs(m)
480
481                if self.get_config('notification', 'smtp_enabled') in ['true']:
482                        self.notification = 1
483                else:
484                        self.notification = 0
485
486                # Must we update existing tickets
487                #
488                if self.TICKET_UPDATE > 0:
489                        if self.ticket_update(m):
490                                return True
491
492                self.new_ticket(m)
493
494        def get_body_text(self, msg):
495                """
496                put the message text in the ticket description or in the changes field.
497                message text can be plain text or html or something else
498                """
499                has_description = 0
500                ubody_text = u'\r\n{{{\nNo plain text message\n}}}\r\n'
501                for part in msg.walk():
502
503                        # 'multipart/*' is a container for multipart messages
504                        #
505                        if part.get_content_maintype() == 'multipart':
506                                continue
507
508                        if part.get_content_type() == 'text/plain':
509                                # Try to decode, if fails then do not decode
510                                #
511                                body_text = part.get_payload(decode=1)
512                                if not body_text:                       
513                                        body_text = part.get_payload(decode=0)
514
515                                # Get contents charset (iso-8859-15 if not defined in mail headers)
516                                # UTF-8 encode body_text
517                                #
518                                charset = part.get_content_charset('iso-8859-15')
519                                try:
520                                        ubody_text = unicode(body_text, charset)
521                                except (UnicodeError,LookupError):
522                                        ubody_text = unicode(body_text, 'iso-8859-15')
523
524                        elif part.get_content_type() == 'text/html':
525                                ubody_text = u'\r\n(see attachment for HTML mail message)\r\n'
526
527                        else:
528                                ubody_text = u'\r\n(see attachment for message)\r\n'
529
530                        has_description = 1
531                        break           # we have the description, so break
532
533                if not has_description:
534                        ubody_text = u'\r\n(see attachment for message)\r\n'
535
536                return ubody_text.encode('utf-8')
537
538        def notify(self, tkt , new=True, modtime=0):
539                """
540                A wrapper for the TRAC notify function. So we can use templates
541                """
542                try:
543                        # create false {abs_}href properties, to trick Notify()
544                        #
545                        self.env.abs_href = Href(self.get_config('project', 'url'))
546                        self.env.href = Href(self.get_config('project', 'url'))
547
548                        tn = TicketNotifyEmail(self.env)
549                        if self.notify_template:
550                                tn.template_name = self.notify_template;
551
552                        tn.notify(tkt, new, modtime)
553
554                except Exception, e:
555                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(tkt['id'], e)
556
557        def mail_line(self, str):
558                return '%s %s' % (self.comment, str)
559
560
561        def html_mailto_link(self, subject, id, body):
562                if not self.author:
563                        author = self.email_addr
564                else:   
565                        author = self.to_unicode(self.author)
566
567                # Must find a fix
568                #
569                #arr = string.split(body, '\n')
570                #arr = map(self.mail_line, arr)
571                #body = string.join(arr, '\n')
572                #body = '%s wrote:\n%s' %(author, body)
573
574                # Temporary fix
575                str = 'mailto:%s?Subject=%s&Cc=%s' %(
576                       urllib.quote(self.email_addr),
577                           urllib.quote('Re: #%s: %s' %(id, subject)),
578                           urllib.quote(self.MAILTO_CC)
579                           )
580
581                str = '\r\n{{{\r\n#!html\r\n<a href="%s">Reply to: %s</a>\r\n}}}\r\n' %(str, author)
582
583                return str
584
585        def attachments(self, message, ticket):
586                '''
587                save any attachments as files in the ticket's directory
588                '''
589                count = 0
590                first = 0
591                number = 0
592                for part in message.walk():
593                        if part.get_content_maintype() == 'multipart':          # multipart/* is just a container
594                                continue
595
596                        if not first:                                                                           # first content is the message
597                                first = 1
598                                if part.get_content_type() == 'text/plain':             # if first is text, is was already put in the description
599                                        continue
600
601                        filename = part.get_filename()
602                        count = count + 1
603                        if not filename:
604                                number = number + 1
605                                filename = 'part%04d' % number
606
607                                ext = mimetypes.guess_extension(part.get_content_type())
608                                if not ext:
609                                        ext = '.bin'
610
611                                filename = '%s%s' % (filename, ext)
612                        else:
613                                filename = self.to_unicode(filename)
614
615                        # From the trac code
616                        #
617                        filename = filename.replace('\\', '/').replace(':', '/')
618                        filename = os.path.basename(filename)
619
620                        # We try to normalize the filename to utf-8 NFC if we can.
621                        # Files uploaded from OS X might be in NFD.
622                        # Check python version and then try it
623                        #
624                        if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
625                                try:
626                                        filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
627                                except TypeError:
628                                        pass
629
630                        url_filename = urllib.quote(filename)
631                        if self.VERSION == 0.8:
632                                dir = os.path.join(self.env.get_attachments_dir(), 'ticket',
633                                                        urllib.quote(str(ticket['id'])))
634                                if not os.path.exists(dir):
635                                        mkdir_p(dir, 0755)
636                        else:
637                                dir = '/tmp'
638
639                        path, fd =  util.create_unique_file(os.path.join(dir, url_filename))
640                        text = part.get_payload(decode=1)
641                        if not text:
642                                text = '(None)'
643                        fd.write(text)
644                        fd.close()
645
646                        # get the filesize
647                        #
648                        stats = os.lstat(path)
649                        filesize = stats[stat.ST_SIZE]
650
651                        # Insert the attachment it differs for the different TRAC versions
652                        #
653                        if self.VERSION == 0.8:
654                                cursor = self.db.cursor()
655                                try:
656                                        cursor.execute('INSERT INTO attachment VALUES("%s","%s","%s",%d,%d,"%s","%s","%s")'
657                                                %('ticket', urllib.quote(str(ticket['id'])), filename + '?format=raw', filesize,
658                                                int(time.time()),'', self.author, 'e-mail') )
659
660                                # Attachment is already known
661                                #
662                                except sqlite.IntegrityError:   
663                                        #self.db.close()
664                                        return count
665
666                                self.db.commit()
667
668                        else:
669                                fd = open(path)
670                                att = attachment.Attachment(self.env, 'ticket', ticket['id'])
671                                att.insert(url_filename, fd, filesize)
672                                fd.close()
673
674                # Return how many attachments
675                #
676                return count
677
678
679def mkdir_p(dir, mode):
680        '''do a mkdir -p'''
681
682        arr = string.split(dir, '/')
683        path = ''
684        for part in arr:
685                path = '%s/%s' % (path, part)
686                try:
687                        stats = os.stat(path)
688                except OSError:
689                        os.mkdir(path, mode)
690
691
692def ReadConfig(file, name):
693        """
694        Parse the config file
695        """
696
697        if not os.path.isfile(file):
698                print 'File %s does not exist' %file
699                sys.exit(1)
700
701        config = ConfigParser.ConfigParser()
702        try:
703                config.read(file)
704        except ConfigParser.MissingSectionHeaderError,detail:
705                print detail
706                sys.exit(1)
707
708
709        # Use given project name else use defaults
710        #
711        if name:
712                if not config.has_section(name):
713                        print "Not a valid project name: %s" %name
714                        print "Valid names: %s" %config.sections()
715                        sys.exit(1)
716
717                project =  dict()
718                for option in  config.options(name):
719                        project[option] = config.get(name, option)
720
721        else:
722                project = config.defaults()
723
724        return project
725
726
727if __name__ == '__main__':
728        # Default config file
729        #
730        configfile = '@email2trac_conf@'
731        project = ''
732        component = ''
733        ENABLE_SYSLOG = 0
734               
735        try:
736                opts, args = getopt.getopt(sys.argv[1:], 'chf:p:', ['component=','help', 'file=', 'project='])
737        except getopt.error,detail:
738                print __doc__
739                print detail
740                sys.exit(1)
741       
742        project_name = None
743        for opt,value in opts:
744                if opt in [ '-h', '--help']:
745                        print __doc__
746                        sys.exit(0)
747                elif opt in ['-c', '--component']:
748                        component = value
749                elif opt in ['-f', '--file']:
750                        configfile = value
751                elif opt in ['-p', '--project']:
752                        project_name = value
753       
754        settings = ReadConfig(configfile, project_name)
755        if not settings.has_key('project'):
756                print __doc__
757                print 'No Trac project is defined in the email2trac config file.'
758                sys.exit(1)
759       
760        if component:
761                settings['component'] = component
762       
763        if settings.has_key('trac_version'):
764                version = float(settings['trac_version'])
765        else:
766                version = trac_default_version
767
768        if settings.has_key('enable_syslog'):
769                ENABLE_SYSLOG =  float(settings['enable_syslog'])
770                       
771        #debug HvB
772        #print settings
773       
774        try:
775                if version == 0.8:
776                        from trac.Environment import Environment
777                        from trac.Ticket import Ticket
778                        from trac.Notify import TicketNotifyEmail
779                        from trac.Href import Href
780                        from trac import util
781                        import sqlite
782                elif version == 0.9:
783                        from trac import attachment
784                        from trac.env import Environment
785                        from trac.ticket import Ticket
786                        from trac.web.href import Href
787                        from trac import util
788                        from trac.Notify import TicketNotifyEmail
789                elif version == 0.10:
790                        from trac import attachment
791                        from trac.env import Environment
792                        from trac.ticket import Ticket
793                        from trac.web.href import Href
794                        from trac import util
795                        # see http://projects.edgewall.com/trac/changeset/2799
796                        from trac.ticket.notification import TicketNotifyEmail
797       
798                env = Environment(settings['project'], create=0)
799                tktparser = TicketEmailParser(env, settings, version)
800                tktparser.parse(sys.stdin)
801
802        # Catch all errors ans log to SYSLOG if we have enabled this
803        # else stdout
804        #
805        except Exception, error:
806                if ENABLE_SYSLOG:
807                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
808                        etype, evalue, etb = sys.exc_info()
809                        for e in traceback.format_exception(etype, evalue, etb):
810                                syslog.syslog(e)
811                        syslog.closelog()
812                else:
813                        traceback.print_exc()
814
815                if m:
816                        self.save_email_for_debug(m, True)
817
818# EOB
Note: See TracBrowser for help on using the repository browser.