Chapter 5. Programming with rpcgen

This chapter describes the rpcgen protocol compiler, which helps automate the process of writing RPC applications. When you use rpcgen, the compiler does most of the dirty work; you need only debug the main features of the application instead of spending time debugging network interface code.

Topics in this chapter include:

Introduction to the rpcgen Compiler

The rpcgen protocol compiler accepts remote program interface definitions written in RPC language and produces C language output for RPC programs. (See Chapter 7, “XDR and RPC Language Structure”, for details about writing program interface definitions using RPC language.) This C output includes:

  • skeleton versions of the client routines

  • a server skeleton

  • XDR filter routines for parameters and results

  • a header file that contains common definitions

  • ANSI C prototyped stub routines (optional)

The client skeletons interface with the RPC library and “hide” the network from its caller. Similarly, the server skeleton hides the network from server procedures that are to be invoked by remote clients.

The programmer writes server procedures, using any language that observes C language calling conventions, and links them with the server skeleton generated by rpcgen to produce an executable server program. To use a remote program, the programmer writes an ordinary main program that makes local procedure calls to the client skeletons produced by rpcgen.


Note: At present, the main program must be written in C or C++.

Linking the main program with rpcgen's skeletons creates an executable program. Options to rpcgen let you suppress stub generation, specify the transport to be used by the server stub, pass flags to cpp, or choose a different preprocessor. See rpcgen(1) for details.

Changing Local Procedures to Remote Procedures

Assume you have an application that runs on a single machine and you want to convert it to run over a network. The following code sample demonstrates the conversion for a program that prints a message to the console:

/*
 * printmsg.c: print a message on the console
 */ 
#include <stdio.h> 

main(int argc, char **argv)
{
    char *message;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <message>\n", argv[0]);
        exit(1);
    }
    message = argv[1];

    if (!printmessage(message)) {
        fprintf(stderr, "%s: couldn't print your message\n", 
                argv[0]);
        exit(1);
    } 
    printf("Message Delivered!\n");
    exit(0); 
}

/*
 * Print a message to the console. Return a boolean
 * indicating whether the message was actually printed.
 */
printmessage(char *msg)
{
    FILE *f;

    f = fopen("/dev/console", "w");
    if (f == NULL) {
        return(0);
    }
    fprintf(f, "%s\n", msg);
    fclose(f);
    return(1); 
}

And then, of course:

% cc printmsg.c -o printmsg
% printmsg "Hello, there"
Message Delivered!
%

If printmessage() were turned into a remote procedure, it could be called from anywhere in the network. It would be nice to be able to simply insert a keyword such as remote in front of a procedure to turn it into a remote procedure. Unfortunately, you have to live within the constraints of the C language, since it existed long before RPC did. But even without language support, it's not very difficult to make a procedure remote.

In general, it's necessary to figure out what the types are for all procedure inputs and outputs. In this case, there is a procedure, printmessage(), that takes a string as input and returns an integer as output. Knowing this, you can write a protocol specification in RPC language that describes the remote version of printmessage():

/*
 * msg.x: Remote message printing protocol
 */ 
program MESSAGEPROG {
    version MESSAGEVERS {
        int PRINTMESSAGE(string) = 1;
    } = 1;
} = 99;

Remote procedures are part of remote programs, so an entire remote program was declared here that contains the single procedure PRINTMESSAGE. This procedure was declared to be in version 1 of the remote program. No null procedure (procedure 0) is necessary, because rpcgen generates it automatically.


Note: Notice that everything is declared with all capital letters. This is not required, but it is a good convention to follow.

Notice that the argument type is string and not char *. This is because char * is ambiguous in C. Programmers usually intend it to mean a null-terminated string of characters, but it could also represent a pointer to a single character or a pointer to an array of characters. In RPC language, a null-terminated string is unambiguously called a string.

Next, define the remote procedure itself. The following example implements the PRINTMESSAGE procedure declared above:

/*
 *  msg_proc.c: implementation of the remote
 *  procedure "printmessage"
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <rpc/rpc.h>  /* Required. */
#define _RPCGEN_SVC   /*Selects server function prototypes.*/
#include "msg.h"      /* This will be generated by rpcgen. */

/*  Remote version of "printmessage" */
int *printmessage_1(msg, UNUSED)
/* UNUSED specified for prototype agreement */
char **msg;
struct svc_req *UNUSED;
{
    static int result;  /* must be static! */

    FILE *f;

    f = fopen("/dev/console", "w");
    if (f == NULL) {            /* failure! */
        result = 0;
        return (&result);
    }

    fprintf(f, "%s\n", *msg);   /* success! */
    fclose(f);
    result = 1;
    return (&result);
}

Notice that the declaration of the remote procedure printmessage_1() differs from the declaration of the local procedure printmessage() in three ways:

  • printmessage_1() takes a pointer to a string instead of a string itself, which is true of all remote procedures; they always take pointers to their arguments rather than the arguments themselves.

  • printmessage_1() returns a pointer to an integer instead of returning an integer itself. This is also generally true of remote procedures: they return a pointer to their results.

  • printmessage_1() has _1 appended to its name. In general, all remote procedures called by rpcgen are named using the following rule: the name in the program definition (here PRINTMESSAGE) is converted to all lowercase letters and an underscore (_) is appended to it, followed by the version number (here, 1).

Finally, declare the main client program that will call the remote procedure:

/*
 * rprintmsg.c: remote version of "printmsg.c"
 */ 
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <rpc/rpc.h>  /* Required. */
#define _RPCGEN_CLNT  /*selects client function prototypes*/
#include "msg.h"      /* This will be generated by rpcgen. */

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

    if (argc < 3) {
        fprintf(stderr, "usage: %s host message\n", argv[0]);
        exit(1);
    }

    /* save values of command line arguments */
    server = argv[1];
    message = argv[2];

    /* Create client "handle" used for calling MESSAGEPROG
     * on the server designated on the command line. We tell
     * the RPC package to use "tcp" when contacting the
     * server.
     */
    cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS,
                     "tcp");
    if (cl == NULL) {
        /* Couldn't establish connection with the server.
         * Print error message and exit.
         */
        clnt_pcreateerror(server);
        exit(1);
    }

    cl->cl_auth = authunix_create_default();

    /* Call the remote procedure "printmessage" on the
     * server */
    result = printmessage_1(&message, cl);
    if (result == NULL) {
        /*
         * An error occured while calling the server.
         * Print error message and exit.
         */
        clnt_perror(cl, server);
        exit(1);
    }

    /* Okay, we've *called( the server; now, did it print
     * the message? */
    if (*result == 0) {
        /*  The server was unable to print our message.
         *  Print error message and exit.
         */
        fprintf(stderr,
                "%s: %s couldn't print your message\n",
                argv[0], server);
        exit(1);
    }
    /* The message was printed on the server's console */
    printf("Message delivered to %s!\n", server);
    exit(0);
}

There are two things to note:

  • A client handle is created using the RPC library routine clnt_create(). This client handle will be passed to the stub routines that call the remote procedure.

  • The remote procedure printmessage_1() is called exactly the same way as it is declared in msg_proc.c except for the inserted client handle as the second argument.

Here's how to put the pieces together:

% rpcgen -P msg.x
% cc rprintmsg.c msg_clnt.c -o rprintmsg
rprintmsg.c:
msg_clnt.c:
% cc msg_proc.c msg_svc.c -o msg_server
msg_proc.c:
msg_svc.c:
%


Note: The command-line option –lsun used to be required to compile these programs, but it should no longer be used because libsun has been incorporated into libc.

Two programs were compiled: the client program rprintmsg and the server program msg_server. Before compilation, rpcgen was used to fill in the missing pieces. The following explains what rpcgen did with the input file msg.x:

  • rpcgen created a header file called msg.h that contained #defines for MESSAGEPROG, MESSAGEVERS, and PRINTMESSAGE for use in the other modules.

  • rpcgen created client stub routines in the msg_clnt.c file. In this case, there is only one, the printmessage_1() that was referred to from the printmsg client program. The name of the output file for client stub routines is always formed in this way: if the name of the input file is foo.x, the client stubs output file is called foo_clnt.c.

  • rpcgen created the server program that calls printmessage_1() in msg_proc.c. This server program is named msg_svc.c. The rule for naming the server output file is similar to the previous one: for an input file called foo.x, the output server file is named foo_svc.c.

The following shows the contents of the file msg_svc.c, as generated by rpcgen from the file msg.x. Note that all registerable RPC server functions take the same parameters as the function messageprog_1(), shown in this example.

/* msg_svc.c
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#include <bstring.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#define _RPCGEN_SVC
#include "msg.h"

static void messageprog_1(struct svc_req *, SVCXPRT *);

main(void)
{
    register SVCXPRT *transp;

    (void) pmap_unset(MESSAGEPROG, MESSAGEVERS);

    transp = svcudp_create(RPC_ANYSOCK);
    if (transp == NULL) {
        fprintf(stderr, "cannot create udp service.");
        exit(1);
    }
    if (!svc_register(transp, MESSAGEPROG, MESSAGEVERS, messageprog_1, IPPROTO_UDP)) {
        fprintf(stderr, "unable to register (MESSAGEPROG, MESSAGEVERS, udp).");
        exit(1);
    }

    transp = svctcp_create(RPC_ANYSOCK, 0, 0);
    if (transp == NULL) {
        fprintf(stderr, "cannot create tcp service.");
        exit(1);
    }
    if (!svc_register(transp, MESSAGEPROG, MESSAGEVERS, messageprog_1, IPPROTO_TCP)) {
        fprintf(stderr, "unable to register (MESSAGEPROG, MESSAGEVERS, tcp).");
        exit(1);
    }
    svc_run();
    fprintf(stderr, "svc_run returned");
    exit(1);
    /* NOTREACHED */
}

static void
messageprog_1(struct svc_req *rqstp, SVCXPRT *transp)
{
    union __svcargun {
        char *printmessage_1_arg;
    } argument;
    xdrproc_t xdr_argument, xdr_result;
    void *result;
    typedef void *(*__svcproc_t)(union __svcargun *, struct svc_req *);
    __svcproc_t local;

    switch (rqstp->rq_proc) {
    case NULLPROC:
        (void) svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
        return;

    case PRINTMESSAGE:
        xdr_argument = (xdrproc_t)xdr_wrapstring;
        xdr_result = (xdrproc_t)xdr_int;
        local = (__svcproc_t) printmessage_1;
        break;

    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)) {
        fprintf(stderr, "unable to free arguments");
        exit(1);
    }
    return;
}

Now you're ready to have some fun. For this example, the local machine is called bonnie and the remote machine is called clyde. First, copy the server to a remote machine and run it:

clyde% msg_server & 


Note: Server processes are run in the background because they never exit.

Next, on the local machine (bonnie), print a message on the remote machine's console:

bonnie% rprintmsg clyde "Hello, clyde" 
Message delivered to clyde!
bonnie%

The message will print on clyde's console. You can print a message on anybody's console (including your own) with this program if you are able to copy the server to that person's machine and run it.

Generating XDR Routines

The previous example demonstrated the automatic generation of client and server RPC code. You can also use rpcgen to generate XDR routines; that is, the routines necessary to convert local data structures into network format and vice versa. This example presents a complete RPC service, a remote directory listing service; rpcgen is used to generate stub routines and to generate the XDR routines.

This code is an example of a protocol description file:

/* dir.x: Remote directory listing protocol */
const MAXNAMELEN = 255; /*maximum length of directory entry*/
typedef string nametype<MAXNAMELEN>;  /* directory entry */
typedef struct namenode *namelist;    /* a link in listing */

/* A node in the directory listing */
struct namenode {
    nametype name;        /* name of directory entry */
    namelist next;        /* next entry */
};

/* The result of a READDIR operation. */
union readdir_res switch (int errno) {
case 0:
    namelist list;   /* no error: return directory listing */
default:
    void;        /* error occurred: nothing else to return */
};

/* The directory program definition */
program    DIRPROG {
        version DIRVERS {
            readdir_res READDIR(nametype) = 1;
        } = 1;
} = 76;


Note: Define types (such as readdir_res in the example above) by using the struct, union, and enum keywords; these keywords should not be used in subsequent declarations of variables of those types. For example, if you define a union foo, you should declare using only foo and not union foo. In fact, rpcgen compiles RPC unions into C structures; it is an error to declare them using the union keyword.

Running rpcgen –P on dir.x creates four output files. Three are the same as before: a header file, client stub routines, and a server skeleton. The fourth output file consists of the XDR routines necessary for converting the data types you declared into XDR format and vice versa. These routines are output in the file dir_xdr.c.

This example implements the READDIR procedure:

/* dir_proc.c: remote readdir implementation */
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <rpc/rpc.h>
#include <sys/dir.h>
#include "dir.h"

readdir_res *readdir_1(dirname, UNUSED)
/* UNUSED specified for prototype agreement */
nametype *dirname;
struct svc_req *UNUSED;
{
    DIR *dirp;
    struct direct *d;
    namelist nl;
    namelist *nlp;
    static readdir_res res;     /* must be static! */
    
    /* Open directory */
    dirp = opendir(*dirname);
    if (dirp == NULL) {
        res.errno = errno;
        return (&res);
    } 
    /* Free previous result */
    xdr_free(xdr_readdir_res, &res);
    
    /* Collect directory entries. Memory allocated here
     * will be freed by xdr_free next time readdir_1 is
     * called
     */
    nlp = &res.readdir_res_u.list;
    while (d = readdir(dirp)) {
        nl = *nlp = (namenode *) malloc(sizeof(namenode));
        nl->name = strdup(d->d_name);
        nlp = &nl->next;
    }
    *nlp = NULL;

    /* Return the result */
    res.errno = 0;
    closedir(dirp);
    return (&res);
}

This example shows the client-side program to call the server:

/*
 * rls.c: Remote directory listing client
 */
#include <stdio.h>
#include <errno.h>
#include <rpc/rpc.h>   /* always need this */
#define _RPCGEN_CLNT   /*selects client function prototypes*/
#include "dir.h"       /* will be generated by rpcgen */

main(argc, argv)
int argc;
char **argv;
{
    CLIENT *cl;
    char *server;
    char *dir;
    readdir_res *result;
    namelist nl;
    if (argc != 3) {
        fprintf(stderr, "usage: %s host directory\n",
                argv[0]);
        exit(1);
    }
    /* Remember what command line arguments refer to */
    server = argv[1];
    dir = argv[2]; 
    /* Create client "handle" used for calling
     * MESSAGEPROG on the server designated on the
     * command line. We tell the RPC package to use the
     * "tcp" protocol when contacting the server.
     */
    cl = clnt_create(server, DIRPROG, DIRVERS, "tcp");
    if (cl == NULL) {
        /* Couldn't establish connection with server.
         * Print error message and close up shop.
         */
        clnt_pcreateerror(server);
        exit(1);
    } 
    /* Call the remote procedure readdir() on the server */
    result = readdir_1(&dir, cl);
    if (result == NULL) {
        /* An error occurred while calling the server.
         * Print error message and exit.
         */
        clnt_perror(cl, server);
        exit(1);
    } 
    /* Okay, the remote procedure was called successfully. */

    if (result->errno != 0) {
        /* A remote system error occurred. Print error
         * message and exit.
         */
        errno = result->errno;
        perror(dir);
        exit(1);
    }
    /* Successfully got a directory listing.
     * Print it out.
     */
    for (nl = result->readdir_res_u.list; nl != NULL;
         nl = nl->next) {
        printf("%s\n", nl->name);
    }
    exit(0);
}

Finally, compile everything and run the server:

bonnie% rpcgen -P dir.x 
bonnie% cc rls.c dir_clnt.c dir_xdr.c -o rls 
rls.c:
dir_clnt.c:
dir_xdr.c:
bonnie% cc dir_svc.c dir_proc.c dir_xdr.c -o dir_svc 
dir_svc.c:
dir_proc.c:
dir_xdr.c:
bonnie% dir_svc & 

Now run the client from another machine:

clyde% rls bonnie /usr/pub 
.
..
apseqnchar
cateqnchar
eqnchar
psceqnchar
terminals
clyde%

You can test the client program and the server procedure together as a single program by linking them to each other, rather than linking to the client and server stubs. The procedure calls will be executed as ordinary local procedure calls, and the program can be debugged with a local debugger such as dbx. When the program is working, the client program can be linked to the client stub produced by rpcgen, and the server procedures can be linked to the server stub produced by rpcgen.

Note that if you link the programs in this way, you may want to comment out calls to RPC library routines, and have client-side routines call server routines directly.

The C Preprocessor

The C preprocessor is run on all input files before they are compiled, so all preprocessor directives are legal within a .x file.

Four symbols may be defined, depending on which output file is being generated. These symbols are listed in Table 5-1.

Table 5-1. C Preprocessor Symbol Definition

Symbol

Usage

RPC_CLNT

for client stub output

RPC_HDR

for header file output

RPC_SVC

for server skeleton output

RPC_XDR

for XDR routine output

rpcgen also does some preprocessing of its own. Any line that begins with a percent sign (%) is passed directly into the output file, without any interpretation of the line. The following example demonstrates the preprocessing features:

/* time.x: Remote time protocol */
program TIMEPROG {
        version TIMEVERS {
            unsigned int TIMEGET(void) = 1;
        } = 1;
} = 44;

#ifdef RPC_SVC
%u_int *timeget_1()
%{
%    static u_int thetime;
%
%    thetime = time(0);
%    return (&thetime);
%}
#endif


Note: The percent (%) feature is not generally recommended, since there is no guarantee that the compiler will put the output where you intended.


pcgen Programming Notes

This section describes ANSI C prototypes, timeout changes, broadcast on the server side, and information passed to server procedures.

Generating ANSI C Prototypes

To generate prototyped XDR and stub function declarations and definitions suitable for ANSI C, use the –P option to rpcgen—see rpcgen(1). The prototypes for the client and server-side stubs are different; their declarations in the generated header file are conditionally compiled with the value _RPCGEN_CLNT or _RPCGEN_SVC. If you write your own client or server code, you must define the appropriate value in your source files before including the generated header file.

For instance, in the remote message example from the “Changing Local Procedures to Remote Procedures” section, the file for client code uses:

#define _RPCGEN_CLNT
#include "msg.h"

and the file for server code uses:

#define _RPCGEN_SVC
#include "msg.h"

Client-side Timeout Changes

RPC sets a default timeout of 25 seconds for RPC calls when clnt_create() is used. This timeout may be changed using clnt_control(). This code fragment demonstrates the use of clnt_control():

struct timeval tv;
CLIENT *cl; 
cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "tcp");
if (cl == NULL) {
    exit(1);
}
/* change timeout to 1 minute */
tv.tv_sec = 60;
tv.tv_usec = 0;
clnt_control(cl, CLSET_TIMEOUT, &tv);

Server-side Broadcast Handling

When a procedure is known to be called via broadcast RPC, it is usually wise for the server not to reply unless it can provide some useful information to the client. This prevents the network from being flooded by useless replies.

To prevent the server from replying, a remote procedure can return NULL as its result, and the server code generated by rpcgen will detect the NULL and not send out a reply.

The next example shows a simple procedure that replies only if it thinks it is an NFS server. It assumes an NFS client won't have this file, which may not be valid.

void *reply_if_nfsserver(void)
{
    char notnull;  /* just here so you can use its address */ 
    if (access("/etc/exports", F_OK) < 0) {
        return (NULL);    /* prevent RPC from replying */
    }
    /*return non-null pointer so RPC will send out a reply*/
    return ((void *)&notnull);
}

Note that if a procedure returns type void *, it must return a non-NULL pointer if it wants RPC to reply to it.

Other Information Passed to Server Procedures

Server procedures will often want to know more about an RPC call than just its arguments. For example, getting authentication information is important to procedures that want to implement some level of security.

This extra information is actually supplied to the server procedure as a second argument, as shown in the following example. The previous printmessage_1() procedure has been rewritten to allow only root users to print a message to the console:

/*
 *  msg_proc.c: implementation of the remote
 *  procedure "printmessage"
 */
