source: trunk/email2trac.py.in @ 301

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

prevent mail loop, closes #172

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