2 #***************************************************************************
3 #* Copyright (C) 2004-2008 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
)
252 def list_call_locked(method
, userdesc
, perms
, mlist
, edit
, *arg
):
253 """Call the given method after locking the mlist.
257 ret
= method(userdesc
, perms
, mlist
, *arg
)
265 # TODO: use finally when switching to python 2.5
267 #-------------------------------------------------------------------------------
271 def is_subscription_pending(userdesc
, perms
, mlist
):
272 for id in mlist
.GetSubscriptionIds():
273 if userdesc
.address
== mlist
.GetRecord(id)[1]:
277 def get_list_info(userdesc
, perms
, mlist
, front_page
=0):
278 members
= mlist
.getRegularMemberKeys()
279 is_member
= userdesc
.address
in members
280 is_owner
= userdesc
.address
in mlist
.owner
281 if mlist
.advertised
or is_member
or is_owner
or (not front_page
and perms
== 'admin'):
283 if not is_member
and (mlist
.subscribe_policy
> 1):
284 is_pending
= list_call_locked(is_subscription_pending
, userdesc
, perms
, mlist
, False)
288 host
= mlist
.internal_name().split(VHOST_SEP
)[0].lower()
290 'list' : mlist
.real_name
,
291 'addr' : mlist
.real_name
.lower() + '@' + host
,
293 'desc' : quote(mlist
.description
),
294 'info' : quote(mlist
.info
),
295 'diff' : (mlist
.default_member_moderation
>0) + (mlist
.generic_nonmember_action
>0),
296 'ins' : mlist
.subscribe_policy
> 1,
297 'priv' : 1-mlist
.advertised
,
298 'sub' : 2*is_member
+ is_pending
,
300 'nbsub': len(members
)
302 return (details
, members
)
305 def get_options(userdesc
, perms
, mlist
, opts
):
306 """ Get the options of a list.
311 for (k
, v
) in mlist
.__dict__
.iteritems():
314 options
[k
] = quote(v
)
316 details
= get_list_info(userdesc
, perms
, mlist
)[0]
317 return (details
, options
)
319 def set_options(userdesc
, perms
, mlist
, vals
):
320 """ Set the options of a list.
325 for (k
, v
) in vals
.iteritems():
328 if k
== 'default_member_moderation':
329 for member
in mlist
.getMembers():
330 mlist
.setMemberOption(member
, mm_cfg
.Moderate
, int(v
))
331 t
= type(mlist
.__dict__
[k
])
332 if t
is bool: mlist
.__dict__
[k
] = bool(v
)
333 elif t
is int: mlist
.__dict__
[k
] = int(v
)
334 elif t
is str: mlist
.__dict__
[k
] = Utils
.uncanonstr(v
, 'fr')
335 else: mlist
.__dict__
[k
] = v
338 #-------------------------------------------------------------------------------
339 # users procedures for [ index.php ]
342 def get_lists(userdesc
, perms
, vhost
, email
=None):
343 """ List available lists for the given vhost
348 udesc
= UserDesc(email
.lower(), email
.lower(), None, 0)
349 prefix
= vhost
.lower()+VHOST_SEP
350 names
= Utils
.list_names()
354 if not name
.startswith(prefix
):
357 mlist
= MailList
.MailList(name
, lock
=0)
361 details
= get_list_info(udesc
, perms
, mlist
, (email
is None and vhost
== PLATAL_DOMAIN
))[0]
362 result
.append(details
)
364 sys
.stderr
.write('Can\'t get list %s: %s\n' %
(name
, str(e
)))
368 def subscribe(userdesc
, perms
, mlist
):
369 """ Subscribe to a list.
373 if ( mlist
.subscribe_policy
in (0, 1) ) or userdesc
.address
in mlist
.owner
:
374 mlist
.ApprovedAddMember(userdesc
)
379 mlist
.AddMember(userdesc
)
380 except Errors
.MMNeedApproval
:
384 def unsubscribe(userdesc
, perms
, mlist
):
385 """ Unsubscribe from a list
389 mlist
.ApprovedDeleteMember(userdesc
.address
)
392 #-------------------------------------------------------------------------------
393 # users procedures for [ index.php ]
396 def get_name(member
):
398 return quote(mlist
.getMemberName(member
))
402 def get_members(userdesc
, perms
, mlist
):
403 """ List the members of a list.
406 details
, members
= get_list_info(userdesc
, perms
, mlist
)
408 members
= map(lambda member
: (get_name(member
), member
), members
)
409 return (details
, members
, mlist
.owner
)
412 #-------------------------------------------------------------------------------
413 # users procedures for [ trombi.php ]
416 def get_members_limit(userdesc
, perms
, mlist
, page
, nb_per_page
):
417 """ Get a range of members of the list.
420 members
= get_members(userdesc
, perms
, mlist
)[1]
421 i
= int(page
) * int(nb_per_page
)
422 return (len(members
), members
[i
:i
+int(nb_per_page
)])
424 def get_owners(userdesc
, perms
, mlist
):
425 """ Get the owners of the list.
428 details
, members
, owners
= get_members(userdesc
, perms
, mlist
)
429 return (details
, owners
)
432 #-------------------------------------------------------------------------------
433 # owners procedures [ admin.php ]
436 def replace_email(userdesc
, perms
, mlist
, from_email
, to_email
):
437 """ Replace the address of a member by another one.
442 mlist
.ApprovedChangeMemberAddress(from_email
.lower(), to_email
.lower(), 0)
445 def mass_subscribe(userdesc
, perms
, mlist
, users
):
446 """ Add a list of users to the list.
451 members
= mlist
.getRegularMemberKeys()
455 email
, name
= to_forlife(user
)
456 if ( email
is None ) or ( email
in members
):
458 userd
= UserDesc(email
, name
, None, 0)
459 mlist
.ApprovedAddMember(userd
)
460 added
.append( (quote(userd
.fullname
), userd
.address
) )
463 def mass_unsubscribe(userdesc
, perms
, mlist
, users
):
464 """ Remove a list of users from the list.
469 map(lambda user
: mlist
.ApprovedDeleteMember(user
), users
)
472 def add_owner(userdesc
, perms
, mlist
, user
):
473 """ Add a owner to the list.
478 email
= to_forlife(user
)[0]
481 if email
not in mlist
.owner
:
482 mlist
.owner
.append(email
)
485 def del_owner(userdesc
, perms
, mlist
, user
):
486 """ Remove a owner of the list.
491 if len(mlist
.owner
) < 2:
493 mlist
.owner
.remove(user
)
496 #-------------------------------------------------------------------------------
497 # owners procedures [ admin.php ]
500 def get_pending_ops(userdesc
, perms
, mlist
):
501 """ Get the list of operation waiting for an action from the owners.
509 for id in mlist
.GetSubscriptionIds():
510 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
512 mlist
.HandleRequest(id, mm_cfg
.DISCARD
)
517 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
518 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
})
520 subs
.append({'id': id, 'name': quote(fullname
), 'addr': addr
})
523 for id in mlist
.GetHeldMessageIds():
524 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(id)
525 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
527 size
= os
.path
.getsize(fpath
)
529 if e
.errno
<> errno
.ENOENT
: raise
532 msg
= readMessage(fpath
)
533 fromX
= msg
.has_key("X-Org-Mail")
538 'sender': quote(sender
, True),
540 'subj' : quote(subject
, True),
548 def handle_request(userdesc
, perms
, mlist
, id, value
, comment
):
549 """ Handle a moderation request.
554 mlist
.HandleRequest(int(id), int(value
), comment
)
557 def get_pending_sub(userdesc
, perms
, mlist
, id):
558 """ Get informations about a given subscription moderation.
565 if id in mlist
.GetSubscriptionIds():
566 time
, addr
, fullname
, passwd
, digest
, lang
= mlist
.GetRecord(id)
568 login
= re
.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr
.split('@')[0]).group()
569 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
, 'login': login
}
571 sub
= {'id': id, 'name': quote(fullname
), 'addr': addr
}
574 def get_pending_mail(userdesc
, perms
, mlist
, id, raw
=0):
575 """ Get informations about a given mail moderation.
580 ptime
, sender
, subject
, reason
, filename
, msgdata
= mlist
.GetRecord(int(id))
581 fpath
= os
.path
.join(mm_cfg
.DATA_DIR
, filename
)
582 size
= os
.path
.getsize(fpath
)
583 msg
= readMessage(fpath
)
586 return quote(str(msg
))
589 for part
in typed_subpart_iterator(msg
, 'text', 'plain'):
590 c
= part
.get_payload()
591 if c
is not None: results_plain
.append (c
)
592 results_plain
= map(lambda x
: quote(x
), results_plain
)
593 for part
in typed_subpart_iterator(msg
, 'text', 'html'):
594 c
= part
.get_payload()
595 if c
is not None: results_html
.append (c
)
596 results_html
= map(lambda x
: quote(x
), results_html
)
598 'sender': quote(sender
, True),
600 'subj' : quote(subject
, True),
602 'parts_plain' : results_plain
,
603 'parts_html': results_html
}
605 #-------------------------------------------------------------------------------
606 # owner options [ options.php ]
609 owner_opts
= ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
610 'default_member_moderation', 'generic_nonmember_action', 'info', \
611 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
614 def get_owner_options(userdesc
, perms
, mlist
):
615 """ Get the owner options of a list.
619 return get_options(userdesc
, perms
, mlist
, owner_opts
)
621 def set_owner_options(userdesc
, perms
, mlist
, values
):
622 """ Set the owner options of a list.
627 return set_options(userdesc
, perms
, mlist
, owner_opts
, values
)
629 def add_to_wl(userdesc
, perms
, mlist
, addr
):
630 """ Add addr to the whitelist
635 mlist
.accept_these_nonmembers
.append(addr
)
638 def del_from_wl(userdesc
, perms
, mlist
, addr
):
639 """ Remove an address from the whitelist
644 mlist
.accept_these_nonmembers
.remove(addr
)
647 def get_bogo_level(userdesc
, perms
, mlist
):
648 """ Compute bogo level from the filtering rules set up on the list.
652 if len(mlist
.header_filter_rules
) == 0:
659 # The first rule filters Unsure mails
660 if mlist
.header_filter_rules
[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
664 # Check the other rules:
665 # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes)
666 # - we have only one rule with HOLD directive : this is level 1 (moderate spams)
667 # - we have only one rule with DISCARD directive : this is level 3 (drop spams)
669 action
= mlist
.header_filter_rules
[filterbase
+ 1][1]
672 action
= mlist
.header_filter_rules
[filterbase
][1]
673 if action
== mm_cfg
.HOLD
:
675 elif action
== mm_cfg
.DISCARD
:
677 return (filterlevel
<< 1) + unsurelevel
679 def set_bogo_level(userdesc
, perms
, vhost
, listname
, level
):
680 """ Set filter to the specified level.
687 # The level is a combination of a spam filtering level and unsure filtering level
688 # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures)
689 # - the spam filtering level is a number growing with filtering strength
690 # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams)
691 bogolevel
= int(level
)
692 filterlevel
= bogolevel
>> 1
693 unsurelevel
= bogolevel
& 1
695 # Set up unusre filtering
697 hfr
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
699 # Set up spam filtering
701 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
702 elif filterlevel
is 2:
703 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg
.DISCARD
, False))
704 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
705 elif filterlevel
is 3:
706 hfr
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.DISCARD
, False))
709 if mlist
.header_filter_rules
!= hfr
:
710 mlist
.header_filter_rules
= hfr
713 #-------------------------------------------------------------------------------
714 # admin procedures [ soptions.php ]
717 admin_opts
= [ 'advertised', 'archive', \
718 'max_message_size', 'msg_footer', 'msg_header']
720 def get_admin_options(userdesc
, perms
, mlist
):
721 """ Get administrator options.
725 return get_options(userdesc
, perms
, mlist
, admin_opts
)
727 def set_admin_options(userdesc
, perms
, mlist
, values
):
728 """ Set administrator options.
733 return set_options(userdesc
, perms
, mlist
, admin_opts
, values
)
735 #-------------------------------------------------------------------------------
736 # admin procedures [ check.php ]
740 'acceptable_aliases' : '',
741 'admin_immed_notify' : True,
742 'administrivia' : True,
743 'anonymous_list' : False,
744 'autorespond_admin' : False,
745 'autorespond_postings' : False,
746 'autorespond_requests' : False,
747 'available_languages' : ['fr'],
749 'bounce_matching_headers' : '',
750 'bounce_processing' : False,
751 'convert_html_to_plaintext' : False,
752 'digestable' : False,
753 'digest_is_default' : False,
754 'discard_these_nonmembers' : [],
756 'encode_ascii_prefixes' : 2,
757 'filter_content' : False,
758 'first_strip_reply_to' : False,
759 'forward_auto_discards' : True,
760 'hold_these_nonmembers' : [],
761 'host_name' : 'listes.polytechnique.org',
762 'include_list_post_header' : False,
763 'include_rfc2369_headers' : False,
764 'max_num_recipients' : 0,
765 'new_member_options' : 256,
766 'nondigestable' : True,
767 'obscure_addresses' : True,
768 'preferred_language' : 'fr',
769 'reject_these_nonmembers' : [],
770 'reply_goes_to_list' : 0,
771 'reply_to_address' : '',
772 'require_explicit_destination' : False,
773 'send_reminders' : 0,
774 'send_welcome_msg' : True,
775 'topics_enabled' : False,
776 'umbrella_list' : False,
777 'unsubscribe_policy' : 0,
780 def check_options_runner(userdesc
, perms
, mlist
, listname
, correct
):
782 for (k
, v
) in check_opts
.iteritems():
783 if mlist
.__dict__
[k
] != v
:
784 options
[k
] = v
, mlist
.__dict__
[k
]
785 if correct
: mlist
.__dict__
[k
] = v
786 if mlist
.real_name
.lower() != listname
:
787 options
['real_name'] = listname
, mlist
.real_name
788 if correct
: mlist
.real_name
= listname
789 details
= get_list_info(userdesc
, perms
, mlist
)[0]
790 return (details
, options
)
793 def check_options(userdesc
, perms
, vhost
, listname
, correct
=False):
797 listname
= listname
.lower()
798 mlist
= MailList
.MailList(vhost
+ VHOST_SEP
+ listname
, lock
=0)
800 return list_call_locked(check_options_runner
, userdesc
, perms
, mlist
, True, listname
, True)
802 return check_options_runner(userdesc
, perms
, mlist
, listname
, False)
804 #-------------------------------------------------------------------------------
805 # super-admin procedures
808 def get_all_lists(userdesc
, perms
, vhost
):
809 """ Get all the list for the given vhost
811 prefix
= vhost
.lower()+VHOST_SEP
812 names
= Utils
.list_names()
816 if not name
.startswith(prefix
):
818 result
.append(name
.replace(prefix
, ''))
821 def create_list(userdesc
, perms
, vhost
, listname
, desc
, advertise
, modlevel
, inslevel
, owners
, members
):
822 """ Create a new list.
825 name
= vhost
.lower() + VHOST_SEP
+ listname
.lower();
826 if Utils
.list_exists(name
):
831 email
= to_forlife(o
)[0]
832 if email
is not None:
837 mlist
= MailList
.MailList()
839 oldmask
= os
.umask(002)
840 pw
= sha
.new('foobar').hexdigest()
843 mlist
.Create(name
, owner
[0], pw
)
847 mlist
.real_name
= listname
848 mlist
.host_name
= 'listes.polytechnique.org'
849 mlist
.description
= desc
851 mlist
.advertised
= int(advertise
) is 0
852 mlist
.default_member_moderation
= int(modlevel
) is 2
853 mlist
.generic_nonmember_action
= int(modlevel
) > 0
854 mlist
.subscribe_policy
= 2 * (int(inslevel
) is 1)
855 mlist
.admin_notify_mchanges
= (mlist
.subscribe_policy
or mlist
.generic_nonmember_action
or mlist
.default_member_moderation
or not mlist
.advertised
)
859 mlist
.subject_prefix
= '['+listname
+'] '
860 mlist
.max_message_size
= 0
862 inverted_listname
= listname
.lower() + '_' + vhost
.lower()
863 mlist
.msg_footer
= "_______________________________________________\n" \
864 + "Liste de diffusion %(real_name)s\n" \
865 + "http://listes.polytechnique.org/members/" + inverted_listname
867 mlist
.header_filter_rules
= []
868 mlist
.header_filter_rules
.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg
.HOLD
, False))
869 mlist
.header_filter_rules
.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg
.HOLD
, False))
873 if ON_CREATE_CMD
!= '':
874 try: os
.system(ON_CREATE_CMD
+ ' ' + name
)
877 check_options(userdesc
, perms
, mlist
, True)
878 mass_subscribe(userdesc
, perms
, mlist
, members
)
880 # avoid the "-1 mail to moderate" bug
881 mlist
= MailList
.MailList(name
)
882 mlist
._UpdateRecords()
890 def delete_list(userdesc
, perms
, mlist
, del_archives
=0):
895 lname
= mlist
.internal_name()
897 REMOVABLES
= [ os
.path
.join('lists', lname
), ]
898 # remove stalled locks
899 for filename
in os
.listdir(mm_cfg
.LOCK_DIR
):
900 fn_lname
= filename
.split('.')[0]
901 if fn_lname
== lname
:
902 REMOVABLES
.append(os
.path
.join(mm_cfg
.LOCK_DIR
, filename
))
906 os
.path
.join('archives', 'private', lname
),
907 os
.path
.join('archives', 'private', lname
+'.mbox'),
908 os
.path
.join('archives', 'public', lname
),
909 os
.path
.join('archives', 'public', lname
+'.mbox')
911 map(lambda dir: remove_it(lname
, os
.path
.join(mm_cfg
.VAR_PREFIX
, dir)), REMOVABLES
)
914 def kill(userdesc
, perms
, vhost
, alias
, del_from_promo
):
915 """ Remove a user from all the lists.
918 if not del_from_promo
:
919 exclude
.append(PLATAL_DOMAIN
+ VHOST_SEP
+ 'promo' + alias
[-4:])
920 for list in Utils
.list_names():
924 mlist
= MailList
.MailList(list, lock
=0)
929 mlist
.ApprovedDeleteMember(alias
+'@'+PLATAL_DOMAIN
, None, 0, 0)
937 #-------------------------------------------------------------------------------
940 class FastXMLRPCServer(SocketServer
.ThreadingMixIn
, SimpleXMLRPCServer
):
941 allow_reuse_address
= True
943 ################################################################################
947 #-------------------------------------------------------------------------------
948 # use Mailman user and group (not root)
949 # fork in background if asked to
952 uid
= getpwnam(mm_cfg
.MAILMAN_USER
)[2]
953 gid
= getgrnam(mm_cfg
.MAILMAN_GROUP
)[2]
956 os
.setregid(gid
, gid
)
957 os
.setreuid(uid
, uid
)
959 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
961 if ( os
.getuid() is not uid
) or ( os
.getgid() is not gid
):
964 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'f')
966 if o
== '-f' and os
.fork():
969 i18n
.set_language('fr')
973 #-------------------------------------------------------------------------------
976 server
= FastXMLRPCServer((SRV_HOST
, SRV_PORT
), BasicAuthXMLRPCRequestHandler
)
979 server
.register_function(get_lists
)
980 server
.register_function(subscribe
)
981 server
.register_function(unsubscribe
)
983 server
.register_function(get_members
)
985 server
.register_function(get_members_limit
)
986 server
.register_function(get_owners
)
988 server
.register_function(replace_email
)
989 server
.register_function(mass_subscribe
)
990 server
.register_function(mass_unsubscribe
)
991 server
.register_function(add_owner
)
992 server
.register_function(del_owner
)
994 server
.register_function(get_pending_ops
)
995 server
.register_function(handle_request
)
996 server
.register_function(get_pending_sub
)
997 server
.register_function(get_pending_mail
)
999 server
.register_function(get_owner_options
)
1000 server
.register_function(set_owner_options
)
1001 server
.register_function(add_to_wl
)
1002 server
.register_function(del_from_wl
)
1003 server
.register_function(get_bogo_level
)
1004 server
.register_function(set_bogo_level
)
1006 server
.register_function(get_admin_options
)
1007 server
.register_function(set_admin_options
)
1009 server
.register_function(check_options
)
1011 server
.register_function(get_all_lists
)
1012 server
.register_function(create_list
)
1013 server
.register_function(delete_list
)
1015 server
.register_function(kill
)
1017 server
.serve_forever()
1019 # vim:set et sw=4 sts=4 sws=4: