source: trunk/email2trac.py.in @ 305

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

Prevent mail loops when from address is set, see #172

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