2 #***************************************************************************
3 #* Copyright (C) 2003-2010 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
:
63 sys
.stderr
.write('%s\n' % str
(e
))
68 MYSQL_USER
= get_config('Core', 'dbuser')
69 MYSQL_PASS
= get_config('Core', 'dbpwd')
70 MYSQL_DB
= get_config('Core', 'dbdb')
72 PLATAL_DOMAIN
= get_config('Mail', 'domain')
73 PLATAL_DOMAIN2
= get_config('Mail', 'domain2', '')
74 sys
.stderr
.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN
)
76 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
77 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
79 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
80 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
82 ################################################################################
86 #------------------------------------------------
87 # Manage Basic authentication
90 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
92 """XMLRPC Request Handler
93 This request handler is used to provide BASIC HTTP user authentication.
94 It first overloads the do_POST() function, authenticates the user, then
95 calls the super.do_POST().
97 Moreover, we override _dispatch, so that we call functions with as first
98 argument a UserDesc taken from the database, containing name, email and perms
101 def _get_function(self
, method
):
103 # check to see if a matching function has been registered
104 return self
.server
.funcs
[method
]
106 raise Exception('method "%s" is not supported' % method
)
108 def is_rpc_path_valid(self
):
111 def _dispatch(self
, method
, params
):
112 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
116 _
, auth
= self
.headers
["authorization"].split()
117 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
118 vhost
= self
.path
.split('/')[1].lower()
119 self
.data
= self
.getUser(uid
, md5
, vhost
)
120 if self
.data
is None:
122 # Call super.do_POST() to do the actual work
123 SimpleXMLRPCRequestHandler
.do_POST(self
)
125 self
.send_response(401)
128 def getUser(self
, uid
, md5
, vhost
):
129 res
= mysql_fetchone ("""SELECT a.full_name, aa.alias, IF (a.is_admin, 'admin', NULL)
131 INNER JOIN aliases AS aa ON (a.uid = aa.uid AND aa.type = 'a_vie')
132 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
136 name
, forlife
, perms
= res
137 if vhost
!= PLATAL_DOMAIN
:
138 res
= mysql_fetchone ("""SELECT m.uid
139 FROM group_members AS m
140 INNER JOIN groups AS g ON (m.asso_id = g.id)
141 WHERE perms = 'admin' AND uid = '%s' AND mail_domain = '%s'""" \
145 userdesc
= UserDesc(forlife
+'@'+PLATAL_DOMAIN
, name
, None, 0)
146 return (userdesc
, perms
, vhost
)
150 ################################################################################
154 #-------------------------------------------------------------------------------
159 db
= MySQLdb
.connect(
163 unix_socket
='/var/run/mysqld/mysqld.sock')
167 def mysql_fetchone(query
):
172 if int(mysql
.rowcount
) > 0:
173 ret
= mysql
.fetchone()
178 def is_admin_on(userdesc
, perms
, mlist
):
179 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
182 def quote(s
, is_header
=False):
184 h
= Utils
.oneline(s
, 'iso-8859-1')
187 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
188 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
190 def to_forlife(email
):
192 mbox
, fqdn
= email
.split('@')
196 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
197 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
199 INNER JOIN aliases AS f ON (f.uid = a.uid AND f.type = 'a_vie')
200 INNER JOIN aliases AS aa ON (aa.uid = a.uid AND aa.alias = '%s'
201 AND a.type != 'homonyme')
202 WHERE a.state = 'active'
204 %
(PLATAL_DOMAIN
, mbox
))
209 return (email
.lower(), mbox
)
212 # see /usr/lib/mailman/bin/rmlist
214 def remove_it(listname
, filename
):
215 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
217 elif os
.path
.isdir(filename
):
218 shutil
.rmtree(filename
)
224 def has_annotation(method
, name
):
225 """ Check if the method contains the given annoation.
227 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
229 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
230 """Dispatch the call to the right handler.
231 This function checks the options of the called method the set the environment of the call.
232 The dispatcher uses method annotation (special tokens in the documentation of the method) to
233 guess the requested environment:
234 @mlist: the handler requires a mlist object instead of the vhost/listname couple
235 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
236 @edit: the handler edit the mlist (@mlist MUST be specified)
237 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
238 @root: the handler requires site admin rights
241 if has_annotation(method
, "root") and perms
!= "admin":
243 if has_annotation(method
, "mlist"):
246 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
247 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
249 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
250 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
252 return method(userdesc
, perms
, mlist
, *arg
)
254 return method(userdesc
, perms
, vhost
, *arg
)
256 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
260 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
261 """Call the given method after locking the mlist.
265 ret
= method(userdesc
, perms
, mlist
, *arg
)
271 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
274 # TODO: use finally when switching to python 2.5
276 #-------------------------------------------------------------------------------
280 def is_subscription_pending(userdesc
, perms
, mlist
):
281 for id in mlist
.GetSubscriptionIds():
282 if userdesc
.address
== mlist
.GetRecord(id)[1]:
286 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
287 members
= mlist
.getRegularMemberKeys()
288 is_member
= userdesc
.address
in members
289 is_owner
= userdesc
.address
in mlist
.owner
290 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
292 if not is_member
and (mlist
.subscribe_policy
> 1):
293 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
297 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
299 'list' : mlist
.real_name
,
300 'addr' : mlist
.real_name
.lower() + '@' + host
,
302 'desc' : quote(mlist
.description
),
303 'info' : quote(mlist
.info
),
304 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
305 'ins' : mlist
.subscribe_policy
> 1,
306 'priv' : 1-mlist
.advertised
,
307 'sub' : 2*is_member
+ is_pending
,
309 'nbsub': len(members
)
311 return (details
, members
)
314 def get_options(userdesc
, perms
, mlist
, opts
):
315 """ Get the options of a list.
320 for (k
, v
) in mlist
.__dict__
.iteritems():
323 options
[k
] = quote(v
)
325 details
= get_list_info(userdesc
, perms
, mlist
)[0]
326 return (details
, options
)
328 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
329 for (k
, v
) in vals
.iteritems():
332 if k
== 'default_member_moderation':
333 for member
in mlist
.getMembers():
334 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
335 t
= type(mlist
.__dict__
[k
])
336 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
337 elif t
is int: mlist
.__dict__
[k
] = int(v
)
338 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
339 else: mlist
.__dict__
[k
] = v
342 #-------------------------------------------------------------------------------
343 # users procedures for [ index.php ]
346 def get_lists(userdesc
, perms
, vhost
, email
=None):
347 """ List available lists for the given vhost
352 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
353 prefix
= vhost
.lower()+VHOST_SEP
354 names
= Utils
.list_names()
358 if not name
.startswith(prefix
):
361 mlist
= MailList
.MailList(name
, lock
=0)
365 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
366 result
.append(details
)
368 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
372 def subscribe(userdesc
, perms
, mlist
):
373 """ Subscribe to a list.
377 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
378 mlist
.ApprovedAddMember(userdesc
)
383 mlist
.AddMember(userdesc
)
384 except Errors
.MMNeedApproval
:
388 def unsubscribe(userdesc
, perms
, mlist
):
389 """ Unsubscribe from a list
393 mlist
.ApprovedDeleteMember(userdesc
.address
)
396 #-------------------------------------------------------------------------------
397 # users procedures for [ index.php ]
400 def get_name(member
):
402 return quote(mlist
.getMemberName(member
))
406 def get_members(userdesc
, perms
, mlist
):
407 """ List the members of a list.
410 details
, members
= get_list_info(userdesc
, perms
, mlist
)
412 members
= map(lambda member
: (get_name(member
), member
), members
)
413 return (details
, members
, mlist
.owner
)
416 #-------------------------------------------------------------------------------
417 # users procedures for [ trombi.php ]
420 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
421 """ Get a range of members of the list.
424 members
= get_members(userdesc
, perms
, mlist
)[1]
425 i
= int(page
) * int(nb_per_page
)
426 return (len(members
), members
[i
:i
+int(nb_per_page
)])
428 def get_owners(userdesc
, perms
, mlist
):
429 """ Get the owners of the list.
432 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
433 return (details
, owners
)
436 #-------------------------------------------------------------------------------
437 # owners procedures [ admin.php ]
440 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
441 """ Replace the address of a member by another one.
446 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
449 def mass_subscribe(userdesc
, perms
, mlist
, users
):
450 """ Add a list of users to the list.
455 members
= mlist
.getRegularMemberKeys()
458 email
, name
= to_forlife(user
)
459 if ( email
is None ) or ( email
in members
):
461 userd
= UserDesc(email
, name
, None, 0)
462 mlist
.ApprovedAddMember(userd
)
463 added
.append( (quote(userd
.fullname
), userd
.address
) )
466 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
467 """ Remove a list of users from the list.
472 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
475 def add_owner(userdesc
, perms
, mlist
, user
):
476 """ Add a owner to the list.
481 email
= to_forlife(user
)[0]
484 if email
not in mlist
.owner
:
485 mlist
.owner
.append(email
)
488 def del_owner(userdesc
, perms
, mlist
, user
):
489 """ Remove a owner of the list.
494 if len(mlist
.owner
) < 2:
496 mlist
.owner
.remove(user
)
499 #-------------------------------------------------------------------------------
500 # owners procedures [ admin.php ]
503 def get_pending_ops(userdesc
, perms
, mlist
):
504 """ Get the list of operation waiting for an action from the owners.
512 for id in mlist
.GetSubscriptionIds():
513 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
515 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
520 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
521 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
523 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
526 for id in mlist
.GetHeldMessageIds():
527 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
528 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
530 size
= os
.path
.getsize(fpath
)
532 if e
.errno
<> errno
.ENOENT
: raise
535 msg
= readMessage(fpath
)
536 fromX
= msg
.has_key("X-Org-Mail")
541 'sender': quote(sender
, True),
543 'subj' : quote(subject
, True),
551 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
552 """ Handle a moderation request.
557 mlist
.HandleRequest(int(id), int(value
), comment
)
560 def get_pending_sub(userdesc
, perms
, mlist
, id):
561 """ Get informations about a given subscription moderation.
568 if id in mlist
.GetSubscriptionIds():
569 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
571 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
572 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
574 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
577 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
578 """ Get informations about a given mail moderation.
583 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
584 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
585 size
= os
.path
.getsize(fpath
)
586 msg
= readMessage(fpath
)
589 return quote(str(msg
))
592 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
593 c
= part
.get_payload()
594 if c
is not None: results_plain
.append (c
)
595 results_plain
= map(lambda x
: quote(x
), results_plain
)
596 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
597 c
= part
.get_payload()
598 if c
is not None: results_html
.append (c
)
599 results_html
= map(lambda x
: quote(x
), results_html
)
601 'sender': quote(sender
, True),
603 'subj' : quote(subject
, True),
605 'parts_plain' : results_plain
,
606 'parts_html': results_html
}
608 #-------------------------------------------------------------------------------
609 # owner options [ options.php ]
612 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
613 'default_member_moderation', 'generic_nonmember_action', 'info', \
614 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
617 def get_owner_options(userdesc
, perms
, mlist
):
618 """ Get the owner options of a list.
622 return get_options(userdesc
, perms
, mlist
, owner_opts
)
624 def set_owner_options(userdesc
, perms
, mlist
, values
):
625 """ Set the owner options of a list.
630 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
632 def add_to_wl(userdesc
, perms
, mlist
, addr
):
633 """ Add addr to the whitelist
638 mlist
.accept_these_nonmembers
.append(addr
)
641 def del_from_wl(userdesc
, perms
, mlist
, addr
):
642 """ Remove an address from the whitelist
647 mlist
.accept_these_nonmembers
.remove(addr
)
650 def get_bogo_level(userdesc
, perms
, mlist
):
651 """ Compute bogo level from the filtering rules set up on the list.
655 if len(mlist
.header_filter_rules
) == 0:
662 # The first rule filters Unsure mails
663 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
667 # Check the other rules:
668 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
669 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
670 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
672 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
675 action
= mlist
.header_filter_rules
[filterbase
][1]
676 if action
== mm_cfg
.HOLD
:
678 elif action
== mm_cfg
.DISCARD
:
680 return (filterlevel
<< 1) + unsurelevel
682 def set_bogo_level(userdesc
, perms
, mlist
, level
):
683 """ Set filter to the specified level.
690 # The level is a combination of a spam filtering level and unsure filtering level
691 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
692 # - the spam filtering level is a number growing with filtering strength
693 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
694 bogolevel
= int(level
)
695 filterlevel
= bogolevel
>> 1
696 unsurelevel
= bogolevel
& 1
698 # Set up unusre filtering
700 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
702 # Set up spam filtering
704 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
705 elif filterlevel
is 2:
706 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
707 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
708 elif filterlevel
is 3:
709 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
712 if mlist
.header_filter_rules
!= hfr
:
713 mlist
.header_filter_rules
= hfr
716 #-------------------------------------------------------------------------------
717 # admin procedures [ soptions.php ]
720 admin_opts
= [ 'advertised', 'archive', \
721 'max_message_size', 'msg_footer', 'msg_header']
723 def get_admin_options(userdesc
, perms
, mlist
):
724 """ Get administrator options.
728 return get_options(userdesc
, perms
, mlist
, admin_opts
)
730 def set_admin_options(userdesc
, perms
, mlist
, values
):
731 """ Set administrator options.
736 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
738 #-------------------------------------------------------------------------------
739 # admin procedures [ check.php ]
743 'acceptable_aliases' : '',
744 'admin_immed_notify' : True,
745 'administrivia' : True,
746 'anonymous_list' : False,
747 'autorespond_admin' : False,
748 'autorespond_postings' : False,
749 'autorespond_requests' : False,
750 'available_languages' : ['fr'],
752 'bounce_matching_headers' : '',
753 'bounce_processing' : False,
754 'convert_html_to_plaintext' : False,
755 'digestable' : False,
756 'digest_is_default' : False,
757 'discard_these_nonmembers' : [],
759 'encode_ascii_prefixes' : 2,
760 'filter_content' : False,
761 'first_strip_reply_to' : False,
762 'forward_auto_discards' : True,
763 'hold_these_nonmembers' : [],
764 'host_name' : 'listes.polytechnique.org',
765 'include_list_post_header' : False,
766 'include_rfc2369_headers' : False,
767 'max_num_recipients' : 0,
768 'new_member_options' : 256,
769 'nondigestable' : True,
770 'obscure_addresses' : True,
771 'preferred_language' : 'fr',
772 'reject_these_nonmembers' : [],
773 'reply_goes_to_list' : 0,
774 'reply_to_address' : '',
775 'require_explicit_destination' : False,
776 'send_reminders' : 0,
777 'send_welcome_msg' : True,
778 'topics_enabled' : False,
779 'umbrella_list' : False,
780 'unsubscribe_policy' : 0,
783 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
785 for (k
, v
) in check_opts
.iteritems():
786 if mlist
.__dict__
[k
] != v
:
787 options
[k
] = v
, mlist
.__dict__
[k
]
788 if correct
: mlist
.__dict__
[k
] = v
789 if mlist
.real_name
.lower() != listname
:
790 options
['real_name'] = listname
, mlist
.real_name
791 if correct
: mlist
.real_name
= listname
795 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
799 listname
= listname
.lower()
800 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
802 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
804 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
806 #-------------------------------------------------------------------------------
807 # super-admin procedures
810 def get_all_lists(userdesc
, perms
, vhost
):
811 """ Get all the list for the given vhost
813 prefix
= vhost
.lower()+VHOST_SEP
814 names
= Utils
.list_names()
818 if not name
.startswith(prefix
):
820 result
.append(name
.replace(prefix
, ''))
823 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
824 """ Create a new list.
827 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
828 if Utils
.list_exists(name
):
833 email
= to_forlife(o
)[0]
834 if email
is not None:
839 mlist
= MailList
.MailList()
841 oldmask
= os
.umask(002)
842 pw
= sha
.new('foobar').hexdigest()
845 mlist
.Create(name
, owner
[0], pw
)
849 mlist
.real_name
= listname
850 mlist
.host_name
= 'listes.polytechnique.org'
851 mlist
.description
= desc
853 mlist
.advertised
= int(advertise
) is 0
854 mlist
.default_member_moderation
= int(modlevel
) is 2
855 mlist
.generic_nonmember_action
= int(modlevel
) > 0
856 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
857 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
861 mlist
.subject_prefix
= '['+listname
+'] '
862 mlist
.max_message_size
= 0
864 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
865 mlist
.msg_footer
= "_______________________________________________\n" \
866 + "Liste de diffusion %(real_name)s\n" \
867 + "http://listes.polytechnique.org/members/" + inverted_listname
869 mlist
.header_filter_rules
= []
870 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
871 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
873 if ON_CREATE_CMD
!= '':
874 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
877 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
878 mass_subscribe(userdesc
, perms
, mlist
, members
)
883 # avoid the "-1 mail to moderate" bug
884 mlist
= MailList
.MailList(name
)
886 mlist
._UpdateRecords()
892 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
897 lname
= mlist
.internal_name()
899 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
900 # remove stalled locks
901 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
902 fn_lname
= filename
.split('.')[0]
903 if fn_lname
== lname
:
904 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
908 os
.path
.join('archives', 'private', lname
),
909 os
.path
.join('archives', 'private', lname
+'.mbox'),
910 os
.path
.join('archives', 'public', lname
),
911 os
.path
.join('archives', 'public', lname
+'.mbox')
913 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
916 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
917 """ Remove a user from all the lists.
920 if not del_from_promo
:
921 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
922 for list in Utils
.list_names():
926 mlist
= MailList
.MailList(list, lock
=0)
931 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
939 #-------------------------------------------------------------------------------
942 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
943 allow_reuse_address
= True
945 ################################################################################
949 #-------------------------------------------------------------------------------
950 # use Mailman user and group (not root)
951 # fork in background if asked to
954 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
955 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
958 os
.setregid(gid
, gid
)
959 os
.setreuid(uid
, uid
)
961 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
963 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
966 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
968 if o
== '-f' and os
.fork():
971 i18n
.set_language('fr')
975 #-------------------------------------------------------------------------------
978 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
981 server
.register_function(get_lists
)
982 server
.register_function(subscribe
)
983 server
.register_function(unsubscribe
)
985 server
.register_function(get_members
)
987 server
.register_function(get_members_limit
)
988 server
.register_function(get_owners
)
990 server
.register_function(replace_email
)
991 server
.register_function(mass_subscribe
)
992 server
.register_function(mass_unsubscribe
)
993 server
.register_function(add_owner
)
994 server
.register_function(del_owner
)
996 server
.register_function(get_pending_ops
)
997 server
.register_function(handle_request
)
998 server
.register_function(get_pending_sub
)
999 server
.register_function(get_pending_mail
)
1001 server
.register_function(get_owner_options
)
1002 server
.register_function(set_owner_options
)
1003 server
.register_function(add_to_wl
)
1004 server
.register_function(del_from_wl
)
1005 server
.register_function(get_bogo_level
)
1006 server
.register_function(set_bogo_level
)
1008 server
.register_function(get_admin_options
)
1009 server
.register_function(set_admin_options
)
1011 server
.register_function(check_options
)
1013 server
.register_function(get_all_lists
)
1014 server
.register_function(create_list
)
1015 server
.register_function(delete_list
)
1017 server
.register_function(kill
)
1019 server
.serve_forever()
1021 # vim:set et sw=4 sts=4 sws=4: