Added --disable-backup option.
[confparser-old] / mail.filter
1 #!/usr/bin/python
2 # -*- coding: iso-8859-1 -*-
3
4 #
5 # MailFilter - Mail filter to replace procmail.
6 # Copyright (C) 2004  Frédéric Jolliton <frederic@jolliton.com>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 #
22
23 #
24 # Policy when error are encountered:
25 #
26 # We backup the mail in a special directory. It will be
27 # at the admin discretion to feed it again to this program
28 # (or may be script that.)
29 #
30
31 #
32 # TODO:
33 #
34 #   [ ] Define precisely what return code use for each possible case.
35 #
36
37 import sys
38 import os
39 import time
40 import email
41 import types
42 import re
43
44 import confparser
45
46 from os import EX_USAGE, EX_OK, EX_NOUSER, EX_TEMPFAIL, EX_DATAERR
47 # EX_OK           0       ok
48 # EX_USAGE        64      command line usage error
49 # EX_DATAERR      65      data format error
50 # EX_NOINPUT      66      cannot open input
51 # EX_NOUSER       67      addressee unknown
52 # EX_NOHOST       68      host name unknown
53 # EX_UNAVAILABLE  69      service unavailable
54 # EX_SOFTWARE     70      internal software error
55 # EX_OSERR        71      system error (e.g., can't fork)
56 # EX_OSFILE       72      critical OS file missing
57 # EX_CANTCREAT    73      can't create (user) output file
58 # EX_IOERR        74      input/output error
59 # EX_TEMPFAIL     75      temp failure; user is invited to retry
60 # EX_PROTOCOL     76      remote error in protocol
61 # EX_NOPERM       77      permission denied
62 # EX_CONFIG       78      configuration error
63
64 #
65 # Path to subprocess module. Ideally not needed if subprocess
66 # (formerly popen5) is installed into /site-packages/ directory.
67 #
68 #sys.path.insert( 0 , '/usr/local/lib/python/' )
69
70 #
71 # subprocess (formerly popen5) - See PEP 324
72 # http://www.lysator.liu.se/~astrand/popen5/
73 #
74 # >>> cat = subprocess.Popen( 'cat' , stdin = subprocess.PIPE , stdout = subprocess.PIPE )
75 # >>> cat.communicate( 'bla' )
76 # ('bla', None)
77 # >>> cat.returncode
78 # 0
79 #
80 try :
81         import subprocess
82 except ImportError :
83         try :
84                 import popen5 as subprocess
85         except ImportError :
86                 print 'Please install subprocess module.'
87                 print 'See http://www.lysator.liu.se/~astrand/popen5/.'
88                 sys.exit( 1 )
89
90 #--[ Configuration variables ]------------------------------------------------
91
92 #
93 # Filename where to put log.
94 #
95 g_pathLog          = '/var/log/mail.filter.log'
96
97 #
98 # For which users receiving a mail should we send a UDP packet.
99 #
100 g_userNotificationFilter = [ 'fred' ]
101
102 #
103 # For which IP address should we send the notification.
104 # (Can include broadcast address.)
105 #
106 g_notificationAddresses = [ '192.168.1.255' ]
107
108 #
109 # On which port should we send the notification.
110 #
111 g_notificationPort = 23978
112
113 #
114 # Max mail size to be processed by this script.
115 #
116 # Larger mail are just not filtered.
117 #
118 g_maxMailSize = 2 * 1024 * 1024
119
120 #
121 # Where to save copy of mail in case of error.
122 #
123 g_directoryBackup  = '/var/mail.filter/recovery/'
124
125 #
126 # If set, then no backup are produced in case of error.
127 #
128 g_backupDisabled = False
129
130 #
131 # Where to find rules about each user.
132
133 # Filename for user 'joe' will be named 'joe.mf' in that
134 # directory. If the file doesn't exist, no filtering is
135 # done (not even spam/virus filtering.)
136 #
137 g_directoryRules   = '/var/mail.filter/rules/'
138
139 #--[ External commands ]------------------------------------------------------
140
141 #
142 # Path to Cyrus's deliver binary.
143 #
144 g_pathCyrusDeliver = '/usr/cyrus/bin/deliver'
145
146 #
147 # Path to spamprobe binary.
148 #
149 g_pathSpamProbe    = '/usr/bin/spamprobe'
150
151 g_pathSpamProbeDb  = '/var/spamprobe/db'
152
153 #
154 # Path to ClamAV binary.
155 #
156 # Could point either to 'clamdscan' or 'clamscan'.
157 #
158 # The first one is *HIGHLY* recommended since
159 # it will use the ClamAV daemon.
160 #
161 g_pathClamdscan    = '/usr/bin/clamdscan'
162
163 #--[ Global variables ]-------------------------------------------------------
164
165 #
166 # Should the log be also printed on stdout ?
167 #
168 g_copyLogToStdout = False
169
170 #
171 # Don't actually feed the mail to Cyrus.
172 #
173 g_testMode = False
174
175 #
176 # The user name of the recipient.
177 #
178 g_user = None
179
180 #
181 # The current mail as string (as read from stdin.)
182 #
183 g_mailText = None
184
185 #
186 # The current mail as email.Message.Message object.
187 #
188 g_mail = None
189
190 #-----------------------------------------------------------------------------
191
192 #
193 # check if predicate is True for all items in list 'lst'.
194 #
195 def all( lst , predicate ) :
196
197         for item in lst :
198                 if not predicate( item ) :
199                         return False
200         return True
201
202 #
203 # check if predicate is True for at least one item in list 'lst'.
204 #
205 def some( lst , predicate ) :
206
207         for item in lst :
208                 if predicate( item ) :
209                         return True
210         return False
211
212 #
213 # Remove leading and trailing blank, and replace any
214 # blank character sequence by one space character.
215 #
216 def normalizeBlank( s ) :
217
218         return ' '.join( s.split() )
219
220 #-----------------------------------------------------------------------------
221
222 #
223 # Utility function to return traceback as string from most recent
224 # exception.
225 #
226 def getTraceBack() : 
227  
228         import traceback, sys 
229         return ''.join( traceback.format_exception( *sys.exc_info() ) )
230
231 #
232 # Return (returnCode, stdout, stderr)
233 #
234 def pipe( cmd , input ) :
235
236         p = subprocess.Popen( cmd ,
237                 stdin = subprocess.PIPE ,
238                 stdout = subprocess.PIPE ,
239                 stderr = subprocess.PIPE )
240         try :
241                 # much faster than passing 'input' to communicate directly..
242                 p.stdin.write( input )
243         except IOError :
244                 pass
245         r = p.communicate()
246         return p.returncode , r[ 0 ] , r[ 1 ]
247
248 #
249 # Return an ISO-8661 date representation for the UTC
250 # timezone.
251 #
252 # timestamp( 0 ) => 1970-01-01T00:00:00Z
253 #
254 def timestamp() :
255
256         t = time.gmtime()
257         return '%04d-%02d-%02dT%02d:%02d:%02dZ' % t[ : 6 ]
258
259 #
260 # Log message 'msg'.
261 #
262 def logMessage( msg ) :
263
264         if not logMessage.logFile and not g_testMode :
265                 #
266                 # If log file is not yet open, try to open it.
267                 #
268                 try :
269                         logMessage.logFile = open( g_pathLog , 'a+' )
270                 except :
271                         if not g_copyLogToStdout :
272                                 return
273
274         msg = msg.splitlines()
275         prefix = timestamp() + ' [%s] ' % os.getpid()
276
277         #
278         # Output to log file.
279         #
280         if logMessage.logFile :
281
282                 for line in msg :
283                         line = prefix + line
284                         try :
285                                 logMessage.logFile.write( line + '\n' )
286                                 logMessage.logFile.flush()
287                         except :
288                                 pass
289
290         #
291         # Output to standard output.
292         #
293         if g_copyLogToStdout :
294
295                 for line in msg :
296                         line = prefix + line
297                         sys.stdout.write( line + '\n' )
298                         sys.stdout.flush()
299
300 logMessage.logFile = None
301
302 #
303 # Make a backup of the mail (in case it's impossible
304 # to store the mail to Cyrus.)
305 #
306 def backup( filenamePrefix = None ) :
307
308         if g_testMode :
309                 logMessage( 'TEST MODE: Backup of the mail requested.' )
310                 return
311
312         if g_backupDisabled :
313                 logMessage( 'Backup requested, but disabled.' )
314                 return
315
316         try :
317                 # Ensure directory exist
318                 import os
319                 os.makedirs( g_directoryBackup )
320         except :
321                 pass
322
323         basename = ''
324         if filenamePrefix :
325                 basename += filenamePrefix + '-'
326         #
327         # Append current unix time as suffix
328         #
329         basename += '%.3f' % time.time()
330
331         fn = g_directoryBackup + '/' + basename
332         try :
333                 f = open( fn , 'a+' )
334                 f.write( g_mailText )
335                 f.close()
336         except :
337                 logMessage( 'PANIC: Unable to write backup to %s.' % fn )
338         else :
339                 logMessage( 'Message appended to backup directory as `%s\'.' % basename )
340
341 #-----------------------------------------------------------------------------
342
343 class Action : pass
344
345 class NullAction( Action ) :
346
347         def __repr__( self ) :
348
349                 return '<NullAction>'
350
351 class FileToFolderAction( Action ) :
352
353         def __init__( self , folder ) :
354
355                 self.folder = folder
356
357         def __repr__( self ) :
358
359                 return '<FileToFolderAction %r>' % ( self.folder , )
360
361 class CustomErrorCodeAction( Action ) :
362
363         def __init__( self , code ) :
364
365                 self.code = code
366
367         def __repr__( self ) :
368
369                 return '<NullAction %r>' % ( self.code , )
370
371 #-----------------------------------------------------------------------------
372
373 #
374 # Experimental !
375 #
376 # Packet payload contains:
377 #
378 # <username> + char( 0 ) + <foldername> + char( 0 )
379 #
380 def notifyDeliver( user , folder ) :
381
382         if user not in g_userNotificationFilter :
383                 return
384         try :
385                 import socket
386                 s = socket.socket( socket.AF_INET , socket.SOCK_DGRAM )
387                 msg = user + chr( 0 ) + folder + chr( 0 )
388                 for address in g_notificationAddresses :
389                         s.sendto( msg , ( address , g_notificationPort ) )
390         except :
391                 pass
392
393 #
394 # Deliver a mail to Cyrus for user 'username' in
395 # folder 'folderName' (or default folder if not
396 # specified.)
397 #
398 def deliverTo( username , folderName = None ) :
399
400         if not folderName :
401                 pseudoFolderName = 'INBOX'
402                 folderName = 'user.' + username
403         else :
404                 pseudoFolderName = 'INBOX.' + folderName
405                 folderName = 'user.' + username + '.' + folderName
406
407         #
408         # Build the command line for running deliver.
409         #
410         cmd = [ g_pathCyrusDeliver ]
411         cmd += [ '-a' , username ]
412         cmd += [ '-m' , folderName ]
413
414         if g_testMode :
415                 logMessage( 'TEST MODE: Delivering mail in `%s\' requested.' % ( folderName , ) )
416                 logMessage( 'TEST MODE: Command: %r.' % cmd )
417                 return EX_OK
418
419         try :
420                 rc , stdout , stderr = pipe( cmd , g_mailText )
421         except OSError , e :
422                 logMessage( 'Error running `%s\': %s.' % ( cmd[ 0 ] , e[ 1 ] ) )
423                 return EX_TEMPFAIL
424
425         if rc == EX_OK :
426                 logMessage( 'Message delivered in folder `%s\'.' % folderName )
427                 notifyDeliver( username , pseudoFolderName )
428         else :
429                 errorMessage = stdout.rstrip()
430                 #
431                 # Extract raw error message
432                 #
433                 # Example of output:
434                 #
435                 #   +user.fred: Message contains invalid header
436                 #
437                 m = errorMessage.split( ': ' , 1 )
438                 if len( m ) == 2 :
439                         m = m[ 1 ]
440                 else :
441                         m = None
442                 rcMsg = '%d' % rc
443                 if m == 'Message contains invalid header' :
444                         rc = EX_DATAERR
445                         rcMsg += '->%d' % rc
446                 elif m == 'Mailbox does not exist' :
447                         rc = EX_NOUSER
448                         rcMsg += '->%d' % rc
449                 else :
450                         # FIXME: DATAERR ok here ?
451                         rc = EX_DATAERR
452                 logMessage( 'Refused by Cyrus: [%s] `%s\'.' % ( rcMsg , errorMessage ) )
453         return rc
454
455 #--[ Antivirus ]--------------------------------------------------------------
456
457 #
458 # Return virus list from the output of ClamAV.
459 #
460 def extractVirusList( clamdOutput ) :
461
462         res = []
463         for line in clamdOutput.splitlines() :
464                 r = extractVirusList.reClamdVirus.search( line.rstrip() )
465                 if r == None : continue
466                 res.append( r.group( 1 ) )
467         return res
468
469 extractVirusList.reClamdVirus = re.compile( r'^[^:]+: (\S+) FOUND$' )
470
471 #
472 # Check for virus.
473 #
474 # Return True if mail is clean.
475 #
476 def antivirusScan() :
477
478         cmd = [ g_pathClamdscan , '-' ]
479
480         if g_testMode :
481                 logMessage( 'TEST MODE: Virus scan requested.' )
482                 logMessage( 'TEST MODE: Command: %r.' % cmd )
483                 return True
484
485         rc , stdout , stderr = pipe( cmd , g_mailText )
486         output = stderr or ''
487         #logMessage( 'clamdscan returned %s' % rc )
488         if rc == 2 :
489                 raise 'Unable to scan for viruses (%s)' % cmd
490         ok = not rc
491         if not ok :
492                 msg = 'Virus found.'
493                 viruses = extractVirusList( output )
494                 if viruses :
495                         msg += ' [%s]' % ' '.join( viruses )
496                 logMessage( msg )
497         return ok
498
499 #--[ Antispam ]---------------------------------------------------------------
500
501 #
502 # Check for spam.
503 #
504 # Return True if mail is correct.
505 #
506 def spamScan() :
507
508         if not g_user : return True
509
510         cmd = [ g_pathSpamProbe ]
511         cmd += [ '-d' , g_pathSpamProbeDb + '/' + g_user + '/' ]
512         cmd += [ 'receive' ]
513
514         if g_testMode :
515                 logMessage( 'TEST MODE: Spam scan requested.' )
516                 logMessage( 'TEST MODE: Command: %r.' % cmd )
517                 return True
518
519         rc , stdout , stderr = pipe( cmd , g_mailText )
520         r = ( stdout or '' ).split()
521         return r[ 0 ] != 'SPAM'
522
523 #-----------------------------------------------------------------------------
524
525 def errorNameToErrorCode( code ) :
526
527         code = code.lower()
528         if code == 'nouser' :
529                 return EX_NOUSER
530         elif code == 'tempfail' :
531                 return EX_TEMPFAIL
532         elif code == 'dataerr' :
533                 return EX_DATAERR
534         else :
535                 try :
536                         return int( code )
537                 except :
538                         return 0
539
540 #-----------------------------------------------------------------------------
541
542 #
543 # FIXME: I think it could be better to cache the parsed
544 # configuration, and also to cache the result of the validator
545 # so that we don't run the test each time this script is run !
546 #
547 def readUserRules( user ) :
548
549         filename = g_directoryRules + '/' + user + '.mf'
550
551         #
552         # Read the configuration.
553         #
554         try :
555                 return confparser.readConfiguration( filename )
556         except OSError , e :
557                 pass
558         except Exception , e :
559                 logMessage( 'Error in file %r. See option -c to check this file.' % ( filename , ) )
560
561 #-----------------------------------------------------------------------------
562
563 #
564 # Test a match rule against a particular header.
565 #
566 def ruleMatch( header , matchType , text ) :
567
568         if matchType == 'match' :
569                 try :
570                         return re.search( text , header , re.I ) != None
571                 except :
572                         logMessage( 'Error with regex `%s\' from %s\'s user configuration.' % ( text , g_user ) )
573                         return False
574         elif matchType == 'is' :
575                 return header.strip().lower() == text.strip().lower()
576         elif matchType == 'contains' :
577                 return header.lower().find( text.strip().lower() ) != -1
578         else :
579                 logMessage( 'Unknown match type `%s\' from %s\'s user configuration.' % ( matchType , g_user ) )
580         return False
581                         
582 #
583 # Test rule 'rule' against the mail.
584 #
585 def testRule( rule ) :
586
587         cmd = rule[ 0 ]
588
589         if cmd == 'and' :
590                 return all( rule[ 2 ] , testRule )
591
592         if cmd == 'or' :
593                 return some( rule[ 2 ] , testRule )
594
595         if cmd == 'not' :
596                 return not some( rule[ 2 ] , testRule )
597
598         #
599         # Matching a header
600         #
601         if cmd == 'header' :
602                 if g_mail == None :
603                         return False
604                 args = rule[ 1 ]
605                 headerName , matchType , text = args
606                 headers = map( normalizeBlank , g_mail.get_all( headerName ) or [] )
607                 return some( headers , lambda header : ruleMatch( header , matchType , text ) )
608
609         #
610         # Broken mail
611         #
612         if cmd == 'broken' :
613                 return g_mail == None
614
615         #
616         # Infected mail
617         #
618         if cmd == 'infected' :
619                 return g_mail != None and not antivirusScan()
620
621         #
622         # Spam mail
623         #
624         if cmd == 'spam' :
625                 return g_mail != None and not spamScan()
626
627         #
628         # Unknown rule
629         #
630         logMessage( 'Unknown rule name `%s\'.' % ( cmd , ) )
631         return False
632
633 #-----------------------------------------------------------------------------
634
635 #
636 # Find the destination folder for user 'user' according to rules defined for
637 # him/her against the current mail.
638 #
639 # Return an Action.
640 #
641 def checkUserRules( user ) :
642
643         action = FileToFolderAction( None )
644
645         conf = readUserRules( user )
646
647         if not conf :
648
649                 logMessage( 'No rules defined for user `%s\'.' % user )
650
651         elif not conf[ 2 ] :
652
653                 logMessage( 'Empty rules set or syntax error encountered for user `%s\'.' % user )
654
655         else :
656
657                 for item in conf[ 2 ] :
658                         actionName , args , subs = item[ : 3 ]
659
660                         if some( subs , testRule ) :
661                                 if actionName == 'folder' :
662                                         action = FileToFolderAction( args[ 0 ] )
663                                 elif actionName == 'reject' :
664                                         action = CustomErrorCodeAction( errorNameToErrorCode( args[ 0 ] ) )
665                                 else :
666                                         logMessage( 'Unknown action `%s\'.' % actionName )
667                                 break
668
669         return action
670
671 #-----------------------------------------------------------------------------
672
673 #
674 # Read mail from standard input.
675 #
676 def readMail() :
677
678         global g_mailText
679
680         #
681         # FIXME: Should we be reading the mail by block, so that
682         # we can at least read and backup a part of the standard input
683         # in case an error occur ? (broken pipe for example)
684         #
685         # If error occur, and since we can't backup the mail,
686         # we ask sendmail to retry later.
687         #
688         try :
689                 g_mailText = sys.stdin.read()
690         except :
691                 logMessage( getTraceBack() )
692                 sys.exit( EX_TEMPFAIL )
693
694 #
695 # Check if the mail is bigger than a predefined amount.
696 #
697 def checkForLargeMail() :
698
699         if len( g_mailText ) > g_maxMailSize :
700                 logMessage( 'Message too big (%s bytes). Not filtering it.' % len( g_mailText ) )
701                 rc = None
702                 if g_user :
703                         rc = deliverTo( g_user )
704                         if rc != EX_OK :
705                                 logMessage( 'Unable to deliver it to user `%s\'.' % g_user )
706                 if rc != EX_OK :
707                         backup( g_user )
708                 sys.exit( EX_OK )
709
710 #
711 # Check if user was specified of command line.
712 #
713 def checkForUser() :
714
715         #
716         # No user specified.
717         #
718         if not g_user :
719                 logMessage( 'No user specified.' )
720                 backup()
721                 sys.exit( EX_OK )
722
723 #
724 # Parse the mail using email python standard module.
725 #
726 def parseMail() :
727
728         global g_mail
729
730         #
731         # Parsing the mail.
732         #
733         try :
734                 g_mail = email.message_from_string( g_mailText , strict = False )
735         except :
736                 logMessage( getTraceBack() )
737
738 #
739 # Dispatch the mail to the correct folder (deduced from rules.)
740 #
741 # Return an error code.
742 #
743 def dispatchMail() :
744
745         action = checkUserRules( g_user )
746
747         #
748         # If custom error code is returned, stop processing
749         # here (mail is not saved.)
750         #
751         if isinstance( action , CustomErrorCodeAction ) :
752                 logMessage( 'Custom exit code is %d.' % r.code )
753                 return action.code
754
755         #
756         # File the mail into the specified folder.
757         #
758         if isinstance( action , FileToFolderAction ) :
759                 #
760                 # We got a folder name (or None for default folder.)
761                 #
762                 folder = action.folder
763                 pseudoName = 'INBOX'
764                 if folder : pseudoName += '.' + folder
765
766                 #
767                 # Try to deliver in the named folder
768                 #
769                 rc = deliverTo( g_user , folder )
770                 #
771                 # If we get an error code, then we deliver the mail to default folder,
772                 # except if the error was "data error" or if we already tried to deliver
773                 # it to default folder.
774                 #
775                 if rc not in [ EX_OK , EX_DATAERR ] and folder != None :
776                         logMessage( 'Error delivering to folder %s of user `%s\'.' % ( pseudoName , g_user ) )
777                         logMessage( 'Mail will go into default folder.' )
778                         rc = deliverTo( g_user )
779                 #
780                 # Check again.
781                 #
782                 # Here we also handle the case of EX_DATAERR not handled above.
783                 #
784                 if rc != EX_OK :
785                         logMessage( 'Error delivering to default folder of user `%s\'.' % ( g_user , ) )
786                         #
787                         # Since it's still not ok, backup the mail.
788                         #
789                         backup( g_user )
790                         #
791                         # All errors code different from "data error" are translated to
792                         # "no user" error code.
793                         #
794                         # FIXME: Why?!
795                         #
796                         if rc != EX_DATAERR :
797                                 rc = EX_NOUSER
798
799                 #
800                 # FIXME: !!!!!
801                 #
802                 rc = EX_OK
803
804                 return rc
805         
806         raise Exception( 'Unknown action type' )
807
808 #
809 #
810 #
811 def process() :
812
813         readMail()
814         try :
815                 checkForUser()
816                 checkForLargeMail()
817                 parseMail()
818                 return dispatchMail()
819         except SystemExit :
820                 raise
821         except :
822                 logMessage( getTraceBack() )
823                 return EX_DATAERR
824
825 #-----------------------------------------------------------------------------
826
827 def checkConfiguration( filename ) :
828
829         try :
830                 confparser.readConfiguration( filename )
831         except Exception , e :
832                 print e
833
834 #-----------------------------------------------------------------------------
835
836 def usage() :
837
838         print '''Usage: mail.filter [OPTIONS] username < EMAIL
839
840  -h, --help                Print this help.
841  -v, --verbose             Verbose mode, output log to stdout.
842  -t, --test                Test mode. Don't feed the mail to Cyrus, don't
843                            do backup, and don't write anything into log file.
844  -l, --log=FILENAME        Set log filename.
845  -r, --rules=DIRECTORY     Directory where are located users rules.
846  -c, --check-config=FILENAME
847                            Check syntax and structure of configuration file
848                            FILENAME.
849      --disable-backup      Disable backup.
850 '''
851
852         print 'Current paths are:\n'
853         print '    spamprobe  : %s' % g_pathSpamProbe
854         print '    clamd      : %s' % g_pathClamdscan
855         print '    deliver    : %s' % g_pathCyrusDeliver
856         print '    log        : %s' % g_pathLog
857         print
858         print 'Current directories are:\n'
859         print '    spamprobedb: %s' % g_pathSpamProbeDb
860         print '    rules      : %s' % g_directoryRules
861         print '''
862 Latest version is available from:
863
864     arch://arch.intra.tuxee.net/2004/mail-filter
865
866 Report bugs to <fj@tuxee.net>.'''
867
868 def main() :
869
870         global g_user, g_mail, g_mailText, g_copyLogToStdout, g_pathLog, g_directoryRules , g_testMode, g_backupDisabled
871
872         #--[ Command line ]-------------------------------------------------------
873
874         import getopt
875         try :
876                 _getopt = getopt.gnu_getopt
877         except :
878                 _getopt = getopt.getopt
879
880         try :
881                 options , parameters = \
882                         _getopt( sys.argv[ 1 : ] ,
883                                 'hvtl:r:c:' ,
884                                 ( 'help' , 'verbose' , 'test' , 'log=' , 'rules=' , 'check-config=' , 'disable-backup' ) )
885         except getopt.GetoptError , e :
886                 myName = sys.argv[ 0 ].split( '/' )[ -1 ]
887                 print '%s: %s' % ( myName , e[ 0 ] )
888                 print 'Try `%s --help\' for more information.' % myName
889                 sys.exit( 1 )
890
891         for option , argument in options :
892                 if option in [ '-h' , '--help' ] :
893                         usage()
894                         sys.exit( 0 )
895                 elif option in [ '-v' , '--verbose' ] :
896                         g_copyLogToStdout = True
897                 elif option in [ '-t' , '--test' ] :
898                         g_testMode = True
899                 elif option in [ '-l' , '--log' ] :
900                         g_pathLog = argument
901                 elif option in [ '-r' , '--rules' ] :
902                         g_directoryRules = argument
903                 elif option in [ '-c' , '--check-config' ] :
904                         checkConfiguration( argument )
905                         sys.exit( 0 )
906                 elif option in [ '--disable-backup' ] :
907                         g_backupDisabled = True
908
909         #
910         # At most one parameter expected.
911         #
912         if len( parameters ) > 1 :
913                 #
914                 # We just log a error message. We continue to proceed
915                 # to not lost the mail !
916                 #
917                 logMessage( 'Warning: Expected only one user name.' )
918
919         if parameters :
920                 g_user = parameters[ 0 ]
921
922         #--[ Core ]---------------------------------------------------------------
923
924         logMessage( 'Running mail.filter for user `%s\'.' % g_user )
925
926         return process()
927
928 if __name__ == '__main__' :
929         sys.exit( main() )