Compaq TCP/IP Services for OpenVMS
ONC RPC Programming


Previous Contents Index

3.6 Authentication of RPC Calls

In the examples presented so far, the client never identified itself to the server, nor did the server require it from the client. Every RPC call is authenticated by the RPC package on the server, and similarly, the RPC client package generates and sends authentication parameters. Just as different transports (TCP/IP or UDP/IP) can be used when creating RPC clients and servers, different forms of authentication can be associated with RPC clients. The default authentication type is none . The authentication subsystem of the RPC package, with its ability to create and send authentication parameters, can support commercially available authentication software.

This manual describes only one type of authentication---authentication through the operating system. The following sections describe client and server authentication through the operating system.

3.6.1 The Client Side

Assume that a client creates the following new RPC client handle:


     clnt = clntudp_create(address, prognum, versnum, wait, sockp) 

The client handle includes a field describing the associated authentication handle:


     clnt->cl_auth = authnone_create(); 

The RPC client can choose to use authentication that is native to the operating system by setting clnt->cl_auth after creating the RPC client handle:


     clnt->cl_auth = authunix_create_default(); 

This causes each RPC call associated with clnt to carry with it the following authentication credentials structure:


     /* 
      * credentials native to the operating system 
      */ 
     struct authunix_parms { 
          u_long  aup_time;       /* credentials creation time  */ 
          char    *aup_machname;  /* host name where client is  */ 
          int     aup_uid;        /* client's OpenVMS uid       */ 
          int     aup_gid;        /* client's current group id  */ 
          u_int   aup_len;        /* element length of aup_gids */ 
                                  /* (set to 0 on OpenVMS)      */ 
          int     *aup_gids;      /* array of groups user is in */ 
                                  /* (set to NULL on OpenVMS)   */ 
     }; 

In this example, the fields are set by authunix_create_default by invoking the appropriate system calls. Because the program created this new style of authentication, the program is responsible for destroying it (to save memory) with the following:


     auth_destroy(clnt->cl_auth); 

3.6.2 The Server Side

It is difficult for service implementors to handle authentication because the RPC package passes to the service dispatch routine a request that has an arbitrary authentication style associated with it. Consider the fields of a request handle passed to a service dispatch routine:


     /* 
      * An RPC Service request 
      */ 
     struct svc_req { 
          u_long  rq_prog;            /* service program number */ 
          u_long  rq_vers;            /* service protocol vers num */ 
          u_long  rq_proc;            /* desired procedure number */ 
          struct opaque_auth rq_cred; /* raw credentials from wire */ 
          caddr_t rq_clntcred;        /* credentials (read only) */ 
     }; 

The rq_cred is mostly opaque except for one field, the style of authentication credentials:


     /* 
      * Authentication info.  Mostly opaque to the programmer. 
      */ 
     struct opaque_auth { 
          enum_t    oa_flavor;      /* style of credentials */ 
          caddr_t   oa_base;        /* address of more auth stuff */ 
          u_int     oa_length;      /* not to exceed MAX_AUTH_BYTES */ 
     }; 

The RPC package guarantees the following to the service dispatch routine:

The rq_clntcred field also could be cast to a pointer to an authunix_parms structure. If rq_clntcred is NULL , the service implementor can inspect the other (opaque) fields of rq_cred to determine whether the service knows about a new type of authentication that is unknown to the RPC package.

Example 3-9 extends the previous remote user's service (see Example 3-3) so it computes results for all users except UID 16.

Example 3-9 Authentication on Server Side

nuser(rqstp, transp) 
     struct svc_req *rqstp; 
     SVCXPRT *transp; 
{ 
     struct authunix_parms *unix_cred; 
     int uid; 
     unsigned long nusers; 
 
     /* 
      * we don't care about authentication for null proc 
      */ 
     if (rqstp->rq_proc == NULLPROC) { 
          if (!svc_sendreply(transp, xdr_void, 0)) 
               fprintf(stderr, "can't reply to RPC call\n"); 
          return; 
     } 
     /* 
      * now get the uid 
      */ 
     switch (rqstp->rq_cred.oa_flavor) { 
     case AUTH_UNIX: 
          unix_cred = (struct authunix_parms *)rqstp->rq_clntcred; 
          uid = unix_cred->aup_uid; 
          break; 
 
     case AUTH_NULL: 
 
     default:        /* return weak authentication error */ 
          svcerr_weakauth(transp); 
          return; 
     } 
     switch (rqstp->rq_proc) { 
     case RUSERSPROC_NUM: 
          /* 
           * make sure client is allowed to call this proc 
           */ 
          if (uid == 16) { 
               svcerr_systemerr(transp); 
               return; 
          } 
          /* 
           * Code here to compute the number of users 
           * and assign it to the variable nusers 
           */ 
          if (!svc_sendreply(transp, xdr_u_long, &nusers)) 
               fprintf(stderr, "can't reply to RPC call\n"); 
          return; 
 
     default: 
          svcerr_noproc(transp); 
          return; 
     } 
} 

As in this example, it is not customary to check the authentication parameters associated with NULLPROC (procedure 0). Also, if the authentication parameter type is not suitable for your service, have your program call svcerr_weakauth .

The service protocol itself returns status for access denied; in Example 3-9, the protocol does not do this. Instead, it makes a call to the service primitive, svcerr_systemerr . RPC deals only with authentication and not with the access control of an individual service. The services themselves must implement their own access control policies and must reflect these policies as return statuses in their protocols.

3.7 Using the Internet Service Daemon (INETd)

You can start an RPC server from INETd. The only difference from the usual code is that it is best to have the service creation routine called in the following form because INETd passes a socket as file descriptor 0 :


     transp = svcudp_create(0);     /* For UDP */ 
     transp = svctcp_create(0,0,0); /* For listener TCP sockets */ 
     transp = svcfd_create(0,0,0);  /* For connected TCP sockets */ 

Also, call svc_register as follows, with the last parameter flag set to 0, because the program is already registered with the Portmapper by INETd:


     svc_register(transp, PROGNUM, VERSNUM, service, 0); 

If you want to exit from the server process and return control to INETd, you must do so explicitly, because svc_run never returns.

To show all the RPC service entries in the services database, use the following command:


TCPIP> SHOW SERVICES/RPC/PERMANENT 
 
                        RPC                   Protocol Versions 
Service             Program Number             Lowest / Highest 
 
MEL                       101010                 1         10 
TORME                      20202                 1          2 
   .
   .
   .
TCPIP> 
 

To show detailed information about a single RPC service entry in the services database, use the following command:


TCPIP> SHOW SERVICES/FULL/PERMANENT MEL 
 
Service: MEL 
 
Port:             1111     Protocol:  UDP             Address:  0.0.0.0 
Inactivity:          5     User_name: GEORGE          Process:  MEL 
Limit:               1 
 
File:         NLA0: 
Flags:        Listen 
 
Socket Opts:  Rcheck Scheck 
 Receive:            0     Send:               0 
 
Log Opts:     None 
 File:        not defined 
 
RPC Opts 
 Program number:      101010  Lowest:      1   Highest:     10 
 
Security 
 Reject msg:  not defined 
 Accept host: 0.0.0.0 
 Accept netw: 0.0.0.0 
TCPIP> 

3.8 Additional Examples

The following sections present additional examples for server and client sides, TCP, and callback procedures.

3.8.1 Program Versions on the Server Side

By convention, the first version of program PROG is designated as PROGVERS_ORIG and the most recent version is PROGVERS . Suppose there is a new version of the user program that returns an unsigned short result rather than a long result. If you name this version RUSERSVERS_SHORT , then a server that wants to support both versions would register both. It is not necessary to create another server handle for the new version, as shown in this segment of code:


     if (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG, 
       nuser, IPPROTO_TCP)) { 
          fprintf(stderr, "can't register RUSER service\n"); 
          exit(1); 
     } 
     if (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, 
       nuser, IPPROTO_TCP)) { 
          fprintf(stderr, "can't register new service\n"); 
          exit(1); 
     } 

You can handle both versions with the same C procedure, as in Example 3-10.

Example 3-10 C Procedure That Returns Two Different Data Types

nuser(rqstp, transp) 
     struct svc_req *rqstp; 
     SVCXPRT *transp; 
{ 
     unsigned long nusers; 
     unsigned short nusers2; 
 
     switch (rqstp->rq_proc) { 
     case NULLPROC: 
          if (!svc_sendreply(transp, xdr_void, 0)) { 
               fprintf(stderr, "can't reply to RPC call\n"); 
               return; 
          } 
          return; 
     case RUSERSPROC_NUM: 
          /* 
           * Code here to compute the number of users 
           * and assign it to the variable, nusers 
           */ 
          nusers2 = nusers; 
          switch (rqstp->rq_vers) { 
          case RUSERSVERS_ORIG: 
               if (!svc_sendreply(transp, xdr_u_long, &nusers)) { 
                    fprintf(stderr,"can't reply to RPC call\n"); 
               } 
               break; 
          case RUSERSVERS_SHORT: 
               if (!svc_sendreply(transp, xdr_u_short, &nusers2)) { 
                    fprintf(stderr,"can't reply to RPC call\n"); 
               } 
               break; 
          } 
     default: 
          svcerr_noproc(transp); 
          return; 
     } 
} 

3.8.2 Program Versions on the Client Side

The network can have different versions of an RPC server. For example, one server might run RUSERSVERS_ORIG , and another might run RUSERSVERS_SHORT .

If the version of the server running does not match the version number in the client creation routines, then clnt_call fails with an RPC_PROGVERSMISMATCH error. You can determine the version numbers supported by the server and then create a client handle with an appropriate version number. To do this, use clnt_create_vers (refer to Chapter 5 for more information) or the routine shown in Example 3-11.

  1. The program begins by creating the client handle with the clnt_create routine.
  2. Next, the clnt_call routine attempts to call the remote program. Because of the previous clnt_create call, the program version requested is RUSERVERS_SHORT . If the clnt_call routine is successful, the version was correct.
  3. If the clnt_call attempt failed, then the program checks the failure reason. If it is RPC_PROGVERSMISMATCH , the program goes on to find the versions supported.
  4. In this step, the program parses the error status and retrieves the highest and lowest versions supported by the server. The program then checks whether the version RUSERSVERS_SHORT is in the supported range.
  5. If the RUSERSVERS_SHORT version is supported, the program destroys the old client handle using the clnt_destroy routine. It then creates a new handle using the RUSERSVERS_SHORT version.
  6. Finally, the program uses the new client handle to make a call to the server using the RUSERSVERS_SHORT version.

Example 3-11 Determining Server-Supported Versions and Creating Associated Client Handles

/* 
 * A sample client to sense server versions 
 */ 
#include <rpc/rpc.h> 
#include <stdio.h> 
#include "rusers.h" 
 
main(argc,argv) 
     int argc; 
     char **argv; 
{ 
     struct rpc_err rpcerr; 
     struct timeval to; 
     CLIENT *clnt; 
     enum clnt_stat status; 
     int maxvers, minvers; 
     int exit(); 
     u_short num_s; 
     u_int num_l; 
     char *host; 
 
     host = argv[1]; 
 
     clnt = clnt_create(host, RUSERSPROG, RUSERSVERS_SHORT, "udp"); (1)
 
     if (clnt == NULL) { 
          clnt_pcreateerror("clnt"); 
          exit(-1); 
     } 
 
     to.tv_sec = 10; /* set the time outs */ 
     to.tv_usec = 0; 
     status = clnt_call(clnt, RUSERSPROC_NUM, (2)
       xdr_void, NULL, xdr_u_short, &num_s, to); 
 
     if (status == RPC_SUCCESS) { 
          /* We found the latest version number */ 
          clnt_destroy(clnt); 
          printf("num = %d\n",num_s); 
          exit(0); 
     } 
 
     if (status != RPC_PROGVERSMISMATCH) { (3)
          /* Some other error */ 
          clnt_perror(clnt, "rusers"); 
          exit(-1); 
     } 
 
     clnt_geterr(clnt, &rpcerr); (4)
     maxvers = rpcerr.re_vers.high; /*highest version supported */ 
     minvers = rpcerr.re_vers.low;  /*lowest version supported */ 
 
     if (RUSERSVERS_ORIG < minvers || 
          RUSERS_ORIG > maxvers) { 
          /* doesn't meet minimum standards */ 
          clnt_perror(clnt, "version mismatch"); 
          exit(-1); 
     } 
 
     /* This version not supported */ 
     clnt_destroy(clnt);     /* destroy the earlier handle */ (5)
     clnt = clnt_create(host, RUSERSPROG, 
       RUSERSVERS_ORIG, "udp"); /* try different version */ 
 
     if (clnt == NULL) { 
          clnt_pcreateerror("clnt"); 
          exit(-1); 
     } 
 
     status = clnt_call(clnt, RUSERSPROCNUM, (6)
       xdr_void, NULL, xdr_u_long, &num_l, to); 
 
     if (status == RPC_SUCCESS) { 
          /* We found the latest version number */ 
          printf("num = %d\n", num_l); 
     } else { 
          clnt_perror(clnt, "rusers"); 
          exit(-1); 
     } 
} 
 

