Checking NSS User Quotas on Open Enterprise Server for Linux
Novell Cool Solutions: Feature
By Marc-Andre Vallee
|
Digg This -
Slashdot This
Posted: 6 Apr 2006 |
Problem:
You want to give a friendly, convenient interface to the helpdesk to check user quotas.
Solution:
Software requirement:
- Novell Open Enterprise Server for Linux (Would probably work on Netware, but not tested - if working, please let us know)
- PHP with LDAP module (included with OES, but not installed by default)
- Perl (included with OES)
- Apache2
- NSS & all the NCP adminfs modules
**Note - OES Linux SP2 has a bug into iManager when managing user quotas when too many users are having quotas on the volume. This is scheduled to be fix into SP3.
Step 1
- Create a directory under /srv/www/htdocs named quota
- Password protect it, using .htaccess file
- Include an index.php file, which will give a friendly interface to the helpdesk technician, to search for the username. A dropdown menu is included, to restrict the search context into eDirectory
- This index.php will list results, and give a link to the showuserquota.pl script
- Create a directory cgi-bin under the quota directory, chown it to admin:admingroup (chmod admin:admingroup cgi-bin or chmod 600:600 cgi-bin
- Include this perl script that will send a command to the Virtual File System, and get the user quota
- chown it to 600:600 also (important! give the file the admin:admingroup ownership, so suexec will run this script as admin)
- chmod it to 770
- Go to /etc/apache2/vhosts.d and create a file named myvhost.conf
- Enter these information's, and replace bold item with your specification.
- Restart Apache (/etc/init.d/apache2 restart)
AuthName "Quota Administration" AuthType Basic AuthLDAPURL ldap://localhost:389/o=YOURORG require user admin admin123
<?php
if ( $_POST["recherche_contexte"] != "" )
{
$ldap_host = "localhost";
$ldap_port = "389";
$filter = "(cn=" . $_POST["recherche_user"] . "*)";
if ( $_POST["recherche_contexte"] == "all" )
{
echo "Searching students... <br>";
$base_dn = "ou=students,o=YOURORG";
$connect = ldap_connect( $ldap_host, $ldap_port);
ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
$read = ldap_search($connect, $base_dn, $filter);
$info = ldap_get_entries($connect, $read);
echo $info["count"]." results found<BR><BR>";
for($ligne = 0; $ligne<$info["count"]; $ligne++)
{
for($colonne = 0; $colonne<$info[$ligne]["count"]; $colonne++)
{
$data = $info[$ligne][$colonne];
if ( $data == "cn" )
{
echo "<a href=\"cgi bin/showUserSpace.pl?volume=VOLUENAME&username=" . $info[$ligne][$data][0] . "&contexte=students\">" . $info[$ligne][$data][0] . ".students</a><br />";
}
}
}
ldap_close($connect);
echo "Searching teachers... <br>";
$base_dn = "ou=teachers,o=YOURORG";
$connect = ldap_connect( $ldap_host, $ldap_port);
ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
$read = ldap_search($connect, $base_dn, $filter);
$info = ldap_get_entries($connect, $read);
echo $info["count"]." results found<BR><BR>";
for($ligne = 0; $ligne<$info["count"]; $ligne++)
{
for($colonne = 0; $colonne<$info[$ligne]["count"]; $colonne++)
{
$data = $info[$ligne][$colonne];
if ( $data == "cn" )
{
echo "<a href=\"cgi bin/showUserSpace.pl?volume=VOLUMENAME&username=" . $info[$ligne][$data][0] . "&contexte=teachers\">" . $info[$ligne][$data][0] . ".teachers</a><br />";
}
}
}
ldap_close($connect);
}
else
{
$base_dn = "ou=" . $_POST["recherche_contexte"] . ",o=YOURORG";
$connect = ldap_connect( $ldap_host, $ldap_port);
ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
$read = ldap_search($connect, $base_dn, $filter);
$info = ldap_get_entries($connect, $read);
echo $info["count"]." results found<BR><BR>";
for($ligne = 0; $ligne<$info["count"]; $ligne++)
{
for($colonne = 0; $colonne<$info[$ligne]["count"]; $colonne++)
{
$data = $info[$ligne][$colonne];
if ( $data == "cn" )
{
echo "<a href=\"cgi bin/showUserSpace.pl?volume=VOLUMENAME&username=" . $info[$ligne][$data][0] . "&contexte=" . $_POST["recherche_contexte"] . "\">" . $info[$ligne][$data][0] . "." . $_POST["recherche_contexte"] . "</a><br />";
}
}
}
ldap_close($connect);
}
echo "<br /><a href=\"index.php\">Back</a>";
}
else
{
?><html>
<head>
<meta http equiv="Content Type" content="text/html; charset=windows 1252">
<title>Quota viewer</title>
</head>
<body>
<form method="POST" action="">
User : <input name="recherche_user" type="text" size=20>*<br />
Context : <select name="recherche_contexte">
<option selected value="all">All</option>
<option value="students">Students</option>
<option value="teachers">Teachers</option>
</select>
<br />
<input type="submit" value="Search">
<input type="reset" value="Reset">
</form>
</body>
</html>
<?php
}
?>
#!/usr/bin/perl
#
# Main
#
{
print "Content type: text/html\n\n";
if (length ($ENV{'QUERY_STRING'}) > 0){
$buffer = $ENV{'QUERY_STRING'};
@pairs = split(/&/, $buffer);
foreach $pair (@pairs){
($name, $value) = split(/=/, $pair);
$value =~ s/%([a fA F0 9][a fA F0 9])/pack("C", hex($1))/eg;
$in{$name} = $value;
}
}
my $volume = $in{'volume'};
my $username = $in{'username'};
my $contexte = $in{'contexte'};
if ($volume eq "" || $username eq "" || $contexte eq "")
{
print "Error, args missing\n";
exit;
}
open(NSSFILE, "+". $_[0] . " ". $_[1] . "." . $_[2] . ".YOURORG ");
$ret = ParseList($nssResult);
}
else
{
print "Can't send XML request: $ret\n";
}
return $ret;
}
########################################################################
# Set the datastream for a file handle to the passed in value.
########################################################################
sub SetDataStream(*$)
{
my $fh = $_[0];
my $dataStream = $_[1];
my $result;
my $command;
$command = " ";
seek $fh, 0, 0;
if (!syswrite($fh, $command, length($command)))
{
$result .= "Unable to send datastream command to NDS management. ";
seek $fh, 0, 0;
if (sysread($fh, $error, 3000))
{
$result .= $error;
}
$result .= "\n";
}
return $result;
}
########################################################################
# Write a command to a file and get the result
########################################################################
sub WriteCommand(*$)
{
my $fh = $_[0];
my $command = $_[1];
my $result;
# print("command=$command\n");
seek $fh, 0, 0;
if (!syswrite($fh, $command, length($command)))
{
$result .= "Unable to send command to virtual file. ";
seek $fh, 0, 0;
if (sysread($fh, $error, 3000))
{
$result .= $error;
}
$result .= "\n";
}
else
{
seek $fh, 0, 0;
sysread($fh, $reply, 3000);
$result .= $reply;
}
# print("result=$result\n");
return $result;
}
########################################################################
# Parse the result of the list partition operation
########################################################################
sub ParseList($)
{
my $xml = $_[0];
my $result;
my @xml;
my $out;
@xml = $xml =~ /(.*?)<\/nssReply>/gs;
foreach $result (@xml)
{
if ($result =~ /(.*?)<\/quota>/s)
{
$quotaamount = $1;
}
if ($result =~ /(.*?)<\/spaceUsed>/s)
{
$quotaused = $1;
}
$out .= "";
$out .= "Quota assigned : " . $quotaamount / 1048576 . " MB\n";
$out .= "Quota used : " . $quotaused / 1048576 . " MB\n";
$out .= " " . $quotaused / 1024 . " KB\n";
$out .= "";
}
print $out;
return 0;
}
<VirtualHost your_virtual_server_ip:80>
ServerAdmin webmaster@domain.com
ServerName quota.domain.com
SuexecUserGroup admin admingroup #here's the magic
DocumentRoot /srv/www/htdocs/quota
DirectoryIndex index.html index.htm index.php
ErrorLog /var/log/apache2/quota error_log
CustomLog /var/log/apache2/quota access_log combined
HostnameLookups Off
UseCanonicalName Off
ServerSignature On
ScriptAlias /cgi bin/ "/srv/www/htdocs/quota/cgi bin/"
<Directory "/srv/www/htdocs/quota/cgi bin">
AllowOverride None
Options +ExecCGI Includes
Order allow,deny
Allow from all
</Directory>
<Directory "/srv/www/htdocs/quota">
Options Indexes FollowSymLinks ExecCGI
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Step 2
- Enjoy! Go to http://your_server_ip/quota/
- enter your valid NDS account
- search, and view the user quota
Troubleshooting:
- If not able to login into the webpage, check under /var/log/apache2/quota error_log
- If you see the in the browser what you have enter into the php script, then php is not working correctly, you should get a box to search users.
- If the script to get quota is returning 0, make sure the user has quota !
- If the script returns an error 500, make sure setuid is working (/var/log/apache2/suexec)
Explanations:
I found PHP easier to query LDAP. This would probably possible with Perl. Also, a dynamic XML file is available under /_admin, but this can be heavy to parse under large environment. The adminfs getuserspacequota directive is pretty fast, and lightweight.
The password protection with .htaccess is easy to manage, and secures your interface.
The perl script is derived from /sbin/nss, and other things found on the net. This would also be possible to include a setuserspacequota action. You can find more on http://developer.novell.com/ndk/doc/vfs/index.html?page=/ndk/doc/vfs/vfs__enu/data/bktitle.html This would also be possible to execute under PHP, but I think Perl is the right choice for this.
All the magic is taking place with the setuid statement under apache. Without this, the regular apache user (wwwrun) wouldn't be able to send request to the adminfs daemon.
If you have a cluster, you will need to put those files on all cluster nodes. Technically, it's possible to put those scripts under the cluster volume, and only put the apache conf file on all servers.
Have fun with VFS, thousands of operations can be possible with that.
Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

