Submitted By: Mario Fetka (geos_one) (mario dot fetka at gmail dot com) Date: 2010-02-05 Initial Package Version: 2.3.2 Origin: https://ml.mandriva.net/wws/arc/mds-devel/2010-02/msg00004.html Upstream Status: unknown Description: add userquota plugin and add support for large groups quota change (ldap timeout) diff -Naur mmc-agent-2.3.2.orig/conf/plugins/userquota.ini mmc-agent-2.3.2/conf/plugins/userquota.ini --- mmc-agent-2.3.2.orig/conf/plugins/userquota.ini 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/conf/plugins/userquota.ini 2010-01-15 06:19:24.000000000 +0000 @@ -0,0 +1,19 @@ +[main] +disable = 0 + +[diskquota] +enable = 1 +# block size found using dumpe2fs -h /dev/vda1 | awk '/Block size:/ { print $3 }' + +# devicemap format: device:blocksize:displayname, ... +devicemap = /dev/vda1:4096:Test Root,/dev/mapper/home:4096:Home dir +softquotablocks = 0.95 +softquotainodes = 0.95 +inodesperblock = 1.60 + +setquotascript = echo /usr/sbin/setquota $uid $softblocks $blocks $softinodes $inodes $devicepath +delquotascript = echo /usr/sbin/setquota $uid 0 0 0 0 $devicepath + +[networkquota] +enable = 1 +networkmap = Internet:0.0.0.0/0:any diff -Naur mmc-agent-2.3.2.orig/contrib/ldap/quota.schema mmc-agent-2.3.2/contrib/ldap/quota.schema --- mmc-agent-2.3.2.orig/contrib/ldap/quota.schema 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/ldap/quota.schema 2010-01-25 02:16:23.000000000 +0000 @@ -0,0 +1,30 @@ +## +## schema file for Unix Quotas +## Schema for storing Unix Quotas in LDAP +## OIDs are owned by Cogent Innovators, LLC +## +## 1.3.6.1.4.1.19937.1.1.x - attributetypes +## 1.3.6.1.4.1.19937.1.2.x - objectclasses +## + +attributetype ( 1.3.6.1.4.1.19937.1.1.1 NAME 'quota' + DESC 'Quotas (FileSystem:BlocksSoft,BlocksHard,InodesSoft,InodesHard)' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} ) + +attributetype ( 1.3.6.1.4.1.19937.1.1.2 NAME 'networkquota' + DESC 'Network Quotas (network,protocol,bytes)' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{255} ) + +objectclass ( 1.3.6.1.4.1.19937.1.2.1 NAME 'systemQuotas' SUP posixAccount AUXILIARY + DESC 'System Quotas' + MUST ( uid ) + MAY ( quota $ networkquota )) + +objectclass ( 1.3.6.1.4.1.19937.1.2.2 NAME 'defaultQuotas' + DESC 'Quota defaults to apply to members of a group' + SUP top AUXILIARY + MUST ( cn ) + MAY ( quota $ networkquota )) + diff -Naur mmc-agent-2.3.2.orig/contrib/userquota/README mmc-agent-2.3.2/contrib/userquota/README --- mmc-agent-2.3.2.orig/contrib/userquota/README 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/userquota/README 2010-02-04 01:42:11.000000000 +0000 @@ -0,0 +1,71 @@ +# (c) 2009 OSS - Glen Ogilvie +# License: GPLv2 or above +# Version: 0.0.3 + +Description: Mandriva Directory Server plugin to store disk and network quotas in OpenLDAP and manage them +using MDS. It provides multiple quotas for both, mapped from the configuration file. It also supports turning +off either disk or network quotas if you only wish to quota one of them. + +This plugin also supports setting quotas for all the members of a group. This can be done by browsing to the +group list in MDS and using the edit group action. It also allows you to set quotas per individual, although these +are reset when you perform a group action. + +When quotas are set for a member of the group, the object classes of that member will be updated to include the systemQuotas schema. +The group will also have an additional object class added to store the defaults for that group. + +New members of the group do not currently get the group default quotas assigned but may do in the future. + + +Installation: +cp ./etc/mmc/plugins/userquota.ini /etc/mmc/plugins/ +cp -r ./mmc-python/plugins/userquota /usr/lib64/python2.6/site-packages/mmc/plugins/ +cp -r ./mmc-web/modules/userquota /usr/share/mmc/modules/ +cp ./etc/openldap/schema/quota.schema /etc/openldap/schema + +Edit: /etc/openldap/schema/local.schema +insert line: include /etc/openldap/schema/quota.schema +restart slapd. + +Patches: +These patches are against python-ldap-2.3.9-1mdv2010.0 and mmc-agent-2.3.2-8.2mdv2010.0) + +To resolve an ldap connection issue I had, the following files needed to be patched to use the ReconnectLDAPObject. + +/usr/lib64/python2.6/site-packages/mmc/plugins/base/__init__.py (use ReconnectLDAPObject and change ".modify_s(" to ".modify_ext_s(" +/usr/lib64/python2.6/site-packages/ldap/ldapobject.py (add pass exception after self.reconnect(self._uri)) + +These changes are included in: +patches/mmc-python-base_userquota_ldap.patch +patches/ldapobject.patch + +cd /usr/lib64/python2.6 +patch -p0 < ~/userquota/patches/ldapobject.patch +patch -p0 < ~/userquota/patches/patches/mmc-python-base_userquota_ldap.patch + +For both of these, I have sent an email to the mmc-devel mailing list to discuss options, as would rather not have to patch these files. + + +Configuration: +Edit: /etc/mmc/plugins/userquota.ini + +Configuration options: diskquota +devicemap + - This maps your physical device that will have the quota applied to a nice human readable name and also includes the block size of the disk + used for calculation of megabytes. + +softquotablocks, softquotainodes, inodesperblock + - multipliers for the values passed to the quota command. + +setquotascript, delquotascript + - system command to run.. Note that in example, these are echo statements so they do nothing + +Configuration options: networkquota +networkmap + - this maps the name of a network to a subnet and protocol. How the subnet and protocol are processed is up to whatever is reading the network + - quota values from each user. In my case, a custom script will read these and use them with TC for rate limiting. + +Documentation: +See: mmc-web/modules/userquota/help/doc.txt + +Bugs: +The connection the LDAP server may get disconnected sometimes. The code works around this by using the reconnection ldap class for python. diff -Naur mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/applyTCrules.py mmc-agent-2.3.2/contrib/userquota/networkquota_example/applyTCrules.py --- mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/applyTCrules.py 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/userquota/networkquota_example/applyTCrules.py 2010-02-04 01:57:16.000000000 +0000 @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8; -*- +# Example to apply network quotas from ldap to tc traffic shaping rules. +# +# (c) 2009 Open Systems Specilists - Glen Ogilvie +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# EXAMPLE ONLY: You will need to modify this to suit your network. +# +import ldap +import subprocess +import re +import warnings + +# hide DepercationWarning when importing MySQLdb +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + import MySQLdb + +uri = "ldap://localhost:389" +base = "ou=People,dc=example,dc=com" +scope = ldap.SCOPE_SUBTREE +filterstr = "(objectClass=systemQuotas)" +attrlist = ['uid', 'networkquota', 'uidNumber'] +query = """select sum(bytes_in + bytes_out) as bytes, username from +ulog where username is not null and bytes_in is not null and bytes_out is not null +group by username;""" + +# Test command to get tc filters. +tcfilterlist = "cat tcexample.txt" +tcapplycmd = "./tcruleadd.sh %s %x" # args: uid, uid in hex +tcremovecmd = "./tcruledel.sh %s %x" + + + +def getBytes(username, network): + user = False + for dn, record in res: + if "networkquota" in record: +# print record["uid"][0] + if record["uid"][0] == username: + user = True + for q in record["networkquota"]: + if q.split(',')[0] == network: + return q.split(',')[1] + + if not user: + raise NameError("username: %s not in ldap" % username) + raise NameError("Getbytes function failed to find matching networkquota attribute value %s record for user: %s" % (network, username)) + +def getUid(username): + for dn, record in res: + if record["uid"][0] == username: + return record["uidNumber"][0] + return False + +def hasQuota(username): + q = False + for dn, record in res: + if "networkquota" in record: + if record["uid"][0] == username: + q = True + return q + +def processNetworkQuotas(username, actualbytes): + if not hasQuota(username): + print "User: %s does not have a network quota set in ldap" % (username) + removeRateLimiting(username) + return False + + network = "Internet:0.0.0.0/0:any" + quotabytes = getBytes(username, network) + print "User: %s has used %s of %s bytes for network: %s" % (username, actualbytes, quotabytes , network) + if int(actualbytes) > int(quotabytes): + print "%s is over the limit by %d bytes" % (username, (int(actualbytes) - int(quotabytes))) + applyRateLimiting(username) + else: + removeRateLimiting(username) + + +def applyRateLimiting(username): + if (isRateLimited(username)): + print "User: %s already rate limited" % (username) + return True + print "Rate limiting user: %s (%s)" % (username, getUid(username)) + uid = (getUid(username)) + print "CMD: " + tcapplycmd % (uid, int(uid)) + subprocess.Popen(tcapplycmd % (uid, int(uid)), shell=True) + return True + +def removeRateLimiting(username): + if (isRateLimited(username)): + print "removing quota for: " + username + uid = (getUid(username)) + print "CMD: " + tcremovecmd % (uid, int(uid)) + subprocess.Popen(tcremovecmd % (uid, int(uid)), shell=True) + return True + return False + +def isRateLimited(username): + print "Checking if user: %s (%s) is rate limited" % (username, getUid(username)) + # python regex + p = re.compile('filter parent 1: protocol ip pref 1 fw handle 0x([0-9A-Fa-f]+) classid 1:[0-9A-Fa-f]+') + for r in tcrules: +# print "rule: " + r + m = p.match(r) + if m: + uid = int(m.groups()[0], 16) +# print "match found" + str(uid) +# print "uid from username" + getUid(username) + if uid == int(getUid(username)): + print "matching rule found " + r + return True + return False +# connect to ldap +l = ldap.initialize(uri) +res = l.search_s(base, scope, filterstr, attrlist) +print "\nLDAP Search results" +for dn, record in res: + if "networkquota" in record: +# print "Processing: " + repr(dn) + print "%-15s: %s" % (record["uid"], record["networkquota"]) +# print repr(record) +l.unbind() + + +# do this only once, instead of every time function is called. Read existing TC rules. +tcproc = subprocess.Popen(tcfilterlist, shell=True, stdout=subprocess.PIPE) +tcrules = tcproc.communicate()[0].split("\n") + +# connect to Mysql +db = MySQLdb.connect(passwd="passwd", db="nufw", host="laptop", user="root", port=13306) +c = db.cursor(MySQLdb.cursors.DictCursor) + +#query = """select bytes_in + bytes_out as bytes, username from +#ulog where username is not null and bytes_in is not null and bytes_out is not null;""" + +c.execute(query) +print "\nMysql Results:" +results = c.fetchall() +for x in results: + print "%(username)-15s: %(bytes)s\n" % x + processNetworkQuotas(x["username"], x["bytes"]) diff -Naur mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/setup.txt mmc-agent-2.3.2/contrib/userquota/networkquota_example/setup.txt --- mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/setup.txt 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/userquota/networkquota_example/setup.txt 2010-02-04 01:50:02.000000000 +0000 @@ -0,0 +1,10 @@ +# remove the existing classless qdisc +tc qdisc del dev eth1 root + +# print status +tc -s qdisc ls dev eth1 + +# add new root qdisc +tc qdisc add dev eth1 root handle 1: htb default 1 + + diff -Naur mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/tcruleadd.sh mmc-agent-2.3.2/contrib/userquota/networkquota_example/tcruleadd.sh --- mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/tcruleadd.sh 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/userquota/networkquota_example/tcruleadd.sh 2010-02-04 01:59:10.000000000 +0000 @@ -0,0 +1,10 @@ +#!/bin/bash +# (c) 2009 Open Systems Specilists - Glen Ogilvie. License: GPL +# Examle apply shell script +USERID=$1 +# class id is Userid in hex. +CLASSID=$2 +RATE="20kbps burst 5k" +tc class add dev eth1 parent 1: classid 1:$CLASSID htb rate $RATE +tc qdisc add dev eth1 parent 1:$CLASSID handle $CLASSID: sfq perturb 10 +tc filter add dev eth1 parent 1: protocol ip prio 1 handle $USERID fw flowid 1:$CLASSID diff -Naur mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/tcruledel.sh mmc-agent-2.3.2/contrib/userquota/networkquota_example/tcruledel.sh --- mmc-agent-2.3.2.orig/contrib/userquota/networkquota_example/tcruledel.sh 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/contrib/userquota/networkquota_example/tcruledel.sh 2010-02-04 01:58:59.000000000 +0000 @@ -0,0 +1,11 @@ +#!/bin/bash +# (c) 2009 Open Systems Specilists - Glen Ogilvie. License: GPL +# Examle remove shell script +USERID=$1 +# class id is Userid in hex. +CLASSID=$2 +RATE="20kbps burst 5k" +tc filter del dev eth1 parent 1: protocol ip prio 1 handle $USERID fw flowid 1:$CLASSID +tc qdisc del dev eth1 parent 1:$CLASSID handle $CLASSID: sfq perturb 10 +tc class del dev eth1 parent 1: classid 1:$CLASSID htb rate $RATE + diff -Naur mmc-agent-2.3.2.orig/mmc/plugins/base/__init__.py mmc-agent-2.3.2/mmc/plugins/base/__init__.py --- mmc-agent-2.3.2.orig/mmc/plugins/base/__init__.py 2010-02-05 18:08:26.095049411 +0000 +++ mmc-agent-2.3.2/mmc/plugins/base/__init__.py 2010-02-05 18:14:08.567292865 +0000 @@ -665,9 +665,8 @@ if self.config.has_section(USERDEFAULT): for option in self.config.options(USERDEFAULT): self.userDefault["base"][option] = self.config.get(USERDEFAULT, option) - - self.l = ldap.open(self.ldapHost) - +# self.l = ldap.open(self.ldapHost) + self.l = ldap.ldapobject.ReconnectLDAPObject("ldap://%s:389" % self.ldapHost, retry_max=5, retry_delay=10) # you should set this to ldap.VERSION2 if you're using a v2 directory self.l.protocol_version = ldap.VERSION3 @@ -715,7 +714,7 @@ new = old.copy() new["loginShell"] = "/bin/bash" # FIXME: should not be hardcoded but put in a conf file modlist = ldap.modlist.modifyModlist(old, new) - self.l.modify_s(dn, modlist) + self.l.modify_ext_s(dn, modlist) return 0 def disableUser(self, login): @@ -731,7 +730,7 @@ new = old.copy() new["loginShell"] = "/bin/false" # FIXME: should not be hardcoded but put in a conf file modlist = ldap.modlist.modifyModlist(old, new) - self.l.modify_s(dn, modlist) + self.l.modify_ext_s(dn, modlist) return 0 def isEnabled(self, login): @@ -983,7 +982,7 @@ cngroup = cngroup.encode("utf-8") uiduser = uiduser.encode("utf-8") try: - self.l.modify_s('cn=' + cngroup + ',' + self.baseGroupsDN, [(ldap.MOD_DELETE, 'memberUid', uiduser)]) + self.l.modify_ext_s('cn=' + cngroup + ',' + self.baseGroupsDN, [(ldap.MOD_DELETE, 'memberUid', uiduser)]) except ldap.NO_SUCH_ATTRIBUTE: # There are no member in this group pass @@ -1086,7 +1085,7 @@ cngroup = cngroup.encode("utf-8") uiduser = uiduser.encode("utf-8") try: - self.l.modify_s('cn=' + cngroup + ',' + base, [(ldap.MOD_ADD, 'memberUid', uiduser)]) + self.l.modify_ext_s('cn=' + cngroup + ',' + base, [(ldap.MOD_ADD, 'memberUid', uiduser)]) except ldap.TYPE_OR_VALUE_EXISTS: # Try to add a the user to one of his/her group # Can be safely ignored @@ -1113,11 +1112,11 @@ elif isinstance(attrVal, xmlrpclib.Binary): # Needed for binary string coming from XMLRPC attrVal = str(attrVal) - self.l.modify_s('uid='+uid+','+ self.baseUsersDN, [(ldap.MOD_REPLACE,attr,attrVal)]) + self.l.modify_ext_s('uid='+uid+','+ self.baseUsersDN, [(ldap.MOD_REPLACE,attr,attrVal)]) else: # Remove the attribute because its value is empty try: - self.l.modify_s('uid='+uid+','+ self.baseUsersDN, [(ldap.MOD_DELETE,attr, None)]) + self.l.modify_ext_s('uid='+uid+','+ self.baseUsersDN, [(ldap.MOD_DELETE,attr, None)]) except ldap.NO_SUCH_ATTRIBUTE: # The attribute has been already deleted pass @@ -1138,10 +1137,10 @@ group = group.encode("utf-8") if attrVal: attrVal = str(attrVal.encode("utf-8")) - self.l.modify_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_REPLACE,attr,attrVal)]) + self.l.modify_ext_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_REPLACE,attr,attrVal)]) else: - self.l.modify_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_REPLACE,attr,'rien')]) - self.l.modify_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_DELETE,attr,'rien')]) + self.l.modify_ext_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_REPLACE,attr,'rien')]) + self.l.modify_ext_s('cn=' + group + ','+ self.baseGroupsDN, [(ldap.MOD_DELETE,attr,'rien')]) return 0 def changeUserPasswd(self, uid, passwd): @@ -1158,7 +1157,7 @@ self.l.passwd_s('uid=' + uid + ',' + self.baseUsersDN, None, str(passwd)) else: userpassword = self._generatePassword(passwd) - self.l.modify_s('uid=' + uid + ',' + self.baseUsersDN, [(ldap.MOD_REPLACE, "userPassword", userpassword)]) + self.l.modify_ext_s('uid=' + uid + ',' + self.baseUsersDN, [(ldap.MOD_REPLACE, "userPassword", userpassword)]) # Run ChangeUserPassword hook self.runHook("base.changeuserpassword", uid, passwd) @@ -1628,7 +1627,7 @@ # Apply modification mlist = ldap.modlist.modifyModlist(attrs, newattrs) - self.l.modify_s(cn, mlist) + self.l.modify_ext_s(cn, mlist) def removeGroupObjectClass(self, group, className): # Create LDAP path @@ -1654,7 +1653,7 @@ # Apply modification mlist = ldap.modlist.modifyModlist(attrs, newattrs) - self.l.modify_s(cn, mlist) + self.l.modify_ext_s(cn, mlist) def getAttrToDelete(self, dn, className): """retrieve all attributes to delete wich correspond to param schema""" @@ -1921,7 +1920,7 @@ """ dn = "uid=" + uid + "," + self.l.baseUsersDN try: - self.l.l.modify_s( self._getGpoDN(gpoName), [(ldap.MOD_ADD, 'member', dn)]) + self.l.l.modify_ext_s( self._getGpoDN(gpoName), [(ldap.MOD_ADD, 'member', dn)]) except ldap.TYPE_OR_VALUE_EXISTS: # Value already set pass @@ -1935,7 +1934,7 @@ """ dn = "uid=" + uid + "," + self.l.baseUsersDN try: - self.l.l.modify_s(self._getGpoDN(gpoName), [(ldap.MOD_DELETE, 'member', dn)]) + self.l.l.modify_ext_s(self._getGpoDN(gpoName), [(ldap.MOD_DELETE, 'member', dn)]) except ldap.NO_SUCH_ATTRIBUTE: # Value already deleted pass @@ -1969,7 +1968,7 @@ """ dn = "cn=" + group + "," + self.l.baseGroupsDN try: - self.l.l.modify_s( self._getGpoDN(gpoName), [(ldap.MOD_ADD, 'member', dn)]) + self.l.l.modify_ext_s( self._getGpoDN(gpoName), [(ldap.MOD_ADD, 'member', dn)]) except ldap.TYPE_OR_VALUE_EXISTS: # Value already set pass @@ -1983,7 +1982,7 @@ """ dn = "cn=" + group + "," + self.l.baseGroupsDN try: - self.l.l.modify_s(self._getGpoDN(gpoName), [(ldap.MOD_DELETE, 'member', dn)]) + self.l.l.modify_ext_s(self._getGpoDN(gpoName), [(ldap.MOD_DELETE, 'member', dn)]) except ldap.NO_SUCH_ATTRIBUTE: # Value already deleted pass diff -Naur mmc-agent-2.3.2.orig/mmc/plugins/userquota/__init__.py mmc-agent-2.3.2/mmc/plugins/userquota/__init__.py --- mmc-agent-2.3.2.orig/mmc/plugins/userquota/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.2/mmc/plugins/userquota/__init__.py 2010-02-04 00:06:51.000000000 +0000 @@ -0,0 +1,602 @@ +# -*- coding: utf-8; -*- +# (c) 2009 Open Systems Specilists - Glen Ogilvie +# +# $Id: __init__.py $ +# +# This file is a plugin for Mandriva Management Console (MMC). +# +# MMC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# MMC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with MMC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import socket +import ldap +import logging +import os +import os.path +import grp +import tempfile + +from mmc.plugins.base import ldapUserGroupControl +from mmc.support.config import * +from mmc.support import mmctools +from string import Template + + +import mmc + +INI = "/etc/mmc/plugins/userquota.ini" + +VERSION = "0.0.3" +APIVERSION = "1:0:0" +REVISION = int("$Rev: 1 $".split(':')[1].strip(' $')) + +def getVersion(): return VERSION +def getApiVersion(): return APIVERSION +def getRevision(): return REVISION + + +def activate(): + return True + +def getActiveComponents(): + return UserQuotaControl().getActiveComponents() + +def getDevicemap(): + return UserQuotaControl().getDevicemap() + +def getNetworkmap(): + return UserQuotaControl().getNetworkmap() + +def setDiskQuota(uid, device, quota): + return UserQuotaControl().setDiskQuota(uid, device, quota) + +def setNetworkQuota(uid, network, quota): + return UserQuotaControl().setNetworkQuota(uid, network, quota) + +def setGroupDiskQuota(group, device, quota, overwrite): + return UserQuotaControl().setGroupDiskQuota(group, device, quota, overwrite) + +def deleteGroupDiskQuota(cn, device): + return UserQuotaControl().deleteGroupDiskQuotas(cn, device) + +def setGroupNetworkQuota(group, network, quota, overwrite): + return UserQuotaControl().setGroupNetworkQuota(group, network, quota, overwrite) + +def deleteGroupNetworkQuota(cn, device): + return UserQuotaControl().deleteGroupNetworkQuotas(cn, device) + + +def deleteDiskQuota(uid, device): + return UserQuotaControl().deleteDiskQuota(uid, device) + +def deleteNetworkQuota(uid, network): + return UserQuotaControl().deleteNetworkQuota(uid, network) + +def setUserQuotaDefaults(user, group): + return UserQuotaControl().setUserQuotaDefaults(user, group) + +class UserQuotaConfig(PluginConfig): + + def readConf(self): + PluginConfig.readConf(self) + try: self.diskquotaenable = self.getboolean("diskquota", "enable") + except: pass + try: self.networkquotaenable = self.getboolean("networkquota", "enable") + except: pass + self.devicemap = self.get("diskquota", "devicemap").split(',') + self.inodesperblock = self.getfloat("diskquota", "inodesperblock") + self.softquotablocks = self.getfloat("diskquota", "softquotablocks") + self.softquotainodes = self.getfloat("diskquota", "softquotainodes") + self.setquotascript = self.get("diskquota", "setquotascript") + self.delquotascript = self.get("diskquota", "delquotascript") + self.networkmap = self.get("networkquota", "networkmap").split(',') + + def setDefault(self): + PluginConfig.setDefault(self) + self.diskquotaenable = True + self.networkquotaenable = False + + +class UserQuotaControl(ldapUserGroupControl): + def __init__(self, conffile=None, conffilebase=None): + mmc.plugins.base.ldapUserGroupControl.__init__(self, conffilebase) + self.configuserquota = UserQuotaConfig("userquota", conffile) + self.tempfilename = False + self.tempdelfilename = False + def getDevicemap(self): + return self.configuserquota.devicemap + def getNetworkmap(self): + return self.configuserquota.networkmap + def getActiveComponents(self): + return ({"disk":self.configuserquota.diskquotaenable, "network":self.configuserquota.networkquotaenable}) + + def deleteNetworkQuota(self, uid, network): + logger = logging.getLogger() + try: + currentquotas = self.getDetailedUser(uid)["networkquota"] + newquotas = [] + for x in currentquotas: + if not x.split(',')[0] == network: + newquotas.append(x) + + if len(newquotas) == 0: + self.changeUserAttributes(uid, 'networkquota', None) + self.delQuotaObjectClass(uid) + + else: + self.changeUserAttributes(uid, 'networkquota', newquotas) + + except KeyError: + pass + return True + + def deleteDiskQuota(self, uid, device, single=True): + logger = logging.getLogger() +# logger.info("Deleting quota for: "+ uid) + devicepath = device.split(':')[0] + try: + currentquotas = self.getDetailedUser(uid)["quota"] + newquotas = [] + for x in currentquotas: + if not x.split('=')[0] == devicepath: + newquotas.append(x) + + if len(newquotas) == 0: + self.changeUserAttributes(uid, 'quota', None) + self.delQuotaObjectClass(uid) + + else: + self.changeUserAttributes(uid, 'quota', newquotas) + + self.appendDeleteQuotatasks(uid, devicepath) + if single: + logger.info("Delete Single quota") + self.deleteQuotaOnFS(); + + except KeyError: + pass + + return True + + + def setNetworkQuota(self, uid, network, quota, overwrite = "all"): + logger = logging.getLogger() + ldapquota = '%s,%s' % (network, str(int(quota) * 1048576)) + logger.info("Network Quota:" + ldapquota) + + if not self.hasDiskQuotaObjectClass(uid): + self.addDiskQuotaObjectClass(uid) + # @todo, fix copy from disk quotas. + try: + userdetails = self.getDetailedUser(uid) + currentquotas = userdetails["networkquota"] + newquotas = [] + quotachanged = False + for x in currentquotas: + if x.split(',')[0] == network: + logger.info("Current network quota sizes: " + str(self.convertNetworkQuotaToMB(x))) + logger.info("Requested quota size: " + quota) + logger.info("Overwrite mode: " + overwrite) + if (overwrite == "none"): + logger.info('No overwrite mode set. so not overwriting.') + return False + if overwrite == "smaller" and self.convertNetworkQuotaToMB(x) > int(quota): + logger.info('Current network quota is bigger than new quota, so not overwriting') + return False + if overwrite == "larger" and int(quota) > self.convertNetworkQuotaToMB(x): + logger.info('Current network quota is smaller than new quota, so not overwriting') + return False + newquotas.append(ldapquota) + quotachanged = True + else: + newquotas.append(x) + + if not quotachanged: + newquotas.append(ldapquota) + + self.changeUserAttributes(uid, 'networkquota', newquotas) + except KeyError: + self.changeUserAttributes(uid, 'networkquota', ldapquota) + pass + + return True + + def setDiskQuota(self, uid, device, quota, overwrite = "all",single = True): + logger = logging.getLogger() + logger.info("received quota for " + uid + ", device: " + device + ", size: " + quota) + blocks = self.convertMBtoBlocks(quota, device); + softblocks = int (blocks * self.configuserquota.softquotablocks) + inodes = int(blocks * self.configuserquota.inodesperblock) + softinodes = int(inodes * self.configuserquota.softquotainodes) + devicepath = device.split(':')[0] +# logger.info("Devicepath: " + devicepath) +# logger.info("Blocks: " + str(blocks)) +# logger.info("Soft Blocks: " + str(softblocks)) +# logger.info("Inodes: " + str(inodes)) +# logger.info("Soft Inodes: " + str(softinodes)) + ldapquota = '%s=%s:%s:%s:%s' % (devicepath, str(blocks), str(softblocks), str(inodes), str(softinodes)) + logger.info("Quota for: " + uid + " - " + ldapquota) + + if not self.hasDiskQuotaObjectClass(uid): + self.addDiskQuotaObjectClass(uid) + try: + userdetails = self.getDetailedUser(uid) + currentquotas = userdetails["quota"] + newquotas = [] + quotachanged = False + for x in currentquotas: + if x.split('=')[0] == devicepath: + logger.info("Current network quota sizes: " + str(self.convertDiskQuotaToMB(x))) + logger.info("Requested quota size: " + quota) + logger.info("Overwrite mode: " + overwrite) + if overwrite == "none": + return False + if overwrite == "smaller" and self.convertDiskQuotaToMB(x) > int(quota): + logger.info('Current quota is bigger than new quota, so not overwriting') + return False + if overwrite == "larger" and int(quota) > self.convertDiskQuotaToMB(x): + logger.info('Current quota is smaller than new quota, so not overwriting') + return False + + newquotas.append(ldapquota) + quotachanged = True + else: + newquotas.append(x) + + if not quotachanged: + newquotas.append(ldapquota) + self.changeUserAttributes(uid, 'quota', newquotas) + except KeyError: + self.changeUserAttributes(uid, 'quota', ldapquota) + pass + + self.appendQuotatasks(uid, blocks, softblocks, inodes, softinodes, devicepath) + if single: + self.applyQuotaToFS() + + return True + + def appendQuotatasks(self, uid, blocks, softblocks, inodes, softinodes, devicepath): + logger = logging.getLogger() + if not self.tempfilename: + self.tempfilename = tempfile.mktemp() + logger.info("Temp file: %s" % (self.tempfilename)) + + + s = Template(self.configuserquota.setquotascript) + shellscript = s.substitute(uid=uid, blocks=blocks, softblocks=softblocks, inodes=inodes, softinodes=softinodes, devicepath=devicepath) + logger.info("Append SetQuotaScript: " + shellscript); + f = open(self.tempfilename, 'a') + f.write("%s\n" % (shellscript)) + f.close + + + def applyQuotaToFS(self): + if not self.tempfilename: + return + logger = logging.getLogger() + shellscript = "/bin/sh %s" % (self.tempfilename) + logger.info("ApplyQuotas: " + shellscript); + def cb(shprocess): + # The callback just return the process outputs + # + if shprocess.exitCode != 0: + logger.error("Error applying quotas: " + shprocess.err) + logger.info("shell result:" + shprocess.out) + logger.error("See: " + self.tempfilename + " for details of the commands run") + else: + os.remove(self.tempfilename) + + self.tempfilename = False + return shprocess.exitCode, shprocess.out, shprocess.err + + d = mmctools.shLaunchDeferred(shellscript) + # shLaunchDeferred returns a Deferred() object + # We add the cb function as a callback + d.addCallback(cb) + # We return the Deferred() object + return d + + def appendDeleteQuotatasks(self, uid, devicepath): + logger = logging.getLogger() + if not self.tempdelfilename: + self.tempdelfilename = tempfile.mktemp() + logger.info("Temp file: %s" % (self.tempdelfilename)) + + s = Template(self.configuserquota.delquotascript) + shellscript = s.substitute(uid=uid, devicepath=devicepath) + logger.info("Append DelQuotaScript: " + shellscript); + f = open(self.tempdelfilename, 'a') + f.write("%s\n" % (shellscript)) + f.close + + + def deleteQuotaOnFS(self): + if not self.tempdelfilename: + return + logger = logging.getLogger() + shellscript = "/bin/sh %s" % (self.tempdelfilename) + logger.info("DelQuotaScript: " + shellscript); + def cb(shprocess): + # The callback just return the process outputs + if shprocess.exitCode != 0: + logger.error("Error applying del quotas: " + shprocess.err) + logger.info("shell result:" + shprocess.out) + logger.error("See: " + self.tempfilename + " for details of the commands run") + else: + os.remove(self.tempdelfilename) + + self.tempdelfilename = False + return shprocess.exitCode, shprocess.out, shprocess.err + d = mmctools.shLaunchDeferred(shellscript) + # shLaunchDeferred returns a Deferred() object + # We add the cb function as a callback + d.addCallback(cb) + # We return the Deferred() object + return d + + def convertMBtoBlocks(self, quota, device): + parts = device.split(':') + blocks = int(parts[1]) + bytes = int(quota) * 1048576 + return int(bytes / blocks) + + def convertNetworkQuotaToMB(self, quota): + return int(quota.split(',')[1])/1048576 + + def convertDiskQuotaToMB(self,quota): + devicemap = self.getDevicemap() + devicepath = quota.split('=')[0] + for x in devicemap: + if x.split(':')[0] == devicepath: + return int(quota.split('=')[1].split(":")[0]) * int(x.split(':')[1]) / 1048576 + return False + + + def hasDiskQuotaObjectClass(self, uid): + """ + Return true if the user owns the systemQuotas objectClass. + + @param uid: user name + @type uid: str + + @return: return True if the user owns the mailAccount objectClass. + @rtype: boolean + """ + return "systemQuotas" in self.getDetailedUser(uid)["objectClass"] + + def delQuotaObjectClass(self, uid): + """ + Return true if the objectClass is removed. + + @return: return True if the object class is able to be removed + @rtype: boolean + """ + logger = logging.getLogger() + user = self.getDetailedUser(uid) + logger.info("Del object class") + + if "quota" in user.keys() or "networkquota" in user.keys(): + return False + + logger.info("Del object class removal") + if "systemQuotas" in user["objectClass"]: + user["objectClass"].remove("systemQuotas") + self.changeUserAttributes(uid, 'objectClass', user["objectClass"]) + return True + return False + + def delGroupQuotaObjectClass(self, cn): + """ + Return true if the objectClass is removed. + + @return: return True if the object class is able to be removed + @rtype: boolean + """ + logger = logging.getLogger() + group = self.getDetailedGroup(cn) + logger.info("Del Group object class") + logger.info("group keys" + str(group.keys())) + if "quota" in group.keys() or "networkquota" in group.keys(): + return False + + logger.info("Del object class removal") + if "defaultQuotas" in group["objectClass"]: + group["objectClass"].remove("defaultQuotas") + logger.info("ObjectClass to save:" + str( group["objectClass"]) ) + self.changeGroupAttribute(cn, 'objectClass', group["objectClass"]) + return True + return False + + def addDiskQuotaObjectClass(self, uid): + user = self.getDetailedUser(uid)['objectClass'] + if not "systemQuotas" in user: + user.append("systemQuotas") + self.l.modify_ext_s('uid=' + uid + ',' + self.baseUsersDN, [(ldap.MOD_REPLACE, 'objectClass', user)]) + return True + + + def setGroupDiskQuota(self, group, device, quota, overwrite): + logger = logging.getLogger() + logger.info("SetGroupDiskQuota: Overwrite mode: " + overwrite) + logger.info("ldap timeout:" + str(self.l.timeout)) + self.l.set_option(ldap.OPT_NETWORK_TIMEOUT, 100) + logger.info("ldap network timeout:" + str(self.l.get_option(ldap.OPT_NETWORK_TIMEOUT))) + + + self.addGroupDefaultDiskQuotaObjectClass(group) + + blocks = self.convertMBtoBlocks(quota, device); + softblocks = int (blocks * self.configuserquota.softquotablocks) + inodes = int(blocks * self.configuserquota.inodesperblock) + softinodes = int(inodes * self.configuserquota.softquotainodes) + devicepath = device.split(':')[0] + ldapquota = '%s=%s:%s:%s:%s' % (devicepath, str(blocks), str(softblocks), str(inodes), str(softinodes)) + # @todo improve this, it's a copy of set disk quota. + try: + currentquotas = self.getDetailedGroup(group)["quota"] + newquotas = [] + quotachanged = False + for x in currentquotas: + if x.split('=')[0] == devicepath: + newquotas.append(ldapquota) + quotachanged = True + else: + newquotas.append(x) + + if not quotachanged: + newquotas.append(ldapquota) + + self.changeGroupAttribute(group, "quota", newquotas) + + except KeyError: + self.changeGroupAttribute(group, "quota", ldapquota) + pass + + + for uid in self.getMembers(group): + self.setDiskQuota(uid, device, quota, overwrite, single=False) + + # apply the group quotas to FS. + self.applyQuotaToFS() + + + return True + + def setGroupNetworkQuota(self, group, network, quota, overwrite): + logger = logging.getLogger() + logger.info("SetGroupNetworkQuota Overwrite mode: " + overwrite) + self.addGroupDefaultDiskQuotaObjectClass(group) + ldapquota = '%s,%s' % (network, str(int(quota) * 1048576)) + # @todo improve this, it's a copy of set disk quota. + try: + currentquotas = self.getDetailedGroup(group)["networkquota"] + newquotas = [] + quotachanged = False + for x in currentquotas: + if x.split(',')[0] == network: + newquotas.append(ldapquota) + quotachanged = True + else: + newquotas.append(x) + + if not quotachanged: + newquotas.append(ldapquota) + + self.changeGroupAttribute(group, "networkquota", newquotas) + + except KeyError: + self.changeGroupAttribute(group, "networkquota", ldapquota) + pass + + for uid in self.getMembers(group): + self.setNetworkQuota(uid, network, quota, overwrite) + return True + + def setUserQuotaDefaults(self, uid, group): + # @todo: unfinished, does nothing yet. + logger = logging.getLogger() + logger.info("Set user quota defaults: user: " + uid + " group: " + group) + keys = [] + # don't set the quota if one has been set before. + logger.info(self.getDetailedUser(uid)["objectClass"]) + logger.info("Value of: self.hasDiskQuotaObjectClass(uid)" + str(self.hasDiskQuotaObjectClass(uid))) + if self.hasDiskQuotaObjectClass(uid): + logger.info("User already has quota Object class") + return keys + + groupdefaults = self.getDetailedGroup(group) + + # @todo, check components before action + if "quota" in groupdefaults.keys(): + # copy quota values to user + logger.info("copy quota values to user:" + uid) + self.addDiskQuotaObjectClass(uid) + self.changeUserAttributes(uid, "quota", groupdefaults["quota"]) + keys.append('quota') + + if "networkquota" in groupdefaults.keys(): + # copy networkquota values to user + logger.info("copy network quota values to user:" + uid) + self.addDiskQuotaObjectClass(uid) + self.changeUserAttributes(uid, "networkquota", groupdefaults["networkquota"]) + keys.append('networkquota') + + return keys + + def addGroupDefaultDiskQuotaObjectClass(self, cn): + group = self.getDetailedGroup(cn)['objectClass'] + if not "defaultQuotas" in group: + group.append("defaultQuotas") + self.changeGroupAttribute(cn, 'objectClass', group) + return True + + def changeGroupAttribute(self, cn, attr, attrval): + self.l.modify_ext_s('cn=' + cn + ',' + self.baseGroupsDN, [(ldap.MOD_REPLACE, attr, attrval)]) + + def deleteGroupDiskQuotas(self, cn, device): + logger = logging.getLogger() + devicepath = device.split(':')[0] + logger.info("Delete quotas for members of:" + cn) + logger.info("ldap timeout:" + str(self.l.timeout)) + self.l.set_option(ldap.OPT_NETWORK_TIMEOUT, 100) + logger.info("ldap network timeout:" + str(self.l.get_option(ldap.OPT_NETWORK_TIMEOUT))) + + + for uid in self.getMembers(cn): + self.deleteDiskQuota(uid, device, single=False) + + self.deleteQuotaOnFS(); + + try: + currentquotas = self.getDetailedGroup(cn)["quota"] + newquotas = [] + for x in currentquotas: + if not x.split('=')[0] == devicepath: + newquotas.append(x) + + if len(newquotas) == 0: + self.changeGroupAttribute(cn, 'quota', None) + self.delGroupQuotaObjectClass(cn) + else: + self.changeGroupAttribute(cn, 'quota', newquotas) + + except KeyError: + pass + + return True + + def deleteGroupNetworkQuotas(self, cn, network): + logger = logging.getLogger() + logger.info("Delete networkquotas for members of: " + cn) + for uid in self.getMembers(cn): + self.deleteNetworkQuota(uid, network) + try: + currentquotas = self.getDetailedGroup(cn)["networkquota"] + newquotas = [] + + for x in currentquotas: + if not x.split(',')[0] == network: + newquotas.append(x) + + if len(newquotas) == 0: + self.changeGroupAttribute(cn, 'networkquota', None) + self.delGroupQuotaObjectClass(cn) + else: + self.changeGroupAttribute(cn, 'networkquota', newquotas) + + except KeyError: + pass + return True + diff -Naur mmc-agent-2.3.2.orig/setup.py mmc-agent-2.3.2/setup.py --- mmc-agent-2.3.2.orig/setup.py 2010-02-05 18:08:41.170067138 +0000 +++ mmc-agent-2.3.2/setup.py 2010-02-05 18:09:35.879049358 +0000 @@ -8,5 +8,5 @@ author_email = "cdelfosse@mandriva.com", maintainer = "Cedric Delfosse", maintainer_email = "cdelfosse@mandriva.com", - packages = ["mmc", "mmc.support", "mmc.plugins", "mmc.plugins.base", "mmc.plugins.samba", "mmc.plugins.proxy", "mmc.plugins.mail", "mmc.plugins.network", "mmc.plugins.kerberos", "mmc.plugins.printstats", "mmc.plugins.printing"], + packages = ["mmc", "mmc.support", "mmc.plugins", "mmc.plugins.base", "mmc.plugins.samba", "mmc.plugins.proxy", "mmc.plugins.mail", "mmc.plugins.network", "mmc.plugins.kerberos", "mmc.plugins.printstats", "mmc.plugins.printing", "mmc.plugins.userquota"], )