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