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
:
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
)
153 ################################################################################
157 #-------------------------------------------------------------------------------
162 db
= MySQLdb
.connect(
166 unix_socket
='/var/run/mysqld/mysqld.sock')
170 def mysql_fetchone(query
):
175 if int(mysql
.rowcount
) > 0:
176 ret
= mysql
.fetchone()
181 def is_admin_on(userdesc
, perms
, mlist
):
182 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
185 def quote(s
, is_header
=False):
187 h
= Utils
.oneline(s
, 'iso-8859-1')
190 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
191 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
193 def to_forlife(email
):
195 mbox
, fqdn
= email
.split('@')
199 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
200 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
202 INNER JOIN aliases AS f ON (f.uid = a.uid AND f.type = 'a_vie')
203 INNER JOIN aliases AS aa ON (aa.uid = a.uid AND aa.alias = '%s'
204 AND a.type != 'homonyme')
205 WHERE a.state = 'active'
207 %
(PLATAL_DOMAIN
, mbox
))
212 return (email
.lower(), mbox
)
215 # see /usr/lib/mailman/bin/rmlist
217 def remove_it(listname
, filename
):
218 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
220 elif os
.path
.isdir(filename
):
221 shutil
.rmtree(filename
)
227 def has_annotation(method
, name
):
228 """ Check if the method contains the given annoation.
230 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
232 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
233 """Dispatch the call to the right handler.
234 This function checks the options of the called method the set the environment of the call.
235 The dispatcher uses method annotation (special tokens in the documentation of the method) to
236 guess the requested environment:
237 @mlist: the handler requires a mlist object instead of the vhost/listname couple
238 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
239 @edit: the handler edit the mlist (@mlist MUST be specified)
240 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
241 @root: the handler requires site admin rights
244 print "calling method: %s" % method
245 if has_annotation(method
, "root") and perms
!= "admin":
247 if has_annotation(method
, "mlist"):
248 listname
= str(arg
[0])
250 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
251 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
253 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
254 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
256 return method(userdesc
, perms
, mlist
, *arg
)
258 return method(userdesc
, perms
, vhost
, *arg
)
260 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
264 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
265 """Call the given method after locking the mlist.
269 ret
= method(userdesc
, perms
, mlist
, *arg
)
275 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
278 # TODO: use finally when switching to python 2.5
280 #-------------------------------------------------------------------------------
284 def is_subscription_pending(userdesc
, perms
, mlist
):
285 for id in mlist
.GetSubscriptionIds():
286 if userdesc
.address
== mlist
.GetRecord(id)[1]:
290 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
291 members
= mlist
.getRegularMemberKeys()
292 is_member
= userdesc
.address
in members
293 is_owner
= userdesc
.address
in mlist
.owner
294 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
296 if not is_member
and (mlist
.subscribe_policy
> 1):
297 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
301 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
303 'list' : mlist
.real_name
,
304 'addr' : mlist
.real_name
.lower() + '@' + host
,
306 'desc' : quote(mlist
.description
),
307 'info' : quote(mlist
.info
),
308 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
309 'ins' : mlist
.subscribe_policy
> 1,
310 'priv' : 1-mlist
.advertised
,
311 'sub' : 2*is_member
+ is_pending
,
313 'nbsub': len(members
)
315 return (details
, members
)
318 def get_options(userdesc
, perms
, mlist
, opts
):
319 """ Get the options of a list.
324 for (k
, v
) in mlist
.__dict__
.iteritems():
327 options
[k
] = quote(v
)
329 details
= get_list_info(userdesc
, perms
, mlist
)[0]
330 return (details
, options
)
332 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
333 for (k
, v
) in vals
.iteritems():
336 if k
== 'default_member_moderation':
337 for member
in mlist
.getMembers():
338 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
339 t
= type(mlist
.__dict__
[k
])
340 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
341 elif t
is int: mlist
.__dict__
[k
] = int(v
)
342 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
343 else: mlist
.__dict__
[k
] = v
346 #-------------------------------------------------------------------------------
347 # users procedures for [ index.php ]
350 def get_lists(userdesc
, perms
, vhost
, email
=None):
351 """ List available lists for the given vhost
356 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
357 prefix
= vhost
.lower()+VHOST_SEP
358 names
= Utils
.list_names()
362 if not name
.startswith(prefix
):
365 mlist
= MailList
.MailList(name
, lock
=0)
369 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
370 if details
is not None:
371 result
.append(details
[0])
373 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
377 def subscribe(userdesc
, perms
, mlist
):
378 """ Subscribe to a list.
382 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
383 mlist
.ApprovedAddMember(userdesc
)
388 mlist
.AddMember(userdesc
)
389 except Errors
.MMNeedApproval
:
393 def unsubscribe(userdesc
, perms
, mlist
):
394 """ Unsubscribe from a list
398 mlist
.ApprovedDeleteMember(userdesc
.address
)
401 #-------------------------------------------------------------------------------
402 # users procedures for [ index.php ]
405 def get_name(member
):
407 return quote(mlist
.getMemberName(member
))
411 def get_members(userdesc
, perms
, mlist
):
412 """ List the members of a list.
415 details
, members
= get_list_info(userdesc
, perms
, mlist
)
417 members
= map(lambda member
: (get_name(member
), member
), members
)
418 return (details
, members
, mlist
.owner
)
421 #-------------------------------------------------------------------------------
422 # users procedures for [ trombi.php ]
425 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
426 """ Get a range of members of the list.
429 members
= get_members(userdesc
, perms
, mlist
)[1]
430 i
= int(page
) * int(nb_per_page
)
431 return (len(members
), members
[i
:i
+int(nb_per_page
)])
433 def get_owners(userdesc
, perms
, mlist
):
434 """ Get the owners of the list.
437 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
438 return (details
, owners
)
441 #-------------------------------------------------------------------------------
442 # owners procedures [ admin.php ]
445 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
446 """ Replace the address of a member by another one.
451 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
454 def mass_subscribe(userdesc
, perms
, mlist
, users
):
455 """ Add a list of users to the list.
460 members
= mlist
.getRegularMemberKeys()
463 email
, name
= to_forlife(user
)
464 if ( email
is None ) or ( email
in members
):
466 userd
= UserDesc(email
, name
, None, 0)
467 mlist
.ApprovedAddMember(userd
)
468 added
.append( (quote(userd
.fullname
), userd
.address
) )
471 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
472 """ Remove a list of users from the list.
477 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
480 def add_owner(userdesc
, perms
, mlist
, user
):
481 """ Add a owner to the list.
486 email
= to_forlife(user
)[0]
489 if email
not in mlist
.owner
:
490 mlist
.owner
.append(email
)
493 def del_owner(userdesc
, perms
, mlist
, user
):
494 """ Remove a owner of the list.
499 if len(mlist
.owner
) < 2:
501 mlist
.owner
.remove(user
)
504 #-------------------------------------------------------------------------------
505 # owners procedures [ admin.php ]
508 def get_pending_ops(userdesc
, perms
, mlist
):
509 """ Get the list of operation waiting for an action from the owners.
517 for id in mlist
.GetSubscriptionIds():
518 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
520 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
525 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
526 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
528 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
531 for id in mlist
.GetHeldMessageIds():
532 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
533 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
535 size
= os
.path
.getsize(fpath
)
537 if e
.errno
<> errno
.ENOENT
: raise
540 msg
= readMessage(fpath
)
541 fromX
= msg
.has_key("X-Org-Mail")
546 'sender': quote(sender
, True),
548 'subj' : quote(subject
, True),
556 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
557 """ Handle a moderation request.
562 mlist
.HandleRequest(int(id), int(value
), comment
)
565 def get_pending_sub(userdesc
, perms
, mlist
, id):
566 """ Get informations about a given subscription moderation.
573 if id in mlist
.GetSubscriptionIds():
574 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
576 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
577 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
579 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
582 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
583 """ Get informations about a given mail moderation.
588 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
589 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
590 size
= os
.path
.getsize(fpath
)
591 msg
= readMessage(fpath
)
594 return quote(str(msg
))
597 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
598 c
= part
.get_payload()
599 if c
is not None: results_plain
.append (c
)
600 results_plain
= map(lambda x
: quote(x
), results_plain
)
601 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
602 c
= part
.get_payload()
603 if c
is not None: results_html
.append (c
)
604 results_html
= map(lambda x
: quote(x
), results_html
)
606 'sender': quote(sender
, True),
608 'subj' : quote(subject
, True),
610 'parts_plain' : results_plain
,
611 'parts_html': results_html
}
613 #-------------------------------------------------------------------------------
614 # owner options [ options.php ]
617 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
618 'default_member_moderation', 'generic_nonmember_action', 'info', \
619 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
622 def get_owner_options(userdesc
, perms
, mlist
):
623 """ Get the owner options of a list.
627 return get_options(userdesc
, perms
, mlist
, owner_opts
)
629 def set_owner_options(userdesc
, perms
, mlist
, values
):
630 """ Set the owner options of a list.
635 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
637 def add_to_wl(userdesc
, perms
, mlist
, addr
):
638 """ Add addr to the whitelist
643 mlist
.accept_these_nonmembers
.append(addr
)
646 def del_from_wl(userdesc
, perms
, mlist
, addr
):
647 """ Remove an address from the whitelist
652 mlist
.accept_these_nonmembers
.remove(addr
)
655 def get_bogo_level(userdesc
, perms
, mlist
):
656 """ Compute bogo level from the filtering rules set up on the list.
660 if len(mlist
.header_filter_rules
) == 0:
667 # The first rule filters Unsure mails
668 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
672 # Check the other rules:
673 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
674 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
675 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
677 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
680 action
= mlist
.header_filter_rules
[filterbase
][1]
681 if action
== mm_cfg
.HOLD
:
683 elif action
== mm_cfg
.DISCARD
:
685 return (filterlevel
<< 1) + unsurelevel
687 def set_bogo_level(userdesc
, perms
, mlist
, level
):
688 """ Set filter to the specified level.
695 # The level is a combination of a spam filtering level and unsure filtering level
696 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
697 # - the spam filtering level is a number growing with filtering strength
698 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
699 bogolevel
= int(level
)
700 filterlevel
= bogolevel
>> 1
701 unsurelevel
= bogolevel
& 1
703 # Set up unusre filtering
705 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
707 # Set up spam filtering
709 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
710 elif filterlevel
is 2:
711 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
712 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
713 elif filterlevel
is 3:
714 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
717 if mlist
.header_filter_rules
!= hfr
:
718 mlist
.header_filter_rules
= hfr
721 #-------------------------------------------------------------------------------
722 # admin procedures [ soptions.php ]
725 admin_opts
= [ 'advertised', 'archive', \
726 'max_message_size', 'msg_footer', 'msg_header']
728 def get_admin_options(userdesc
, perms
, mlist
):
729 """ Get administrator options.
733 return get_options(userdesc
, perms
, mlist
, admin_opts
)
735 def set_admin_options(userdesc
, perms
, mlist
, values
):
736 """ Set administrator options.
741 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
743 #-------------------------------------------------------------------------------
744 # admin procedures [ check.php ]
748 'acceptable_aliases' : '',
749 'admin_immed_notify' : True,
750 'administrivia' : True,
751 'anonymous_list' : False,
752 'autorespond_admin' : False,
753 'autorespond_postings' : False,
754 'autorespond_requests' : False,
755 'available_languages' : ['fr'],
757 'bounce_matching_headers' : '',
758 'bounce_processing' : False,
759 'convert_html_to_plaintext' : False,
760 'digestable' : False,
761 'digest_is_default' : False,
762 'discard_these_nonmembers' : [],
764 'encode_ascii_prefixes' : 2,
765 'filter_content' : False,
766 'first_strip_reply_to' : False,
767 'forward_auto_discards' : True,
768 'hold_these_nonmembers' : [],
769 'host_name' : 'listes.polytechnique.org',
770 'include_list_post_header' : False,
771 'include_rfc2369_headers' : False,
772 'max_num_recipients' : 0,
773 'new_member_options' : 256,
774 'nondigestable' : True,
775 'obscure_addresses' : True,
776 'preferred_language' : 'fr',
777 'reject_these_nonmembers' : [],
778 'reply_goes_to_list' : 0,
779 'reply_to_address' : '',
780 'require_explicit_destination' : False,
781 'send_reminders' : 0,
782 'send_welcome_msg' : True,
783 'topics_enabled' : False,
784 'umbrella_list' : False,
785 'unsubscribe_policy' : 0,
788 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
790 for (k
, v
) in check_opts
.iteritems():
791 if mlist
.__dict__
[k
] != v
:
792 options
[k
] = v
, mlist
.__dict__
[k
]
793 if correct
: mlist
.__dict__
[k
] = v
794 if mlist
.real_name
.lower() != listname
:
795 options
['real_name'] = listname
, mlist
.real_name
796 if correct
: mlist
.real_name
= listname
800 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
804 listname
= listname
.lower()
805 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
807 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
809 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
811 #-------------------------------------------------------------------------------
812 # super-admin procedures
815 def get_all_lists(userdesc
, perms
, vhost
):
816 """ Get all the list for the given vhost
819 prefix
= vhost
.lower()+VHOST_SEP
820 names
= Utils
.list_names()
824 if not name
.startswith(prefix
):
826 result
.append(name
.replace(prefix
, ''))
829 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
830 """ Get all the lists for the given user
833 names
= Utils
.list_names()
838 mlist
= MailList
.MailList(name
, lock
=0)
839 ismember
= email
in mlist
.getRegularMemberKeys()
840 isowner
= email
in mlist
.owner
841 if not ismember
and not isowner
:
843 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
844 result
.append({ 'list': mlist
.real_name
,
845 'addr': mlist
.real_name
.lower() + '@' + host
,
854 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
855 """ Change the email of a user
858 from_email
= from_email
.lower()
859 to_email
= to_email
.lower()
860 for list in Utils
.list_names():
862 mlist
= MailList
.MailList(list, lock
=0)
867 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
875 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
876 """ Create a new list.
879 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
880 if Utils
.list_exists(name
):
885 email
= to_forlife(o
)[0]
886 if email
is not None:
891 mlist
= MailList
.MailList()
893 oldmask
= os
.umask(002)
894 pw
= sha
.new('foobar').hexdigest()
897 mlist
.Create(name
, owner
[0], pw
)
901 mlist
.real_name
= listname
902 mlist
.host_name
= 'listes.polytechnique.org'
903 mlist
.description
= desc
905 mlist
.advertised
= int(advertise
) is 0
906 mlist
.default_member_moderation
= int(modlevel
) is 2
907 mlist
.generic_nonmember_action
= int(modlevel
) > 0
908 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
909 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
913 mlist
.subject_prefix
= '['+listname
+'] '
914 mlist
.max_message_size
= 0
916 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
917 mlist
.msg_footer
= "_______________________________________________\n" \
918 + "Liste de diffusion %(real_name)s\n" \
919 + "http://listes.polytechnique.org/members/" + inverted_listname
921 mlist
.header_filter_rules
= []
922 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
923 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
925 if ON_CREATE_CMD
!= '':
926 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
929 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
930 mass_subscribe(userdesc
, perms
, mlist
, members
)
935 # avoid the "-1 mail to moderate" bug
936 mlist
= MailList
.MailList(name
)
938 mlist
._UpdateRecords()
944 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
949 lname
= mlist
.internal_name()
951 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
952 # remove stalled locks
953 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
954 fn_lname
= filename
.split('.')[0]
955 if fn_lname
== lname
:
956 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
960 os
.path
.join('archives', 'private', lname
),
961 os
.path
.join('archives', 'private', lname
+'.mbox'),
962 os
.path
.join('archives', 'public', lname
),
963 os
.path
.join('archives', 'public', lname
+'.mbox')
965 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
968 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
969 """ Remove a user from all the lists.
972 if not del_from_promo
:
973 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
974 for list in Utils
.list_names():
978 mlist
= MailList
.MailList(list, lock
=0)
983 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
991 #-------------------------------------------------------------------------------
994 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
995 allow_reuse_address
= True
997 ################################################################################
1001 #-------------------------------------------------------------------------------
1002 # use Mailman user and group (not root)
1003 # fork in background if asked to
1006 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1007 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1010 os
.setregid(gid
, gid
)
1011 os
.setreuid(uid
, uid
)
1013 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1015 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1018 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1020 if o
== '-f' and os
.fork():
1023 i18n
.set_language('fr')
1027 #-------------------------------------------------------------------------------
1030 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1033 server
.register_function(get_lists
)
1034 server
.register_function(subscribe
)
1035 server
.register_function(unsubscribe
)
1037 server
.register_function(get_members
)
1039 server
.register_function(get_members_limit
)
1040 server
.register_function(get_owners
)
1042 server
.register_function(replace_email
)
1043 server
.register_function(mass_subscribe
)
1044 server
.register_function(mass_unsubscribe
)
1045 server
.register_function(add_owner
)
1046 server
.register_function(del_owner
)
1048 server
.register_function(get_pending_ops
)
1049 server
.register_function(handle_request
)
1050 server
.register_function(get_pending_sub
)
1051 server
.register_function(get_pending_mail
)
1053 server
.register_function(get_owner_options
)
1054 server
.register_function(set_owner_options
)
1055 server
.register_function(add_to_wl
)
1056 server
.register_function(del_from_wl
)
1057 server
.register_function(get_bogo_level
)
1058 server
.register_function(set_bogo_level
)
1060 server
.register_function(get_admin_options
)
1061 server
.register_function(set_admin_options
)
1063 server
.register_function(check_options
)
1065 server
.register_function(get_all_lists
)
1066 server
.register_function(get_all_user_lists
)
1067 server
.register_function(change_user_email
)
1068 server
.register_function(create_list
)
1069 server
.register_function(delete_list
)
1071 server
.register_function(kill
)
1073 server
.serve_forever()
1075 # vim:set et sw=4 sts=4 sws=4: