Seite 1 von 36. Seite 2 von

Seite 1 von 36 Seite 2 von 36 The first version of the Remote Procedure Call Library was introduced in 1985 as a platform for developing distributed...
1 downloads 4 Views 52KB Size
Seite 1 von 36

Seite 2 von 36

The first version of the Remote Procedure Call Library was introduced in 1985 as a platform for developing distributed applications and Network Services. The platform is referred to as ONC (Open Network Computing). The source and the specifications are publicly available. The relevant RFC's (Request For Comments) are

SUN RPC/XDR PROGRAMMING Submitted towards completion of Master's Project Computer and Information Sciences, Temple University Spring 1993 Pragnesh Sampat Guide: Dr. Robert Stafford

1057 - RPC (Remote Procedure Call) protocol specification and language description. ACKNOWLEDGEMENT I would like to thank Dr. Robert Stafford for guiding and helping project and his excellent Class on Computer Networks.

me in this

1014 - XDR (eXternal Data Representation. Sun's way of solving the big-endian - small-endian problem. 1057 - NFS (Network File System) which is an RPC based program allowing 50 remote disks to serve as file servers.

CONTENTS 2. DISTRIBUTION (Getting the Library) 1. Background The source code for the SunOS 4.0 RPC and XDR Library, referred to as rpcsrc 4.0, is alicense-free version of the Sun's RPC and XDR Library. It is available on the Internet viaanonymous ftp program. The implementation uses only TCP and UDP transports. There is a Transport Independent version (TIRPC) available which is shipped with SVR4 (AT&T's new release System V Release 4) based on the TLI (Transport Layer Interface). The rpcsrc distribution consists of the following parts:

2. Distribution 3. The Client-Server Model 4. Sun RPC Model 4.1 RPC Messages 4.2 The Portmapper

rpcgen - a compiler for generating client and server stubs given the program definition in RPC language (which is very similar to C language).

5. An Example 5.1 5.2 5.3 5.4

Using the example (running the program) Questions Implementing the example Improving the example

runtime library - uses the BSD sockets to provide a high level interface to the programmer and also has routines for data representation. miscellaneous utilities and files.

6. Closer look at the distribution 6.1 RPCGEN 6.2 Runtime Library 6.2.1 RPC Runtime 6.2.2 XDR Runtime 6.3 The command rpcinfo (1) 6.4 Portmapper routines

3. THE CLIENT/SERVER MODEL The Client/Server Model is a popular model for developing distributed applications. It applies to any environment where a set of processes (clients) request that a certain work is to be done and another set (or a single process) carries out the actual work (servers). Both the sets can be on the same machine or on different machines. The X window system is an example of a Client/Server system though the meaning of Client and Server is different here. Figure 3.1 illustrates the idea

7. Using rpcgen(1) 8. Additional RPC Features 8.1 Batch RPC 8.2 Broadcast RPC 8.3 Callback RPC

Local Procedure Call 9. Authentication and Secure RPC +--------+ arguments +--------+ | |------------>| | | Client | | Server | | |

12. Appendix

Remote Procedure Call

1. BACKGROUND

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 3 von 36

+--------+ args +--------+ request message +--------+ args +--------+ | |-------->| |---------------->| |-------->| | | Client | | Client | | Server | | Server | | | client stub (both can be in one file). client stub -> network. (note that the data is in XDR) network -> server stub (data is put in local format) server stub -> server procedures (which can be in the same file)

5.2 Questions (about the example and in general) 1. Where does this fit in the OSI (or tcp/ip) model ?

and then reverse all the way.

The current RPC mechanism does not exactly fit into the OSI model though it can be rewritten so that it does. A close comparison is given below in Figure 5.1.1

7. How does the client "find" the server (there might be hundreds of active processes on picasso)? All the systems supporting the SUN RPC paradigm have a process called the "portmapper" running all the time at a fixed (known) port (111). When a server is brought up it "registers" itself with the portmapper with the triple (prognumber, progvers and procnumber) This means that a client has to first contact the portmapper to know if a service is registered and get the port of the service. The client can then directly contact the service at that port. This communication between the client and the portmapper happens in the form of request-reply packets.

+-------------------------+ | Application | |-------------------------| | Presentation (XDR) | |-------------------------| | Session (RPC) | |-------------------------| | Transport | |-------------------------| | Network | |-------------------------| | Data Link | |-------------------------| | Physical | +-------------------------+

8. How exactly is the called procedure invoked (need for a dispatch service)? There is a library function (svc_run) for handling this problem. After registration the server calls svc_run. svc_run waits for connections and dispatches the requests to the correct procedure. 9. How would you "update" the server code (support more than one version )?

Figure 5.1.1

A new procedure can be added to the server to support a new version of the service. It has to register itself just like any other procedure. (Actually a service can contain more than one procedure - the service is registered not just a procedure).

OSI Model

2. Do we deal with sockets ? Are there ports, ip addresses etc. to be taken care of ?

5.3 Implementing the example

A basic understanding of Inter Process Communication (IPC) using sockets is necessary though we will not be dealing directly with it. The RPC library itself is organized in layers offering varying levels of controls. Socket programming is necessary only at the very lowest layer.

This is a simple program in the RPC/XDR environment. The RPC Library is available with the -lrpcsvc option (see compiling information below). The relevant headers are in /usr/include/rpc directory (or /usr/include/bsd/rpc).

3. How is the "20" represented (astro and picasso could use totally different ways of representing integers) ? A standard data representing scheme called the eXternal Data Representation (XDR - RFC 1014) is used to encode/decode the data. There are routines to handle the conversion from local representation to standard representation and back to the local (on the remote machine). 4. Can I pass more than one argument ? Not in SUN RPC. Only one argument can be passed and only can be returned. However, the data can be put in a structure and passed. This allows more than one argument to be passed. Similarly for the return value data can be packed into a structure and sent. However for using struct a XDR routine must be written and used as there is no built-in xdr struct type (as there are, for example, other built-in types). 5. Is there a limit on the size of data ?

The organization of the files is as follows. Server is on picasso and client is on astro. Both the files (adda_svc.c on picasso and adda_clnt.c on astro) include a common header file (adda.h). Files common --> adda.h (on both) client --> adda_clnt.c (on astro) server --> adda_svc.c (on picasso)

Yes and No. If the transport used is UDP then a practical limit of 8k is imposed. For TCP there is no limit. 6. Is there a particular way to organize the files (as there seems to be more

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

The program is a ADD ONE client/server program. A program in astro (the client) when given the hostname and an integer, increments it by one and returns it. The procedure to increment is executed on picasso which has the server. The procedure takes an integer and returns an integer. The important point is that in the server the procedure actually takes a pointer to the arguments and returns a pointer (this is reflected in the header definition). The problem of finding the right process to communicate (referred to as BINDING) is solved by a process which is running as a daemon at a fixed port 111 (portmapper).

26.08.99

Let us consider the header file which is common on both client and the server (Figure 5.2.1)

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 7 von 36

Seite 8 von 36

The synopsis for callrpc() is as follows: int callrpc(host, char u_long xdrproc_t char

/* * This is the program number. It is a number between 0x20000000 and * and 0x40000000. It is a label for a collection of related procedures * to be grouped under. */ #define ADDPROG ((u_long)0x20001515) #define ADDVERS ((u_long)1) /* version number of the service */ #define add ((u_long)1) /* procedure number. It is usually */ /* 1, 2, 3 etc. procedure 0 is reg */ /* istered by default and used for */ /* testing purposes */ /* * The actual procedure. The underscore is a convention */ extern int *adda_1();

On the server side, we use two calls registerrpc() and svc_run. See Figure 5.2.3 for the server file.

Figure 5.2.1 HEADER FILE : adda.h Now, let us consider the client file which uses the call registerrpc() from the RPC library. See Figure 5.2.2 for usage example.

#include #include #include "adda.h "

prognum, versnum, procnum, inproc, in, outproc, out) *host; prognum, versnum, procnum; inproc, outproc; *in, *out;

#include #include /* standard RPC include file */ #include "adda.h" /* common header file */ main() { /* * registerrpc registers the service with the portmapper. The first 3 * args are for that. The next arg is the name of the function which * will be called when a request arrives. The next 2 args are the * input and output parameters. svc_run waits for the request to arrive * and takes care of the conversion and parameter passing to the * actual server proc which is add_1(the _1 is a convention). */ if ( registerrpc (ADDPROG, ADDVERS, add , add_1, xdr_int, xdr_int) == -1) { fprintf(stderr, "Error : cannot register service\n"); exit(1); } svc_run(); /* waits for requests and dispatches */

/* standard RPC include file */ /* common header for client and server */

main(argc, argv) int argc; char *argv[]; { /* * local is the local variable, result is where the result after * the call is stored and status is for error checking. */ int status, local, result; if (argc !=3) { fprintf(stderr,"usage : %s servername number\n", argv[0]); exit(1); } local = atoi(argv[2]); /* convert to integer */ /* * callrpc has 7 arguments: hostname, the next 3 identify the triple of * progname, progvers and procnumber, xdr_int for neutral data rep, * local is the local data, xdr_int again for neutral data rep, result * for putting results back. */ status = callrpc(argv[1], ADDPROG, ADDVERS, add , xdr_int, (char *)&local, xdr_int, (char *)&result); if (status == 0) printf("result is %d\n", result); else { fprintf(stderr, "Error : callrpc(); %s\n", clnt_sperrno ((enum clnt_stat) status)); exit(2); } return(0); } Figure 5.2.2

} int *add_1(int *number) { static int result; result = *number + 1; return(&result); }

/* must be static */ /* note that the pointer is returned */ Figure 5.2.3

SERVER FILE : adda_svc.c

The synopsis for registerrpc() is as follows: int registerrpc(prognum, versnum, procnum, procname, inproc, outproc) u-long prognum, versnum, procnum; char *(*procname)(); xdrproc_t inproc, outproc; The first three arguments identify the program number, the version number and the procedure number being registered. The next argument procname is the address of the procedure being registered. The next two arguments inproc and outproc are the addresses of the XDR filters that will decode and encode the arguments. The routine returns 0 to indicate success and -1 for failure. registerrpc() is used as many times as there are procedures to register (except for the NULL procedure, which is handled automatically).

CLIENT FILE : add_clnt.c The synopsis of the svc_run() routine follows:

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 9 von 36 void svc_run()

Seite 10 von 36 */ extern bool_t xdr_sumargs(XDR *xdr_handlep, argtype *);

This routine has no arguments and no returns. It never returns control to the calling function unless a fatal error is detected. After registering the procedure the server program calls svc_run() to wait for requests and dispatch appropriately.

Figure 5.3.1 HEADER FILE: add.h

When a server is brought up it registers itself with the portmapper (announces its services) which is running on the server. The portmapper thus contains information which allows the client to contact the portmapper to get this information. This information is in the form of a triple (PROGRAM NUMBER, VERSION AND PROCEDURE NAME).

#include #include #include "add.h"

The library provides interface to the programmer at more than one levels. This program uses the highest layer (less flexible for options) to make calls and get results back(the only calls used are registerrpc and svc_run in the server (picasso) and callrpc in the client (astro)).

/* * * *

Compiling information. On astro: cc -systype bsd43 add_clnt.c -lrpcsvc -o add_clnt will create the client exec file add_clnt. On picasso: cc add_svc.c -o add_svc -lrpcsvc will create the server exec file add_svc. In general for a SYSV type of machine the BSD libraries have to be linked and relevant headers included.

/* includes other RPC /* types.h and xdr.h */

related

files

like

*/

All XDR functions return a type called bool_t which is defined in xdr.h as TRUE or FALSE. The functions take a pointer to XDR struct and a pointer to the data being filtered (encoded/decoded) /

bool_t xdr_sumargs(xdr_handlep, objp) XDR *xdr_handlep; argtype *objp; { /* * xdr_int is a built-in filter from which we are constructing a custom * filter. Some of the other built-in filters are xdr_char xdr_short, * xdr_reference and many others. */ return ((xdr_int(xdr_handlep, &objp->low)) && (xdr_int(xdr_handlep, &objp->high)) && (xdr_int(xdr_handlep, &objp->step))); } Figure 5.3.2

5.4 Improving the example

XDR FILE: add_xdr.c

(common to both)

Can we improve or change the function of the server ? For example, suppose we want to execute a loop given the low, high and a optional step size (add low through high with step=stepsize) and return the sum. Can we provide this? Yes. The only things we have to take care are argument passing. As SUN RPC allows only one argument and one return value we have to pack the arguments in a structure and pass it. Simple XDR conversion is handled by the built-in XDR filters like xdr_int, xdr_char xdr_string etc. For a structure there is no built-in type for automatic conversion. We have to write a function consisting of combination of simpler built-in types. For example if the structure consists of two fields, int and char then we can encode the fields using the built-in types xdr_int and xdr_char. The files add_xdr.c and add.h contain more comments about this (Note: The organization and placement of the files is the same except for the addition of the file add_xdr.c to both the server and the client). To provide the old version also, it is better to use low level RPC calls (along with the protocol compiler rpcgen). See Figures 5.3.1 through 5.3.4 for the improved version

#define ADDPROG #define ADDVERS #define ADDPROC extern int *add_1(); struct argtype { int low; int high; int step; } ;

((u_long)0x20000100) ((u_long)1) ((u_long)1)

/* /* /* /* /*

prog number */ vers number */ proc number */ the actual procedure */ args struct for passing */

typedef struct argtype argtype; /* * declare the XDR routine which will be called for args encoding/decoding. * See add_xdr.c for more comments.

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

#include #include #include "add.h" #define STEPSIZE

/* std RPC include file */ /* common header file for client and

server */

1

main(argc, argv) int argc; char *argv[]; { int defaultstep=STEPSIZE; int status, result; argtype sumargs;

/* return code from call and result */ /* argument to be passed */ /* defined in the header add.h */

if ((argc != 4) && (argc !=5)) { printf("usage: add_clnt server low high [step]\n"); exit(1); } if (argc == 5) defaultstep = atoi(argv[4]); sumargs.low = atoi(argv[2]); /* fill in the struct to be sent */ sumargs.high = atoi(argv[3]); sumargs.step = defaultstep; status = callrpc(argv[1], ADDPROG, ADDVERS, ADDPROC , xdr_sumargs, (argtype *)&sumargs, xdr_int, (int *)&result); if (status == 0) printf("result is %d\n", result2); else { fprintf(stderr, "Error: callrpc(); %s\n", clnt_sperrno

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 11 von 36 ((enum clnt_stat) status)); exit(2);

Seite 12 von 36 -o

file

- to write into the file as other options write to the stdout.

} return(0); }

6.2 The Runtime Library Figure 5.3.3

The run time library has two parts RPC and XDR which can be linked while compiling by -lrpcsvc (as done before)

CLIENT FILE: add_clnt.c 6.2.1 RPC runtime #include #include /* std RPC header file */ #include "add.h" /* common file to both */ main() { int *add_1(argtype *); /* procedure */ if (registerrpc(ADDPROG, ADDVERS, ADDPROC , add_1, xdr_sumargs, xdr_int) == -1) { fprintf(stderr, "Error : cannot register\n"); exit(2); } svc_run(); } int *add_1(argtype *sumptr) { int i; static int loopsum; /* must be static */ loopsum = 0; for(i = sumptr->low; i high; i = i + sumptr->step) { loopsum = loopsum + i; } return (&loopsum); }

The RPC library is organized functionally as o o o o o

server side routines client side routines error routines authentication routines portmapper routines.

and some miscellaneous ones for broadcast, asynchronous or other special tasks. 6.2.2 XDR runtime The XDR routines are there to encode and decode the parameters in a convenient manner independent of the data representation of the machines involved. In fact XDR can be used in a non-RPC environment also as a standard way of transferring data. XDR routines are of two types broadly - routines to manipulate streams and routines to transfer data to and from the streams. The latter are called filters and will be used mostly. XDR library provides for primitive bi-directional filters (same filters for encoding and decoding depending on the context) using which more complex filters can be constructed. The filters are trifunctional i.e. encode, decode and free memory. All filters return value of type bool_t (defined in the header file xdr.h) which is true or false depending on the success or failure of the function. A filter looks like this bool_t xdrproc(XDR xdrs, *argresp)

Figure 5.3.4 where xdrs is an instance of XDR handle defined in xdr.h, argresp is the location where the data to be converted is stored (if encoding). If decoding data from xdrs is translated and placed in memory at the location argresp.

SERVER FILE: add_svc.c

Some examples 6. CLOSER LOOK AT THE DISTRIBUTION 6.1 RPCGEN As mentioned above, the protocol compiler rpcgen(1) comes with the distribution. The normal way to build applications is to provide a description of the program and procedures in a high level description language called RPCL which is very similar to C. RPCL is described formally in RFC 1057. The Compiler takes the description file (a file with .x) and generates client and server stubs (normal .c files) and a common header file. It may also generate XDR routines depending on the definition file. In fact it is recommended to generate XDR routines this way rather than hand coding them as done in the previous example. These files can be compiled normally and linked with the run time library to get executable server (along with the server procedures) and executable client (along with the client side program). Some of the options of rpcgen are -s transport -c -l -h

-

Filter

XDR type

char int long void float

xdr_char() xdr_int() xdr_long xdr_void xdr_float

int int long void float

In addition to these some composite types are provided like xdr_string(), xdr_reference() and xdr_pointer (the difference is that xdr_pointer handles null pointers also), xdr_array and xdr_vector (routines to handle an array of objects), xdr_opaque and xdr_bytes (to handle bytes) and xdr_union(). There is no xdr_struct type; it has to be custom built. This is done in example 2 (it can be done in more than one ways). 6.3 The Command rpcinfo(1)

for generating server stubs with transport. to generate XDR routines. to generate client stubs. to generate header.

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

C type

It is a program which gives information about the programs currently supported at a host. The display is in a long format containing progno, versnum, transport, port assigned and a short description. For example, output from the command "rpcinfo -p astro" from picasso gives:

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 13 von 36 int addloop(argtype) = 1; } = 1; } = 0x20000200;

p:/usr/users/psampat>rpcinfo -p astro program vers proto 100005 100008 100002 100011 100012 100003 100021 100021 100021 100021 100020 100020 100021 100021 100024 100024 100005 536870912

1 1 1 1 1 2 1 1 3 3 1 1 2 2 1 1 1 1

udp udp udp udp udp udp tcp udp tcp udp udp tcp tcp udp udp tcp tcp udp

Seite 14 von 36

port 1024 1025 1026 1027 1028 2049 915 1029 919 1030 1031 924 927 1032 921 923 928 1712

mountd walld rusersd rquotad sprayd nfs nlockmgr nlockmgr nlockmgr nlockmgr llockmgr llockmgr nlockmgr nlockmgr status status mountd user prog (note - decimal number)

Then we use rpcgen to compile add.x and get (rpcgen add.x) add.h (common header file) add_svc.c (server stub) add_clnt.c (client stub) add_xdr.c (common xdr file) The client stub has to interact with a client program given below: FILE: getsum.c # include # include # include "add.h" #define DEFAULTSTEPSIZE 1 #define HOSTNAMESIZE 40

The rpcinfo command also allows us to determine whether a server has crashed by calling the procedure 0 of the service. By convention procedures are numbered 1, 2, 3 etc. and procedure 0 is called NULL procedure and automatically registered.

main (argc, argv) int argc; char *argv[]; { CLIENT *cl; int *result; argtype *argumentptr;

/* step size to use for increment */

/* client handle */ /* note the pointer */

6.3 Portmapper Routines The RPC Library includes some routines for communicating with the portmapper. This interface is tied to the socket paradigm. Some of them can be used to write utilities useful while debugging and managing the servers. These routines are: o o o o o

pmap_getmaps pmap_getport pmap_rmtcall pmap_set pmap_unset

The routines that communicate with other computers take addresses in the form of a sockaddr_in data structure.

7.0 USING RPCGEN(1) (PROGRAM 3) Now, for running the same example 2 using rpcgen we create a file called add.x in RPCL: FILE: add.x /* declare the struct with args */ struct argtype { int high; int low; int step; }; /* * program identifier, version identifier and procedure identifier. Note * that the procedure definition contains return type also. */ program ADDPROG { version ADDVERS {

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

/* * Do args processing. */ if (argc != 4 && argc != 5) { fprintf (stderr, "usage : %s hostname low high [step]\n", argv[0]); exit(1); } /* * The function clnt_create(....) creates a client handle. It is one * of the four routines that do the same function. The other three * routines require an internet address as the first argument (which * can be obtained by gethostbyname()). The client handle, along with * the transport handle (SVCXPRT defined in rpc/svc.h) is a data * abstraction to isolate the programmer from the transport details. * The structure CLIENT is defined in rpc/clnt.h */ if ((cl = clnt_create(argv[1], ADDPROG, ADDVERS, "udp")) == NULL) { clnt_pcreateerror(argv[1]); /* error routine */ exit(2); } /* fill in the structure appropriately */ argumentptr = (argtype *)malloc(sizeof(struct argtype)); argumentptr->low = atoi(argv[2]); argumentptr->high = atoi(argv[3]); if (argc == 5) argumentptr->step = atoi(argv[4]); else argumentptr->step = DEFAULTSTEPSIZE; /* * Call the procedure with the handle created as another argument. * This is because the actual call is made by the stub which rpcgen * will be generating. That call is clnt_call(.......). */ result = (int *) addloop_1(argumentptr, cl); if (result == NULL) { clnt_perror(cl, argv[1]); /* error routine */ exit(3);

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 15 von 36 } printf("the sum is %d\n", *result); clnt_destroy(cl); return 0;

Seite 16 von 36

#include #include .... .... CLIENT cl; struct timeval timeout; .... .... timeout.tv_sec = 0; timeout_tv_usec = 0;

/* print the result */ /* destroy the handle */

} The server procedures which the server stub will be calling are file called add.c. In this case there is just one procedure.

in another

FILE: add.c if (clnt_control(cl, CLSET_TIMEOUT, timeout) == FALSE ) { #include #include #include "add.h"

/* error message and exit */ } This makes the client non-blocking but it can be used only when the client is not expecting any replies. Note that the argument to handle the reply (the XDR routine) must be NULL instead of xdr_void. This kind of non-blocking RPC can be used in situations where client has many requests to send but does not need any reply till the server has received all the requests.

int *addloop_1(argtype *sumptr) { int i; static int loopsum; /* must be static */ loopsum = 0; for(i = sumptr->low; i high; i = i + sumptr->step) { loopsum = loopsum + i; } return (&loopsum); }

8.2 Broadcast RPC

We are now ready for compiling information. It is convenient to follow these steps: 1. rpcgen add.x 2. make all

The call clnt_broadcast () is used to do broadcast RPC. It is similar to callrpc () but has one more argument to take care of the replies. This argument is the address of a function to process the reply. Each time a reply is received this routine calls the above mentioned function. The synopsis for this function is:

The makefile is given below. This is on the astro. FILE: makefile # remember to have REAL TABS for the compiling commands. LIBS = -lrpcsvc all: add getsum add: add.c add_svc.c add_xdr.c cc -systype bsd43 -g add.c add_svc.c add_xdr.c -o add $(LIBS) getsum: getsum.c add_clnt.c add_xdr.o cc -systype bsd43 -g getsum.c add_clnt.c add_xdr.o -o getsum $(LIBS)

8.

reply(res, addr) char *res; struct sockaddr_in *addr; See Figure 8.2.1 below for an example on how to use the broadcast feature.

#include #include #include #include

ADDITIONAL FEATURES

The current implementation of svc_run() does not return control to the caller, so to do multitasking at the server, you need to roll your own svc_run(). This is not a major problem as the sources are available publicly. This section includes additional features dealing with asynchronicity at the client side. These features are o Batch RPC o Broadcast RPC o Callback RPC 8.1 Batch RPC Some asynchronous processing can be done at the client by setting the TIMEOUT in clnt_call() to 0. This is done by calling the routine clnt_control(). For example

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

In broadcast RPC, the client sends a broadcast packet and waits for replies. It uses unreliable packet-based transport like UDP. The amount of data broadcast is limited by the type of network used (For example, on the ethernet it cannot be more than 1500 bytes including the header and arguments). The number of computers that receive the broadcast depends on the configuration of the network as the routers may not allow broadcast packets to be forwarded. The authentication flavor (see section 9) defaults to AUTH_UNIX and cannot be changed.

26.08.99



#define HOSTNAMESIZE 35 static char host[HOSTNAMESIZE]; main(argc, argv) int argc; char *argv[]; { enum clnt_stat stat; int reply(); u_long prognum, versnum, procnum; if (argc != 4) { fprintf(stderr, "usage: %s prognum, versnum, procnum\n", argv[0]); exit(1); }

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 17 von 36 prognum = atoi(argv[1]); versnum = atoi(argv[2]); procnum = atoi(argv[3]); host[0] = ' '; stat = clnt_broadcast(prognum, versnum, procnum, xdr_void, NULL, xdr_void, NULL, reply); if ((stat != RPC_SUCCESS) && (stat != RPC_TIMEDOUT)) { fprintf(stderr, "broadcast: %s\n", clnt_sperrno(stat)); exit(2); } exit(0);

Seite 18 von 36 void u_long

(*dispatch)(); protocol;

{ static u_long prognum = 0x40000000; /* beginning of transient range */ /* * get the program number for the callback routine */ while (pmap_set(prognum, version, protocol, transp->xp_port) == 0 && prognum < 0x50000000) { prognum++; } if (prognum < 0x50000000) { /* * means that pmap_set succeeded - i. e returned 0 */ if (svc_register(transp, prognum, version, dispatch, 0) == FALSE) { svc_unregister(prognum, version); return 0; } } else { prognum = 0x40000000; return 0; } /* * increment the prognum so that the next call will be ok */ return (prognum++);

} reply(res, addr) void *res; struct sockaddr_in *addr; { struct hostent *hp; hp = gethostbyaddr((char *) &addr->sin_addr, sizeof(addr->sin_addr), AF_INET); printf("%s\t\t%s\n", inet_ntoa(addr->sin_addr), (hp == NULL) ? "(unknown)" : hp->h_name); return(FALSE); } Figure 8.2.1 Broadcast RPC } 8.3 Callback RPC

Figure 8.3.1 Callback RPC is the most powerful asynchronous method available to the client as of current release. The classic example given here is that of a remote debugger interacting with a client window program. Most of the time the user clicks a mouse button which converts it into a command and makes a RPC to the server. When the debugger reaches a breakpoint, it needs to callback the client. In order to do this the server needs a program number to call the client on. This is typically chosen from another range allotted for such purposes. This is from 0x40000000 to 0x5FFFFFFF. The client registers the callback service (or collection service) with the local portmapper (via pmap_set) and registers the dispatch service (via svc_register()). The program number is then sent as a part of the RPC to the server. When the server is ready it initiates a normal RPC to the client at the given port (which the server finds out from the local portmapper). The following program demonstrates the use of such a callback service. The server main() routine gets a transport handle (transp) and registers the dispatch service (dispatch in the server file) and then calls svc_run to wait for RPC requests. When it receives a request the dispatch routine, processes the request (EXAMPLE_PROC in this case) and does some work (sleep here for 10 seconds) and calls back the client using the routine docallback(). The client gets a transport handle andregisters itself calling the svc_register_transient routine written in a separate file trans.c. It then make a RPC to the server using callrpc() and calls svc_run to wait for the requests. Typically here, you can use your own svc_run to allow further processing or fork() a child process to handle the waiting and continue with other processing (see the appendix for an example of the latter kind). The example is in 4 files (Figures 8.3.1 to 8.3.4). #include #include #include u_long svc_register_transient(transp, version, dispatch, protocol) SVCXPRT *transp; u_long version;

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

FILE: trans.c

#include #include #include #include

"callback.h"

struct timeval timeout = {25, 0}; main() { SVCXPRT *transp; void dispatch(); transp = svcudp_create(RPC_ANYSOCK); if (transp == NULL ){ fprintf(stderr, "error: svcudp_create() failed\n"); exit(1); } pmap_unset(EXAMPLE_PROG, EXAMPLE_VERS); if (svc_register(transp, EXAMPLE_PROG, EXAMPLE_VERS, dispatch, IPPROTO_UDP) == FALSE) { fprintf(stderr, "error: unable to register service\n"); svc_destroy(transp); exit(2); } printf("server going into svc_run\n"); svc_run(); fprintf(stderr, "error: svc_run() returned !\n"); svc_unregister(EXAMPLE_PROG, EXAMPLE_VERS); svc_destroy(transp); return 1; } void dispatch(rqstp, transp) struct svc_req *rqstp;

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 19 von 36

Seite 20 von 36

SVCXPRT *transp;

call_info callback_info; int stat; u_long svc_register_transient(); void callback(); if (argc !=2) { fprintf(stderr, "usage: %s hostname\n", argv[0]); exit(1); } strncpy(callback_info.hostname, argv[1], MAX_HOST_NAME_LEN); if ((transp = svcudp_create(RPC_ANYSOCK)) == NULL ) { fprintf(stderr, "error: svcudp_create() failed\n"); exit(2); } callback_info.prognum = svc_register_transient (transp, EXAMPLE_CALLBACK_VERS, callback, IPPROTO_UDP); if (callback_info.prognum == 0) { fprintf(stderr, "error: couldn't register transient servive\n"); svc_destroy(transp); exit(3); } printf("using transient program number: %lu\n", callback_info.prognum); callback_info.versnum = EXAMPLE_CALLBACK_VERS; callback_info.procnum = EXAMPLE_CALLBACK_PROC; stat = callrpc(callback_info.hostname, EXAMPLE_PROG, EXAMPLE_VERS, EXAMPLE_PROC, xdr_call_info, &callback_info, xdr_void, (xdrproc_t)NULL); if (stat != 0) { fprintf(stderr, "error callrpc(): %s\n", clnt_sperrno((enum clnt_stat)stat)); svc_unregister(callback_info.prognum, EXAMPLE_CALLBACK_VERS); svc_destroy(transp); exit(4); } svc_run();

{ call_info void

callback_info; docallback();

switch(rqstp->rq_proc) { case NULLPROC: if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "error: svc_sendreply() for NULLPROC failed\n"); } break; case EXAMPLE_PROC: if (svc_getargs(transp, xdr_call_info, &callback_info) == FALSE) { svcerr_decode(transp); exit; } if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr,"error: svc_sendreply() for EXAMPLE_PROC failed\n"); return; } /* * do a compute intensive task */ printf("Computing..... :-)\n"); sleep(10); printf("calling the client back with the results\n"); docallback(&callback_info); break; } } void docallback(call_infop) call_info *call_infop; { CLIENT *cl; enum clnt_stat stat;

} void callback(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { switch (rqstp->rq_proc) { case NULLPROC: if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "error: svc_sendreply() for NULLPROC failed\n"); } break; case EXAMPLE_CALLBACK_PROC: if (svc_getargs(transp, xdr_void, 0) == TRUE) { printf("client got callback from server\n"); if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr,"error: exampleprog\n"); } } else { svcerr_decode(transp); return; } svc_unregister(rqstp->rq_prog, rqstp->rq_vers); svc_destroy(transp); exit(0); break; } } Figure 8.3.3

cl = clnt_create(call_infop->hostname, call_infop->prognum, call_infop->versnum, "udp"); if (cl == NULL) { clnt_pcreateerror(call_infop->hostname); return; } stat = clnt_call(cl, call_infop->procnum, xdr_void, NULL, xdr_void, NULL, timeout); if (stat != RPC_SUCCESS) { clnt_perrno(cl, call_infop->hostname); } clnt_destroy(cl); } Figure 8.3.2 FILE: ca_svc.c

#include #include #include #include #include

"callback.h"

/* void callback(SVCXPRT, struct svc_req); */

FILE: ca_clnt.c

main(argc, argv) int argc; char *argv[]; { SVCXPRT *transp;

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 21 von 36 #include #include "callback.h"

9.1 UNIX Authentication The caller wishes to identify himself as on a UNIX machine. The credentials body consists of the following structure (opaque - uninterpreted data)

bool_t xdr_call_info(xdrs, objp) XDR *xdrs; call_info *objp;

struct auth_unix { unsigned int stamp; string machinename; unsigned int uid; unsigned int gid; unsigned int gids; };

{ char *tmp; tmp = objp->hostname; if (xdr_u_long(xdrs, &objp->prognum) == FALSE) { return(FALSE); } if (xdr_u_long(xdrs, &objp->versnum) == FALSE) { return(FALSE); } if (xdr_u_long(xdrs, &objp->procnum) == FALSE) { return(FALSE); } return (xdr_string(xdrs, &tmp, MAX_HOST_NAME_LEN));

The stamp isan arbitrary ID which the caller may generate, the machinename is the name of the caller's machine, the uid is the caller's effective user ID, gid is the caller's effective group ID and gids is a counted array of groups which have caller as a member. The main routine available forthe client to build UNIX - style authentication is the following:

} Figure 8.3.4

AUTH * authunix_create(hostname, uid, gid, grouplen, gidlistp) char *hostname; int uid, gid, grouplen, *gidlistp;

FILE: ca_xdr.c

This routine creates and returns a pointer to the authentication handle that contains UNIX authentication information. The routine authunix_create_default() calls the above routine with default values set.

The makefile is given below: # make SURE the commands are TABBED. CFLAGS = -g SYSTYPE = -systype bsd43 LIBS = -lrpcsvc SOURCES1 = callback_clnt.c callback_xdr.c transient.c SOURCES2 = callback_svc.c callback_xdr.c

On the server side, you can access the credentials of the client through the rq_cred member of the svc_req structure which is passed into the dispatch routine. The example below will make this clear. The program is used to send a message to another user on the same or different machine (server must be running). The remote procedure registered by the server opens the tty associated with the user (if permitted) and writes on it.

all: ca_clnt ca_svc ca_clnt: $(SOURCES1) callback.h cc $(CFLAGS) $(SYSTYPE) $(SOURCES1) -o $@ $(LIBS) ca_svc: $(SOURCES2) callback.h cc $(CFLAGS) $(SYSTYPE) $(SOURCES2) -o $@ $(LIBS)

9.

FILE: msg.x (protocol specification file) /* This is the protocol specification file. I have edited the files generated by * putting the authentication code */

AUTHENTICATION

The RPC Library provides for an open-ended authentication scheme; a variety of schemes can be plugged in. Currently it supports three types of authentication "flavors". The first is no authentication, which is the default. The second is UNIX style authentication that uses UNIX credentials and the third is based on the DES (Data Encryption Standard). The authentication type is identified by a unique number similar to the one for program assignment. These numbers are : Authentication Number 0 1 2 3

Seite 22 von 36

Description None UNIX - style Short hand UNIX - style DES

Theclient specifies the type of authentication by setting the value of the pointer to the authentication handle (AUTH defined in auth.h) the client handle (CLIENT), by using some functions which we will see in the next sections.

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

/* * rpcgen handles limited amount of preprocessing. There are four built-in * macros useful for preprocessing directives RPC_HDR, RPC_SVC, RPC_XDR * and RPC_CLNT. The line "const identifier = value" gets translated into * #define identifier value. In addition to this any line starting with * the percent sign(%) is passed unchanged to the output (with the percent * removed) but we are warned to be careful to use it as it is unpredictable */ #ifdef RPC_HDR const RNAME = 12; const RHOST = 45; const RMSG = 255; #endif struct msgtype { char *rname; char *rhost; char *rmsg; }; program SENDMSG_PROG { version SENDMSG_VERS { int MSG(msgtype) = 1; } = 1;

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 23 von 36

Seite 24 von 36

} = 0x20000000;

} auth_destroy(cl->cl_auth); clnt_destroy(cl); exit(0);

FILE: msg.h (header file) } #define RNAME 12 #define RHOST 45 #define RMSG 255

The client stub follows. Note that it is generated by the rpcgen.

struct msgtype { char *rname; char *rhost; char *rmsg; };

FILE: msg_clnt.c (client stub) #include #include #include "mesg.h"

typedef struct msgtype msgtype; bool_t xdr_msgtype(); #define SENDMSG_PROG ((u_long)0x20000000) #define SENDMSG_VERS ((u_long)1) #define MSG ((u_long)1) extern int *msg_1();

/* Default timeout can be changed using clnt_control() */ static struct timeval TIMEOUT = { 25, 0 }; int * msg_1(argp, clnt) msgtype *argp; CLIENT *clnt; { static int res; enum clnt_stat stat;

The client program follows. FILE: msg.c (client file) # # # #

include include include include

bzero((char *)&res, sizeof(res)); if (clnt_call(clnt, MSG, xdr_msgtype, argp, xdr_int, &res, TIMEOUT) != RPC_SUCCESS) { clnt_perrno(stat); return (NULL); } return (&res);

"msg.h"

main (argc, argv) int argc; char *argv[]; { CLIENT *cl; char *server; int *result; msgtype *message;

} The server stub generated by rpcgen follows. Note that it has to be edited to handle authentication (see related comments).

if (argc !=4) { fprintf (stderr, "usage : %s username hostname message\n", argv[0]); exit(1); } server = argv[2]; if ( (cl = clnt_create(server, SENDMSG_PROG, SENDMSG_VERS, "udp")) == NULL ) { clnt_pcreateerror(server); exit(2); } /* fill the CLIENT strcuture with appropriate call */ cl->cl_auth = authunix_create_default(); message = (msgtype *)malloc(RNAME+RHOST+RMSG); message->rname = argv[1]; message->rhost = "psampat@astro"; /* should be done in a better way */ message->rmsg = argv[3]; result = msg_1(message, cl); switch (*result) { case NULL: clnt_perror(cl, server); break; case 1 : printf("user not logged in\n"); break; case 2 : printf("message sent!\n"); break; default : printf("default case\n"); break;

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

FILE: msg_svc.c (server stub) #include #include #include "msg.h" static void sendmsg_prog_1(); main() { SVCXPRT *transp; (void)pmap_unset(SENDMSG_PROG, SENDMSG_VERS); transp = svcudp_create(RPC_ANYSOCK); if (transp == NULL) { (void)fprintf(stderr, "cannot create udp service.\n"); exit(1); } if (!svc_register(transp, SENDMSG_PROG, SENDMSG_VERS, sendmsg_prog_1, IPPROTO_UDP)) { (void)fprintf(stderr, "unable to register (SENDMSG_PROG, SENDMSG_VERS, udp).\n"); exit(1); } svc_run(); (void)fprintf(stderr, "svc_run returned\n"); exit(1); }

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 25 von 36 static void sendmsg_prog_1(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { struct authunix_parms *unix_cred; /* defined in auth_unix.h */ int uid; union { msgtype msg_1_arg; } argument; char *result; bool_t (*xdr_argument)(), (*xdr_result)(); char *(*local)(); switch (rqstp->rq_proc) { case NULLPROC: (void)svc_sendreply(transp, xdr_void, (char *)NULL); return; case MSG: /* The server checks the client's credentials here. This is the only section * that needs to be added. Authentication can also be done in the server * procedures but it seems much easier and cleaner here. */ switch (rqstp->rq_cred.oa_flavor) { case AUTH_UNIX: unix_cred = (struct authunix_parms *) rqstp->rq_clntcred; uid = unix_cred->aup_uid; if (uid != 515) { /* that's me */ svcerr_weakauth(transp); fprintf(stderr, "u r !allowed to do this \n"); return; } break; default: svcerr_weakauth(transp); return; } /* printf's to check the creds on the server */ fprintf(stderr,"uid = %d\n",unix_cred->aup_uid); xdr_argument = xdr_msgtype; xdr_result = xdr_int; local = (char *(*)()) msg_1; break;

Seite 26 von 36

#include #include #include #include #include #include #include /* IMPORTANT TO INCLUDE - bool_t problem */ #include "msg.h" char *devicename; int loginfo(char *); int *msg_1(msgtype *message) { FILE *fp; static int result=2; if (loginfo(message->rname) == 0) { result = 1; return(&result); } else { fp = fopen(devicename, "w"); if (fp == NULL) { fprintf(stderr, "error writing to the terminal"); exit(2); } fprintf(fp,"Message from %s:\n %s\n", message->rhost, message->rmsg); fclose(fp); return(&result); } } int loginfo(username) char *usename; { int log=0; int fd; struct utmp *ut; struct utmp *getutent(); void setutent();

/* note that the functions used below are not necessary. if they are not ava * fopen() with fread() should do the trick. actually had to do it once */ fd = open("/etc/utmp", O_RDONLY, 0); /* should do error checking here */ setutent(); /* important ! doesn't reset for later calls */ while ((ut=getutent())!=(struct utmp *)0) { if (strcmp(username,ut->ut_user)==0) { log = 1; devicename = (char *)malloc(25 * sizeof (char)); strcpy (devicename, "/dev/"); strcat(devicename, ut->ut_line); break; } } close(fd); return(log);

default: svcerr_noproc(transp); return; } bzero((char *)&argument, sizeof(argument)); if (!svc_getargs(transp, xdr_argument, &argument)) { svcerr_decode(transp); return; } result = (*local)(&argument, rqstp); if (result != NULL && !svc_sendreply(transp, xdr_result, result)) { svcerr_systemerr(transp); } if (!svc_freeargs(transp, xdr_argument, &argument)) { (void)fprintf(stderr, "unable to free arguments\n"); exit(1); }

/* makes non-portable code */

} And now the XDR file. The file generated by rpcgen was creating problems, so this is not generated by rpcgen.

} FILE: msg_xdr.c Now, for the server procedure.

#include #include #include "msg.h"

FILE: msg_proc.c

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 27 von 36 bool_t xdr_msgtype(xdrhandle, mptr) XDR *xdrhandle; msgtype *mptr; { return((xdr_string(xdrhandle, &mptr->rname, RNAME)) && (xdr_string(xdrhandle, &mptr->rhost, RHOST)) && (xdr_string(xdrhandle, &mptr->rmsg, RMSG))); }

Seite 28 von 36 constants (a 48 bit hex number). The common key thus obtained is 192 bits long. To reduce the number of bits, the middle-most 8 bytes are chosen and the lower order bits are made the parity bits to get an effective Conversation key of 56 bits. It is this key which is used to encrypt the time stamps. For illustration, let's consider the sequence of steps taking place when a client "A" wants to talk with a server "B". This is just for the first transaction, the rest of the transactions are simpler. o For the first transaction the client's credential consists of the name A, the conversation key CK encrypted with the common key KAB and the window encrypted with CK. The window is the life of a credential. It is used to prevent someone to replay the client's conversation at a later time. The client's verifier consists of encrypted time stamp and encrypted version of the window + 1 to prevent somebody that is monitoring the communications from finding the values easily.

FILE: makefile (for the client) # make SURE the commands are TABBED. # msg contains the actual client procedures. CFLAGS = -g SYSTYPE = -systype bsd43 LIBS = -lrpcsvc # make the 'client' executable. msg: msg.h msg_clnt.c msg_xdr.c mesgsend.c cc $(SYSTYPE) $(CFLAGS) msg_clnt.c msg_xdr.c msg.c -o $@ $(LIBS) msg.h: msg.x rpcgen -h msg.x -o $@ # commented out after generating. #msg_clnt.c: msg.x # rpcgen -l msg.x -o $@

o The server creates a credential table and stores the client's name, CK, window and the time stamp. The first three are used for future use and the time stamp is used to protect against replays. It accepts only chronologically greater time stamps. In it's reply, the server sends an ID (which is used by the client as and index into the credential table) and time stamp - 1 (to insure that the client does not send and invalid time stamp). o In all future transactions the client sends the ID and encrypted time stamp and the server sends back time stamp - 1 encrypted by the CK. Figure 9.2.1 (page 42) outlines the steps involved.

The server can be made similarly. Two main problems associated with UNIX - style of authentication are

For DES authentication to work several things have to be satisfied : 1. The naming is UNIX oriented, and there is a possibilty of user ID's colliding. 2. There is no verifier, so credentials are easy to fake.

o Create an entry in the /etc/publickey database for the given username and the server machine. This can be done by using the newkey (1) command (for the superuser) or the chkey (1) command (for the user). This should create an entry of the form : user publickey (hex):secret key (also hex; encrypted)

9.2 DES Authentication

where user is user name or machine name. The secret key is encrypted by the login password of the user.

9.2.1 Background DES authentication solves some of the problems associated with UNIX-style authentication. This section explains the details of DES authentication scheme. The security of DES is based on the sender's ability to encrypt some data (current time in this case). Two things are necessary for this scheme to work: o time synchronization (client and server must agree on what the current time is). o same encryption key. When a client wishes to talk to a server, it generates a key called as conversation key CK. This is encrypted using a public key scheme (Diffie-Hellman in this case). In this scheme both the client and the server arrive at the same common key (192 bit) using the following method: A computes:

o Either at the time of login (by the login program) or later by the use of the keylogin (1) program (which prompts the user for the login password) the user can decrypt the secret key stored in the public key database. Once decrypted, the key is stored by the local keyserv daemon to be used for secure services. o The keyserv is an RPC program started from the /etc/rc.local registered with the portmapper with program number 100029. The protocol specification for this is in the file key_prot.x and the server routines in keyserv.c. The keyserv performs the following services: o o o o o

store the secret key. encrypt a conversation key decrypt a conversation key generate a secure conversation key get the credentials associated with a netname

ComKey(A, B) = (PK(B) ** SK(A)) mod MODULUS 9.2.2 User level interface to DES routines

while B Computes: ComKey(A, B) = (PK(A) ** SK(B)) mod MODULUS where PK is the Public key, SK is the Secret key and MODULUS is one of the

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

A netname is a string of printable characters and it is this name which is authenticated. Thus a netname is a credential. The library routine user2netname (host2netname if the process is running as a root process)

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 29 von 36 converts a domain-specific username, specified by the uid and domain to an OS independent netname. For example a netname might look like:

Seite 30 von 36 The whole suite of products is available as DCE. A good overview of DCE and its components is given in [4]. Some other standardization efforts like RPC and ISO and RPC and POSIX are covered in [3] [2].

[email protected] The client uses authdes_create () to build a DES handle. The synopsis follows

11. References 1. Birell, Andrew and Nelson, B. "Implementing Remote Procedures Calls" in ACM Transactions on Computer Systems, Feb. 1984, p 39 - 59.

AUTH * authdes_create(netname, window, syncaddr, deskeyp) char *netname; u_int window; struct sockaddr *syncaddr; des_block *deskeyp;

2. Bloomer, John. Power Programming with RPC, O'Reilly and Associates, 1992

The first argument is the netname as explained above. The second argument is the number of seconds upto which the credential is valid. The third argument syncaddr, is optional. If an address is given, the authentication system consults it to synchronize the client and server times. If NULL it assumes that no synchronization is needed. The final argument is again optional, being either a pointer to a DES key or NULL (assuming that the system generates a random key). On the server side the netname is converted to a domain-specific user ID by using the routine netname2user. Its synopsis follows

3. Corbin, John. The Art of Distributed Applications: Programming Techniques for Remote Procedure Calls, Springer-Verlag 1991. (includes the RFC 1057 for RPC and RFC 1014 for XDR) 4. Open Software Foundation. Introduction to OSF DCE, Prentice-Hall, 1992. 5. Weihl, William in Distributed Systems Distributed Systems, ACM press, 1989. 6. Stevens, Richard.

edited by Mullender, Sape.

UNIX Network Programming, Prentice Hall, 1990.

7. Sun Microsystems. Network Programmers Guide. int netname2user(netname, uidp, gidp, gidlenp, gidlistp) char *netname; int *uidp, *gidp, *gidlepn, *gidlistp;

8. Tanenbaum, Andrew. Computer networks, Prentice-Hall, 1988. 9. The USENET newsgroup "comp.client-server" and the ACM publication "Operating systems review" also contain ideas and discussions related to RPC's.

See appendix for an example of using the DES authentication. Add Figure 9.2

12. APPENDIX 10. HISTORY, FUTURE and STANDARDS XDR Many research and commercial RPC systems have been built since Nelson's thesis (1981). Some Operating systems like Amoeba and Mach have kernel support for RPC's. Some notable ones are [5] o o o o o o

This section has some programs referred to in the previous sections and some basic material about using XDR without RPC (for portability). As mentioned elsewhere there are two types of XDR functions, those that create and manipulate XDR streams and those that encode/decode data. There are three types of functions for creating and manipulating XDR streams

Courier in the XNS family of protocols The Cedar RPC system [1] Sun RPC [3] Argus HRPC developed at the University of Washington DCE based on Apollo's NCS (Network Computing System) [2] [4]

o Standard I/O Streams o Memory Streams o Record Streams

It seems that the RPC mechanism or a similarmessage passing scheme will serve as the basis for building distributed applications. Some of the additional features desirable in any implementation of the RPC Library are already becoming available. These additional capabilities include

/* An example to show the usage of XDR Memory streams */ #include #include

o manycast support o clean interface to asynchronous routines o support for lightweight processes so that the clients and servers can be multi-threaded

#define BUF_SIZE 255

A good comparison of the features of some of the commercial options is given in [2]. As the RPC mechanism becomes more widely used there is a need for standard RPC protocol and also for a standard API (Applications Programming Interface). This will allow applications to be ported easily and run on various platforms. Even though the goals of many offerings are similar they are not compatible (non-portable, for example ONC from Sun and NCS from Apollo). OSF has selected pieces from both but has chosen the NCS RPC mechanism over ONC RPC mechanism.

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

The RPC Library uses Memory Streams for encoding/decoding though it is transparent to the user. An example is given below to illustrate the use of memory streams.

26.08.99

int twod[2][2]={1,1,1,1}; main() { int result; int *mem; u_int size; XDR xdrs; size = RNDUP(BUF_SIZE); /* for rounding to BYTES_PER_XDR_UNIT */ mem = malloc(size);

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 31 von 36 xdrmem_create(&xdrs, mem, size, XDR_ENCODE); printf("res is %d\n", xdr_2d(&xdrs, twod));

Seite 32 von 36

/* MUST USE THIS FUNCTION */

svc_destroy(transp); exit(4);

}

} /* we fork() here and let child handle the svc_run() */ if ((pid = fork()) == 0) { svc_run(); } else if (pid) { /* parent */ execlp("/usr2/cis/psampat/rpcprogs/fork/clnt2", "clnt2", argv[2], NULL); } else { fprintf(stderr, "fork error\n"); exit (5); }

bool_t xdr_2d(xdrs, objp) XDR *xdrs; int *objp; { u_int sizep=4; return (xdr_array(xdrs, &objp, &sizep,4, sizeof(int), xdr_int)); } } Programs The first is the callback program with the client forking a child to handle the svc_run() (to wait for replies) and parent exec'ing another program (the same client, without the fork()) which calls a copy of the same server registered with a different program number. Only the new client file is given below. FILE: newclient.c #include #include #include #include #include "callback.h" main(argc, argv) int argc; char *argv[]; { SVCXPRT *transp; call_info callback_info; int stat1; u_long svc_register_transient(); void callback(); int pid; if (argc != 3) { fprintf(stderr, "usage: %s hostname1 hostname2\n", argv[0]); exit(1); } strncpy(callback_info.hostname, argv[1], MAX_HOST_NAME_LEN); if ((transp = svcudp_create(RPC_ANYSOCK)) == NULL ) { fprintf(stderr, "error: svcudp_create() failed\n"); exit(2); } callback_info.prognum = svc_register_transient (transp, EXAMPLE_CALLBACK_VERS, callback, IPPROTO_UDP); if (callback_info.prognum == 0) { fprintf(stderr, "error: couldn't register transient servive\n"); svc_destroy(transp); exit(3); } fprintf(stderr, "using transient program number: %lu for callback from astro\n", callback_info.prognum); callback_info.versnum = EXAMPLE_CALLBACK_VERS; callback_info.procnum = EXAMPLE_CALLBACK_PROC1; stat1 = callrpc(callback_info.hostname, EXAMPLE_PROG, EXAMPLE_VERS, EXAMPLE_PROC, xdr_call_info, &callback_info, xdr_void, (xdrproc_t)NULL); if (stat1 != 0) { fprintf(stderr, "error callrpc(): %s\n", clnt_sperrno((enum clnt_stat1)stat1)); svc_unregister(callback_info.prognum, EXAMPLE_CALLBACK_VERS);

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

void callback(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { switch (rqstp->rq_proc) { case NULLPROC: if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "error: svc_sendreply() for NULLPROC failed\n"); } break; case EXAMPLE_CALLBACK_PROC1: if (svc_getargs(transp, xdr_void, 0) == TRUE) { fprintf (stderr , "client got callback from astro server\n"); if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr,"error: exampleprog\n"); } } else { svcerr_decode(transp); return; } svc_unregister(rqstp->rq_prog, rqstp->rq_vers); svc_destroy(transp); exit(0); break; } } The second program is to test the DES authentication scheme. This is demo program provided by Sun in the rpcsrc distribution. This program did not execute because of the following reason: when the server calls the getpublickey() call, the function looks into a file called "publickey.byname" assuming NIS (Network Information Service) is running. The file /etc/publickey has the right looking entries though. FILE: whomai.x /* @(#)whoami.x

2.2 88/08/22 4.0 RPCSRC */

const WHOAMI_NGROUPS = 16; typedef string name; struct remote_identity { bool authenticated; /* TRUE if the server authenticates us */ name remote_username; /* login name */ name remote_realname; /* gcos-field name (long name) */ int uid; int gid; int gids; }; program WHOAMI { version WHOAMI_V1 {

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 33 von 36

Seite 34 von 36

/* * Report on the server's notion of the client's identity. * Will respond to AUTH_DES only. */ remote_identity WHOAMI_IASK(void) = 1; /* * Return server's netname. AUTH_NONE is okay. * This routine allows this server to be started under any uid, * and the client can ask it its netname for use in authdes_create(). */ name WHOAMI_WHORU(void) = 2; } = 1; } = 0x20002000;

clnt_pcreateerror(server); exit(1); } /* * Get network identifier for server machine. */ servername = whoami_whoru_1(nullp, cl); if (servername == NULL) { fprintf(stderr, "Trouble communicating with %s\n", clnt_sperror(cl, server)); exit(1); } else if (*servername[0] == '\0') { fprintf(stderr, "Could not determine netname of WHOAMI server.\n"); exit(1); } printf("Server's netname is: %s\n", *servername); /* * A wide window and no synchronization is used. Client and server * clock must be with five minutes of each other. */ cl->cl_auth = authdes_create(*servername, 300, ×erver, NULL); /* * Find out who I am, in the server's point of view. */ remote_me = whoami_iask_1(nullp, cl); if (remote_me == NULL) { fprintf(stderr, "Trouble getting my identity from \n%s\n", clnt_sperror(cl, server)); exit(1); } /* * Print out my identity. */ printf("My remote user name: %s\n", remote_me->remote_username); printf("My remote real name: %s\n", remote_me->remote_realname); exit(0);

FILE: rme.c (client file) #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)rme.c 2.2 88/08/22 4.0 RPCSRC"; #endif /* * rme.c: secure identity verifier and reporter: client side */ #include #include #include "whoami.h" #include #include #include /* * Before running this program, the user must have a key in the publickey * database, and must have logged in with a password (or used keylogin). * The user's machine and the server's machine must both be running keyserv. */ main(argc, argv) int argc; char *argv[]; { CLIENT *cl; char *server; remote_identity *remote_me; name *servername; void *nullp; struct sockaddr_in timeserver; timeserver.sin_family = AF_INET; timeserver.sin_port = 0; timeserver.sin_addr.s_addr = INADDR_ANY; if (argc != 2) { fprintf(stderr, "usage: %s host\n", argv[0]); exit(1); } /* * Remember what our command line argument refers to */ server = argv[1]; /* * Create client "handle" used for calling WHOAMI on the * server designated on the command line. We tell the rpc package * to use the "udp" protocol when contacting the server. */ cl = clnt_create(server, WHOAMI, WHOAMI_V1, "udp"); if (cl == NULL) { /* * Couldn't establish connection with server. * Print error message and die. */

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

} FILE: whoami_proc.c #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)whoami_proc.c 2.1 88/08/12 4.0 RPCSRC"; #endif /* * whoami_proc.c: secure identity verifier and reporter: server proc */ #include #include #include #include "whoami.h" #include extern char *strcpy(); /* * Report on the server's notion of the client's identity. */ remote_identity * whoami_iask_1(nullarg, rqstp) void *nullarg; struct svc_req *rqstp; { static remote_identity

26.08.99

whoisthem;

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

Seite 35 von 36 static char static char char

Seite 36 von 36

username[MAXNETNAMELEN+1]; realname[MAXNETNAMELEN+1]; /* really gecos field */ publickey[HEXKEYBYTES+1];

struct authdes_cred *des_cred; struct passwd *pwdent; switch (rqstp->rq_cred.oa_flavor) { case AUTH_DES: whoisthem.remote_username = username; whoisthem.remote_realname = realname; des_cred = (struct authdes_cred *) rqstp->rq_clntcred; /* * Check to see if the netname being used is in the public key * database (if not, reject this (potential) imposter). */ if (! getpublickey(des_cred->adc_fullname.name, publickey)) { svcerr_weakauth(rqstp->rq_xprt); fprintf(stderr, "this is in getpublickey call\n"); return(NULL); } /* * Get the info that the client wants. */ if (! netname2user(des_cred->adc_fullname.name, &whoisthem.uid, &whoisthem.gid, &whoisthem.gids.gids_len, whoisthem.gids.gids_val)) { /* netname not found */ whoisthem.authenticated = FALSE; strcpy(whoisthem.remote_username, "nobody"); strcpy(whoisthem.remote_realname, "INTERLOPER!"); whoisthem.uid = -2; whoisthem.gid = -2; whoisthem.gids.gids_len = 0; return(&whoisthem); } /* else we found the netname */ whoisthem.authenticated = TRUE; pwdent = getpwuid(whoisthem.uid); strcpy(whoisthem.remote_username, pwdent->pw_name); strcpy(whoisthem.remote_realname, pwdent->pw_gecos); return(&whoisthem); break; case AUTH_UNIX: case AUTH_NULL: default: svcerr_weakauth(rqstp->rq_xprt); return(NULL); } } /* * Return server's netname. AUTH_NONE is valid. * This routine allows this server to be started under any uid, * and the client can ask us our netname for use in authdes_create(). */ name * whoami_whoru_1(nullarg, rqstp) void *nullarg; struct svc_req *rqstp; { static name whoru; static char servername[MAXNETNAMELEN]; whoru = servername; if (! user2netname(servername, getuid(), NULL)) servername[0]='\0'; return(&whoru); }

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99

http://jedi.cis.temple.edu:8080/cis662/rpc/rpc.txt

26.08.99