/*	    Configuration Manager for RPC			        cm_env.c
**	    =============================
**
**  This module provides address translation facilities for an RPC system,
**  using the process environment (on VMS: logical name tables) as a
**  local database, and then the WAY name server.
**
**  Authors:
**
**	Roberto Bagnara CERN, Univ. Pisa (RB)
**	Tim Berners-Lee CERN (TBL)
**
**  History:
**
**	 4 Dec 89   Adapted from cm_env.c (RB)
**	   Dec 89   Various small bug fixes (TBL)
*/

#define RPC_S_NO_WAY_ACCESS 120

/*
**	Include files.
**	--------------
*/

#include <stdio.h>
#include <string.h>		/* ANSI form of strings.h */

#include "rpcrts.h"		/* RPC macros. */
#include "syspec.h"	/* Condition the internal macros */
#include "rpc_code.h"		/* Macros for internal use by RPC code. */
#include "way.h"

/*
**	External functions.
**	-------------------
*/

#ifdef __STDC__
extern char *malloc(int size);
extern void free(char *ptr);
extern void abort(void);
extern void rpc_open(rpc_status *status, int *phandle, rpc_name service_name);
extern void rpc_close(rpc_status *status, int handle);
extern way_status way_register(const char *service, int version,
				int compatibility, const char *address);
extern way_status way_unregister(const char *service, int version,
							const char *address);
extern way_status way_request(const char *service,
	int version, int compatibility,
	const char *pref_addresses, int address_length, char *address);
extern rpc_status way_register_errors(void);
#else
extern char *malloc();
extern void free();
extern void abort();
extern void rpc_open();
extern void rpc_close();
extern way_status way_register();
extern way_status way_unregister();
extern way_status way_request();
extern rpc_status way_register_errors();
#endif


/*
**
**	Capitalise a character.
**	-----------------------
**
**	
**	char uppercase(char c)
**
**  Parameters:	c		character to be upper-cased.
**
**  Return:			upper-cased character.
**
**  On entry:	none
**
**  On exit:	none
**
**  Description:
**
**	Self explanatory.
**
**  History:
**
**	14 Aug 89	Cleaned up. (RB)
**
*/
#ifdef UPPERCASE_LOGICALS
#ifdef __STDC__
char uppercase(char c)
#else
char uppercase(c)
char c;
#endif
{
    return ((c >= 'a') && (c <= 'z')) ? (c - 'a' + 'A') : c;
}
#endif


/*
**
**	Capitalise a string.
**	--------------------
**
**	
**	void capital(char *string)
**
**  Parameters:	string		pointer to string to be capitalised.
**
**  Return:	none
**
**  On entry:	string		null terminated string.
**
**  On exit:	string		same as above but capitalised.
**
**  Description:
**
**	Self explanatory.
**
**  History:
**
**	14 Aug 89	Cleaned up. (RB)
**
*/

#ifdef UPPERCASE_LOGICALS
#ifdef __STDC__
void capital(char *string)
#else
void capital(string)
char *string;
#endif
{
    register char *p;

    for (p = string; *p; p++)
	*p = uppercase(*p);
}
#endif


/*
**
**	Recursively translates logical names.
**	-------------------------------------
**
**	
**	rpc_status translate(char *buffer, int buflen, int curlevel,
**				    int maxlevel, char *previous[])
**
**  Parameters:	buffer		pointer to string containing the name to
**				be translated on entry, its translation
**				on exit;
**
**		buflen		maximum length of the translation;
**
**		curlevel	current recursion level;
**
**		maxlevel	maximum recursion level;
**
**		previous	array of pointers to previous translation
**				strings.
**
**  Return:			RPC_S_NO_TRANSLATION if no translation exist
**					or if a translation loop was detected;
**				RPC_S_BUFFER_OVERFLOW if the supplied buffer
**					was not big enough to hold the
**					translation;
**				RPC_S_NORMAL otherwise.
**
**  On entry:	buffer		null terminated string containing the name to
**				translate;
**
**		previous	contains in positions 0 to curlevel-1
**				pointers to the previous translation strings.
**
**  On exit:	buffer		null terminated translation string.
**
**  Description:
**
**	Recursively gets the translation of the string contained in 'buffer'
**	from the environment. Recursion stops when either the maximum
**	recursion level 'maxlevel' is reached, or there is no translation,
**	or a translation loop is detected (in this case RPC_S_NO_TRANSLATION
**	is returned).
**
**  History:
**
**	14 Aug 89	Rewritten. (RB)
**	 4 Dec 89	Bug fix (return NO_TRANSATION if no translation). (TBL)
*/

#ifdef __STDC__
static rpc_status translate(char *buffer, int buflen, int curlevel,
				 int maxlevel, char *previous[])
#else
static rpc_status translate(buffer, buflen, curlevel, maxlevel, previous)
char *buffer;
int buflen;
int curlevel;
int maxlevel;
char *previous[];
#endif
{
    int status;

    if (curlevel < maxlevel) {

        extern char *getenv();
	char *translation;
        char *aux_buf;

	/* Copy the input string into our private buffer. */

	aux_buf = malloc(buflen);
	(void) strcpy(aux_buf, buffer);

#ifdef UPPERCASE_LOGICALS
	/* Lowercase environment variables are not allowed. */

	capital(aux_buf);
#endif

	/* Save pointer to name we are trying to translate. */

	previous[curlevel] = aux_buf;

	/* Try translating. */

        if ((translation = getenv(aux_buf)) != NULL) {

	    int k;

	    /* Translation worked: save and retry. */

            if (strlen(translation) > buflen - 1) {
            	CTRACE(tfp, "RPC/CM: Buffer overflow translating `%s' to `%s'\n",
            	    aux_buf, translation);
                return RPC_S_BUFFER_OVERFLOW;
            }

	    (void) strcpy(buffer, translation);

	    CTRACE(tfp, "RPC/CM: Translated  `%s' to `%s'\n", aux_buf, buffer);

	    /* Check for translation loop. */

	    for (k = 0; k <= curlevel; k++)
		if (strcmp(previous[k], buffer) == 0) {
		    UTRACE(tfp, "RPC/CM: Translation loop detected, fail.\n");
		    free(aux_buf);
		    return RPC_S_NO_TRANSLATION;
		}

	    status = translate(buffer, buflen, curlevel+1, maxlevel, previous);

	    if (status == RPC_S_NO_TRANSLATION)	/* If we couldn't again, */
		status = RPC_S_NORMAL;		/* we did it once, anyway */

	} else {

	    /* Translation didn't work, say it's bad. */

	    CTRACE(tfp, "RPC/CM: No translation for `%s'.\n", aux_buf);
	    status = RPC_S_NO_TRANSLATION;
	}
	free(aux_buf);

    } else { /* Maximum translation level reached, return ok. */

 	status = RPC_S_NORMAL;

    } 
    return status;
}


/*
**
**	Satisfy requests for configuration items.
**	-----------------------------------------
**
**	
**	rpc_status cm_translate(const char *name, char *expansion, int explen)
**
**  Parameters:	name		pointer to string containing the name to be
**				looked up;
**
**		expansion	pointer to string where the expansion for
**				'name' will be stored;
**
**		explen		maximum length of the expansion for 'name'.
**
**  Return:			RPC_S_NO_TRANSLATION if no translation exist
**					or if a translation loop was detected;
**				RPC_S_BUFFER_OVERFLOW if the supplied buffer
**					was not big enough to hold the
**					translation;
**				RPC_S_NORMAL otherwise.
**
**  On entry:	name		Null terminated string containing the name to
**				looked up.
**
**  On exit:	expansion	Null terminated string containing the expansion
**				for name, possibly truncated to explen-1
**				characters.
**
**  Description:
**
**	Initialises for calling the translate() recursive function
**	that will do the real job.
**
**  History:
**
**	14 Aug 89	Rewritten. (RB)
**
*/

#ifdef __STDC__
rpc_status cm_translate(const char *name, char *expansion, int explen)
#else
rpc_status cm_translate(name, expansion, explen)
char *name;
char *expansion;
int  explen;
#endif
{
#define TRANSLATION_LIMIT 10

    /* This array is used to detect translation loops. */

    char *previous[TRANSLATION_LIMIT];

    /* Copy name to expansion, if there is enough room. */

    if (strlen(name) > explen - 1) {
	CTRACE(tfp, "RPC/CM: Buffer overflow when translating `%s'.\n",
	    name);
	return RPC_S_BUFFER_OVERFLOW;
    }

    (void) strcpy(expansion, name);

    /* Do the actual translation. */

    return translate(expansion, explen, 0, TRANSLATION_LIMIT, previous);
}

#define WAY_REGISTER_NAME "way_register"
#define WAY_REQUEST_NAME  "way_request"

/*
**
**	Satisfy requests for named service.
**	-----------------------------------
**
**	
**	rpc_status cm_request(rpc_name logical,	int version,
**			int compatibility, rpc_name buffer, int buffer_size)
**
**  Parameters:	logical		pointer to the null-terminated string
**				containing the name of the requested service;
**
**		version		is the actual version of the client;
**
**		compatibility	is the lowest previous version of server
**				which is also supported;
**
**		buffer		pointer to string where the server RPC address
**				will be stored;
**
**		buffer_size	maximum length of the server address.
**
**  Return:			RPC_S_NO_TRANSLATION if no suitable server is
**					available or if a translation loop
**					was detected;
**				RPC_S_BUFFER_OVERFLOW if the supplied buffer
**					was not big enough to hold the
**					server address;
**				RPC_S_NORMAL otherwise.
**
**  On entry:	logical		Null terminated string containing the name to
**				looked up.
**
**  On exit:	expansion	Null terminated string containing the
**				server address, if RPC_S_NORMAL was returned.
**
**  Description:
**
**	Given the logical name of a requested service, this procedure does
**	whatever is necessary to locate the service, and return the physical
**	address.
**
**				WARNING
**				-------
**
**	    THIS FUNCTION IS SUBJECT TO FREQUENT CHANGES, AS THE
**	    WAY FUNCTIONALITIES ARE EXTENDED AND THE INTEGRATION
**	    WITH RaPaCe IS IMPROVED.
**
**	This (preliminary) implementation tries a local translation first,
**	using the environment as the database: this is done by calling
**	cm_translate(). If the translation succeeds then its result is
**	returned as the server address. If it fails because a symbol
**	correspondent to the service logical name could not be found
**	in the environment, then an attempt to use the WAY name server
**	takes place.
**
**	This is done by first trying to establish a connection with the WAY
**	server providing the "WAY Request Service". If the communication
**	can be set up, the WAY server is asked to look into its database
**	for the specified service logical name (with version checking and
**	so on). The WAY completion code returned is then directly translated
**	into an RPC one and returned to the caller.
**
**  History:
**
**	14 Aug 89	Rewritten. (RB)
**	16 Nov 89	Added code for WAY interrogation. (RB)
**	 7 Dec 89	original_h_way remembered in case recursive (TBL)
**
*/

#ifdef __STDC__
rpc_status cm_request(rpc_name logical,	int version, int compatibility,
			rpc_name buffer, int buffer_size)
#else
rpc_status cm_request(logical, version, compatibility, buffer, buffer_size)
#endif
{
    rpc_status		r_status;
    way_status		w_status;
    extern rpc_handle	h_way;
    rpc_handle		original_h_way;

    /* Try a translation using the environment as the database. */

    r_status = cm_translate(logical, buffer, buffer_size);

    /* If it worked or something horrible has happened, or
       we are trying to use WAY, just return the status. */

    if (r_status != RPC_S_NO_TRANSLATION || !strcmp(logical, WAY_REQUEST_NAME))
	return r_status;

    /* Try using WAY (Where Are You ?). */

    original_h_way = h_way;		    /* Save old handle */
    rpc_open(&r_status, &h_way, WAY_REQUEST_NAME);

    /* If open failed return RPC_S_NO_TRANSLATION. */

    if (BAD(r_status)) {
    	CTRACE(tfp, "RPC/CM: Cannot access WAY, no translation is possible.\n");
	h_way = original_h_way;	    /* restore original version */
	return RPC_S_NO_TRANSLATION;
    }

    /* Open succeeded, ask WAY if a suitable server is around. */

    w_status = way_request(logical, version, compatibility,
	"*", buffer_size, buffer);
    
    /* Close the connection with WAY. */

    rpc_close(&r_status, h_way);
    h_way = original_h_way;	    /* restore original version */

    /* Return the appropriate completion code. */

    switch (w_status) {
    	case WAY_S_NORMAL: {
	    CTRACE(tfp, "RPC/CM/WAY: A compatible `%s' server is available at `%s'.\n",
		logical, buffer);
	    r_status = RPC_S_NORMAL;
	    break;
    	}
    	case WAY_S_NO_SERVER: {
	    CTRACE(tfp, "RPC/CM/WAY: No compatible `%s' server is available.\n",
		logical);
	    r_status = RPC_S_NO_TRANSLATION;
	    break;
    	}
    	case WAY_S_BUFFER_OVERFLOW: {
	    CTRACE(tfp, "RPC/CM/WAY: Buffer overflow looking for a `%s' server.\n",
		logical);
	    r_status = RPC_S_BUFFER_OVERFLOW;
	    break;
    	}
    	default:	/* Should never happen. */
    	    abort();
	    break;
    }

    /* Return the appropriate RPC status. */

    return r_status;
}

/*
**
**	Register a service.
**	-------------------
**
**
**	rpc_status cm_register(const char *service, int version,
**				int compatibility, const char *address)
**
**  Parameters:	service		pointer to the null-terminated string
**				containing the name of the offered service;
**
**		version		is the actual version of the server;
**
**		compatibility	is the lowest previous version of client
**				which is also supported;
**
**		address		pointer to the null-terminated string
**				containing the server RPC address.
**
**  Return:			RPC_S_NO_WAY_ACCESS if no WAY server was
**					available making the registration
**					impossible;
**				RPC_S_NORMAL if the registration succeeded.
**
**  On entry:			All the parameters are set to the appropriate
**				value.
**
**  On exit:			The parameters are unchanged.
**
**  Description:
**
**	This routine records, when possible, the provision of a service at
**	a particular given RPC address. The service is characterized by its
**	name and its version and compatibility number. At present the only
**	possibility to achieve such a recording is to call a remote WAY
**	server, if one is defined.
**
**				WARNING
**				-------
**
**	    THIS FUNCTION IS SUBJECT TO FREQUENT CHANGES, AS THE
**	    WAY FUNCTIONALITIES ARE EXTENDED AND THE INTEGRATION
**	    WITH RaPaCe IS IMPROVED.
**
**	This (preliminary) implementation tries to establish a connection
**	with the WAY server providing the "WAY Register Service". If the
**	communication can be set up, the WAY server is asked to do the
**	registration. The WAY completion code returned is then directly
**	translated into an RPC one and returned to the caller.
**
**  History:
**
**	18 Nov 89	Written. (RB)
**	 7 Dec 89	original_h_way remembered in case recursive (TBL)
**
*/

#ifdef __STDC__
rpc_status cm_register(const char *service, int version,
				int compatibility, const char *address)
#else
rpc_status cm_register(service, version, compatibility, address)
char *service;
int version;
int compatibility;
char *address;
#endif
{
    rpc_status		r_status;
    way_status		w_status;
    extern rpc_handle	h_way;
    rpc_handle		original_h_way;

    /* Try to avoid entering an endless loop. Really, since in principle
       a WAY server could register itself into another WAY server, a
       neater implementation would check both the service name and the
       server address. This could be done in future revisions. */

    if (!strcmp(service, WAY_REGISTER_NAME))
	return RPC_S_NORMAL;

    /* Try to open a connection with the remote WAY server. */

    original_h_way = h_way;	/* Save in case recursively called */
    rpc_open(&r_status, &h_way, WAY_REGISTER_NAME);

    /* If open failed return RPC_S_NO_WAY_ACCESS. */

    if (BAD(r_status)) {
    	CTRACE(tfp, "RPC/CM: Cannot access WAY, no registration is possible.\n");
	h_way = original_h_way; /* Restore */
	return RPC_S_NO_WAY_ACCESS;
    }

    /* Open succeeded, ask WAY to do the registration. */

    w_status = way_register(service, version, compatibility, address);
    
    /* Close the connection with WAY. */

    rpc_close(&r_status, h_way);
    h_way = original_h_way;	/* Restore */

    /* Return the appropriate completion code. */

    switch (w_status) {
    	case WAY_S_NORMAL: {
	    CTRACE(tfp, "RPC/CM/WAY: Registered `%s' (Version %d Compat %d) at `%s'.",
		service, version, compatibility, address);
	    r_status = RPC_S_NORMAL;
	    break;
    	}
    	default:	/* Should never happen. */
    	    abort();
	    break;
    }

    /* Return the appropriate RPC status. */

    return r_status;
}

/*
**
**	Remove a registration.
**	----------------------
**
**
**	rpc_status cm_unregister(const char *service, int version,
**							const char *address)
**
**  Parameters:	service		pointer to the null-terminated string
**				containing the name of the service to
**				be unregistered;
**
**		version		is the actual version of the server;
**
**		address		pointer to the null-terminated string
**				containing the server address.
**
**  Return:			RPC_S_NO_WAY_ACCESS if no WAY server was
**					available making the unregistration
**					impossible;
**				RPC_S_NORMAL if the un registration succeeded.
**
**  On entry:			All the parameters are set to the appropriate
**				value.
**
**  On exit:			The parameters are unchanged.
**
**  Description:
**
**	This routine removes the record of the provision of a service at a
**	particular given RPC address. The service is characterized by its
**	name and its version number. This routine is the symmetrical
**	counterpart of cm_register(), the same general comments apply.
**
**				WARNING
**				-------
**
**	    THIS FUNCTION IS SUBJECT TO FREQUENT CHANGES, AS THE
**	    WAY FUNCTIONALITIES ARE EXTENDED AND THE INTEGRATION
**	    WITH RaPaCe IS IMPROVED.
**
**	This (preliminary) implementation tries to establish a connection
**	with the WAY server providing the "WAY Register Service". If the
**	communication can be set up, the WAY server is asked to do the
**	unregistration. The WAY completion code returned is then directly
**	translated into an RPC one and returned to the caller.
**
**  History:
**
**	18 Nov 89	Written. (RB)
**	 7 Dec 89	original_h_way remembered in case recursive (TBL)
**
*/

#ifdef __STDC__
rpc_status cm_unregister(const char *service, int version,
							const char *address)
#else
rpc_status cm_unregister(service, version, address)
char *service;
int version;
char *address;
#endif
{
    rpc_status		r_status;
    way_status		w_status;
    extern rpc_handle	h_way;
    rpc_handle		original_h_way;

    /* The same comment in cm_register() apply here. */

    if (!strcmp(service, WAY_REGISTER_NAME))
	return RPC_S_NORMAL;

    /* Try to open a connection with the remote WAY server. */

    original_h_way = h_way;	    /* Save */
    rpc_open(&r_status, &h_way, WAY_REGISTER_NAME);

    /* If open failed return RPC_S_NO_WAY_ACCESS. */

    if (BAD(r_status)) {
    	CTRACE(tfp, "RPC/CM: Cannot access WAY, no unregistration is possible.\n");
	h_way = original_h_way; /* Restore */
	return RPC_S_NO_WAY_ACCESS;
    }

    /* Open succeeded, ask WAY to do the unregistration. */

    w_status = way_unregister(service, version, address);
    
    /* Close the connection with WAY. */

    rpc_close(&r_status, h_way);
    h_way = original_h_way; /* Restore */

    /* Return the appropriate completion code. */

    switch (w_status) {
    	case WAY_S_NORMAL: {
	    CTRACE(tfp, "RPC/CM/WAY: Unregistered `%s' (Version %d) at `%s'.",
		service, version, address);
	    r_status = RPC_S_NORMAL;
	    break;
    	}
    	default:	/* Should never happen. */
    	    abort();
	    break;
    }

    /* Return the appropriate RPC status. */

    return r_status;
}

/*	Initialse This Module					       cm_init()
**	---------------------
**
**  On entry,
**	No prerequisites.
**
**  On exit,
**	CM may be used. More specifically, the error codes reported by
**	CM will be registered for translation.
**
*/
#ifdef __STDC__
rpc_status cm_init(void)
#else
rpc_status cm_init()
#endif
{
#ifndef vms
    return way_register_errors();
#else
    globalvalue way_s_normal;	    /* Force inclusion of error module */
    return way_s_normal;
#endif
}