#include <stdio.h>
#include <syslog.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <rpc/rpc.h>  /* Required. */
#define _RPCGEN_SVC   /*Selects server function prototypes.*/
#include "msg.h"      /* This will be generated by rpcgen. */

#define MAX_LOG_MESSAGE 160

int printmessage_1_client_ok(struct svc_req *rqstp);

/*  Remote version of "printmessage" */
int *printmessage_1(msg, rq)
char **msg;
struct svc_req *rq;
{
    static int result;    /* Must be static or external */
    FILE *f;
    struct authunix_parms *aup;

    /* perform authentication checks on client credentials */
    if (! printmessage_1_client_ok(rq)) {
        result = 0;
        return (&result);
    }

    /*  Same code as before. */
    f = fopen("/dev/console", "w");
    if (f == NULL) {            /* failure! */
        result = 0;
        return (&result);
    }
    fprintf(f, "%s\n", *msg);   /* success! */
    fclose(f);
    result = 1;
    return (&result);
}

static int logging_successful_requests = 1;

/* This routine attempts to verify that the client user is
 * authorized access on the server host.  A true value is
 * returned to indicate that the client user is not authorized.
 * Otherwize the value returned is false. */
int
printmessage_1_client_ok(struct svc_req *rqstp)
{
    SVCXPRT *   transp  = rqstp->rq_xprt;
    char *      user    = NULL;
    uid_t       uid;
    struct authunix_parms *unix_cred;
    struct hostent *host_entry = NULL;
    struct passwd  *passwd_entry = NULL;
    char        log_message[MAX_LOG_MESSAGE];

    static u_long peer_addr        = 0;
    static u_long client_host_addr = 0;
    static char * client_host      = NULL;

    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:            /* invalid credentials */
        sprintf(log_message, "Rejected request, "
                 "invalid credentials, type %d", rqstp->rq_cred.oa_flavor);
        syslog(LOG_NOTICE | LOG_AUTH, log_message);
        return 0;
    }

    if (transp->xp_raddr.sin_port >= IPPORT_RESERVED) {
        sprintf(log_message, "Rejected request, "
                 "non-priviledged port %d", transp->xp_raddr.sin_port);
        syslog(LOG_NOTICE | LOG_AUTH, log_message);
        return 0;
    }
    /* Determine the client host name and address. */
    if (peer_addr != transp->xp_raddr.sin_addr.s_addr) {
        host_entry = gethostbyaddr(&transp->xp_raddr.sin_addr,
                                    sizeof(struct in_addr),
                                    AF_INET);
        if (host_entry == NULL) {
            sprintf(log_message, "Rejected request, "
                     "unknown client host at address 0x%08x",
                     transp->xp_raddr.sin_addr);
            syslog(LOG_NOTICE | LOG_AUTH, log_message);
            return 0;
        }
        peer_addr = transp->xp_raddr.sin_addr.s_addr;
        if (client_host != NULL) {
            free(client_host);
        }
        client_host = strdup(host_entry->h_name);
        client_host_addr = *(u_long *) host_entry->h_addr;
    }

    /* Determine the user name. */
    passwd_entry = getpwuid(uid);
    if (passwd_entry == NULL) {
        sprintf(log_message, "Rejected request, "
                 "unknown uid %d from host %s",
                 uid, client_host);
        syslog(LOG_NOTICE | LOG_AUTH, log_message);
        return 0;
    }
    user = strdup(passwd_entry->pw_name);
    if (passwd_entry->pw_passwd != NULL &&
        *passwd_entry->pw_passwd != '\0' &&
        ruserok(client_host, 1, user, "root") < 0) {
        sprintf(log_message, "Rejected request by %s at %s",
                 user, client_host);
        syslog(LOG_NOTICE | LOG_AUTH, log_message);
        free(user);
        return 0;
    }

    if (logging_successful_requests) {
        if (user != NULL) {
            sprintf(log_message, "Granted request by %s at %s",
                     user, client_host);
        } else {
            sprintf(log_message, "Granted request by uid %d at %s",
                     uid, client_host);
        }
        syslog(LOG_INFO | LOG_AUTH, log_message);
    }
    if (user != NULL) {
        free(user);
    }
    return 1;
} /* printmessage_1_client_ok */