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_HOST
= get_config('Core', 'dbhost')
73 MYSQL_DB
= get_config('Core', 'dbdb')
75 PLATAL_DOMAIN
= get_config('Mail', 'domain')
76 PLATAL_DOMAIN2
= get_config('Mail', 'domain2', '')
77 sys
.stderr
.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN
)
78 sys
.stderr
.write("MYSQL_DB = %s\n" % MYSQL_DB
)
80 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
81 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
83 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
84 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
86 ################################################################################
90 #------------------------------------------------
91 # Manage Basic authentication
94 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
96 """XMLRPC Request Handler
97 This request handler is used to provide BASIC HTTP user authentication.
98 It first overloads the do_POST() function, authenticates the user, then
99 calls the super.do_POST().
101 Moreover, we override _dispatch, so that we call functions with as first
102 argument a UserDesc taken from the database, containing name, email and perms
105 def _get_function(self
, method
):
107 # check to see if a matching function has been registered
108 return self
.server
.funcs
[method
]
110 raise Exception('method "%s" is not supported' % method
)
112 def is_rpc_path_valid(self
):
115 def _dispatch(self
, method
, params
):
116 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
120 _
, auth
= self
.headers
["authorization"].split()
121 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
122 vhost
= self
.path
.split('/')[1].lower()
123 self
.data
= self
.getUser(uid
, md5
, vhost
)
124 if self
.data
is None:
126 # Call super.do_POST() to do the actual work
127 SimpleXMLRPCRequestHandler
.do_POST(self
)
129 self
.send_response(401)
132 def getUser(self
, uid
, md5
, vhost
):
133 res
= mysql_fetchone ("""SELECT a.full_name, IF(s.email IS NULL, a.email, CONCAT(s.email, '@%s')),
134 IF (a.is_admin, 'admin',
135 IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL))
137 INNER JOIN account_types AS at ON (at.type = a.type)
138 LEFT JOIN email_source_account AS s ON (s.uid = a.uid AND s.type = 'forlife')
139 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
141 %
(PLATAL_DOMAIN
, uid
, md5
))
143 name
, forlife
, perms
= res
144 if vhost
!= PLATAL_DOMAIN
and perms
!= 'admin':
145 res
= mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists')
146 FROM group_members AS m
147 INNER JOIN groups AS g ON (m.asso_id = g.id)
148 WHERE uid = '%s' AND mail_domain = '%s'""" \
152 userdesc
= UserDesc(forlife
, name
, None, 0)
153 return (userdesc
, perms
, vhost
)
155 print >> sys
.stderr
, "no user found for uid: %s, passwd: %s" %
(uid
, md5
)
158 ################################################################################
162 #-------------------------------------------------------------------------------
167 db
= MySQLdb
.connect(
176 def mysql_fetchone(query
):
181 if int(mysql
.rowcount
) > 0:
182 ret
= mysql
.fetchone()
187 def is_admin_on(userdesc
, perms
, mlist
):
188 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
191 def quote(s
, is_header
=False):
193 h
= Utils
.oneline(s
, 'iso-8859-1')
196 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
197 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
199 def to_forlife(email
):
201 mbox
, fqdn
= email
.split('@')
205 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
206 res
= mysql_fetchone("""SELECT CONCAT(s1.email, '@%s'), a.full_name
208 INNER JOIN email_source_account AS s1 ON (a.uid = s1.uid AND s1.type = 'forlife')
209 INNER JOIN email_source_account AS s2 ON (a.uid = s2.uid AND s2.email = '%s')
210 WHERE a.state = 'active'
212 %
(PLATAL_DOMAIN
, mbox
))
217 return (email
.lower(), mbox
)
220 # see /usr/lib/mailman/bin/rmlist
222 def remove_it(listname
, filename
):
223 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
225 elif os
.path
.isdir(filename
):
226 shutil
.rmtree(filename
)
232 def has_annotation(method
, name
):
233 """ Check if the method contains the given annoation.
235 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
237 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
238 """Dispatch the call to the right handler.
239 This function checks the options of the called method the set the environment of the call.
240 The dispatcher uses method annotation (special tokens in the documentation of the method) to
241 guess the requested environment:
242 @mlist: the handler requires a mlist object instead of the vhost/listname couple
243 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
244 @edit: the handler edit the mlist (@mlist MUST be specified)
245 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
246 @root: the handler requires site admin rights
249 print >> sys
.stderr
, "calling method: %s" % method
250 if has_annotation(method
, "root") and perms
!= "admin":
252 if has_annotation(method
, "mlist"):
253 listname
= str(arg
[0])
255 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
256 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
258 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
259 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
261 return method(userdesc
, perms
, mlist
, *arg
)
263 return method(userdesc
, perms
, vhost
, *arg
)
265 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
269 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
270 """Call the given method after locking the mlist.
274 ret
= method(userdesc
, perms
, mlist
, *arg
)
280 traceback
.print_exc(file=sys
.stderr
)
281 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
284 # TODO: use finally when switching to python 2.5
286 #-------------------------------------------------------------------------------
290 def is_subscription_pending(userdesc
, perms
, mlist
):
291 for id in mlist
.GetSubscriptionIds():
292 if userdesc
.address
== mlist
.GetRecord(id)[1]:
296 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
297 members
= mlist
.getRegularMemberKeys()
298 is_member
= userdesc
.address
in members
299 is_owner
= userdesc
.address
in mlist
.owner
300 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
302 if not is_member
and (mlist
.subscribe_policy
> 1):
303 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
307 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
309 'list' : mlist
.real_name
,
310 'addr' : mlist
.real_name
.lower() + '@' + host
,
312 'desc' : quote(mlist
.description
),
313 'info' : quote(mlist
.info
),
314 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
315 'ins' : mlist
.subscribe_policy
> 1,
316 'priv' : 1-mlist
.advertised
,
317 'sub' : 2*is_member
+ is_pending
,
319 'nbsub': len(members
)
321 return (details
, members
)
324 def get_options(userdesc
, perms
, mlist
, opts
):
325 """ Get the options of a list.
330 for (k
, v
) in mlist
.__dict__
.iteritems():
333 options
[k
] = quote(v
)
335 details
= get_list_info(userdesc
, perms
, mlist
)[0]
336 return (details
, options
)
338 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
339 for (k
, v
) in vals
.iteritems():
342 if k
== 'default_member_moderation':
343 for member
in mlist
.getMembers():
344 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
345 t
= type(mlist
.__dict__
[k
])
346 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
347 elif t
is int: mlist
.__dict__
[k
] = int(v
)
348 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
349 else: mlist
.__dict__
[k
] = v
352 #-------------------------------------------------------------------------------
353 # users procedures for [ index.php ]
356 def get_lists(userdesc
, perms
, vhost
, email
=None):
357 """ List available lists for the given vhost
362 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
363 prefix
= vhost
.lower()+VHOST_SEP
364 names
= Utils
.list_names()
368 if not name
.startswith(prefix
):
371 mlist
= MailList
.MailList(name
, lock
=0)
375 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
376 if details
is not None:
377 result
.append(details
[0])
379 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
383 def subscribe(userdesc
, perms
, mlist
):
384 """ Subscribe to a list.
388 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
389 mlist
.ApprovedAddMember(userdesc
)
394 mlist
.AddMember(userdesc
)
395 except Errors
.MMNeedApproval
:
399 def unsubscribe(userdesc
, perms
, mlist
):
400 """ Unsubscribe from a list
404 mlist
.ApprovedDeleteMember(userdesc
.address
)
407 #-------------------------------------------------------------------------------
408 # users procedures for [ index.php ]
411 def get_name(member
):
413 return quote(mlist
.getMemberName(member
))
417 def get_members(userdesc
, perms
, mlist
):
418 """ List the members of a list.
421 infos
= get_list_info(userdesc
, perms
, mlist
)
423 # Do not return None, this is not serializable
425 details
, members
= infos
427 members
= map(lambda member
: (get_name(member
), member
), members
)
428 return (details
, members
, mlist
.owner
)
431 #-------------------------------------------------------------------------------
432 # users procedures for [ trombi.php ]
435 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
436 """ Get a range of members of the list.
439 members
= get_members(userdesc
, perms
, mlist
)[1]
440 i
= int(page
) * int(nb_per_page
)
441 return (len(members
), members
[i
:i
+int(nb_per_page
)])
443 def get_owners(userdesc
, perms
, mlist
):
444 """ Get the owners of the list.
447 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
448 return (details
, owners
)
451 #-------------------------------------------------------------------------------
452 # owners procedures [ admin.php ]
455 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
456 """ Replace the address of a member by another one.
461 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
464 def mass_subscribe(userdesc
, perms
, mlist
, users
):
465 """ Add a list of users to the list.
470 if not isinstance(users
, list):
471 raise Exception("userlist must be a list")
472 members
= mlist
.getRegularMemberKeys()
475 email
, name
= to_forlife(user
)
476 if ( email
is None ) or ( email
in members
):
478 userd
= UserDesc(email
, name
, None, 0)
479 mlist
.ApprovedAddMember(userd
)
480 added
.append( (quote(userd
.fullname
), userd
.address
) )
483 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
484 """ Remove a list of users from the list.
489 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
492 def add_owner(userdesc
, perms
, mlist
, user
):
493 """ Add a owner to the list.
498 email
= to_forlife(user
)[0]
501 if email
not in mlist
.owner
:
502 mlist
.owner
.append(email
)
505 def del_owner(userdesc
, perms
, mlist
, user
):
506 """ Remove a owner of the list.
511 if len(mlist
.owner
) < 2:
513 mlist
.owner
.remove(user
)
516 #-------------------------------------------------------------------------------
517 # owners procedures [ admin.php ]
520 def get_pending_ops(userdesc
, perms
, mlist
):
521 """ Get the list of operation waiting for an action from the owners.
529 for id in mlist
.GetSubscriptionIds():
530 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
532 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
537 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
538 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
540 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
543 for id in mlist
.GetHeldMessageIds():
544 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
545 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
547 size
= os
.path
.getsize(fpath
)
549 if e
.errno
<> errno
.ENOENT
: raise
552 msg
= readMessage(fpath
)
553 fromX
= msg
.has_key("X-Org-Mail")
558 'sender': quote(sender
, True),
560 'subj' : quote(subject
, True),
568 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
569 """ Handle a moderation request.
574 # Force encoding to mailman's default for french, since this is what
575 # Mailman will use internally
576 # LC_DESCRIPTIONS is a dict of lang => (name, charset, direction) tuples.
577 encoding
= mm_cfg
.LC_DESCRIPTIONS
['fr'][1]
578 comment
= comment
.encode(encoding
, 'replace')
579 mlist
.HandleRequest(int(id), int(value
), comment
)
582 def get_pending_sub(userdesc
, perms
, mlist
, id):
583 """ Get informations about a given subscription moderation.
590 if id in mlist
.GetSubscriptionIds():
591 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
593 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
594 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
596 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
599 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
600 """ Get informations about a given mail moderation.
605 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
606 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
607 size
= os
.path
.getsize(fpath
)
608 msg
= readMessage(fpath
)
611 return quote(str(msg
))
614 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
615 c
= part
.get_payload()
616 if c
is not None: results_plain
.append (c
)
617 results_plain
= map(lambda x
: quote(x
), results_plain
)
618 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
619 c
= part
.get_payload()
620 if c
is not None: results_html
.append (c
)
621 results_html
= map(lambda x
: quote(x
), results_html
)
623 'sender': quote(sender
, True),
625 'subj' : quote(subject
, True),
627 'parts_plain' : results_plain
,
628 'parts_html': results_html
}
630 #-------------------------------------------------------------------------------
631 # owner options [ options.php ]
634 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
635 'default_member_moderation', 'generic_nonmember_action', 'info', \
636 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
639 def get_owner_options(userdesc
, perms
, mlist
):
640 """ Get the owner options of a list.
644 return get_options(userdesc
, perms
, mlist
, owner_opts
)
646 def set_owner_options(userdesc
, perms
, mlist
, values
):
647 """ Set the owner options of a list.
652 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
654 def add_to_wl(userdesc
, perms
, mlist
, addr
):
655 """ Add addr to the whitelist
660 mlist
.accept_these_nonmembers
.append(addr
)
663 def del_from_wl(userdesc
, perms
, mlist
, addr
):
664 """ Remove an address from the whitelist
669 mlist
.accept_these_nonmembers
.remove(addr
)
672 def get_bogo_level(userdesc
, perms
, mlist
):
673 """ Compute bogo level from the filtering rules set up on the list.
677 if len(mlist
.header_filter_rules
) == 0:
684 # The first rule filters Unsure mails
685 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
689 # Check the other rules:
690 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
691 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
692 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
694 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
697 action
= mlist
.header_filter_rules
[filterbase
][1]
698 if action
== mm_cfg
.HOLD
:
700 elif action
== mm_cfg
.DISCARD
:
702 return (filterlevel
<< 1) + unsurelevel
704 def set_bogo_level(userdesc
, perms
, mlist
, level
):
705 """ Set filter to the specified level.
712 # The level is a combination of a spam filtering level and unsure filtering level
713 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
714 # - the spam filtering level is a number growing with filtering strength
715 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
716 bogolevel
= int(level
)
717 filterlevel
= bogolevel
>> 1
718 unsurelevel
= bogolevel
& 1
720 # Set up unusre filtering
722 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
724 # Set up spam filtering
726 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
727 elif filterlevel
is 2:
728 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
729 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
730 elif filterlevel
is 3:
731 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
734 if mlist
.header_filter_rules
!= hfr
:
735 mlist
.header_filter_rules
= hfr
738 #-------------------------------------------------------------------------------
739 # admin procedures [ soptions.php ]
742 admin_opts
= [ 'advertised', 'archive', \
743 'max_message_size', 'msg_footer', 'msg_header']
745 def get_admin_options(userdesc
, perms
, mlist
):
746 """ Get administrator options.
750 return get_options(userdesc
, perms
, mlist
, admin_opts
)
752 def set_admin_options(userdesc
, perms
, mlist
, values
):
753 """ Set administrator options.
758 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
760 #-------------------------------------------------------------------------------
761 # admin procedures [ check.php ]
765 'acceptable_aliases' : '',
766 'admin_immed_notify' : True,
767 'administrivia' : True,
768 'anonymous_list' : False,
769 'autorespond_admin' : False,
770 'autorespond_postings' : False,
771 'autorespond_requests' : False,
772 'available_languages' : ['fr'],
774 'bounce_matching_headers' : '',
775 'bounce_processing' : False,
776 'convert_html_to_plaintext' : False,
777 'digestable' : False,
778 'digest_is_default' : False,
779 'discard_these_nonmembers' : [],
781 'encode_ascii_prefixes' : 2,
782 'filter_content' : False,
783 'first_strip_reply_to' : False,
784 'forward_auto_discards' : True,
785 'hold_these_nonmembers' : [],
786 'host_name' : 'listes.polytechnique.org',
787 'include_list_post_header' : False,
788 'include_rfc2369_headers' : False,
789 'max_num_recipients' : 0,
790 'new_member_options' : 256,
791 'nondigestable' : True,
792 'obscure_addresses' : True,
793 'preferred_language' : 'fr',
794 'reject_these_nonmembers' : [],
795 'reply_goes_to_list' : 0,
796 'reply_to_address' : '',
797 'require_explicit_destination' : False,
798 'send_reminders' : 0,
799 'send_welcome_msg' : True,
800 'topics_enabled' : False,
801 'umbrella_list' : False,
802 'unsubscribe_policy' : 0,
805 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
807 for (k
, v
) in check_opts
.iteritems():
808 if mlist
.__dict__
[k
] != v
:
809 options
[k
] = v
, mlist
.__dict__
[k
]
810 if correct
: mlist
.__dict__
[k
] = v
811 if mlist
.real_name
.lower() != listname
:
812 options
['real_name'] = listname
, mlist
.real_name
813 if correct
: mlist
.real_name
= listname
817 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
821 listname
= listname
.lower()
822 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
824 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
826 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
828 #-------------------------------------------------------------------------------
829 # super-admin procedures
832 def get_all_lists(userdesc
, perms
, vhost
):
833 """ Get all the list for the given vhost
836 prefix
= vhost
.lower()+VHOST_SEP
837 names
= Utils
.list_names()
841 if not name
.startswith(prefix
):
843 result
.append(name
.replace(prefix
, ''))
846 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
847 """ Get all the lists for the given user
850 names
= Utils
.list_names()
855 mlist
= MailList
.MailList(name
, lock
=0)
856 ismember
= email
in mlist
.getRegularMemberKeys()
857 isowner
= email
in mlist
.owner
858 if not ismember
and not isowner
:
860 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
861 result
.append({ 'list': mlist
.real_name
,
862 'addr': mlist
.real_name
.lower() + '@' + host
,
871 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
872 """ Change the email of a user
875 from_email
= from_email
.lower()
876 to_email
= to_email
.lower()
877 for list in Utils
.list_names():
879 mlist
= MailList
.MailList(list, lock
=0)
884 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
892 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
893 """ Create a new list.
896 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
897 if Utils
.list_exists(name
):
898 print >> sys
.stderr
, "List ", name
, " already exists"
903 email
= to_forlife(o
)
904 print >> sys
.stderr
, "owner in list", o
, email
906 if email
is not None:
909 print >> sys
.stderr
, "No owner found in ", owners
912 mlist
= MailList
.MailList()
914 oldmask
= os
.umask(002)
915 pw
= sha
.new('foobar').hexdigest()
918 mlist
.Create(name
, owner
[0], pw
)
922 mlist
.real_name
= listname
923 mlist
.host_name
= 'listes.polytechnique.org'
924 mlist
.description
= desc
926 mlist
.advertised
= int(advertise
) is 0
927 mlist
.default_member_moderation
= int(modlevel
) is 2
928 mlist
.generic_nonmember_action
= int(modlevel
) > 0
929 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
930 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
934 mlist
.subject_prefix
= '['+listname
+'] '
935 mlist
.max_message_size
= 0
937 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
938 mlist
.msg_footer
= "_______________________________________________\n" \
939 + "Liste de diffusion %(real_name)s\n" \
940 + "http://listes.polytechnique.org/members/" + inverted_listname
942 mlist
.header_filter_rules
= []
943 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
944 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
946 if ON_CREATE_CMD
!= '':
947 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
950 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
951 mass_subscribe(userdesc
, perms
, mlist
, members
)
956 # avoid the "-1 mail to moderate" bug
957 mlist
= MailList
.MailList(name
)
959 mlist
._UpdateRecords()
965 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
970 lname
= mlist
.internal_name()
972 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
973 # remove stalled locks
974 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
975 fn_lname
= filename
.split('.')[0]
976 if fn_lname
== lname
:
977 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
981 os
.path
.join('archives', 'private', lname
),
982 os
.path
.join('archives', 'private', lname
+'.mbox'),
983 os
.path
.join('archives', 'public', lname
),
984 os
.path
.join('archives', 'public', lname
+'.mbox')
986 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
989 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
990 """ Remove a user from all the lists.
993 if not del_from_promo
:
994 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
995 for list in Utils
.list_names():
999 mlist
= MailList
.MailList(list, lock
=0)
1004 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
1012 #-------------------------------------------------------------------------------
1015 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
1016 allow_reuse_address
= True
1018 ################################################################################
1022 #-------------------------------------------------------------------------------
1023 # use Mailman user and group (not root)
1024 # fork in background if asked to
1027 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1028 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1031 os
.setregid(gid
, gid
)
1032 os
.setreuid(uid
, uid
)
1034 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1036 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1039 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1041 if o
== '-f' and os
.fork():
1044 i18n
.set_language('fr')
1048 #-------------------------------------------------------------------------------
1051 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1054 server
.register_function(get_lists
)
1055 server
.register_function(subscribe
)
1056 server
.register_function(unsubscribe
)
1058 server
.register_function(get_members
)
1060 server
.register_function(get_members_limit
)
1061 server
.register_function(get_owners
)
1063 server
.register_function(replace_email
)
1064 server
.register_function(mass_subscribe
)
1065 server
.register_function(mass_unsubscribe
)
1066 server
.register_function(add_owner
)
1067 server
.register_function(del_owner
)
1069 server
.register_function(get_pending_ops
)
1070 server
.register_function(handle_request
)
1071 server
.register_function(get_pending_sub
)
1072 server
.register_function(get_pending_mail
)
1074 server
.register_function(get_owner_options
)
1075 server
.register_function(set_owner_options
)
1076 server
.register_function(add_to_wl
)
1077 server
.register_function(del_from_wl
)
1078 server
.register_function(get_bogo_level
)
1079 server
.register_function(set_bogo_level
)
1081 server
.register_function(get_admin_options
)
1082 server
.register_function(set_admin_options
)
1084 server
.register_function(check_options
)
1086 server
.register_function(get_all_lists
)
1087 server
.register_function(get_all_user_lists
)
1088 server
.register_function(change_user_email
)
1089 server
.register_function(create_list
)
1090 server
.register_function(delete_list
)
1092 server
.register_function(kill
)
1094 server
.serve_forever()
1096 # vim:set et sw=4 sts=4 sws=4: