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, IF(aa.alias IS NULL, a.email, CONCAT(aa.alias, '@%s')),
130 IF (a.is_admin, 'admin',
131 IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL))
133 INNER JOIN account_types AS at ON (at.type = a.type)
134 LEFT JOIN aliases AS aa ON (a.uid = aa.uid AND aa.type = 'a_vie')
135 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
137 %
(PLATAL_DOMAIN
, uid
, md5
))
139 name
, forlife
, perms
= res
140 if vhost
!= PLATAL_DOMAIN
and perms
!= 'admin':
141 res
= mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists')
142 FROM group_members AS m
143 INNER JOIN groups AS g ON (m.asso_id = g.id)
144 WHERE uid = '%s' AND mail_domain = '%s'""" \
148 userdesc
= UserDesc(forlife
, name
, None, 0)
149 return (userdesc
, perms
, vhost
)
151 print "no user found for uid: %s, passwd: %s" %
(uid
, md5
)
154 ################################################################################
158 #-------------------------------------------------------------------------------
163 db
= MySQLdb
.connect(
167 unix_socket
='/var/run/mysqld/mysqld.sock')
171 def mysql_fetchone(query
):
176 if int(mysql
.rowcount
) > 0:
177 ret
= mysql
.fetchone()
182 def is_admin_on(userdesc
, perms
, mlist
):
183 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
186 def quote(s
, is_header
=False):
188 h
= Utils
.oneline(s
, 'iso-8859-1')
191 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
192 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
194 def to_forlife(email
):
196 mbox
, fqdn
= email
.split('@')
200 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
201 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
203 INNER JOIN aliases AS f ON (f.uid = a.uid AND f.type = 'a_vie')
204 INNER JOIN aliases AS aa ON (aa.uid = a.uid AND aa.alias = '%s'
205 AND a.type != 'homonyme')
206 WHERE a.state = 'active'
208 %
(PLATAL_DOMAIN
, mbox
))
213 return (email
.lower(), mbox
)
216 # see /usr/lib/mailman/bin/rmlist
218 def remove_it(listname
, filename
):
219 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
221 elif os
.path
.isdir(filename
):
222 shutil
.rmtree(filename
)
228 def has_annotation(method
, name
):
229 """ Check if the method contains the given annoation.
231 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
233 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
234 """Dispatch the call to the right handler.
235 This function checks the options of the called method the set the environment of the call.
236 The dispatcher uses method annotation (special tokens in the documentation of the method) to
237 guess the requested environment:
238 @mlist: the handler requires a mlist object instead of the vhost/listname couple
239 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
240 @edit: the handler edit the mlist (@mlist MUST be specified)
241 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
242 @root: the handler requires site admin rights
245 print "calling method: %s" % method
246 if has_annotation(method
, "root") and perms
!= "admin":
248 if has_annotation(method
, "mlist"):
249 listname
= str(arg
[0])
251 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
252 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
254 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
255 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
257 return method(userdesc
, perms
, mlist
, *arg
)
259 return method(userdesc
, perms
, vhost
, *arg
)
261 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
265 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
266 """Call the given method after locking the mlist.
270 ret
= method(userdesc
, perms
, mlist
, *arg
)
276 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
279 # TODO: use finally when switching to python 2.5
281 #-------------------------------------------------------------------------------
285 def is_subscription_pending(userdesc
, perms
, mlist
):
286 for id in mlist
.GetSubscriptionIds():
287 if userdesc
.address
== mlist
.GetRecord(id)[1]:
291 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
292 members
= mlist
.getRegularMemberKeys()
293 is_member
= userdesc
.address
in members
294 is_owner
= userdesc
.address
in mlist
.owner
295 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
297 if not is_member
and (mlist
.subscribe_policy
> 1):
298 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
302 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
304 'list' : mlist
.real_name
,
305 'addr' : mlist
.real_name
.lower() + '@' + host
,
307 'desc' : quote(mlist
.description
),
308 'info' : quote(mlist
.info
),
309 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
310 'ins' : mlist
.subscribe_policy
> 1,
311 'priv' : 1-mlist
.advertised
,
312 'sub' : 2*is_member
+ is_pending
,
314 'nbsub': len(members
)
316 return (details
, members
)
319 def get_options(userdesc
, perms
, mlist
, opts
):
320 """ Get the options of a list.
325 for (k
, v
) in mlist
.__dict__
.iteritems():
328 options
[k
] = quote(v
)
330 details
= get_list_info(userdesc
, perms
, mlist
)[0]
331 return (details
, options
)
333 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
334 for (k
, v
) in vals
.iteritems():
337 if k
== 'default_member_moderation':
338 for member
in mlist
.getMembers():
339 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
340 t
= type(mlist
.__dict__
[k
])
341 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
342 elif t
is int: mlist
.__dict__
[k
] = int(v
)
343 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
344 else: mlist
.__dict__
[k
] = v
347 #-------------------------------------------------------------------------------
348 # users procedures for [ index.php ]
351 def get_lists(userdesc
, perms
, vhost
, email
=None):
352 """ List available lists for the given vhost
357 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
358 prefix
= vhost
.lower()+VHOST_SEP
359 names
= Utils
.list_names()
363 if not name
.startswith(prefix
):
366 mlist
= MailList
.MailList(name
, lock
=0)
370 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
371 if details
is not None:
372 result
.append(details
[0])
374 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
378 def subscribe(userdesc
, perms
, mlist
):
379 """ Subscribe to a list.
383 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
384 mlist
.ApprovedAddMember(userdesc
)
389 mlist
.AddMember(userdesc
)
390 except Errors
.MMNeedApproval
:
394 def unsubscribe(userdesc
, perms
, mlist
):
395 """ Unsubscribe from a list
399 mlist
.ApprovedDeleteMember(userdesc
.address
)
402 #-------------------------------------------------------------------------------
403 # users procedures for [ index.php ]
406 def get_name(member
):
408 return quote(mlist
.getMemberName(member
))
412 def get_members(userdesc
, perms
, mlist
):
413 """ List the members of a list.
416 details
, members
= get_list_info(userdesc
, perms
, mlist
)
418 members
= map(lambda member
: (get_name(member
), member
), members
)
419 return (details
, members
, mlist
.owner
)
422 #-------------------------------------------------------------------------------
423 # users procedures for [ trombi.php ]
426 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
427 """ Get a range of members of the list.
430 members
= get_members(userdesc
, perms
, mlist
)[1]
431 i
= int(page
) * int(nb_per_page
)
432 return (len(members
), members
[i
:i
+int(nb_per_page
)])
434 def get_owners(userdesc
, perms
, mlist
):
435 """ Get the owners of the list.
438 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
439 return (details
, owners
)
442 #-------------------------------------------------------------------------------
443 # owners procedures [ admin.php ]
446 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
447 """ Replace the address of a member by another one.
452 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
455 def mass_subscribe(userdesc
, perms
, mlist
, users
):
456 """ Add a list of users to the list.
461 members
= mlist
.getRegularMemberKeys()
464 email
, name
= to_forlife(user
)
465 if ( email
is None ) or ( email
in members
):
467 userd
= UserDesc(email
, name
, None, 0)
468 mlist
.ApprovedAddMember(userd
)
469 added
.append( (quote(userd
.fullname
), userd
.address
) )
472 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
473 """ Remove a list of users from the list.
478 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
481 def add_owner(userdesc
, perms
, mlist
, user
):
482 """ Add a owner to the list.
487 email
= to_forlife(user
)[0]
490 if email
not in mlist
.owner
:
491 mlist
.owner
.append(email
)
494 def del_owner(userdesc
, perms
, mlist
, user
):
495 """ Remove a owner of the list.
500 if len(mlist
.owner
) < 2:
502 mlist
.owner
.remove(user
)
505 #-------------------------------------------------------------------------------
506 # owners procedures [ admin.php ]
509 def get_pending_ops(userdesc
, perms
, mlist
):
510 """ Get the list of operation waiting for an action from the owners.
518 for id in mlist
.GetSubscriptionIds():
519 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
521 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
526 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
527 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
529 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
532 for id in mlist
.GetHeldMessageIds():
533 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
534 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
536 size
= os
.path
.getsize(fpath
)
538 if e
.errno
<> errno
.ENOENT
: raise
541 msg
= readMessage(fpath
)
542 fromX
= msg
.has_key("X-Org-Mail")
547 'sender': quote(sender
, True),
549 'subj' : quote(subject
, True),
557 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
558 """ Handle a moderation request.
563 mlist
.HandleRequest(int(id), int(value
), comment
)
566 def get_pending_sub(userdesc
, perms
, mlist
, id):
567 """ Get informations about a given subscription moderation.
574 if id in mlist
.GetSubscriptionIds():
575 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
577 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
578 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
580 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
583 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
584 """ Get informations about a given mail moderation.
589 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
590 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
591 size
= os
.path
.getsize(fpath
)
592 msg
= readMessage(fpath
)
595 return quote(str(msg
))
598 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
599 c
= part
.get_payload()
600 if c
is not None: results_plain
.append (c
)
601 results_plain
= map(lambda x
: quote(x
), results_plain
)
602 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
603 c
= part
.get_payload()
604 if c
is not None: results_html
.append (c
)
605 results_html
= map(lambda x
: quote(x
), results_html
)
607 'sender': quote(sender
, True),
609 'subj' : quote(subject
, True),
611 'parts_plain' : results_plain
,
612 'parts_html': results_html
}
614 #-------------------------------------------------------------------------------
615 # owner options [ options.php ]
618 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
619 'default_member_moderation', 'generic_nonmember_action', 'info', \
620 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
623 def get_owner_options(userdesc
, perms
, mlist
):
624 """ Get the owner options of a list.
628 return get_options(userdesc
, perms
, mlist
, owner_opts
)
630 def set_owner_options(userdesc
, perms
, mlist
, values
):
631 """ Set the owner options of a list.
636 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
638 def add_to_wl(userdesc
, perms
, mlist
, addr
):
639 """ Add addr to the whitelist
644 mlist
.accept_these_nonmembers
.append(addr
)
647 def del_from_wl(userdesc
, perms
, mlist
, addr
):
648 """ Remove an address from the whitelist
653 mlist
.accept_these_nonmembers
.remove(addr
)
656 def get_bogo_level(userdesc
, perms
, mlist
):
657 """ Compute bogo level from the filtering rules set up on the list.
661 if len(mlist
.header_filter_rules
) == 0:
668 # The first rule filters Unsure mails
669 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
673 # Check the other rules:
674 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
675 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
676 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
678 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
681 action
= mlist
.header_filter_rules
[filterbase
][1]
682 if action
== mm_cfg
.HOLD
:
684 elif action
== mm_cfg
.DISCARD
:
686 return (filterlevel
<< 1) + unsurelevel
688 def set_bogo_level(userdesc
, perms
, mlist
, level
):
689 """ Set filter to the specified level.
696 # The level is a combination of a spam filtering level and unsure filtering level
697 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
698 # - the spam filtering level is a number growing with filtering strength
699 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
700 bogolevel
= int(level
)
701 filterlevel
= bogolevel
>> 1
702 unsurelevel
= bogolevel
& 1
704 # Set up unusre filtering
706 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
708 # Set up spam filtering
710 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
711 elif filterlevel
is 2:
712 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
713 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
714 elif filterlevel
is 3:
715 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
718 if mlist
.header_filter_rules
!= hfr
:
719 mlist
.header_filter_rules
= hfr
722 #-------------------------------------------------------------------------------
723 # admin procedures [ soptions.php ]
726 admin_opts
= [ 'advertised', 'archive', \
727 'max_message_size', 'msg_footer', 'msg_header']
729 def get_admin_options(userdesc
, perms
, mlist
):
730 """ Get administrator options.
734 return get_options(userdesc
, perms
, mlist
, admin_opts
)
736 def set_admin_options(userdesc
, perms
, mlist
, values
):
737 """ Set administrator options.
742 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
744 #-------------------------------------------------------------------------------
745 # admin procedures [ check.php ]
749 'acceptable_aliases' : '',
750 'admin_immed_notify' : True,
751 'administrivia' : True,
752 'anonymous_list' : False,
753 'autorespond_admin' : False,
754 'autorespond_postings' : False,
755 'autorespond_requests' : False,
756 'available_languages' : ['fr'],
758 'bounce_matching_headers' : '',
759 'bounce_processing' : False,
760 'convert_html_to_plaintext' : False,
761 'digestable' : False,
762 'digest_is_default' : False,
763 'discard_these_nonmembers' : [],
765 'encode_ascii_prefixes' : 2,
766 'filter_content' : False,
767 'first_strip_reply_to' : False,
768 'forward_auto_discards' : True,
769 'hold_these_nonmembers' : [],
770 'host_name' : 'listes.polytechnique.org',
771 'include_list_post_header' : False,
772 'include_rfc2369_headers' : False,
773 'max_num_recipients' : 0,
774 'new_member_options' : 256,
775 'nondigestable' : True,
776 'obscure_addresses' : True,
777 'preferred_language' : 'fr',
778 'reject_these_nonmembers' : [],
779 'reply_goes_to_list' : 0,
780 'reply_to_address' : '',
781 'require_explicit_destination' : False,
782 'send_reminders' : 0,
783 'send_welcome_msg' : True,
784 'topics_enabled' : False,
785 'umbrella_list' : False,
786 'unsubscribe_policy' : 0,
789 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
791 for (k
, v
) in check_opts
.iteritems():
792 if mlist
.__dict__
[k
] != v
:
793 options
[k
] = v
, mlist
.__dict__
[k
]
794 if correct
: mlist
.__dict__
[k
] = v
795 if mlist
.real_name
.lower() != listname
:
796 options
['real_name'] = listname
, mlist
.real_name
797 if correct
: mlist
.real_name
= listname
801 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
805 listname
= listname
.lower()
806 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
808 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
810 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
812 #-------------------------------------------------------------------------------
813 # super-admin procedures
816 def get_all_lists(userdesc
, perms
, vhost
):
817 """ Get all the list for the given vhost
820 prefix
= vhost
.lower()+VHOST_SEP
821 names
= Utils
.list_names()
825 if not name
.startswith(prefix
):
827 result
.append(name
.replace(prefix
, ''))
830 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
831 """ Get all the lists for the given user
834 names
= Utils
.list_names()
839 mlist
= MailList
.MailList(name
, lock
=0)
840 ismember
= email
in mlist
.getRegularMemberKeys()
841 isowner
= email
in mlist
.owner
842 if not ismember
and not isowner
:
844 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
845 result
.append({ 'list': mlist
.real_name
,
846 'addr': mlist
.real_name
.lower() + '@' + host
,
855 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
856 """ Change the email of a user
859 from_email
= from_email
.lower()
860 to_email
= to_email
.lower()
861 for list in Utils
.list_names():
863 mlist
= MailList
.MailList(list, lock
=0)
868 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
876 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
877 """ Create a new list.
880 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
881 if Utils
.list_exists(name
):
886 email
= to_forlife(o
)[0]
887 if email
is not None:
892 mlist
= MailList
.MailList()
894 oldmask
= os
.umask(002)
895 pw
= sha
.new('foobar').hexdigest()
898 mlist
.Create(name
, owner
[0], pw
)
902 mlist
.real_name
= listname
903 mlist
.host_name
= 'listes.polytechnique.org'
904 mlist
.description
= desc
906 mlist
.advertised
= int(advertise
) is 0
907 mlist
.default_member_moderation
= int(modlevel
) is 2
908 mlist
.generic_nonmember_action
= int(modlevel
) > 0
909 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
910 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
914 mlist
.subject_prefix
= '['+listname
+'] '
915 mlist
.max_message_size
= 0
917 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
918 mlist
.msg_footer
= "_______________________________________________\n" \
919 + "Liste de diffusion %(real_name)s\n" \
920 + "http://listes.polytechnique.org/members/" + inverted_listname
922 mlist
.header_filter_rules
= []
923 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
924 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
926 if ON_CREATE_CMD
!= '':
927 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
930 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
931 mass_subscribe(userdesc
, perms
, mlist
, members
)
936 # avoid the "-1 mail to moderate" bug
937 mlist
= MailList
.MailList(name
)
939 mlist
._UpdateRecords()
945 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
950 lname
= mlist
.internal_name()
952 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
953 # remove stalled locks
954 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
955 fn_lname
= filename
.split('.')[0]
956 if fn_lname
== lname
:
957 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
961 os
.path
.join('archives', 'private', lname
),
962 os
.path
.join('archives', 'private', lname
+'.mbox'),
963 os
.path
.join('archives', 'public', lname
),
964 os
.path
.join('archives', 'public', lname
+'.mbox')
966 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
969 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
970 """ Remove a user from all the lists.
973 if not del_from_promo
:
974 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
975 for list in Utils
.list_names():
979 mlist
= MailList
.MailList(list, lock
=0)
984 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
992 #-------------------------------------------------------------------------------
995 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
996 allow_reuse_address
= True
998 ################################################################################
1002 #-------------------------------------------------------------------------------
1003 # use Mailman user and group (not root)
1004 # fork in background if asked to
1007 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1008 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1011 os
.setregid(gid
, gid
)
1012 os
.setreuid(uid
, uid
)
1014 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1016 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1019 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1021 if o
== '-f' and os
.fork():
1024 i18n
.set_language('fr')
1028 #-------------------------------------------------------------------------------
1031 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1034 server
.register_function(get_lists
)
1035 server
.register_function(subscribe
)
1036 server
.register_function(unsubscribe
)
1038 server
.register_function(get_members
)
1040 server
.register_function(get_members_limit
)
1041 server
.register_function(get_owners
)
1043 server
.register_function(replace_email
)
1044 server
.register_function(mass_subscribe
)
1045 server
.register_function(mass_unsubscribe
)
1046 server
.register_function(add_owner
)
1047 server
.register_function(del_owner
)
1049 server
.register_function(get_pending_ops
)
1050 server
.register_function(handle_request
)
1051 server
.register_function(get_pending_sub
)
1052 server
.register_function(get_pending_mail
)
1054 server
.register_function(get_owner_options
)
1055 server
.register_function(set_owner_options
)
1056 server
.register_function(add_to_wl
)
1057 server
.register_function(del_from_wl
)
1058 server
.register_function(get_bogo_level
)
1059 server
.register_function(set_bogo_level
)
1061 server
.register_function(get_admin_options
)
1062 server
.register_function(set_admin_options
)
1064 server
.register_function(check_options
)
1066 server
.register_function(get_all_lists
)
1067 server
.register_function(get_all_user_lists
)
1068 server
.register_function(change_user_email
)
1069 server
.register_function(create_list
)
1070 server
.register_function(delete_list
)
1072 server
.register_function(kill
)
1074 server
.serve_forever()
1076 # vim:set et sw=4 sts=4 sws=4: