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
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
)
75 sys
.stderr
.write("MYSQL_DB = %s\n" % MYSQL_DB
)
77 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
78 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
80 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
81 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
83 ################################################################################
87 #------------------------------------------------
88 # Manage Basic authentication
91 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
93 """XMLRPC Request Handler
94 This request handler is used to provide BASIC HTTP user authentication.
95 It first overloads the do_POST() function, authenticates the user, then
96 calls the super.do_POST().
98 Moreover, we override _dispatch, so that we call functions with as first
99 argument a UserDesc taken from the database, containing name, email and perms
102 def _get_function(self
, method
):
104 # check to see if a matching function has been registered
105 return self
.server
.funcs
[method
]
107 raise Exception('method "%s" is not supported' % method
)
109 def is_rpc_path_valid(self
):
112 def _dispatch(self
, method
, params
):
113 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
117 _
, auth
= self
.headers
["authorization"].split()
118 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
119 vhost
= self
.path
.split('/')[1].lower()
120 self
.data
= self
.getUser(uid
, md5
, vhost
)
121 if self
.data
is None:
123 # Call super.do_POST() to do the actual work
124 SimpleXMLRPCRequestHandler
.do_POST(self
)
126 self
.send_response(401)
129 def getUser(self
, uid
, md5
, vhost
):
130 res
= mysql_fetchone ("""SELECT a.full_name, IF(s.email IS NULL, a.email, CONCAT(s.email, '@%s')),
131 IF (a.is_admin, 'admin',
132 IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL))
134 INNER JOIN account_types AS at ON (at.type = a.type)
135 LEFT JOIN email_source_account AS s ON (s.uid = a.uid AND s.type = 'forlife')
136 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
138 %
(PLATAL_DOMAIN
, uid
, md5
))
140 name
, forlife
, perms
= res
141 if vhost
!= PLATAL_DOMAIN
and perms
!= 'admin':
142 res
= mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists')
143 FROM group_members AS m
144 INNER JOIN groups AS g ON (m.asso_id = g.id)
145 WHERE uid = '%s' AND mail_domain = '%s'""" \
149 userdesc
= UserDesc(forlife
, name
, None, 0)
150 return (userdesc
, perms
, vhost
)
152 print >> sys
.stderr
, "no user found for uid: %s, passwd: %s" %
(uid
, md5
)
155 ################################################################################
159 #-------------------------------------------------------------------------------
164 db
= MySQLdb
.connect(
168 unix_socket
='/var/run/mysqld/mysqld.sock')
173 def mysql_fetchone(query
):
178 if int(mysql
.rowcount
) > 0:
179 ret
= mysql
.fetchone()
184 def is_admin_on(userdesc
, perms
, mlist
):
185 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
188 def quote(s
, is_header
=False):
190 h
= Utils
.oneline(s
, 'iso-8859-1')
193 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
194 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
196 def to_forlife(email
):
198 mbox
, fqdn
= email
.split('@')
202 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
203 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
205 INNER JOIN email_source_account AS s1 ON (a.uid = s1.uid AND s1.type = 'forlife')
206 INNER JOIN email_source_account AS s2 ON (a.uid = s2.uid AND s2.email = '%s')
207 WHERE a.state = 'active'
209 %
(PLATAL_DOMAIN
, mbox
))
214 return (email
.lower(), mbox
)
217 # see /usr/lib/mailman/bin/rmlist
219 def remove_it(listname
, filename
):
220 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
222 elif os
.path
.isdir(filename
):
223 shutil
.rmtree(filename
)
229 def has_annotation(method
, name
):
230 """ Check if the method contains the given annoation.
232 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
234 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
235 """Dispatch the call to the right handler.
236 This function checks the options of the called method the set the environment of the call.
237 The dispatcher uses method annotation (special tokens in the documentation of the method) to
238 guess the requested environment:
239 @mlist: the handler requires a mlist object instead of the vhost/listname couple
240 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
241 @edit: the handler edit the mlist (@mlist MUST be specified)
242 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
243 @root: the handler requires site admin rights
246 print >> sys
.stderr
, "calling method: %s" % method
247 if has_annotation(method
, "root") and perms
!= "admin":
249 if has_annotation(method
, "mlist"):
250 listname
= str(arg
[0])
252 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
253 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
255 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
256 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
258 return method(userdesc
, perms
, mlist
, *arg
)
260 return method(userdesc
, perms
, vhost
, *arg
)
262 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
266 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
267 """Call the given method after locking the mlist.
271 ret
= method(userdesc
, perms
, mlist
, *arg
)
277 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
280 # TODO: use finally when switching to python 2.5
282 #-------------------------------------------------------------------------------
286 def is_subscription_pending(userdesc
, perms
, mlist
):
287 for id in mlist
.GetSubscriptionIds():
288 if userdesc
.address
== mlist
.GetRecord(id)[1]:
292 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
293 members
= mlist
.getRegularMemberKeys()
294 is_member
= userdesc
.address
in members
295 is_owner
= userdesc
.address
in mlist
.owner
296 if (mlist
.advertised
and perms
in ('lists', 'admin')) or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
298 if not is_member
and (mlist
.subscribe_policy
> 1):
299 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
303 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
305 'list' : mlist
.real_name
,
306 'addr' : mlist
.real_name
.lower() + '@' + host
,
308 'desc' : quote(mlist
.description
),
309 'info' : quote(mlist
.info
),
310 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
311 'ins' : mlist
.subscribe_policy
> 1,
312 'priv' : 1-mlist
.advertised
,
313 'sub' : 2*is_member
+ is_pending
,
315 'nbsub': len(members
)
317 return (details
, members
)
320 def get_options(userdesc
, perms
, mlist
, opts
):
321 """ Get the options of a list.
326 for (k
, v
) in mlist
.__dict__
.iteritems():
329 options
[k
] = quote(v
)
331 details
= get_list_info(userdesc
, perms
, mlist
)[0]
332 return (details
, options
)
334 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
335 for (k
, v
) in vals
.iteritems():
338 if k
== 'default_member_moderation':
339 for member
in mlist
.getMembers():
340 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
341 t
= type(mlist
.__dict__
[k
])
342 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
343 elif t
is int: mlist
.__dict__
[k
] = int(v
)
344 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
345 else: mlist
.__dict__
[k
] = v
348 #-------------------------------------------------------------------------------
349 # users procedures for [ index.php ]
352 def get_lists(userdesc
, perms
, vhost
, email
=None):
353 """ List available lists for the given vhost
358 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
359 prefix
= vhost
.lower()+VHOST_SEP
360 names
= Utils
.list_names()
364 if not name
.startswith(prefix
):
367 mlist
= MailList
.MailList(name
, lock
=0)
371 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))
372 if details
is not None:
373 result
.append(details
[0])
375 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
379 def subscribe(userdesc
, perms
, mlist
):
380 """ Subscribe to a list.
384 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
385 mlist
.ApprovedAddMember(userdesc
)
390 mlist
.AddMember(userdesc
)
391 except Errors
.MMNeedApproval
:
395 def unsubscribe(userdesc
, perms
, mlist
):
396 """ Unsubscribe from a list
400 mlist
.ApprovedDeleteMember(userdesc
.address
)
403 #-------------------------------------------------------------------------------
404 # users procedures for [ index.php ]
407 def get_name(member
):
409 return quote(mlist
.getMemberName(member
))
413 def get_members(userdesc
, perms
, mlist
):
414 """ List the members of a list.
417 infos
= get_list_info(userdesc
, perms
, mlist
)
419 # Do not return None, this is not serializable
421 details
, members
= infos
423 members
= map(lambda member
: (get_name(member
), member
), members
)
424 return (details
, members
, mlist
.owner
)
427 #-------------------------------------------------------------------------------
428 # users procedures for [ trombi.php ]
431 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
432 """ Get a range of members of the list.
435 members
= get_members(userdesc
, perms
, mlist
)[1]
436 i
= int(page
) * int(nb_per_page
)
437 return (len(members
), members
[i
:i
+int(nb_per_page
)])
439 def get_owners(userdesc
, perms
, mlist
):
440 """ Get the owners of the list.
443 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
444 return (details
, owners
)
447 #-------------------------------------------------------------------------------
448 # owners procedures [ admin.php ]
451 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
452 """ Replace the address of a member by another one.
457 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
460 def mass_subscribe(userdesc
, perms
, mlist
, users
):
461 """ Add a list of users to the list.
466 if not isinstance(users
, list):
467 raise Exception("userlist must be a list")
468 members
= mlist
.getRegularMemberKeys()
471 email
, name
= to_forlife(user
)
472 if ( email
is None ) or ( email
in members
):
474 userd
= UserDesc(email
, name
, None, 0)
475 mlist
.ApprovedAddMember(userd
)
476 added
.append( (quote(userd
.fullname
), userd
.address
) )
479 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
480 """ Remove a list of users from the list.
485 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
488 def add_owner(userdesc
, perms
, mlist
, user
):
489 """ Add a owner to the list.
494 email
= to_forlife(user
)[0]
497 if email
not in mlist
.owner
:
498 mlist
.owner
.append(email
)
501 def del_owner(userdesc
, perms
, mlist
, user
):
502 """ Remove a owner of the list.
507 if len(mlist
.owner
) < 2:
509 mlist
.owner
.remove(user
)
512 #-------------------------------------------------------------------------------
513 # owners procedures [ admin.php ]
516 def get_pending_ops(userdesc
, perms
, mlist
):
517 """ Get the list of operation waiting for an action from the owners.
525 for id in mlist
.GetSubscriptionIds():
526 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
528 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
533 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
534 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
536 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
539 for id in mlist
.GetHeldMessageIds():
540 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
541 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
543 size
= os
.path
.getsize(fpath
)
545 if e
.errno
<> errno
.ENOENT
: raise
548 msg
= readMessage(fpath
)
549 fromX
= msg
.has_key("X-Org-Mail")
554 'sender': quote(sender
, True),
556 'subj' : quote(subject
, True),
564 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
565 """ Handle a moderation request.
570 mlist
.HandleRequest(int(id), int(value
), comment
)
573 def get_pending_sub(userdesc
, perms
, mlist
, id):
574 """ Get informations about a given subscription moderation.
581 if id in mlist
.GetSubscriptionIds():
582 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
584 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
585 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
587 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
590 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
591 """ Get informations about a given mail moderation.
596 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
597 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
598 size
= os
.path
.getsize(fpath
)
599 msg
= readMessage(fpath
)
602 return quote(str(msg
))
605 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
606 c
= part
.get_payload()
607 if c
is not None: results_plain
.append (c
)
608 results_plain
= map(lambda x
: quote(x
), results_plain
)
609 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
610 c
= part
.get_payload()
611 if c
is not None: results_html
.append (c
)
612 results_html
= map(lambda x
: quote(x
), results_html
)
614 'sender': quote(sender
, True),
616 'subj' : quote(subject
, True),
618 'parts_plain' : results_plain
,
619 'parts_html': results_html
}
621 #-------------------------------------------------------------------------------
622 # owner options [ options.php ]
625 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
626 'default_member_moderation', 'generic_nonmember_action', 'info', \
627 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
630 def get_owner_options(userdesc
, perms
, mlist
):
631 """ Get the owner options of a list.
635 return get_options(userdesc
, perms
, mlist
, owner_opts
)
637 def set_owner_options(userdesc
, perms
, mlist
, values
):
638 """ Set the owner options of a list.
643 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
645 def add_to_wl(userdesc
, perms
, mlist
, addr
):
646 """ Add addr to the whitelist
651 mlist
.accept_these_nonmembers
.append(addr
)
654 def del_from_wl(userdesc
, perms
, mlist
, addr
):
655 """ Remove an address from the whitelist
660 mlist
.accept_these_nonmembers
.remove(addr
)
663 def get_bogo_level(userdesc
, perms
, mlist
):
664 """ Compute bogo level from the filtering rules set up on the list.
668 if len(mlist
.header_filter_rules
) == 0:
675 # The first rule filters Unsure mails
676 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
680 # Check the other rules:
681 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
682 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
683 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
685 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
688 action
= mlist
.header_filter_rules
[filterbase
][1]
689 if action
== mm_cfg
.HOLD
:
691 elif action
== mm_cfg
.DISCARD
:
693 return (filterlevel
<< 1) + unsurelevel
695 def set_bogo_level(userdesc
, perms
, mlist
, level
):
696 """ Set filter to the specified level.
703 # The level is a combination of a spam filtering level and unsure filtering level
704 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
705 # - the spam filtering level is a number growing with filtering strength
706 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
707 bogolevel
= int(level
)
708 filterlevel
= bogolevel
>> 1
709 unsurelevel
= bogolevel
& 1
711 # Set up unusre filtering
713 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
715 # Set up spam filtering
717 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
718 elif filterlevel
is 2:
719 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
720 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
721 elif filterlevel
is 3:
722 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
725 if mlist
.header_filter_rules
!= hfr
:
726 mlist
.header_filter_rules
= hfr
729 #-------------------------------------------------------------------------------
730 # admin procedures [ soptions.php ]
733 admin_opts
= [ 'advertised', 'archive', \
734 'max_message_size', 'msg_footer', 'msg_header']
736 def get_admin_options(userdesc
, perms
, mlist
):
737 """ Get administrator options.
741 return get_options(userdesc
, perms
, mlist
, admin_opts
)
743 def set_admin_options(userdesc
, perms
, mlist
, values
):
744 """ Set administrator options.
749 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
751 #-------------------------------------------------------------------------------
752 # admin procedures [ check.php ]
756 'acceptable_aliases' : '',
757 'admin_immed_notify' : True,
758 'administrivia' : True,
759 'anonymous_list' : False,
760 'autorespond_admin' : False,
761 'autorespond_postings' : False,
762 'autorespond_requests' : False,
763 'available_languages' : ['fr'],
765 'bounce_matching_headers' : '',
766 'bounce_processing' : False,
767 'convert_html_to_plaintext' : False,
768 'digestable' : False,
769 'digest_is_default' : False,
770 'discard_these_nonmembers' : [],
772 'encode_ascii_prefixes' : 2,
773 'filter_content' : False,
774 'first_strip_reply_to' : False,
775 'forward_auto_discards' : True,
776 'hold_these_nonmembers' : [],
777 'host_name' : 'listes.polytechnique.org',
778 'include_list_post_header' : False,
779 'include_rfc2369_headers' : False,
780 'max_num_recipients' : 0,
781 'new_member_options' : 256,
782 'nondigestable' : True,
783 'obscure_addresses' : True,
784 'preferred_language' : 'fr',
785 'reject_these_nonmembers' : [],
786 'reply_goes_to_list' : 0,
787 'reply_to_address' : '',
788 'require_explicit_destination' : False,
789 'send_reminders' : 0,
790 'send_welcome_msg' : True,
791 'topics_enabled' : False,
792 'umbrella_list' : False,
793 'unsubscribe_policy' : 0,
796 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
798 for (k
, v
) in check_opts
.iteritems():
799 if mlist
.__dict__
[k
] != v
:
800 options
[k
] = v
, mlist
.__dict__
[k
]
801 if correct
: mlist
.__dict__
[k
] = v
802 if mlist
.real_name
.lower() != listname
:
803 options
['real_name'] = listname
, mlist
.real_name
804 if correct
: mlist
.real_name
= listname
808 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
812 listname
= listname
.lower()
813 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
815 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
817 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
819 #-------------------------------------------------------------------------------
820 # super-admin procedures
823 def get_all_lists(userdesc
, perms
, vhost
):
824 """ Get all the list for the given vhost
827 prefix
= vhost
.lower()+VHOST_SEP
828 names
= Utils
.list_names()
832 if not name
.startswith(prefix
):
834 result
.append(name
.replace(prefix
, ''))
837 def get_all_user_lists(userdesc
, perms
, vhost
, email
):
838 """ Get all the lists for the given user
841 names
= Utils
.list_names()
846 mlist
= MailList
.MailList(name
, lock
=0)
847 ismember
= email
in mlist
.getRegularMemberKeys()
848 isowner
= email
in mlist
.owner
849 if not ismember
and not isowner
:
851 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
852 result
.append({ 'list': mlist
.real_name
,
853 'addr': mlist
.real_name
.lower() + '@' + host
,
862 def change_user_email(userdesc
, perms
, vhost
, from_email
, to_email
):
863 """ Change the email of a user
866 from_email
= from_email
.lower()
867 to_email
= to_email
.lower()
868 for list in Utils
.list_names():
870 mlist
= MailList
.MailList(list, lock
=0)
875 mlist
.ApprovedChangeMemberAddress(from_email
, to_email
, 0)
883 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
884 """ Create a new list.
887 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
888 if Utils
.list_exists(name
):
889 print >> sys
.stderr
, "List ", name
, " already exists"
894 email
= to_forlife(o
)
895 print >> sys
.stderr
, "owner in list", o
, email
897 if email
is not None:
900 print >> sys
.stderr
, "No owner found in ", owners
903 mlist
= MailList
.MailList()
905 oldmask
= os
.umask(002)
906 pw
= sha
.new('foobar').hexdigest()
909 mlist
.Create(name
, owner
[0], pw
)
913 mlist
.real_name
= listname
914 mlist
.host_name
= 'listes.polytechnique.org'
915 mlist
.description
= desc
917 mlist
.advertised
= int(advertise
) is 0
918 mlist
.default_member_moderation
= int(modlevel
) is 2
919 mlist
.generic_nonmember_action
= int(modlevel
) > 0
920 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
921 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
925 mlist
.subject_prefix
= '['+listname
+'] '
926 mlist
.max_message_size
= 0
928 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
929 mlist
.msg_footer
= "_______________________________________________\n" \
930 + "Liste de diffusion %(real_name)s\n" \
931 + "http://listes.polytechnique.org/members/" + inverted_listname
933 mlist
.header_filter_rules
= []
934 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
935 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
937 if ON_CREATE_CMD
!= '':
938 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
941 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
942 mass_subscribe(userdesc
, perms
, mlist
, members
)
947 # avoid the "-1 mail to moderate" bug
948 mlist
= MailList
.MailList(name
)
950 mlist
._UpdateRecords()
956 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
961 lname
= mlist
.internal_name()
963 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
964 # remove stalled locks
965 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
966 fn_lname
= filename
.split('.')[0]
967 if fn_lname
== lname
:
968 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
972 os
.path
.join('archives', 'private', lname
),
973 os
.path
.join('archives', 'private', lname
+'.mbox'),
974 os
.path
.join('archives', 'public', lname
),
975 os
.path
.join('archives', 'public', lname
+'.mbox')
977 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
980 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
981 """ Remove a user from all the lists.
984 if not del_from_promo
:
985 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
986 for list in Utils
.list_names():
990 mlist
= MailList
.MailList(list, lock
=0)
995 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
1003 #-------------------------------------------------------------------------------
1006 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
1007 allow_reuse_address
= True
1009 ################################################################################
1013 #-------------------------------------------------------------------------------
1014 # use Mailman user and group (not root)
1015 # fork in background if asked to
1018 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
1019 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
1022 os
.setregid(gid
, gid
)
1023 os
.setreuid(uid
, uid
)
1025 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
1027 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
1030 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
1032 if o
== '-f' and os
.fork():
1035 i18n
.set_language('fr')
1039 #-------------------------------------------------------------------------------
1042 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
1045 server
.register_function(get_lists
)
1046 server
.register_function(subscribe
)
1047 server
.register_function(unsubscribe
)
1049 server
.register_function(get_members
)
1051 server
.register_function(get_members_limit
)
1052 server
.register_function(get_owners
)
1054 server
.register_function(replace_email
)
1055 server
.register_function(mass_subscribe
)
1056 server
.register_function(mass_unsubscribe
)
1057 server
.register_function(add_owner
)
1058 server
.register_function(del_owner
)
1060 server
.register_function(get_pending_ops
)
1061 server
.register_function(handle_request
)
1062 server
.register_function(get_pending_sub
)
1063 server
.register_function(get_pending_mail
)
1065 server
.register_function(get_owner_options
)
1066 server
.register_function(set_owner_options
)
1067 server
.register_function(add_to_wl
)
1068 server
.register_function(del_from_wl
)
1069 server
.register_function(get_bogo_level
)
1070 server
.register_function(set_bogo_level
)
1072 server
.register_function(get_admin_options
)
1073 server
.register_function(set_admin_options
)
1075 server
.register_function(check_options
)
1077 server
.register_function(get_all_lists
)
1078 server
.register_function(get_all_user_lists
)
1079 server
.register_function(change_user_email
)
1080 server
.register_function(create_list
)
1081 server
.register_function(delete_list
)
1083 server
.register_function(kill
)
1085 server
.serve_forever()
1087 # vim:set et sw=4 sts=4 sws=4: