Add S::rssActivated() to check whether the rss_hash is set AND not empty.
[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
ea626742 174 h = str('').join(re.split('[\x00-\x08\x0B-\x1f]+', h))
25112e7a 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
79a9ca23 358def get_name(member):
359 try:
360 return quote(mlist.getMemberName(member))
361 except:
79a9ca23 362 return ''
363
ae6c293b 364def get_members(userdesc, perms, vhost, listname):
0337d704 365 try:
ae6c293b 366 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 367 except:
368 return 0
369 try:
ae6c293b 370 details, members = get_list_info(userdesc, perms, mlist)
0337d704 371 members.sort()
79a9ca23 372 members = map(lambda member: (get_name(member), member), members)
ae6c293b 373 return (details, members, mlist.owner)
0337d704 374 except:
375 return 0
376
377#-------------------------------------------------------------------------------
378# users procedures for [ trombi.php ]
379#
380
ae6c293b 381def get_members_limit(userdesc, perms, vhost, listname, page, nb_per_page):
0337d704 382 try:
ae6c293b 383 members = get_members(userdesc, perms, vhost, listname.lower())[1]
0337d704 384 except:
385 return 0
386 i = int(page) * int(nb_per_page)
387 return (len(members), members[i:i+int(nb_per_page)])
388
ae6c293b 389def get_owners(userdesc, perms, vhost, listname):
0337d704 390 try:
ae6c293b 391 details, members, owners = get_members(userdesc, perms, vhost, listname.lower())
0337d704 392 except:
393 return 0
ae6c293b 394 return (details, owners)
0337d704 395
396#-------------------------------------------------------------------------------
397# owners procedures [ admin.php ]
398#
399
c4d57bd8 400def replace_email(userdesc, perms, vhost, listname, from_email, to_email):
401 try:
402 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
403 except:
404 return 0
405 try:
406 if not is_admin_on(userdesc, perms, mlist):
407 return 0
408
409 mlist.Lock()
cf5e8ef1 410 mlist.ApprovedChangeMemberAddress(from_email.lower(), to_email.lower(), 0)
c4d57bd8 411 mlist.Save()
412 mlist.Unlock()
413 return 1
414 except:
415 return 0
416
ae6c293b 417def mass_subscribe(userdesc, perms, vhost, listname, users):
0337d704 418 try:
ae6c293b 419 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 420 except:
421 return 0
422 try:
423 if not is_admin_on(userdesc, perms, mlist):
424 return 0
ae6c293b 425
0337d704 426 members = mlist.getRegularMemberKeys()
427 added = []
428 mlist.Lock()
429 for user in users:
430 email, name = to_forlife(user)
431 if ( email is None ) or ( email in members ):
432 continue
433 userd = UserDesc(email, name, None, 0)
434 mlist.ApprovedAddMember(userd)
435 added.append( (quote(userd.fullname), userd.address) )
436 mlist.Save()
437 except:
438 pass
439 mlist.Unlock()
440 return added
441
ae6c293b 442def mass_unsubscribe(userdesc, perms, vhost, listname, users):
0337d704 443 try:
ae6c293b 444 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 445 except:
446 return 0
447 try:
448 if not is_admin_on(userdesc, perms, mlist):
449 return 0
ae6c293b 450
0337d704 451 mlist.Lock()
452 map(lambda user: mlist.ApprovedDeleteMember(user), users)
453 mlist.Save()
454 except:
455 pass
456 mlist.Unlock()
457 return users
458
ae6c293b 459def add_owner(userdesc, perms, vhost, listname, user):
0337d704 460 try:
ae6c293b 461 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 462 except:
463 return 0
464 try:
465 if not is_admin_on(userdesc, perms, mlist):
466 return 0
467 email = to_forlife(user)[0]
468 if email is None:
469 return 0
470 if email not in mlist.owner:
471 mlist.Lock()
472 mlist.owner.append(email)
473 mlist.Save()
474 except:
475 pass
476 mlist.Unlock()
477 return True
478
ae6c293b 479def del_owner(userdesc, perms, vhost, listname, user):
0337d704 480 try:
ae6c293b 481 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 482 except:
483 return 0
484 try:
485 if not is_admin_on(userdesc, perms, mlist):
486 return 0
487 if len(mlist.owner) < 2:
488 return 0
489 mlist.Lock()
490 mlist.owner.remove(user)
491 mlist.Save()
492 except:
493 pass
494 mlist.Unlock()
495 return True
496
497#-------------------------------------------------------------------------------
498# owners procedures [ admin.php ]
499#
500
ae6c293b 501def get_pending_ops(userdesc, perms, vhost, listname):
0337d704 502 try:
ae6c293b 503 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 504 except:
505 return 0
506 try:
507 if not is_admin_on(userdesc, perms, mlist):
508 return 0
ae6c293b 509
0337d704 510 mlist.Lock()
ae6c293b 511
0337d704 512 subs = []
513 seen = []
514 dosave = False
515 for id in mlist.GetSubscriptionIds():
516 time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
517 if addr in seen:
518 mlist.HandleRequest(id, mm_cfg.DISCARD)
519 dosave = True
520 continue
521 seen.append(addr)
522 try:
523 login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group()
524 subs.append({'id': id, 'name': quote(fullname), 'addr': addr, 'login': login })
525 except:
526 subs.append({'id': id, 'name': quote(fullname), 'addr': addr })
527
528 helds = []
529 for id in mlist.GetHeldMessageIds():
530 ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(id)
717b9fa7 531 fpath = os.path.join(mm_cfg.DATA_DIR, filename)
0337d704 532 try:
717b9fa7 533 size = os.path.getsize(fpath)
0337d704 534 except OSError, e:
535 if e.errno <> errno.ENOENT: raise
536 continue
717b9fa7
FB
537 try:
538 msg = readMessage(fpath)
539 fromX = msg.has_key("X-Org-Mail")
540 except:
541 pass
0337d704 542 helds.append({
543 'id' : id,
544 'sender': quote(sender, True),
545 'size' : size,
546 'subj' : quote(subject, True),
717b9fa7
FB
547 'stamp' : ptime,
548 'fromx' : fromX
0337d704 549 })
550 if dosave: mlist.Save()
551 mlist.Unlock()
552 except:
553 mlist.Unlock()
554 return 0
ae6c293b 555 return (subs, helds)
0337d704 556
ae6c293b 557def handle_request(userdesc, perms, vhost, listname, id, value, comment):
0337d704 558 try:
ae6c293b 559 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 560 except:
561 return 0
562 try:
563 if not is_admin_on(userdesc, perms, mlist):
564 return 0
565 mlist.Lock()
ae6c293b 566 mlist.HandleRequest(int(id), int(value), comment)
0337d704 567 mlist.Save()
568 mlist.Unlock()
569 return 1
570 except:
571 mlist.Unlock()
572 return 0
573
4b0d9ef3 574def get_pending_sub(userdesc, perms, vhost, listname, id):
575 try:
576 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
577 except:
578 return 0
579 try:
580 if not is_admin_on(userdesc, perms, mlist):
581 return 0
582
583 mlist.Lock()
584 sub = 0
585 id = int(id)
586 if id in mlist.GetSubscriptionIds():
587 time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id)
588 try:
589 login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group()
590 sub = {'id': id, 'name': quote(fullname), 'addr': addr, 'login': login }
591 except:
592 sub = {'id': id, 'name': quote(fullname), 'addr': addr }
593 mlist.Unlock()
594 except:
595 mlist.Unlock()
596 return 0
597 return sub
0337d704 598
ae6c293b 599def get_pending_mail(userdesc, perms, vhost, listname, id, raw=0):
0337d704 600 try:
ae6c293b 601 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 602 except:
603 return 0
604 try:
605 if not is_admin_on(userdesc, perms, mlist):
606 return 0
607 mlist.Lock()
608 ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(int(id))
609 fpath = os.path.join(mm_cfg.DATA_DIR, filename)
610 size = os.path.getsize(fpath)
611 msg = readMessage(fpath)
612 mlist.Unlock()
ae6c293b 613
0337d704 614 if raw:
ea626742 615 return quote(str(msg))
ae0508cb 616 results_plain = []
617 results_html = []
ae6c293b 618 for part in typed_subpart_iterator(msg, 'text', 'plain'):
0337d704 619 c = part.get_payload()
ae0508cb 620 if c is not None: results_plain.append (c)
621 results_plain = map(lambda x: quote(x), results_plain)
622 for part in typed_subpart_iterator(msg, 'text', 'html'):
623 c = part.get_payload()
624 if c is not None: results_html.append (c)
625 results_html = map(lambda x: quote(x), results_html)
0337d704 626 return {'id' : id,
627 'sender': quote(sender, True),
628 'size' : size,
629 'subj' : quote(subject, True),
630 'stamp' : ptime,
ae0508cb 631 'parts_plain' : results_plain,
632 'parts_html': results_html }
0337d704 633 except:
634 mlist.Unlock()
635 return 0
636
637#-------------------------------------------------------------------------------
638# owner options [ options.php ]
639#
640
641owner_opts = ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', \
642 'default_member_moderation', 'generic_nonmember_action', 'info', \
643 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \
644 'welcome_msg']
645
ae6c293b 646def get_owner_options(userdesc, perms, vhost, listname):
647 return get_options(userdesc, perms, vhost, listname.lower(), owner_opts)
0337d704 648
ae6c293b 649def set_owner_options(userdesc, perms, vhost, listname, values):
650 return set_options(userdesc, perms, vhost, listname.lower(), owner_opts, values)
0337d704 651
ae6c293b 652def add_to_wl(userdesc, perms, vhost, listname, addr):
0337d704 653 try:
ae6c293b 654 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 655 except:
656 return 0
657 try:
658 if not is_admin_on(userdesc, perms, mlist):
659 return 0
660 mlist.Lock()
661 mlist.accept_these_nonmembers.append(addr)
662 mlist.Save()
663 mlist.Unlock()
664 return 1
665 except:
666 mlist.Unlock()
667 return 0
668
ae6c293b 669def del_from_wl(userdesc, perms, vhost, listname, addr):
0337d704 670 try:
ae6c293b 671 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 672 except:
673 return 0
674 try:
675 if not is_admin_on(userdesc, perms, mlist):
676 return 0
677 mlist.Lock()
678 mlist.accept_these_nonmembers.remove(addr)
679 mlist.Save()
680 mlist.Unlock()
681 return 1
682 except:
683 mlist.Unlock()
684 return 0
685
ae6c293b 686def get_bogo_level(userdesc, perms, vhost, listname):
0337d704 687 try:
ae6c293b 688 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 689 except:
690 return 0
691 try:
692 if not is_admin_on(userdesc, perms, mlist):
693 return 0
694 if mlist.header_filter_rules == []:
695 return 0
4988c5a9 696 try:
cc44d7f5 697 action = mlist.header_filter_rules[2][1]
0337d704 698 return 2
4988c5a9 699 except:
cc44d7f5 700 action = mlist.header_filter_rules[1][1]
4988c5a9 701 if action == mm_cfg.HOLD:
702 return 1
703 if action == mm_cfg.DISCARD:
704 return 3
0337d704 705 except:
706 return 0
707
ae6c293b 708def set_bogo_level(userdesc, perms, vhost, listname, level):
0337d704 709 try:
ae6c293b 710 mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0)
0337d704 711 except:
712 return 0
713 try:
714 if not is_admin_on(userdesc, perms, mlist):
715 return 0
716 hfr = []
717 if int(level) is 1:
bb2dae30 718 hfr.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
0337d704 719 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
720 elif int(level) is 2:
bb2dae30 721 hfr.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
4988c5a9 722 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg.DISCARD, False))
723 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
724 elif int(level) is 3:
bb2dae30 725 hfr.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False))
0337d704 726 hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.DISCARD, False))
727 if mlist.header_filter_rules != hfr:
728 mlist.Lock()
729 mlist.header_filter_rules = hfr
730 mlist.Save()
731 mlist.Unlock()
732 return 1
733 except:
734 mlist.Unlock()
735 return 0
736
737#-------------------------------------------------------------------------------
738# admin procedures [ soptions.php ]
739#
740
741admin_opts = [ 'advertised', 'archive', \
742 'max_message_size', 'msg_footer', 'msg_header']
743
ae6c293b 744def get_admin_options(userdesc, perms, vhost, listname):
0337d704 745 if perms != 'admin':
746 return 0
ae6c293b 747 return get_options(userdesc, perms, vhost, listname.lower(), admin_opts)
0337d704 748
ae6c293b 749def set_admin_options(userdesc, perms, vhost, listname, values):
0337d704 750 if perms != 'admin':
751 return 0
ae6c293b 752 return set_options(userdesc, perms, vhost, listname.lower(), admin_opts, values)
0337d704 753
754#-------------------------------------------------------------------------------
755# admin procedures [ check.php ]
756#
757
758check_opts = {
759 'acceptable_aliases' : '',
760 'admin_immed_notify' : True,
761 'administrivia' : True,
762 'anonymous_list' : False,
763 'autorespond_admin' : False,
764 'autorespond_postings' : False,
765 'autorespond_requests' : False,
766 'available_languages' : ['fr'],
767 'ban_list' : [],
768 'bounce_matching_headers' : '',
769 'bounce_processing' : False,
770 'convert_html_to_plaintext' : False,
771 'digestable' : False,
772 'digest_is_default' : False,
773 'discard_these_nonmembers' : [],
774 'emergency' : False,
775 'encode_ascii_prefixes' : 2,
776 'filter_content' : False,
777 'first_strip_reply_to' : False,
778 'forward_auto_discards' : True,
779 'hold_these_nonmembers' : [],
780 'host_name' : 'listes.polytechnique.org',
781 'include_list_post_header' : False,
782 'include_rfc2369_headers' : False,
783 'max_num_recipients' : 0,
784 'new_member_options' : 256,
785 'nondigestable' : True,
786 'obscure_addresses' : True,
787 'preferred_language' : 'fr',
788 'reject_these_nonmembers' : [],
789 'reply_goes_to_list' : 0,
790 'reply_to_address' : '',
791 'require_explicit_destination' : False,
792 'send_reminders' : 0,
793 'send_welcome_msg' : True,
794 'topics_enabled' : False,
795 'umbrella_list' : False,
796 'unsubscribe_policy' : 0,
797}
798
ae6c293b 799def check_options(userdesc, perms, vhost, listname, correct=False):
88f7a3f1 800 listname = listname.lower()
0337d704 801 try:
88f7a3f1 802 mlist = MailList.MailList(vhost+VHOST_SEP+listname, lock=0)
0337d704 803 except:
804 return 0
805 try:
806 if perms != 'admin': return 0
807 if correct:
808 mlist.Lock()
809 options = { }
ae6c293b 810 for (k, v) in check_opts.iteritems():
0337d704 811 if mlist.__dict__[k] != v:
ae6c293b 812 options[k] = v, mlist.__dict__[k]
0337d704 813 if correct: mlist.__dict__[k] = v
814 if mlist.real_name.lower() != listname:
815 options['real_name'] = listname, mlist.real_name
816 if correct: mlist.real_name = listname
817 if correct:
818 mlist.Save()
819 mlist.Unlock()
ae6c293b 820 details = get_list_info(userdesc, perms, mlist)[0]
821 return (details, options)
0337d704 822 except:
823 if correct: mlist.Unlock()
824 return 0
825
826#-------------------------------------------------------------------------------
827# super-admin procedures
828#
829
ae6c293b 830def get_all_lists(userdesc, perms, vhost):
0337d704 831 prefix = vhost.lower()+VHOST_SEP
832 names = Utils.list_names()
833 names.sort()
834 result = []
835 for name in names:
836 if not name.startswith(prefix):
837 continue
ae6c293b 838 result.append(name.replace(prefix, ''))
0337d704 839 return result
840
ae6c293b 841def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, inslevel, owners, members):
0337d704 842 if perms != 'admin':
843 return 0
844 name = vhost.lower()+VHOST_SEP+listname.lower();
845 if Utils.list_exists(name):
846 return 0
ae6c293b 847
0337d704 848 owner = []
849 for o in owners:
850 email = to_forlife(o)[0]
851 if email is not None:
852 owner.append(email)
853 if len(owner) is 0:
854 return 0
855
856 mlist = MailList.MailList()
857 try:
858 oldmask = os.umask(002)
859 pw = sha.new('foobar').hexdigest()
ae6c293b 860
0337d704 861 try:
862 mlist.Create(name, owner[0], pw)
863 finally:
864 os.umask(oldmask)
865
866 mlist.real_name = listname
867 mlist.host_name = 'listes.polytechnique.org'
868 mlist.description = desc
869
870 mlist.advertised = int(advertise) is 0
871 mlist.default_member_moderation = int(modlevel) is 2
872 mlist.generic_nonmember_action = int(modlevel) > 0
873 mlist.subscribe_policy = 2 * (int(inslevel) is 1)
874 mlist.admin_notify_mchanges = (mlist.subscribe_policy or mlist.generic_nonmember_action or mlist.default_member_moderation or not mlist.advertised)
ae6c293b 875
0337d704 876 mlist.owner = owner
ae6c293b 877
0337d704 878 mlist.subject_prefix = '['+listname+'] '
879 mlist.max_message_size = 0
880
e480db84 881 inverted_listname = listname.lower() + '_' + vhost.lower()
0337d704 882 mlist.msg_footer = "_______________________________________________\n" \
31f2df6a 883 + "Liste de diffusion %(real_name)s\n" \
884 + "http://listes.polytechnique.org/members/" + inverted_listname
ae6c293b 885
0337d704 886 mlist.header_filter_rules = []
887 mlist.header_filter_rules.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False))
888
889 mlist.Save()
890
891 mlist.Unlock()
892
98c79ede 893 if ON_CREATE_CMD != '':
894 try: os.system(ON_CREATE_CMD + ' ' + name)
895 except: pass
896
ae6c293b 897 check_options(userdesc, perms, vhost, listname.lower(), True)
898 mass_subscribe(userdesc, perms, vhost, listname.lower(), members)
0337d704 899
900 # avoid the "-1 mail to moderate" bug
901 mlist = MailList.MailList(name)
902 mlist._UpdateRecords()
903 mlist.Save()
904 mlist.Unlock()
0337d704 905 except:
906 try:
907 mlist.Unlock()
908 except:
909 pass
910 return 0
911 return 1
912
ae6c293b 913def delete_list(userdesc, perms, vhost, listname, del_archives=0):
0337d704 914 lname = vhost+VHOST_SEP+listname.lower()
915 try:
ae6c293b 916 mlist = MailList.MailList(lname, lock=0)
0337d704 917 except:
918 return 0
919 try:
920 if not is_admin_on(userdesc, perms, mlist):
921 return 0
922 # remove the list
923 REMOVABLES = [ os.path.join('lists', lname), ]
924 # remove stalled locks
925 for filename in os.listdir(mm_cfg.LOCK_DIR):
926 fn_lname = filename.split('.')[0]
927 if fn_lname == lname:
928 REMOVABLES.append(os.path.join(mm_cfg.LOCK_DIR, filename))
929 # remove archives ?
930 if del_archives:
931 REMOVABLES.extend([
932 os.path.join('archives', 'private', lname),
933 os.path.join('archives', 'private', lname+'.mbox'),
934 os.path.join('archives', 'public', lname),
935 os.path.join('archives', 'public', lname+'.mbox')
936 ])
937 map(lambda dir: remove_it(lname, os.path.join(mm_cfg.VAR_PREFIX, dir)), REMOVABLES)
938 return 1
939 except:
940 return 0
941
ae6c293b 942def kill(userdesc, perms, vhost, alias, del_from_promo):
0337d704 943 exclude = []
944 if not del_from_promo:
945 exclude.append(PLATAL_DOMAIN+VHOST_SEP+'promo'+alias[-4:])
946 for list in Utils.list_names():
947 if list in exclude: continue
948 try:
ae6c293b 949 mlist = MailList.MailList(list, lock=0)
0337d704 950 except:
951 continue
952 try:
953 mlist.Lock()
ae6c293b 954 mlist.ApprovedDeleteMember(alias+'@'+PLATAL_DOMAIN, None, 0, 0)
0337d704 955 mlist.Save()
956 mlist.Unlock()
957 except:
958 mlist.Unlock()
959 return 1
960
961
962#-------------------------------------------------------------------------------
963# server
964#
965class FastXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer):
ae6c293b 966 allow_reuse_address = True
0337d704 967
968################################################################################
969#
ae6c293b 970# INIT
0337d704 971#
972#-------------------------------------------------------------------------------
973# use Mailman user and group (not root)
974# fork in background if asked to
975#
976
977uid = getpwnam(mm_cfg.MAILMAN_USER)[2]
978gid = getgrnam(mm_cfg.MAILMAN_GROUP)[2]
979
980if not os.getuid():
ae6c293b 981 os.setregid(gid, gid)
982 os.setreuid(uid, uid)
0337d704 983
984signal.signal(signal.SIGHUP, signal.SIG_IGN)
985
986if ( os.getuid() is not uid ) or ( os.getgid() is not gid):
987 sys.exit(0)
988
989opts, args = getopt.getopt(sys.argv[1:], 'f')
990for o, a in opts:
991 if o == '-f' and os.fork():
992 sys.exit(0)
993
994i18n.set_language('fr')
995mysql = connectDB()
996lock = Lock()
997
998#-------------------------------------------------------------------------------
999# server
1000#
1001server = FastXMLRPCServer(("localhost", 4949), BasicAuthXMLRPCRequestHandler)
1002
1003# index.php
1004server.register_function(get_lists)
1005server.register_function(subscribe)
1006server.register_function(unsubscribe)
1007# members.php
1008server.register_function(get_members)
1009# trombi.php
1010server.register_function(get_members_limit)
1011server.register_function(get_owners)
1012# admin.php
c4d57bd8 1013server.register_function(replace_email)
0337d704 1014server.register_function(mass_subscribe)
1015server.register_function(mass_unsubscribe)
1016server.register_function(add_owner)
1017server.register_function(del_owner)
1018# moderate.php
1019server.register_function(get_pending_ops)
1020server.register_function(handle_request)
4b0d9ef3 1021server.register_function(get_pending_sub)
0337d704 1022server.register_function(get_pending_mail)
1023# options.php
1024server.register_function(get_owner_options)
1025server.register_function(set_owner_options)
1026server.register_function(add_to_wl)
1027server.register_function(del_from_wl)
1028server.register_function(get_bogo_level)
1029server.register_function(set_bogo_level)
1030# soptions.php
1031server.register_function(get_admin_options)
1032server.register_function(set_admin_options)
1033# check.php
1034server.register_function(check_options)
1035# create + del
1036server.register_function(get_all_lists)
1037server.register_function(create_list)
1038server.register_function(delete_list)
1039# utilisateurs.php
1040server.register_function(kill)
1041
1042server.serve_forever()
1043
1044# vim:set et: