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 if has_annotation(method
, "root") and perms
!= "admin":
246 if has_annotation(method
, "mlist"):
249 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
250 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
252 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
253 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
255 return method(userdesc
, perms
, mlist
, *arg
)
257 return method(userdesc
, perms
, vhost
, *arg
)
259 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
263 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
264 """Call the given method after locking the mlist.
268 ret
= method(userdesc
, perms
, mlist
, *arg
)
274 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
277 # TODO: use finally when switching to python 2.5
279 #-------------------------------------------------------------------------------
283 def is_subscription_pending(userdesc
, perms
, mlist
):
284 for id in mlist
.GetSubscriptionIds():
285 if userdesc
.address
== mlist
.GetRecord(id)[1]:
289 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
290 members
= mlist
.getRegularMemberKeys()
291 is_member
= userdesc
.address
in members
292 is_owner
= userdesc
.address
in mlist
.owner
293 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
295 if not is_member
and (mlist
.subscribe_policy
> 1):
296 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
300 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
302 'list' : mlist
.real_name
,
303 'addr' : mlist
.real_name
.lower() + '@' + host
,
305 'desc' : quote(mlist
.description
),
306 'info' : quote(mlist
.info
),
307 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
308 'ins' : mlist
.subscribe_policy
> 1,
309 'priv' : 1-mlist
.advertised
,
310 'sub' : 2*is_member
+ is_pending
,
312 'nbsub': len(members
)
314 return (details
, members
)
317 def get_options(userdesc
, perms
, mlist
, opts
):
318 """ Get the options of a list.
323 for (k
, v
) in mlist
.__dict__
.iteritems():
326 options
[k
] = quote(v
)
328 details
= get_list_info(userdesc
, perms
, mlist
)[0]
329 return (details
, options
)
331 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
332 for (k
, v
) in vals
.iteritems():
335 if k
== 'default_member_moderation':
336 for member
in mlist
.getMembers():
337 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
338 t
= type(mlist
.__dict__
[k
])
339 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
340 elif t
is int: mlist
.__dict__
[k
] = int(v
)
341 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
342 else: mlist
.__dict__
[k
] = v
345 #-------------------------------------------------------------------------------
346 # users procedures for [ index.php ]
349 def get_lists(userdesc
, perms
, vhost
, email
=None):
350 """ List available lists for the given vhost
355 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
356 prefix
= vhost
.lower()+VHOST_SEP
357 names
= Utils
.list_names()
361 if not name
.startswith(prefix
):
364 mlist
= MailList
.MailList(name
, lock
=0)
368 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
369 result
.append(details
)
371 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
375 def subscribe(userdesc
, perms
, mlist
):
376 """ Subscribe to a list.
380 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
381 mlist
.ApprovedAddMember(userdesc
)
386 mlist
.AddMember(userdesc
)
387 except Errors
.MMNeedApproval
:
391 def unsubscribe(userdesc
, perms
, mlist
):
392 """ Unsubscribe from a list
396 mlist
.ApprovedDeleteMember(userdesc
.address
)
399 #-------------------------------------------------------------------------------
400 # users procedures for [ index.php ]
403 def get_name(member
):
405 return quote(mlist
.getMemberName(member
))
409 def get_members(userdesc
, perms
, mlist
):
410 """ List the members of a list.
413 details
, members
= get_list_info(userdesc
, perms
, mlist
)
415 members
= map(lambda member
: (get_name(member
), member
), members
)
416 return (details
, members
, mlist
.owner
)
419 #-------------------------------------------------------------------------------
420 # users procedures for [ trombi.php ]
423 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
424 """ Get a range of members of the list.
427 members
= get_members(userdesc
, perms
, mlist
)[1]
428 i
= int(page
) * int(nb_per_page
)
429 return (len(members
), members
[i
:i
+int(nb_per_page
)])
431 def get_owners(userdesc
, perms
, mlist
):
432 """ Get the owners of the list.
435 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
436 return (details
, owners
)
439 #-------------------------------------------------------------------------------
440 # owners procedures [ admin.php ]
443 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
444 """ Replace the address of a member by another one.
449 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
452 def mass_subscribe(userdesc
, perms
, mlist
, users
):
453 """ Add a list of users to the list.
458 members
= mlist
.getRegularMemberKeys()
461 email
, name
= to_forlife(user
)
462 if ( email
is None ) or ( email
in members
):
464 userd
= UserDesc(email
, name
, None, 0)
465 mlist
.ApprovedAddMember(userd
)
466 added
.append( (quote(userd
.fullname
), userd
.address
) )
469 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
470 """ Remove a list of users from the list.
475 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
478 def add_owner(userdesc
, perms
, mlist
, user
):
479 """ Add a owner to the list.
484 email
= to_forlife(user
)[0]
487 if email
not in mlist
.owner
:
488 mlist
.owner
.append(email
)
491 def del_owner(userdesc
, perms
, mlist
, user
):
492 """ Remove a owner of the list.
497 if len(mlist
.owner
) < 2:
499 mlist
.owner
.remove(user
)
502 #-------------------------------------------------------------------------------
503 # owners procedures [ admin.php ]
506 def get_pending_ops(userdesc
, perms
, mlist
):
507 """ Get the list of operation waiting for an action from the owners.
515 for id in mlist
.GetSubscriptionIds():
516 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
518 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
523 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
524 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
526 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
529 for id in mlist
.GetHeldMessageIds():
530 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
531 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
533 size
= os
.path
.getsize(fpath
)
535 if e
.errno
<> errno
.ENOENT
: raise
538 msg
= readMessage(fpath
)
539 fromX
= msg
.has_key("X-Org-Mail")
544 'sender': quote(sender
, True),
546 'subj' : quote(subject
, True),
554 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
555 """ Handle a moderation request.
560 mlist
.HandleRequest(int(id), int(value
), comment
)
563 def get_pending_sub(userdesc
, perms
, mlist
, id):
564 """ Get informations about a given subscription moderation.
571 if id in mlist
.GetSubscriptionIds():
572 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
574 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
575 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
577 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
580 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
581 """ Get informations about a given mail moderation.
586 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
587 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
588 size
= os
.path
.getsize(fpath
)
589 msg
= readMessage(fpath
)
592 return quote(str(msg
))
595 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
596 c
= part
.get_payload()
597 if c
is not None: results_plain
.append (c
)
598 results_plain
= map(lambda x
: quote(x
), results_plain
)
599 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
600 c
= part
.get_payload()
601 if c
is not None: results_html
.append (c
)
602 results_html
= map(lambda x
: quote(x
), results_html
)
604 'sender': quote(sender
, True),
606 'subj' : quote(subject
, True),
608 'parts_plain' : results_plain
,
609 'parts_html': results_html
}
611 #-------------------------------------------------------------------------------
612 # owner options [ options.php ]
615 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
616 'default_member_moderation', 'generic_nonmember_action', 'info', \
617 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
620 def get_owner_options(userdesc
, perms
, mlist
):
621 """ Get the owner options of a list.
625 return get_options(userdesc
, perms
, mlist
, owner_opts
)
627 def set_owner_options(userdesc
, perms
, mlist
, values
):
628 """ Set the owner options of a list.
633 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
635 def add_to_wl(userdesc
, perms
, mlist
, addr
):
636 """ Add addr to the whitelist
641 mlist
.accept_these_nonmembers
.append(addr
)
644 def del_from_wl(userdesc
, perms
, mlist
, addr
):
645 """ Remove an address from the whitelist
650 mlist
.accept_these_nonmembers
.remove(addr
)
653 def get_bogo_level(userdesc
, perms
, mlist
):
654 """ Compute bogo level from the filtering rules set up on the list.
658 if len(mlist
.header_filter_rules
) == 0:
665 # The first rule filters Unsure mails
666 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
670 # Check the other rules:
671 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
672 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
673 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
675 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
678 action
= mlist
.header_filter_rules
[filterbase
][1]
679 if action
== mm_cfg
.HOLD
:
681 elif action
== mm_cfg
.DISCARD
:
683 return (filterlevel
<< 1) + unsurelevel
685 def set_bogo_level(userdesc
, perms
, mlist
, level
):
686 """ Set filter to the specified level.
693 # The level is a combination of a spam filtering level and unsure filtering level
694 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
695 # - the spam filtering level is a number growing with filtering strength
696 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
697 bogolevel
= int(level
)
698 filterlevel
= bogolevel
>> 1
699 unsurelevel
= bogolevel
& 1
701 # Set up unusre filtering
703 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
705 # Set up spam filtering
707 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
708 elif filterlevel
is 2:
709 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
710 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
711 elif filterlevel
is 3:
712 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
715 if mlist
.header_filter_rules
!= hfr
:
716 mlist
.header_filter_rules
= hfr
719 #-------------------------------------------------------------------------------
720 # admin procedures [ soptions.php ]
723 admin_opts
= [ 'advertised', 'archive', \
724 'max_message_size', 'msg_footer', 'msg_header']
726 def get_admin_options(userdesc
, perms
, mlist
):
727 """ Get administrator options.
731 return get_options(userdesc
, perms
, mlist
, admin_opts
)
733 def set_admin_options(userdesc
, perms
, mlist
, values
):
734 """ Set administrator options.
739 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
741 #-------------------------------------------------------------------------------
742 # admin procedures [ check.php ]
746 'acceptable_aliases' : '',
747 'admin_immed_notify' : True,
748 'administrivia' : True,
749 'anonymous_list' : False,
750 'autorespond_admin' : False,
751 'autorespond_postings' : False,
752 'autorespond_requests' : False,
753 'available_languages' : ['fr'],
755 'bounce_matching_headers' : '',
756 'bounce_processing' : False,
757 'convert_html_to_plaintext' : False,
758 'digestable' : False,
759 'digest_is_default' : False,
760 'discard_these_nonmembers' : [],
762 'encode_ascii_prefixes' : 2,
763 'filter_content' : False,
764 'first_strip_reply_to' : False,
765 'forward_auto_discards' : True,
766 'hold_these_nonmembers' : [],
767 'host_name' : 'listes.polytechnique.org',
768 'include_list_post_header' : False,
769 'include_rfc2369_headers' : False,
770 'max_num_recipients' : 0,
771 'new_member_options' : 256,
772 'nondigestable' : True,
773 'obscure_addresses' : True,
774 'preferred_language' : 'fr',
775 'reject_these_nonmembers' : [],
776 'reply_goes_to_list' : 0,
777 'reply_to_address' : '',
778 'require_explicit_destination' : False,
779 'send_reminders' : 0,
780 'send_welcome_msg' : True,
781 'topics_enabled' : False,
782 'umbrella_list' : False,
783 'unsubscribe_policy' : 0,
786 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
788 for (k
, v
) in check_opts
.iteritems():
789 if mlist
.__dict__
[k
] != v
:
790 options
[k
] = v
, mlist
.__dict__
[k
]
791 if correct
: mlist
.__dict__
[k
] = v
792 if mlist
.real_name
.lower() != listname
:
793 options
['real_name'] = listname
, mlist
.real_name
794 if correct
: mlist
.real_name
= listname
798 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
802 listname
= listname
.lower()
803 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
805 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
807 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
809 #-------------------------------------------------------------------------------
810 # super-admin procedures
813 def get_all_lists(userdesc
, perms
, vhost
):
814 """ Get all the list for the given vhost
817 prefix
= vhost
.lower()+VHOST_SEP
818 names
= Utils
.list_names()
822 if not name
.startswith(prefix
):
824 result
.append(name
.replace(prefix
, ''))
827 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
828 """ Get all the lists for the given user
831 names
= Utils
.list_names()
836 mlist
= MailList
.MailList(name
, lock
=0)
837 ismember
= email
in mlist
.getRegularMemberKeys()
838 isowner
= email
in mlist
.owner
839 if not ismember
and not isowner
:
841 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
842 result
.append({ 'list': mlist
.real_name
,
843 'addr': mlist
.real_name
.lower() + '@' + host
,
852 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
853 """ Change the email of a user
856 from_email
= from_email
.lower()
857 to_email
= to_email
.lower()
858 for list in Utils
.list_names():
860 mlist
= MailList
.MailList(list, lock
=0)
865 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
873 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
874 """ Create a new list.
877 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
878 if Utils
.list_exists(name
):
883 email
= to_forlife(o
)[0]
884 if email
is not None:
889 mlist
= MailList
.MailList()
891 oldmask
= os
.umask(002)
892 pw
= sha
.new('foobar').hexdigest()
895 mlist
.Create(name
, owner
[0], pw
)
899 mlist
.real_name
= listname
900 mlist
.host_name
= 'listes.polytechnique.org'
901 mlist
.description
= desc
903 mlist
.advertised
= int(advertise
) is 0
904 mlist
.default_member_moderation
= int(modlevel
) is 2
905 mlist
.generic_nonmember_action
= int(modlevel
) > 0
906 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
907 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
911 mlist
.subject_prefix
= '['+listname
+'] '
912 mlist
.max_message_size
= 0
914 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
915 mlist
.msg_footer
= "_______________________________________________\n" \
916 + "Liste de diffusion %(real_name)s\n" \
917 + "http://listes.polytechnique.org/members/" + inverted_listname
919 mlist
.header_filter_rules
= []
920 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
921 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
923 if ON_CREATE_CMD
!= '':
924 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
927 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
928 mass_subscribe(userdesc
, perms
, mlist
, members
)
933 # avoid the "-1 mail to moderate" bug
934 mlist
= MailList
.MailList(name
)
936 mlist
._UpdateRecords()
942 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
947 lname
= mlist
.internal_name()
949 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
950 # remove stalled locks
951 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
952 fn_lname
= filename
.split('.')[0]
953 if fn_lname
== lname
:
954 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
958 os
.path
.join('archives', 'private', lname
),
959 os
.path
.join('archives', 'private', lname
+'.mbox'),
960 os
.path
.join('archives', 'public', lname
),
961 os
.path
.join('archives', 'public', lname
+'.mbox')
963 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
966 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
967 """ Remove a user from all the lists.
970 if not del_from_promo
:
971 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
972 for list in Utils
.list_names():
976 mlist
= MailList
.MailList(list, lock
=0)
981 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
989 #-------------------------------------------------------------------------------
992 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
993 allow_reuse_address
= True
995 ################################################################################
999 #-------------------------------------------------------------------------------
1000 # use Mailman user and group (not root)
1001 # fork in background if asked to
1004 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1005 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1008 os
.setregid(gid
, gid
)
1009 os
.setreuid(uid
, uid
)
1011 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1013 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1016 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1018 if o
== '-f' and os
.fork():
1021 i18n
.set_language('fr')
1025 #-------------------------------------------------------------------------------
1028 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1031 server
.register_function(get_lists
)
1032 server
.register_function(subscribe
)
1033 server
.register_function(unsubscribe
)
1035 server
.register_function(get_members
)
1037 server
.register_function(get_members_limit
)
1038 server
.register_function(get_owners
)
1040 server
.register_function(replace_email
)
1041 server
.register_function(mass_subscribe
)
1042 server
.register_function(mass_unsubscribe
)
1043 server
.register_function(add_owner
)
1044 server
.register_function(del_owner
)
1046 server
.register_function(get_pending_ops
)
1047 server
.register_function(handle_request
)
1048 server
.register_function(get_pending_sub
)
1049 server
.register_function(get_pending_mail
)
1051 server
.register_function(get_owner_options
)
1052 server
.register_function(set_owner_options
)
1053 server
.register_function(add_to_wl
)
1054 server
.register_function(del_from_wl
)
1055 server
.register_function(get_bogo_level
)
1056 server
.register_function(set_bogo_level
)
1058 server
.register_function(get_admin_options
)
1059 server
.register_function(set_admin_options
)
1061 server
.register_function(check_options
)
1063 server
.register_function(get_all_lists
)
1064 server
.register_function(get_all_user_lists
)
1065 server
.register_function(change_user_email
)
1066 server
.register_function(create_list
)
1067 server
.register_function(delete_list
)
1069 server
.register_function(kill
)
1071 server
.serve_forever()
1073 # vim:set et sw=4 sts=4 sws=4: