X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;f=bin%2Flists.rpc.py;h=cda3511ba7fe0267dcf4ef51ac899ca38ef4ffef;hb=3424387cde6c635ff16c3b5459c4caa88bb76e2e;hp=ea7e412ed59fd3cdb6e6e7c29925f5e2fe5efb58;hpb=25112e7afb16a4625be821fa5ff45e8c640dc57b;p=platal.git diff --git a/bin/lists.rpc.py b/bin/lists.rpc.py index ea7e412..cda3511 100755 --- a/bin/lists.rpc.py +++ b/bin/lists.rpc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python #*************************************************************************** -#* Copyright (C) 2004 polytechnique.org * +#* Copyright (C) 2003-2014 Polytechnique.org * #* http://opensource.polytechnique.org/ * #* * #* This program is free software; you can redistribute it and/or modify * @@ -22,6 +22,8 @@ import base64, MySQLdb, os, getopt, sys, sha, signal, re, shutil, ConfigParser import MySQLdb.converters import SocketServer +import errno +import traceback sys.path.append('/usr/lib/mailman/bin') @@ -60,20 +62,27 @@ def get_config(sec, val, default=None): return config.get(sec, val)[1:-1] except ConfigParser.NoOptionError, e: if default is None: - print e + sys.stderr.write('%s\n' % str(e)) sys.exit(1) else: return default MYSQL_USER = get_config('Core', 'dbuser') MYSQL_PASS = get_config('Core', 'dbpwd') +MYSQL_HOST = get_config('Core', 'dbhost') +MYSQL_DB = get_config('Core', 'dbdb') PLATAL_DOMAIN = get_config('Mail', 'domain') PLATAL_DOMAIN2 = get_config('Mail', 'domain2', '') +sys.stderr.write('PLATAL_DOMAIN = %s\n' % PLATAL_DOMAIN ) +sys.stderr.write("MYSQL_DB = %s\n" % MYSQL_DB) VHOST_SEP = get_config('Lists', 'vhost_sep', '_') ON_CREATE_CMD = get_config('Lists', 'on_create', '') +SRV_HOST = get_config('Lists', 'rpchost', 'localhost') +SRV_PORT = int(get_config('Lists', 'rpcport', '4949')) + ################################################################################ # # CLASSES @@ -93,13 +102,18 @@ class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): argument a UserDesc taken from the database, containing name, email and perms """ + def _get_function(self, method): + try: + # check to see if a matching function has been registered + return self.server.funcs[method] + except: + raise Exception('method "%s" is not supported' % method) + + def is_rpc_path_valid(self): + return True + def _dispatch(self, method, params): - # TODO: subclass in SimpleXMLRPCDispatcher and not here. - new_params = list(params) - new_params.insert(0, self.data[2]) - new_params.insert(0, self.data[1]) - new_params.insert(0, self.data[0]) - return self.server._dispatch(method, new_params) + return list_call_dispatcher(self._get_function(method), self.data[0], self.data[1], self.data[2], *params) def do_POST(self): try: @@ -116,22 +130,29 @@ class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): self.end_headers() def getUser(self, uid, md5, vhost): - res = mysql_fetchone ("""SELECT CONCAT(u.prenom, ' ', u.nom), a.alias, u.perms - FROM auth_user_md5 AS u - INNER JOIN aliases AS a ON ( a.id=u.user_id AND a.type='a_vie' ) - WHERE u.user_id = '%s' AND u.password = '%s' AND u.perms IN ('admin', 'user') - LIMIT 1""" %( uid, md5 ) ) + res = mysql_fetchone ("""SELECT a.full_name, IF(s.email IS NULL, a.email, CONCAT(s.email, '@%s')), + IF (a.is_admin, 'admin', + IF(FIND_IN_SET('lists', at.perms) OR FIND_IN_SET('lists', a.user_perms), 'lists', NULL)) + FROM accounts AS a + INNER JOIN account_types AS at ON (at.type = a.type) + LEFT JOIN email_source_account AS s ON (s.uid = a.uid AND s.type = 'forlife') + WHERE a.uid = '%s' AND a.password = '%s' AND a.state = 'active' + LIMIT 1""" \ + % (PLATAL_DOMAIN, uid, md5)) if res: name, forlife, perms = res - if vhost != PLATAL_DOMAIN: - res = mysql_fetchone ("""SELECT uid - FROM groupex.membres AS m - INNER JOIN groupex.asso AS a ON (m.asso_id = a.id) - WHERE perms='admin' AND uid='%s' AND mail_domain='%s'""" %( uid , vhost ) ) - if res: perms= 'admin' - userdesc = UserDesc(forlife+'@'+PLATAL_DOMAIN, name, None, 0) + if vhost != PLATAL_DOMAIN and perms != 'admin': + res = mysql_fetchone ("""SELECT m.uid, IF(m.perms = 'admin', 'admin', 'lists') + FROM group_members AS m + INNER JOIN groups AS g ON (m.asso_id = g.id) + WHERE uid = '%s' AND mail_domain = '%s'""" \ + % (uid, vhost)) + if res: + _, perms = res + userdesc = UserDesc(forlife, name, None, 0) return (userdesc, perms, vhost) else: + print >> sys.stderr, "no user found for uid: %s, passwd: %s" % (uid, md5) return None ################################################################################ @@ -144,11 +165,12 @@ class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): def connectDB(): db = MySQLdb.connect( - db='x4dat', + db=MYSQL_DB, user=MYSQL_USER, passwd=MYSQL_PASS, - unix_socket='/var/run/mysqld/mysqld.sock') + host=MYSQL_HOST) db.ping() + db.autocommit(True) return db.cursor() def mysql_fetchone(query): @@ -181,12 +203,13 @@ def to_forlife(email): mbox = email fqdn = PLATAL_DOMAIN if ( fqdn == PLATAL_DOMAIN ) or ( fqdn == PLATAL_DOMAIN2 ): - res = mysql_fetchone("""SELECT CONCAT(f.alias, '@%s'), CONCAT(u.prenom, ' ', u.nom) - FROM auth_user_md5 AS u - INNER JOIN aliases AS f ON (f.id=u.user_id AND f.type='a_vie') - INNER JOIN aliases AS a ON (a.id=u.user_id AND a.alias='%s' AND a.type!='homonyme') - WHERE u.perms IN ('admin', 'user') - LIMIT 1""" %( PLATAL_DOMAIN, mbox ) ) + res = mysql_fetchone("""SELECT CONCAT(s1.email, '@%s'), a.full_name + FROM accounts AS a + INNER JOIN email_source_account AS s1 ON (a.uid = s1.uid AND s1.type = 'forlife') + INNER JOIN email_source_account AS s2 ON (a.uid = s2.uid AND s2.email = '%s') + WHERE a.state = 'active' + LIMIT 1""" \ + % (PLATAL_DOMAIN, mbox)) if res: return res else: @@ -202,27 +225,84 @@ def remove_it(listname, filename): elif os.path.isdir(filename): shutil.rmtree(filename) +## +# Call dispatcher +## + +def has_annotation(method, name): + """ Check if the method contains the given annoation. + """ + return method.__doc__ and method.__doc__.find("@%s" % name) > -1 + +def list_call_dispatcher(method, userdesc, perms, vhost, *arg): + """Dispatch the call to the right handler. + This function checks the options of the called method the set the environment of the call. + The dispatcher uses method annotation (special tokens in the documentation of the method) to + guess the requested environment: + @mlist: the handler requires a mlist object instead of the vhost/listname couple + @lock: the handler requires the mlist to be locked (@mlist MUST be specified) + @edit: the handler edit the mlist (@mlist MUST be specified) + @admin: the handler requires admin rights on the list (@mlist MUST be specified) + @root: the handler requires site admin rights + """ + try: + print >> sys.stderr, "calling method: %s" % method + if has_annotation(method, "root") and perms != "admin": + return 0 + if has_annotation(method, "mlist"): + listname = str(arg[0]) + arg = arg[1:] + mlist = MailList.MailList(vhost + VHOST_SEP + listname.lower(), lock=0) + if has_annotation(method, "admin") and not is_admin_on(userdesc, perms, mlist): + return 0 + if has_annotation(method, "edit") or has_annotation(method, "lock"): + return list_call_locked(method, userdesc, perms, mlist, has_annotation(method, "edit"), *arg) + else: + return method(userdesc, perms, mlist, *arg) + else: + return method(userdesc, perms, vhost, *arg) + except Exception, e: + sys.stderr.write('Exception in dispatcher %s\n' % str(e)) + raise e + return 0 + +def list_call_locked(method, userdesc, perms, mlist, edit, *arg): + """Call the given method after locking the mlist. + """ + try: + mlist.Lock() + ret = method(userdesc, perms, mlist, *arg) + if edit: + mlist.Save() + mlist.Unlock() + return ret + except Exception, e: + traceback.print_exc(file=sys.stderr) + sys.stderr.write('Exception in locked call %s: %s\n' % (method.__name__, str(e))) + mlist.Unlock() + return 0 + # TODO: use finally when switching to python 2.5 + #------------------------------------------------------------------------------- # helpers on lists # +def is_subscription_pending(userdesc, perms, mlist): + for id in mlist.GetSubscriptionIds(): + if userdesc.address == mlist.GetRecord(id)[1]: + return True + return False + def get_list_info(userdesc, perms, mlist, front_page=0): members = mlist.getRegularMemberKeys() is_member = userdesc.address in members is_owner = userdesc.address in mlist.owner - if mlist.advertised or is_member or is_owner or (not front_page and perms == 'admin'): + if (mlist.advertised and perms in ('lists', 'admin')) or is_member or is_owner or (not front_page and perms == 'admin'): is_pending = False if not is_member and (mlist.subscribe_policy > 1): - try: - mlist.Lock() - for id in mlist.GetSubscriptionIds(): - if userdesc.address == mlist.GetRecord(id)[1]: - is_pending = 1 - break - mlist.Unlock() - except: - mlist.Unlock() - return 0 + is_pending = list_call_locked(is_subscription_pending, userdesc, perms, mlist, False) + if is_pending is 0: + return None host = mlist.internal_name().split(VHOST_SEP)[0].lower() details = { @@ -239,59 +319,43 @@ def get_list_info(userdesc, perms, mlist, front_page=0): 'nbsub': len(members) } return (details, members) - return 0 + return None -def get_options(userdesc, perms, vhost, listname, opts): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - options = { } - for (k, v) in mlist.__dict__.iteritems(): - if k in opts: - if type(v) is str: - options[k] = quote(v) - else: options[k] = v - details = get_list_info(userdesc, perms, mlist)[0] - return (details, options) - except: - return 0 - -def set_options(userdesc, perms, vhost, listname, opts, vals): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - mlist.Lock() - for (k, v) in vals.iteritems(): - if k not in opts: - continue - if k == 'default_member_moderation': - for member in mlist.getMembers(): - mlist.setMemberOption(member, mm_cfg.Moderate, int(v)) - t = type(mlist.__dict__[k]) - if t is bool: mlist.__dict__[k] = bool(v) - elif t is int: mlist.__dict__[k] = int(v) - elif t is str: mlist.__dict__[k] = Utils.uncanonstr(v, 'fr') - else: mlist.__dict__[k] = v - mlist.Save() - mlist.Unlock() - return 1 - except: - mlist.Unlock() - return 0 +def get_options(userdesc, perms, mlist, opts): + """ Get the options of a list. + @mlist + @admin + """ + options = { } + for (k, v) in mlist.__dict__.iteritems(): + if k in opts: + if type(v) is str: + options[k] = quote(v) + else: options[k] = v + details = get_list_info(userdesc, perms, mlist)[0] + return (details, options) + +def set_options(userdesc, perms, mlist, opts, vals): + for (k, v) in vals.iteritems(): + if k not in opts: + continue + if k == 'default_member_moderation': + for member in mlist.getMembers(): + mlist.setMemberOption(member, mm_cfg.Moderate, int(v)) + t = type(mlist.__dict__[k]) + if t is bool: mlist.__dict__[k] = bool(v) + elif t is int: mlist.__dict__[k] = int(v) + elif t is str: mlist.__dict__[k] = Utils.uncanonstr(v, 'fr') + else: mlist.__dict__[k] = v + return 1 #------------------------------------------------------------------------------- # users procedures for [ index.php ] # def get_lists(userdesc, perms, vhost, email=None): + """ List available lists for the given vhost + """ if email is None: udesc = userdesc else: @@ -308,48 +372,37 @@ def get_lists(userdesc, perms, vhost, email=None): except: continue try: - details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN))[0] - result.append(details) - except: + details = get_list_info(udesc, perms, mlist, (email is None and vhost == PLATAL_DOMAIN)) + if details is not None: + result.append(details[0]) + except Exception, e: + sys.stderr.write('Can\'t get list %s: %s\n' % (name, str(e))) continue return result -def subscribe(userdesc, perms, vhost, listname): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - mlist.Lock() - if ( mlist.subscribe_policy in (0, 1) ) or userdesc.address in mlist.owner: - mlist.ApprovedAddMember(userdesc) - result = 2 - else: - result = 1 - try: - mlist.AddMember(userdesc) - except Errors.MMNeedApproval: - pass - mlist.Save() - except: - result = 0 - mlist.Unlock() +def subscribe(userdesc, perms, mlist): + """ Subscribe to a list. + @mlist + @edit + """ + if ( mlist.subscribe_policy in (0, 1) ) or userdesc.address in mlist.owner: + mlist.ApprovedAddMember(userdesc) + result = 2 + else: + result = 1 + try: + mlist.AddMember(userdesc) + except Errors.MMNeedApproval: + pass return result -def unsubscribe(userdesc, perms, vhost, listname): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - mlist.Lock() - mlist.ApprovedDeleteMember(userdesc.address) - mlist.Save() - mlist.Unlock() - return 1 - except: - mlist.Unlock() - return 0 +def unsubscribe(userdesc, perms, mlist): + """ Unsubscribe from a list + @mlist + @edit + """ + mlist.ApprovedDeleteMember(userdesc.address) + return 1 #------------------------------------------------------------------------------- # users procedures for [ index.php ] @@ -361,271 +414,220 @@ def get_name(member): except: return '' -def get_members(userdesc, perms, vhost, listname): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - details, members = get_list_info(userdesc, perms, mlist) - members.sort() - members = map(lambda member: (get_name(member), member), members) - return (details, members, mlist.owner) - except: +def get_members(userdesc, perms, mlist): + """ List the members of a list. + @mlist + """ + infos = get_list_info(userdesc, perms, mlist) + if infos is None: + # Do not return None, this is not serializable return 0 + details, members = infos + members.sort() + members = map(lambda member: (get_name(member), member), members) + return (details, members, mlist.owner) + #------------------------------------------------------------------------------- # users procedures for [ trombi.php ] # -def get_members_limit(userdesc, perms, vhost, listname, page, nb_per_page): - try: - members = get_members(userdesc, perms, vhost, listname.lower())[1] - except: - return 0 +def get_members_limit(userdesc, perms, mlist, page, nb_per_page): + """ Get a range of members of the list. + @mlist + """ + members = get_members(userdesc, perms, mlist)[1] i = int(page) * int(nb_per_page) return (len(members), members[i:i+int(nb_per_page)]) -def get_owners(userdesc, perms, vhost, listname): - try: - details, members, owners = get_members(userdesc, perms, vhost, listname.lower()) - except: - return 0 +def get_owners(userdesc, perms, mlist): + """ Get the owners of the list. + @mlist + """ + details, members, owners = get_members(userdesc, perms, mlist) return (details, owners) + #------------------------------------------------------------------------------- # owners procedures [ admin.php ] # -def replace_email(userdesc, perms, vhost, listname, from_email, to_email): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - - mlist.Lock() - mlist.ApprovedChangeMemberAddress(from_email.lower(), to_email.lower(), 0) - mlist.Save() - mlist.Unlock() - return 1 - except: - return 0 - -def mass_subscribe(userdesc, perms, vhost, listname, users): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 +def replace_email(userdesc, perms, mlist, from_email, to_email): + """ Replace the address of a member by another one. + @mlist + @edit + @admin + """ + mlist.ApprovedChangeMemberAddress(from_email.lower(), to_email.lower(), 0) + return 1 - members = mlist.getRegularMemberKeys() - added = [] - mlist.Lock() - for user in users: - email, name = to_forlife(user) - if ( email is None ) or ( email in members ): - continue - userd = UserDesc(email, name, None, 0) - mlist.ApprovedAddMember(userd) - added.append( (quote(userd.fullname), userd.address) ) - mlist.Save() - except: - pass - mlist.Unlock() +def mass_subscribe(userdesc, perms, mlist, users): + """ Add a list of users to the list. + @mlist + @edit + @admin + """ + if isinstance(users, dict): + users = users.values() + if not isinstance(users, list): + raise Exception("userlist must be a list") + members = mlist.getRegularMemberKeys() + added = [] + for user in users: + email, name = to_forlife(user) + if ( email is None ) or ( email in members ): + continue + userd = UserDesc(email, name, None, 0) + mlist.ApprovedAddMember(userd) + added.append( (quote(userd.fullname), userd.address) ) return added -def mass_unsubscribe(userdesc, perms, vhost, listname, users): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - - mlist.Lock() - map(lambda user: mlist.ApprovedDeleteMember(user), users) - mlist.Save() - except: - pass - mlist.Unlock() +def mass_unsubscribe(userdesc, perms, mlist, users): + """ Remove a list of users from the list. + @mlist + @edit + @admin + """ + map(lambda user: mlist.ApprovedDeleteMember(user), users) return users -def add_owner(userdesc, perms, vhost, listname, user): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: +def add_owner(userdesc, perms, mlist, user): + """ Add a owner to the list. + @mlist + @edit + @admin + """ + email = to_forlife(user)[0] + if email is None: return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - email = to_forlife(user)[0] - if email is None: - return 0 - if email not in mlist.owner: - mlist.Lock() - mlist.owner.append(email) - mlist.Save() - except: - pass - mlist.Unlock() + if email not in mlist.owner: + mlist.owner.append(email) return True -def del_owner(userdesc, perms, vhost, listname, user): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: +def del_owner(userdesc, perms, mlist, user): + """ Remove a owner of the list. + @mlist + @edit + @admin + """ + if len(mlist.owner) < 2: return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - if len(mlist.owner) < 2: - return 0 - mlist.Lock() - mlist.owner.remove(user) - mlist.Save() - except: - pass - mlist.Unlock() + mlist.owner.remove(user) return True #------------------------------------------------------------------------------- # owners procedures [ admin.php ] # -def get_pending_ops(userdesc, perms, vhost, listname): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - - mlist.Lock() - - subs = [] - seen = [] - dosave = False - for id in mlist.GetSubscriptionIds(): - time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) - if addr in seen: - mlist.HandleRequest(id, mm_cfg.DISCARD) - dosave = True - continue - seen.append(addr) - try: - login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group() - subs.append({'id': id, 'name': quote(fullname), 'addr': addr, 'login': login }) - except: - subs.append({'id': id, 'name': quote(fullname), 'addr': addr }) - - helds = [] - for id in mlist.GetHeldMessageIds(): - ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(id) - try: - size = os.path.getsize(os.path.join(mm_cfg.DATA_DIR, filename)) - except OSError, e: - if e.errno <> errno.ENOENT: raise - continue - helds.append({ - 'id' : id, - 'sender': quote(sender, True), - 'size' : size, - 'subj' : quote(subject, True), - 'stamp' : ptime - }) - if dosave: mlist.Save() - mlist.Unlock() - except: - mlist.Unlock() - return 0 - return (subs, helds) - -def handle_request(userdesc, perms, vhost, listname, id, value, comment): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - mlist.Lock() - mlist.HandleRequest(int(id), int(value), comment) - mlist.Save() - mlist.Unlock() - return 1 - except: - mlist.Unlock() - return 0 - -def get_pending_sub(userdesc, perms, vhost, listname, id): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - - mlist.Lock() - sub = 0 - id = int(id) - if id in mlist.GetSubscriptionIds(): - time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) - try: - login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group() - sub = {'id': id, 'name': quote(fullname), 'addr': addr, 'login': login } - except: - sub = {'id': id, 'name': quote(fullname), 'addr': addr } - mlist.Unlock() - except: - mlist.Unlock() - return 0 - return sub +def get_pending_ops(userdesc, perms, mlist): + """ Get the list of operation waiting for an action from the owners. + @mlist + @lock + @admin + """ + subs = [] + seen = [] + dosave = False + for id in mlist.GetSubscriptionIds(): + time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) + if addr in seen: + mlist.HandleRequest(id, mm_cfg.DISCARD) + dosave = True + continue + seen.append(addr) + try: + login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group() + subs.append({'id': id, 'name': quote(fullname), 'addr': addr, 'login': login }) + except: + subs.append({'id': id, 'name': quote(fullname), 'addr': addr }) -def get_pending_mail(userdesc, perms, vhost, listname, id, raw=0): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - mlist.Lock() - ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(int(id)) + helds = [] + for id in mlist.GetHeldMessageIds(): + ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(id) fpath = os.path.join(mm_cfg.DATA_DIR, filename) - size = os.path.getsize(fpath) - msg = readMessage(fpath) - mlist.Unlock() - - if raw: - return quote(str(msg)) - results_plain = [] - results_html = [] - for part in typed_subpart_iterator(msg, 'text', 'plain'): - c = part.get_payload() - if c is not None: results_plain.append (c) - results_plain = map(lambda x: quote(x), results_plain) - for part in typed_subpart_iterator(msg, 'text', 'html'): - c = part.get_payload() - if c is not None: results_html.append (c) - results_html = map(lambda x: quote(x), results_html) - return {'id' : id, + try: + size = os.path.getsize(fpath) + except OSError, e: + if e.errno <> errno.ENOENT: raise + continue + try: + msg = readMessage(fpath) + fromX = msg.has_key("X-Org-Mail") + except: + pass + helds.append({ + 'id' : id, 'sender': quote(sender, True), 'size' : size, 'subj' : quote(subject, True), 'stamp' : ptime, - 'parts_plain' : results_plain, - 'parts_html': results_html } - except: - mlist.Unlock() - return 0 + 'fromx' : fromX + }) + if dosave: + mlist.Save() + return (subs, helds) + +def handle_request(userdesc, perms, mlist, id, value, comment): + """ Handle a moderation request. + @mlist + @edit + @admin + """ + # Force encoding to mailman's default for french, since this is what + # Mailman will use internally + # LC_DESCRIPTIONS is a dict of lang => (name, charset, direction) tuples. + encoding = mm_cfg.LC_DESCRIPTIONS['fr'][1] + comment = comment.encode(encoding, 'replace') + mlist.HandleRequest(int(id), int(value), comment) + return 1 + +def get_pending_sub(userdesc, perms, mlist, id): + """ Get informations about a given subscription moderation. + @mlist + @lock + @admin + """ + sub = 0 + id = int(id) + if id in mlist.GetSubscriptionIds(): + time, addr, fullname, passwd, digest, lang = mlist.GetRecord(id) + try: + login = re.match("^[^.]*\.[^.]*\.\d\d\d\d$", addr.split('@')[0]).group() + sub = {'id': id, 'name': quote(fullname), 'addr': addr, 'login': login } + except: + sub = {'id': id, 'name': quote(fullname), 'addr': addr } + return sub + +def get_pending_mail(userdesc, perms, mlist, id, raw=0): + """ Get informations about a given mail moderation. + @mlist + @lock + @admin + """ + ptime, sender, subject, reason, filename, msgdata = mlist.GetRecord(int(id)) + fpath = os.path.join(mm_cfg.DATA_DIR, filename) + size = os.path.getsize(fpath) + msg = readMessage(fpath) + + if raw: + return quote(str(msg)) + results_plain = [] + results_html = [] + for part in typed_subpart_iterator(msg, 'text', 'plain'): + c = part.get_payload() + if c is not None: results_plain.append (c) + results_plain = map(lambda x: quote(x), results_plain) + for part in typed_subpart_iterator(msg, 'text', 'html'): + c = part.get_payload() + if c is not None: results_html.append (c) + results_html = map(lambda x: quote(x), results_html) + return {'id' : id, + 'sender': quote(sender, True), + 'size' : size, + 'subj' : quote(subject, True), + 'stamp' : ptime, + 'parts_plain' : results_plain, + 'parts_html': results_html } #------------------------------------------------------------------------------- # owner options [ options.php ] @@ -636,93 +638,104 @@ owner_opts = ['accept_these_nonmembers', 'admin_notify_mchanges', 'description', 'subject_prefix', 'goodbye_msg', 'send_goodbye_msg', 'subscribe_policy', \ 'welcome_msg'] -def get_owner_options(userdesc, perms, vhost, listname): - return get_options(userdesc, perms, vhost, listname.lower(), owner_opts) +def get_owner_options(userdesc, perms, mlist): + """ Get the owner options of a list. + @mlist + @admin + """ + return get_options(userdesc, perms, mlist, owner_opts) -def set_owner_options(userdesc, perms, vhost, listname, values): - return set_options(userdesc, perms, vhost, listname.lower(), owner_opts, values) +def set_owner_options(userdesc, perms, mlist, values): + """ Set the owner options of a list. + @mlist + @edit + @admin + """ + return set_options(userdesc, perms, mlist, owner_opts, values) -def add_to_wl(userdesc, perms, vhost, listname, addr): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - mlist.Lock() - mlist.accept_these_nonmembers.append(addr) - mlist.Save() - mlist.Unlock() - return 1 - except: - mlist.Unlock() - return 0 +def add_to_wl(userdesc, perms, mlist, addr): + """ Add addr to the whitelist + @mlist + @edit + @admin + """ + mlist.accept_these_nonmembers.append(addr) + return 1 -def del_from_wl(userdesc, perms, vhost, listname, addr): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - mlist.Lock() - mlist.accept_these_nonmembers.remove(addr) - mlist.Save() - mlist.Unlock() - return 1 - except: - mlist.Unlock() - return 0 +def del_from_wl(userdesc, perms, mlist, addr): + """ Remove an address from the whitelist + @mlist + @edit + @admin + """ + mlist.accept_these_nonmembers.remove(addr) + return 1 -def get_bogo_level(userdesc, perms, vhost, listname): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - if mlist.header_filter_rules == []: - return 0 - try: - action = mlist.header_filter_rules[1][1] - return 2 - except: - action = mlist.header_filter_rules[0][1] - if action == mm_cfg.HOLD: - return 1 - if action == mm_cfg.DISCARD: - return 3 - except: +def get_bogo_level(userdesc, perms, mlist): + """ Compute bogo level from the filtering rules set up on the list. + @mlist + @admin + """ + if len(mlist.header_filter_rules) == 0: return 0 -def set_bogo_level(userdesc, perms, vhost, listname, level): - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname.lower(), lock=0) - except: - return 0 + unsurelevel = 0 + filterlevel = 0 + filterbase = 0 + + # The first rule filters Unsure mails + if mlist.header_filter_rules[0][0] == 'X-Spam-Flag: Unsure, tests=bogofilter': + unsurelevel = 1 + filterbase = 1 + + # Check the other rules: + # - we have 2 rules: this is level 2 (drop > 0.999999, moderate Yes) + # - we have only one rule with HOLD directive : this is level 1 (moderate spams) + # - we have only one rule with DISCARD directive : this is level 3 (drop spams) try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - hfr = [] - if int(level) is 1: - hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) - elif int(level) is 2: - hfr.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg.DISCARD, False)) - hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) - elif int(level) is 3: - hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.DISCARD, False)) - if mlist.header_filter_rules != hfr: - mlist.Lock() - mlist.header_filter_rules = hfr - mlist.Save() - mlist.Unlock() - return 1 + action = mlist.header_filter_rules[filterbase + 1][1] + filterlevel = 2 except: - mlist.Unlock() - return 0 + action = mlist.header_filter_rules[filterbase][1] + if action == mm_cfg.HOLD: + filterlevel = 1 + elif action == mm_cfg.DISCARD: + filterlevel = 3 + return (filterlevel << 1) + unsurelevel + +def set_bogo_level(userdesc, perms, mlist, level): + """ Set filter to the specified level. + @mlist + @edit + @admin + """ + hfr = [] + + # The level is a combination of a spam filtering level and unsure filtering level + # - the unsure filtering level is only 1 bit (1 = HOLD unsures, 0 = Accept unsures) + # - the spam filtering level is a number growing with filtering strength + # (0 = no filtering, 1 = moderate spam, 2 = drop 0.999999 and moderate others, 3 = drop spams) + bogolevel = int(level) + filterlevel = bogolevel >> 1 + unsurelevel = bogolevel & 1 + + # Set up unusre filtering + if unsurelevel == 1: + hfr.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False)) + + # Set up spam filtering + if filterlevel is 1: + hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) + elif filterlevel is 2: + hfr.append(('X-Spam-Flag: Yes, tests=bogofilter, spamicity=(0\.999999|1\.000000)', mm_cfg.DISCARD, False)) + hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) + elif filterlevel is 3: + hfr.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.DISCARD, False)) + + # save configuration + if mlist.header_filter_rules != hfr: + mlist.header_filter_rules = hfr + return 1 #------------------------------------------------------------------------------- # admin procedures [ soptions.php ] @@ -731,15 +744,20 @@ def set_bogo_level(userdesc, perms, vhost, listname, level): admin_opts = [ 'advertised', 'archive', \ 'max_message_size', 'msg_footer', 'msg_header'] -def get_admin_options(userdesc, perms, vhost, listname): - if perms != 'admin': - return 0 - return get_options(userdesc, perms, vhost, listname.lower(), admin_opts) +def get_admin_options(userdesc, perms, mlist): + """ Get administrator options. + @mlist + @root + """ + return get_options(userdesc, perms, mlist, admin_opts) -def set_admin_options(userdesc, perms, vhost, listname, values): - if perms != 'admin': - return 0 - return set_options(userdesc, perms, vhost, listname.lower(), admin_opts, values) +def set_admin_options(userdesc, perms, mlist, values): + """ Set administrator options. + @mlist + @edit + @root + """ + return set_options(userdesc, perms, mlist, admin_opts, values) #------------------------------------------------------------------------------- # admin procedures [ check.php ] @@ -786,38 +804,37 @@ check_opts = { 'unsubscribe_policy' : 0, } +def check_options_runner(userdesc, perms, mlist, listname, correct): + options = { } + for (k, v) in check_opts.iteritems(): + if mlist.__dict__[k] != v: + options[k] = v, mlist.__dict__[k] + if correct: mlist.__dict__[k] = v + if mlist.real_name.lower() != listname: + options['real_name'] = listname, mlist.real_name + if correct: mlist.real_name = listname + return 1 + + def check_options(userdesc, perms, vhost, listname, correct=False): + """ Check the list. + @root + """ listname = listname.lower() - try: - mlist = MailList.MailList(vhost+VHOST_SEP+listname, lock=0) - except: - return 0 - try: - if perms != 'admin': return 0 - if correct: - mlist.Lock() - options = { } - for (k, v) in check_opts.iteritems(): - if mlist.__dict__[k] != v: - options[k] = v, mlist.__dict__[k] - if correct: mlist.__dict__[k] = v - if mlist.real_name.lower() != listname: - options['real_name'] = listname, mlist.real_name - if correct: mlist.real_name = listname - if correct: - mlist.Save() - mlist.Unlock() - details = get_list_info(userdesc, perms, mlist)[0] - return (details, options) - except: - if correct: mlist.Unlock() - return 0 + mlist = MailList.MailList(vhost + VHOST_SEP + listname, lock=0) + if correct: + return list_call_locked(check_options_runner, userdesc, perms, mlist, True, listname, True) + else: + return check_options_runner(userdesc, perms, mlist, listname, False) #------------------------------------------------------------------------------- # super-admin procedures # def get_all_lists(userdesc, perms, vhost): + """ Get all the list for the given vhost + @root + """ prefix = vhost.lower()+VHOST_SEP names = Utils.list_names() names.sort() @@ -828,19 +845,70 @@ def get_all_lists(userdesc, perms, vhost): result.append(name.replace(prefix, '')) return result +def get_all_user_lists(userdesc, perms, vhost, email): + """ Get all the lists for the given user + @root + """ + names = Utils.list_names() + names.sort() + result = [] + for name in names: + try: + mlist = MailList.MailList(name, lock=0) + ismember = email in mlist.getRegularMemberKeys() + isowner = email in mlist.owner + if not ismember and not isowner: + continue + host = mlist.internal_name().split(VHOST_SEP)[0].lower() + result.append({ 'list': mlist.real_name, + 'addr': mlist.real_name.lower() + '@' + host, + 'host': host, + 'own' : isowner, + 'sub' : ismember + }) + except Exception, e: + continue + return result + +def change_user_email(userdesc, perms, vhost, from_email, to_email): + """ Change the email of a user + @root + """ + from_email = from_email.lower() + to_email = to_email.lower() + for list in Utils.list_names(): + try: + mlist = MailList.MailList(list, lock=0) + except: + continue + try: + mlist.Lock() + mlist.ApprovedChangeMemberAddress(from_email, to_email, 0) + mlist.Save() + mlist.Unlock() + except: + mlist.Unlock() + return 1 + + def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, inslevel, owners, members): - if perms != 'admin': - return 0 - name = vhost.lower()+VHOST_SEP+listname.lower(); + """ Create a new list. + @root + """ + name = vhost.lower() + VHOST_SEP + listname.lower(); if Utils.list_exists(name): + print >> sys.stderr, "List ", name, " already exists" return 0 owner = [] for o in owners: - email = to_forlife(o)[0] + email = to_forlife(o) + print >> sys.stderr, "owner in list", o, email + email = email[0] if email is not None: owner.append(email) if len(owner) is 0: + print >> sys.stderr, "No owner found in ", owners return 0 mlist = MailList.MailList() @@ -874,67 +942,61 @@ def create_list(userdesc, perms, vhost, listname, desc, advertise, modlevel, ins + "http://listes.polytechnique.org/members/" + inverted_listname mlist.header_filter_rules = [] + mlist.header_filter_rules.append(('X-Spam-Flag: Unsure, tests=bogofilter', mm_cfg.HOLD, False)) mlist.header_filter_rules.append(('X-Spam-Flag: Yes, tests=bogofilter', mm_cfg.HOLD, False)) - mlist.Save() - - mlist.Unlock() - if ON_CREATE_CMD != '': try: os.system(ON_CREATE_CMD + ' ' + name) except: pass - check_options(userdesc, perms, vhost, listname.lower(), True) - mass_subscribe(userdesc, perms, vhost, listname.lower(), members) + check_options_runner(userdesc, perms, mlist, listname.lower(), True) + mass_subscribe(userdesc, perms, mlist, members) + mlist.Save() + finally: + mlist.Unlock() - # avoid the "-1 mail to moderate" bug - mlist = MailList.MailList(name) + # avoid the "-1 mail to moderate" bug + mlist = MailList.MailList(name) + try: mlist._UpdateRecords() mlist.Save() + finally: mlist.Unlock() - except: - try: - mlist.Unlock() - except: - pass - return 0 return 1 -def delete_list(userdesc, perms, vhost, listname, del_archives=0): - lname = vhost+VHOST_SEP+listname.lower() - try: - mlist = MailList.MailList(lname, lock=0) - except: - return 0 - try: - if not is_admin_on(userdesc, perms, mlist): - return 0 - # remove the list - REMOVABLES = [ os.path.join('lists', lname), ] - # remove stalled locks - for filename in os.listdir(mm_cfg.LOCK_DIR): - fn_lname = filename.split('.')[0] - if fn_lname == lname: - REMOVABLES.append(os.path.join(mm_cfg.LOCK_DIR, filename)) - # remove archives ? - if del_archives: - REMOVABLES.extend([ - os.path.join('archives', 'private', lname), - os.path.join('archives', 'private', lname+'.mbox'), - os.path.join('archives', 'public', lname), - os.path.join('archives', 'public', lname+'.mbox') - ]) - map(lambda dir: remove_it(lname, os.path.join(mm_cfg.VAR_PREFIX, dir)), REMOVABLES) - return 1 - except: - return 0 +def delete_list(userdesc, perms, mlist, del_archives=0): + """ Delete the list. + @mlist + @admin + """ + lname = mlist.internal_name() + # remove the list + REMOVABLES = [ os.path.join('lists', lname), ] + # remove stalled locks + for filename in os.listdir(mm_cfg.LOCK_DIR): + fn_lname = filename.split('.')[0] + if fn_lname == lname: + REMOVABLES.append(os.path.join(mm_cfg.LOCK_DIR, filename)) + # remove archives ? + if del_archives: + REMOVABLES.extend([ + os.path.join('archives', 'private', lname), + os.path.join('archives', 'private', lname+'.mbox'), + os.path.join('archives', 'public', lname), + os.path.join('archives', 'public', lname+'.mbox') + ]) + map(lambda dir: remove_it(lname, os.path.join(mm_cfg.VAR_PREFIX, dir)), REMOVABLES) + return 1 def kill(userdesc, perms, vhost, alias, del_from_promo): + """ Remove a user from all the lists. + """ exclude = [] if not del_from_promo: - exclude.append(PLATAL_DOMAIN+VHOST_SEP+'promo'+alias[-4:]) + exclude.append(PLATAL_DOMAIN + VHOST_SEP + 'promo' + alias[-4:]) for list in Utils.list_names(): - if list in exclude: continue + if list in exclude: + continue try: mlist = MailList.MailList(list, lock=0) except: @@ -988,7 +1050,7 @@ lock = Lock() #------------------------------------------------------------------------------- # server # -server = FastXMLRPCServer(("localhost", 4949), BasicAuthXMLRPCRequestHandler) +server = FastXMLRPCServer((SRV_HOST, SRV_PORT), BasicAuthXMLRPCRequestHandler) # index.php server.register_function(get_lists) @@ -1024,6 +1086,8 @@ server.register_function(set_admin_options) server.register_function(check_options) # create + del server.register_function(get_all_lists) +server.register_function(get_all_user_lists) +server.register_function(change_user_email) server.register_function(create_list) server.register_function(delete_list) # utilisateurs.php @@ -1031,4 +1095,4 @@ server.register_function(kill) server.serve_forever() -# vim:set et: +# vim:set et sw=4 sts=4 sws=4: