sk(3)

NAME
sk (done) P Client/Server
                    (Rev. 03-May-1996)

SYNTAX

=>  Server startup:
    int sock = sk_start(char *service, char * mode);
    int sock = sk_astart(char *service, char * mode, char *authorisation_file);
    int plug = sk_accept(int sock, int queue_slots);
    int status = sk_ack(int plug);
    char *caller = sk_caller(int plug);
    sk_urgent(int plug);

=>  Client Connection:
    int plug = sk_open(char *host, char * service);
    int plug = sk_connect(char *host, char * service, char *username, char *password);
    int stat = sk_close(int plug);

=>  Client/Server Dialogue:
    int bytes = sk_read(int plug, char *buf, int len);
    int bytes = sk_get(int plug, char *buf, int len);
    int bytes = sk_gets(int plug, char *buf, int len);
    long value = sk_getl(int plug);
    int bytes = sk_write(int plug, char *buf, int len);
    int bytes = sk_put(int plug, char *buf);
    int bytes = sk_puts(int plug, char *buf);
    int status = sk_putl(int plug, long value);

=>  Data Exchange on Server Side
    int bytes = sk_toclient(int plug, int file_to_send );
    int bytes = sk_fromclient(int plug, int file_to_receive);
    int status = sk_errsend(int plug, char * message);
    int status = sk_perrsend(int plug, char * message);

=>  Data Exchange on Client Side
    int bytes = sk_obeyserver(int plug, int (* digest_routine)(), int (*more_routine)());
    int bytes = sk_fromserver(int plug, int file_to_receive, file_to_send);
    char *message = sk_error();
    sk_kill(int plug, int signo);

=>  Logging & Debugging:
    FILE *old_logfile = sk_setlog(FILE *logfile);
    FILE *old_debfile = sk_iolog(FILE *debfile);
    int lev = sk_log(int level, char *format, ... );

=>  Server Utilities:
    int status = sk_umatch(char *remote_id, char * template);

=>  Socket Configuration:
    int bytes = sk_setb(int socket_size)

DESCRIPTION
This set of functions, stored in the libraries

/usr/local/lib/libsk.a for the client routines, and in

/usr/local/lib/libskserv.a (server routines),

allow to build client/server dialogues using socket(2) facilities in stream mode. A client connected to Internet may reach a server connected to Internet independently of the machine architectures. The client/server model is a way of RPC (Remote Process Call) implementation.

The service is a character string representing either a number in the range 1024-2047 or a symbolic service name appearing in the file /etc/services(5); it pertains to which kind of service we want to connect to.

INCLUDE FILES
Function declarations are gathered in the two files:

=>  /usr/local/include/sk.h for client routines

=>  /usr/local/include/skserv.h for server routines

SERVER STARTUP
The server can be launched, either as a daemon by inetd (8), or as a standalone program in background mode. The choice between these two modes is made via the the mode argument of the sk_start routine: when it contains the character d, the server is started via inetd(8), which requires a specific line in the /etc/inetd.conf(5) file. The possible characters in the mode argument are:

=>  d to start in daemon mode

=>  l to log the server events in the syslog(8)

=>  v to add debugging messages in the log

=>  vv (two v's) for very verbose (debugging) log.

sk_start returns -1 in case of failure, and prints an error message in the log; otherwise sk_start returns a non-negative socket number.

sk_astart is a version which uses an authorisation file which can be the /etc/passwd(5) file, or a file similar to it: the client has to provide a username and a password for identification. This client normally uses the sk_connect routine to identify himself. The authorisation_file parameter may be

=>  NULL (i.e. (char *)0); in this case no user authentification is performed

=>  void string (i.e. ""); in this case the standard /etc/passwd file is used;

=>  a valid file name which is then used instead of the /etc/passwd file. See below the description of the authorisation file,

Once launched, the server will normally listen to the created socket, waiting for a client connection. The sk_accept routine provides this functionnality, i.e. a successful return from sk_accept means that a client asked for a dialogue with the server. The queue_slots argument specifies the size of the queue of clients waiting for a connection with the server: when the queue is full, the unsuccessful client will get an error message.

After a successful sk_accept, the server has to identify himself, and ask for client's identification. This is achieved with the sk_ack function.

In a daemon mode, the function sk_accept is called by the parent process, which forks ; the sk_ack function is normally called by the child process, while the parent process waits for another connection. See the example in the Example section below.

Once a client has started a dialogue with the server (via sk_accept and sk_ack), the client's name and address can then be retrieved with sk_caller. The syntax of the character string returned by sk_caller is

Remote_Username[!]@InterNet_Number(Host_Name): Provided_Username

where the ! indicates a user with root privileges.

A NULL string ((char *)0) is returned by sk_caller in case of a bad plug number or in case of error.

The sk_urgent routine allows the server to receive signals sent by the connected client; see below the Signal Communication section.

CLIENT CONNECTION
A program can start a dialogue with a server using the sk_open (no identification) or the sk_connect function. service, as for the server, is either a numeric string representing a number in the range 1024-2047, or is a name normally existing in the /etc/services file. The username and password provided by sk_connect, as well as the client's node and username, are checked against what is known in the authorisation_file (see below)

A failure to reach the server specified by its hostname and its service name or number returns a negative value, and an error message is printed or logged (see the sk_setlog function); otherwise sk_open or sk_connect return a non-negative number to be used as the first argument of dialogue routines.

In case of failure, the returned codes mean the following:

=>  -1: the connection to the Server can't be established due to network problems, like unknown host or service;

=>  -2: the Server has been reached, but refused to pursue for invalid username/password or unauthorized machine.

CLIENT/SERVER DIALOGUE
When the connection between the client and the server is established, both can read or write on the socket. Which of the client or server has to read or write on the socket is defined only by the calling programs; the dialogue has therefore to be carefully designed to avoid endless waits on an opened socket. Other functions described in the next section use a very simple protocol for exchange of data.

Four functions are provided available for reading on the socket, and four other are provided for writing on the socket. All these 8 functions return -1 in case of error.

1.  int bytes = sk_read(int plug, char *buf, int len)
    is the simplest reading interface, and reads up to len bytes. The returned number of bytes is generally smaller than len: no attempt is made to fill buf.

2.  int bytes = sk_get(int plug, char *buf, int len)
    fills buf from what comes on the socket. The returned number of bytes is therefore identical to len, unless an error occured or the partner closed the connection.

3.  int bytes = sk_gets(int plug, char *buf, int len)
    fills buf up to len bytes, or until a newline is found. The returned length includes the newline. Notice however that buf is not terminated by the NULL character.

4.  long value = sk_getl(int plug)
    reads a long integer (4-byte integer) on the socket (normally issued by the partner using sk_putl). Byte swapping is performed if the local machine architecture differs from the network one.

5.  int bytes = sk_write(int plug, char *buf, int len)
    writes len bytes onto the socket. Its returned number of bytes is identical to len, unless an error occured or the partner closed the connection.

6.  int bytes = sk_put(int plug, char *buf)
    writes a null-terminated string on the socket.

7.  int bytes = sk_puts(int plug, char *buf)
    writes a null-terminated string as a line on the socket, i.e. a newline is appended to the string before being sent to the partner.

8.  int status = sk_putl(int plug, long value)
    sends a 4-byte long integer to the partner; byte swapping is performed if necessary.

Note that plug is a file handle, and it is therefore possible to use standard i/o routines after the association of a FILE structure generated by the fdopen(2) routine.

DATA EXCHANGE ON SERVER SIDE
Four control characters are used for the data exchange functions. When the server sends to the client the character:

=>  D . It means:
    ``I've finished to talk. It's now up to you''

=>  B . It means:
    ``I'll send Counted Buffers, i.e. data prefixed with its length expressed as a 4-byte integer. I'll continue to send Counted Buffers until the prefix specifies a zero length''. This convention is used by sk_toclient.

=>  C . It means:
    ``I'll send a single counted buffer, i.e. data prefixed with its 4-byte length. It contains normally an error message.

=>  F . It means:
    ``Please send me a Counted Buffer''. This convention is used by sk_fromclient.

The detailed functionnalities are:

=>  int bytes = sk_toclient(int plug, int file_to_send )
    is used by the Server to send a file to the connected client, using the B convention:

    1.  Server sends B

    2.  Client acknowledges with F

    3.  Server sends counted buffers (buffers preceded by their 4-byte length). The last counted buffer has a length of zero.

Upon return from sk_toclient, the Server has still to send a D to the client to tell him that it's up to him to talk.
file_to_send must have been opened in read mode by open (2).

=>  int bytes = sk_fromclient(int plug, int file_to_receive)
    is used by the Server to get a data set from the connected client, using the F convention:

    1.  Server sends F

    2.  Client returns a counted buffer (4-byte integer expressing the buffer length, followed by the actual buffer)

    3.  Server asks for the next buffer with F if the length of the received counted buffer is not zero.

sk_fromclient stops just after the client sent a zero length data buffer; Upon return from sk_fromclient, the Server has still to send a D to the client to tell him that it's up to him to talk.
file_to_receive must have been opened in write mode by open(2).

=>  int status = sk_errsend(int plug, char * message)
    allows to send a single counted buffer to the client, which generally represents an error message. If the client uses the sk_obeyserver or the sk_fromserver routine, the server has to send a D to the client to tell him to return from the sk_obeyserver function.

=>  int status = sk_perrsend(int plug, char * message)
    is similar to sk_errsend, but message is followed by the system error message as in perror(2).

DATA EXCHANGE ON CLIENT SIDE

=>  int bytes = sk_fromserver(int plug, int file_to_receive, file_to_send)
    is used by the Client to follow the above conventions: it reads what comes over the plug socket and writes it onto file_to_receive which must have been opened in write mode by open(2); when the Server asks to send a Counted Buffer, it reads it from file_to_send.
    The return from sk_fromserver therfore occurs

    =>  either when the server sends a D in non-buffered mode;

    =>  or when an error occurs.

=>  int bytes = sk_obeyserver(int plug, int (* digest_routine)(), int (*more_routine)())
    is similar to sk_fromserver, but routines are used instead of files:

    =>  digest_routine(char *buf, int length) collects what's sent by the Server

    =>  more_routine(char *buf, int length) is called when the Server asks for more data.

Both digest and more routines must return the number of bytes processed, 0 for end-of-file, and -1 for error . As for sk_fromserver, the client has normally to send something to the Server, since he got the D telling he has to talk.

=>  char *message = sk_error()
    returns the last encountered error message. This function is normally to use when one of the sk routines returns -1.
    Note that errors are also written to stderr by default; another file - or no file at all - may be chosen as a logfile.

The sk_kill routine allows the client to send signals to the sever; see below the Signal Communication section.

LOGGING & DEBUGGING
The server normally logs the occuring events, either to a log file (which can be the stdout terminal), or in the system log. The usage of the syslog facility (in file /var/log/syslog) is recommended when the server is launched in daemon mode by inetd(8).

At any time, the server can log details in the syslog with the sk_log function, which will direct the message to the currently opened logfile or to the syslog.

The first argument of the sk_log(int level, char * format, ...) function is a number defined in the syslog.h file, or -1 to close the log file; this level argument is returned in case of success. The other arguments of the sk_log are similar to those of the printf(3) function.

The sk_setlog function allows to switch the log file at any time, and returns the previously active logfile. Notice that the syslog is indicated by a NULL value: to use the syslog as a log file, use the call

old_logfile = sk_setlog((FILE *)0))

A very verbose debugging is activated with the sk_iolog function: whatever is read or written on the socket is printed on the supplied log file. As sk_setlog, sk_iolog returns the old logfile. A NULL logfile argument asks to stop this debugging feature.

SIGNAL COMMUNICATION
The client can send a signal to the server via the sk_kill(int plug, int signo) routine; signo must be a valid signal (see signal(3)). However, the signals can be received by the server only after the server has specified that he would accept signal interruptions. The acceptance of signal interrupts is specified once with the call to sk_urgent(int plug).

SERVER UTILITIES
char *sk_caller(plug) allows to retrieve a client identification; it is described above.

int sk_umatch(char *remote_id, char *template ) is a routine which returns 0 if remote_identification as returned by sk_caller does not match the template of authorized Remote_Username@Internet separated by commas; the wild chars * and ? can be used in this field. Remember also the ending ! in the username which indicates root privileges. Some examples of template:

=>  root!@130.79.*.* matches the root user from any of the machines which Internet number starts with 130.79

=>  *@130.79.128.5,root!@130.79.*.* matches anybody having an account on the 130.79.128.5 machine, or the root user on other machines with Internet number starting with 130.79

AUTHORISATION FILE
An Authorisation File can be used to restrict access to authorized users via a Username and a Password. The Authorisation File can be identical to the /etc/passwd(5) file when the third parameter of the sk_start routine is a blank string ""

The three fields which are used are:

1.  the Username the client has to provide (default is guest)

2.  the Password (encrypted)

3.  the 5th field (also called gecos which may contain a list of allowed Remote_Username@Internet_Number, templates separated by commas or blanks; the wild chars * and ? can be used in this field

The password in the /etc/passwd file can be modified with the passwd(1) utility, as well as the gecos field with the -f option. An alternative to the passwd -f is the chfn (1) utility.

SOCKET CONFIGURATION
The size of blocks transferred onto the network can be changed with the sk_setb function. The standard size is generally 8 blocks (4K). sk_setb returns the current configuration.

A negative or null value of socket_size does not modify the size of socket blocks; sk_setb(0) can therefore be used to know the current socket size.

EXAMPLE OF A SERVER
The following shows the basic writing of a server which creates a child via fork(2) who has to deal with the client. The server is assumed to receive the name of a file and to send its contents to the Client.

#include <skserv.h>
#include <signal.h>
#include <sys/wait.h>
void on_death() /* Read Zombie status */
{ while(wait3(NULL, WNOHANG, NULL) > 0) ; }
static int theplug;
void on_intr() /* When the Serveur in Interrupted by the Client */
{ sk_puts(theplug, "Bye-Bye"); exit(1); }
        /* A routine to send a file to the Client */
display_file(plug, filename) int plug; char *filename;
{ int file;
    file = open(filename, 0);
    if (file < 0) perrsend(filename);
    else {
        sk_toclient(plug, file);
        close(file);
    }
}
main()
{
  int sock, plug;
  char buffer[133];
    sock = sk_start("service", "v"); /* Verbose option */
    if (sock < 0) exit(1);
    signal(SIGCHLD, on_death); /* Handler for Zombies */
    while(1) { /* Loop on incoming connections */
        plug = sk_accept(sock, 1);
        if (plug < 0) exit(1);
        if (fork()) { /* Father Here. He doesn't need plug */
            close(plug);
            continue;
        }
        /* ===Child Here. sock is only for new connections;
              therefore close it */
        close(sock);
    sk_ack(plug);    /* Acknowledge the Client    */
        sk_log("%s just called\n", sk_caller(plug));
        sk_urgent(plug);        /* Allow client to send Signal     */
        signal(SIGINT, on_intr);
        theplug = plug;         /* To communicate with on_intr     */
        sk_gets(plug, buffer, sizeof(buffer)); /* Get question */
        display_file(plug, buffer);
        sk_put(plug, "\04"); /* Tell the Client: I've finished to talk */
        exit(0);
    }
}

The corresponding client can be aclient(1), or the following code if the program is assumed to have three parameters which are the host, the service, and the filename to list.
main(argc, argv) int argc; char *argv[];
{
  int plug;
    plug = sk_open(argv[1], argv[2]);
    if (plug < 0 ) { perror(argv[1]); exit(1); }
    if (sk_puts(plug, argv[3]) < 0) exit(1);
    sk_obeyserver(plug, 1, 0); /* Digest = file#1 (stdout) */
}

SEE ALSO
aclient(1) aserver(1) chfn(1) socket(2) open(2) fdopen(2) fork(2) perror(2) printf(3) passwd(1) passwd(5) services(5) inetd(8) signal(3) syslog(8) wait3(2)

QUESTIONS & PROBLEMES
a Fox (francois@simbad.u-strasbg.fr)