Fix user account deletion
[platal.git] / bin / lists.rpc.py
CommitLineData
0337d704 1#!/usr/bin/env python
2#***************************************************************************
3#* Copyright (C) 2004 polytechnique.org *
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:
63 print e
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', '')
73
0337d704 74VHOST_SEP = get_config('Lists', 'vhost_sep', '_')
75ON_CREATE_CMD = get_config('Lists', 'on_create', '')
76
77################################################################################
78#
79# CLASSES
80#
81#------------------------------------------------
82# Manage Basic authentication
83#
84
85class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
86
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().
91
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
94 """
95
ae6c293b 96 def _dispatch(self, method, params):
0337d704 97 # TODO: subclass in SimpleXMLRPCDispatcher and not here.
98 new_params = list(params)
ae6c293b 99 new_params.insert(0, self.data[2])
100 new_params.insert(0, self.data[1])
101 new_params.insert(0, self.data[0])
102 return self.server._dispatch(method, new_params)
0337d704 103
104 def do_POST(self):
105 try:
106 _, auth = self.headers["authorization"].split()
107 uid, md5 = base64.decodestring(auth).strip().split(':')
108 vhost = self.path.split('/')[1].lower()
ae6c293b 109 self.data = self.getUser(uid, md5, vhost)
0337d704 110 if self.data is None:
111 raise AuthFailed
112 # Call super.do_POST() to do the actual work
113 SimpleXMLRPCRequestHandler.do_POST(self)
114 except:
115 self.send_response(401)
116 self.end_headers()
117
118 def getUser(self, uid, md5, vhost):
ae6c293b 119 res = mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms
0337d704 120 FROM auth_user_md5 AS u
121 INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' )
ae6c293b 122 WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user')
0337d704 123 LIMIT 1""" %( uid, md5 ) )
124 if res:
ae6c293b 125 name, forlife, perms = res
0337d704 126 if vhost != PLATAL_DOMAIN:
127 res = mysql_fetchone ("""SELECT uid
128 FROM groupex.membres AS m
129 INNER JOIN groupex.asso AS a ON (m.asso_id = a.id)
130 WHERE perms='admin' AND uid='%s' AND mail_domain='%s'""" %( uid , vhost ) )
131 if res: perms= 'admin'
132 userdesc = UserDesc(forlife+'@'+PLATAL_DOMAIN, name, None, 0)
ae6c293b 133 return (userdesc, perms, vhost)
0337d704 134 else:
135 return None
ae6c293b 136
0337d704 137################################################################################
138#
139# XML RPC STUFF
140#
141#-------------------------------------------------------------------------------
142# helpers
143#
144
145def connectDB():
146 db = MySQLdb.connect(
147 db='x4dat',
148 user=MYSQL_USER,
149 passwd=MYSQL_PASS,
6bd94db9 150 unix_socket='/var/run/mysqld/mysqld.sock')
0337d704 151 db.ping()
152 return db.cursor()
153
154def mysql_fetchone(query):
155 ret = None
156 try:
157 lock.acquire()
158 mysql.execute(query)
159 if int(mysql.rowcount) > 0:
160 ret = mysql.fetchone()
161 finally:
162 lock.release()
163 return ret
164
ae6c293b 165def is_admin_on(userdesc, perms, mlist):
0337d704 166 return ( perms == 'admin' ) or ( userdesc.address in mlist.owner )
167
168
ae6c293b 169def quote(s, is_header=False):
0337d704 170 if is_header:
ae6c293b 171 h = Utils.oneline(s, 'iso-8859-1')
0337d704 172 else:
173 h = s
720a960d 174 h = str('').join(re.split('[\x00-\x09\x0B-\x1f]+', h))
c8529706 175 return Utils.uquote(h.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'))
0337d704 176
177def to_forlife(email):
178 try:
ae6c293b 179 mbox, fqdn = email.split('@')
0337d704 180 except:
181 mbox = email
182 fqdn = PLATAL_DOMAIN
183 if ( fqdn == PLATAL_DOMAIN ) or ( fqdn == PLATAL_DOMAIN2 ):
ae6c293b 184 res = mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom)
0337d704 185 FROM auth_user_md5 AS u
186 INNER JOIN aliases AS f ON (f.id=u.user_id AND f.type='a_vie')
187 INNER JOIN aliases AS a ON (a.id=u.user_id AND a.alias='%s' AND a.type!='homonyme')
ae6c293b 188 WHERE u.perms IN ('admin', 'user')
0337d704 189 LIMIT 1""" %( PLATAL_DOMAIN, mbox ) )
190 if res:
191 return res
192 else:
ae6c293b 193 return (None, None)
cf5e8ef1 194 return (email.lower(), mbox)
0337d704 195
196##
197# see /usr/lib/mailman/bin/rmlist
198##
199def remove_it(listname, filename):
200 if os.path.islink(filename) or os.path.isfile(filename):
201 os.unlink(filename)
202 elif os.path.isdir(filename):
203 shutil.rmtree(filename)
ae6c293b 204
0337d704 205#-------------------------------------------------------------------------------
206# helpers on lists
207#
208
ae6c293b 209def get_list_info(userdesc, perms, mlist, front_page=0):
0337d704 210 members = mlist.getRegularMemberKeys()
211 is_member = userdesc.address in members
ae6c293b 212 is_owner = userdesc.address in mlist.owner
0337d704 213 if mlist.advertised or is_member or is_owner or (not front_page and perms == 'admin'):
214 is_pending = False
215 if not is_member and (mlist.subscribe_policy > 1):
216 try:
217 mlist.Lock()
218 for id in mlist.GetSubscriptionIds():
219 if userdesc.address == mlist.GetRecord(id)[1]:
220 is_pending = 1
221 break
222 mlist.Unlock()
223 except:
224 mlist.Unlock()
225 return 0
226
227 host = mlist.internal_name().split(VHOST_SEP)[0].lower()
228 details = {
229 'list' : mlist.real_name,
230 'addr' : mlist.real_name.lower() + '@' + host,
231 'host' : host,
232 'desc' : quote(mlist.description),
233 'info' : quote(mlist.info),
234 'diff' : (mlist.default_member_moderation>0) + (mlist.generic_nonmember_action>0),
235 'ins' : mlist.subscribe_policy > 1,
ae6c293b 236 'priv' : 1-mlist.advertised,
0337d704 237 'sub' : 2*is_member + is_pending,
238 'own' : is_owner,
239 'nbsub': len(members)
240 }
ae6c293b 241 return (details, members)
0337d704 242 return 0
243
ae6c293b 244def get_options(userdesc, perms, vhost, listname, opts):
0337d704 245 try:
ae6c293b 246 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 247 except:
248 return 0
249 try:
250 if not is_admin_on(userdesc, perms, mlist):
251 return 0
252 options = { }
ae6c293b 253 for (k, v) in mlist.__dict__.iteritems():
0337d704 254 if k in opts:
255 if type(v) is str:
256 options[k] = quote(v)
257 else: options[k] = v
ae6c293b 258 details = get_list_info(userdesc, perms, mlist)[0]
259 return (details, options)
0337d704 260 except:
261 return 0
262
ae6c293b 263def set_options(userdesc, perms, vhost, listname, opts, vals):
0337d704 264 try:
ae6c293b 265 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 266 except:
267 return 0
268 try:
269 if not is_admin_on(userdesc, perms, mlist):
270 return 0
271 mlist.Lock()
ae6c293b 272 for (k, v) in vals.iteritems():
0337d704 273 if k not in opts:
274 continue
275 if k == 'default_member_moderation':
276 for member in mlist.getMembers():
277 mlist.setMemberOption(member, mm_cfg.Moderate, int(v))
278 t = type(mlist.__dict__[k])
279 if t is bool: mlist.__dict__[k] = bool(v)
280 elif t is int: mlist.__dict__[k] = int(v)
ae6c293b 281 elif t is str: mlist.__dict__[k] = Utils.uncanonstr(v, 'fr')
0337d704 282 else: mlist.__dict__[k] = v
283 mlist.Save()
284 mlist.Unlock()
285 return 1
286 except:
287 mlist.Unlock()
288 return 0
289
290#-------------------------------------------------------------------------------
291# users procedures for [ index.php ]
292#
293
ae6c293b 294def get_lists(userdesc, perms, vhost, email=None):
0337d704 295 if email is None:
296 udesc = userdesc
297 else:
cf5e8ef1 298 udesc = UserDesc(email.lower(), email.lower(), None, 0)
0337d704 299 prefix = vhost.lower()+VHOST_SEP
300 names = Utils.list_names()
301 names.sort()
302 result = []
303 for name in names:
304 if not name.startswith(prefix):
305 continue
306 try:
ae6c293b 307 mlist = MailList.MailList(name, lock=0)
0337d704 308 except:
309 continue
310 try:
ae6c293b 311 details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN))[0]
0337d704 312 result.append(details)
313 except:
314 continue
315 return result
316
ae6c293b 317def subscribe(userdesc, perms, vhost, listname):
0337d704 318 try:
ae6c293b 319 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 320 except:
321 return 0
322 try:
323 mlist.Lock()
ae6c293b 324 if ( mlist.subscribe_policy in (0, 1) ) or userdesc.address in mlist.owner:
0337d704 325 mlist.ApprovedAddMember(userdesc)
326 result = 2
327 else:
328 result = 1
329 try:
330 mlist.AddMember(userdesc)
331 except Errors.MMNeedApproval:
332 pass
333 mlist.Save()
334 except:
335 result = 0
336 mlist.Unlock()
337 return result
338
ae6c293b 339def unsubscribe(userdesc, perms, vhost, listname):
0337d704 340 try:
ae6c293b 341 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 342 except:
343 return 0
344 try:
345 mlist.Lock()
346 mlist.ApprovedDeleteMember(userdesc.address)
347 mlist.Save()
348 mlist.Unlock()
349 return 1
350 except:
351 mlist.Unlock()
352 return 0
353
354#-------------------------------------------------------------------------------
355# users procedures for [ index.php ]
356#
357
ae6c293b 358def get_members(userdesc, perms, vhost, listname):
0337d704 359 try:
ae6c293b 360 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 361 except:
362 return 0
363 try:
ae6c293b 364 details, members = get_list_info(userdesc, perms, mlist)
0337d704 365 members.sort()
366 members = map(lambda member: (quote(mlist.getMemberName(member)) or '', member), members)
ae6c293b 367 return (details, members, mlist.owner)
0337d704 368 except:
369 return 0
370
371#-------------------------------------------------------------------------------
372# users procedures for [ trombi.php ]
373#
374
ae6c293b 375def get_members_limit(userdesc, perms, vhost, listname, page, nb_per_page):
0337d704 376 try:
ae6c293b 377 members = get_members(userdesc, perms, vhost, listname.lower())[1]
0337d704 378 except:
379 return 0
380 i = int(page) * int(nb_per_page)
381 return (len(members), members[i:i+int(nb_per_page)])
382
ae6c293b 383def get_owners(userdesc, perms, vhost, listname):
0337d704 384 try:
ae6c293b 385 details, members, owners = get_members(userdesc, perms, vhost, listname.lower())
0337d704 386 except:
387 return 0
ae6c293b 388 return (details, owners)
0337d704 389
390#-------------------------------------------------------------------------------
391# owners procedures [ admin.php ]
392#
393
c4d57bd8 394def replace_email(userdesc, perms, vhost, listname, from_email, to_email):
395 try:
396 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
397 except:
398 return 0
399 try:
400 if not is_admin_on(userdesc, perms, mlist):
401 return 0
402
403 mlist.Lock()
cf5e8ef1 404 mlist.ApprovedChangeMemberAddress(from_email.lower(), to_email.lower(), 0)
c4d57bd8 405 mlist.Save()
406 mlist.Unlock()
407 return 1
408 except:
409 return 0
410
ae6c293b 411def mass_subscribe(userdesc, perms, vhost, listname, users):
0337d704 412 try:
ae6c293b 413 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 414 except:
415 return 0
416 try:
417 if not is_admin_on(userdesc, perms, mlist):
418 return 0
ae6c293b 419
0337d704 420 members = mlist.getRegularMemberKeys()
421 added = []
422 mlist.Lock()
423 for user in users:
424 email, name = to_forlife(user)
425 if ( email is None ) or ( email in members ):
426 continue
427 userd = UserDesc(email, name, None, 0)
428 mlist.ApprovedAddMember(userd)
429 added.append( (quote(userd.fullname), userd.address) )
430 mlist.Save()
431 except:
432 pass
433 mlist.Unlock()
434 return added
435
ae6c293b 436def mass_unsubscribe(userdesc, perms, vhost, listname, users):
0337d704 437 try:
ae6c293b 438 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 439 except:
440 return 0
441 try:
442 if not is_admin_on(userdesc, perms, mlist):
443 return 0
ae6c293b 444
0337d704 445 mlist.Lock()
446 map(lambda user: mlist.ApprovedDeleteMember(user), users)
447 mlist.Save()
448 except:
449 pass
450 mlist.Unlock()
451 return users
452
ae6c293b 453def add_owner(userdesc, perms, vhost, listname, user):
0337d704 454 try:
ae6c293b 455 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 456 except:
457 return 0
458 try:
459 if not is_admin_on(userdesc, perms, mlist):
460 return 0
461 email = to_forlife(user)[0]
462 if email is None:
463 return 0
464 if email not in mlist.owner:
465 mlist.Lock()
466 mlist.owner.append(email)
467 mlist.Save()
468 except:
469 pass
470 mlist.Unlock()
471 return True
472
ae6c293b 473def del_owner(userdesc, perms, vhost, listname, user):
0337d704 474 try:
ae6c293b 475 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 476 except:
477 return 0
478 try:
479 if not is_admin_on(userdesc, perms, mlist):
480 return 0
481 if len(mlist.owner) < 2:
482 return 0
483 mlist.Lock()
484 mlist.owner.remove(user)
485 mlist.Save()
486 except:
487 pass
488 mlist.Unlock()
489 return True
490
491#-------------------------------------------------------------------------------
492# owners procedures [ admin.php ]
493#
494
ae6c293b 495def get_pending_ops(userdesc, perms, vhost, listname):
0337d704 496 try:
ae6c293b 497 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 498 except:
499 return 0
500 try:
501 if not is_admin_on(userdesc, perms, mlist):
502 return 0
ae6c293b 503
0337d704 504 mlist.Lock()
ae6c293b 505
0337d704 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 })
521
522 helds = []
523 for id in mlist.GetHeldMessageIds():
524 ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(id)
525 try:
526 size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename))
527 except OSError, e:
528 if e.errno <> errno.ENOENT: raise
529 continue
530 helds.append({
531 'id' : id,
532 'sender': quote(sender, True),
533 'size' : size,
534 'subj' : quote(subject, True),
535 'stamp' : ptime
536 })
537 if dosave: mlist.Save()
538 mlist.Unlock()
539 except:
540 mlist.Unlock()
541 return 0
ae6c293b 542 return (subs, helds)
0337d704 543
544
ae6c293b 545def handle_request(userdesc, perms, vhost, listname, id, value, comment):
0337d704 546 try:
ae6c293b 547 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 548 except:
549 return 0
550 try:
551 if not is_admin_on(userdesc, perms, mlist):
552 return 0
553 mlist.Lock()
ae6c293b 554 mlist.HandleRequest(int(id), int(value), comment)
0337d704 555 mlist.Save()
556 mlist.Unlock()
557 return 1
558 except:
559 mlist.Unlock()
560 return 0
561
562
ae6c293b 563def get_pending_mail(userdesc, perms, vhost, listname, id, raw=0):
0337d704 564 try:
ae6c293b 565 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 566 except:
567 return 0
568 try:
569 if not is_admin_on(userdesc, perms, mlist):
570 return 0
571 mlist.Lock()
572 ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(int(id))
573 fpath = os.path.join(mm_cfg.DATA_DIR, filename)
574 size = os.path.getsize(fpath)
575 msg = readMessage(fpath)
576 mlist.Unlock()
ae6c293b 577
0337d704 578 if raw:
579 return str(msg)
ae0508cb 580 results_plain = []
581 results_html = []
ae6c293b 582 for part in typed_subpart_iterator(msg, 'text', 'plain'):
0337d704 583 c = part.get_payload()
ae0508cb 584 if c is not None: results_plain.append (c)
585 results_plain = map(lambda x: quote(x), results_plain)
586 for part in typed_subpart_iterator(msg, 'text', 'html'):
587 c = part.get_payload()
588 if c is not None: results_html.append (c)
589 results_html = map(lambda x: quote(x), results_html)
0337d704 590 return {'id' : id,
591 'sender': quote(sender, True),
592 'size' : size,
593 'subj' : quote(subject, True),
594 'stamp' : ptime,
ae0508cb 595 'parts_plain' : results_plain,
596 'parts_html': results_html }
0337d704 597 except:
598 mlist.Unlock()
599 return 0
600
601#-------------------------------------------------------------------------------
602# owner options [ options.php ]
603#
604
605owner_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', \
608 'welcome_msg']
609
ae6c293b 610def get_owner_options(userdesc, perms, vhost, listname):
611 return get_options(userdesc, perms, vhost, listname.lower(), owner_opts)
0337d704 612
ae6c293b 613def set_owner_options(userdesc, perms, vhost, listname, values):
614 return set_options(userdesc, perms, vhost, listname.lower(), owner_opts, values)
0337d704 615
ae6c293b 616def add_to_wl(userdesc, perms, vhost, listname, addr):
0337d704 617 try:
ae6c293b 618 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 619 except:
620 return 0
621 try:
622 if not is_admin_on(userdesc, perms, mlist):
623 return 0
624 mlist.Lock()
625 mlist.accept_these_nonmembers.append(addr)
626 mlist.Save()
627 mlist.Unlock()
628 return 1
629 except:
630 mlist.Unlock()
631 return 0
632
ae6c293b 633def del_from_wl(userdesc, perms, vhost, listname, addr):
0337d704 634 try:
ae6c293b 635 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 636 except:
637 return 0
638 try:
639 if not is_admin_on(userdesc, perms, mlist):
640 return 0
641 mlist.Lock()
642 mlist.accept_these_nonmembers.remove(addr)
643 mlist.Save()
644 mlist.Unlock()
645 return 1
646 except:
647 mlist.Unlock()
648 return 0
649
ae6c293b 650def get_bogo_level(userdesc, perms, vhost, listname):
0337d704 651 try:
ae6c293b 652 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 653 except:
654 return 0
655 try:
656 if not is_admin_on(userdesc, perms, mlist):
657 return 0
658 if mlist.header_filter_rules == []:
659 return 0
4988c5a9 660 try:
661 action = mlist.header_filter_rules[1][1]
0337d704 662 return 2
4988c5a9 663 except:
664 action = mlist.header_filter_rules[0][1]
665 if action == mm_cfg.HOLD:
666 return 1
667 if action == mm_cfg.DISCARD:
668 return 3
0337d704 669 except:
670 return 0
671
ae6c293b 672def set_bogo_level(userdesc, perms, vhost, listname, level):
0337d704 673 try:
ae6c293b 674 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 675 except:
676 return 0
677 try:
678 if not is_admin_on(userdesc, perms, mlist):
679 return 0
680 hfr = []
681 if int(level) is 1:
682 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
683 elif int(level) is 2:
4988c5a9 684 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg.DISCARD, False))
685 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
686 elif int(level) is 3:
0337d704 687 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.DISCARD, False))
688 if mlist.header_filter_rules != hfr:
689 mlist.Lock()
690 mlist.header_filter_rules = hfr
691 mlist.Save()
692 mlist.Unlock()
693 return 1
694 except:
695 mlist.Unlock()
696 return 0
697
698#-------------------------------------------------------------------------------
699# admin procedures [ soptions.php ]
700#
701
702admin_opts = [ 'advertised', 'archive', \
703 'max_message_size', 'msg_footer', 'msg_header']
704
ae6c293b 705def get_admin_options(userdesc, perms, vhost, listname):
0337d704 706 if perms != 'admin':
707 return 0
ae6c293b 708 return get_options(userdesc, perms, vhost, listname.lower(), admin_opts)
0337d704 709
ae6c293b 710def set_admin_options(userdesc, perms, vhost, listname, values):
0337d704 711 if perms != 'admin':
712 return 0
ae6c293b 713 return set_options(userdesc, perms, vhost, listname.lower(), admin_opts, values)
0337d704 714
715#-------------------------------------------------------------------------------
716# admin procedures [ check.php ]
717#
718
719check_opts = {
720 'acceptable_aliases' : '',
721 'admin_immed_notify' : True,
722 'administrivia' : True,
723 'anonymous_list' : False,
724 'autorespond_admin' : False,
725 'autorespond_postings' : False,
726 'autorespond_requests' : False,
727 'available_languages' : ['fr'],
728 'ban_list' : [],
729 'bounce_matching_headers' : '',
730 'bounce_processing' : False,
731 'convert_html_to_plaintext' : False,
732 'digestable' : False,
733 'digest_is_default' : False,
734 'discard_these_nonmembers' : [],
735 'emergency' : False,
736 'encode_ascii_prefixes' : 2,
737 'filter_content' : False,
738 'first_strip_reply_to' : False,
739 'forward_auto_discards' : True,
740 'hold_these_nonmembers' : [],
741 'host_name' : 'listes.polytechnique.org',
742 'include_list_post_header' : False,
743 'include_rfc2369_headers' : False,
744 'max_num_recipients' : 0,
745 'new_member_options' : 256,
746 'nondigestable' : True,
747 'obscure_addresses' : True,
748 'preferred_language' : 'fr',
749 'reject_these_nonmembers' : [],
750 'reply_goes_to_list' : 0,
751 'reply_to_address' : '',
752 'require_explicit_destination' : False,
753 'send_reminders' : 0,
754 'send_welcome_msg' : True,
755 'topics_enabled' : False,
756 'umbrella_list' : False,
757 'unsubscribe_policy' : 0,
758}
759
ae6c293b 760def check_options(userdesc, perms, vhost, listname, correct=False):
88f7a3f1 761 listname = listname.lower()
0337d704 762 try:
88f7a3f1 763 mlist = MailList.MailList(vhost+VHOST_SEP+listname, lock=0)
0337d704 764 except:
765 return 0
766 try:
767 if perms != 'admin': return 0
768 if correct:
769 mlist.Lock()
770 options = { }
ae6c293b 771 for (k, v) in check_opts.iteritems():
0337d704 772 if mlist.__dict__[k] != v:
ae6c293b 773 options[k] = v, mlist.__dict__[k]
0337d704 774 if correct: mlist.__dict__[k] = v
775 if mlist.real_name.lower() != listname:
776 options['real_name'] = listname, mlist.real_name
777 if correct: mlist.real_name = listname
778 if correct:
779 mlist.Save()
780 mlist.Unlock()
ae6c293b 781 details = get_list_info(userdesc, perms, mlist)[0]
782 return (details, options)
0337d704 783 except:
784 if correct: mlist.Unlock()
785 return 0
786
787#-------------------------------------------------------------------------------
788# super-admin procedures
789#
790
ae6c293b 791def get_all_lists(userdesc, perms, vhost):
0337d704 792 prefix = vhost.lower()+VHOST_SEP
793 names = Utils.list_names()
794 names.sort()
795 result = []
796 for name in names:
797 if not name.startswith(prefix):
798 continue
ae6c293b 799 result.append(name.replace(prefix, ''))
0337d704 800 return result
801
ae6c293b 802def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, inslevel, owners, members):
0337d704 803 if perms != 'admin':
804 return 0
805 name = vhost.lower()+VHOST_SEP+listname.lower();
806 if Utils.list_exists(name):
807 return 0
ae6c293b 808
0337d704 809 owner = []
810 for o in owners:
811 email = to_forlife(o)[0]
812 if email is not None:
813 owner.append(email)
814 if len(owner) is 0:
815 return 0
816
817 mlist = MailList.MailList()
818 try:
819 oldmask = os.umask(002)
820 pw = sha.new('foobar').hexdigest()
ae6c293b 821
0337d704 822 try:
823 mlist.Create(name, owner[0], pw)
824 finally:
825 os.umask(oldmask)
826
827 mlist.real_name = listname
828 mlist.host_name = 'listes.polytechnique.org'
829 mlist.description = desc
830
831 mlist.advertised = int(advertise) is 0
832 mlist.default_member_moderation = int(modlevel) is 2
833 mlist.generic_nonmember_action = int(modlevel) > 0
834 mlist.subscribe_policy = 2 * (int(inslevel) is 1)
835 mlist.admin_notify_mchanges = (mlist.subscribe_policy or mlist.generic_nonmember_action or mlist.default_member_moderation or not mlist.advertised)
ae6c293b 836
0337d704 837 mlist.owner = owner
ae6c293b 838
0337d704 839 mlist.subject_prefix = '['+listname+'] '
840 mlist.max_message_size = 0
841
31f2df6a 842 inverted_listname = '_'.join(listname.split('_', 1)[-1::-1])
0337d704 843 mlist.msg_footer = "_______________________________________________\n" \
31f2df6a 844 + "Liste de diffusion %(real_name)s\n" \
845 + "http://listes.polytechnique.org/members/" + inverted_listname
ae6c293b 846
0337d704 847 mlist.header_filter_rules = []
848 mlist.header_filter_rules.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
849
850 mlist.Save()
851
852 mlist.Unlock()
853
98c79ede 854 if ON_CREATE_CMD != '':
855 try: os.system(ON_CREATE_CMD + ' ' + name)
856 except: pass
857
ae6c293b 858 check_options(userdesc, perms, vhost, listname.lower(), True)
859 mass_subscribe(userdesc, perms, vhost, listname.lower(), members)
0337d704 860
861 # avoid the "-1 mail to moderate" bug
862 mlist = MailList.MailList(name)
863 mlist._UpdateRecords()
864 mlist.Save()
865 mlist.Unlock()
0337d704 866 except:
867 try:
868 mlist.Unlock()
869 except:
870 pass
871 return 0
872 return 1
873
ae6c293b 874def delete_list(userdesc, perms, vhost, listname, del_archives=0):
0337d704 875 lname = vhost+VHOST_SEP+listname.lower()
876 try:
ae6c293b 877 mlist = MailList.MailList(lname, lock=0)
0337d704 878 except:
879 return 0
880 try:
881 if not is_admin_on(userdesc, perms, mlist):
882 return 0
883 # remove the list
884 REMOVABLES = [ os.path.join('lists', lname), ]
885 # remove stalled locks
886 for filename in os.listdir(mm_cfg.LOCK_DIR):
887 fn_lname = filename.split('.')[0]
888 if fn_lname == lname:
889 REMOVABLES.append(os.path.join(mm_cfg.LOCK_DIR, filename))
890 # remove archives ?
891 if del_archives:
892 REMOVABLES.extend([
893 os.path.join('archives', 'private', lname),
894 os.path.join('archives', 'private', lname+'.mbox'),
895 os.path.join('archives', 'public', lname),
896 os.path.join('archives', 'public', lname+'.mbox')
897 ])
898 map(lambda dir: remove_it(lname, os.path.join(mm_cfg.VAR_PREFIX, dir)), REMOVABLES)
899 return 1
900 except:
901 return 0
902
ae6c293b 903def kill(userdesc, perms, vhost, alias, del_from_promo):
0337d704 904 exclude = []
905 if not del_from_promo:
906 exclude.append(PLATAL_DOMAIN+VHOST_SEP+'promo'+alias[-4:])
907 for list in Utils.list_names():
908 if list in exclude: continue
909 try:
ae6c293b 910 mlist = MailList.MailList(list, lock=0)
0337d704 911 except:
912 continue
913 try:
914 mlist.Lock()
ae6c293b 915 mlist.ApprovedDeleteMember(alias+'@'+PLATAL_DOMAIN, None, 0, 0)
0337d704 916 mlist.Save()
917 mlist.Unlock()
918 except:
919 mlist.Unlock()
920 return 1
921
922
923#-------------------------------------------------------------------------------
924# server
925#
926class FastXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
ae6c293b 927 allow_reuse_address = True
0337d704 928
929################################################################################
930#
ae6c293b 931# INIT
0337d704 932#
933#-------------------------------------------------------------------------------
934# use Mailman user and group (not root)
935# fork in background if asked to
936#
937
938uid = getpwnam(mm_cfg.MAILMAN_USER)[2]
939gid = getgrnam(mm_cfg.MAILMAN_GROUP)[2]
940
941if not os.getuid():
ae6c293b 942 os.setregid(gid, gid)
943 os.setreuid(uid, uid)
0337d704 944
945signal.signal(signal.SIGHUP, signal.SIG_IGN)
946
947if ( os.getuid() is not uid ) or ( os.getgid() is not gid):
948 sys.exit(0)
949
950opts, args = getopt.getopt(sys.argv[1:], 'f')
951for o, a in opts:
952 if o == '-f' and os.fork():
953 sys.exit(0)
954
955i18n.set_language('fr')
956mysql = connectDB()
957lock = Lock()
958
959#-------------------------------------------------------------------------------
960# server
961#
962server = FastXMLRPCServer(("localhost", 4949), BasicAuthXMLRPCRequestHandler)
963
964# index.php
965server.register_function(get_lists)
966server.register_function(subscribe)
967server.register_function(unsubscribe)
968# members.php
969server.register_function(get_members)
970# trombi.php
971server.register_function(get_members_limit)
972server.register_function(get_owners)
973# admin.php
c4d57bd8 974server.register_function(replace_email)
0337d704 975server.register_function(mass_subscribe)
976server.register_function(mass_unsubscribe)
977server.register_function(add_owner)
978server.register_function(del_owner)
979# moderate.php
980server.register_function(get_pending_ops)
981server.register_function(handle_request)
982server.register_function(get_pending_mail)
983# options.php
984server.register_function(get_owner_options)
985server.register_function(set_owner_options)
986server.register_function(add_to_wl)
987server.register_function(del_from_wl)
988server.register_function(get_bogo_level)
989server.register_function(set_bogo_level)
990# soptions.php
991server.register_function(get_admin_options)
992server.register_function(set_admin_options)
993# check.php
994server.register_function(check_options)
995# create + del
996server.register_function(get_all_lists)
997server.register_function(create_list)
998server.register_function(delete_list)
999# utilisateurs.php
1000server.register_function(kill)
1001
1002server.serve_forever()
1003
1004# vim:set et: