source: trunk/email2trac.py.in @ 297

Last change on this file since 297 was 297, checked in by bas, 12 years ago

added a new parameter subject_field_separator default value is &, closes #166

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 39.7 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
30Authors:
31  Bas van der Vlies <basv@sara.nl>
32  Walter de Jong <walter@sara.nl>
33
34The scripts reads emails from stdin and inserts directly into a Trac database.
35
36How to use
37----------
38 * See https://subtrac.sara.nl/oss/email2trac/
39
40 * Create an config file:
41    [DEFAULT]                        # REQUIRED
42    project      : /data/trac/test   # REQUIRED
43    debug        : 1                 # OPTIONAL, if set print some DEBUG info
44    trac_version : 0.10              # OPTIONAL, default is 0.11
45
46    [jouvin]                         # OPTIONAL project declaration, if set both fields necessary
47    project      : /data/trac/jouvin # use -p|--project jouvin. 
48       
49 * default config file is : /etc/email2trac.conf
50
51 * Commandline opions:
52                -h,--help
53                -f,--file  <configuration file>
54                -n,--dry-run
55                -p, --project <project name>
56                -t, --ticket_prefix <name>
57
58SVN Info:
59        $Id: email2trac.py.in 297 2010-01-07 15:49:51Z bas $
60"""
61import os
62import sys
63import string
64import getopt
65import stat
66import time
67import email
68import email.Iterators
69import email.Header
70import re
71import urllib
72import unicodedata
73from stat import *
74import mimetypes
75import traceback
76
77
78# Will fail where unavailable, e.g. Windows
79#
80try:
81    import syslog
82    SYSLOG_AVAILABLE = True
83except ImportError:
84    SYSLOG_AVAILABLE = False
85
86from datetime import tzinfo, timedelta, datetime
87from trac import config as trac_config
88
89# Some global variables
90#
91trac_default_version = '0.11'
92m = None
93
94# A UTC class needed for trac version 0.11, added by
95# tbaschak at ktc dot mb dot ca
96#
97class UTC(tzinfo):
98        """UTC"""
99        ZERO = timedelta(0)
100        HOUR = timedelta(hours=1)
101       
102        def utcoffset(self, dt):
103                return self.ZERO
104               
105        def tzname(self, dt):
106                return "UTC"
107               
108        def dst(self, dt):
109                return self.ZERO
110
111
112class TicketEmailParser(object):
113        env = None
114        comment = '> '
115   
116        def __init__(self, env, parameters, version):
117                self.env = env
118
119                # Database connection
120                #
121                self.db = None
122
123                # Save parameters
124                #
125                self.parameters = parameters
126
127                # Some useful mail constants
128                #
129                self.email_name = None
130                self.email_addr = None
131                self.email_from = None
132                self.author     = None
133                self.id         = None
134               
135                self.STRIP_CONTENT_TYPES = list()
136
137                self.VERSION = version
138                self.DRY_RUN = parameters['dry_run']
139
140                self.get_config = self.env.config.get
141
142                if parameters.has_key('umask'):
143                        os.umask(int(parameters['umask'], 8))
144
145                if parameters.has_key('debug'):
146                        self.DEBUG = int(parameters['debug'])
147                else:
148                        self.DEBUG = 0
149
150                if parameters.has_key('mailto_link'):
151                        self.MAILTO = int(parameters['mailto_link'])
152                        if parameters.has_key('mailto_cc'):
153                                self.MAILTO_CC = parameters['mailto_cc']
154                        else:
155                                self.MAILTO_CC = ''
156                else:
157                        self.MAILTO = 0
158
159                if parameters.has_key('spam_level'):
160                        self.SPAM_LEVEL = int(parameters['spam_level'])
161                else:
162                        self.SPAM_LEVEL = 0
163
164                if parameters.has_key('spam_header'):
165                        self.SPAM_HEADER = parameters['spam_header']
166                else:
167                        self.SPAM_HEADER = 'X-Spam-Score'
168
169                if parameters.has_key('email_quote'):
170                        self.EMAIL_QUOTE = str(parameters['email_quote'])
171                else:   
172                        self.EMAIL_QUOTE = '> '
173
174                if parameters.has_key('email_header'):
175                        self.EMAIL_HEADER = int(parameters['email_header'])
176                else:
177                        self.EMAIL_HEADER = 0
178
179                if parameters.has_key('alternate_notify_template'):
180                        self.notify_template = str(parameters['alternate_notify_template'])
181                else:
182                        self.notify_template = None
183
184                if parameters.has_key('alternate_notify_template_update'):
185                        self.notify_template_update = str(parameters['alternate_notify_template_update'])
186                else:
187                        self.notify_template_update = None
188
189                if parameters.has_key('reply_all'):
190                        self.REPLY_ALL = int(parameters['reply_all'])
191                else:
192                        self.REPLY_ALL = 0
193
194                if parameters.has_key('ticket_update'):
195                        self.TICKET_UPDATE = int(parameters['ticket_update'])
196                else:
197                        self.TICKET_UPDATE = 0
198
199                if parameters.has_key('drop_spam'):
200                        self.DROP_SPAM = int(parameters['drop_spam'])
201                else:
202                        self.DROP_SPAM = 0
203
204                if parameters.has_key('verbatim_format'):
205                        self.VERBATIM_FORMAT = int(parameters['verbatim_format'])
206                else:
207                        self.VERBATIM_FORMAT = 1
208
209                if parameters.has_key('reflow'):
210                        self.REFLOW = int(parameters['reflow'])
211                else:
212                        self.REFLOW = 1
213
214                if parameters.has_key('drop_alternative_html_version'):
215                        self.DROP_ALTERNATIVE_HTML_VERSION = int(parameters['drop_alternative_html_version'])
216                else:
217                        self.DROP_ALTERNATIVE_HTML_VERSION = 0
218
219                if parameters.has_key('strip_signature'):
220                        self.STRIP_SIGNATURE = int(parameters['strip_signature'])
221                else:
222                        self.STRIP_SIGNATURE = 0
223
224                if parameters.has_key('strip_quotes'):
225                        self.STRIP_QUOTES = int(parameters['strip_quotes'])
226                else:
227                        self.STRIP_QUOTES = 0
228
229                if parameters.has_key('use_textwrap'):
230                        self.USE_TEXTWRAP = int(parameters['use_textwrap'])
231                else:
232                        self.USE_TEXTWRAP = 0
233
234                if parameters.has_key('binhex'):
235                        self.STRIP_CONTENT_TYPES.append('application/mac-binhex40')
236
237                if parameters.has_key('applesingle'):
238                        self.STRIP_CONTENT_TYPES.append('application/applefile')
239
240                if parameters.has_key('appledouble'):
241                        self.STRIP_CONTENT_TYPES.append('application/applefile')
242
243                if parameters.has_key('strip_content_types'):
244                        items = parameters['strip_content_types'].split(',')
245                        for item in items:
246                                self.STRIP_CONTENT_TYPES.append(item.strip())
247
248                self.WORKFLOW = None
249                if parameters.has_key('workflow'):
250                        self.WORKFLOW = parameters['workflow']
251
252                # Use OS independend functions
253                #
254                self.TMPDIR = os.path.normcase('/tmp')
255                if parameters.has_key('tmpdir'):
256                        self.TMPDIR = os.path.normcase(str(parameters['tmpdir']))
257
258                if parameters.has_key('ignore_trac_user_settings'):
259                        self.IGNORE_TRAC_USER_SETTINGS = int(parameters['ignore_trac_user_settings'])
260                else:
261                        self.IGNORE_TRAC_USER_SETTINGS = 0
262
263                if parameters.has_key('subject_field_separator'):
264                        self.SUBJECT_FIELD_SEPARATOR = parameters['subject_field_separator'].strip()
265                else:
266                        self.SUBJECT_FIELD_SEPARATOR = '&'
267
268        def spam(self, message):
269                """
270                # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
271                # Note if Spam_level then '*' are included
272                """
273                spam = False
274                if message.has_key(self.SPAM_HEADER):
275                        spam_l = string.split(message[self.SPAM_HEADER])
276
277                        try:
278                                number = spam_l[0].count('*')
279                        except IndexError, detail:
280                                number = 0
281                               
282                        if number >= self.SPAM_LEVEL:
283                                spam = True
284                               
285                # treat virus mails as spam
286                #
287                elif message.has_key('X-Virus-found'):                 
288                        spam = True
289
290                # How to handle SPAM messages
291                #
292                if self.DROP_SPAM and spam:
293                        if self.DEBUG > 2 :
294                                print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
295
296                        return 'drop'   
297
298                elif spam:
299
300                        return 'Spam'   
301
302                else:
303
304                        return False
305
306        def email_header_acl(self, keyword, header_field, default):
307                """
308                This function wil check if the email address is allowed or denied
309                to send mail to the ticket list
310            """
311                try:
312                        mail_addresses = self.parameters[keyword]
313
314                        # Check if we have an empty string
315                        #
316                        if not mail_addresses:
317                                return default
318
319                except KeyError, detail:
320                        if self.DEBUG > 2 :
321                                print 'TD: %s not defined, all messages are allowed.' %(keyword)
322
323                        return default
324
325                mail_addresses = string.split(mail_addresses, ',')
326
327                for entry in mail_addresses:
328                        entry = entry.strip()
329                        TO_RE = re.compile(entry, re.VERBOSE|re.IGNORECASE)
330                        result =  TO_RE.search(header_field)
331                        if result:
332                                return True
333
334                return False
335
336        def email_to_unicode(self, message_str):
337                """
338                Email has 7 bit ASCII code, convert it to unicode with the charset
339        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
340                understands it.
341                """
342                results =  email.Header.decode_header(message_str)
343                s = None
344                for text,format in results:
345                        if format:
346                                try:
347                                        temp = unicode(text, format)
348                                except UnicodeError, detail:
349                                        # This always works
350                                        #
351                                        temp = unicode(text, 'iso-8859-15')
352                                except LookupError, detail:
353                                        #text = 'ERROR: Could not find charset: %s, please install' %format
354                                        #temp = unicode(text, 'iso-8859-15')
355                                        temp = message_str
356                                       
357                        else:
358                                temp = string.strip(text)
359                                temp = unicode(text, 'iso-8859-15')
360
361                        if s:
362                                s = '%s %s' %(s, temp)
363                        else:
364                                s = '%s' %temp
365
366                #s = s.encode('utf-8')
367                return s
368
369        def debug_body(self, message_body, tempfile=False):
370                if tempfile:
371                        import tempfile
372                        body_file = tempfile.mktemp('.email2trac')
373                else:
374                        body_file = os.path.join(self.TMPDIR, 'body.txt')
375
376                print 'TD: writing body (%s)' % body_file
377                fx = open(body_file, 'wb')
378                if not message_body:
379                        message_body = '(None)'
380
381                message_body = message_body.encode('utf-8')
382                #message_body = unicode(message_body, 'iso-8859-15')
383
384                fx.write(message_body)
385                fx.close()
386                try:
387                        os.chmod(body_file,S_IRWXU|S_IRWXG|S_IRWXO)
388                except OSError:
389                        pass
390
391        def debug_attachments(self, message_parts):
392                n = 0
393                for part in message_parts:
394                        # Skip inline text parts
395                        if not isinstance(part, tuple):
396                                continue
397                               
398                        (original, filename, part) = part
399
400                        n = n + 1
401                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
402                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
403
404                        part_file = os.path.join(self.TMPDIR, filename)
405                        #part_file = '/var/tmp/part%d' % n
406                        print 'TD: writing part%d (%s)' % (n,part_file)
407                        fx = open(part_file, 'wb')
408                        text = part.get_payload(decode=1)
409                        if not text:
410                                text = '(None)'
411                        fx.write(text)
412                        fx.close()
413                        try:
414                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
415                        except OSError:
416                                pass
417
418        def email_header_txt(self, m):
419                """
420                Display To and CC addresses in description field
421                """
422                s = ''
423                #if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
424                if m['To'] and len(m['To']) > 0:
425                        s = "'''To:''' %s\r\n" %(m['To'])
426                if m['Cc'] and len(m['Cc']) > 0:
427                        s = "%s'''Cc:''' %s\r\n" % (s, m['Cc'])
428
429                return  self.email_to_unicode(s)
430
431
432        def get_sender_info(self, message):
433                """
434                Get the default author name and email address from the message
435                """
436
437                self.email_to = self.email_to_unicode(message['to'])
438                self.to_name, self.to_email_addr = email.Utils.parseaddr (self.email_to)
439
440                self.email_from = self.email_to_unicode(message['from'])
441                self.email_name, self.email_addr  = email.Utils.parseaddr(self.email_from)
442
443                # Trac can not handle author's name that contains spaces
444                #
445                self.author = self.email_addr
446
447                if self.IGNORE_TRAC_USER_SETTINGS:
448                        return
449
450                # Is this a registered user, use email address as search key:
451                # result:
452                #   u : login name
453                #   n : Name that the user has set in the settings tab
454                #   e : email address that the user has set in the settings tab
455                #
456                users = [ (u,n,e) for (u, n, e) in self.env.get_known_users(self.db)
457                        if e and (e.lower() == self.email_addr.lower()) ]
458
459                if len(users) == 1:
460                        self.email_from = users[0][0]
461                        self.author = users[0][0]
462
463        def set_reply_fields(self, ticket, message):
464                """
465                Set all the right fields for a new ticket
466                """
467
468                ## Only use name or email adress
469                #ticket['reporter'] = self.email_from
470                ticket['reporter'] = self.author
471
472
473                # Put all CC-addresses in ticket CC field
474                #
475                if self.REPLY_ALL:
476                        #tos = message.get_all('to', [])
477                        ccs = message.get_all('cc', [])
478
479                        addrs = email.Utils.getaddresses(ccs)
480                        if not addrs:
481                                return
482
483                        # Remove reporter email address if notification is
484                        # on
485                        #
486                        if self.notification:
487                                try:
488                                        addrs.remove((self.author, self.email_addr))
489                                except ValueError, detail:
490                                        pass
491
492                        for name,mail in addrs:
493                                try:
494                                        mail_list = '%s, %s' %(mail_list, mail)
495                                except UnboundLocalError, detail:
496                                        mail_list = mail
497
498                        if mail_list:
499                                ticket['cc'] = self.email_to_unicode(mail_list)
500
501        def save_email_for_debug(self, message, tempfile=False):
502                if tempfile:
503                        import tempfile
504                        msg_file = tempfile.mktemp('.email2trac')
505                else:
506                        #msg_file = '/var/tmp/msg.txt'
507                        msg_file = os.path.join(self.TMPDIR, 'msg.txt')
508
509                print 'TD: saving email to %s' % msg_file
510                fx = open(msg_file, 'wb')
511                fx.write('%s' % message)
512                fx.close()
513                try:
514                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
515                except OSError:
516                        pass
517
518        def str_to_dict(self, s):
519                """
520                Transfrom a string of the form [<key>=<value>]+ to dict[<key>] = <value>
521                """
522
523                fields = string.split(s, self.SUBJECT_FIELD_SEPARATOR)
524
525                result = dict()
526                for field in fields:
527                        try:
528                                index, value = string.split(field, '=')
529
530                                # We can not change the description of a ticket via the subject
531                                # line. The description is the body of the email
532                                #
533                                if index.lower() in ['description']:
534                                        continue
535
536                                if value:
537                                        result[index.lower()] = value
538
539                        except ValueError:
540                                pass
541                return result
542
543        def update_ticket_fields(self, ticket, user_dict, use_default=None):
544                """
545                This will update the ticket fields. It will check if the
546                given fields are known and if the right values are specified
547                It will only update the ticket field value:
548                        - If the field is known
549                        - If the value supplied is valid for the ticket field.
550                          If not then there are two options:
551                           1) Skip the value (use_default=None)
552                           2) Set default value for field (use_default=1)
553                """
554
555                # Build a system dictionary from the ticket fields
556                # with field as index and option as value
557                #
558                sys_dict = dict()
559                for field in ticket.fields:
560                        try:
561                                sys_dict[field['name']] = field['options']
562
563                        except KeyError:
564                                sys_dict[field['name']] = None
565                                pass
566
567                # Check user supplied fields an compare them with the
568                # system one's
569                #
570                for field,value in user_dict.items():
571                        if self.DEBUG >= 10:
572                                print  'user_field\t %s = %s' %(field,value)
573
574                        if sys_dict.has_key(field):
575
576                                # Check if value is an allowed system option, if TypeError then
577                                # every value is allowed
578                                #
579                                try:
580                                        if value in sys_dict[field]:
581                                                ticket[field] = value
582                                        else:
583                                                # Must we set a default if value is not allowed
584                                                #
585                                                if use_default:
586                                                        value = self.get_config('ticket', 'default_%s' %(field) )
587                                                        ticket[field] = value
588
589                                except TypeError:
590                                        ticket[field] = value
591
592                                if self.DEBUG >= 10:
593                                        print  'ticket_field\t %s = %s' %(field,  ticket[field])
594                                       
595        def ticket_update(self, m, id, spam):
596                """
597                If the current email is a reply to an existing ticket, this function
598                will append the contents of this email to that ticket, instead of
599                creating a new one.
600                """
601                if self.DEBUG:
602                        print "TD: ticket_update: %s" %id
603
604                # Must we update ticket fields
605                #
606                update_fields = dict()
607                try:
608                        id, keywords = string.split(id, '?')
609
610                        # Skip the last ':' character
611                        #
612                        keywords = keywords[:-1]
613                        update_fields = self.str_to_dict(keywords)
614
615                        # Strip '#'
616                        #
617                        self.id = int(id[1:])
618
619                except ValueError:
620                        # Strip '#' and ':'
621                        #
622                        self.id = int(id[1:-1])
623
624
625                # When is the change committed
626                #
627                if self.VERSION == 0.11:
628                        utc = UTC()
629                        when = datetime.now(utc)
630                else:
631                        when = int(time.time())
632
633                try:
634                        tkt = Ticket(self.env, self.id, self.db)
635                except util.TracError, detail:
636                        # Not a valid ticket
637                        self.id = None
638                        return False
639
640                # How many changes has this ticket
641                cnum = len(tkt.get_changelog())
642
643
644                # reopen the ticket if it is was closed
645                # We must use the ticket workflow framework
646                #
647                if tkt['status'] in ['closed']:
648
649                        #print controller.actions['reopen']
650                        #
651                        # As reference 
652                        # req = Mock(href=Href('/'), abs_href=Href('http://www.example.com/'), authname='anonymous', perm=MockPerm(), args={})
653                        #
654                        #a = controller.render_ticket_action_control(req, tkt, 'reopen')
655                        #print 'controller : ', a
656                        #
657                        #b = controller.get_all_status()
658                        #print 'get all status: ', b
659                        #
660                        #b = controller.get_ticket_changes(req, tkt, 'reopen')
661                        #print 'get_ticket_changes :', b
662
663                        if self.WORKFLOW and (self.VERSION in [0.11]) :
664                                from trac.ticket.default_workflow import ConfigurableTicketWorkflow
665                                from trac.test import Mock, MockPerm
666
667                                req = Mock(authname='anonymous', perm=MockPerm(), args={})
668
669                                controller = ConfigurableTicketWorkflow(self.env)
670                                fields = controller.get_ticket_changes(req, tkt, self.WORKFLOW)
671
672                                if self.DEBUG:
673                                        print 'TD: Workflow ticket update fields: ', fields
674
675                                for key in fields.keys():
676                                        tkt[key] = fields[key]
677
678                        else:
679                                tkt['status'] = 'reopened'
680                                tkt['resolution'] = ''
681
682                # Must we update some ticket fields properties
683                #
684                if update_fields:
685                        self.update_ticket_fields(tkt, update_fields)
686
687                message_parts = self.get_message_parts(m)
688                message_parts = self.unique_attachment_names(message_parts)
689
690                if self.EMAIL_HEADER:
691                        message_parts.insert(0, self.email_header_txt(m))
692
693                body_text = self.body_text(message_parts)
694
695                if body_text.strip() or update_fields:
696                        if self.DRY_RUN:
697                                print 'DRY_RUN: tkt.save_changes(self.author, body_text, ticket_change_number) ', self.author, cnum
698                        else:
699                                tkt.save_changes(self.author, body_text, when, None, str(cnum))
700                       
701
702                if self.VERSION  == 0.9:
703                        s = self.attachments(message_parts, True)
704                else:
705                        s = self.attachments(message_parts)
706
707                if self.notification and not spam:
708                        self.notify(tkt, False, when)
709
710                return True
711
712        def set_ticket_fields(self, ticket):
713                """
714                set the ticket fields to value specified
715                        - /etc/email2trac.conf with <prefix>_<field>
716                        - trac default values, trac.ini
717                """
718                user_dict = dict()
719
720                for field in ticket.fields:
721
722                        name = field['name']
723
724                        # skip some fields like resolution
725                        #
726                        if name in [ 'resolution' ]:
727                                continue
728
729                        # default trac value
730                        #
731                        if not field.get('custom'):
732                                value = self.get_config('ticket', 'default_%s' %(name) )
733                        else:
734                                value = field.get('value')
735                                options = field.get('options')
736                                if value and options and value not in options:
737                                        value = options[int(value)]
738
739                        if self.DEBUG > 10:
740                                print 'trac.ini name %s = %s' %(name, value)
741
742                        prefix = self.parameters['ticket_prefix']
743                        try:
744                                value = self.parameters['%s_%s' %(prefix, name)]
745                                if self.DEBUG > 10:
746                                        print 'email2trac.conf %s = %s ' %(name, value)
747
748                        except KeyError, detail:
749                                pass
750               
751                        if self.DEBUG:
752                                print 'user_dict[%s] = %s' %(name, value)
753
754                        user_dict[name] = value
755
756                self.update_ticket_fields(ticket, user_dict, use_default=1)
757
758                # Set status ticket
759                #`
760                ticket['status'] = 'new'
761
762
763
764        def new_ticket(self, msg, subject, spam, set_fields = None):
765                """
766                Create a new ticket
767                """
768                if self.DEBUG:
769                        print "TD: new_ticket"
770
771                tkt = Ticket(self.env)
772                self.set_ticket_fields(tkt)
773
774                # Old style setting for component, will be removed
775                #
776                if spam:
777                        tkt['component'] = 'Spam'
778
779                elif self.parameters.has_key('component'):
780                        tkt['component'] = self.parameters['component']
781
782                if not msg['Subject']:
783                        tkt['summary'] = u'(No subject)'
784                else:
785                        tkt['summary'] = subject
786
787                self.set_reply_fields(tkt, msg)
788
789                if set_fields:
790                        rest, keywords = string.split(set_fields, '?')
791
792                        if keywords:
793                                update_fields = self.str_to_dict(keywords)
794                                self.update_ticket_fields(tkt, update_fields)
795
796                # produce e-mail like header
797                #
798                head = ''
799                if self.EMAIL_HEADER > 0:
800                        head = self.email_header_txt(msg)
801
802                                       
803                message_parts = self.get_message_parts(msg)
804                if self.DEBUG:
805                        print 'TD: self.get_message_parts ',
806                        print message_parts
807
808                message_parts = self.unique_attachment_names(message_parts)
809                if self.DEBUG:
810                        print 'TD: self.unique_attachment_names',
811                        print message_parts
812               
813                if self.EMAIL_HEADER > 0:
814                        message_parts.insert(0, self.email_header_txt(msg))
815                       
816                body_text = self.body_text(message_parts)
817
818                tkt['description'] = body_text
819
820                #when = int(time.time())
821                #
822                utc = UTC()
823                when = datetime.now(utc)
824
825                if not self.DRY_RUN:
826                        self.id = tkt.insert()
827       
828                changed = False
829                comment = ''
830
831                # some routines in trac are dependend on ticket id     
832                # like alternate notify template
833                #
834                if self.notify_template:
835                        tkt['id'] = self.id
836                        changed = True
837
838                ## Rewrite the description if we have mailto enabled
839                #
840                if self.MAILTO:
841                        changed = True
842                        comment = u'\nadded mailto line\n'
843                        mailto = self.html_mailto_link( m['Subject'], body_text)
844
845                        tkt['description'] = u'%s\r\n%s%s\r\n' \
846                                %(head, mailto, body_text)
847       
848                ## Save the attachments to the ticket   
849                #
850                has_attachments =  self.attachments(message_parts)
851
852                #  Disabled
853                #if has_attachments:
854                #       changed = True
855                #       comment = '%s\n%s\n' %(comment, has_attachments)
856
857                if changed:
858                        if self.DRY_RUN:
859                                print 'DRY_RUN: tkt.save_changes(self.author, comment) ', self.author
860                        else:
861                                tkt.save_changes(self.author, comment)
862                                #print tkt.get_changelog(self.db, when)
863
864                if self.notification and not spam:
865                        self.notify(tkt, True)
866
867
868        def blog(self, id):
869                """
870                The blog create/update function
871                """
872                # import the modules
873                #
874                from tracfullblog.core import FullBlogCore
875                from tracfullblog.model import BlogPost, BlogCommen
876
877                # instantiate blog core
878                blog = FullBlogCore(self.env)
879                req = ''
880               
881                if id:
882
883                        # update blog
884                        #
885                        comment = BlogComment(self.env, id)
886                        comment.author = self.author
887                        comment.comment = self.get_body_text(m)
888                        blog.create_comment(req, comment)
889
890                else:
891                        # create blog
892                        #
893                        import time
894                        post = BlogPost(self.env, 'blog_'+time.strftime("%Y%m%d%H%M%S", time.gmtime()))
895
896                        #post = BlogPost(self.env, blog._get_default_postname(self.env))
897                       
898                        post.author = self.author
899                        post.title = self.email_to_unicode(m['Subject'])
900                        post.body = self.get_body_text(m)
901                       
902                        blog.create_post(req, post, self.author, u'Created by email2trac', False)
903
904
905        def parse(self, fp):
906                global m
907
908                m = email.message_from_file(fp)
909               
910                if not m:
911                        if self.DEBUG:
912                                print "TD: This is not a valid email message format"
913                        return
914                       
915                # Work around lack of header folding in Python; see http://bugs.python.org/issue4696
916                m.replace_header('Subject', m['Subject'].replace('\r', '').replace('\n', ''))
917
918                if self.DEBUG > 1:        # save the entire e-mail message text
919                        message_parts = self.get_message_parts(m)
920                        message_parts = self.unique_attachment_names(message_parts)
921                        self.save_email_for_debug(m, True)
922                        body_text = self.body_text(message_parts)
923                        self.debug_body(body_text, True)
924                        self.debug_attachments(message_parts)
925
926                self.db = self.env.get_db_cnx()
927                self.get_sender_info(m)
928
929                if not self.email_header_acl('white_list', self.email_addr, True):
930                        if self.DEBUG > 1 :
931                                print 'Message rejected : %s not in white list' %(self.email_addr)
932                        return False
933
934                if self.email_header_acl('black_list', self.email_addr, False):
935                        if self.DEBUG > 1 :
936                                print 'Message rejected : %s in black list' %(self.email_addr)
937                        return False
938
939                if not self.email_header_acl('recipient_list', self.to_email_addr, True):
940                        if self.DEBUG > 1 :
941                                print 'Message rejected : %s not in recipient list' %(self.to_email_addr)
942                        return False
943
944                # If drop the message
945                #
946                if self.spam(m) == 'drop':
947                        return False
948
949                elif self.spam(m) == 'spam':
950                        spam_msg = True
951                else:
952                        spam_msg = False
953
954                if self.get_config('notification', 'smtp_enabled') in ['true']:
955                        self.notification = 1
956                else:
957                        self.notification = 0
958
959                # Check if  FullBlogPlugin is installed
960                #
961                blog_enabled = None
962                if self.get_config('components', 'tracfullblog.*') in ['enabled']:
963                        blog_enabled = True
964                       
965                if not m['Subject']:
966                        return False
967                else:
968                        subject  = self.email_to_unicode(m['Subject'])         
969
970                #
971                # [hic] #1529: Re: LRZ
972                # [hic] #1529?owner=bas,priority=medium: Re: LRZ
973                #
974                TICKET_RE = re.compile(r"""
975                        (?P<blog>blog:(?P<blog_id>\w*))
976                        |(?P<new_fields>[#][?].*)
977                        |(?P<reply>[#][\d]+:)
978                        |(?P<reply_fields>[#][\d]+\?.*?:)
979                        """, re.VERBOSE)
980
981                # Find out if this is a ticket or a blog
982                #
983                result =  TICKET_RE.search(subject)
984
985                if result:
986                        if result.group('blog') and blog_enabled:
987                                self.blog(result.group('blog_id'))
988
989                        # update ticket + fields
990                        #
991                        if result.group('reply_fields') and self.TICKET_UPDATE:
992                                self.ticket_update(m, result.group('reply_fields'), spam_msg)
993
994                        # Update ticket
995                        #
996                        elif result.group('reply') and self.TICKET_UPDATE:
997                                self.ticket_update(m, result.group('reply'), spam_msg)
998
999                        # New ticket + fields
1000                        #
1001                        elif result.group('new_fields'):
1002                                self.new_ticket(m, subject[:result.start('new_fields')], spam_msg, result.group('new_fields'))
1003
1004                # Create ticket
1005                #
1006                else:
1007                        self.new_ticket(m, subject, spam_msg)
1008
1009        def strip_signature(self, text):
1010                """
1011                Strip signature from message, inspired by Mailman software
1012                """
1013                body = []
1014                for line in text.splitlines():
1015                        if line == '-- ':
1016                                break
1017                        body.append(line)
1018
1019                return ('\n'.join(body))
1020
1021        def reflow(self, text, delsp = 0):
1022                """
1023                Reflow the message based on the format="flowed" specification (RFC 3676)
1024                """
1025                flowedlines = []
1026                quotelevel = 0
1027                prevflowed = 0
1028
1029                for line in text.splitlines():
1030                        from re import match
1031                       
1032                        # Figure out the quote level and the content of the current line
1033                        m = match('(>*)( ?)(.*)', line)
1034                        linequotelevel = len(m.group(1))
1035                        line = m.group(3)
1036
1037                        # Determine whether this line is flowed
1038                        if line and line != '-- ' and line[-1] == ' ':
1039                                flowed = 1
1040                        else:
1041                                flowed = 0
1042
1043                        if flowed and delsp and line and line[-1] == ' ':
1044                                line = line[:-1]
1045
1046                        # If the previous line is flowed, append this line to it
1047                        if prevflowed and line != '-- ' and linequotelevel == quotelevel:
1048                                flowedlines[-1] += line
1049                        # Otherwise, start a new line
1050                        else:
1051                                flowedlines.append('>' * linequotelevel + line)
1052
1053                        prevflowed = flowed
1054                       
1055
1056                return '\n'.join(flowedlines)
1057
1058        def strip_quotes(self, text):
1059                """
1060                Strip quotes from message by Nicolas Mendoza
1061                """
1062                body = []
1063                for line in text.splitlines():
1064                        if line.startswith(self.EMAIL_QUOTE):
1065                                continue
1066                        body.append(line)
1067
1068                return ('\n'.join(body))
1069
1070        def wrap_text(self, text, replace_whitespace = False):
1071                """
1072                Will break a lines longer then given length into several small
1073                lines of size given length
1074                """
1075                import textwrap
1076
1077                LINESEPARATOR = '\n'
1078                reformat = ''
1079
1080                for s in text.split(LINESEPARATOR):
1081                        tmp = textwrap.fill(s,self.USE_TEXTWRAP)
1082                        if tmp:
1083                                reformat = '%s\n%s' %(reformat,tmp)
1084                        else:
1085                                reformat = '%s\n' %reformat
1086
1087                return reformat
1088
1089                # Python2.4 and higher
1090                #
1091                #return LINESEPARATOR.join(textwrap.fill(s,width) for s in str.split(LINESEPARATOR))
1092                #
1093
1094
1095        def get_message_parts(self, msg):
1096                """
1097                parses the email message and returns a list of body parts and attachments
1098                body parts are returned as strings, attachments are returned as tuples of (filename, Message object)
1099                """
1100                message_parts = []
1101       
1102                ALTERNATIVE_MULTIPART = False
1103
1104                for part in msg.walk():
1105                        if self.DEBUG:
1106                                print 'TD: Message part: Main-Type: %s' % part.get_content_maintype()
1107                                print 'TD: Message part: Content-Type: %s' % part.get_content_type()
1108
1109                        ## Check content type
1110                        #
1111                        if part.get_content_type() in self.STRIP_CONTENT_TYPES:
1112
1113                                if self.DEBUG:
1114                                        print "TD: A %s attachment named '%s' was skipped" %(part.get_content_type(), part.get_filename())
1115
1116                                continue
1117
1118                        ## Catch some mulitpart execptions
1119                        #
1120                        if part.get_content_type() == 'multipart/alternative':
1121                                ALTERNATIVE_MULTIPART = True
1122                                continue
1123
1124                        ## Skip multipart containers
1125                        #
1126                        if part.get_content_maintype() == 'multipart':
1127                                if self.DEBUG:
1128                                        print "TD: Skipping multipart container"
1129                                continue
1130                       
1131                        ## Check if this is an inline part. It's inline if there is co Cont-Disp header, or if there is one and it says "inline"
1132                        #
1133                        inline = self.inline_part(part)
1134
1135                        ## Drop HTML message
1136                        #
1137                        if ALTERNATIVE_MULTIPART and self.DROP_ALTERNATIVE_HTML_VERSION:
1138                                if part.get_content_type() == 'text/html':
1139                                        if self.DEBUG:
1140                                                print "TD: Skipping alternative HTML message"
1141
1142                                        ALTERNATIVE_MULTIPART = False
1143                                        continue
1144
1145                        ## Inline text parts are where the body is
1146                        #
1147                        if part.get_content_type() == 'text/plain' and inline:
1148                                if self.DEBUG:
1149                                        print 'TD:               Inline body part'
1150
1151                                # Try to decode, if fails then do not decode
1152                                #
1153                                body_text = part.get_payload(decode=1)
1154                                if not body_text:                       
1155                                        body_text = part.get_payload(decode=0)
1156
1157                                format = email.Utils.collapse_rfc2231_value(part.get_param('Format', 'fixed')).lower()
1158                                delsp = email.Utils.collapse_rfc2231_value(part.get_param('DelSp', 'no')).lower()
1159
1160                                if self.REFLOW and not self.VERBATIM_FORMAT and format == 'flowed':
1161                                        body_text = self.reflow(body_text, delsp == 'yes')
1162       
1163                                if self.STRIP_SIGNATURE:
1164                                        body_text = self.strip_signature(body_text)
1165
1166                                if self.STRIP_QUOTES:
1167                                        body_text = self.strip_quotes(body_text)
1168
1169                                if self.USE_TEXTWRAP:
1170                                        body_text = self.wrap_text(body_text)
1171
1172                                ## Get contents charset (iso-8859-15 if not defined in mail headers)
1173                                #
1174                                charset = part.get_content_charset()
1175                                if not charset:
1176                                        charset = 'iso-8859-15'
1177
1178                                try:
1179                                        ubody_text = unicode(body_text, charset)
1180
1181                                except UnicodeError, detail:
1182                                        ubody_text = unicode(body_text, 'iso-8859-15')
1183
1184                                except LookupError, detail:
1185                                        ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset)
1186
1187                                if self.VERBATIM_FORMAT:
1188                                        message_parts.append('{{{\r\n%s\r\n}}}' %ubody_text)
1189                                else:
1190                                        message_parts.append('%s' %ubody_text)
1191                        else:
1192                                if self.DEBUG:
1193                                        print 'TD:               Filename: %s' % part.get_filename()
1194
1195                                message_parts.append((part.get_filename(), part))
1196
1197                return message_parts
1198               
1199        def unique_attachment_names(self, message_parts):
1200                """
1201                """
1202                renamed_parts = []
1203                attachment_names = set()
1204
1205                for part in message_parts:
1206                       
1207                        ## If not an attachment, leave it alone
1208                        #
1209                        if not isinstance(part, tuple):
1210                                renamed_parts.append(part)
1211                                continue
1212                               
1213                        (filename, part) = part
1214
1215                        ## If no filename, use a default one
1216                        #
1217                        if not filename:
1218                                filename = 'untitled-part'
1219
1220                                # Guess the extension from the content type, use non strict mode
1221                                # some additional non-standard but commonly used MIME types
1222                                # are also recognized
1223                                #
1224                                ext = mimetypes.guess_extension(part.get_content_type(), False)
1225                                if not ext:
1226                                        ext = '.bin'
1227
1228                                filename = '%s%s' % (filename, ext)
1229
1230# We now use the attachment insert function
1231#
1232                        ## Discard relative paths in attachment names
1233                        #
1234                        #filename = filename.replace('\\', '/').replace(':', '/')
1235                        #filename = os.path.basename(filename)
1236                        #
1237                        # We try to normalize the filename to utf-8 NFC if we can.
1238                        # Files uploaded from OS X might be in NFD.
1239                        # Check python version and then try it
1240                        #
1241                        #if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
1242                        #       try:
1243                        #               filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
1244                        #       except TypeError:
1245                        #               pass
1246
1247                        # Make the filename unique for this ticket
1248                        num = 0
1249                        unique_filename = filename
1250                        dummy_filename, ext = os.path.splitext(filename)
1251
1252                        while unique_filename in attachment_names or self.attachment_exists(unique_filename):
1253                                num += 1
1254                                unique_filename = "%s-%s%s" % (dummy_filename, num, ext)
1255                               
1256                        if self.DEBUG:
1257                                print 'TD: Attachment with filename %s will be saved as %s' % (filename, unique_filename)
1258
1259                        attachment_names.add(unique_filename)
1260
1261                        renamed_parts.append((filename, unique_filename, part))
1262       
1263                return renamed_parts
1264                       
1265        def inline_part(self, part):
1266                return part.get_param('inline', None, 'Content-Disposition') == '' or not part.has_key('Content-Disposition')
1267               
1268                       
1269        def attachment_exists(self, filename):
1270
1271                if self.DEBUG:
1272                        print "TD: attachment_exists: Ticket number : %s, Filename : %s" %(self.id, filename)
1273
1274                # We have no valid ticket id
1275                #
1276                if not self.id:
1277                        return False
1278
1279                try:
1280                        att = attachment.Attachment(self.env, 'ticket', self.id, filename)
1281                        return True
1282                except attachment.ResourceNotFound:
1283                        return False
1284                       
1285        def body_text(self, message_parts):
1286                body_text = []
1287               
1288                for part in message_parts:
1289                        # Plain text part, append it
1290                        if not isinstance(part, tuple):
1291                                body_text.extend(part.strip().splitlines())
1292                                body_text.append("")
1293                                continue
1294                               
1295                        (original, filename, part) = part
1296                        inline = self.inline_part(part)
1297                       
1298                        if part.get_content_maintype() == 'image' and inline:
1299                                body_text.append('[[Image(%s)]]' % filename)
1300                                body_text.append("")
1301                        else:
1302                                body_text.append('[attachment:"%s"]' % filename)
1303                                body_text.append("")
1304                               
1305                body_text = '\r\n'.join(body_text)
1306                return body_text
1307
1308        def notify(self, tkt, new=True, modtime=0):
1309                """
1310                A wrapper for the TRAC notify function. So we can use templates
1311                """
1312                if self.DRY_RUN:
1313                                print 'DRY_RUN: self.notify(tkt, True) ', self.author
1314                                return
1315                try:
1316                        # create false {abs_}href properties, to trick Notify()
1317                        #
1318                        if not self.VERSION == 0.11:
1319                                self.env.abs_href = Href(self.get_config('project', 'url'))
1320                                self.env.href = Href(self.get_config('project', 'url'))
1321
1322                        tn = TicketNotifyEmail(self.env)
1323
1324                        if self.notify_template:
1325
1326                                if self.VERSION == 0.11:
1327
1328                                        from trac.web.chrome import Chrome
1329
1330                                        if self.notify_template_update and not new:
1331                                                tn.template_name = self.notify_template_update
1332                                        else:
1333                                                tn.template_name = self.notify_template
1334
1335                                        tn.template = Chrome(tn.env).load_template(tn.template_name, method='text')
1336                                               
1337                                else:
1338
1339                                        tn.template_name = self.notify_template;
1340
1341                        tn.notify(tkt, new, modtime)
1342
1343                except Exception, e:
1344                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(self.id, e)
1345
1346        def html_mailto_link(self, subject, body):
1347                """
1348                This function returns a HTML mailto tag with the ticket id and author email address
1349                """
1350                if not self.author:
1351                        author = self.email_addr
1352                else:   
1353                        author = self.author
1354
1355                # use urllib to escape the chars
1356                #
1357                s = 'mailto:%s?Subject=%s&Cc=%s' %(
1358                       urllib.quote(self.email_addr),
1359                           urllib.quote('Re: #%s: %s' %(self.id, subject)),
1360                           urllib.quote(self.MAILTO_CC)
1361                           )
1362
1363                s = '\r\n{{{\r\n#!html\r\n<a\r\n href="%s">Reply to: %s\r\n</a>\r\n}}}\r\n' %(s, author)
1364                return s
1365
1366        def attachments(self, message_parts, update=False):
1367                '''
1368                save any attachments as files in the ticket's directory
1369                '''
1370                if self.DRY_RUN:
1371                        print "DRY_RUN: no attachments saved"
1372                        return ''
1373
1374                count = 0
1375
1376                # Get Maxium attachment size
1377                #
1378                max_size = int(self.get_config('attachment', 'max_size'))
1379                status   = ''
1380               
1381                for part in message_parts:
1382                        # Skip body parts
1383                        if not isinstance(part, tuple):
1384                                continue
1385                               
1386                        (original, filename, part) = part
1387                        #
1388                        # Must be tuneables HvB
1389                        #
1390                        path, fd =  util.create_unique_file(os.path.join(self.TMPDIR, filename))
1391                        text = part.get_payload(decode=1)
1392                        if not text:
1393                                text = '(None)'
1394                        fd.write(text)
1395                        fd.close()
1396
1397                        # get the file_size
1398                        #
1399                        stats = os.lstat(path)
1400                        file_size = stats[stat.ST_SIZE]
1401
1402                        # Check if the attachment size is allowed
1403                        #
1404                        if (max_size != -1) and (file_size > max_size):
1405                                status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \
1406                                        %(status, original, file_size, max_size)
1407
1408                                os.unlink(path)
1409                                continue
1410                        else:
1411                                count = count + 1
1412                                       
1413                        # Insert the attachment
1414                        #
1415                        fd = open(path, 'rb')
1416                        att = attachment.Attachment(self.env, 'ticket', self.id)
1417
1418                        # This will break the ticket_update system, the body_text is vaporized
1419                        # ;-(
1420                        #
1421                        if not update:
1422                                att.author = self.author
1423                                att.description = self.email_to_unicode('Added by email2trac')
1424
1425                        att.insert(filename, fd, file_size)
1426
1427                        #except  util.TracError, detail:
1428                        #       print detail
1429
1430                        # Remove the created temporary filename
1431                        #
1432                        fd.close()
1433                        os.unlink(path)
1434
1435                # Return how many attachments
1436                #
1437                status = 'This message has %d attachment(s)\n%s' %(count, status)
1438                return status
1439
1440
1441def mkdir_p(dir, mode):
1442        '''do a mkdir -p'''
1443
1444        arr = string.split(dir, '/')
1445        path = ''
1446        for part in arr:
1447                path = '%s/%s' % (path, part)
1448                try:
1449                        stats = os.stat(path)
1450                except OSError:
1451                        os.mkdir(path, mode)
1452
1453def ReadConfig(file, name):
1454        """
1455        Parse the config file
1456        """
1457        if not os.path.isfile(file):
1458                print 'File %s does not exist' %file
1459                sys.exit(1)
1460
1461        config = trac_config.Configuration(file)
1462
1463        # Use given project name else use defaults
1464        #
1465        if name:
1466                sections = config.sections()
1467                if not name in sections:
1468                        print "Not a valid project name: %s" %name
1469                        print "Valid names: %s" %sections
1470                        sys.exit(1)
1471
1472                project =  dict()
1473                for option, value in  config.options(name):
1474                        project[option] = value
1475
1476        else:
1477                # use some trac internals to get the defaults
1478                #
1479                project = config.parser.defaults()
1480
1481        return project
1482
1483
1484if __name__ == '__main__':
1485        # Default config file
1486        #
1487        configfile = '@email2trac_conf@'
1488        project = ''
1489        component = ''
1490        ticket_prefix = 'default'
1491        dry_run = None
1492
1493        ENABLE_SYSLOG = 0
1494
1495
1496        SHORT_OPT = 'chf:np:t:'
1497        LONG_OPT  =  ['component=', 'dry-run', 'help', 'file=', 'project=', 'ticket_prefix=']
1498
1499        try:
1500                opts, args = getopt.getopt(sys.argv[1:], SHORT_OPT, LONG_OPT)
1501        except getopt.error,detail:
1502                print __doc__
1503                print detail
1504                sys.exit(1)
1505       
1506        project_name = None
1507        for opt,value in opts:
1508                if opt in [ '-h', '--help']:
1509                        print __doc__
1510                        sys.exit(0)
1511                elif opt in ['-c', '--component']:
1512                        component = value
1513                elif opt in ['-f', '--file']:
1514                        configfile = value
1515                elif opt in ['-n', '--dry-run']:
1516                        dry_run = True
1517                elif opt in ['-p', '--project']:
1518                        project_name = value
1519                elif opt in ['-t', '--ticket_prefix']:
1520                        ticket_prefix = value
1521       
1522        settings = ReadConfig(configfile, project_name)
1523        if not settings.has_key('project'):
1524                print __doc__
1525                print 'No Trac project is defined in the email2trac config file.'
1526                sys.exit(1)
1527       
1528        if component:
1529                settings['component'] = component
1530
1531        # The default prefix for ticket values in email2trac.conf
1532        #
1533        settings['ticket_prefix'] = ticket_prefix
1534        settings['dry_run'] = dry_run
1535       
1536        if settings.has_key('trac_version'):
1537                version = settings['trac_version']
1538        else:
1539                version = trac_default_version
1540
1541
1542        #debug HvB
1543        #print settings
1544
1545        try:
1546                if version == '0.9':
1547                        from trac import attachment
1548                        from trac.env import Environment
1549                        from trac.ticket import Ticket
1550                        from trac.web.href import Href
1551                        from trac import util
1552                        from trac.Notify import TicketNotifyEmail
1553                elif version == '0.10':
1554                        from trac import attachment
1555                        from trac.env import Environment
1556                        from trac.ticket import Ticket
1557                        from trac.web.href import Href
1558                        from trac import util
1559                        #
1560                        # return  util.text.to_unicode(str)
1561                        #
1562                        # see http://projects.edgewall.com/trac/changeset/2799
1563                        from trac.ticket.notification import TicketNotifyEmail
1564                        from trac import config as trac_config
1565                elif version == '0.11':
1566                        from trac import attachment
1567                        from trac.env import Environment
1568                        from trac.ticket import Ticket
1569                        from trac.web.href import Href
1570                        from trac import config as trac_config
1571                        from trac import util
1572
1573
1574                        #
1575                        # return  util.text.to_unicode(str)
1576                        #
1577                        # see http://projects.edgewall.com/trac/changeset/2799
1578                        from trac.ticket.notification import TicketNotifyEmail
1579                else:
1580                        print 'TRAC version %s is not supported' %version
1581                        sys.exit(1)
1582                       
1583                if settings.has_key('enable_syslog'):
1584                        if SYSLOG_AVAILABLE:
1585                                ENABLE_SYSLOG =  float(settings['enable_syslog'])
1586
1587
1588                # Must be set before environment is created
1589                #
1590                if settings.has_key('python_egg_cache'):
1591                        python_egg_cache = str(settings['python_egg_cache'])
1592                        os.environ['PYTHON_EGG_CACHE'] = python_egg_cache
1593
1594                env = Environment(settings['project'], create=0)
1595                tktparser = TicketEmailParser(env, settings, float(version))
1596                tktparser.parse(sys.stdin)
1597
1598        # Catch all errors ans log to SYSLOG if we have enabled this
1599        # else stdout
1600        #
1601        except Exception, error:
1602                if ENABLE_SYSLOG:
1603                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
1604
1605                        etype, evalue, etb = sys.exc_info()
1606                        for e in traceback.format_exception(etype, evalue, etb):
1607                                syslog.syslog(e)
1608
1609                        syslog.closelog()
1610                else:
1611                        traceback.print_exc()
1612
1613                if m:
1614                        tktparser.save_email_for_debug(m, True)
1615
1616                sys.exit(1)
1617# EOB
Note: See TracBrowser for help on using the repository browser.