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.
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 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.
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.
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
$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;
#!/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;