sk - Man Page

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)

Referenced By

aclient(1).