Table of Contents | Previous | Next | Index | Bookshelf

NSAPI Programmer's Guide for iPlanet Web Server


Chapter 6
Examples of Custom SAFs

This chapter discusses examples of custom Sever Application Functions (SAFs) for each directive in the request-response process. You may wish to use these examples as the basis for implementing your own custom SAFs. For more information about creating your own custom SAFs, see Chapter 4, "Creating Custom SAFs."

Before writing custom SAFs, you should be familiar with the request-response process (discussed in Chapter 1, "Basics of Server Operation") and the role of the configuration file obj.conf (discussed in Chapter 2, "Syntax and Use of obj.conf").

Before writing your own SAF, check if an existing SAF serves your purpose. The pre-defined SAFs are discussed in Chapter 3, "Predefined SAFs and the Request Handling Process."

For a list of the NSAPI functions for creating new SAFs, see Chapter 5, "NSAPI Function Reference."

This chapter has the following sections:

Examples in the Build

The nsapi/examples/ or plugins/nsapi/examples subdirectory within the server installation directory contains examples of source code for SAFs.

You can use the example.mak makefile in the same directory to compile the examples and create a library containing the functions in all the example files.

To test an example, load the examples shared library into the iPlanet Web Server by adding the following directive in the Init section of obj.conf:

Init fn=load-modules shlib=examples.so/dll
funcs=function1,function2,function3
The funcs parameter specifies the functions to load from the shared library.

If the example uses an initialization function, be sure to specify the initialization function in the funcs argument to load-modules, and also add an Init directive to call the initialization function.

For example, the PathCheck example implements the restrict-by-acf function, which is initialized by the acf-init function. The following directive loads both these functions:

Init fn=load-modules yourlibrary funcs=acf-init,restrict-by-acf
The following directive calls the acf-init function during server initialization:

Init fn=acf-init file=extra-arg
To invoke the new SAF at the appropriate step in the response handling process, add an appropriate directive in the object to which it applies, for example:

PathCheck fn=restrict-by-acf
After modifying obj.conf manually, you'll need to load the configuration files in the Server Manager interface if it is open. If it is not open, you'll need to stop and start the server to have your changes take effect, since the server loads obj.conf during initialization.

After adding new Init directives to obj.conf, you always need to restart the iPlanet Web Server to load the changes, since Init directives are only applied during server initialization.

AuthTrans Example

This simple example of an AuthTrans function demonstrate how to use your own custom ways of verifying that the username and password that a remote client provided is accurate. This program uses a hard coded table of usernames and passwords and checks a given user's password against the one in the static data array. The userdb parameter is not used in this function.

AuthTrans directives work in conjunction with PathCheck directives. Generally, an AuthTrans function checks if the username and password associated with the request are acceptable, but it does not allow or deny access to the request -- it leaves that to a PathCheck function.

AuthTrans functions get the username and password from the headers associated with the request. When a client initially makes a request, the username and password are unknown so the AuthTrans function and PathCheck function work together to reject the request, since they can't validate the username and password. When the client receives the rejection, the usual response is for it to pop up a dialog box asking the user for their username and password, and then the client submits the request again, this time including the username and password in the headers.

In this example, the hardcoded-auth function, which is invoked during the AuthTrans step, checks if the username and password correspond to an entry in the hardcoded table of users and passwords.

Installing the Example

To install the function on the iPlanet Web Server, add the following Init directive at the beginning of obj.conf to load the compiled function:

Init fn=load-modules shlib=yourlibrary funcs=hardcoded-auth
Inside the default object in obj.conf add the following AuthTrans directive:

AuthTrans fn=basic-auth auth-type="basic" userfn=hardcoded-auth
userdb=unused
Note that this function does not actually enforce authorization requirements, it only takes given information and tells the server if it's correct or not. The PathCheck function require-auth performs the enforcement, so add the following PathCheck directive also:

PathCheck fn=require-auth realm="test realm" auth-type="basic"

Source Code

The source code for this example is in the auth.c file in the nsapi/examples/ or plugins/nsapi/examples subdirectory of the server root directory.

#include nsapi.h
typedef struct {
   char *name;
   char *pw;
} user_s;
/* This is the array of users and passwords */
static user_s user_set[] = {
   {"nikki", "bones"},
   {"boots", "frisbee"},
   {"jack", "steak"},
   {"topper", "kibble"},
   {"beulah", "rollover"},
   {NULL, NULL}
};
#include "frame/log.h"
#ifdef __cplusplus
extern "C"
#endif
/* hardcoded_auth is our custom SAF */
NSAPI_PUBLIC int hardcoded_auth(pblock *param, Session *sn, Request *rq)
{
   /* Parameters given to us by auth-basic.
   * Use pblock_findval to find the value of a specific parameter.
   */
   /* pwfile will be null, but that's OK because we don't use it */
   char *pwfile = pblock_findval("userdb", param);
   /* Get the user and password */
   char *user = pblock_findval("user", param);
   char *pw = pblock_findval("pw", param);
   /* Temp variables */
   register int x;

   /* Iterate over the hardcoded array of users and passwords
   * to see if the current user is in there.
   */
   for(x = 0; user_set[x].name != NULL; ++x) {
      /* If this isn't the user we want, keep going */
      if(strcmp(user, user_set[x].name) != 0)
      continue;
         /* If this is the user we want, verify password.
         * If password is wrong, log an error and return REQ_NOACTION.
         */
         if(strcmp(pw, user_set[x].pw)) {
            log_error(LOG_SECURITY, "hardcoded-auth", sn, rq,
                  "user %s entered wrong password", user);
            return REQ_NOACTION;
            }
         /* If username and password are vaild, return REQ_PROCEED */
         return REQ_PROCEED;
   }
   /* If the username was not found in our array, log an error 
   * and return REQ_NOACTION.
   */
   log_error(LOG_SECURITY, "hardcoded-auth", sn, rq, 
      "unknown user %s", user);
    return REQ_NOACTION;
}

NameTrans Example

The ntrans.c file in the nsapi/examples/ or plugins/nsapi/examples subdirectory of the server root directory contains source code for two example NameTrans functions:

This section discusses the first example. Look at the source code in ntrans.c for the second example.

Note: The main thing that a NameTrans function usually does is to convert the logical URL in ppath in rq->vars to a physical pathname. However, the example discussed here, explicit_pathinfo, does not translate the URL into a physical pathname, it changes the value of the requested URL. See the second example, https_redirect, in ntrans.c for an example of a NameTrans function that converts the value of ppath in rq->vars from a URL to a physical pathname.

The explicit_pathinfo example allows URLs to explicitly include extra path information for use by a CGI program. The extra path information is delimited from the main URL by a specified separator, such as a comma.

For example:

http://server-name/cgi/marketing,/jan/releases/hardware
In this case, the URL of the requested resource (which would be a CGI program) is http://server-name/cgi/marketing and the extra path information to give to the CGI program is /jan/releases/hardware.

When choosing a separator, be sure to pick a character that will never be used as part of the real URL.

The explicit_pathinfo function reads the URL, strips out everything following the comma and puts it in the path-info field of the vars field in the request object (rq->vars). CGI programs can access this information through the PATH_INFO environment variable.

One side effect of explicit_pathinfo is that the SCRIPT_NAME CGI environment variable has the separator character tacked on the end.

Normally NameTrans directives return REQ_PROCEED when they change the path so that the server does not process any more NameTrans directives. However, in this case we want name translation to continue after we have extracted the path info, since we have not yet translated the URL to a physical pathname.

Installing the Example

To install the function on the iPlanet Web Server, add the following Init directive at the beginning of obj.conf to load the compiled function:

Init fn=load-modules shlib=yourlibrary funcs=explicit-pathinfo
Inside the default object in obj.conf add the following NameTrans directive:

NameTrans fn=explicit-pathinfo separator=","
This NameTrans directive should appear before other NameTrans directives in the default object.

Source Code

This example is in the ntrans.c file in the nsapi/examples/ or plugins/nsapi/examples subdirectory of the server root directory.

#include nsapi.h
#include <string.h>      /* strchr */
#include "frame/log.h"   /* log_error */

#ifdef __cplusplus
extern "C"
#endif
/* explicit-pathinfo is our new NameTrans SAF */
NSAPI_PUBLIC int explicit_pathinfo(pblock *pb, Session *sn, Request *rq)
{
   /* The separator parameter is specified in the directive line
   * in obj.conf that invokes this function.
   * The separator separates the URL of the requested resource
   * from the extra path information to put into PATH_INFO
   */
   char *sep = pblock_findval("separator", pb);
   /* Get the ppath from the vars field of the request object*/
   char *ppath = pblock_findval("ppath", rq->vars);
   /* Temp var */
   char *t;
   /* Verify correct usage */
   if(!sep) {
      log_error(LOG_MISCONFIG, "explicit-pathinfo", sn, rq,
            "missing parameter (need root)");
      /* When we abort, the default status code is 500 Server Error *
      return REQ_ABORTED;
   }
   /* Check for separator. If not there, don't do anything */
   t = strchr(ppath, sep[0]);
   if(!t)
      return REQ_NOACTION;
   /* If path contains separator, truncate path at the separator */
   *t++ = '\0';
   /* Put the extra path info into the path-info field of rq->vars*/
   pblock_nvinsert("path-info", t, rq->vars);
   /* Normally NameTrans functions return REQ_PROCEED when they change
   * the path. However, we want name translation to continue after we
   * have extracted the extra path info since we haven't translated the
   * URL to a physical file name yet.
   */
   return REQ_NOACTION;
}

PathCheck Example

The example in this section demonstrates how to implement a custom SAF for performing path checks. This example simply checks if the requesting host is on a list of allowed hosts.

The Init function acf-init loads a file containing a list of allowable IP addresses with one IP address per line. The PathCheck function restrict_by_acf gets the IP address of the host that is making the request and checks if it is on the list. If the host is on the list, it is allowed access otherwise access is denied.

For simplicity, the stdio library is used to scan the IP addresses from the file.

Installing the Example

To load the shared object containing your functions add the following line in the Init section of the obj.conf file:

Init fn=load-modules yourlibrary funcs=acf-init,restrict-by-acf
To call acf-init to read the list of allowable hosts, add the following line to the Init section in obj.conf. (This line must come after the one that loads the library containing acf-init).

Init fn=acf-init file=fileContainingHostsList
To execute your custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file:

PathCheck fn=restrict-by-acf

Source Code

The source code for this example is in pcheck.c in the nsapi/examples/ or plugins/nsapi/examples subdirectory within the server root directory.

#include nsapi.h
/* Set hosts to NULL to prevent problems if acf-init is not called */
static char **hosts = NULL;
#include <stdio.h>
#include "base/daemon.h"
#include "base/util.h"          /* util_sprintf */
#include "frame/log.h"          /* log_error */
#include "frame/protocol.h"     /* protocol_status */
/* The longest line we'll allow in an access control file */
#define MAX_ACF_LINE 256
#ifdef __cplusplus
extern "C"
#endif
/* Used to free static array on restart */
NSAPI_PUBLIC void acf_free(void *unused)
{
   register int x;
   for(x = 0; hosts[x]; ++x)
      FREE(hosts[x]);
      FREE(hosts);
      hosts = NULL;
}
/* This is the initialization function that gets invoked
* during the Init stage in obj.conf.
* This function opens the custom file and reads the IP addresses
* of the allowed hosts into the global variable hosts.
*/
NSAPI_PUBLIC int acf_init(pblock *pb, Session *sn, Request *rq)
{
   /* The file parameter is specified in the PathCheck directive
   * that invokes this function.
   */
   char *acf_file = pblock_findval("file", pb);
   /* Working variables */
   int num_hosts;
   FILE *f;
   char err[MAGNUS_ERROR_LEN];
   char buf[MAX_ACF_LINE];
   /* Check usage. Note: Init functions have special error logging */
   if(!acf_file) {
      util_sprintf(err, "missing parameter to acf_init (need file)");
      pblock_nvinsert("error", err, pb);
      return REQ_ABORTED;
   }
   /* Open the file containing the list of allowed hosts */
   f = fopen(acf_file, "r");
   /* Did we open it? */
   if(!f) {
      util_sprintf(err, "can't open access control file %s (%s)",
      acf_file, system_errmsg());
      pblock_nvinsert("error", err, pb);
      return REQ_ABORTED;
   }
   /* Initialize hosts array */
   num_hosts = 0;
   hosts = (char **) MALLOC(1 * sizeof(char *));
   hosts[0] = NULL;
   while(fgets(buf, MAX_ACF_LINE, f)) {
      /* Blast linefeed that stdio helpfully leaves on there *
         buf[strlen(buf) - 1] = '\0';
         hosts = (char **) REALLOC(hosts, (num_hosts + 2) * sizeof(char *));
         hosts[num_hosts++] = STRDUP(buf);
         hosts[num_hosts] = NULL;
   }
   /* Close the file */
   fclose(f);
   /* At restart, free hosts array */
   daemon_atrestart(acf_free, NULL);
   return REQ_PROCEED;
}
/* restrict_by_acf is the new PathCheck SAF.
* It checks if the requesting host is in the list of allowed hosts.
* The list of hosts is in the hosts[] array which was set up by
* acf-init during server initialization.
*/
NSAPI_PUBLIC int restrict_by_acf(pblock *pb, Session *sn, Request *rq)
{
   /* No need to get any parameters from the directive in obj.conf. */
   /* Working variables */
   /* Get the client's ip address */
   char *remip = pblock_findval("ip", sn->client);
   register int x;
   /* If the hosts variable is not set, it means acf-init was not called
   * so log an error and return REQ_ABORTED
   if(!hosts) {
      log_error(LOG_MISCONFIG, "restrict-by-acf", sn, rq,
         "restrict-by-acf called without call to acf-init");
         /* The default abort status code is 500 Server Error */
      return REQ_ABORTED;
   }
   /* if hosts is defined, iterate through the hosts list to see 
   * if the host that sent the request is allowed access.
   * If the host is on the list, all is well, so return REQ_NOACTION.
   */
   for(x = 0; hosts[x] != NULL; ++x) {
      if(!strcmp(remip, hosts[x]))
         return REQ_NOACTION;
      }
   /* If the requesting host is not on the list, access is denied */
   /* Set response code to forbidden and return an error. *
   protocol_status(sn, rq, PROTOCOL_FORBIDDEN, NULL);
   return REQ_ABORTED;
}

ObjectType Example

The example in this section demonstrates how to implement html2shtml, a custom SAF that instructs the server to treat a .html file as a .shtml file if a .shtml version of the requested file exists.

A well-behaved ObjectType function checks if the content type is already set, and if so, does nothing except return REQ_NOACTION.

   if(pblock_findval("content-type", rq->srvhdrs))
      return REQ_NOACTION;
The main thing an ObjectType directive needs to do is to set the content type (if it is not already set). This example sets it to magnus-internal/parsed-html in the following lines:

/* Set the content-type to magnus-internal/parsed-html */
pblock_nvinsert("content-type", "magnus-internal/parsed-html",
   rq->srvhdrs);
The html2shtml function looks at the requested file name. If it ends with .html, the function looks for a file with the same base name, but with the extension .shtml instead. If it finds one, it uses that path and informs the server that the file is parsed HTML instead of regular HTML. Note that this requires an extra stat call for every HTML file accessed.

Installing the Example

To load the shared object containing your function, add the following line in the Init section of the obj.conf file :

Init fn=load-modules shlib=yourlibrary funcs=html2shtml
To execute the custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file:

ObjectType fn=html2shtml

Source Code

The source code for this example is in otype.c in the nsapi/examples/ or plugins/nsapi/examples subdirectory within the server root directory.

# include nsapi.h
#include <string.h>     /* strncpy */
#include "base/util.h"
#ifdef __cplusplus
extern "C"
#endif
/* This is the custom SAF. Whenever a request is made for an
* html file, this SAF checks if another file with the same base name
* but with a .shtml extension exists. If it does, it sets the
* type to magnus-internal/parsed-html.
*/
NSAPI_PUBLIC int html2shtml(pblock *pb, Session *sn, Request *rq)
{
   /* No need to get any parameters from the directive in obj.conf. */
   /* Work variables */
   /* Get the path from the request object */
   pb_param *path = pblock_find("path", rq->vars);
   struct stat finfo;
   char *npath;
   int baselen;
   /* This is a nicely behaved ObjectType function, so obey the rules 
   * and if the type has already been set, don't do anything.
   */
   if(pblock_findval("content-type", rq->srvhdrs))
      return REQ_NOACTION;
   /* If path does not end in .html, don't do anything */
   baselen = strlen(path->value) - 5;
   if(strcasecmp(&path->value[baselen], ".html") != 0)
      return REQ_NOACTION;
   /* If we got this far, the file ends in .html */
   /* Add 1 character to make room to convert html to shtml */
   npath = (char *) MALLOC((baselen + 5) + 1 + 1);
   strncpy(npath, path->value, baselen);
   strcpy(&npath[baselen], ".shtml");
   /* If the .shtml version of the file does not exist, 
   * don't do anything */
   if(stat(npath, &finfo) == -1) {
      FREE(npath);
      return REQ_NOACTION;
   }
   /* If the .shtml version of the file does exist, change the pathname
   * of the requested file to the .shtml version
   */
   FREE(path->value);
   path->value = npath;
   /* The server caches the stat() of the current path. Update it. *
   (void) request_stat_path(NULL, rq);
   /* Set the content-type to magnus-internal/parsed-html */
   pblock_nvinsert("content-type", "magnus-internal/parsed-html",
      rq->srvhdrs);
   /* We have successfully set the type, so return REQ_PROCEED */
   return REQ_PROCEED;
}

Service Example

This section discusses a very simple Service function called simple_service. All this function does is send a message in response to a client request. The message is initialized by the init_simple_service function during server initialization.

For a more complex example, see the file service.c in the examples directory, which is discussed in "More Complex Service Example."

Installing the Example

To load the shared object containing your functions add the following line in the Init section of the obj.conf file:

Init fn=load-modules shlib=yourlibrary funcs=simple-service-init,simple-service
To call the simple-service-init function to initialize the message representing the generated output, add the following line to the Init section in obj.conf. (This line must come after the one that loads the library containing simple-service-init).

Init fn=simple-service-init 
generated-output="<H1>Generated output msg</H1>"
To execute the custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file:

Service type="text/html" fn=simple-service
The type="text/html" argument indicates that this function is invoked during the Service stage only if the content-type has been set to text/html.

Source Code

#include <nsapi.h> 
static char *simple_msg = "default customized content";
/* This is the initialization function.
 * It gets the value of the generated-output parameter
 * specified in the Init directive in obj.conf
*/
NSAPI_PUBLIC int init-simple-service(pblock *pb, Session *sn,
Request *rq)
{
   /* Get the message from the parameter in the directive in obj.conf */
   simple_msg = pblock_findval("generated-output", pb);   
   return REQ_PROCEED;
}
/* This is the customized Service SAF.
 * It sends the "generated-output" message to the client.
*/
NSAPI_PUBLIC int simple-service(pblock *pb, Session *sn, Request *rq)
{    
   int return_value;
   char msg_length[8];   
   /* Use the protocol_status function to set the status of the 
   * response before calling protocol_start_response.
   */
   protocol_status(sn, rq, PROTOCOL_OK, NULL);
   /* Although we would expect the ObjectType stage to 
   * set the content-type, set it here just to be
   * completely sure that it gets set to text/html.
   */
   param_free(pblock_remove("content-type", rq->srvhdrs));
   pblock_nvinsert("content-type", "text/html", rq->srvhdrs);
   /* If you want to use keepalive, need to set content-length header.   
   * The util_itoa function converts a specified integer to a string,
   * and returns the length of the string. Use this
   * function to create a textual representation of a number.
   */
   util_itoa(strlen(simple_msg), msg_length);
   pblock_nvinsert("content-length", msg_length, rq->srvhdrs);
   /* Send the headers to the client*/
   return_value = protocol_start_response(sn, rq);
   if (return_value == REQ_NOACTION) {
      /* HTTP HEAD instead of GET */
      return REQ_PROCEED;
   }
   /* Write the output using net_write*/
   return_value = net_write(sn->csd, simple_msg, strlen(simple_msg));
   if (return_value == IO_ERROR) {
      return REQ_EXIT;
   }
   return REQ_PROCEED; 
} 

More Complex Service Example

The send-images function is a custom SAF which replaces the doit.cgi demonstration available on the Netscape home pages. When a file is accessed as /dir1/dir2/something.picgroup, the send-images function checks if the file is being accessed by a Mozilla/1.1 browser. If not, it sends a short error message. The file something.picgroup contains a list of lines, each of which specifies a filename followed by a content-type (for example, one.gif image/gif).

To load the shared object containing your function, add the following line at the beginning of the obj.conf file:

Init fn=load-modules shlib=yourlibrary funcs=send-images
Also, add the following line to the mime.types file:

type=magnus-internal/picgroup exts=picgroup
To execute the custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file (send-images takes an optional parameter, delay, which is not used for this example):

Service method=(GET|HEAD) type=magnus-internal/picgroup fn=send-images 
The source code is in service.c in the nsapi/examples/ or plugins/nsapi/examples subdirectory within the server root directory.

AddLog Example

The example in this section demonstrates how to implement brief-log, a custom SAF for logging only three items of information about a request: the IP address, the method, and the URI (for example, 198.93.95.99 GET /jocelyn/dogs/homesneeded.html).

Installing the Example

To load the shared object containing your functions add the following line in the Init section of the obj.conf file:

Init fn=load-modules shlib=yourlibrary funcs=brief-init,brief-log
To call brief-init to open the log file, add the following line to the Init section in obj.conf. (This line must come after the one that loads the library containing brief-init).

Init fn=brief-init file=/tmp/brief.log
To execute your custom SAF during the AddLog stage for some object, add the following line to that object in the obj.conf file:

AddLog fn=brief-log

Source Code

The source code is in addlog.c is in the nsapi/examples/ or plugins/nsapi/examples subdirectory within the server root directory.

#include nsapi.h
#include "base/daemon.h"    /* daemon_atrestart */
#include "base/file.h"      /* system_fopenWA, system_fclose */
#include "base/util.h"         /* sprintf */
/* File descriptor to be shared between the processes */
static SYS_FILE logfd = SYS_ERROR_FD;
#ifdef __cplusplus
extern "C"
#endif
/* brief_terminate closes the log file when the server is restarted */
NSAPI_PUBLIC void brief_terminate(void *parameter)
{
   system_fclose(logfd);
   logfd = SYS_ERROR_FD;
}
/* brief-init opens the log file when the server is initialized */
NSAPI_PUBLIC int brief_init(pblock *pb, Session *sn, Request *rq)
{
   /* Get the file parameter from the directive in obj.conf that
   * invokes this function.
   */
   char *fn = pblock_findval("file", pb);
   /* If no file name is given, abort the process */
   if(!fn) {
      pblock_nvinsert("error", "brief-init: needs a file name",pb);
      return REQ_ABORTED;
   }
   /* Open the log file */
   logfd = system_fopenWA(fn);
   /* If a sys error occurs, abort the process */
   if(logfd == SYS_ERROR_FD) {
      pblock_nvinsert("error", "brief-init: needs a file name", pb);
      return REQ_ABORTED;
   }
    /* Close log file when server is restarted *
   daemon_atrestart(brief_terminate, NULL);
   return REQ_PROCEED;
}
NSAPI_PUBLIC int brief_log(pblock *pb, Session *sn, Request *rq)
{
    /* No need to get parameters from the directive in obj.conf */
   /* Get the method, uri, and ip from the request object */
   char *method = pblock_findval("method", rq->reqpb);
   char *uri = pblock_findval("uri", rq->reqpb);
   char *ip = pblock_findval("ip", sn->client);
   /* Create the log message string */
   char *logmsg;
   int len;
   /* Put the ip, method, and uri in the log message */
   logmsg = (char *) MALLOC(strlen(ip) + 1 + strlen(method) + 1 +
      strlen(uri) + 1 + 1);
   len = util_sprintf(logmsg, "%s %s %s\n", ip, method, uri);
   /* Write the log message to the log file.
   * The atomic version uses locking to prevent interference
   */
   system_fwrite_atomic(logfd, logmsg, len);
   /* free the log message string */
   FREE(logmsg);
   /* Log entry has been successfully written so proceed */
   return REQ_PROCEED;
}

Table of Contents | Previous | Next | Index | Bookshelf

Last Updated: 03/01/00 09:22:19

© Copyright © 2000 Sun Microsystems, Inc. Some preexisting portions Copyright © 2000 Netscape Communications Corp. All rights reserved.