RATS client programing guide.

 

 Overview


 The RATS client package is primarily composed of four major parts: the client library, the configuration files, the daemon, and the client applications. The system was designed under the principle that application logic should be done in the client applications and all actual operations should be dispatch to either the client daemon or to the main RATS server. This paradigm is generally followed by the the provided tools, with a few rare exceptions, such as the UID generation for which the logic is mostly handled in the daemon part of the client software. This method is not enforced but strongly recommended. Note that all provided tools and libraries are implemented in Perl and the rest of this document assumes that changes and new apps will also be done in Perl. This does not mean that other languages can not be used.  It does however mean that we do not currently offer support for them outside defining the API and the protocol.

 Talking to each other


 The communication protocol between all parts of the RATS system (including client applications, client daemons, and master server) communicate using a shared API and protocols. The API call ranges are fragmented between different type of calls as documented in the API documentation. Each API call is considered atomic, by which we mean that for each request a separate network connection is established, the command sent, the answer received, and the connection closed. Note that the API offers support for requests and answers which may span more then one network transmission per connection. When writing RATS network code make sure that if you receive a "continuing call" you are ready to read all following transmissions. The RATS client library offers functions which abstract this away, as we will see bellow.

 The communication protocol requires that each API call must be encrypted. The current encryption is triple CBC DES using two keys. This can be simulated with single CBC DES by encrypting the call with the first key, decrypting it with the second, end re-encrypting with the first. The resulting information (which may contain nulls and other odd characters) is then sent over a normal network connection. The transmission itself should be comprised of then length of the encrypted text in a network long format, followed by the actual encrypted text. Getting these details right may be a bit hard, so we recommend using the abstraction layer described bellow, if using the RATS Perl client libraries.
 

