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
)
109 def _dispatch(self
, method
, params
):
110 new_params
= list(params
)
111 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
115 _
, auth
= self
.headers
["authorization"].split()
116 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
117 vhost
= self
.path
.split('/')[1].lower()
118 self
.data
= self
.getUser(uid
, md5
, vhost
)
119 if self
.data
is None:
121 # Call super.do_POST() to do the actual work
122 SimpleXMLRPCRequestHandler
.do_POST(self
)
124 self
.send_response(401)
127 def getUser(self
, uid
, md5
, vhost
):
128 res
= mysql_fetchone ("""SELECT a.full_name, aa.alias, IF (a.is_admin, 'admin', NULL)
130 INNER JOIN aliases AS aa ON (a.uid = aa.uid AND aa.type = 'a_vie')
131 WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active'
135 name
, forlife
, perms
= res
136 if vhost
!= PLATAL_DOMAIN
:
137 res
= mysql_fetchone ("""SELECT m.uid
138 FROM group_members AS m
139 INNER JOIN groups AS g ON (m.asso_id = g.id)
140 WHERE perms = 'admin' AND uid = '%s' AND mail_domain = '%s'""" \
144 userdesc
= UserDesc(forlife
+'@'+PLATAL_DOMAIN
, name
, None, 0)
145 return (userdesc
, perms
, vhost
)
149 ################################################################################
153 #-------------------------------------------------------------------------------
158 db
= MySQLdb
.connect(
162 unix_socket
='/var/run/mysqld/mysqld.sock')
166 def mysql_fetchone(query
):
171 if int(mysql
.rowcount
) > 0:
172 ret
= mysql
.fetchone()
177 def is_admin_on(userdesc
, perms
, mlist
):
178 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
181 def quote(s
, is_header
=False):
183 h
= Utils
.oneline(s
, 'iso-8859-1')
186 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
187 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
189 def to_forlife(email
):
191 mbox
, fqdn
= email
.split('@')
195 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
196 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), a.full_name
198 INNER JOIN aliases AS f ON (f.uid = a.uid AND f.type = 'a_vie')
199 INNER JOIN aliases AS aa ON (aa.uid = a.uid AND aa.alias = '%s'
200 AND a.type != 'homonyme')
201 WHERE a.state = 'active'
203 %
(PLATAL_DOMAIN
, mbox
))
208 return (email
.lower(), mbox
)
211 # see /usr/lib/mailman/bin/rmlist
213 def remove_it(listname
, filename
):
214 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
216 elif os
.path
.isdir(filename
):
217 shutil
.rmtree(filename
)
223 def has_annotation(method
, name
):
224 """ Check if the method contains the given annoation.
226 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
228 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
229 """Dispatch the call to the right handler.
230 This function checks the options of the called method the set the environment of the call.
231 The dispatcher uses method annotation (special tokens in the documentation of the method) to
232 guess the requested environment:
233 @mlist: the handler requires a mlist object instead of the vhost/listname couple
234 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
235 @edit: the handler edit the mlist (@mlist MUST be specified)
236 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
237 @root: the handler requires site admin rights
240 if has_annotation(method
, "root") and perms
!= "admin":
242 if has_annotation(method
, "mlist"):
245 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
246 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
248 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
249 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
251 return method(userdesc
, perms
, mlist
, *arg
)
253 return method(userdesc
, perms
, vhost
, *arg
)
255 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
259 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
260 """Call the given method after locking the mlist.
264 ret
= method(userdesc
, perms
, mlist
, *arg
)
270 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
273 # TODO: use finally when switching to python 2.5
275 #-------------------------------------------------------------------------------
279 def is_subscription_pending(userdesc
, perms
, mlist
):
280 for id in mlist
.GetSubscriptionIds():
281 if userdesc
.address
== mlist
.GetRecord(id)[1]:
285 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
286 members
= mlist
.getRegularMemberKeys()
287 is_member
= userdesc
.address
in members
288 is_owner
= userdesc
.address
in mlist
.owner
289 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
291 if not is_member
and (mlist
.subscribe_policy
> 1):
292 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
296 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
298 'list' : mlist
.real_name
,
299 'addr' : mlist
.real_name
.lower() + '@' + host
,
301 'desc' : quote(mlist
.description
),
302 'info' : quote(mlist
.info
),
303 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
304 'ins' : mlist
.subscribe_policy
> 1,
305 'priv' : 1-mlist
.advertised
,
306 'sub' : 2*is_member
+ is_pending
,
308 'nbsub': len(members
)
310 return (details
, members
)
313 def get_options(userdesc
, perms
, mlist
, opts
):
314 """ Get the options of a list.
319 for (k
, v
) in mlist
.__dict__
.iteritems():
322 options
[k
] = quote(v
)
324 details
= get_list_info(userdesc
, perms
, mlist
)[0]
325 return (details
, options
)
327 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
328 for (k
, v
) in vals
.iteritems():
331 if k
== 'default_member_moderation':
332 for member
in mlist
.getMembers():
333 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
334 t
= type(mlist
.__dict__
[k
])
335 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
336 elif t
is int: mlist
.__dict__
[k
] = int(v
)
337 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
338 else: mlist
.__dict__
[k
] = v
341 #-------------------------------------------------------------------------------
342 # users procedures for [ index.php ]
345 def get_lists(userdesc
, perms
, vhost
, email
=None):
346 """ List available lists for the given vhost
351 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
352 prefix
= vhost
.lower()+VHOST_SEP
353 names
= Utils
.list_names()
357 if not name
.startswith(prefix
):
360 mlist
= MailList
.MailList(name
, lock
=0)
364 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
365 result
.append(details
)
367 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
371 def subscribe(userdesc
, perms
, mlist
):
372 """ Subscribe to a list.
376 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
377 mlist
.ApprovedAddMember(userdesc
)
382 mlist
.AddMember(userdesc
)
383 except Errors
.MMNeedApproval
:
387 def unsubscribe(userdesc
, perms
, mlist
):
388 """ Unsubscribe from a list
392 mlist
.ApprovedDeleteMember(userdesc
.address
)
395 #-------------------------------------------------------------------------------
396 # users procedures for [ index.php ]
399 def get_name(member
):
401 return quote(mlist
.getMemberName(member
))
405 def get_members(userdesc
, perms
, mlist
):
406 """ List the members of a list.
409 details
, members
= get_list_info(userdesc
, perms
, mlist
)
411 members
= map(lambda member
: (get_name(member
), member
), members
)
412 return (details
, members
, mlist
.owner
)
415 #-------------------------------------------------------------------------------
416 # users procedures for [ trombi.php ]
419 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
420 """ Get a range of members of the list.
423 members
= get_members(userdesc
, perms
, mlist
)[1]
424 i
= int(page
) * int(nb_per_page
)
425 return (len(members
), members
[i
:i
+int(nb_per_page
)])
427 def get_owners(userdesc
, perms
, mlist
):
428 """ Get the owners of the list.
431 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
432 return (details
, owners
)
435 #-------------------------------------------------------------------------------
436 # owners procedures [ admin.php ]
439 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
440 """ Replace the address of a member by another one.
445 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
448 def mass_subscribe(userdesc
, perms
, mlist
, users
):
449 """ Add a list of users to the list.
454 members
= mlist
.getRegularMemberKeys()
457 email
, name
= to_forlife(user
)
458 if ( email
is None ) or ( email
in members
):
460 userd
= UserDesc(email
, name
, None, 0)
461 mlist
.ApprovedAddMember(userd
)
462 added
.append( (quote(userd
.fullname
), userd
.address
) )
465 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
466 """ Remove a list of users from the list.
471 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
474 def add_owner(userdesc
, perms
, mlist
, user
):
475 """ Add a owner to the list.
480 email
= to_forlife(user
)[0]
483 if email
not in mlist
.owner
:
484 mlist
.owner
.append(email
)
487 def del_owner(userdesc
, perms
, mlist
, user
):
488 """ Remove a owner of the list.
493 if len(mlist
.owner
) < 2:
495 mlist
.owner
.remove(user
)
498 #-------------------------------------------------------------------------------
499 # owners procedures [ admin.php ]
502 def get_pending_ops(userdesc
, perms
, mlist
):
503 """ Get the list of operation waiting for an action from the owners.
511 for id in mlist
.GetSubscriptionIds():
512 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
514 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
519 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
520 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
522 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
525 for id in mlist
.GetHeldMessageIds():
526 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
527 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
529 size
= os
.path
.getsize(fpath
)
531 if e
.errno
<> errno
.ENOENT
: raise
534 msg
= readMessage(fpath
)
535 fromX
= msg
.has_key("X-Org-Mail")
540 'sender': quote(sender
, True),
542 'subj' : quote(subject
, True),
550 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
551 """ Handle a moderation request.
556 mlist
.HandleRequest(int(id), int(value
), comment
)
559 def get_pending_sub(userdesc
, perms
, mlist
, id):
560 """ Get informations about a given subscription moderation.
567 if id in mlist
.GetSubscriptionIds():
568 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
570 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
571 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
573 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
576 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
577 """ Get informations about a given mail moderation.
582 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
583 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
584 size
= os
.path
.getsize(fpath
)
585 msg
= readMessage(fpath
)
588 return quote(str(msg
))
591 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
592 c
= part
.get_payload()
593 if c
is not None: results_plain
.append (c
)
594 results_plain
= map(lambda x
: quote(x
), results_plain
)
595 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
596 c
= part
.get_payload()
597 if c
is not None: results_html
.append (c
)
598 results_html
= map(lambda x
: quote(x
), results_html
)
600 'sender': quote(sender
, True),
602 'subj' : quote(subject
, True),
604 'parts_plain' : results_plain
,
605 'parts_html': results_html
}
607 #-------------------------------------------------------------------------------
608 # owner options [ options.php ]
611 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
612 'default_member_moderation', 'generic_nonmember_action', 'info', \
613 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
616 def get_owner_options(userdesc
, perms
, mlist
):
617 """ Get the owner options of a list.
621 return get_options(userdesc
, perms
, mlist
, owner_opts
)
623 def set_owner_options(userdesc
, perms
, mlist
, values
):
624 """ Set the owner options of a list.
629 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
631 def add_to_wl(userdesc
, perms
, mlist
, addr
):
632 """ Add addr to the whitelist
637 mlist
.accept_these_nonmembers
.append(addr
)
640 def del_from_wl(userdesc
, perms
, mlist
, addr
):
641 """ Remove an address from the whitelist
646 mlist
.accept_these_nonmembers
.remove(addr
)
649 def get_bogo_level(userdesc
, perms
, mlist
):
650 """ Compute bogo level from the filtering rules set up on the list.
654 if len(mlist
.header_filter_rules
) == 0:
661 # The first rule filters Unsure mails
662 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
666 # Check the other rules:
667 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
668 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
669 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
671 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
674 action
= mlist
.header_filter_rules
[filterbase
][1]
675 if action
== mm_cfg
.HOLD
:
677 elif action
== mm_cfg
.DISCARD
:
679 return (filterlevel
<< 1) + unsurelevel
681 def set_bogo_level(userdesc
, perms
, mlist
, level
):
682 """ Set filter to the specified level.
689 # The level is a combination of a spam filtering level and unsure filtering level
690 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
691 # - the spam filtering level is a number growing with filtering strength
692 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
693 bogolevel
= int(level
)
694 filterlevel
= bogolevel
>> 1
695 unsurelevel
= bogolevel
& 1
697 # Set up unusre filtering
699 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
701 # Set up spam filtering
703 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
704 elif filterlevel
is 2:
705 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
706 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
707 elif filterlevel
is 3:
708 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
711 if mlist
.header_filter_rules
!= hfr
:
712 mlist
.header_filter_rules
= hfr
715 #-------------------------------------------------------------------------------
716 # admin procedures [ soptions.php ]
719 admin_opts
= [ 'advertised', 'archive', \
720 'max_message_size', 'msg_footer', 'msg_header']
722 def get_admin_options(userdesc
, perms
, mlist
):
723 """ Get administrator options.
727 return get_options(userdesc
, perms
, mlist
, admin_opts
)
729 def set_admin_options(userdesc
, perms
, mlist
, values
):
730 """ Set administrator options.
735 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
737 #-------------------------------------------------------------------------------
738 # admin procedures [ check.php ]
742 'acceptable_aliases' : '',
743 'admin_immed_notify' : True,
744 'administrivia' : True,
745 'anonymous_list' : False,
746 'autorespond_admin' : False,
747 'autorespond_postings' : False,
748 'autorespond_requests' : False,
749 'available_languages' : ['fr'],
751 'bounce_matching_headers' : '',
752 'bounce_processing' : False,
753 'convert_html_to_plaintext' : False,
754 'digestable' : False,
755 'digest_is_default' : False,
756 'discard_these_nonmembers' : [],
758 'encode_ascii_prefixes' : 2,
759 'filter_content' : False,
760 'first_strip_reply_to' : False,
761 'forward_auto_discards' : True,
762 'hold_these_nonmembers' : [],
763 'host_name' : 'listes.polytechnique.org',
764 'include_list_post_header' : False,
765 'include_rfc2369_headers' : False,
766 'max_num_recipients' : 0,
767 'new_member_options' : 256,
768 'nondigestable' : True,
769 'obscure_addresses' : True,
770 'preferred_language' : 'fr',
771 'reject_these_nonmembers' : [],
772 'reply_goes_to_list' : 0,
773 'reply_to_address' : '',
774 'require_explicit_destination' : False,
775 'send_reminders' : 0,
776 'send_welcome_msg' : True,
777 'topics_enabled' : False,
778 'umbrella_list' : False,
779 'unsubscribe_policy' : 0,
782 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
784 for (k
, v
) in check_opts
.iteritems():
785 if mlist
.__dict__
[k
] != v
:
786 options
[k
] = v
, mlist
.__dict__
[k
]
787 if correct
: mlist
.__dict__
[k
] = v
788 if mlist
.real_name
.lower() != listname
:
789 options
['real_name'] = listname
, mlist
.real_name
790 if correct
: mlist
.real_name
= listname
794 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
798 listname
= listname
.lower()
799 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
801 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
803 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
805 #-------------------------------------------------------------------------------
806 # super-admin procedures
809 def get_all_lists(userdesc
, perms
, vhost
):
810 """ Get all the list for the given vhost
812 prefix
= vhost
.lower()+VHOST_SEP
813 names
= Utils
.list_names()
817 if not name
.startswith(prefix
):
819 result
.append(name
.replace(prefix
, ''))
822 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
823 """ Create a new list.
826 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
827 if Utils
.list_exists(name
):
832 email
= to_forlife(o
)[0]
833 if email
is not None:
838 mlist
= MailList
.MailList()
840 oldmask
= os
.umask(002)
841 pw
= sha
.new('foobar').hexdigest()
844 mlist
.Create(name
, owner
[0], pw
)
848 mlist
.real_name
= listname
849 mlist
.host_name
= 'listes.polytechnique.org'
850 mlist
.description
= desc
852 mlist
.advertised
= int(advertise
) is 0
853 mlist
.default_member_moderation
= int(modlevel
) is 2
854 mlist
.generic_nonmember_action
= int(modlevel
) > 0
855 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
856 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
860 mlist
.subject_prefix
= '['+listname
+'] '
861 mlist
.max_message_size
= 0
863 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
864 mlist
.msg_footer
= "_______________________________________________\n" \
865 + "Liste de diffusion %(real_name)s\n" \
866 + "http://listes.polytechnique.org/members/" + inverted_listname
868 mlist
.header_filter_rules
= []
869 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
870 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
872 if ON_CREATE_CMD
!= '':
873 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
876 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
877 mass_subscribe(userdesc
, perms
, mlist
, members
)
882 # avoid the "-1 mail to moderate" bug
883 mlist
= MailList
.MailList(name
)
884 mlist
._UpdateRecords()
888 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
893 lname
= mlist
.internal_name()
895 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
896 # remove stalled locks
897 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
898 fn_lname
= filename
.split('.')[0]
899 if fn_lname
== lname
:
900 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
904 os
.path
.join('archives', 'private', lname
),
905 os
.path
.join('archives', 'private', lname
+'.mbox'),
906 os
.path
.join('archives', 'public', lname
),
907 os
.path
.join('archives', 'public', lname
+'.mbox')
909 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
912 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
913 """ Remove a user from all the lists.
916 if not del_from_promo
:
917 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
918 for list in Utils
.list_names():
922 mlist
= MailList
.MailList(list, lock
=0)
927 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
935 #-------------------------------------------------------------------------------
938 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
939 allow_reuse_address
= True
941 ################################################################################
945 #-------------------------------------------------------------------------------
946 # use Mailman user and group (not root)
947 # fork in background if asked to
950 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
951 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
954 os
.setregid(gid
, gid
)
955 os
.setreuid(uid
, uid
)
957 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
959 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
962 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
964 if o
== '-f' and os
.fork():
967 i18n
.set_language('fr')
971 #-------------------------------------------------------------------------------
974 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
977 server
.register_function(get_lists
)
978 server
.register_function(subscribe
)
979 server
.register_function(unsubscribe
)
981 server
.register_function(get_members
)
983 server
.register_function(get_members_limit
)
984 server
.register_function(get_owners
)
986 server
.register_function(replace_email
)
987 server
.register_function(mass_subscribe
)
988 server
.register_function(mass_unsubscribe
)
989 server
.register_function(add_owner
)
990 server
.register_function(del_owner
)
992 server
.register_function(get_pending_ops
)
993 server
.register_function(handle_request
)
994 server
.register_function(get_pending_sub
)
995 server
.register_function(get_pending_mail
)
997 server
.register_function(get_owner_options
)
998 server
.register_function(set_owner_options
)
999 server
.register_function(add_to_wl
)
1000 server
.register_function(del_from_wl
)
1001 server
.register_function(get_bogo_level
)
1002 server
.register_function(set_bogo_level
)
1004 server
.register_function(get_admin_options
)
1005 server
.register_function(set_admin_options
)
1007 server
.register_function(check_options
)
1009 server
.register_function(get_all_lists
)
1010 server
.register_function(create_list
)
1011 server
.register_function(delete_list
)
1013 server
.register_function(kill
)
1015 server
.serve_forever()
1017 # vim:set et sw=4 sts=4 sws=4: