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
28 sys
.path
.append('/usr/lib/mailman/bin')
30 from pwd
import getpwnam
31 from grp
import getgrnam
33 from SimpleXMLRPCServer
import SimpleXMLRPCServer
34 from SimpleXMLRPCServer
import SimpleXMLRPCRequestHandler
37 from Mailman
import MailList
38 from Mailman
import Utils
39 from Mailman
import Message
40 from Mailman
import Errors
41 from Mailman
import mm_cfg
42 from Mailman
import i18n
43 from Mailman
.UserDesc
import UserDesc
44 from Mailman
.ListAdmin
import readMessage
45 from email
.Iterators
import typed_subpart_iterator
46 from threading
import Lock
48 class AuthFailed(Exception): pass
50 ################################################################################
54 #------------------------------------------------
56 config
= ConfigParser
.ConfigParser()
57 config
.read(os
.path
.dirname(__file__
)+'/../configs/platal.ini')
58 config
.read(os
.path
.dirname(__file__
)+'/../configs/platal.conf')
60 def get_config(sec
, val
, default
=None):
62 return config
.get(sec
, val
)[1:-1]
63 except ConfigParser
.NoOptionError
, e
:
65 sys
.stderr
.write('%s\n' % str
(e
))
70 MYSQL_USER
= get_config('Core', 'dbuser')
71 MYSQL_PASS
= get_config('Core', 'dbpwd')
72 MYSQL_DB
= get_config('Core', 'dbdb')
74 PLATAL_DOMAIN
= get_config('Mail', 'domain')
75 PLATAL_DOMAIN2
= get_config('Mail', 'domain2', '')
76 sys
.stderr
.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN
)
77 sys
.stderr
.write("MYSQL_DB = %s\n" % MYSQL_DB
)
79 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
80 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
82 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
83 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
85 ################################################################################
89 #------------------------------------------------
90 # Manage Basic authentication
93 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
95 """XMLRPC Request Handler
96 This request handler is used to provide BASIC HTTP user authentication.
97 It first overloads the do_POST() function, authenticates the user, then
98 calls the super.do_POST().
100 Moreover, we override _dispatch, so that we call functions with as first
101 argument a UserDesc taken from the database, containing name, email and perms
104 def _get_function(self
, method
):
106 # check to see if a matching function has been registered
107 return self
.server
.funcs
[method
]
109 raise Exception('method "%s" is not supported' % method
)
111 def is_rpc_path_valid(self
):
114 def _dispatch(self
, method
, params
):
115 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
119 _
, auth
= self
.headers
["authorization"].split()
120 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
121 vhost
= self
.path
.split('/')[1].lower()
122 self
.data
= self
.getUser(uid
, md5
, vhost
)
123 if self
.data
is None:
125 # Call super.do_POST() to do the actual work
126 SimpleXMLRPCRequestHandler
.do_POST(self
)
128 self
.send_response(401)
131 def getUser(self
, uid
, md5
, vhost
):
132 res
= mysql_fetchone ("""SELECT a.full_name, IF(s.email IS NULL, a.email, CONCAT(s.email, '@%s')),
133 IF (a.is_admin, 'admin',
134 IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL))
136 INNER JOIN account_types AS at ON (at.type = a.type)
137 LEFT JOIN email_source_account AS s ON (s.uid = a.uid AND s.type = 'forlife')
138 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
140 %
(PLATAL_DOMAIN
, uid
, md5
))
142 name
, forlife
, perms
= res
143 if vhost
!= PLATAL_DOMAIN
and perms
!= 'admin':
144 res
= mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists')
145 FROM group_members AS m
146 INNER JOIN groups AS g ON (m.asso_id = g.id)
147 WHERE uid = '%s' AND mail_domain = '%s'""" \
151 userdesc
= UserDesc(forlife
, name
, None, 0)
152 return (userdesc
, perms
, vhost
)
154 print >> sys
.stderr
, "no user found for uid: %s, passwd: %s" %
(uid
, md5
)
157 ################################################################################
161 #-------------------------------------------------------------------------------
166 db
= MySQLdb
.connect(
170 unix_socket
='/var/run/mysqld/mysqld.sock')
175 def mysql_fetchone(query
):
180 if int(mysql
.rowcount
) > 0:
181 ret
= mysql
.fetchone()
186 def is_admin_on(userdesc
, perms
, mlist
):
187 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
190 def quote(s
, is_header
=False):
192 h
= Utils
.oneline(s
, 'iso-8859-1')
195 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
196 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
198 def to_forlife(email
):
200 mbox
, fqdn
= email
.split('@')
204 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
205 res
= mysql_fetchone("""SELECT CONCAT(s1.email, '@%s'), a.full_name
207 INNER JOIN email_source_account AS s1 ON (a.uid = s1.uid AND s1.type = 'forlife')
208 INNER JOIN email_source_account AS s2 ON (a.uid = s2.uid AND s2.email = '%s')
209 WHERE a.state = 'active'
211 %
(PLATAL_DOMAIN
, mbox
))
216 return (email
.lower(), mbox
)
219 # see /usr/lib/mailman/bin/rmlist
221 def remove_it(listname
, filename
):
222 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
224 elif os
.path
.isdir(filename
):
225 shutil
.rmtree(filename
)
231 def has_annotation(method
, name
):
232 """ Check if the method contains the given annoation.
234 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
236 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
237 """Dispatch the call to the right handler.
238 This function checks the options of the called method the set the environment of the call.
239 The dispatcher uses method annotation (special tokens in the documentation of the method) to
240 guess the requested environment:
241 @mlist: the handler requires a mlist object instead of the vhost/listname couple
242 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
243 @edit: the handler edit the mlist (@mlist MUST be specified)
244 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
245 @root: the handler requires site admin rights
248 print >> sys
.stderr
, "calling method: %s" % method
249 if has_annotation(method
, "root") and perms
!= "admin":
251 if has_annotation(method
, "mlist"):
252 listname
= str(arg
[0])
254 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
255 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
257 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
258 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
260 return method(userdesc
, perms
, mlist
, *arg
)
262 return method(userdesc
, perms
, vhost
, *arg
)
264 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
268 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
269 """Call the given method after locking the mlist.
273 ret
= method(userdesc
, perms
, mlist
, *arg
)
279 traceback
.print_exc(file=sys
.stderr
)
280 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
283 # TODO: use finally when switching to python 2.5
285 #-------------------------------------------------------------------------------
289 def is_subscription_pending(userdesc
, perms
, mlist
):
290 for id in mlist
.GetSubscriptionIds():
291 if userdesc
.address
== mlist
.GetRecord(id)[1]:
295 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
296 members
= mlist
.getRegularMemberKeys()
297 is_member
= userdesc
.address
in members
298 is_owner
= userdesc
.address
in mlist
.owner
299 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
301 if not is_member
and (mlist
.subscribe_policy
> 1):
302 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
306 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
308 'list' : mlist
.real_name
,
309 'addr' : mlist
.real_name
.lower() + '@' + host
,
311 'desc' : quote(mlist
.description
),
312 'info' : quote(mlist
.info
),
313 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
314 'ins' : mlist
.subscribe_policy
> 1,
315 'priv' : 1-mlist
.advertised
,
316 'sub' : 2*is_member
+ is_pending
,
318 'nbsub': len(members
)
320 return (details
, members
)
323 def get_options(userdesc
, perms
, mlist
, opts
):
324 """ Get the options of a list.
329 for (k
, v
) in mlist
.__dict__
.iteritems():
332 options
[k
] = quote(v
)
334 details
= get_list_info(userdesc
, perms
, mlist
)[0]
335 return (details
, options
)
337 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
338 for (k
, v
) in vals
.iteritems():
341 if k
== 'default_member_moderation':
342 for member
in mlist
.getMembers():
343 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
344 t
= type(mlist
.__dict__
[k
])
345 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
346 elif t
is int: mlist
.__dict__
[k
] = int(v
)
347 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
348 else: mlist
.__dict__
[k
] = v
351 #-------------------------------------------------------------------------------
352 # users procedures for [ index.php ]
355 def get_lists(userdesc
, perms
, vhost
, email
=None):
356 """ List available lists for the given vhost
361 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
362 prefix
= vhost
.lower()+VHOST_SEP
363 names
= Utils
.list_names()
367 if not name
.startswith(prefix
):
370 mlist
= MailList
.MailList(name
, lock
=0)
374 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
375 if details
is not None:
376 result
.append(details
[0])
378 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
382 def subscribe(userdesc
, perms
, mlist
):
383 """ Subscribe to a list.
387 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
388 mlist
.ApprovedAddMember(userdesc
)
393 mlist
.AddMember(userdesc
)
394 except Errors
.MMNeedApproval
:
398 def unsubscribe(userdesc
, perms
, mlist
):
399 """ Unsubscribe from a list
403 mlist
.ApprovedDeleteMember(userdesc
.address
)
406 #-------------------------------------------------------------------------------
407 # users procedures for [ index.php ]
410 def get_name(member
):
412 return quote(mlist
.getMemberName(member
))
416 def get_members(userdesc
, perms
, mlist
):
417 """ List the members of a list.
420 infos
= get_list_info(userdesc
, perms
, mlist
)
422 # Do not return None, this is not serializable
424 details
, members
= infos
426 members
= map(lambda member
: (get_name(member
), member
), members
)
427 return (details
, members
, mlist
.owner
)
430 #-------------------------------------------------------------------------------
431 # users procedures for [ trombi.php ]
434 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
435 """ Get a range of members of the list.
438 members
= get_members(userdesc
, perms
, mlist
)[1]
439 i
= int(page
) * int(nb_per_page
)
440 return (len(members
), members
[i
:i
+int(nb_per_page
)])
442 def get_owners(userdesc
, perms
, mlist
):
443 """ Get the owners of the list.
446 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
447 return (details
, owners
)
450 #-------------------------------------------------------------------------------
451 # owners procedures [ admin.php ]
454 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
455 """ Replace the address of a member by another one.
460 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
463 def mass_subscribe(userdesc
, perms
, mlist
, users
):
464 """ Add a list of users to the list.
469 if not isinstance(users
, list):
470 raise Exception("userlist must be a list")
471 members
= mlist
.getRegularMemberKeys()
474 email
, name
= to_forlife(user
)
475 if ( email
is None ) or ( email
in members
):
477 userd
= UserDesc(email
, name
, None, 0)
478 mlist
.ApprovedAddMember(userd
)
479 added
.append( (quote(userd
.fullname
), userd
.address
) )
482 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
483 """ Remove a list of users from the list.
488 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
491 def add_owner(userdesc
, perms
, mlist
, user
):
492 """ Add a owner to the list.
497 email
= to_forlife(user
)[0]
500 if email
not in mlist
.owner
:
501 mlist
.owner
.append(email
)
504 def del_owner(userdesc
, perms
, mlist
, user
):
505 """ Remove a owner of the list.
510 if len(mlist
.owner
) < 2:
512 mlist
.owner
.remove(user
)
515 #-------------------------------------------------------------------------------
516 # owners procedures [ admin.php ]
519 def get_pending_ops(userdesc
, perms
, mlist
):
520 """ Get the list of operation waiting for an action from the owners.
528 for id in mlist
.GetSubscriptionIds():
529 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
531 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
536 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
537 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
539 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
542 for id in mlist
.GetHeldMessageIds():
543 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
544 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
546 size
= os
.path
.getsize(fpath
)
548 if e
.errno
<> errno
.ENOENT
: raise
551 msg
= readMessage(fpath
)
552 fromX
= msg
.has_key("X-Org-Mail")
557 'sender': quote(sender
, True),
559 'subj' : quote(subject
, True),
567 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
568 """ Handle a moderation request.
573 # Force encoding to mailman's default for french, since this is what
574 # Mailman will use internally
575 # LC_DESCRIPTIONS is a dict of lang => (name, charset, direction) tuples.
576 encoding
= mm_cfg
.LC_DESCRIPTIONS
['fr'][1]
577 comment
= comment
.encode(encoding
, 'replace')
578 mlist
.HandleRequest(int(id), int(value
), comment
)
581 def get_pending_sub(userdesc
, perms
, mlist
, id):
582 """ Get informations about a given subscription moderation.
589 if id in mlist
.GetSubscriptionIds():
590 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
592 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
593 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
595 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
598 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
599 """ Get informations about a given mail moderation.
604 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
605 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
606 size
= os
.path
.getsize(fpath
)
607 msg
= readMessage(fpath
)
610 return quote(str(msg
))
613 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
614 c
= part
.get_payload()
615 if c
is not None: results_plain
.append (c
)
616 results_plain
= map(lambda x
: quote(x
), results_plain
)
617 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
618 c
= part
.get_payload()
619 if c
is not None: results_html
.append (c
)
620 results_html
= map(lambda x
: quote(x
), results_html
)
622 'sender': quote(sender
, True),
624 'subj' : quote(subject
, True),
626 'parts_plain' : results_plain
,
627 'parts_html': results_html
}
629 #-------------------------------------------------------------------------------
630 # owner options [ options.php ]
633 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
634 'default_member_moderation', 'generic_nonmember_action', 'info', \
635 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
638 def get_owner_options(userdesc
, perms
, mlist
):
639 """ Get the owner options of a list.
643 return get_options(userdesc
, perms
, mlist
, owner_opts
)
645 def set_owner_options(userdesc
, perms
, mlist
, values
):
646 """ Set the owner options of a list.
651 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
653 def add_to_wl(userdesc
, perms
, mlist
, addr
):
654 """ Add addr to the whitelist
659 mlist
.accept_these_nonmembers
.append(addr
)
662 def del_from_wl(userdesc
, perms
, mlist
, addr
):
663 """ Remove an address from the whitelist
668 mlist
.accept_these_nonmembers
.remove(addr
)
671 def get_bogo_level(userdesc
, perms
, mlist
):
672 """ Compute bogo level from the filtering rules set up on the list.
676 if len(mlist
.header_filter_rules
) == 0:
683 # The first rule filters Unsure mails
684 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
688 # Check the other rules:
689 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
690 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
691 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
693 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
696 action
= mlist
.header_filter_rules
[filterbase
][1]
697 if action
== mm_cfg
.HOLD
:
699 elif action
== mm_cfg
.DISCARD
:
701 return (filterlevel
<< 1) + unsurelevel
703 def set_bogo_level(userdesc
, perms
, mlist
, level
):
704 """ Set filter to the specified level.
711 # The level is a combination of a spam filtering level and unsure filtering level
712 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
713 # - the spam filtering level is a number growing with filtering strength
714 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
715 bogolevel
= int(level
)
716 filterlevel
= bogolevel
>> 1
717 unsurelevel
= bogolevel
& 1
719 # Set up unusre filtering
721 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
723 # Set up spam filtering
725 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
726 elif filterlevel
is 2:
727 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
728 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
729 elif filterlevel
is 3:
730 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
733 if mlist
.header_filter_rules
!= hfr
:
734 mlist
.header_filter_rules
= hfr
737 #-------------------------------------------------------------------------------
738 # admin procedures [ soptions.php ]
741 admin_opts
= [ 'advertised', 'archive', \
742 'max_message_size', 'msg_footer', 'msg_header']
744 def get_admin_options(userdesc
, perms
, mlist
):
745 """ Get administrator options.
749 return get_options(userdesc
, perms
, mlist
, admin_opts
)
751 def set_admin_options(userdesc
, perms
, mlist
, values
):
752 """ Set administrator options.
757 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
759 #-------------------------------------------------------------------------------
760 # admin procedures [ check.php ]
764 'acceptable_aliases' : '',
765 'admin_immed_notify' : True,
766 'administrivia' : True,
767 'anonymous_list' : False,
768 'autorespond_admin' : False,
769 'autorespond_postings' : False,
770 'autorespond_requests' : False,
771 'available_languages' : ['fr'],
773 'bounce_matching_headers' : '',
774 'bounce_processing' : False,
775 'convert_html_to_plaintext' : False,
776 'digestable' : False,
777 'digest_is_default' : False,
778 'discard_these_nonmembers' : [],
780 'encode_ascii_prefixes' : 2,
781 'filter_content' : False,
782 'first_strip_reply_to' : False,
783 'forward_auto_discards' : True,
784 'hold_these_nonmembers' : [],
785 'host_name' : 'listes.polytechnique.org',
786 'include_list_post_header' : False,
787 'include_rfc2369_headers' : False,
788 'max_num_recipients' : 0,
789 'new_member_options' : 256,
790 'nondigestable' : True,
791 'obscure_addresses' : True,
792 'preferred_language' : 'fr',
793 'reject_these_nonmembers' : [],
794 'reply_goes_to_list' : 0,
795 'reply_to_address' : '',
796 'require_explicit_destination' : False,
797 'send_reminders' : 0,
798 'send_welcome_msg' : True,
799 'topics_enabled' : False,
800 'umbrella_list' : False,
801 'unsubscribe_policy' : 0,
804 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
806 for (k
, v
) in check_opts
.iteritems():
807 if mlist
.__dict__
[k
] != v
:
808 options
[k
] = v
, mlist
.__dict__
[k
]
809 if correct
: mlist
.__dict__
[k
] = v
810 if mlist
.real_name
.lower() != listname
:
811 options
['real_name'] = listname
, mlist
.real_name
812 if correct
: mlist
.real_name
= listname
816 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
820 listname
= listname
.lower()
821 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
823 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
825 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
827 #-------------------------------------------------------------------------------
828 # super-admin procedures
831 def get_all_lists(userdesc
, perms
, vhost
):
832 """ Get all the list for the given vhost
835 prefix
= vhost
.lower()+VHOST_SEP
836 names
= Utils
.list_names()
840 if not name
.startswith(prefix
):
842 result
.append(name
.replace(prefix
, ''))
845 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
846 """ Get all the lists for the given user
849 names
= Utils
.list_names()
854 mlist
= MailList
.MailList(name
, lock
=0)
855 ismember
= email
in mlist
.getRegularMemberKeys()
856 isowner
= email
in mlist
.owner
857 if not ismember
and not isowner
:
859 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
860 result
.append({ 'list': mlist
.real_name
,
861 'addr': mlist
.real_name
.lower() + '@' + host
,
870 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
871 """ Change the email of a user
874 from_email
= from_email
.lower()
875 to_email
= to_email
.lower()
876 for list in Utils
.list_names():
878 mlist
= MailList
.MailList(list, lock
=0)
883 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
891 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
892 """ Create a new list.
895 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
896 if Utils
.list_exists(name
):
897 print >> sys
.stderr
, "List ", name
, " already exists"
902 email
= to_forlife(o
)
903 print >> sys
.stderr
, "owner in list", o
, email
905 if email
is not None:
908 print >> sys
.stderr
, "No owner found in ", owners
911 mlist
= MailList
.MailList()
913 oldmask
= os
.umask(002)
914 pw
= sha
.new('foobar').hexdigest()
917 mlist
.Create(name
, owner
[0], pw
)
921 mlist
.real_name
= listname
922 mlist
.host_name
= 'listes.polytechnique.org'
923 mlist
.description
= desc
925 mlist
.advertised
= int(advertise
) is 0
926 mlist
.default_member_moderation
= int(modlevel
) is 2
927 mlist
.generic_nonmember_action
= int(modlevel
) > 0
928 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
929 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
933 mlist
.subject_prefix
= '['+listname
+'] '
934 mlist
.max_message_size
= 0
936 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
937 mlist
.msg_footer
= "_______________________________________________\n" \
938 + "Liste de diffusion %(real_name)s\n" \
939 + "http://listes.polytechnique.org/members/" + inverted_listname
941 mlist
.header_filter_rules
= []
942 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
943 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
945 if ON_CREATE_CMD
!= '':
946 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
949 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
950 mass_subscribe(userdesc
, perms
, mlist
, members
)
955 # avoid the "-1 mail to moderate" bug
956 mlist
= MailList
.MailList(name
)
958 mlist
._UpdateRecords()
964 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
969 lname
= mlist
.internal_name()
971 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
972 # remove stalled locks
973 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
974 fn_lname
= filename
.split('.')[0]
975 if fn_lname
== lname
:
976 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
980 os
.path
.join('archives', 'private', lname
),
981 os
.path
.join('archives', 'private', lname
+'.mbox'),
982 os
.path
.join('archives', 'public', lname
),
983 os
.path
.join('archives', 'public', lname
+'.mbox')
985 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
988 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
989 """ Remove a user from all the lists.
992 if not del_from_promo
:
993 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
994 for list in Utils
.list_names():
998 mlist
= MailList
.MailList(list, lock
=0)
1003 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
1011 #-------------------------------------------------------------------------------
1014 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
1015 allow_reuse_address
= True
1017 ################################################################################
1021 #-------------------------------------------------------------------------------
1022 # use Mailman user and group (not root)
1023 # fork in background if asked to
1026 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1027 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1030 os
.setregid(gid
, gid
)
1031 os
.setreuid(uid
, uid
)
1033 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1035 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1038 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1040 if o
== '-f' and os
.fork():
1043 i18n
.set_language('fr')
1047 #-------------------------------------------------------------------------------
1050 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1053 server
.register_function(get_lists
)
1054 server
.register_function(subscribe
)
1055 server
.register_function(unsubscribe
)
1057 server
.register_function(get_members
)
1059 server
.register_function(get_members_limit
)
1060 server
.register_function(get_owners
)
1062 server
.register_function(replace_email
)
1063 server
.register_function(mass_subscribe
)
1064 server
.register_function(mass_unsubscribe
)
1065 server
.register_function(add_owner
)
1066 server
.register_function(del_owner
)
1068 server
.register_function(get_pending_ops
)
1069 server
.register_function(handle_request
)
1070 server
.register_function(get_pending_sub
)
1071 server
.register_function(get_pending_mail
)
1073 server
.register_function(get_owner_options
)
1074 server
.register_function(set_owner_options
)
1075 server
.register_function(add_to_wl
)
1076 server
.register_function(del_from_wl
)
1077 server
.register_function(get_bogo_level
)
1078 server
.register_function(set_bogo_level
)
1080 server
.register_function(get_admin_options
)
1081 server
.register_function(set_admin_options
)
1083 server
.register_function(check_options
)
1085 server
.register_function(get_all_lists
)
1086 server
.register_function(get_all_user_lists
)
1087 server
.register_function(change_user_email
)
1088 server
.register_function(create_list
)
1089 server
.register_function(delete_list
)
1091 server
.register_function(kill
)
1093 server
.serve_forever()
1095 # vim:set et sw=4 sts=4 sws=4: