2 #***************************************************************************
3 #* Copyright (C) 2004-2009 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')
71 PLATAL_DOMAIN
= get_config('Mail', 'domain')
72 PLATAL_DOMAIN2
= get_config('Mail', 'domain2', '')
73 sys
.stderr
.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN
)
75 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
76 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
78 SRV_HOST
= get_config('Lists', 'rpchost', 'localhost')
79 SRV_PORT
= int(get_config('Lists', 'rpcport', '4949'))
81 ################################################################################
85 #------------------------------------------------
86 # Manage Basic authentication
89 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
91 """XMLRPC Request Handler
92 This request handler is used to provide BASIC HTTP user authentication.
93 It first overloads the do_POST() function, authenticates the user, then
94 calls the super.do_POST().
96 Moreover, we override _dispatch, so that we call functions with as first
97 argument a UserDesc taken from the database, containing name, email and perms
100 def _get_function(self
, method
):
102 # check to see if a matching function has been registered
103 return self
.server
.funcs
[method
]
105 raise Exception('method "%s" is not supported' % method
)
108 def _dispatch(self
, method
, params
):
109 new_params
= list(params
)
110 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
114 _
, auth
= self
.headers
["authorization"].split()
115 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
116 vhost
= self
.path
.split('/')[1].lower()
117 self
.data
= self
.getUser(uid
, md5
, vhost
)
118 if self
.data
is None:
120 # Call super.do_POST() to do the actual work
121 SimpleXMLRPCRequestHandler
.do_POST(self
)
123 self
.send_response(401)
126 def getUser(self
, uid
, md5
, vhost
):
127 res
= mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms
128 FROM auth_user_md5 AS u
129 INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' )
130 WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user')
131 LIMIT 1""" %( uid
, md5
) )
133 name
, forlife
, perms
= res
134 if vhost
!= PLATAL_DOMAIN
:
135 res
= mysql_fetchone ("""SELECT uid
136 FROM groupex.membres AS m
137 INNER JOIN groupex.asso AS a ON (m.asso_id = a.id)
138 WHERE perms='admin' AND uid='%s' AND mail_domain='%s'""" %( uid
, vhost
) )
139 if res
: perms
= 'admin'
140 userdesc
= UserDesc(forlife
+'@'+PLATAL_DOMAIN
, name
, None, 0)
141 return (userdesc
, perms
, vhost
)
145 ################################################################################
149 #-------------------------------------------------------------------------------
154 db
= MySQLdb
.connect(
158 unix_socket
='/var/run/mysqld/mysqld.sock')
162 def mysql_fetchone(query
):
167 if int(mysql
.rowcount
) > 0:
168 ret
= mysql
.fetchone()
173 def is_admin_on(userdesc
, perms
, mlist
):
174 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
177 def quote(s
, is_header
=False):
179 h
= Utils
.oneline(s
, 'iso-8859-1')
182 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
183 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
185 def to_forlife(email
):
187 mbox
, fqdn
= email
.split('@')
191 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
192 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom)
193 FROM auth_user_md5 AS u
194 INNER JOIN aliases AS f ON (f.id=u.user_id AND f.type='a_vie')
195 INNER JOIN aliases AS a ON (a.id=u.user_id AND a.alias='%s' AND a.type!='homonyme')
196 WHERE u.perms IN ('admin', 'user')
197 LIMIT 1""" %( PLATAL_DOMAIN
, mbox
) )
202 return (email
.lower(), mbox
)
205 # see /usr/lib/mailman/bin/rmlist
207 def remove_it(listname
, filename
):
208 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
210 elif os
.path
.isdir(filename
):
211 shutil
.rmtree(filename
)
217 def has_annotation(method
, name
):
218 """ Check if the method contains the given annoation.
220 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
222 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
223 """Dispatch the call to the right handler.
224 This function checks the options of the called method the set the environment of the call.
225 The dispatcher uses method annotation (special tokens in the documentation of the method) to
226 guess the requested environment:
227 @mlist: the handler requires a mlist object instead of the vhost/listname couple
228 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
229 @edit: the handler edit the mlist (@mlist MUST be specified)
230 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
231 @root: the handler requires site admin rights
234 if has_annotation(method
, "root") and perms
!= "admin":
236 if has_annotation(method
, "mlist"):
239 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
240 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
242 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
243 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
245 return method(userdesc
, perms
, mlist
, *arg
)
247 return method(userdesc
, perms
, vhost
, *arg
)
249 sys
.stderr
.write('Exception in dispatcher %s\n' % str
(e
))
253 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
254 """Call the given method after locking the mlist.
258 ret
= method(userdesc
, perms
, mlist
, *arg
)
264 sys
.stderr
.write('Exception in locked call %s: %s\n' %
(method
.__name__
, str(e
)))
267 # TODO: use finally when switching to python 2.5
269 #-------------------------------------------------------------------------------
273 def is_subscription_pending(userdesc
, perms
, mlist
):
274 for id in mlist
.GetSubscriptionIds():
275 if userdesc
.address
== mlist
.GetRecord(id)[1]:
279 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
280 members
= mlist
.getRegularMemberKeys()
281 is_member
= userdesc
.address
in members
282 is_owner
= userdesc
.address
in mlist
.owner
283 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
285 if not is_member
and (mlist
.subscribe_policy
> 1):
286 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
290 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
292 'list' : mlist
.real_name
,
293 'addr' : mlist
.real_name
.lower() + '@' + host
,
295 'desc' : quote(mlist
.description
),
296 'info' : quote(mlist
.info
),
297 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
298 'ins' : mlist
.subscribe_policy
> 1,
299 'priv' : 1-mlist
.advertised
,
300 'sub' : 2*is_member
+ is_pending
,
302 'nbsub': len(members
)
304 return (details
, members
)
307 def get_options(userdesc
, perms
, mlist
, opts
):
308 """ Get the options of a list.
313 for (k
, v
) in mlist
.__dict__
.iteritems():
316 options
[k
] = quote(v
)
318 details
= get_list_info(userdesc
, perms
, mlist
)[0]
319 return (details
, options
)
321 def set_options(userdesc
, perms
, mlist
, opts
, vals
):
322 for (k
, v
) in vals
.iteritems():
325 if k
== 'default_member_moderation':
326 for member
in mlist
.getMembers():
327 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
328 t
= type(mlist
.__dict__
[k
])
329 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
330 elif t
is int: mlist
.__dict__
[k
] = int(v
)
331 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
332 else: mlist
.__dict__
[k
] = v
335 #-------------------------------------------------------------------------------
336 # users procedures for [ index.php ]
339 def get_lists(userdesc
, perms
, vhost
, email
=None):
340 """ List available lists for the given vhost
345 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
346 prefix
= vhost
.lower()+VHOST_SEP
347 names
= Utils
.list_names()
351 if not name
.startswith(prefix
):
354 mlist
= MailList
.MailList(name
, lock
=0)
358 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
359 result
.append(details
)
361 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
365 def subscribe(userdesc
, perms
, mlist
):
366 """ Subscribe to a list.
370 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
371 mlist
.ApprovedAddMember(userdesc
)
376 mlist
.AddMember(userdesc
)
377 except Errors
.MMNeedApproval
:
381 def unsubscribe(userdesc
, perms
, mlist
):
382 """ Unsubscribe from a list
386 mlist
.ApprovedDeleteMember(userdesc
.address
)
389 #-------------------------------------------------------------------------------
390 # users procedures for [ index.php ]
393 def get_name(member
):
395 return quote(mlist
.getMemberName(member
))
399 def get_members(userdesc
, perms
, mlist
):
400 """ List the members of a list.
403 details
, members
= get_list_info(userdesc
, perms
, mlist
)
405 members
= map(lambda member
: (get_name(member
), member
), members
)
406 return (details
, members
, mlist
.owner
)
409 #-------------------------------------------------------------------------------
410 # users procedures for [ trombi.php ]
413 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
414 """ Get a range of members of the list.
417 members
= get_members(userdesc
, perms
, mlist
)[1]
418 i
= int(page
) * int(nb_per_page
)
419 return (len(members
), members
[i
:i
+int(nb_per_page
)])
421 def get_owners(userdesc
, perms
, mlist
):
422 """ Get the owners of the list.
425 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
426 return (details
, owners
)
429 #-------------------------------------------------------------------------------
430 # owners procedures [ admin.php ]
433 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
434 """ Replace the address of a member by another one.
439 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
442 def mass_subscribe(userdesc
, perms
, mlist
, users
):
443 """ Add a list of users to the list.
448 members
= mlist
.getRegularMemberKeys()
451 email
, name
= to_forlife(user
)
452 if ( email
is None ) or ( email
in members
):
454 userd
= UserDesc(email
, name
, None, 0)
455 mlist
.ApprovedAddMember(userd
)
456 added
.append( (quote(userd
.fullname
), userd
.address
) )
459 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
460 """ Remove a list of users from the list.
465 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
468 def add_owner(userdesc
, perms
, mlist
, user
):
469 """ Add a owner to the list.
474 email
= to_forlife(user
)[0]
477 if email
not in mlist
.owner
:
478 mlist
.owner
.append(email
)
481 def del_owner(userdesc
, perms
, mlist
, user
):
482 """ Remove a owner of the list.
487 if len(mlist
.owner
) < 2:
489 mlist
.owner
.remove(user
)
492 #-------------------------------------------------------------------------------
493 # owners procedures [ admin.php ]
496 def get_pending_ops(userdesc
, perms
, mlist
):
497 """ Get the list of operation waiting for an action from the owners.
505 for id in mlist
.GetSubscriptionIds():
506 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
508 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
513 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
514 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
516 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
519 for id in mlist
.GetHeldMessageIds():
520 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
521 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
523 size
= os
.path
.getsize(fpath
)
525 if e
.errno
<> errno
.ENOENT
: raise
528 msg
= readMessage(fpath
)
529 fromX
= msg
.has_key("X-Org-Mail")
534 'sender': quote(sender
, True),
536 'subj' : quote(subject
, True),
544 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
545 """ Handle a moderation request.
550 mlist
.HandleRequest(int(id), int(value
), comment
)
553 def get_pending_sub(userdesc
, perms
, mlist
, id):
554 """ Get informations about a given subscription moderation.
561 if id in mlist
.GetSubscriptionIds():
562 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
564 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
565 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
567 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
570 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
571 """ Get informations about a given mail moderation.
576 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
577 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
578 size
= os
.path
.getsize(fpath
)
579 msg
= readMessage(fpath
)
582 return quote(str(msg
))
585 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
586 c
= part
.get_payload()
587 if c
is not None: results_plain
.append (c
)
588 results_plain
= map(lambda x
: quote(x
), results_plain
)
589 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
590 c
= part
.get_payload()
591 if c
is not None: results_html
.append (c
)
592 results_html
= map(lambda x
: quote(x
), results_html
)
594 'sender': quote(sender
, True),
596 'subj' : quote(subject
, True),
598 'parts_plain' : results_plain
,
599 'parts_html': results_html
}
601 #-------------------------------------------------------------------------------
602 # owner options [ options.php ]
605 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
606 'default_member_moderation', 'generic_nonmember_action', 'info', \
607 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
610 def get_owner_options(userdesc
, perms
, mlist
):
611 """ Get the owner options of a list.
615 return get_options(userdesc
, perms
, mlist
, owner_opts
)
617 def set_owner_options(userdesc
, perms
, mlist
, values
):
618 """ Set the owner options of a list.
623 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
625 def add_to_wl(userdesc
, perms
, mlist
, addr
):
626 """ Add addr to the whitelist
631 mlist
.accept_these_nonmembers
.append(addr
)
634 def del_from_wl(userdesc
, perms
, mlist
, addr
):
635 """ Remove an address from the whitelist
640 mlist
.accept_these_nonmembers
.remove(addr
)
643 def get_bogo_level(userdesc
, perms
, mlist
):
644 """ Compute bogo level from the filtering rules set up on the list.
648 if len(mlist
.header_filter_rules
) == 0:
655 # The first rule filters Unsure mails
656 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
660 # Check the other rules:
661 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
662 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
663 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
665 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
668 action
= mlist
.header_filter_rules
[filterbase
][1]
669 if action
== mm_cfg
.HOLD
:
671 elif action
== mm_cfg
.DISCARD
:
673 return (filterlevel
<< 1) + unsurelevel
675 def set_bogo_level(userdesc
, perms
, mlist
, level
):
676 """ Set filter to the specified level.
683 # The level is a combination of a spam filtering level and unsure filtering level
684 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
685 # - the spam filtering level is a number growing with filtering strength
686 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
687 bogolevel
= int(level
)
688 filterlevel
= bogolevel
>> 1
689 unsurelevel
= bogolevel
& 1
691 # Set up unusre filtering
693 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
695 # Set up spam filtering
697 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
698 elif filterlevel
is 2:
699 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
700 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
701 elif filterlevel
is 3:
702 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
705 if mlist
.header_filter_rules
!= hfr
:
706 mlist
.header_filter_rules
= hfr
709 #-------------------------------------------------------------------------------
710 # admin procedures [ soptions.php ]
713 admin_opts
= [ 'advertised', 'archive', \
714 'max_message_size', 'msg_footer', 'msg_header']
716 def get_admin_options(userdesc
, perms
, mlist
):
717 """ Get administrator options.
721 return get_options(userdesc
, perms
, mlist
, admin_opts
)
723 def set_admin_options(userdesc
, perms
, mlist
, values
):
724 """ Set administrator options.
729 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
731 #-------------------------------------------------------------------------------
732 # admin procedures [ check.php ]
736 'acceptable_aliases' : '',
737 'admin_immed_notify' : True,
738 'administrivia' : True,
739 'anonymous_list' : False,
740 'autorespond_admin' : False,
741 'autorespond_postings' : False,
742 'autorespond_requests' : False,
743 'available_languages' : ['fr'],
745 'bounce_matching_headers' : '',
746 'bounce_processing' : False,
747 'convert_html_to_plaintext' : False,
748 'digestable' : False,
749 'digest_is_default' : False,
750 'discard_these_nonmembers' : [],
752 'encode_ascii_prefixes' : 2,
753 'filter_content' : False,
754 'first_strip_reply_to' : False,
755 'forward_auto_discards' : True,
756 'hold_these_nonmembers' : [],
757 'host_name' : 'listes.polytechnique.org',
758 'include_list_post_header' : False,
759 'include_rfc2369_headers' : False,
760 'max_num_recipients' : 0,
761 'new_member_options' : 256,
762 'nondigestable' : True,
763 'obscure_addresses' : True,
764 'preferred_language' : 'fr',
765 'reject_these_nonmembers' : [],
766 'reply_goes_to_list' : 0,
767 'reply_to_address' : '',
768 'require_explicit_destination' : False,
769 'send_reminders' : 0,
770 'send_welcome_msg' : True,
771 'topics_enabled' : False,
772 'umbrella_list' : False,
773 'unsubscribe_policy' : 0,
776 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
778 for (k
, v
) in check_opts
.iteritems():
779 if mlist
.__dict__
[k
] != v
:
780 options
[k
] = v
, mlist
.__dict__
[k
]
781 if correct
: mlist
.__dict__
[k
] = v
782 if mlist
.real_name
.lower() != listname
:
783 options
['real_name'] = listname
, mlist
.real_name
784 if correct
: mlist
.real_name
= listname
788 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
792 listname
= listname
.lower()
793 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
795 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
797 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
799 #-------------------------------------------------------------------------------
800 # super-admin procedures
803 def get_all_lists(userdesc
, perms
, vhost
):
804 """ Get all the list for the given vhost
806 prefix
= vhost
.lower()+VHOST_SEP
807 names
= Utils
.list_names()
811 if not name
.startswith(prefix
):
813 result
.append(name
.replace(prefix
, ''))
816 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
817 """ Create a new list.
820 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
821 if Utils
.list_exists(name
):
826 email
= to_forlife(o
)[0]
827 if email
is not None:
832 mlist
= MailList
.MailList()
834 oldmask
= os
.umask(002)
835 pw
= sha
.new('foobar').hexdigest()
838 mlist
.Create(name
, owner
[0], pw
)
842 mlist
.real_name
= listname
843 mlist
.host_name
= 'listes.polytechnique.org'
844 mlist
.description
= desc
846 mlist
.advertised
= int(advertise
) is 0
847 mlist
.default_member_moderation
= int(modlevel
) is 2
848 mlist
.generic_nonmember_action
= int(modlevel
) > 0
849 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
850 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
854 mlist
.subject_prefix
= '['+listname
+'] '
855 mlist
.max_message_size
= 0
857 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
858 mlist
.msg_footer
= "_______________________________________________\n" \
859 + "Liste de diffusion %(real_name)s\n" \
860 + "http://listes.polytechnique.org/members/" + inverted_listname
862 mlist
.header_filter_rules
= []
863 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
864 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
866 if ON_CREATE_CMD
!= '':
867 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
870 check_options_runner(userdesc
, perms
, mlist
, listname
.lower(), True)
871 mass_subscribe(userdesc
, perms
, mlist
, members
)
876 # avoid the "-1 mail to moderate" bug
877 mlist
= MailList
.MailList(name
)
878 mlist
._UpdateRecords()
882 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
887 lname
= mlist
.internal_name()
889 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
890 # remove stalled locks
891 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
892 fn_lname
= filename
.split('.')[0]
893 if fn_lname
== lname
:
894 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
898 os
.path
.join('archives', 'private', lname
),
899 os
.path
.join('archives', 'private', lname
+'.mbox'),
900 os
.path
.join('archives', 'public', lname
),
901 os
.path
.join('archives', 'public', lname
+'.mbox')
903 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
906 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
907 """ Remove a user from all the lists.
910 if not del_from_promo
:
911 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
912 for list in Utils
.list_names():
916 mlist
= MailList
.MailList(list, lock
=0)
921 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
929 #-------------------------------------------------------------------------------
932 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
933 allow_reuse_address
= True
935 ################################################################################
939 #-------------------------------------------------------------------------------
940 # use Mailman user and group (not root)
941 # fork in background if asked to
944 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
945 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
948 os
.setregid(gid
, gid
)
949 os
.setreuid(uid
, uid
)
951 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
953 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
956 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
958 if o
== '-f' and os
.fork():
961 i18n
.set_language('fr')
965 #-------------------------------------------------------------------------------
968 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
971 server
.register_function(get_lists
)
972 server
.register_function(subscribe
)
973 server
.register_function(unsubscribe
)
975 server
.register_function(get_members
)
977 server
.register_function(get_members_limit
)
978 server
.register_function(get_owners
)
980 server
.register_function(replace_email
)
981 server
.register_function(mass_subscribe
)
982 server
.register_function(mass_unsubscribe
)
983 server
.register_function(add_owner
)
984 server
.register_function(del_owner
)
986 server
.register_function(get_pending_ops
)
987 server
.register_function(handle_request
)
988 server
.register_function(get_pending_sub
)
989 server
.register_function(get_pending_mail
)
991 server
.register_function(get_owner_options
)
992 server
.register_function(set_owner_options
)
993 server
.register_function(add_to_wl
)
994 server
.register_function(del_from_wl
)
995 server
.register_function(get_bogo_level
)
996 server
.register_function(set_bogo_level
)
998 server
.register_function(get_admin_options
)
999 server
.register_function(set_admin_options
)
1001 server
.register_function(check_options
)
1003 server
.register_function(get_all_lists
)
1004 server
.register_function(create_list
)
1005 server
.register_function(delete_list
)
1007 server
.register_function(kill
)
1009 server
.serve_forever()
1011 # vim:set et sw=4 sts=4 sws=4: