Previous | Contents | Index |
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> |
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.
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 |