source: trunk/email2trac.py.in @ 295

Last change on this file since 295 was 295, checked in by bas, 14 years ago

email2trac.py.in:

  • fixed an error for new tickets attachments were not saved
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 39.6 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.9               # 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 295 2010-01-06 13:03:06Z 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('quote_attachment_filenames'):
146                        self.QUOTE_ATTACHMENT_FILENAMES = int(parameters['quote_attachment_filenames'])
147                else:
148                        self.QUOTE_ATTACHMENT_FILENAMES = 1
149
150                if parameters.has_key('debug'):
151                        self.DEBUG = int(parameters['debug'])
152                else:
153                        self.DEBUG = 0
154
155                if parameters.has_key('mailto_link'):
156                        self.MAILTO = int(parameters['mailto_link'])
157                        if parameters.has_key('mailto_cc'):
158                                self.MAILTO_CC = parameters['mailto_cc']
159                        else:
160                                self.MAILTO_CC = ''
161                else:
162                        self.MAILTO = 0
163
164                if parameters.has_key('spam_level'):
165                        self.SPAM_LEVEL = int(parameters['spam_level'])
166                else:
167                        self.SPAM_LEVEL = 0
168
169                if parameters.has_key('spam_header'):
170                        self.SPAM_HEADER = parameters['spam_header']
171                else:
172                        self.SPAM_HEADER = 'X-Spam-Score'
173
174                if parameters.has_key('email_quote'):
175                        self.EMAIL_QUOTE = str(parameters['email_quote'])
176                else:   
177                        self.EMAIL_QUOTE = '> '
178
179                if parameters.has_key('email_header'):
180                        self.EMAIL_HEADER = int(parameters['email_header'])
181                else:
182                        self.EMAIL_HEADER = 0
183
184                if parameters.has_key('alternate_notify_template'):
185                        self.notify_template = str(parameters['alternate_notify_template'])
186                else:
187                        self.notify_template = None
188
189                if parameters.has_key('alternate_notify_template_update'):
190                        self.notify_template_update = str(parameters['alternate_notify_template_update'])
191                else:
192                        self.notify_template_update = None
193
194                if parameters.has_key('reply_all'):
195                        self.REPLY_ALL = int(parameters['reply_all'])
196                else:
197                        self.REPLY_ALL = 0
198
199                if parameters.has_key('ticket_update'):
200                        self.TICKET_UPDATE = int(parameters['ticket_update'])
201                else:
202                        self.TICKET_UPDATE = 0
203
204                if parameters.has_key('drop_spam'):
205                        self.DROP_SPAM = int(parameters['drop_spam'])
206                else:
207                        self.DROP_SPAM = 0
208
209                if parameters.has_key('verbatim_format'):
210                        self.VERBATIM_FORMAT = int(parameters['verbatim_format'])
211                else:
212                        self.VERBATIM_FORMAT = 1
213
214                if parameters.has_key('reflow'):
215                        self.REFLOW = int(parameters['reflow'])
216                else:
217                        self.REFLOW = 1
218
219                if parameters.has_key('drop_alternative_html_version'):
220                        self.DROP_ALTERNATIVE_HTML_VERSION = int(parameters['drop_alternative_html_version'])
221                else:
222                        self.DROP_ALTERNATIVE_HTML_VERSION = 0
223
224                if parameters.has_key('strip_signature'):
225                        self.STRIP_SIGNATURE = int(parameters['strip_signature'])
226                else:
227                        self.STRIP_SIGNATURE = 0
228
229                if parameters.has_key('strip_quotes'):
230                        self.STRIP_QUOTES = int(parameters['strip_quotes'])
231                else:
232                        self.STRIP_QUOTES = 0
233
234                if parameters.has_key('use_textwrap'):
235                        self.USE_TEXTWRAP = int(parameters['use_textwrap'])
236                else:
237                        self.USE_TEXTWRAP = 0
238
239                if parameters.has_key('binhex'):
240                        self.STRIP_CONTENT_TYPES.append('application/mac-binhex40')
241
242                if parameters.has_key('applesingle'):
243                        self.STRIP_CONTENT_TYPES.append('application/applefile')
244
245                if parameters.has_key('appledouble'):
246                        self.STRIP_CONTENT_TYPES.append('application/applefile')
247
248                if parameters.has_key('strip_content_types'):
249                        items = parameters['strip_content_types'].split(',')
250                        for item in items:
251                                self.STRIP_CONTENT_TYPES.append(item.strip())
252
253                self.WORKFLOW = None
254                if parameters.has_key('workflow'):
255                        self.WORKFLOW = parameters['workflow']
256
257                # Use OS independend functions
258                #
259                self.TMPDIR = os.path.normcase('/tmp')
260                if parameters.has_key('tmpdir'):
261                        self.TMPDIR = os.path.normcase(str(parameters['tmpdir']))
262
263                if parameters.has_key('ignore_trac_user_settings'):
264                        self.IGNORE_TRAC_USER_SETTINGS = int(parameters['ignore_trac_user_settings'])
265                else:
266                        self.IGNORE_TRAC_USER_SETTINGS = 0
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, ',')
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
542                return result
543
544        def update_ticket_fields(self, ticket, user_dict, use_default=None):
545                """
546                This will update the ticket fields. It will check if the
547                given fields are known and if the right values are specified
548                It will only update the ticket field value:
549                        - If the field is known
550                        - If the value supplied is valid for the ticket field.
551                          If not then there are two options:
552                           1) Skip the value (use_default=None)
553                           2) Set default value for field (use_default=1)
554                """
555
556                # Build a system dictionary from the ticket fields
557                # with field as index and option as value
558                #
559                sys_dict = dict()
560                for field in ticket.fields:
561                        try:
562                                sys_dict[field['name']] = field['options']
563
564                        except KeyError:
565                                sys_dict[field['name']] = None
566                                pass
567
568                # Check user supplied fields an compare them with the
569                # system one's
570                #
571                for field,value in user_dict.items():
572                        if self.DEBUG >= 10:
573                                print  'user_field\t %s = %s' %(field,value)
574
575                        if sys_dict.has_key(field):
576
577                                # Check if value is an allowed system option, if TypeError then
578                                # every value is allowed
579                                #
580                                try:
581                                        if value in sys_dict[field]:
582                                                ticket[field] = value
583                                        else:
584                                                # Must we set a default if value is not allowed
585                                                #
586                                                if use_default:
587                                                        value = self.get_config('ticket', 'default_%s' %(field) )
588                                                        ticket[field] = value
589
590                                except TypeError:
591                                        ticket[field] = value
592
593                                if self.DEBUG >= 10:
594                                        print  'ticket_field\t %s = %s' %(field,  ticket[field])
595                                       
596        def ticket_update(self, m, id, spam):
597                """
598                If the current email is a reply to an existing ticket, this function
599                will append the contents of this email to that ticket, instead of
600                creating a new one.
601                """
602                if self.DEBUG:
603                        print "TD: ticket_update: %s" %id
604
605                # Must we update ticket fields
606                #
607                update_fields = dict()
608                try:
609                        id, keywords = string.split(id, '?')
610
611                        # Skip the last ':' character
612                        #
613                        keywords = keywords[:-1]
614                        update_fields = self.str_to_dict(keywords)
615
616                        # Strip '#'
617                        #
618                        self.id = int(id[1:])
619
620                except ValueError:
621                        # Strip '#' and ':'
622                        #
623                        self.id = int(id[1:-1])
624
625
626                # When is the change committed
627                #
628                if self.VERSION == 0.11:
629                        utc = UTC()
630                        when = datetime.now(utc)
631                else:
632                        when = int(time.time())
633
634                try:
635                        tkt = Ticket(self.env, self.id, self.db)
636                except util.TracError, detail:
637                        # Not a valid ticket
638                        self.id = None
639                        return False
640
641                # How many changes has this ticket
642                cnum = len(tkt.get_changelog())
643
644
645                # reopen the ticket if it is was closed
646                # We must use the ticket workflow framework
647                #
648                if tkt['status'] in ['closed']:
649
650                        #print controller.actions['reopen']
651                        #
652                        # As reference 
653                        # req = Mock(href=Href('/'), abs_href=Href('http://www.example.com/'), authname='anonymous', perm=MockPerm(), args={})
654                        #
655                        #a = controller.render_ticket_action_control(req, tkt, 'reopen')
656                        #print 'controller : ', a
657                        #
658                        #b = controller.get_all_status()
659                        #print 'get all status: ', b
660                        #
661                        #b = controller.get_ticket_changes(req, tkt, 'reopen')
662                        #print 'get_ticket_changes :', b
663
664                        if self.WORKFLOW and (self.VERSION in [0.11]) :
665                                from trac.ticket.default_workflow import ConfigurableTicketWorkflow
666                                from trac.test import Mock, MockPerm
667
668                                req = Mock(authname='anonymous', perm=MockPerm(), args={})
669
670                                controller = ConfigurableTicketWorkflow(self.env)
671                                fields = controller.get_ticket_changes(req, tkt, self.WORKFLOW)
672
673                                if self.DEBUG:
674                                        print 'TD: Workflow ticket update fields: ', fields
675
676                                for key in fields.keys():
677                                        tkt[key] = fields[key]
678
679                        else:
680                                tkt['status'] = 'reopened'
681                                tkt['resolution'] = ''
682
683                # Must we update some ticket fields properties
684                #
685                if update_fields:
686                        self.update_ticket_fields(tkt, update_fields)
687
688                message_parts = self.get_message_parts(m)
689                message_parts = self.unique_attachment_names(message_parts)
690
691                if self.EMAIL_HEADER:
692                        message_parts.insert(0, self.email_header_txt(m))
693
694                body_text = self.body_text(message_parts)
695
696                if body_text.strip() or update_fields:
697                        if self.DRY_RUN:
698                                print 'DRY_RUN: tkt.save_changes(self.author, body_text, ticket_change_number) ', self.author, cnum
699                        else:
700                                tkt.save_changes(self.author, body_text, when, None, str(cnum))
701                       
702
703                if self.VERSION  == 0.9:
704                        s = self.attachments(message_parts, True)
705                else:
706                        s = self.attachments(message_parts)
707
708                if self.notification and not spam:
709                        self.notify(tkt, False, when)
710
711                return True
712
713        def set_ticket_fields(self, ticket):
714                """
715                set the ticket fields to value specified
716                        - /etc/email2trac.conf with <prefix>_<field>
717                        - trac default values, trac.ini
718                """
719                user_dict = dict()
720
721                for field in ticket.fields:
722
723                        name = field['name']
724
725                        # skip some fields like resolution
726                        #
727                        if name in [ 'resolution' ]:
728                                continue
729
730                        # default trac value
731                        #
732                        if not field.get('custom'):
733                                value = self.get_config('ticket', 'default_%s' %(name) )
734                        else:
735                                value = field.get('value')
736                                options = field.get('options')
737                                if value and options and value not in options:
738                                        value = options[int(value)]
739
740                        if self.DEBUG > 10:
741                                print 'trac.ini name %s = %s' %(name, value)
742
743                        prefix = self.parameters['ticket_prefix']
744                        try:
745                                value = self.parameters['%s_%s' %(prefix, name)]
746                                if self.DEBUG > 10:
747                                        print 'email2trac.conf %s = %s ' %(name, value)
748
749                        except KeyError, detail:
750                                pass
751               
752                        if self.DEBUG:
753                                print 'user_dict[%s] = %s' %(name, value)
754
755                        user_dict[name] = value
756
757                self.update_ticket_fields(ticket, user_dict, use_default=1)
758
759                # Set status ticket
760                #`
761                ticket['status'] = 'new'
762
763
764
765        def new_ticket(self, msg, subject, spam, set_fields = None):
766                """
767                Create a new ticket
768                """
769                if self.DEBUG:
770                        print "TD: new_ticket"
771
772                tkt = Ticket(self.env)
773                self.set_ticket_fields(tkt)
774
775                # Old style setting for component, will be removed
776                #
777                if spam:
778                        tkt['component'] = 'Spam'
779
780                elif self.parameters.has_key('component'):
781                        tkt['component'] = self.parameters['component']
782
783                if not msg['Subject']:
784                        tkt['summary'] = u'(No subject)'
785                else:
786                        tkt['summary'] = subject
787
788                self.set_reply_fields(tkt, msg)
789
790                if set_fields:
791                        rest, keywords = string.split(set_fields, '?')
792
793                        if keywords:
794                                update_fields = self.str_to_dict(keywords)
795                                self.update_ticket_fields(tkt, update_fields)
796
797                # produce e-mail like header
798                #
799                head = ''
800                if self.EMAIL_HEADER > 0:
801                        head = self.email_header_txt(msg)
802                       
803                message_parts = self.get_message_parts(msg)
804                message_parts = self.unique_attachment_names(message_parts)
805               
806                if self.EMAIL_HEADER > 0:
807                        message_parts.insert(0, self.email_header_txt(msg))
808                       
809                body_text = self.body_text(message_parts)
810
811                tkt['description'] = body_text
812
813                #when = int(time.time())
814                #
815                utc = UTC()
816                when = datetime.now(utc)
817
818                if not self.DRY_RUN:
819                        self.id = tkt.insert()
820       
821                changed = False
822                comment = ''
823
824                # some routines in trac are dependend on ticket id     
825                # like alternate notify template
826                #
827                if self.notify_template:
828                        tkt['id'] = self.id
829                        changed = True
830
831                ## Rewrite the description if we have mailto enabled
832                #
833                if self.MAILTO:
834                        changed = True
835                        comment = u'\nadded mailto line\n'
836                        mailto = self.html_mailto_link( m['Subject'], body_text)
837
838                        tkt['description'] = u'%s\r\n%s%s\r\n' \
839                                %(head, mailto, body_text)
840       
841                ## Save the attachments to the ticket   
842                #
843                has_attachments =  self.attachments(message_parts)
844
845                #  Disabled
846                #if has_attachments:
847                #       changed = True
848                #       comment = '%s\n%s\n' %(comment, has_attachments)
849
850                if changed:
851                        if self.DRY_RUN:
852                                print 'DRY_RUN: tkt.save_changes(self.author, comment) ', self.author
853                        else:
854                                tkt.save_changes(self.author, comment)
855                                #print tkt.get_changelog(self.db, when)
856
857                if self.notification and not spam:
858                        self.notify(tkt, True)
859
860
861        def blog(self, id):
862                """
863                The blog create/update function
864                """
865                # import the modules
866                #
867                from tracfullblog.core import FullBlogCore
868                from tracfullblog.model import BlogPost, BlogCommen
869
870                # instantiate blog core
871                blog = FullBlogCore(self.env)
872                req = ''
873               
874                if id:
875
876                        # update blog
877                        #
878                        comment = BlogComment(self.env, id)
879                        comment.author = self.author
880                        comment.comment = self.get_body_text(m)
881                        blog.create_comment(req, comment)
882
883                else:
884                        # create blog
885                        #
886                        import time
887                        post = BlogPost(self.env, 'blog_'+time.strftime("%Y%m%d%H%M%S", time.gmtime()))
888
889                        #post = BlogPost(self.env, blog._get_default_postname(self.env))
890                       
891                        post.author = self.author
892                        post.title = self.email_to_unicode(m['Subject'])
893                        post.body = self.get_body_text(m)
894                       
895                        blog.create_post(req, post, self.author, u'Created by email2trac', False)
896
897
898        def parse(self, fp):
899                global m
900
901                m = email.message_from_file(fp)
902               
903                if not m:
904                        if self.DEBUG:
905                                print "TD: This is not a valid email message format"
906                        return
907                       
908                # Work around lack of header folding in Python; see http://bugs.python.org/issue4696
909                m.replace_header('Subject', m['Subject'].replace('\r', '').replace('\n', ''))
910
911                if self.DEBUG > 1:        # save the entire e-mail message text
912                        message_parts = self.get_message_parts(m)
913                        message_parts = self.unique_attachment_names(message_parts)
914                        self.save_email_for_debug(m, True)
915                        body_text = self.body_text(message_parts)
916                        self.debug_body(body_text, True)
917                        self.debug_attachments(message_parts)
918
919                self.db = self.env.get_db_cnx()
920                self.get_sender_info(m)
921
922                if not self.email_header_acl('white_list', self.email_addr, True):
923                        if self.DEBUG > 1 :
924                                print 'Message rejected : %s not in white list' %(self.email_addr)
925                        return False
926
927                if self.email_header_acl('black_list', self.email_addr, False):
928                        if self.DEBUG > 1 :
929                                print 'Message rejected : %s in black list' %(self.email_addr)
930                        return False
931
932                if not self.email_header_acl('recipient_list', self.to_email_addr, True):
933                        if self.DEBUG > 1 :
934                                print 'Message rejected : %s not in recipient list' %(self.to_email_addr)
935                        return False
936
937                # If drop the message
938                #
939                if self.spam(m) == 'drop':
940                        return False
941
942                elif self.spam(m) == 'spam':
943                        spam_msg = True
944                else:
945                        spam_msg = False
946
947                if self.get_config('notification', 'smtp_enabled') in ['true']:
948                        self.notification = 1
949                else:
950                        self.notification = 0
951
952                # Check if  FullBlogPlugin is installed
953                #
954                blog_enabled = None
955                if self.get_config('components', 'tracfullblog.*') in ['enabled']:
956                        blog_enabled = True
957                       
958                if not m['Subject']:
959                        return False
960                else:
961                        subject  = self.email_to_unicode(m['Subject'])         
962
963                #
964                # [hic] #1529: Re: LRZ
965                # [hic] #1529?owner=bas,priority=medium: Re: LRZ
966                #
967                TICKET_RE = re.compile(r"""
968                        (?P<blog>blog:(?P<blog_id>\w*))
969                        |(?P<new_fields>[#][?].*)
970                        |(?P<reply>[#][\d]+:)
971                        |(?P<reply_fields>[#][\d]+\?.*?:)
972                        """, re.VERBOSE)
973
974                # Find out if this is a ticket or a blog
975                #
976                result =  TICKET_RE.search(subject)
977
978                if result:
979                        if result.group('blog') and blog_enabled:
980                                self.blog(result.group('blog_id'))
981
982                        # update ticket + fields
983                        #
984                        if result.group('reply_fields') and self.TICKET_UPDATE:
985                                self.ticket_update(m, result.group('reply_fields'), spam_msg)
986
987                        # Update ticket
988                        #
989                        elif result.group('reply') and self.TICKET_UPDATE:
990                                self.ticket_update(m, result.group('reply'), spam_msg)
991
992                        # New ticket + fields
993                        #
994                        elif result.group('new_fields'):
995                                self.new_ticket(m, subject[:result.start('new_fields')], spam_msg, result.group('new_fields'))
996
997                # Create ticket
998                #
999                else:
1000                        self.new_ticket(m, subject, spam_msg)
1001
1002        def strip_signature(self, text):
1003                """
1004                Strip signature from message, inspired by Mailman software
1005                """
1006                body = []
1007                for line in text.splitlines():
1008                        if line == '-- ':
1009                                break
1010                        body.append(line)
1011
1012                return ('\n'.join(body))
1013
1014        def reflow(self, text, delsp = 0):
1015                """
1016                Reflow the message based on the format="flowed" specification (RFC 3676)
1017                """
1018                flowedlines = []
1019                quotelevel = 0
1020                prevflowed = 0
1021
1022                for line in text.splitlines():
1023                        from re import match
1024                       
1025                        # Figure out the quote level and the content of the current line
1026                        m = match('(>*)( ?)(.*)', line)
1027                        linequotelevel = len(m.group(1))
1028                        line = m.group(3)
1029
1030                        # Determine whether this line is flowed
1031                        if line and line != '-- ' and line[-1] == ' ':
1032                                flowed = 1
1033                        else:
1034                                flowed = 0
1035
1036                        if flowed and delsp and line and line[-1] == ' ':
1037                                line = line[:-1]
1038
1039                        # If the previous line is flowed, append this line to it
1040                        if prevflowed and line != '-- ' and linequotelevel == quotelevel:
1041                                flowedlines[-1] += line
1042                        # Otherwise, start a new line
1043                        else:
1044                                flowedlines.append('>' * linequotelevel + line)
1045
1046                        prevflowed = flowed
1047                       
1048
1049                return '\n'.join(flowedlines)
1050
1051        def strip_quotes(self, text):
1052                """
1053                Strip quotes from message by Nicolas Mendoza
1054                """
1055                body = []
1056                for line in text.splitlines():
1057                        if line.startswith(self.EMAIL_QUOTE):
1058                                continue
1059                        body.append(line)
1060
1061                return ('\n'.join(body))
1062
1063        def wrap_text(self, text, replace_whitespace = False):
1064                """
1065                Will break a lines longer then given length into several small
1066                lines of size given length
1067                """
1068                import textwrap
1069
1070                LINESEPARATOR = '\n'
1071                reformat = ''
1072
1073                for s in text.split(LINESEPARATOR):
1074                        tmp = textwrap.fill(s,self.USE_TEXTWRAP)
1075                        if tmp:
1076                                reformat = '%s\n%s' %(reformat,tmp)
1077                        else:
1078                                reformat = '%s\n' %reformat
1079
1080                return reformat
1081
1082                # Python2.4 and higher
1083                #
1084                #return LINESEPARATOR.join(textwrap.fill(s,width) for s in str.split(LINESEPARATOR))
1085                #
1086
1087
1088        def get_message_parts(self, msg):
1089                """
1090                parses the email message and returns a list of body parts and attachments
1091                body parts are returned as strings, attachments are returned as tuples of (filename, Message object)
1092                """
1093                message_parts = []
1094       
1095                ALTERNATIVE_MULTIPART = False
1096
1097                for part in msg.walk():
1098                        if self.DEBUG:
1099                                print 'TD: Message part: Main-Type: %s' % part.get_content_maintype()
1100                                print 'TD: Message part: Content-Type: %s' % part.get_content_type()
1101
1102                        ## Check content type
1103                        #
1104                        if part.get_content_type() in self.STRIP_CONTENT_TYPES:
1105
1106                                if self.DEBUG:
1107                                        print "TD: A %s attachment named '%s' was skipped" %(part.get_content_type(), part.get_filename())
1108
1109                                continue
1110
1111                        ## Catch some mulitpart execptions
1112                        #
1113                        if part.get_content_type() == 'multipart/alternative':
1114                                ALTERNATIVE_MULTIPART = True
1115                                continue
1116
1117                        ## Skip multipart containers
1118                        #
1119                        if part.get_content_maintype() == 'multipart':
1120                                if self.DEBUG:
1121                                        print "TD: Skipping multipart container"
1122                                continue
1123                       
1124                        ## 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"
1125                        #
1126                        inline = self.inline_part(part)
1127
1128                        ## Drop HTML message
1129                        #
1130                        if ALTERNATIVE_MULTIPART and self.DROP_ALTERNATIVE_HTML_VERSION:
1131                                if part.get_content_type() == 'text/html':
1132                                        if self.DEBUG:
1133                                                print "TD: Skipping alternative HTML message"
1134
1135                                        ALTERNATIVE_MULTIPART = False
1136                                        continue
1137
1138                        ## Inline text parts are where the body is
1139                        #
1140                        if part.get_content_type() == 'text/plain' and inline:
1141                                if self.DEBUG:
1142                                        print 'TD:               Inline body part'
1143
1144                                # Try to decode, if fails then do not decode
1145                                #
1146                                body_text = part.get_payload(decode=1)
1147                                if not body_text:                       
1148                                        body_text = part.get_payload(decode=0)
1149
1150                                format = email.Utils.collapse_rfc2231_value(part.get_param('Format', 'fixed')).lower()
1151                                delsp = email.Utils.collapse_rfc2231_value(part.get_param('DelSp', 'no')).lower()
1152
1153                                if self.REFLOW and not self.VERBATIM_FORMAT and format == 'flowed':
1154                                        body_text = self.reflow(body_text, delsp == 'yes')
1155       
1156                                if self.STRIP_SIGNATURE:
1157                                        body_text = self.strip_signature(body_text)
1158
1159                                if self.STRIP_QUOTES:
1160                                        body_text = self.strip_quotes(body_text)
1161
1162                                if self.USE_TEXTWRAP:
1163                                        body_text = self.wrap_text(body_text)
1164
1165                                ## Get contents charset (iso-8859-15 if not defined in mail headers)
1166                                #
1167                                charset = part.get_content_charset()
1168                                if not charset:
1169                                        charset = 'iso-8859-15'
1170
1171                                try:
1172                                        ubody_text = unicode(body_text, charset)
1173
1174                                except UnicodeError, detail:
1175                                        ubody_text = unicode(body_text, 'iso-8859-15')
1176
1177                                except LookupError, detail:
1178                                        ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset)
1179
1180                                if self.VERBATIM_FORMAT:
1181                                        message_parts.append('{{{\r\n%s\r\n}}}' %ubody_text)
1182                                else:
1183                                        message_parts.append('%s' %ubody_text)
1184                        else:
1185                                if self.DEBUG:
1186                                        print 'TD:               Filename: %s' % part.get_filename()
1187
1188                                message_parts.append((part.get_filename(), part))
1189
1190                return message_parts
1191               
1192        def unique_attachment_names(self, message_parts):
1193                renamed_parts = []
1194                attachment_names = set()
1195                for part in message_parts:
1196                       
1197                        # If not an attachment, leave it alone
1198                        if not isinstance(part, tuple):
1199                                renamed_parts.append(part)
1200                                continue
1201                               
1202                        (filename, part) = part
1203                        # Decode the filename
1204                        if filename:
1205                                filename = self.email_to_unicode(filename)                     
1206
1207                        # If no name, use a default one
1208                        else:
1209                                filename = 'untitled-part'
1210
1211                                # Guess the extension from the content type, use non strict mode
1212                                # some additional non-standard but commonly used MIME types
1213                                # are also recognized
1214                                #
1215                                ext = mimetypes.guess_extension(part.get_content_type(), False)
1216                                if not ext:
1217                                        ext = '.bin'
1218
1219                                filename = '%s%s' % (filename, ext)
1220
1221                        # Discard relative paths in attachment names
1222                        filename = filename.replace('\\', '/').replace(':', '/')
1223                        filename = os.path.basename(filename)
1224
1225                        # We try to normalize the filename to utf-8 NFC if we can.
1226                        # Files uploaded from OS X might be in NFD.
1227                        # Check python version and then try it
1228                        #
1229                        if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
1230                                try:
1231                                        filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
1232                                except TypeError:
1233                                        pass
1234                                       
1235                        if self.QUOTE_ATTACHMENT_FILENAMES:
1236                                try:
1237                                        filename = urllib.quote(filename)
1238                                except KeyError, detail:
1239                                        filename = urllib.quote(filename.encode('utf-8'))
1240
1241                        # Make the filename unique for this ticket
1242                        num = 0
1243                        unique_filename = filename
1244                        filename, ext = os.path.splitext(filename)
1245
1246                        while unique_filename in attachment_names or self.attachment_exists(unique_filename):
1247                                num += 1
1248                                unique_filename = "%s-%s%s" % (filename, num, ext)
1249                               
1250                        if self.DEBUG:
1251                                print 'TD: Attachment with filename %s will be saved as %s' % (filename, unique_filename)
1252
1253                        attachment_names.add(unique_filename)
1254
1255                        renamed_parts.append((filename, unique_filename, part))
1256               
1257                return renamed_parts
1258                       
1259        def inline_part(self, part):
1260                return part.get_param('inline', None, 'Content-Disposition') == '' or not part.has_key('Content-Disposition')
1261               
1262                       
1263        def attachment_exists(self, filename):
1264
1265                if self.DEBUG:
1266                        print "TD: attachment_exists: Ticket number : %s, Filename : %s" %(self.id, filename)
1267
1268                # We have no valid ticket id
1269                #
1270                if not self.id:
1271                        return False
1272
1273                try:
1274                        att = attachment.Attachment(self.env, 'ticket', self.id, filename)
1275                        return True
1276                except attachment.ResourceNotFound:
1277                        return False
1278                       
1279        def body_text(self, message_parts):
1280                body_text = []
1281               
1282                for part in message_parts:
1283                        # Plain text part, append it
1284                        if not isinstance(part, tuple):
1285                                body_text.extend(part.strip().splitlines())
1286                                body_text.append("")
1287                                continue
1288                               
1289                        (original, filename, part) = part
1290                        inline = self.inline_part(part)
1291                       
1292                        if part.get_content_maintype() == 'image' and inline:
1293                                body_text.append('[[Image(%s)]]' % filename)
1294                                body_text.append("")
1295                        else:
1296                                body_text.append('[attachment:"%s"]' % filename)
1297                                body_text.append("")
1298                               
1299                body_text = '\r\n'.join(body_text)
1300                return body_text
1301
1302        def notify(self, tkt, new=True, modtime=0):
1303                """
1304                A wrapper for the TRAC notify function. So we can use templates
1305                """
1306                if self.DRY_RUN:
1307                                print 'DRY_RUN: self.notify(tkt, True) ', self.author
1308                                return
1309                try:
1310                        # create false {abs_}href properties, to trick Notify()
1311                        #
1312                        if not self.VERSION == 0.11:
1313                                self.env.abs_href = Href(self.get_config('project', 'url'))
1314                                self.env.href = Href(self.get_config('project', 'url'))
1315
1316                        tn = TicketNotifyEmail(self.env)
1317
1318                        if self.notify_template:
1319
1320                                if self.VERSION == 0.11:
1321
1322                                        from trac.web.chrome import Chrome
1323
1324                                        if self.notify_template_update and not new:
1325                                                tn.template_name = self.notify_template_update
1326                                        else:
1327                                                tn.template_name = self.notify_template
1328
1329                                        tn.template = Chrome(tn.env).load_template(tn.template_name, method='text')
1330                                               
1331                                else:
1332
1333                                        tn.template_name = self.notify_template;
1334
1335                        tn.notify(tkt, new, modtime)
1336
1337                except Exception, e:
1338                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(self.id, e)
1339
1340        def html_mailto_link(self, subject, body):
1341                """
1342                This function returns a HTML mailto tag with the ticket id and author email address
1343                """
1344                if not self.author:
1345                        author = self.email_addr
1346                else:   
1347                        author = self.author
1348
1349                # use urllib to escape the chars
1350                #
1351                s = 'mailto:%s?Subject=%s&Cc=%s' %(
1352                       urllib.quote(self.email_addr),
1353                           urllib.quote('Re: #%s: %s' %(self.id, subject)),
1354                           urllib.quote(self.MAILTO_CC)
1355                           )
1356
1357                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)
1358                return s
1359
1360        def attachments(self, message_parts, update=False):
1361                '''
1362                save any attachments as files in the ticket's directory
1363                '''
1364                if self.DRY_RUN:
1365                        print "DRY_RUN: no attachments saved"
1366                        return ''
1367
1368                count = 0
1369
1370                # Get Maxium attachment size
1371                #
1372                max_size = int(self.get_config('attachment', 'max_size'))
1373                status   = ''
1374               
1375                for part in message_parts:
1376                        # Skip body parts
1377                        if not isinstance(part, tuple):
1378                                continue
1379                               
1380                        (original, filename, part) = part
1381                        #
1382                        # Must be tuneables HvB
1383                        #
1384                        path, fd =  util.create_unique_file(os.path.join(self.TMPDIR, filename))
1385                        text = part.get_payload(decode=1)
1386                        if not text:
1387                                text = '(None)'
1388                        fd.write(text)
1389                        fd.close()
1390
1391                        # get the file_size
1392                        #
1393                        stats = os.lstat(path)
1394                        file_size = stats[stat.ST_SIZE]
1395
1396                        # Check if the attachment size is allowed
1397                        #
1398                        if (max_size != -1) and (file_size > max_size):
1399                                status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \
1400                                        %(status, original, file_size, max_size)
1401
1402                                os.unlink(path)
1403                                continue
1404                        else:
1405                                count = count + 1
1406                                       
1407                        # Insert the attachment
1408                        #
1409                        fd = open(path, 'rb')
1410                        att = attachment.Attachment(self.env, 'ticket', self.id)
1411
1412                        # This will break the ticket_update system, the body_text is vaporized
1413                        # ;-(
1414                        #
1415                        if not update:
1416                                att.author = self.author
1417                                att.description = self.email_to_unicode('Added by email2trac')
1418
1419                        att.insert(filename, fd, file_size)
1420                        #except  util.TracError, detail:
1421                        #       print detail
1422
1423                        # Remove the created temporary filename
1424                        #
1425                        fd.close()
1426                        os.unlink(path)
1427
1428                # Return how many attachments
1429                #
1430                status = 'This message has %d attachment(s)\n%s' %(count, status)
1431                return status
1432
1433
1434def mkdir_p(dir, mode):
1435        '''do a mkdir -p'''
1436
1437        arr = string.split(dir, '/')
1438        path = ''
1439        for part in arr:
1440                path = '%s/%s' % (path, part)
1441                try:
1442                        stats = os.stat(path)
1443                except OSError:
1444                        os.mkdir(path, mode)
1445
1446def ReadConfig(file, name):
1447        """
1448        Parse the config file
1449        """
1450        if not os.path.isfile(file):
1451                print 'File %s does not exist' %file
1452                sys.exit(1)
1453
1454        config = trac_config.Configuration(file)
1455
1456        # Use given project name else use defaults
1457        #
1458        if name:
1459                sections = config.sections()
1460                if not name in sections:
1461                        print "Not a valid project name: %s" %name
1462                        print "Valid names: %s" %sections
1463                        sys.exit(1)
1464
1465                project =  dict()
1466                for option, value in  config.options(name):
1467                        project[option] = value
1468
1469        else:
1470                # use some trac internals to get the defaults
1471                #
1472                project = config.parser.defaults()
1473
1474        return project
1475
1476
1477if __name__ == '__main__':
1478        # Default config file
1479        #
1480        configfile = '@email2trac_conf@'
1481        project = ''
1482        component = ''
1483        ticket_prefix = 'default'
1484        dry_run = None
1485
1486        ENABLE_SYSLOG = 0
1487
1488
1489        SHORT_OPT = 'chf:np:t:'
1490        LONG_OPT  =  ['component=', 'dry-run', 'help', 'file=', 'project=', 'ticket_prefix=']
1491
1492        try:
1493                opts, args = getopt.getopt(sys.argv[1:], SHORT_OPT, LONG_OPT)
1494        except getopt.error,detail:
1495                print __doc__
1496                print detail
1497                sys.exit(1)
1498       
1499        project_name = None
1500        for opt,value in opts:
1501                if opt in [ '-h', '--help']:
1502                        print __doc__
1503                        sys.exit(0)
1504                elif opt in ['-c', '--component']:
1505                        component = value
1506                elif opt in ['-f', '--file']:
1507                        configfile = value
1508                elif opt in ['-n', '--dry-run']:
1509                        dry_run = True
1510                elif opt in ['-p', '--project']:
1511                        project_name = value
1512                elif opt in ['-t', '--ticket_prefix']:
1513                        ticket_prefix = value
1514       
1515        settings = ReadConfig(configfile, project_name)
1516        if not settings.has_key('project'):
1517                print __doc__
1518                print 'No Trac project is defined in the email2trac config file.'
1519                sys.exit(1)
1520       
1521        if component:
1522                settings['component'] = component
1523
1524        # The default prefix for ticket values in email2trac.conf
1525        #
1526        settings['ticket_prefix'] = ticket_prefix
1527        settings['dry_run'] = dry_run
1528       
1529        if settings.has_key('trac_version'):
1530                version = settings['trac_version']
1531        else:
1532                version = trac_default_version
1533
1534
1535        #debug HvB
1536        #print settings
1537
1538        try:
1539                if version == '0.9':
1540                        from trac import attachment
1541                        from trac.env import Environment
1542                        from trac.ticket import Ticket
1543                        from trac.web.href import Href
1544                        from trac import util
1545                        from trac.Notify import TicketNotifyEmail
1546                elif version == '0.10':
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                        #
1553                        # return  util.text.to_unicode(str)
1554                        #
1555                        # see http://projects.edgewall.com/trac/changeset/2799
1556                        from trac.ticket.notification import TicketNotifyEmail
1557                        from trac import config as trac_config
1558                elif version == '0.11':
1559                        from trac import attachment
1560                        from trac.env import Environment
1561                        from trac.ticket import Ticket
1562                        from trac.web.href import Href
1563                        from trac import config as trac_config
1564                        from trac import util
1565
1566
1567                        #
1568                        # return  util.text.to_unicode(str)
1569                        #
1570                        # see http://projects.edgewall.com/trac/changeset/2799
1571                        from trac.ticket.notification import TicketNotifyEmail
1572                else:
1573                        print 'TRAC version %s is not supported' %version
1574                        sys.exit(1)
1575                       
1576                if settings.has_key('enable_syslog'):
1577                        if SYSLOG_AVAILABLE:
1578                                ENABLE_SYSLOG =  float(settings['enable_syslog'])
1579
1580
1581                # Must be set before environment is created
1582                #
1583                if settings.has_key('python_egg_cache'):
1584                        python_egg_cache = str(settings['python_egg_cache'])
1585                        os.environ['PYTHON_EGG_CACHE'] = python_egg_cache
1586
1587                env = Environment(settings['project'], create=0)
1588                tktparser = TicketEmailParser(env, settings, float(version))
1589                tktparser.parse(sys.stdin)
1590
1591        # Catch all errors ans log to SYSLOG if we have enabled this
1592        # else stdout
1593        #
1594        except Exception, error:
1595                if ENABLE_SYSLOG:
1596                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
1597
1598                        etype, evalue, etb = sys.exc_info()
1599                        for e in traceback.format_exception(etype, evalue, etb):
1600                                syslog.syslog(e)
1601
1602                        syslog.closelog()
1603                else:
1604                        traceback.print_exc()
1605
1606                if m:
1607                        tktparser.save_email_for_debug(m, True)
1608
1609                sys.exit(1)
1610# EOB
Note: See TracBrowser for help on using the repository browser.