2 #***************************************************************************
3 #* Copyright (C) 2004 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
:
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', '')
74 VHOST_SEP
= get_config('Lists', 'vhost_sep', '_')
75 ON_CREATE_CMD
= get_config('Lists', 'on_create', '')
77 ################################################################################
81 #------------------------------------------------
82 # Manage Basic authentication
85 class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
87 """XMLRPC Request Handler
88 This request handler is used to provide BASIC HTTP user authentication.
89 It first overloads the do_POST() function, authenticates the user, then
90 calls the super.do_POST().
92 Moreover, we override _dispatch, so that we call functions with as first
93 argument a UserDesc taken from the database, containing name, email and perms
96 def _get_function(self
, method
):
98 # check to see if a matching function has been registered
99 return self
.server
.funcs
[method
]
101 raise Exception('method "%s" is not supported' % method
)
104 def _dispatch(self
, method
, params
):
105 new_params
= list(params
)
106 return list_call_dispatcher(self
._get_function(method
), self
.data
[0], self
.data
[1], self
.data
[2], *params
)
110 _
, auth
= self
.headers
["authorization"].split()
111 uid
, md5
= base64
.decodestring(auth
).strip().split(':')
112 vhost
= self
.path
.split('/')[1].lower()
113 self
.data
= self
.getUser(uid
, md5
, vhost
)
114 if self
.data
is None:
116 # Call super.do_POST() to do the actual work
117 SimpleXMLRPCRequestHandler
.do_POST(self
)
119 self
.send_response(401)
122 def getUser(self
, uid
, md5
, vhost
):
123 res
= mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms
124 FROM auth_user_md5 AS u
125 INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' )
126 WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user')
127 LIMIT 1""" %( uid
, md5
) )
129 name
, forlife
, perms
= res
130 if vhost
!= PLATAL_DOMAIN
:
131 res
= mysql_fetchone ("""SELECT uid
132 FROM groupex.membres AS m
133 INNER JOIN groupex.asso AS a ON (m.asso_id = a.id)
134 WHERE perms='admin' AND uid='%s' AND mail_domain='%s'""" %( uid
, vhost
) )
135 if res
: perms
= 'admin'
136 userdesc
= UserDesc(forlife
+'@'+PLATAL_DOMAIN
, name
, None, 0)
137 return (userdesc
, perms
, vhost
)
141 ################################################################################
145 #-------------------------------------------------------------------------------
150 db
= MySQLdb
.connect(
154 unix_socket
='/var/run/mysqld/mysqld.sock')
158 def mysql_fetchone(query
):
163 if int(mysql
.rowcount
) > 0:
164 ret
= mysql
.fetchone()
169 def is_admin_on(userdesc
, perms
, mlist
):
170 return ( perms
== 'admin' ) or ( userdesc
.address
in mlist
.owner
)
173 def quote(s
, is_header
=False):
175 h
= Utils
.oneline(s
, 'iso-8859-1')
178 h
= str('').join(re
.split('[\x00-\x08\x0B-\x1f]+', h
))
179 return Utils
.uquote(h
.replace('&', '&').replace('>', '>').replace('<', '<'))
181 def to_forlife(email
):
183 mbox
, fqdn
= email
.split('@')
187 if ( fqdn
== PLATAL_DOMAIN
) or ( fqdn
== PLATAL_DOMAIN2
):
188 res
= mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom)
189 FROM auth_user_md5 AS u
190 INNER JOIN aliases AS f ON (f.id=u.user_id AND f.type='a_vie')
191 INNER JOIN aliases AS a ON (a.id=u.user_id AND a.alias='%s' AND a.type!='homonyme')
192 WHERE u.perms IN ('admin', 'user')
193 LIMIT 1""" %( PLATAL_DOMAIN
, mbox
) )
198 return (email
.lower(), mbox
)
201 # see /usr/lib/mailman/bin/rmlist
203 def remove_it(listname
, filename
):
204 if os
.path
.islink(filename
) or os
.path
.isfile(filename
):
206 elif os
.path
.isdir(filename
):
207 shutil
.rmtree(filename
)
213 def has_annotation(method
, name
):
214 """ Check if the method contains the given annoation.
216 return method
.__doc__
and method
.__doc__
.find("@%s" % name
) > -1
218 def list_call_dispatcher(method
, userdesc
, perms
, vhost
, *arg
):
219 """Dispatch the call to the right handler.
220 This function checks the options of the called method the set the environment of the call.
221 The dispatcher uses method annotation (special tokens in the documentation of the method) to
222 guess the requested environment:
223 @mlist: the handler requires a mlist object instead of the vhost/listname couple
224 @lock: the handler requires the mlist to be locked (@mlist MUST be specified)
225 @edit: the handler edit the mlist (@mlist MUST be specified)
226 @admin: the handler requires admin rights on the list (@mlist MUST be specified)
227 @root: the handler requires site admin rights
230 if has_annotation(method
, "root") and perms
!= "admin":
232 if has_annotation(method
, "mlist"):
235 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
.lower(), lock
=0)
236 if has_annotation(method
, "admin") and not is_admin_on(userdesc
, perms
, mlist
):
238 if has_annotation(method
, "edit") or has_annotation(method
, "lock"):
239 return list_call_locked(method
, userdesc
, perms
, mlist
, has_annotation(method
, "edit"), *arg
)
241 return method(userdesc
, perms
, mlist
, *arg
)
243 return method(userdesc
, perms
, vhost
, *arg
)
248 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
249 """Call the given method after locking the mlist.
253 ret
= method(userdesc
, perms
, mlist
, *arg
)
261 # TODO: use finally when switching to python 2.5
263 #-------------------------------------------------------------------------------
267 def is_subscription_pending(userdesc
, perms
, mlist
, edit
):
268 for id in mlist
.GetSubscriptionIds():
269 if userdesc
.address
== mlist
.GetRecord(id)[1]:
273 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
274 members
= mlist
.getRegularMemberKeys()
275 is_member
= userdesc
.address
in members
276 is_owner
= userdesc
.address
in mlist
.owner
277 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
279 if not is_member
and (mlist
.subscribe_policy
> 1):
280 is_pending
= list_call_locked(userdesc
, perms
, mlist
, is_subscription_pending
, False)
284 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
286 'list' : mlist
.real_name
,
287 'addr' : mlist
.real_name
.lower() + '@' + host
,
289 'desc' : quote(mlist
.description
),
290 'info' : quote(mlist
.info
),
291 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
292 'ins' : mlist
.subscribe_policy
> 1,
293 'priv' : 1-mlist
.advertised
,
294 'sub' : 2*is_member
+ is_pending
,
296 'nbsub': len(members
)
298 return (details
, members
)
301 def get_options(userdesc
, perms
, mlist
, opts
):
302 """ Get the options of a list.
307 for (k
, v
) in mlist
.__dict__
.iteritems():
310 options
[k
] = quote(v
)
312 details
= get_list_info(userdesc
, perms
, mlist
)[0]
313 return (details
, options
)
315 def set_options(userdesc
, perms
, mlist
, vals
):
316 """ Set the options of a list.
321 for (k
, v
) in vals
.iteritems():
324 if k
== 'default_member_moderation':
325 for member
in mlist
.getMembers():
326 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
327 t
= type(mlist
.__dict__
[k
])
328 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
329 elif t
is int: mlist
.__dict__
[k
] = int(v
)
330 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
331 else: mlist
.__dict__
[k
] = v
334 #-------------------------------------------------------------------------------
335 # users procedures for [ index.php ]
338 def get_lists(userdesc
, perms
, vhost
, email
=None):
339 """ List available lists for the given vhost
344 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
345 prefix
= vhost
.lower()+VHOST_SEP
346 names
= Utils
.list_names()
350 if not name
.startswith(prefix
):
353 mlist
= MailList
.MailList(name
, lock
=0)
357 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
358 result
.append(details
)
363 def subscribe(userdesc
, perms
, mlist
):
364 """ Subscribe to a list.
368 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
369 mlist
.ApprovedAddMember(userdesc
)
374 mlist
.AddMember(userdesc
)
375 except Errors
.MMNeedApproval
:
379 def unsubscribe(userdesc
, perms
, mlist
):
380 """ Unsubscribe from a list
384 mlist
.ApprovedDeleteMember(userdesc
.address
)
387 #-------------------------------------------------------------------------------
388 # users procedures for [ index.php ]
391 def get_name(member
):
393 return quote(mlist
.getMemberName(member
))
397 def get_members(userdesc
, perms
, mlist
):
398 """ List the members of a list.
401 details
, members
= get_list_info(userdesc
, perms
, mlist
)
403 members
= map(lambda member
: (get_name(member
), member
), members
)
404 return (details
, members
, mlist
.owner
)
407 #-------------------------------------------------------------------------------
408 # users procedures for [ trombi.php ]
411 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
412 """ Get a range of members of the list.
415 members
= get_members(userdesc
, perms
, mlist
)[1]
416 i
= int(page
) * int(nb_per_page
)
417 return (len(members
), members
[i
:i
+int(nb_per_page
)])
419 def get_owners(userdesc
, perms
, mlist
):
420 """ Get the owners of the list.
423 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
424 return (details
, owners
)
427 #-------------------------------------------------------------------------------
428 # owners procedures [ admin.php ]
431 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
432 """ Replace the address of a member by another one.
437 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
440 def mass_subscribe(userdesc
, perms
, mlist
, users
):
441 """ Add a list of users to the list.
446 members
= mlist
.getRegularMemberKeys()
450 email
, name
= to_forlife(user
)
451 if ( email
is None ) or ( email
in members
):
453 userd
= UserDesc(email
, name
, None, 0)
454 mlist
.ApprovedAddMember(userd
)
455 added
.append( (quote(userd
.fullname
), userd
.address
) )
458 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
459 """ Remove a list of users from the list.
464 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
467 def add_owner(userdesc
, perms
, mlist
, user
):
468 """ Add a owner to the list.
473 email
= to_forlife(user
)[0]
476 if email
not in mlist
.owner
:
477 mlist
.owner
.append(email
)
480 def del_owner(userdesc
, perms
, mlist
, user
):
481 """ Remove a owner of the list.
486 if len(mlist
.owner
) < 2:
488 mlist
.owner
.remove(user
)
491 #-------------------------------------------------------------------------------
492 # owners procedures [ admin.php ]
495 def get_pending_ops(userdesc
, perms
, mlist
):
496 """ Get the list of operation waiting for an action from the owners.
504 for id in mlist
.GetSubscriptionIds():
505 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
507 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
512 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
513 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
515 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
518 for id in mlist
.GetHeldMessageIds():
519 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
520 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
522 size
= os
.path
.getsize(fpath
)
524 if e
.errno
<> errno
.ENOENT
: raise
527 msg
= readMessage(fpath
)
528 fromX
= msg
.has_key("X-Org-Mail")
533 'sender': quote(sender
, True),
535 'subj' : quote(subject
, True),
543 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
544 """ Handle a moderation request.
549 mlist
.HandleRequest(int(id), int(value
), comment
)
552 def get_pending_sub(userdesc
, perms
, mlist
, id):
553 """ Get informations about a given subscription moderation.
560 if id in mlist
.GetSubscriptionIds():
561 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
563 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
564 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
566 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
569 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
570 """ Get informations about a given mail moderation.
575 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
576 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
577 size
= os
.path
.getsize(fpath
)
578 msg
= readMessage(fpath
)
581 return quote(str(msg
))
584 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
585 c
= part
.get_payload()
586 if c
is not None: results_plain
.append (c
)
587 results_plain
= map(lambda x
: quote(x
), results_plain
)
588 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
589 c
= part
.get_payload()
590 if c
is not None: results_html
.append (c
)
591 results_html
= map(lambda x
: quote(x
), results_html
)
593 'sender': quote(sender
, True),
595 'subj' : quote(subject
, True),
597 'parts_plain' : results_plain
,
598 'parts_html': results_html
}
600 #-------------------------------------------------------------------------------
601 # owner options [ options.php ]
604 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
605 'default_member_moderation', 'generic_nonmember_action', 'info', \
606 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
609 def get_owner_options(userdesc
, perms
, mlist
):
610 """ Get the owner options of a list.
614 return get_options(userdesc
, perms
, mlist
, owner_opts
)
616 def set_owner_options(userdesc
, perms
, mlist
, values
):
617 """ Set the owner options of a list.
622 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
624 def add_to_wl(userdesc
, perms
, mlist
, addr
):
625 """ Add addr to the whitelist
630 mlist
.accept_these_nonmembers
.append(addr
)
633 def del_from_wl(userdesc
, perms
, mlist
, addr
):
634 """ Remove an address from the whitelist
639 mlist
.accept_these_nonmembers
.remove(addr
)
642 def get_bogo_level(userdesc
, perms
, mlist
):
643 """ Compute bogo level from the filtering rules set up on the list.
647 if len(mlist
.header_filter_rules
) == 0:
654 # The first rule filters Unsure mails
655 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
659 # Check the other rules:
660 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
661 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
662 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
664 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
667 action
= mlist
.header_filter_rules
[filterbase
][1]
668 if action
== mm_cfg
.HOLD
:
670 elif action
== mm_cfg
.DISCARD
:
672 return (filterlevel
<< 1) + unsurelevel
674 def set_bogo_level(userdesc
, perms
, vhost
, listname
, level
):
675 """ Set filter to the specified level.
682 # The level is a combination of a spam filtering level and unsure filtering level
683 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
684 # - the spam filtering level is a number growing with filtering strength
685 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
686 bogolevel
= int(level
)
687 filterlevel
= bogolevel
>> 1
688 unsurelevel
= bogolevel
& 1
690 # Set up unusre filtering
692 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
694 # Set up spam filtering
696 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
697 elif filterlevel
is 2:
698 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
699 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
700 elif filterlevel
is 3:
701 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
704 if mlist
.header_filter_rules
!= hfr
:
705 mlist
.header_filter_rules
= hfr
708 #-------------------------------------------------------------------------------
709 # admin procedures [ soptions.php ]
712 admin_opts
= [ 'advertised', 'archive', \
713 'max_message_size', 'msg_footer', 'msg_header']
715 def get_admin_options(userdesc
, perms
, mlist
):
716 """ Get administrator options.
720 return get_options(userdesc
, perms
, mlist
, admin_opts
)
722 def set_admin_options(userdesc
, perms
, mlist
, values
):
723 """ Set administrator options.
728 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
730 #-------------------------------------------------------------------------------
731 # admin procedures [ check.php ]
735 'acceptable_aliases' : '',
736 'admin_immed_notify' : True,
737 'administrivia' : True,
738 'anonymous_list' : False,
739 'autorespond_admin' : False,
740 'autorespond_postings' : False,
741 'autorespond_requests' : False,
742 'available_languages' : ['fr'],
744 'bounce_matching_headers' : '',
745 'bounce_processing' : False,
746 'convert_html_to_plaintext' : False,
747 'digestable' : False,
748 'digest_is_default' : False,
749 'discard_these_nonmembers' : [],
751 'encode_ascii_prefixes' : 2,
752 'filter_content' : False,
753 'first_strip_reply_to' : False,
754 'forward_auto_discards' : True,
755 'hold_these_nonmembers' : [],
756 'host_name' : 'listes.polytechnique.org',
757 'include_list_post_header' : False,
758 'include_rfc2369_headers' : False,
759 'max_num_recipients' : 0,
760 'new_member_options' : 256,
761 'nondigestable' : True,
762 'obscure_addresses' : True,
763 'preferred_language' : 'fr',
764 'reject_these_nonmembers' : [],
765 'reply_goes_to_list' : 0,
766 'reply_to_address' : '',
767 'require_explicit_destination' : False,
768 'send_reminders' : 0,
769 'send_welcome_msg' : True,
770 'topics_enabled' : False,
771 'umbrella_list' : False,
772 'unsubscribe_policy' : 0,
775 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
777 for (k
, v
) in check_opts
.iteritems():
778 if mlist
.__dict__
[k
] != v
:
779 options
[k
] = v
, mlist
.__dict__
[k
]
780 if correct
: mlist
.__dict__
[k
] = v
781 if mlist
.real_name
.lower() != listname
:
782 options
['real_name'] = listname
, mlist
.real_name
783 if correct
: mlist
.real_name
= listname
784 details
= get_list_info(userdesc
, perms
, mlist
)[0]
785 return (details
, options
)
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))
868 if ON_CREATE_CMD
!= '':
869 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
872 check_options(userdesc
, perms
, mlist
, True)
873 mass_subscribe(userdesc
, perms
, mlist
, members
)
875 # avoid the "-1 mail to moderate" bug
876 mlist
= MailList
.MailList(name
)
877 mlist
._UpdateRecords()
885 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
890 lname
= mlist
.internal_name()
892 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
893 # remove stalled locks
894 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
895 fn_lname
= filename
.split('.')[0]
896 if fn_lname
== lname
:
897 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
901 os
.path
.join('archives', 'private', lname
),
902 os
.path
.join('archives', 'private', lname
+'.mbox'),
903 os
.path
.join('archives', 'public', lname
),
904 os
.path
.join('archives', 'public', lname
+'.mbox')
906 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
909 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
910 """ Remove a user from all the lists.
913 if not del_from_promo
:
914 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
915 for list in Utils
.list_names():
919 mlist
= MailList
.MailList(list, lock
=0)
924 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
932 #-------------------------------------------------------------------------------
935 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
936 allow_reuse_address
= True
938 ################################################################################
942 #-------------------------------------------------------------------------------
943 # use Mailman user and group (not root)
944 # fork in background if asked to
947 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
948 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
951 os
.setregid(gid
, gid
)
952 os
.setreuid(uid
, uid
)
954 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
956 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
959 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
961 if o
== '-f' and os
.fork():
964 i18n
.set_language('fr')
968 #-------------------------------------------------------------------------------
971 server
= FastXMLRPCServer(("localhost", 4949), BasicAuthXMLRPCRequestHandler
)
974 server
.register_function(get_lists
)
975 server
.register_function(subscribe
)
976 server
.register_function(unsubscribe
)
978 server
.register_function(get_members
)
980 server
.register_function(get_members_limit
)
981 server
.register_function(get_owners
)
983 server
.register_function(replace_email
)
984 server
.register_function(mass_subscribe
)
985 server
.register_function(mass_unsubscribe
)
986 server
.register_function(add_owner
)
987 server
.register_function(del_owner
)
989 server
.register_function(get_pending_ops
)
990 server
.register_function(handle_request
)
991 server
.register_function(get_pending_sub
)
992 server
.register_function(get_pending_mail
)
994 server
.register_function(get_owner_options
)
995 server
.register_function(set_owner_options
)
996 server
.register_function(add_to_wl
)
997 server
.register_function(del_from_wl
)
998 server
.register_function(get_bogo_level
)
999 server
.register_function(set_bogo_level
)
1001 server
.register_function(get_admin_options
)
1002 server
.register_function(set_admin_options
)
1004 server
.register_function(check_options
)
1006 server
.register_function(get_all_lists
)
1007 server
.register_function(create_list
)
1008 server
.register_function(delete_list
)
1010 server
.register_function(kill
)
1012 server
.serve_forever()
1014 # vim:set et sw=4 sts=4 sws=4: