source: trunk/email2trac.py.in @ 300

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

a better patch for handling mail loop in set_reply and cc-field, depends on smtp_from setting in trac.ini, see #172

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