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