The client daemon


 The client daemon is responsible for handling all the client side calls. This generally means every operation of actual account creation. By this we mean the actual creation of an account (such as creating password file entries, directories, or allocating UID's) and not authentication a user's identity or allocation usernames, which are handled by the main RATS server. The actual daemon is a small network listener which forks a new instance for each network connection, verifies access permissions as defined in the rats.conf file, decrypts the incoming request, parses the request, executes it, encrypts the return value, and sends them back the network connection.

 Note that the daemon parses the incoming API call and runs the appropriate handling function as defined in the rats_internal.conf file. The handling function is required to take as arguments all the parts of the API call _except_ the actual API call number which is removed by the daemon driver. The arguments are no longer separated by semicolons and they are passed as individual arguments. The handling function is also required to return a fully formed API response which the daemon driver encrypts and transmits without modification. This behavior of taking parsed calls and returning complete calls may seem a bit odd, but it simplifies internal calls and chaining of function calls.

 Most handling function are actually comprised of three separate functions. The main function, called by the daemon driver, is responsible for determining if the call should be executed locally or if it should be forwarded to a different host. The second function is used for forwarding the call to a remote host. The third function performs the actual operation locally. Generally, the main function returns the output of the second or third functions as passed on to it. This implies that the local function is in charge of generating the fully formed API response, and that the function in charge of dispatching the call to a remote host must return whatever response the remote host provided. This setup allows for chaining of calls, multi host clustering, and transparent behavior for the client applications.
 

 Adding new client daemon functionality


 It may become necessary to add new functionality or change existing behavior for the client daemon. This is not to be undertaken lightly and it should be coordinated with the RATS development team, particularly if adding new API calls.

To add a new call, reserve the name and the API number for new call. This can be done in the %API hash in rats_internal.conf. After documenting the new API call in the relevant section of the RATS documentation, add the relevant handler functions to a new library or to the RatsLib.pm module. Lastly bind the new functions to the API call using the %COMMAND_HOOKS hash in rats_internal.conf. Note that if you are using a new library you should make sure that rats_internal.conf requires it. Of course any application using it should do the same as well.

 To modify existing behavior, you should write new handler functions and rebind them to the API call as described above.
 

 Writing new applications


 This section will be an example which should eliminate the relevant concepts. We will write a small application which will take a SSN as an argument, look up the person in the PDB, retrieve the primary username, and then contact the local daemons and retrieve a password entry. There is not much use for this in real life but it should illustrate the key points.

 First a bit of abstraction. The rats conf files contains a number of important bits of information. The program should include the conf files and the main RATS lib file (if you plan to use any of the predefined structures or functions). Further more, an important bit of abstraction is the wrapper code around the API and network code. To make life easier, RatsLib.pm offers a functions named send_remote_command(). This function takes API args as well as host and port information and performs a transaction. This involves composing the actual API call, performing encryption, contacting the remote host, performing the transaction and returning the response. For example to look up a password entry on a host we could write:

    $result = send_remote_command('GETPWENT', $username, $my_host, $some_port);
which could compose "35020:vgabriel:" and perform the transaction. $result may now hold an error message or perhaps something like:
    45020:vgabriel:x:10435:199:Vladimir Gabrielescu:/servants/u1/vgabriel:/bin/tcsh:


Note that  send_remote_commands takes the name of the API call as the first argument, followed by all the fields required for that API call, and finally the host and port that should be contacted for this request.

So lets begin. First we include the declarations, including strict and associated vars.  Note that RATS has two configuration files, rats.conf and rats_internal.conf.  This breakdown is there for the sysadmin's convenience. The application programmer need only to include rats_internal.conf which automatically also included rats.conf.
 

#!/usr/local/bin/perl 

use strict;

require "/usr/local/accounts/etc/rats_internal.conf";
require "/usr/local/accounts/lib/RatsLib.pm";
my ($ent, $do, $rats_master, $s_port, $rats_server, $c_port);
my ($ssn, $result, $code, @data, $username, $rcpid, $iid);
my ($first, $last);


Because we are going to use them a couple of times, we should make our code easier to read by aliasing a couple of important variables.
 
 

$do = \&RatsLib::send_remote_command;
$rats_master=$CONFIG::RATS_MASTER;
$s_port=$CONFIG::SERVER_PORT;
$rats_server=$CONFIG::MASTER_HOST;
$c_port=$CONFIG::CLIENT_PORT;
Now we read the SSN from the command line.
 
$ssn = $ARGV[0];
We now need to look up the user in the PDB. Because all we have is the SSN we use "GETRIFL_S" which is call returning user information based on the SSN. For various other ways to extract information, check out the API docs. Note that the return code should be checked. "RETRIFL_S" is the API name of a return containing data for our query. The response could be an error, such as 'NOTFOUND' or something else and we should make sure that we check it out.  It would be a bad idea to handle the API numbers directly as they may change. This is why send_remote_command() takes API names
as a first argument as opposed to the actual number.   In the same manner we should check the return code against the API table by name.  The %CONFIG::API  hash contains mappings of call names to call ID's and we can use it as illustrated bellow.
 
$result = &$do('GETRIFL_S',$ssn,$rats_master,$s_port);
($code,@data) = split (":", $result);
unless ($code == $CONFIG::API{'RETRIFL_S'}) {
    print "Failed to get info for ssn $ssn: @data\n";
    exit;
}
($rcpid, $iid, $first, $last) = @data;


Now that we have obtained the RCPID, we can try to retrieve the primary username. Note that the user may not have one so we should again check the return status. This time we use 'GETUSRINF' and it's associated return code.
 
 

$result = &$do('GETUSRINF',$rcpid,$rats_master,$s_port);
($code,@data) = split (":", $result);
if ($code == $CONFIG::API{'RETUSRINF'}) {
    $username = $data[0];
} else {
    print "User does not have a primary username.\n";
    exit;
}


You will note that both these calls where against the PDB through the main RATS server, which we got from the config file and refered to as $rats_master. We shall now contact the main RATS client daemon for this cluster (which was also retrieved from the config file) and query it about a password entry for the user above. The appropriate API call is 'GETPWENT'.

$result = &$do('GETPWENT', $username, $rats_server, $c_port);
($code, @data) = split(":", $result);
if ($code == $CONFIG::API{'R_GETPWENT'}) {
    $ent = join (":",@data);
    print "$ent\n";
} else {
    print "Could not retrieve password entry for $username: @data\n";
}
exit;
 

And we are done.

For other calls and their returns please check out the API docs. Hope this example helps. It is included bellow in one big lump.
 
 
#!/usr/local/bin/perl 

use strict;

require "/usr/local/accounts/etc/rats_internal.conf";
require "/usr/local/accounts/lib/RatsLib.pm";
my ($ent, $do, $rats_master, $s_port, $rats_server, $c_port);
my ($ssn, $result, $code, @data, $username, $rcpid, $iid);
my ($first, $last);

# remote call function
$do = \&RatsLib::send_remote_command;
$rats_master=$CONFIG::RATS_MASTER;
$s_port=$CONFIG::SERVER_PORT;
$rats_server=$CONFIG::MASTER_HOST;
$c_port=$CONFIG::CLIENT_PORT;


$ssn = $ARGV[0];
        


$result = &$do('GETRIFL_S',$ssn,$rats_master,$s_port);
($code,@data) = split (":", $result);
unless ($code == $CONFIG::API{'RETRIFL_S'}) {
    print "Failed to get info for ssn $ssn: @data\n";
    exit;
}
($rcpid, $iid, $first, $last) = @data;



$result = &$do('GETUSRINF',$rcpid,$rats_master,$s_port);
($code,@data) = split (":", $result);
if ($code == $CONFIG::API{'RETUSRINF'}) {
    $username = $data[0];
} else {
    print "User does not have a primary username.\n";
    exit;
}


$result = &$do('GETPWENT', $username, $rats_server, $c_port);
($code, @data) = split(":", $result);

if ($code == $CONFIG::API{'R_GETPWENT'}) {
    $ent = join (":",@data);
    print "$ent\n";
} else {
    print "Could not retrieve password entry for $username: @data\n";
}

exit;