2 #***************************************************************************
3 #* Copyright (C) 2004-2008 polytechnique.org *
4 #* http://opensource.polytechnique.org/ *
6 #* This program is free software; you can redistribute it and/or modify *
7 #* it under the terms of the GNU General Public License as published by *
8 #* the Free Software Foundation; either version 2 of the License, or *
9 #* (at your option) any later version. *
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. *
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., *
19 #* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 #***************************************************************************
22 import base64
, MySQLdb
, os
, getopt
, sys
, sha
, signal
, re
, shutil
, ConfigParser
23 import MySQLdb
.converters
26 sys
.path
.append('/usr/lib/mailman/bin')
28 from pwd
import getpwnam
29 from grp
import getgrnam
31 from SimpleXMLRPCServer
import SimpleXMLRPCServer
32 from SimpleXMLRPCServer
import SimpleXMLRPCRequestHandler
35 from Mailman
import MailList
36 from Mailman
import Utils
37 from Mailman
import Message
38 from Mailman
import Errors
39 from Mailman
import mm_cfg
40 from Mailman
import i18n
41 from Mailman
.UserDesc
import UserDesc
42 from Mailman
.ListAdmin
import readMessage
43 from email
.Iterators
import typed_subpart_iterator
44 from threading
import Lock
46 class AuthFailed(Exception): pass
48 ################################################################################
52 #------------------------------------------------
54 config
= ConfigParser
.ConfigParser()
55 config
.read(os
.path
.dirname(__file__
)+'/../configs/platal.ini')
56 config
.read(os
.path
.dirname(__file__
)+'/../configs/platal.conf')
58 def get_config(sec
, val
, default
=None):
60 return config
.get(sec
, val
)[1:-1]
61 except ConfigParser
.NoOptionError
, e
:
68 MYSQL_USER
= get_config('Core', 'dbuser')
69 MYSQL_PASS
= get_config('Core', 'dbpwd')
71 PLATAL_DOMAIN
= get_config('Mail', 'domain')
72 PLATAL_DOMAIN2
= get_config('Mail', 'domain2', '')
74 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
75 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
77 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
78 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
80 ################################################################################
84 #------------------------------------------------
85 # Manage Basic authentication
88 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
90 """XMLRPC Request Handler
91 This request handler is used to provide BASIC HTTP user authentication.
92 It first overloads the do_POST() function, authenticates the user, then
93 calls the super.do_POST().
95 Moreover, we override _dispatch, so that we call functions with as first
96 argument a UserDesc taken from the database, containing name, email and perms
99 def _get_function(self
, method
):
101 # check to see if a matching function has been registered
102 return self
.server
.funcs
[method
]
104 raise Exception('method "%s" is not supported' % method
)
107 def _dispatch(self
, method
, params
):
108 new_params
= list(params
)
109 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
113 _
, auth
= self
.headers
["authorization"].split()
114 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
115 vhost
= self
.path
.split('/')[1].lower()
116 self
.data
= self
.getUser(uid
, md5
, vhost
)
117 if self
.data
is None:
119 # Call super.do_POST() to do the actual work
120 SimpleXMLRPCRequestHandler
.do_POST(self
)
122 self
.send_response(401)
125 def getUser(self
, uid
, md5
, vhost
):
126 res
= mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms
127 FROM auth_user_md5 AS u
128 INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' )
129 WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user')
130 LIMIT 1""" %( uid
, md5
) )
132 name
, forlife
, perms
= res
133 if vhost
!= PLATAL_DOMAIN
:
134 res
= mysql_fetchone ("""SELECT uid
135 FROM groupex.membres AS m
136 INNER JOIN groupex.asso AS a ON (m.asso_id = a.id)
137 WHERE perms='admin' AND uid='%s' AND mail_domain='%s'""" %( uid
, vhost
) )
138 if res
: perms
= 'admin'
139 userdesc
= UserDesc(forlife
+'@'+PLATAL_DOMAIN
, name
, None, 0)
140 return (userdesc
, perms
, vhost
)
144 ################################################################################
148 #-------------------------------------------------------------------------------
153 db
= MySQLdb
.connect(
157 unix_socket
='/var/run/mysqld/mysqld.sock')
161 def mysql_fetchone(query
):
166 if int(mysql
.rowcount
) > 0:
167 ret
= mysql
.fetchone()
172 def is_admin_on(userdesc
, perms
, mlist
):
173 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
176 def quote(s
, is_header
=False):
178 h
= Utils
.oneline(s
, 'iso-8859-1')
181 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
182 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
184 def to_forlife(email
):
186 mbox
, fqdn
= email
.split('@')
190 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
191 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom)
192 FROM auth_user_md5 AS u
193 INNER JOIN aliases AS f ON (f.id=u.user_id AND f.type='a_vie')
194 INNER JOIN aliases AS a ON (a.id=u.user_id AND a.alias='%s' AND a.type!='homonyme')
195 WHERE u.perms IN ('admin', 'user')
196 LIMIT 1""" %( PLATAL_DOMAIN
, mbox
) )
201 return (email
.lower(), mbox
)
204 # see /usr/lib/mailman/bin/rmlist
206 def remove_it(listname
, filename
):
207 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
209 elif os
.path
.isdir(filename
):
210 shutil
.rmtree(filename
)
216 def has_annotation(method
, name
):
217 """ Check if the method contains the given annoation.
219 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
221 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
222 """Dispatch the call to the right handler.
223 This function checks the options of the called method the set the environment of the call.
224 The dispatcher uses method annotation (special tokens in the documentation of the method) to
225 guess the requested environment:
226 @mlist: the handler requires a mlist object instead of the vhost/listname couple
227 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
228 @edit: the handler edit the mlist (@mlist MUST be specified)
229 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
230 @root: the handler requires site admin rights
233 if has_annotation(method
, "root") and perms
!= "admin":
235 if has_annotation(method
, "mlist"):
238 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
239 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
241 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
242 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
244 return method(userdesc
, perms
, mlist
, *arg
)
246 return method(userdesc
, perms
, vhost
, *arg
)
251 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
252 """Call the given method after locking the mlist.
256 ret
= method(userdesc
, perms
, mlist
, *arg
)
264 # TODO: use finally when switching to python 2.5
266 #-------------------------------------------------------------------------------
270 def is_subscription_pending(userdesc
, perms
, mlist
, edit
):
271 for id in mlist
.GetSubscriptionIds():
272 if userdesc
.address
== mlist
.GetRecord(id)[1]:
276 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
277 members
= mlist
.getRegularMemberKeys()
278 is_member
= userdesc
.address
in members
279 is_owner
= userdesc
.address
in mlist
.owner
280 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
282 if not is_member
and (mlist
.subscribe_policy
> 1):
283 is_pending
= list_call_locked(userdesc
, perms
, mlist
, is_subscription_pending
, False)
287 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
289 'list' : mlist
.real_name
,
290 'addr' : mlist
.real_name
.lower() + '@' + host
,
292 'desc' : quote(mlist
.description
),
293 'info' : quote(mlist
.info
),
294 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
295 'ins' : mlist
.subscribe_policy
> 1,
296 'priv' : 1-mlist
.advertised
,
297 'sub' : 2*is_member
+ is_pending
,
299 'nbsub': len(members
)
301 return (details
, members
)
304 def get_options(userdesc
, perms
, mlist
, opts
):
305 """ Get the options of a list.
310 for (k
, v
) in mlist
.__dict__
.iteritems():
313 options
[k
] = quote(v
)
315 details
= get_list_info(userdesc
, perms
, mlist
)[0]
316 return (details
, options
)
318 def set_options(userdesc
, perms
, mlist
, vals
):
319 """ Set the options of a list.
324 for (k
, v
) in vals
.iteritems():
327 if k
== 'default_member_moderation':
328 for member
in mlist
.getMembers():
329 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
330 t
= type(mlist
.__dict__
[k
])
331 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
332 elif t
is int: mlist
.__dict__
[k
] = int(v
)
333 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
334 else: mlist
.__dict__
[k
] = v
337 #-------------------------------------------------------------------------------
338 # users procedures for [ index.php ]
341 def get_lists(userdesc
, perms
, vhost
, email
=None):
342 """ List available lists for the given vhost
347 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
348 prefix
= vhost
.lower()+VHOST_SEP
349 names
= Utils
.list_names()
353 if not name
.startswith(prefix
):
356 mlist
= MailList
.MailList(name
, lock
=0)
360 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
361 result
.append(details
)
366 def subscribe(userdesc
, perms
, mlist
):
367 """ Subscribe to a list.
371 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
372 mlist
.ApprovedAddMember(userdesc
)
377 mlist
.AddMember(userdesc
)
378 except Errors
.MMNeedApproval
:
382 def unsubscribe(userdesc
, perms
, mlist
):
383 """ Unsubscribe from a list
387 mlist
.ApprovedDeleteMember(userdesc
.address
)
390 #-------------------------------------------------------------------------------
391 # users procedures for [ index.php ]
394 def get_name(member
):
396 return quote(mlist
.getMemberName(member
))
400 def get_members(userdesc
, perms
, mlist
):
401 """ List the members of a list.
404 details
, members
= get_list_info(userdesc
, perms
, mlist
)
406 members
= map(lambda member
: (get_name(member
), member
), members
)
407 return (details
, members
, mlist
.owner
)
410 #-------------------------------------------------------------------------------
411 # users procedures for [ trombi.php ]
414 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
415 """ Get a range of members of the list.
418 members
= get_members(userdesc
, perms
, mlist
)[1]
419 i
= int(page
) * int(nb_per_page
)
420 return (len(members
), members
[i
:i
+int(nb_per_page
)])
422 def get_owners(userdesc
, perms
, mlist
):
423 """ Get the owners of the list.
426 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
427 return (details
, owners
)
430 #-------------------------------------------------------------------------------
431 # owners procedures [ admin.php ]
434 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
435 """ Replace the address of a member by another one.
440 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
443 def mass_subscribe(userdesc
, perms
, mlist
, users
):
444 """ Add a list of users to the list.
449 members
= mlist
.getRegularMemberKeys()
453 email
, name
= to_forlife(user
)
454 if ( email
is None ) or ( email
in members
):
456 userd
= UserDesc(email
, name
, None, 0)
457 mlist
.ApprovedAddMember(userd
)
458 added
.append( (quote(userd
.fullname
), userd
.address
) )
461 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
462 """ Remove a list of users from the list.
467 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
470 def add_owner(userdesc
, perms
, mlist
, user
):
471 """ Add a owner to the list.
476 email
= to_forlife(user
)[0]
479 if email
not in mlist
.owner
:
480 mlist
.owner
.append(email
)
483 def del_owner(userdesc
, perms
, mlist
, user
):
484 """ Remove a owner of the list.
489 if len(mlist
.owner
) < 2:
491 mlist
.owner
.remove(user
)
494 #-------------------------------------------------------------------------------
495 # owners procedures [ admin.php ]
498 def get_pending_ops(userdesc
, perms
, mlist
):
499 """ Get the list of operation waiting for an action from the owners.
507 for id in mlist
.GetSubscriptionIds():
508 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
510 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
515 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
516 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
518 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
521 for id in mlist
.GetHeldMessageIds():
522 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
523 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
525 size
= os
.path
.getsize(fpath
)
527 if e
.errno
<> errno
.ENOENT
: raise
530 msg
= readMessage(fpath
)
531 fromX
= msg
.has_key("X-Org-Mail")
536 'sender': quote(sender
, True),
538 'subj' : quote(subject
, True),
546 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
547 """ Handle a moderation request.
552 mlist
.HandleRequest(int(id), int(value
), comment
)
555 def get_pending_sub(userdesc
, perms
, mlist
, id):
556 """ Get informations about a given subscription moderation.
563 if id in mlist
.GetSubscriptionIds():
564 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
566 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
567 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
569 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
572 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
573 """ Get informations about a given mail moderation.
578 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
579 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
580 size
= os
.path
.getsize(fpath
)
581 msg
= readMessage(fpath
)
584 return quote(str(msg
))
587 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
588 c
= part
.get_payload()
589 if c
is not None: results_plain
.append (c
)
590 results_plain
= map(lambda x
: quote(x
), results_plain
)
591 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
592 c
= part
.get_payload()
593 if c
is not None: results_html
.append (c
)
594 results_html
= map(lambda x
: quote(x
), results_html
)
596 'sender': quote(sender
, True),
598 'subj' : quote(subject
, True),
600 'parts_plain' : results_plain
,
601 'parts_html': results_html
}
603 #-------------------------------------------------------------------------------
604 # owner options [ options.php ]
607 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
608 'default_member_moderation', 'generic_nonmember_action', 'info', \
609 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
612 def get_owner_options(userdesc
, perms
, mlist
):
613 """ Get the owner options of a list.
617 return get_options(userdesc
, perms
, mlist
, owner_opts
)
619 def set_owner_options(userdesc
, perms
, mlist
, values
):
620 """ Set the owner options of a list.
625 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
627 def add_to_wl(userdesc
, perms
, mlist
, addr
):
628 """ Add addr to the whitelist
633 mlist
.accept_these_nonmembers
.append(addr
)
636 def del_from_wl(userdesc
, perms
, mlist
, addr
):
637 """ Remove an address from the whitelist
642 mlist
.accept_these_nonmembers
.remove(addr
)
645 def get_bogo_level(userdesc
, perms
, mlist
):
646 """ Compute bogo level from the filtering rules set up on the list.
650 if len(mlist
.header_filter_rules
) == 0:
657 # The first rule filters Unsure mails
658 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
662 # Check the other rules:
663 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
664 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
665 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
667 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
670 action
= mlist
.header_filter_rules
[filterbase
][1]
671 if action
== mm_cfg
.HOLD
:
673 elif action
== mm_cfg
.DISCARD
:
675 return (filterlevel
<< 1) + unsurelevel
677 def set_bogo_level(userdesc
, perms
, vhost
, listname
, level
):
678 """ Set filter to the specified level.
685 # The level is a combination of a spam filtering level and unsure filtering level
686 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
687 # - the spam filtering level is a number growing with filtering strength
688 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
689 bogolevel
= int(level
)
690 filterlevel
= bogolevel
>> 1
691 unsurelevel
= bogolevel
& 1
693 # Set up unusre filtering
695 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
697 # Set up spam filtering
699 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
700 elif filterlevel
is 2:
701 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
702 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
703 elif filterlevel
is 3:
704 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
707 if mlist
.header_filter_rules
!= hfr
:
708 mlist
.header_filter_rules
= hfr
711 #-------------------------------------------------------------------------------
712 # admin procedures [ soptions.php ]
715 admin_opts
= [ 'advertised', 'archive', \
716 'max_message_size', 'msg_footer', 'msg_header']
718 def get_admin_options(userdesc
, perms
, mlist
):
719 """ Get administrator options.
723 return get_options(userdesc
, perms
, mlist
, admin_opts
)
725 def set_admin_options(userdesc
, perms
, mlist
, values
):
726 """ Set administrator options.
731 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
733 #-------------------------------------------------------------------------------
734 # admin procedures [ check.php ]
738 'acceptable_aliases' : '',
739 'admin_immed_notify' : True,
740 'administrivia' : True,
741 'anonymous_list' : False,
742 'autorespond_admin' : False,
743 'autorespond_postings' : False,
744 'autorespond_requests' : False,
745 'available_languages' : ['fr'],
747 'bounce_matching_headers' : '',
748 'bounce_processing' : False,
749 'convert_html_to_plaintext' : False,
750 'digestable' : False,
751 'digest_is_default' : False,
752 'discard_these_nonmembers' : [],
754 'encode_ascii_prefixes' : 2,
755 'filter_content' : False,
756 'first_strip_reply_to' : False,
757 'forward_auto_discards' : True,
758 'hold_these_nonmembers' : [],
759 'host_name' : 'listes.polytechnique.org',
760 'include_list_post_header' : False,
761 'include_rfc2369_headers' : False,
762 'max_num_recipients' : 0,
763 'new_member_options' : 256,
764 'nondigestable' : True,
765 'obscure_addresses' : True,
766 'preferred_language' : 'fr',
767 'reject_these_nonmembers' : [],
768 'reply_goes_to_list' : 0,
769 'reply_to_address' : '',
770 'require_explicit_destination' : False,
771 'send_reminders' : 0,
772 'send_welcome_msg' : True,
773 'topics_enabled' : False,
774 'umbrella_list' : False,
775 'unsubscribe_policy' : 0,
778 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
780 for (k
, v
) in check_opts
.iteritems():
781 if mlist
.__dict__
[k
] != v
:
782 options
[k
] = v
, mlist
.__dict__
[k
]
783 if correct
: mlist
.__dict__
[k
] = v
784 if mlist
.real_name
.lower() != listname
:
785 options
['real_name'] = listname
, mlist
.real_name
786 if correct
: mlist
.real_name
= listname
787 details
= get_list_info(userdesc
, perms
, mlist
)[0]
788 return (details
, options
)
791 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
795 listname
= listname
.lower()
796 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
798 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
800 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
802 #-------------------------------------------------------------------------------
803 # super-admin procedures
806 def get_all_lists(userdesc
, perms
, vhost
):
807 """ Get all the list for the given vhost
809 prefix
= vhost
.lower()+VHOST_SEP
810 names
= Utils
.list_names()
814 if not name
.startswith(prefix
):
816 result
.append(name
.replace(prefix
, ''))
819 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
820 """ Create a new list.
823 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
824 if Utils
.list_exists(name
):
829 email
= to_forlife(o
)[0]
830 if email
is not None:
835 mlist
= MailList
.MailList()
837 oldmask
= os
.umask(002)
838 pw
= sha
.new('foobar').hexdigest()
841 mlist
.Create(name
, owner
[0], pw
)
845 mlist
.real_name
= listname
846 mlist
.host_name
= 'listes.polytechnique.org'
847 mlist
.description
= desc
849 mlist
.advertised
= int(advertise
) is 0
850 mlist
.default_member_moderation
= int(modlevel
) is 2
851 mlist
.generic_nonmember_action
= int(modlevel
) > 0
852 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
853 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
857 mlist
.subject_prefix
= '['+listname
+'] '
858 mlist
.max_message_size
= 0
860 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
861 mlist
.msg_footer
= "_______________________________________________\n" \
862 + "Liste de diffusion %(real_name)s\n" \
863 + "http://listes.polytechnique.org/members/" + inverted_listname
865 mlist
.header_filter_rules
= []
866 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
867 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
871 if ON_CREATE_CMD
!= '':
872 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
875 check_options(userdesc
, perms
, mlist
, True)
876 mass_subscribe(userdesc
, perms
, mlist
, members
)
878 # avoid the "-1 mail to moderate" bug
879 mlist
= MailList
.MailList(name
)
880 mlist
._UpdateRecords()
888 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
893 lname
= mlist
.internal_name()
895 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
896 # remove stalled locks
897 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
898 fn_lname
= filename
.split('.')[0]
899 if fn_lname
== lname
:
900 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
904 os
.path
.join('archives', 'private', lname
),
905 os
.path
.join('archives', 'private', lname
+'.mbox'),
906 os
.path
.join('archives', 'public', lname
),
907 os
.path
.join('archives', 'public', lname
+'.mbox')
909 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
912 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
913 """ Remove a user from all the lists.
916 if not del_from_promo
:
917 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
918 for list in Utils
.list_names():
922 mlist
= MailList
.MailList(list, lock
=0)
927 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
935 #-------------------------------------------------------------------------------
938 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
939 allow_reuse_address
= True
941 ################################################################################
945 #-------------------------------------------------------------------------------
946 # use Mailman user and group (not root)
947 # fork in background if asked to
950 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
951 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
954 os
.setregid(gid
, gid
)
955 os
.setreuid(uid
, uid
)
957 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
959 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
962 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
964 if o
== '-f' and os
.fork():
967 i18n
.set_language('fr')
971 #-------------------------------------------------------------------------------
974 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
977 server
.register_function(get_lists
)
978 server
.register_function(subscribe
)
979 server
.register_function(unsubscribe
)
981 server
.register_function(get_members
)
983 server
.register_function(get_members_limit
)
984 server
.register_function(get_owners
)
986 server
.register_function(replace_email
)
987 server
.register_function(mass_subscribe
)
988 server
.register_function(mass_unsubscribe
)
989 server
.register_function(add_owner
)
990 server
.register_function(del_owner
)
992 server
.register_function(get_pending_ops
)
993 server
.register_function(handle_request
)
994 server
.register_function(get_pending_sub
)
995 server
.register_function(get_pending_mail
)
997 server
.register_function(get_owner_options
)
998 server
.register_function(set_owner_options
)
999 server
.register_function(add_to_wl
)
1000 server
.register_function(del_from_wl
)
1001 server
.register_function(get_bogo_level
)
1002 server
.register_function(set_bogo_level
)
1004 server
.register_function(get_admin_options
)
1005 server
.register_function(set_admin_options
)
1007 server
.register_function(check_options
)
1009 server
.register_function(get_all_lists
)
1010 server
.register_function(create_list
)
1011 server
.register_function(delete_list
)
1013 server
.register_function(kill
)
1015 server
.serve_forever()
1017 # vim:set et sw=4 sts=4 sws=4: