Programming with Remote Procedure Calls (RPC)


In the examples presented so far, the caller never identified itself to the server, and the server never required an ID from the caller. Some network services, such as a network filesystem, require stronger security than what has been presented so far.

Every RPC call is subjected to a style of authentication by the RPC package on the server. Similarly, the RPC client package generates and sends authentication parameters suitable for the style of authentication in effect. The default authentication style is AUTH_NONE (none).

Just as different transports can be used when creating RPC clients and servers, different forms of authentication can be associated with RPC clients.

The authentication subsystem of the RPC package is open ended. That is, numerous styles of authentication are easy to support; programmers can design their own authentication style and easily configure the RPC package to support it.

In addition to AUTH_NONE, the RPC package already supports the following authentication styles:

An authentication style based on traditional UNIX operating system process permissions authentication.

An alternate form of AUTH_SYS used by some servers for efficiency. Client programs using AUTH_SYS authentication should be prepared to receive AUTH_SHORT response verifiers from some servers. See ``Authentication protocols'' for details.

An authentication style based on DES encryption techniques.

AUTH_NONE: client side

When a caller creates a new RPC client handle as in:

   clnt = clnt_create(host, prognum, versnum, nettype)
the appropriate transport instance defaults the associated authentication handle to be
   clnt->cl_auth = authnone_create();

NOTE: If the programmer creates a new style of authentication, the programmer is responsible for destroying it with auth_destroy(clnt->cl_auth). This should always be done, to conserve memory.

AUTH_NONE: server side

Service implementors have a harder time dealing with authentication issues because the RPC package passes 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) */
   	SVCXPRT	*rq_xprt;			/* associated transport */
The rq_cred is mostly opaque, except for one field of interest: the style or flavor 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:

AUTH_SYS authentication

The RPC client can choose to use AUTH_SYS style authentication by setting clnt->cl_auth after creating the RPC client handle:

   clnt->cl_auth = authsys_create_default();
This causes each RPC call associated with clnt to carry with it the following authentication credentials structure:
    * AUTH_SYS style credentials.
   struct authsys_parms {
   	u_long 	aup_time;		/* credentials creation time */
   	char 	*aup_machname;	/* host name where client is */
   	uid_t	aup_uid;		/* client's effective uid */
   	gid_t	aup_gid;		/* client's current group id */
   	u_int	aup_len;		/* element length of aup_gids */
   	gid_t	*aup_gids;	/* array of groups user is in */
These fields are set by authsys_create_default by invoking the appropriate system calls.

The following shows the server for a remote procedure, RUSERPROC_n, that computes the number of users on the network. As a trivial demonstration of authentication usage, this server checks AUTH_SYS credentials and does not service requests from callers whose user ID is 16:

   nuser(rqstp, transp)
   	struct svc_req *rqstp;
   	SVCXPRT *transp;
   	struct authsys_parms *SYS_cred;
   	uid_t 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 (1); } return; } /* * now get the uid */ switch (rqstp->rq_cred.oa_flavor) { case AUTH_SYS: SYS_cred = (struct authsys_parms *)rqstp->rq_clntcred; uid = SYS_cred->aup_uid; break; case AUTH_NONE: default: svcerr_weakauth(transp); return; } switch (rqstp->rq_proc) { case RUSERSPROC_n: /* * make sure caller 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 (1); } return; default: svcerr_noproc(transp); return; } }

A few things should be noted here: The last point underscores the relation between the RPC authentication package and the services: RPC deals only with authentication and not with individual services' access control. The services themselves must establish access control policies and reflect these policies as return statuses in their protocols.

AUTH_DES authentication

AUTH_DES authentication is recommended for programs that require more security than that offered by the AUTH_SYS style of authentication.

AUTH_SYS authentication is easy to defeat. For example, instead of using authsys_create_default, a program could call authsys_create, and then change the RPC authentication handle to give itself any desired user ID and hostname.

The details of the AUTH_DES authentication protocol are complicated and are not explained here. See the ``AUTH_DES authentication'' for more information.

For AUTH_DES authentication to work, the keyserv(1Mbnu) daemon must be running on both the server and client machines. The users on these machines need public/secret key pairs assigned by the RPC administrator in the publickey(ADMN) database. And, they need to have decrypted their secret keys using the keylogin(C) command.

AUTH_DES: client side

If a client wishes to use AUTH_DES authentication, it must set its authentication handle appropriately. This is an example:

       cl->cl_auth = authdes_seccreate(servername, 60, server, NULL);
The first argument is the network name or ``netname'' of the owner of the server process. Typically, server processes are root processes and their netname can be derived using the following call:
   char servername[MAXNETNAMELEN];

host2netname(servername, rhostname, NULL);

rhostname is the hostname of the machine on which the server process is running. host2netname populates servername to contain this root process's netname. If the server process was run by a regular user, you could use the call user2netname instead. This is an example for a server process with the same user ID as the client:
   char servername[MAXNETNAMELEN];

user2netname(servername, getuid(), NULL);

The last argument to both of these calls, user2netname and host2netname, is the name of the naming domain where the server is located. The NULL used here means ``use the local domain name.''

The second argument to authdes_seccreate is a lifetime for the credential. Here it is set to sixty seconds which means that the credential will expire 60 seconds from now. If some mischievous program tries to reuse the credential, the server RPC subsystem will recognize that it has expired and will not grant any requests. If the same mischievous program tries to reuse the credential within the sixty second lifetime, it will still be rejected, because the server RPC subsystem remembers credentials it has seen in the near past, and will not grant requests to duplicates.

The third argument to authdes_seccreate is the name of the host to synchronize with. For AUTH_DES authentication to work, the server and client must agree on the time. Here we pass the hostname of the server itself, so the client and server will both be using the same time: the server's time. The argument can be NULL, which means ``don't bother synchronizing.'' A program should pass NULL only if sure the client and server are already synchronized.

The final argument to authdes_seccreate is the address of a DES encryption key to use for encrypting timestamps and data. If this argument is NULL, as it is in this example, a random key will be chosen. The client may find out the encryption key being used by consulting the ah_key field of the authentication handle.

AUTH_DES: server side

The server side is simpler than the client side. This is the previous example rewritten to use the AUTH_DES style instead of AUTH_SYS:

   #include <rpc/rpc.h>
   	. . .
   	. . .
   nuser(rqstp, transp)
   	struct svc_req *rqstp;
   	SVCXPRT *transp;
   	struct authdes_cred *DES_cred;
   	uid_t uid;
   	gid_t gid;
   	int gidlen;
   	gid_t gidlist[10];
   	 * we don't care about authentication for null proc

if (rqstp->rq_proc == NULLPROC) { /* same as before */ }

/* * now get the uid */ switch (rqstp->rq_cred.oa_flavor) { case AUTH_DES: DES_cred = (struct authdes_cred *) rqstp->rq_clntcred; if (! netname2user(DES_cred->, &uid, &gid, &gidlen, gidlist)) { fprintf(stderr, "unknown user: %s\n", DES_cred->; svcerr_systemerr(transp); return; } break; case AUTH_NONE: default: svcerr_weakauth(transp); return; }

/* * The rest is the same as before */

Note the use of the routine netname2user, the inverse of user2netname: it takes a network ID and converts to a local system ID. netname2user also supplies the group IDs, not used in this example, but which may be useful to other programs.
© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005