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