source: trunk/email2trac.py.in @ 315

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

email2trac.py.in:

fixed a debug issue and Unicode chars

run_email2trac.c:

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