3.8.3 Using the TCP Transport

Examples 3_12 , 3_13 , and 3-14 work like the remote file copy command RCP. The initiator of the RPC call, snd , takes its standard input and sends it to the server rcv , which prints it on standard output. The RPC call uses TCP. The example also shows how an XDR procedure behaves differently on serialization than on deserialization.

Example 3-12 RPC Example That Uses TCP Protocol---XDR Routine

/* 
 * The XDR routine: 
 *              on decode, read from wire, write onto fp 
 *              on encode, read from fp, write onto wire 
 */ 
 
#include <stdio.h> 
#include <rpc/rpc.h> 
 
xdr_rcp(xdrs, fp) 
     XDR *xdrs; 
     FILE *fp; 
{ 
     unsigned long size; 
     char buf[BUFSIZ], *p; 
 
     if (xdrs->x_op == XDR_FREE)/* nothing to free */ 
          return 1; 
     while (1) { 
          if (xdrs->x_op == XDR_ENCODE) { 
               if ((size = fread(buf, sizeof(char), BUFSIZ, 
                    fp)) == 0 && ferror(fp)) { 
                    fprintf(stderr, "can't fread\n"); 
                    return (1); 
               } 
          } 
          p = buf; 
          if (!xdr_bytes(xdrs, &p, &size, BUFSIZ)) 
               return (0); 
          if (size == 0) 
               return (1); 
          if (xdrs->x_op == XDR_DECODE) { 
               if (fwrite(buf, sizeof(char), size, 
                    fp) != size) { 
                    fprintf(stderr, "can't fwrite\n"); 
                    return (1); 
               } 
          } 
     } 
} 

Example 3-13 RPC Example That Uses TCP Protocol---Client

/* 
 * snd.c - the sender routines 
 */ 
#include <stdio.h> 
#include <netdb.h> 
#include <rpc/rpc.h> 
#include <sys/socket.h> 
#include "rcp.h"        /* for prog, vers definitions */ 
 
main(argc, argv) 
     int argc; 
     char **argv; 
{ 
     int xdr_rcp(); 
     int err; 
     int exit(); 
     int callrpctcp(); 
 
     if (argc < 2) { 
          fprintf(stderr, "usage: %s servername\n", argv[0]); 
          exit(-1); 
     } 
     if ((err = callrpctcp(argv[1], RCPPROG, RCPPROC, 
       RCPVERS, xdr_rcp, stdin, xdr_void, 0) > 0)) { 
          clnt_perrno(err); 
          fprintf(stderr, "can't make RPC call\n"); 
          exit(1); 
     } 
     exit(0); 
} 
 
int 
callrpctcp(host, prognum, procnum, versnum, 
  inproc, in, outproc, out) 
     char *host, *in, *out; 
     xdrproc_t inproc, outproc; 
{ 
     struct sockaddr_in server_addr; 
     int socket = RPC_ANYSOCK; 
     enum clnt_stat clnt_stat; 
     struct hostent *hp; 
     register CLIENT *client; 
     struct timeval total_timeout; 
     void bcopy(); 
 
     if ((hp = gethostbyname(host)) == NULL) { 
          fprintf(stderr, "can't get addr for '%s'\n", host); 
          return (-1); 
     } 
     bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, 
       hp->h_length); 
     server_addr.sin_family = AF_INET; 
     server_addr.sin_port =  0; 
     if ((client = clnttcp_create(&server_addr, prognum, 
       versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) { 
          clnt_pcreateerror("rpctcp_create"); 
          return (-1); 
     } 
     total_timeout.tv_sec = 20; 
     total_timeout.tv_usec = 0; 
     clnt_stat = clnt_call(client, procnum, 
       inproc, in, outproc, out, total_timeout); 
     clnt_destroy(client); 
     return ((int)clnt_stat); 
} 

Example 3-14 RPC Example That Uses TCP Protocol--- Server

/* 
 * rcv.c - the receiving routines 
 */ 
#include <stdio.h> 
#include <rpc/rpc.h> 
#include <rpc/pmap_clnt.h> 
#include "rcp.h"        /* for prog, vers definitions */ 
 
main() 
{ 
     register SVCXPRT *transp; 
     int rcp_service(), exit(); 
 
     if ((transp = svctcp_create(RPC_ANYSOCK, 
       BUFSIZ, BUFSIZ)) == NULL) { 
          fprintf(stderr,"svctcp_create: error\n"); 
          exit(1); 
     } 
     pmap_unset(RCPPROG, RCPVERS); 
     if (!svc_register(transp, RCPPROG, 
       RCPVERS, rcp_service, IPPROTO_TCP)) { 
          fprintf(stderr, "svc_register: error\n"); 
          exit(1); 
     } 
     svc_run();  /* never returns */ 
     fprintf(stderr, "svc_run should never return\n"); 
} 
 
int 
rcp_service(rqstp, transp) 
     register struct svc_req *rqstp; 
     register SVCXPRT *transp; 
{ 
     int xdr_rcp(); 
 
     switch (rqstp->rq_proc) { 
     case NULLPROC: 
          if (svc_sendreply(transp, xdr_void, 0) == 0) 
               fprintf(stderr, "err: rcp_service"); 
          return; 
     case RCPPROC: 
          if (!svc_getargs(transp, xdr_rcp, stdout)) { 
               svcerr_decode(transp); 
               return; 
          } 
          if (!svc_sendreply(transp, xdr_void, 0)) 
               fprintf(stderr, "can't reply\n"); 
          return; 
     default: 
          svcerr_noproc(transp); 
          return; 
     } 
} 

3.8.4 Callback Procedures

It is sometimes useful to have a server become a client and to make an RPC call back to the process that is its client. An example of this is remote debugging, where the client is a window-system program and the server is a debugger running on the remote system. Mostly, the user clicks a mouse button at the debugging window (converting this to a debugger command), and then makes an RPC call to the server (where the debugger is actually running), telling it to execute that command. However, when the debugger reaches a breakpoint, the roles are reversed, and the debugger wants to make an RPC call to the window program so it can tell the user that a breakpoint has been reached.

Callbacks are also useful when the client cannot block (that is, wait) to hear back from the server (possibly because of excessive processing in serving the request). In such cases, the server could acknowledge the request and use a callback to reply.

To do an RPC callback, you need a program number on which to make the RPC call. The program number is generated dynamically, so it must be in the transient range 0x40000000 to 0c5fffffff. The sample routine gettransient returns a valid program number in the transient range and registers it with the Portmapper. It only communicates with the Portmapper running on the same system as the gettransient routine itself.

The call to pmap_set is a test-and-set operation because it indivisibly tests whether a program number has been registered; if not, it is reserved. The following example shows the sample gettransient routine:


#include <stdio.h> 
#include <rpc/rpc.h> 
 
gettransient(proto, vers, portnum) 
     int proto; 
     u_long vers; 
     u_short portnum; 
{ 
     static u_long prognum = 0x40000000; 
 
     while (!pmap_set(prognum++, vers, proto, portnum)) 
          continue; 
     return (prognum - 1); 
} 

Note that the call to ntohs for portnum is unnecessary because it was already passed in host byte order (as pmap_set expects).

The following list describes how the client/server programs in Example 3_15 and Example 3-16 use the gettransient routine:

In Example 3_15 and Example 3-16, both the client and the server are on the same system; otherwise, host name handling would be different.

Example 3-15 Client Usage of the gettransient Routine

/* 
* client 
*/ 
#include <stdio.h> 
#include <rpc/rpc.h> 
#include "example.h" 
 
int callback(); 
 
main() 
{ 
     int tmp_prog; 
     char hostname[256]; 
     SVCXPRT *xprt; 
     int stat; 
     int callback(), gettransient(); 
     int exit(); 
 
     gethostname(hostname, sizeof(hostname)); 
     if ((xprt = svcudp_create(RPC_ANYSOCK)) == NULL) { 
          fprintf(stderr, "rpc_server: svcudp_create\n"); 
          exit(1); 
     } 
     if ((tmp_prog = gettransient(IPPROTO_UDP, 1, 
          xprt->xp_port)) == 0) { 
          fprintf(stderr,"Client: failed to get transient number\n"); 
          exit(1); 
     } 
     fprintf(stderr, "Client: got program number %08x\n", tmp_prog); 
 
     /* protocol is 0 - gettransient does registering */ 
 
     (void)svc_register(xprt, tmp_prog, 1, callback, 0); 
     stat = callrpc(hostname, EXAMPLEPROG, EXAMPLEVERS, 
       EXAMPLEPROC_CALLBACK,xdr_int,&tmp_prog,xdr_void,0); 
     if (stat != RPC_SUCCESS) { 
          clnt_perrno(stat); 
          exit(1); 
     } 
     svc_run(); 
     fprintf(stderr, "Error: svc_run shouldn't return\n"); 
} 
int 
callback(rqstp, transp) 
     register struct svc_req *rqstp; 
     register SVCXPRT *transp; 
{ 
     int exit(); 
 
     switch (rqstp->rq_proc) { 
 
     case 0: 
          if (!svc_sendreply(transp, xdr_void, 0)) { 
               fprintf(stderr, "err: exampleprog\n"); 
               return (1); 
          } 
          return (0); 
 
     case 1: 
          fprintf(stderr, "Client: got callback\n"); 
          if (!svc_sendreply(transp, xdr_void, 0)) { 
               fprintf(stderr, "Client: error replyingto exampleprog\n"); 
               return (1); 
          } 
          exit(0); 
     } 
     return (0); 
} 

Example 3-16 Server Usage of the gettransient Routine

/* 
* server 
*/ 
#include <stdio.h> 
#include <rpc/rpc.h> 
#include <sys/signal.h> 
#include "example.h" 
 
char hostname[256]; 
void docallback(int); 
int pnum = -1;          /* program number for callback routine */ 
 
main() 
{ 
     char *getnewprog(); 
 
     gethostname(hostname, sizeof(hostname)); 
     registerrpc(EXAMPLEPROG, EXAMPLEVERS, 
       EXAMPLEPROC_CALLBACK, getnewprog, xdr_int, xdr_void); 
     signal(SIGALRM, docallback); 
     alarm(10); 
     svc_run(); 
     fprintf(stderr, "Server: error, svc_run shouldn't return\n"); 
} 
 
char * 
getnewprog(pnump) 
     int *pnump; 
{ 
     pnum = *(int *)pnump; 
     return NULL; 
} 
void 
docallback(int signum) 
{ 
     int ans; 
 
     if (pnum == -1) { 
          fprintf(stderr, "Server: program number not received yet"); 
          signal(SIGALRM, docallback); 
          alarm(10); 
          return;     
     } 
     ans = callrpc(hostname, pnum, 1, 1, xdr_void, 0, 
       xdr_void, 0); 
     if (ans != RPC_SUCCESS) { 
          fprintf(stderr, "Server: %s\n",clnt_sperrno(ans)); 
          exit(1); 
     } 
     if (ans == RPC_SUCCESS) 
          exit(0); 
 
} 


Previous Next Contents Index