Fix SU on skin Sharky.
[platal.git] / bin / lists.rpc.py
CommitLineData
0337d704 1#!/usr/bin/env python
2#***************************************************************************
1fec3393 3#* Copyright (C) 2004-2008 polytechnique.org *
0337d704 4#* http://opensource.polytechnique.org/ *
5#* *
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. *
10#* *
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. *
15#* *
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#***************************************************************************
21
22import base64, MySQLdb, os, getopt, sys, sha, signal, re, shutil, ConfigParser
23import MySQLdb.converters
24import SocketServer
25
26sys.path.append('/usr/lib/mailman/bin')
27
28from pwd import getpwnam
29from grp import getgrnam
30
31from SimpleXMLRPCServer import SimpleXMLRPCServer
32from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
33
34import paths
35from Mailman import MailList
36from Mailman import Utils
37from Mailman import Message
38from Mailman import Errors
39from Mailman import mm_cfg
40from Mailman import i18n
41from Mailman.UserDesc import UserDesc
42from Mailman.ListAdmin import readMessage
43from email.Iterators import typed_subpart_iterator
44from threading import Lock
45
46class AuthFailed(Exception): pass
47
48################################################################################
49#
50# CONFIG
51#
52#------------------------------------------------
53
54config = ConfigParser.ConfigParser()
78dd3eb2 55config.read(os.path.dirname(__file__)+'/../configs/platal.ini')
0337d704 56config.read(os.path.dirname(__file__)+'/../configs/platal.conf')
57
ae6c293b 58def get_config(sec, val, default=None):
0337d704 59 try:
60 return config.get(sec, val)[1:-1]
61 except ConfigParser.NoOptionError, e:
62 if default is None:
849baea6 63 sys.stderr.write('%s\n' % str(e))
0337d704 64 sys.exit(1)
65 else:
66 return default
67
0337d704 68MYSQL_USER = get_config('Core', 'dbuser')
69MYSQL_PASS = get_config('Core', 'dbpwd')
70
71PLATAL_DOMAIN = get_config('Mail', 'domain')
72PLATAL_DOMAIN2 = get_config('Mail', 'domain2', '')
849baea6 73sys.stderr.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN )
0337d704 74
0337d704 75VHOST_SEP = get_config('Lists', 'vhost_sep', '_')
76ON_CREATE_CMD = get_config('Lists', 'on_create', '')
77
1fec3393
FB
78SRV_HOST = get_config('Lists', 'rpchost', 'localhost')
79SRV_PORT = int(get_config('Lists', 'rpcport', '4949'))
80
0337d704 81################################################################################
82#
83# CLASSES
84#
85#------------------------------------------------
86# Manage Basic authentication
87#
88
89class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
90
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().
95
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
98 """
99
c1bae0aa
FB
100 def _get_function(self, method):
101 try:
102 # check to see if a matching function has been registered
103 return self.server.funcs[method]
104 except:
105 raise Exception('method "%s" is not supported' % method)
106
107
ae6c293b 108 def _dispatch(self, method, params):
0337d704 109 new_params = list(params)
c1bae0aa 110 return list_call_dispatcher(self._get_function(method), self.data[0], self.data[1], self.data[2], *params)
0337d704 111
112 def do_POST(self):
113 try:
114 _, auth = self.headers["authorization"].split()
115 uid, md5 = base64.decodestring(auth).strip().split(':')
116 vhost = self.path.split('/')[1].lower()
ae6c293b 117 self.data = self.getUser(uid, md5, vhost)
0337d704 118 if self.data is None:
119 raise AuthFailed
120 # Call super.do_POST() to do the actual work
121 SimpleXMLRPCRequestHandler.do_POST(self)
122 except:
123 self.send_response(401)
124 self.end_headers()
125
126 def getUser(self, uid, md5, vhost):
ae6c293b 127 res = mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms
0337d704 128 FROM auth_user_md5 AS u
129 INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' )
ae6c293b 130 WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user')
0337d704 131 LIMIT 1""" %( uid, md5 ) )
132 if res:
ae6c293b 133 name, forlife, perms = res
0337d704 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)
ae6c293b 141 return (userdesc, perms, vhost)
0337d704 142 else:
143 return None
ae6c293b 144
0337d704 145################################################################################
146#
147# XML RPC STUFF
148#
149#-------------------------------------------------------------------------------
150# helpers
151#
152
153def connectDB():
154 db = MySQLdb.connect(
155 db='x4dat',
156 user=MYSQL_USER,
157 passwd=MYSQL_PASS,
6bd94db9 158 unix_socket='/var/run/mysqld/mysqld.sock')
0337d704 159 db.ping()
160 return db.cursor()
161
162def mysql_fetchone(query):
163 ret = None
164 try:
165 lock.acquire()
166 mysql.execute(query)
167 if int(mysql.rowcount) > 0:
168 ret = mysql.fetchone()
169 finally:
170 lock.release()
171 return ret
172
ae6c293b 173def is_admin_on(userdesc, perms, mlist):
0337d704 174 return ( perms == 'admin' ) or ( userdesc.address in mlist.owner )
175
176
ae6c293b 177def quote(s, is_header=False):
0337d704 178 if is_header:
ae6c293b 179 h = Utils.oneline(s, 'iso-8859-1')
0337d704 180 else:
181 h = s
ea626742 182 h = str('').join(re.split('[\x00-\x08\x0B-\x1f]+', h))
25112e7a 183 return Utils.uquote(h.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'))
0337d704 184
185def to_forlife(email):
186 try:
ae6c293b 187 mbox, fqdn = email.split('@')
0337d704 188 except:
189 mbox = email
190 fqdn = PLATAL_DOMAIN
191 if ( fqdn == PLATAL_DOMAIN ) or ( fqdn == PLATAL_DOMAIN2 ):
ae6c293b 192 res = mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom)
0337d704 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')
ae6c293b 196 WHERE u.perms IN ('admin', 'user')
0337d704 197 LIMIT 1""" %( PLATAL_DOMAIN, mbox ) )
198 if res:
199 return res
200 else:
ae6c293b 201 return (None, None)
cf5e8ef1 202 return (email.lower(), mbox)
0337d704 203
204##
205# see /usr/lib/mailman/bin/rmlist
206##
207def remove_it(listname, filename):
208 if os.path.islink(filename) or os.path.isfile(filename):
209 os.unlink(filename)
210 elif os.path.isdir(filename):
211 shutil.rmtree(filename)
ae6c293b 212
c1bae0aa
FB
213##
214# Call dispatcher
215##
216
217def has_annotation(method, name):
218 """ Check if the method contains the given annoation.
219 """
220 return method.__doc__ and method.__doc__.find("@%s" % name) > -1
221
222def 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
232 """
233 try:
234 if has_annotation(method, "root") and perms != "admin":
235 return 0
236 if has_annotation(method, "mlist"):
237 listname = arg[0]
238 arg = arg[1:]
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):
241 return 0
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)
244 else:
245 return method(userdesc, perms, mlist, *arg)
246 else:
247 return method(userdesc, perms, vhost, *arg)
248 except Exception, e:
249 raise e
250 return 0
251
252def list_call_locked(method, userdesc, perms, mlist, edit, *arg):
253 """Call the given method after locking the mlist.
254 """
255 try:
256 mlist.Lock()
257 ret = method(userdesc, perms, mlist, *arg)
258 if edit:
259 mlist.Save()
260 mlist.Unlock()
261 return ret
262 except:
263 mlist.Unlock()
264 return 0
265 # TODO: use finally when switching to python 2.5
266
0337d704 267#-------------------------------------------------------------------------------
268# helpers on lists
269#
270
849baea6 271def is_subscription_pending(userdesc, perms, mlist):
c1bae0aa
FB
272 for id in mlist.GetSubscriptionIds():
273 if userdesc.address == mlist.GetRecord(id)[1]:
274 return True
275 return False
276
ae6c293b 277def get_list_info(userdesc, perms, mlist, front_page=0):
0337d704 278 members = mlist.getRegularMemberKeys()
279 is_member = userdesc.address in members
ae6c293b 280 is_owner = userdesc.address in mlist.owner
0337d704 281 if mlist.advertised or is_member or is_owner or (not front_page and perms == 'admin'):
282 is_pending = False
283 if not is_member and (mlist.subscribe_policy > 1):
849baea6 284 is_pending = list_call_locked(is_subscription_pending, userdesc, perms, mlist, False)
c1bae0aa 285 if is_pending is 0:
0337d704 286 return 0
287
288 host = mlist.internal_name().split(VHOST_SEP)[0].lower()
289 details = {
290 'list' : mlist.real_name,
291 'addr' : mlist.real_name.lower() + '@' + host,
292 'host' : 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,
ae6c293b 297 'priv' : 1-mlist.advertised,
0337d704 298 'sub' : 2*is_member + is_pending,
299 'own' : is_owner,
300 'nbsub': len(members)
301 }
ae6c293b 302 return (details, members)
0337d704 303 return 0
304
c1bae0aa
FB
305def get_options(userdesc, perms, mlist, opts):
306 """ Get the options of a list.
307 @mlist
308 @admin
309 """
310 options = { }
311 for (k, v) in mlist.__dict__.iteritems():
312 if k in opts:
313 if type(v) is str:
314 options[k] = quote(v)
315 else: options[k] = v
316 details = get_list_info(userdesc, perms, mlist)[0]
317 return (details, options)
318
319def set_options(userdesc, perms, mlist, vals):
320 """ Set the options of a list.
321 @mlist
322 @edit
323 @admin
324 """
325 for (k, v) in vals.iteritems():
326 if k not in opts:
327 continue
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
336 return 1
0337d704 337
338#-------------------------------------------------------------------------------
339# users procedures for [ index.php ]
340#
341
ae6c293b 342def get_lists(userdesc, perms, vhost, email=None):
c1bae0aa
FB
343 """ List available lists for the given vhost
344 """
0337d704 345 if email is None:
346 udesc = userdesc
347 else:
cf5e8ef1 348 udesc = UserDesc(email.lower(), email.lower(), None, 0)
0337d704 349 prefix = vhost.lower()+VHOST_SEP
350 names = Utils.list_names()
351 names.sort()
352 result = []
353 for name in names:
354 if not name.startswith(prefix):
355 continue
356 try:
ae6c293b 357 mlist = MailList.MailList(name, lock=0)
0337d704 358 except:
359 continue
360 try:
ae6c293b 361 details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN))[0]
0337d704 362 result.append(details)
849baea6
FB
363 except Exception, e:
364 sys.stderr.write('Can\'t get list %s: %s\n' % (name, str(e)))
0337d704 365 continue
366 return result
367
c1bae0aa
FB
368def subscribe(userdesc, perms, mlist):
369 """ Subscribe to a list.
370 @mlist
371 @edit
372 """
373 if ( mlist.subscribe_policy in (0, 1) ) or userdesc.address in mlist.owner:
374 mlist.ApprovedAddMember(userdesc)
375 result = 2
376 else:
377 result = 1
378 try:
379 mlist.AddMember(userdesc)
380 except Errors.MMNeedApproval:
381 pass
0337d704 382 return result
383
c1bae0aa
FB
384def unsubscribe(userdesc, perms, mlist):
385 """ Unsubscribe from a list
386 @mlist
387 @edit
388 """
389 mlist.ApprovedDeleteMember(userdesc.address)
390 return 1
0337d704 391
392#-------------------------------------------------------------------------------
393# users procedures for [ index.php ]
394#
395
79a9ca23 396def get_name(member):
397 try:
398 return quote(mlist.getMemberName(member))
399 except:
79a9ca23 400 return ''
401
c1bae0aa
FB
402def get_members(userdesc, perms, mlist):
403 """ List the members of a list.
404 @mlist
405 """
406 details, members = get_list_info(userdesc, perms, mlist)
407 members.sort()
408 members = map(lambda member: (get_name(member), member), members)
409 return (details, members, mlist.owner)
410
0337d704 411
412#-------------------------------------------------------------------------------
413# users procedures for [ trombi.php ]
414#
415
c1bae0aa
FB
416def get_members_limit(userdesc, perms, mlist, page, nb_per_page):
417 """ Get a range of members of the list.
418 @mlist
419 """
420 members = get_members(userdesc, perms, mlist)[1]
0337d704 421 i = int(page) * int(nb_per_page)
422 return (len(members), members[i:i+int(nb_per_page)])
423
c1bae0aa
FB
424def get_owners(userdesc, perms, mlist):
425 """ Get the owners of the list.
426 @mlist
427 """
428 details, members, owners = get_members(userdesc, perms, mlist)
ae6c293b 429 return (details, owners)
0337d704 430
c1bae0aa 431
0337d704 432#-------------------------------------------------------------------------------
433# owners procedures [ admin.php ]
434#
435
c1bae0aa
FB
436def replace_email(userdesc, perms, mlist, from_email, to_email):
437 """ Replace the address of a member by another one.
438 @mlist
439 @edit
440 @admin
441 """
442 mlist.ApprovedChangeMemberAddress(from_email.lower(), to_email.lower(), 0)
443 return 1
ae6c293b 444
c1bae0aa
FB
445def mass_subscribe(userdesc, perms, mlist, users):
446 """ Add a list of users to the list.
447 @mlist
448 @edit
449 @admin
450 """
451 members = mlist.getRegularMemberKeys()
452 added = []
453 mlist.Lock()
454 for user in users:
455 email, name = to_forlife(user)
456 if ( email is None ) or ( email in members ):
457 continue
458 userd = UserDesc(email, name, None, 0)
459 mlist.ApprovedAddMember(userd)
460 added.append( (quote(userd.fullname), userd.address) )
0337d704 461 return added
462
c1bae0aa
FB
463def mass_unsubscribe(userdesc, perms, mlist, users):
464 """ Remove a list of users from the list.
465 @mlist
466 @edit
467 @admin
468 """
469 map(lambda user: mlist.ApprovedDeleteMember(user), users)
0337d704 470 return users
471
c1bae0aa
FB
472def add_owner(userdesc, perms, mlist, user):
473 """ Add a owner to the list.
474 @mlist
475 @edit
476 @admin
477 """
478 email = to_forlife(user)[0]
479 if email is None:
0337d704 480 return 0
c1bae0aa
FB
481 if email not in mlist.owner:
482 mlist.owner.append(email)
0337d704 483 return True
484
c1bae0aa
FB
485def del_owner(userdesc, perms, mlist, user):
486 """ Remove a owner of the list.
487 @mlist
488 @edit
489 @admin
490 """
491 if len(mlist.owner) < 2:
0337d704 492 return 0
c1bae0aa 493 mlist.owner.remove(user)
0337d704 494 return True
495
496#-------------------------------------------------------------------------------
497# owners procedures [ admin.php ]
498#
499
c1bae0aa
FB
500def get_pending_ops(userdesc, perms, mlist):
501 """ Get the list of operation waiting for an action from the owners.
502 @mlist
503 @lock
504 @admin
505 """
506 subs = []
507 seen = []
508 dosave = False
509 for id in mlist.GetSubscriptionIds():
510 time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
511 if addr in seen:
512 mlist.HandleRequest(id, mm_cfg.DISCARD)
513 dosave = True
514 continue
515 seen.append(addr)
516 try:
517 login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group()
518 subs.append({'id': id, 'name': quote(fullname), 'addr': addr, 'login': login })
519 except:
520 subs.append({'id': id, 'name': quote(fullname), 'addr': addr })
0337d704 521
c1bae0aa
FB
522 helds = []
523 for id in mlist.GetHeldMessageIds():
524 ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(id)
0337d704 525 fpath = os.path.join(mm_cfg.DATA_DIR, filename)
c1bae0aa
FB
526 try:
527 size = os.path.getsize(fpath)
528 except OSError, e:
529 if e.errno <> errno.ENOENT: raise
530 continue
531 try:
532 msg = readMessage(fpath)
533 fromX = msg.has_key("X-Org-Mail")
534 except:
535 pass
536 helds.append({
537 'id' : id,
0337d704 538 'sender': quote(sender, True),
539 'size' : size,
540 'subj' : quote(subject, True),
541 'stamp' : ptime,
c1bae0aa
FB
542 'fromx' : fromX
543 })
544 if dosave:
545 mlist.Save()
546 return (subs, helds)
547
548def handle_request(userdesc, perms, mlist, id, value, comment):
549 """ Handle a moderation request.
550 @mlist
551 @edit
552 @admin
553 """
554 mlist.HandleRequest(int(id), int(value), comment)
555 return 1
556
557def get_pending_sub(userdesc, perms, mlist, id):
558 """ Get informations about a given subscription moderation.
559 @mlist
560 @lock
561 @admin
562 """
563 sub = 0
564 id = int(id)
565 if id in mlist.GetSubscriptionIds():
566 time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
567 try:
568 login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group()
569 sub = {'id': id, 'name': quote(fullname), 'addr': addr, 'login': login }
570 except:
571 sub = {'id': id, 'name': quote(fullname), 'addr': addr }
572 return sub
573
574def get_pending_mail(userdesc, perms, mlist, id, raw=0):
575 """ Get informations about a given mail moderation.
576 @mlist
577 @lock
578 @admin
579 """
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)
584
585 if raw:
586 return quote(str(msg))
587 results_plain = []
588 results_html = []
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)
597 return {'id' : id,
598 'sender': quote(sender, True),
599 'size' : size,
600 'subj' : quote(subject, True),
601 'stamp' : ptime,
602 'parts_plain' : results_plain,
603 'parts_html': results_html }
0337d704 604
605#-------------------------------------------------------------------------------
606# owner options [ options.php ]
607#
608
609owner_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', \
612 'welcome_msg']
613
c1bae0aa
FB
614def get_owner_options(userdesc, perms, mlist):
615 """ Get the owner options of a list.
616 @mlist
617 @admin
618 """
619 return get_options(userdesc, perms, mlist, owner_opts)
0337d704 620
c1bae0aa
FB
621def set_owner_options(userdesc, perms, mlist, values):
622 """ Set the owner options of a list.
623 @mlist
624 @edit
625 @admin
626 """
627 return set_options(userdesc, perms, mlist, owner_opts, values)
0337d704 628
c1bae0aa
FB
629def add_to_wl(userdesc, perms, mlist, addr):
630 """ Add addr to the whitelist
631 @mlist
632 @edit
633 @admin
634 """
635 mlist.accept_these_nonmembers.append(addr)
636 return 1
0337d704 637
c1bae0aa
FB
638def del_from_wl(userdesc, perms, mlist, addr):
639 """ Remove an address from the whitelist
640 @mlist
641 @edit
642 @admin
643 """
644 mlist.accept_these_nonmembers.remove(addr)
645 return 1
0337d704 646
c1bae0aa
FB
647def get_bogo_level(userdesc, perms, mlist):
648 """ Compute bogo level from the filtering rules set up on the list.
649 @mlist
650 @admin
651 """
652 if len(mlist.header_filter_rules) == 0:
0337d704 653 return 0
c638d8c8 654
c1bae0aa
FB
655 unsurelevel = 0
656 filterlevel = 0
657 filterbase = 0
c638d8c8 658
c1bae0aa
FB
659 # The first rule filters Unsure mails
660 if mlist.header_filter_rules[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter':
661 unsurelevel = 1
662 filterbase = 1
c638d8c8 663
c1bae0aa
FB
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)
668 try:
669 action = mlist.header_filter_rules[filterbase + 1][1]
670 filterlevel = 2
0337d704 671 except:
c1bae0aa
FB
672 action = mlist.header_filter_rules[filterbase][1]
673 if action == mm_cfg.HOLD:
674 filterlevel = 1
675 elif action == mm_cfg.DISCARD:
676 filterlevel = 3
677 return (filterlevel << 1) + unsurelevel
0337d704 678
ae6c293b 679def set_bogo_level(userdesc, perms, vhost, listname, level):
c1bae0aa
FB
680 """ Set filter to the specified level.
681 @mlist
682 @edit
683 @admin
684 """
685 hfr = []
686
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
694
695 # Set up unusre filtering
696 if unsurelevel == 1:
697 hfr.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
698
699 # Set up spam filtering
700 if filterlevel is 1:
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))
707
708 # save configuration
709 if mlist.header_filter_rules != hfr:
710 mlist.header_filter_rules = hfr
711 return 1
0337d704 712
713#-------------------------------------------------------------------------------
714# admin procedures [ soptions.php ]
715#
716
717admin_opts = [ 'advertised', 'archive', \
718 'max_message_size', 'msg_footer', 'msg_header']
719
c1bae0aa
FB
720def get_admin_options(userdesc, perms, mlist):
721 """ Get administrator options.
722 @mlist
723 @root
724 """
725 return get_options(userdesc, perms, mlist, admin_opts)
0337d704 726
c1bae0aa
FB
727def set_admin_options(userdesc, perms, mlist, values):
728 """ Set administrator options.
729 @mlist
730 @edit
731 @root
732 """
733 return set_options(userdesc, perms, mlist, admin_opts, values)
0337d704 734
735#-------------------------------------------------------------------------------
736# admin procedures [ check.php ]
737#
738
739check_opts = {
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'],
748 'ban_list' : [],
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' : [],
755 'emergency' : False,
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,
778}
779
c1bae0aa
FB
780def check_options_runner(userdesc, perms, mlist, listname, correct):
781 options = { }
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)
791
792
ae6c293b 793def check_options(userdesc, perms, vhost, listname, correct=False):
c1bae0aa
FB
794 """ Check the list.
795 @root
796 """
88f7a3f1 797 listname = listname.lower()
c1bae0aa
FB
798 mlist = MailList.MailList(vhost + VHOST_SEP + listname, lock=0)
799 if correct:
800 return list_call_locked(check_options_runner, userdesc, perms, mlist, True, listname, True)
801 else:
802 return check_options_runner(userdesc, perms, mlist, listname, False)
0337d704 803
804#-------------------------------------------------------------------------------
805# super-admin procedures
806#
807
ae6c293b 808def get_all_lists(userdesc, perms, vhost):
c1bae0aa
FB
809 """ Get all the list for the given vhost
810 """
0337d704 811 prefix = vhost.lower()+VHOST_SEP
812 names = Utils.list_names()
813 names.sort()
814 result = []
815 for name in names:
816 if not name.startswith(prefix):
817 continue
ae6c293b 818 result.append(name.replace(prefix, ''))
0337d704 819 return result
820
ae6c293b 821def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, inslevel, owners, members):
c1bae0aa
FB
822 """ Create a new list.
823 @root
824 """
825 name = vhost.lower() + VHOST_SEP + listname.lower();
0337d704 826 if Utils.list_exists(name):
827 return 0
ae6c293b 828
0337d704 829 owner = []
830 for o in owners:
831 email = to_forlife(o)[0]
832 if email is not None:
833 owner.append(email)
834 if len(owner) is 0:
835 return 0
836
837 mlist = MailList.MailList()
838 try:
839 oldmask = os.umask(002)
840 pw = sha.new('foobar').hexdigest()
ae6c293b 841
0337d704 842 try:
843 mlist.Create(name, owner[0], pw)
844 finally:
845 os.umask(oldmask)
846
847 mlist.real_name = listname
848 mlist.host_name = 'listes.polytechnique.org'
849 mlist.description = desc
850
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)
ae6c293b 856
0337d704 857 mlist.owner = owner
ae6c293b 858
0337d704 859 mlist.subject_prefix = '['+listname+'] '
860 mlist.max_message_size = 0
861
e480db84 862 inverted_listname = listname.lower() + '_' + vhost.lower()
0337d704 863 mlist.msg_footer = "_______________________________________________\n" \
31f2df6a 864 + "Liste de diffusion %(real_name)s\n" \
865 + "http://listes.polytechnique.org/members/" + inverted_listname
ae6c293b 866
0337d704 867 mlist.header_filter_rules = []
c638d8c8 868 mlist.header_filter_rules.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
0337d704 869 mlist.header_filter_rules.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
0337d704 870 mlist.Save()
0337d704 871 mlist.Unlock()
872
98c79ede 873 if ON_CREATE_CMD != '':
874 try: os.system(ON_CREATE_CMD + ' ' + name)
875 except: pass
876
c1bae0aa
FB
877 check_options(userdesc, perms, mlist, True)
878 mass_subscribe(userdesc, perms, mlist, members)
0337d704 879
880 # avoid the "-1 mail to moderate" bug
881 mlist = MailList.MailList(name)
882 mlist._UpdateRecords()
883 mlist.Save()
0337d704 884
0337d704 885 return 1
c1bae0aa
FB
886 finally:
887 mlist.Unlock()
888 return 0
889
890def delete_list(userdesc, perms, mlist, del_archives=0):
891 """ Delete the list.
892 @mlist
893 @admin
894 """
895 lname = mlist.internal_name()
896 # remove the list
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))
903 # remove archives ?
904 if del_archives:
905 REMOVABLES.extend([
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')
910 ])
911 map(lambda dir: remove_it(lname, os.path.join(mm_cfg.VAR_PREFIX, dir)), REMOVABLES)
912 return 1
0337d704 913
ae6c293b 914def kill(userdesc, perms, vhost, alias, del_from_promo):
c1bae0aa
FB
915 """ Remove a user from all the lists.
916 """
0337d704 917 exclude = []
918 if not del_from_promo:
c1bae0aa 919 exclude.append(PLATAL_DOMAIN + VHOST_SEP + 'promo' + alias[-4:])
0337d704 920 for list in Utils.list_names():
c1bae0aa
FB
921 if list in exclude:
922 continue
0337d704 923 try:
ae6c293b 924 mlist = MailList.MailList(list, lock=0)
0337d704 925 except:
926 continue
927 try:
928 mlist.Lock()
ae6c293b 929 mlist.ApprovedDeleteMember(alias+'@'+PLATAL_DOMAIN, None, 0, 0)
0337d704 930 mlist.Save()
931 mlist.Unlock()
932 except:
933 mlist.Unlock()
934 return 1
935
936
937#-------------------------------------------------------------------------------
938# server
939#
940class FastXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
ae6c293b 941 allow_reuse_address = True
0337d704 942
943################################################################################
944#
ae6c293b 945# INIT
0337d704 946#
947#-------------------------------------------------------------------------------
948# use Mailman user and group (not root)
949# fork in background if asked to
950#
951
952uid = getpwnam(mm_cfg.MAILMAN_USER)[2]
953gid = getgrnam(mm_cfg.MAILMAN_GROUP)[2]
954
955if not os.getuid():
ae6c293b 956 os.setregid(gid, gid)
957 os.setreuid(uid, uid)
0337d704 958
959signal.signal(signal.SIGHUP, signal.SIG_IGN)
960
961if ( os.getuid() is not uid ) or ( os.getgid() is not gid):
962 sys.exit(0)
963
964opts, args = getopt.getopt(sys.argv[1:], 'f')
965for o, a in opts:
966 if o == '-f' and os.fork():
967 sys.exit(0)
968
969i18n.set_language('fr')
970mysql = connectDB()
971lock = Lock()
972
973#-------------------------------------------------------------------------------
974# server
975#
1fec3393 976server = FastXMLRPCServer((SRV_HOST, SRV_PORT), BasicAuthXMLRPCRequestHandler)
0337d704 977
978# index.php
979server.register_function(get_lists)
980server.register_function(subscribe)
981server.register_function(unsubscribe)
982# members.php
983server.register_function(get_members)
984# trombi.php
985server.register_function(get_members_limit)
986server.register_function(get_owners)
987# admin.php
c4d57bd8 988server.register_function(replace_email)
0337d704 989server.register_function(mass_subscribe)
990server.register_function(mass_unsubscribe)
991server.register_function(add_owner)
992server.register_function(del_owner)
993# moderate.php
994server.register_function(get_pending_ops)
995server.register_function(handle_request)
4b0d9ef3 996server.register_function(get_pending_sub)
0337d704 997server.register_function(get_pending_mail)
998# options.php
999server.register_function(get_owner_options)
1000server.register_function(set_owner_options)
1001server.register_function(add_to_wl)
1002server.register_function(del_from_wl)
1003server.register_function(get_bogo_level)
1004server.register_function(set_bogo_level)
1005# soptions.php
1006server.register_function(get_admin_options)
1007server.register_function(set_admin_options)
1008# check.php
1009server.register_function(check_options)
1010# create + del
1011server.register_function(get_all_lists)
1012server.register_function(create_list)
1013server.register_function(delete_list)
1014# utilisateurs.php
1015server.register_function(kill)
1016
1017server.serve_forever()
1018
c638d8c8 1019# vim:set et sw=4 sts=4 sws=4: