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