Novell Home

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

  1. Create a directory under /srv/www/htdocs named quota


  2. Password protect it, using .htaccess file
  3. AuthName "Quota Administration"
    AuthType Basic
    AuthLDAPURL ldap://localhost:389/o=YOURORG
    require user admin admin123
  4. 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
  5. <?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
    }
    ?>
    
  6. This index.php will list results, and give a link to the showuserquota.pl script


  7. 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


  8. Include this perl script that will send a command to the Virtual File System, and get the user quota
  9. #!/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; }
  10. chown it to 600:600 also (important! give the file the admin:admingroup ownership, so suexec will run this script as admin)


  11. chmod it to 770


  12. Go to /etc/apache2/vhosts.d and create a file named myvhost.conf


  13. Enter these information's, and replace bold item with your specification.
  14. <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>
    
  15. Restart Apache (/etc/init.d/apache2 restart)

Step 2

  1. Enjoy! Go to http://your_server_ip/quota/
  2. enter your valid NDS account
  3. 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

© 2014 Novell