2 #***************************************************************************
3 #* Copyright (C) 2003-2011 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
)
75 sys
.stderr
.write("MYSQL_DB = %s\n" % MYSQL_DB
)
77 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
78 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
80 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
81 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
83 ################################################################################
87 #------------------------------------------------
88 # Manage Basic authentication
91 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
93 """XMLRPC Request Handler
94 This request handler is used to provide BASIC HTTP user authentication.
95 It first overloads the do_POST() function, authenticates the user, then
96 calls the super.do_POST().
98 Moreover, we override _dispatch, so that we call functions with as first
99 argument a UserDesc taken from the database, containing name, email and perms
102 def _get_function(self
, method
):
104 # check to see if a matching function has been registered
105 return self
.server
.funcs
[method
]
107 raise Exception('method "%s" is not supported' % method
)
109 def is_rpc_path_valid(self
):
112 def _dispatch(self
, method
, params
):
113 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
117 _
, auth
= self
.headers
["authorization"].split()
118 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
119 vhost
= self
.path
.split('/')[1].lower()
120 self
.data
= self
.getUser(uid
, md5
, vhost
)
121 if self
.data
is None:
123 # Call super.do_POST() to do the actual work
124 SimpleXMLRPCRequestHandler
.do_POST(self
)
126 self
.send_response(401)
129 def getUser(self
, uid
, md5
, vhost
):
130 res
= mysql_fetchone ("""SELECT a.full_name, IF(aa.alias IS NULL, a.email, CONCAT(aa.alias, '@%s')),
131 IF (a.is_admin, 'admin',
132 IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL))
134 INNER JOIN account_types AS at ON (at.type = a.type)
135 LEFT JOIN aliases AS aa ON (a.uid = aa.uid AND aa.type = 'a_vie')
136 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
138 %
(PLATAL_DOMAIN
, uid
, md5
))
140 name
, forlife
, perms
= res
141 if vhost
!= PLATAL_DOMAIN
and perms
!= 'admin':
142 res
= mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists')
143 FROM group_members AS m
144 INNER JOIN groups AS g ON (m.asso_id = g.id)
145 WHERE uid = '%s' AND mail_domain = '%s'""" \
149 userdesc
= UserDesc(forlife
, name
, None, 0)
150 return (userdesc
, perms
, vhost
)
152 print >> sys
.stderr
, "no user found for uid: %s, passwd: %s" %
(uid
, md5
)
155 ################################################################################
159 #-------------------------------------------------------------------------------
164 db
= MySQLdb
.connect(
168 unix_socket
='/var/run/mysqld/mysqld.sock')
173 def mysql_fetchone(query
):
178 if int(mysql
.rowcount
) > 0:
179 ret
= mysql
.fetchone()
184 def is_admin_on(userdesc
, perms
, mlist
):
185 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
188 def quote(s
, is_header
=False):
190 h
= Utils
.oneline(s
, 'iso-8859-1')
193 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
194 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
196 def to_forlife(email
):
198 mbox
, fqdn
= email
.split('@')
202 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
203 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
205 INNER JOIN aliases AS f ON (f.uid = a.uid AND f.type = 'a_vie')
206 INNER JOIN aliases AS aa ON (aa.uid = a.uid AND aa.alias = '%s'
207 AND a.type != 'homonyme')
208 WHERE a.state = 'active'
210 %
(PLATAL_DOMAIN
, mbox
))
215 return (email
.lower(), mbox
)
218 # see /usr/lib/mailman/bin/rmlist
220 def remove_it(listname
, filename
):
221 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
223 elif os
.path
.isdir(filename
):
224 shutil
.rmtree(filename
)
230 def has_annotation(method
, name
):
231 """ Check if the method contains the given annoation.
233 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
235 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
236 """Dispatch the call to the right handler.
237 This function checks the options of the called method the set the environment of the call.
238 The dispatcher uses method annotation (special tokens in the documentation of the method) to
239 guess the requested environment:
240 @mlist: the handler requires a mlist object instead of the vhost/listname couple
241 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
242 @edit: the handler edit the mlist (@mlist MUST be specified)
243 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
244 @root: the handler requires site admin rights
247 print >> sys
.stderr
, "calling method: %s" % method
248 if has_annotation(method
, "root") and perms
!= "admin":
250 if has_annotation(method
, "mlist"):
251 listname
= str(arg
[0])
253 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
254 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
256 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
257 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
259 return method(userdesc
, perms
, mlist
, *arg
)
261 return method(userdesc
, perms
, vhost
, *arg
)
263 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
267 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
268 """Call the given method after locking the mlist.
272 ret
= method(userdesc
, perms
, mlist
, *arg
)
278 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
281 # TODO: use finally when switching to python 2.5
283 #-------------------------------------------------------------------------------
287 def is_subscription_pending(userdesc
, perms
, mlist
):
288 for id in mlist
.GetSubscriptionIds():
289 if userdesc
.address
== mlist
.GetRecord(id)[1]:
293 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
294 members
= mlist
.getRegularMemberKeys()
295 is_member
= userdesc
.address
in members
296 is_owner
= userdesc
.address
in mlist
.owner
297 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
299 if not is_member
and (mlist
.subscribe_policy
> 1):
300 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
304 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
306 'list' : mlist
.real_name
,
307 'addr' : mlist
.real_name
.lower() + '@' + host
,
309 'desc' : quote(mlist
.description
),
310 'info' : quote(mlist
.info
),
311 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
312 'ins' : mlist
.subscribe_policy
> 1,
313 'priv' : 1-mlist
.advertised
,
314 'sub' : 2*is_member
+ is_pending
,
316 'nbsub': len(members
)
318 return (details
, members
)
321 def get_options(userdesc
, perms
, mlist
, opts
):
322 """ Get the options of a list.
327 for (k
, v
) in mlist
.__dict__
.iteritems():
330 options
[k
] = quote(v
)
332 details
= get_list_info(userdesc
, perms
, mlist
)[0]
333 return (details
, options
)
335 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
336 for (k
, v
) in vals
.iteritems():
339 if k
== 'default_member_moderation':
340 for member
in mlist
.getMembers():
341 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
342 t
= type(mlist
.__dict__
[k
])
343 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
344 elif t
is int: mlist
.__dict__
[k
] = int(v
)
345 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
346 else: mlist
.__dict__
[k
] = v
349 #-------------------------------------------------------------------------------
350 # users procedures for [ index.php ]
353 def get_lists(userdesc
, perms
, vhost
, email
=None):
354 """ List available lists for the given vhost
359 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
360 prefix
= vhost
.lower()+VHOST_SEP
361 names
= Utils
.list_names()
365 if not name
.startswith(prefix
):
368 mlist
= MailList
.MailList(name
, lock
=0)
372 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
373 if details
is not None:
374 result
.append(details
[0])
376 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
380 def subscribe(userdesc
, perms
, mlist
):
381 """ Subscribe to a list.
385 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
386 mlist
.ApprovedAddMember(userdesc
)
391 mlist
.AddMember(userdesc
)
392 except Errors
.MMNeedApproval
:
396 def unsubscribe(userdesc
, perms
, mlist
):
397 """ Unsubscribe from a list
401 mlist
.ApprovedDeleteMember(userdesc
.address
)
404 #-------------------------------------------------------------------------------
405 # users procedures for [ index.php ]
408 def get_name(member
):
410 return quote(mlist
.getMemberName(member
))
414 def get_members(userdesc
, perms
, mlist
):
415 """ List the members of a list.
418 infos
= get_list_info(userdesc
, perms
, mlist
)
420 # Do not return None, this is not serializable
422 details
, members
= infos
424 members
= map(lambda member
: (get_name(member
), member
), members
)
425 return (details
, members
, mlist
.owner
)
428 #-------------------------------------------------------------------------------
429 # users procedures for [ trombi.php ]
432 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
433 """ Get a range of members of the list.
436 members
= get_members(userdesc
, perms
, mlist
)[1]
437 i
= int(page
) * int(nb_per_page
)
438 return (len(members
), members
[i
:i
+int(nb_per_page
)])
440 def get_owners(userdesc
, perms
, mlist
):
441 """ Get the owners of the list.
444 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
445 return (details
, owners
)
448 #-------------------------------------------------------------------------------
449 # owners procedures [ admin.php ]
452 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
453 """ Replace the address of a member by another one.
458 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
461 def mass_subscribe(userdesc
, perms
, mlist
, users
):
462 """ Add a list of users to the list.
467 if not isinstance(users
, list):
468 raise Exception("userlist must be a list")
469 members
= mlist
.getRegularMemberKeys()
472 email
, name
= to_forlife(user
)
473 if ( email
is None ) or ( email
in members
):
475 userd
= UserDesc(email
, name
, None, 0)
476 mlist
.ApprovedAddMember(userd
)
477 added
.append( (quote(userd
.fullname
), userd
.address
) )
480 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
481 """ Remove a list of users from the list.
486 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
489 def add_owner(userdesc
, perms
, mlist
, user
):
490 """ Add a owner to the list.
495 email
= to_forlife(user
)[0]
498 if email
not in mlist
.owner
:
499 mlist
.owner
.append(email
)
502 def del_owner(userdesc
, perms
, mlist
, user
):
503 """ Remove a owner of the list.
508 if len(mlist
.owner
) < 2:
510 mlist
.owner
.remove(user
)
513 #-------------------------------------------------------------------------------
514 # owners procedures [ admin.php ]
517 def get_pending_ops(userdesc
, perms
, mlist
):
518 """ Get the list of operation waiting for an action from the owners.
526 for id in mlist
.GetSubscriptionIds():
527 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
529 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
534 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
535 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
537 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
540 for id in mlist
.GetHeldMessageIds():
541 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
542 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
544 size
= os
.path
.getsize(fpath
)
546 if e
.errno
<> errno
.ENOENT
: raise
549 msg
= readMessage(fpath
)
550 fromX
= msg
.has_key("X-Org-Mail")
555 'sender': quote(sender
, True),
557 'subj' : quote(subject
, True),
565 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
566 """ Handle a moderation request.
571 mlist
.HandleRequest(int(id), int(value
), comment
)
574 def get_pending_sub(userdesc
, perms
, mlist
, id):
575 """ Get informations about a given subscription moderation.
582 if id in mlist
.GetSubscriptionIds():
583 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
585 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
586 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
588 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
591 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
592 """ Get informations about a given mail moderation.
597 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
598 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
599 size
= os
.path
.getsize(fpath
)
600 msg
= readMessage(fpath
)
603 return quote(str(msg
))
606 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
607 c
= part
.get_payload()
608 if c
is not None: results_plain
.append (c
)
609 results_plain
= map(lambda x
: quote(x
), results_plain
)
610 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
611 c
= part
.get_payload()
612 if c
is not None: results_html
.append (c
)
613 results_html
= map(lambda x
: quote(x
), results_html
)
615 'sender': quote(sender
, True),
617 'subj' : quote(subject
, True),
619 'parts_plain' : results_plain
,
620 'parts_html': results_html
}
622 #-------------------------------------------------------------------------------
623 # owner options [ options.php ]
626 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
627 'default_member_moderation', 'generic_nonmember_action', 'info', \
628 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
631 def get_owner_options(userdesc
, perms
, mlist
):
632 """ Get the owner options of a list.
636 return get_options(userdesc
, perms
, mlist
, owner_opts
)
638 def set_owner_options(userdesc
, perms
, mlist
, values
):
639 """ Set the owner options of a list.
644 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
646 def add_to_wl(userdesc
, perms
, mlist
, addr
):
647 """ Add addr to the whitelist
652 mlist
.accept_these_nonmembers
.append(addr
)
655 def del_from_wl(userdesc
, perms
, mlist
, addr
):
656 """ Remove an address from the whitelist
661 mlist
.accept_these_nonmembers
.remove(addr
)
664 def get_bogo_level(userdesc
, perms
, mlist
):
665 """ Compute bogo level from the filtering rules set up on the list.
669 if len(mlist
.header_filter_rules
) == 0:
676 # The first rule filters Unsure mails
677 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
681 # Check the other rules:
682 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
683 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
684 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
686 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
689 action
= mlist
.header_filter_rules
[filterbase
][1]
690 if action
== mm_cfg
.HOLD
:
692 elif action
== mm_cfg
.DISCARD
:
694 return (filterlevel
<< 1) + unsurelevel
696 def set_bogo_level(userdesc
, perms
, mlist
, level
):
697 """ Set filter to the specified level.
704 # The level is a combination of a spam filtering level and unsure filtering level
705 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
706 # - the spam filtering level is a number growing with filtering strength
707 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
708 bogolevel
= int(level
)
709 filterlevel
= bogolevel
>> 1
710 unsurelevel
= bogolevel
& 1
712 # Set up unusre filtering
714 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
716 # Set up spam filtering
718 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
719 elif filterlevel
is 2:
720 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
721 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
722 elif filterlevel
is 3:
723 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
726 if mlist
.header_filter_rules
!= hfr
:
727 mlist
.header_filter_rules
= hfr
730 #-------------------------------------------------------------------------------
731 # admin procedures [ soptions.php ]
734 admin_opts
= [ 'advertised', 'archive', \
735 'max_message_size', 'msg_footer', 'msg_header']
737 def get_admin_options(userdesc
, perms
, mlist
):
738 """ Get administrator options.
742 return get_options(userdesc
, perms
, mlist
, admin_opts
)
744 def set_admin_options(userdesc
, perms
, mlist
, values
):
745 """ Set administrator options.
750 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
752 #-------------------------------------------------------------------------------
753 # admin procedures [ check.php ]
757 'acceptable_aliases' : '',
758 'admin_immed_notify' : True,
759 'administrivia' : True,
760 'anonymous_list' : False,
761 'autorespond_admin' : False,
762 'autorespond_postings' : False,
763 'autorespond_requests' : False,
764 'available_languages' : ['fr'],
766 'bounce_matching_headers' : '',
767 'bounce_processing' : False,
768 'convert_html_to_plaintext' : False,
769 'digestable' : False,
770 'digest_is_default' : False,
771 'discard_these_nonmembers' : [],
773 'encode_ascii_prefixes' : 2,
774 'filter_content' : False,
775 'first_strip_reply_to' : False,
776 'forward_auto_discards' : True,
777 'hold_these_nonmembers' : [],
778 'host_name' : 'listes.polytechnique.org',
779 'include_list_post_header' : False,
780 'include_rfc2369_headers' : False,
781 'max_num_recipients' : 0,
782 'new_member_options' : 256,
783 'nondigestable' : True,
784 'obscure_addresses' : True,
785 'preferred_language' : 'fr',
786 'reject_these_nonmembers' : [],
787 'reply_goes_to_list' : 0,
788 'reply_to_address' : '',
789 'require_explicit_destination' : False,
790 'send_reminders' : 0,
791 'send_welcome_msg' : True,
792 'topics_enabled' : False,
793 'umbrella_list' : False,
794 'unsubscribe_policy' : 0,
797 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
799 for (k
, v
) in check_opts
.iteritems():
800 if mlist
.__dict__
[k
] != v
:
801 options
[k
] = v
, mlist
.__dict__
[k
]
802 if correct
: mlist
.__dict__
[k
] = v
803 if mlist
.real_name
.lower() != listname
:
804 options
['real_name'] = listname
, mlist
.real_name
805 if correct
: mlist
.real_name
= listname
809 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
813 listname
= listname
.lower()
814 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
816 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
818 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
820 #-------------------------------------------------------------------------------
821 # super-admin procedures
824 def get_all_lists(userdesc
, perms
, vhost
):
825 """ Get all the list for the given vhost
828 prefix
= vhost
.lower()+VHOST_SEP
829 names
= Utils
.list_names()
833 if not name
.startswith(prefix
):
835 result
.append(name
.replace(prefix
, ''))
838 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
839 """ Get all the lists for the given user
842 names
= Utils
.list_names()
847 mlist
= MailList
.MailList(name
, lock
=0)
848 ismember
= email
in mlist
.getRegularMemberKeys()
849 isowner
= email
in mlist
.owner
850 if not ismember
and not isowner
:
852 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
853 result
.append({ 'list': mlist
.real_name
,
854 'addr': mlist
.real_name
.lower() + '@' + host
,
863 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
864 """ Change the email of a user
867 from_email
= from_email
.lower()
868 to_email
= to_email
.lower()
869 for list in Utils
.list_names():
871 mlist
= MailList
.MailList(list, lock
=0)
876 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
884 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
885 """ Create a new list.
888 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
889 if Utils
.list_exists(name
):
890 print >> sys
.stderr
, "List ", name
, " already exists"
895 email
= to_forlife(o
)
896 print >> sys
.stderr
, "owner in list", o
, email
898 if email
is not None:
901 print >> sys
.stderr
, "No owner found in ", owners
904 mlist
= MailList
.MailList()
906 oldmask
= os
.umask(002)
907 pw
= sha
.new('foobar').hexdigest()
910 mlist
.Create(name
, owner
[0], pw
)
914 mlist
.real_name
= listname
915 mlist
.host_name
= 'listes.polytechnique.org'
916 mlist
.description
= desc
918 mlist
.advertised
= int(advertise
) is 0
919 mlist
.default_member_moderation
= int(modlevel
) is 2
920 mlist
.generic_nonmember_action
= int(modlevel
) > 0
921 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
922 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
926 mlist
.subject_prefix
= '['+listname
+'] '
927 mlist
.max_message_size
= 0
929 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
930 mlist
.msg_footer
= "_______________________________________________\n" \
931 + "Liste de diffusion %(real_name)s\n" \
932 + "http://listes.polytechnique.org/members/" + inverted_listname
934 mlist
.header_filter_rules
= []
935 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
936 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
938 if ON_CREATE_CMD
!= '':
939 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
942 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
943 mass_subscribe(userdesc
, perms
, mlist
, members
)
948 # avoid the "-1 mail to moderate" bug
949 mlist
= MailList
.MailList(name
)
951 mlist
._UpdateRecords()
957 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
962 lname
= mlist
.internal_name()
964 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
965 # remove stalled locks
966 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
967 fn_lname
= filename
.split('.')[0]
968 if fn_lname
== lname
:
969 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
973 os
.path
.join('archives', 'private', lname
),
974 os
.path
.join('archives', 'private', lname
+'.mbox'),
975 os
.path
.join('archives', 'public', lname
),
976 os
.path
.join('archives', 'public', lname
+'.mbox')
978 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
981 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
982 """ Remove a user from all the lists.
985 if not del_from_promo
:
986 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
987 for list in Utils
.list_names():
991 mlist
= MailList
.MailList(list, lock
=0)
996 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
1004 #-------------------------------------------------------------------------------
1007 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
1008 allow_reuse_address
= True
1010 ################################################################################
1014 #-------------------------------------------------------------------------------
1015 # use Mailman user and group (not root)
1016 # fork in background if asked to
1019 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1020 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1023 os
.setregid(gid
, gid
)
1024 os
.setreuid(uid
, uid
)
1026 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1028 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1031 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1033 if o
== '-f' and os
.fork():
1036 i18n
.set_language('fr')
1040 #-------------------------------------------------------------------------------
1043 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1046 server
.register_function(get_lists
)
1047 server
.register_function(subscribe
)
1048 server
.register_function(unsubscribe
)
1050 server
.register_function(get_members
)
1052 server
.register_function(get_members_limit
)
1053 server
.register_function(get_owners
)
1055 server
.register_function(replace_email
)
1056 server
.register_function(mass_subscribe
)
1057 server
.register_function(mass_unsubscribe
)
1058 server
.register_function(add_owner
)
1059 server
.register_function(del_owner
)
1061 server
.register_function(get_pending_ops
)
1062 server
.register_function(handle_request
)
1063 server
.register_function(get_pending_sub
)
1064 server
.register_function(get_pending_mail
)
1066 server
.register_function(get_owner_options
)
1067 server
.register_function(set_owner_options
)
1068 server
.register_function(add_to_wl
)
1069 server
.register_function(del_from_wl
)
1070 server
.register_function(get_bogo_level
)
1071 server
.register_function(set_bogo_level
)
1073 server
.register_function(get_admin_options
)
1074 server
.register_function(set_admin_options
)
1076 server
.register_function(check_options
)
1078 server
.register_function(get_all_lists
)
1079 server
.register_function(get_all_user_lists
)
1080 server
.register_function(change_user_email
)
1081 server
.register_function(create_list
)
1082 server
.register_function(delete_list
)
1084 server
.register_function(kill
)
1086 server
.serve_forever()
1088 # vim:set et sw=4 sts=4 sws=4: