/*
**++
**  FACILITY:	SMTP
**
**  ABSTRACT:	Customization routines for MultiNet SMTP.
**
**  MODULE DESCRIPTION:
**
**  This is a template SMTP customization image.  It provides the
**  following callouts:
**
**  	find_gateway_v33    	    - for routing messages via a gateway
**  	    	    	    	      system based on host names
**
**  	alias_lookup_v33    	    - for aliasing local mailbox names
**
**  	vrfy_lookup_v32	    	    - for changing VRFY command processing
**  	    	    	    	      in the SMTP server
**
**  	expn_lookup_v32	    	    - for changing EXPN command processing
**  	    	    	    	      in the SMTP server
**
**  If the shareable image MULTINET:USER_SMTP_DISPATCH.EXE exists, the
**  symbiont and server will call these routines in it, instead of the
**  linked-in routines.
**
**  THIS INTERFACE IS SUBJECT TO CHANGE WITHOUT NOTICE.
**
**  N.B.: While the SMTP symbiont and server processes are currently
**  	  single-threaded, they may be changed to multi-threaded processes
**  	  in a future release.  For that reason, the use of static variables
**  	  is strongly discouraged.
**
**  Compile and link:
**  $ CC USER_SMTP_DISPATCH
**  $ LINK/SHARE USER_SMTP_DISPATCH/OPT
**
**  Install by placing USER_SMTP_DISPATCH.EXE in the MULTINET: directory
**  and restarting the SMTP symbiont with STOP/QUEUE/NEXT MULTINET_SMTP_QUEUE
**  and START/QUEUE MULTINET_SMTP_QUEUE.
**
**  DESCRIPTION OF MODIFICATIONS IN THIS MODULE  (courtesy of Aaaron Leonard)
**  -------------------------------------------
**
**   Based upon the domain name of the destination host, either
**   deliver the message as-is, or else send it to a designated
**   gateway.  This behavior can be controlled by several logicals:
**
**   MULTINET_SMTP_MAILHUB      - if defined, all mail not more 
**                                specifically routed will be sent here
**   MULTINET_SMTP_GATEWAYS     - list of domains that are to be routed
**                                to specific gateways
**   MULTINET_SMTP_LOCALDOMAINS - list of domains that are to be handled
**                                locally, instead of being sent to the
**                                MULTINET_SMTP_MAILHUB
**
**   In more detail: in find_gateway_v33():
**     - if dest host is domain literal [w.x.y.z] then
**         if MULTINET_SMTP_MAILHUB is defined, then send there
**         else send as-is
**     - if no MULTINET_SMTP_GATEWAYS and no MULTINET_SMTP_LOCALDOMAINS then
**         if MULTINET_SMTP_MAILHUB is defined, then send there
**         else send as-is
**     - now - (with MULTINET_SMTP_GATEWAYS and/or 
**              MULTINET_SMTP_LOCALDOMAINS defined)
**     - for each domain token
**         if matching MULTINET_SMTP_GATEWAYS entry, then send there
**         if matching MULTINET_SMTP_LOCALDOMAINS entry, then send as-is
**     - finally, if MULTINET_SMTP_MAILHUB is defined, then send there, else 
**         send as-is    
**
**  AUTHOR: 	    M. Madison
**  	    	    COPYRIGHT © 1993, 1994, 1995  TGV, INC.
**                  ALL RIGHTS RESERVED.
**
**  CREATION DATE:  05-MAY-1993
**
**  MODIFICATION HISTORY:
**
**  	05-MAY-1993 V1.0    Madison 	Initial coding.
**  	05-OCT-1993 V1.0-1  Madison 	Fix recursive expansion with -1 ctx.
**  	02-NOV-1993 V1.0-2  Madison 	Fix DECNET-DOMAIN stuff.
**  	02-DEC-1994 V1.0-3  Madison 	Fix multi-file reload check logic.
**  	25-MAY-1995 V1.1    Madison 	Add LOCALDOMAINS support, thanks Aaron!
**  	14-JUN-1995 V1.1-1  Madison 	Fix hashing of 8-bit characters.
**--
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#if defined(__GNUC__) || defined(TGVBUILD)
#include <vms/descrip.h>
#include <vms/uaidef.h>
#include <vms/stsdef.h>
#include <vms/lnmdef.h>
#include <vms/rms.h>
#include <vms/libvmdef.h>
#include <vms/ssdef.h>
    extern char *strchr();
#else /* GNU C or TGV build */
#include <descrip.h> 
#include <uaidef.h>
#include <stsdef.h>
#include <lnmdef.h>
#include <rms.h>
#include <libvmdef.h>
#include <lib$routines.h>
#include <starlet.h>
#include <ssdef.h>
#endif /* GNU C or TGV build */

/*
**  For queue handling, use builtin INSQUE and REMQUE instructions, if available.
**  Otherwise, use our own routines.
*/
#ifdef __GNUC__
    static void queue_insert();
    static int  queue_remove();
#else /* __GNUC__ */
#ifdef __DECC
#include <builtins.h>
#ifdef __ALPHA
#define queue_insert(item,pred) __PAL_INSQUEL((void *)(pred),(void *)(item))
#define queue_remove(entry,addr) (((struct QUEUE *)entry)->head == entry ? 0 :\
    	    	    	    (__PAL_REMQUEL((void *)(entry),(void *)(addr)),1))
#else
#define queue_insert(item,pred) _INSQUE(item,pred)
#define queue_remove(entry,addr) (((struct QUEUE *)entry)->head == entry ? 0 :\
    	    	    	    (_REMQUE(entry,addr),1))
#endif /* __ALPHA */
#else  /* __DECC  */
#pragma builtins
#define queue_insert(item,pred) _INSQUE(item,pred)
#define queue_remove(entry,addr) (((struct QUEUE *)entry)->head == entry ? 0 :\
    	    	    	    (_REMQUE(entry,addr),1))
#endif /* __DECC */
#endif /* __GNUC__ */

/*
**  Structures and macros used in these routines
*/

    struct vmstime {int long0, long1;};
    typedef struct vmstime TIME;

#define TIMEQL(t1,t2) (((t1).long0 == (t2).long0)&&((t1).long1 == (t2).long1))
#define OK(x) $VMS_STATUS_SUCCESS(x)
#define min(a,b) ((a) < (b) ? (a) : (b))

    static TIME one_minute={-600000000, -1};
    static unsigned int vm_zone = 0;
    struct QUEUE {void *head, *tail;};
    struct ITMLST {
    	    unsigned short bufsiz, itmcod;
    	    void *bufadr, *retlen;
    };

#define HASH_SIZE 512

/*
**  Hash table structure used by the alias_lookup routine
*/
    struct EXPAN {
    	struct EXPAN *flink, *blink;
    	int explen;
    	char expansion[1];
    };

    struct ALIAS {
    	struct ALIAS *flink, *blink;
    	struct QUEUE expansion_list;
    	int expansion_in_progress;
    	int mailing_list;
    	int owner_len;
    	char *list_owner;
    	char alias[81];
    };

    static struct QUEUE AliasTable[HASH_SIZE];

/*
**  Context structure used by the alias_lookup routine
*/
    struct ACTX {
    	struct ACTX *flink;
    	struct ALIAS *alias;
    	struct EXPAN *expansion;
    	char *bufp;
    	struct FAB fab;
    	struct RAB rab;
    };

/*
**  Structure used by memory allocation routines
*/
    struct MBUF {
    	unsigned int real_size;
    	unsigned int size;
    };

/*
**  Forward declarations
**
**  Entry points
*/
    unsigned int find_gateway_v33(char *, void (*)(), char *, int);
    unsigned int alias_lookup_v33(char *, struct ACTX **, void (*)(),
    	    	    void (*)(), char *, int, int *);
    unsigned int vrfy_lookup_v32(char *, char *, char *, int, void (*)());
    unsigned int expn_lookup_v32(char *, struct ACTX **, char *, char *,
    	    	    int, void (*)(), void (*)());

/*
**  Support routines
*/
    static unsigned int Alias_Lookup(char *, struct ACTX **, void (*)(),
    	    	    void (*)(), char *, int, int *);
    static void Fill_Alias_Table(void (*)(), void (*)());
    static unsigned int open_file(char *, struct FAB *, struct RAB *);
    static unsigned int close_file(struct FAB *, struct RAB *);
    static unsigned int read_file(struct RAB *);
    static int get_token(char **, char *, int, int *);
    static void *mem_alloc(int);
    static void mem_free(void *);
    static int get_logical(char *, char *, int, int);
    static int streql_case_blind(char *, char *);
    static int hash(char *);
    static unsigned get_file_rdt(char *, TIME *);
    static int is_local_address(char *);

/*
**++
**  ROUTINE:	find_gateway_v33
**
**  FUNCTIONAL DESCRIPTION:
**
**  	This routine is called before delivering mail to a remote host.
**  The name of the host is passed in.  The routine can return the name
**  of a gateway to which the mail should be directed.  This can be used
**  to implement pseudo-domains like ".BITNET".
**
**  	This substitution takes place at a high level, before MX expansion
**  and processing of the SMTP forwarder setting.  A hostname returned by
**  this routine is subject to such processing.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	find_gateway_v33(char *host, void (*Dprintf)(), char *gwy, int gwysize)
**
**  host:   	ASCIZ_string, read only, by reference
**  	    	The hostname to be looked up.
**
**  Dprintf:	procedure, read only, by reference
**  	    	A printf-like routine for sending OPCOM messages.
**
**  gwy:    	ASCIZ_string, write only, by reference
**  	    	Buffer for result (which should be a null-terminated string).
**
**  gwysize:	longword, read only, by value
**  	    	Size of gwy buffer.
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	SS$_NORMAL: 	    Gateway located
**  	non-success value:  No gateway located
**
**  SIDE EFFECTS:   	None.
**
**--
*/
unsigned int find_gateway_v33(char *host, void (*Dprintf)(),
    	    	    	    	char *gwy, int gwysize)     	{

    char tmp[256], *cp, *cp1;
    int i, have_gateways, have_localdomains;

/*
**  Gateway processing doesn't apply to numerical addresses
**  (of form [a.b.c.d]).
*/
    if (*host == '[') {
    	if (get_logical("MULTINET_SMTP_MAILHUB", gwy, gwysize, 0)) return SS$_NORMAL;
    	return 0;
    }

/*
**  Shortcut for when the gateways and localdomains logicals aren't there
*/
    have_gateways = get_logical("MULTINET_SMTP_GATEWAYS", tmp, sizeof(tmp), 0);
    have_localdomains = get_logical("MULTINET_SMTP_LOCALDOMAINS", tmp, sizeof(tmp), 0);
    if (!(have_gateways || have_localdomains)) {
       	if (get_logical("MULTINET_SMTP_MAILHUB", gwy, gwysize, 0)) return SS$_NORMAL;
    	return 0;
    }

/*
**  For each domain token, scan the LocalDomains search list.  Translation
**  strings are of the form
**
**  	    "host.name" or 
**          ".domain.name"
**
**  These are hosts and/or domains to which we can make direct SMTP connections,
**  bypassing the mailhub.
*/
    if (have_localdomains) {
    	cp = host;
    	while (*cp != '\0') {
    	    for (i = 0; get_logical("MULTINET_SMTP_LOCALDOMAINS", tmp, sizeof(tmp), i); i++) {
    	    	if (streql_case_blind(cp, tmp)) return 0;
    	    }
/*
**  Increment to next dot (for start of domain name)
*/
    	    if (*cp == '.') cp++;
    	    while (*cp != '\0' && *cp != '.') cp++;
    	}
    }

/*
**  For each domain token, scan the Gateways search list.  Translation
**  strings are of the form
**
**  	    "name=gateway"
**
**  e.g.,   ".bitnet=cunyvm.cuny.edu"
*/
    if (have_gateways) {
    	cp = host;
    	while (*cp != '\0') {
    	    for (i = 0; get_logical("MULTINET_SMTP_GATEWAYS", tmp, sizeof(tmp), i); i++) {
    	    	for (cp1 = tmp; *cp1 && *cp1 != '='; cp1++);
    	    	if (*cp1 == '\0') continue;
    	    	*cp1++ = '\0';
    	    	if (streql_case_blind(cp, tmp)) {
    	    	    if (strlen(cp1) < gwysize) {
    	    	    	strcpy(gwy, cp1);
    	    	    	return SS$_NORMAL;
    	    	    } else {
    	    	    	(*Dprintf)("SMTP: find_gateway: gateway name %s too long\n",
    	    	    	    	cp1);
    	    	    	return 0;   /* Gateway found, but too big! */
    	    	    }
    	    	}
    	    }
/*
**  Increment to next dot (for start of domain name)
*/
    	    if (*cp == '.') cp++;
    	    while (*cp != '\0' && *cp != '.') cp++;
    	}
    }

/*
**  No matching localdomains or gateways, return mailhub name
*/

    if (get_logical("MULTINET_SMTP_MAILHUB", gwy, gwysize, 0)) return SS$_NORMAL;
    return 0;

} /* find_gateway_v33 */

/*
**++
**  ROUTINE:	alias_lookup_v33
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Expands an alias into one or more addresses.  Addresses are
**  maintained in the alias file(s) defined by the logical name
**  MULTINET_ALIAS_FILE.  Expansions are returned one at a time; when
**  no further expansions are to be provided, a non-success status code
**  is returned.
**
**  	The "ctx" (context) argument is initialized to zero by the caller
**  and is reserved for use by this routine.  To maintain any context between
**  calls, allocate a structure from dynamic memory.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	alias_lookup_v33(char *mailbox, struct ACTX **ctx, void (*Dprintf)(),
**  	    	    	    void (*Remove_Local_Hostname)(), char *result,
**  	    	    	    int result_size, int *is_list);
**
**  mailbox:	    ASCIZ_string, read only, by reference
**  	    	    Local mailbox name to be looked up.
**
**  ctx:    	    user_arg, modify, by reference
**  	    	    Context variable for use by this routine.
**
**  Dprintf:	    procedure, read only, by reference
**  	    	    printf-like routine for sending OPCOM messages.
**
**  Remove_Local_Hostname: procedure, read only, by reference
**  	    	    Procedure that can be called to strip local hostname
**  	    	    references off of addresses.  Takes one parameter, an
**  	    	    ASCIZ string, which gets modified by the call.
**
**  result: 	    ASCIZ_string, write only, by reference
**  	    	    Buffer for result (which should be a null-terminated
**  	    	    string).
**
**  result_size:    longword, read only, by value
**  	    	    Size of result buffer.
**
**  is_list:	    longword, write only, by reference
**  	    	    Flag that indicates when an alias refers to a mailing
**  	    	    list.  When set, the result buffer holds the address
**  	    	    of the list owner, rather than a member of the list.
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	SS$_NORMAL or other success: 	alias found; result is valid
**  	non-success:	    	    	no (more) results
**
**  SIDE EFFECTS:   	None.
**
**--
*/
unsigned int alias_lookup_v33(char *mailbox, struct ACTX **xctx,
    	    	    void (*Dprintf)(), void (*Remove_Local_Hostname)(),
    	    	    char *result, int result_size, int *is_list)    	{

    struct ACTX *ctx;

/*
**  Check to see if the alias table needs reloading only if we're not
**  in the middle of an expansion.
*/
    if (*xctx == 0) {
    	Fill_Alias_Table(Remove_Local_Hostname, Dprintf);
    }

    return Alias_Lookup(mailbox, xctx, Dprintf, Remove_Local_Hostname,
    	    	    	    	    	    result, result_size, is_list);

} /* alias_lookup_v33 */

/*
**++
**  ROUTINE:	vrfy_lookup_v32
**
**  FUNCTIONAL DESCRIPTION:
**
**  	This routine is called when the SMTP server is presented with
**  a VRFY command that specifies a local username.  By default, if the
**  username is valid, the owner field for that username (extracted from
**  the UAF) is returned.  Otherwise, it will return a non-success status
**  value.
**
**  	You may customize this routine to return any information you
**  wish about local users.  If you do not want information about local
**  users to be divulged (you can base this decision on the inquiring
**  host's address, if desired), simply return a non-success status value.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	vrfy_lookup_v32(char *uname, char *remhost, char *text, int text_size,
**    	    	    	    	void (*Dprintf)())
**
**  uname:  	ASCIZ_string, read only, by reference
**  	    	The username to be looked up.
**
**  remhost:	ASCIZ_string, read only, by reference
**  	    	A string containing the inquiring host's IP address in
**  	    	dotted-decimal format.
**
**  text:   	ASCIZ_string, write only, by reference
**  	    	Buffer in which to store the result
**  	    	(which should be a null-terminated string).
**
**  text_size:	longword_signed, read only, by value
**  	    	Size of the text buffer.
**
**  Dprintf:	procedure, read only, by reference
**  	    	printf-type routine for sending OPCOM messages.
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	SS$_NORMAL: 	    	Normal successful completion; text returned
**  	any non-success status:	No text returned
**
**  SIDE EFFECTS:   	None.
**
**--
*/
unsigned int vrfy_lookup_v32(char *uname, char *remhost, char *text,
    	    	    int text_size, void (*Dprintf)()) {

    struct ITMLST uailst[2];
    struct dsc$descriptor userdsc;
    char owner[32], user[32], *cp, *cp1;
    unsigned int status;
    int i;

/*
**  Check to see if the username is too long
*/
    userdsc.dsc$w_length = strlen(uname);
    if (userdsc.dsc$w_length > sizeof(user)) return 0;
    userdsc.dsc$b_dtype = DSC$K_DTYPE_T;
    userdsc.dsc$b_class = DSC$K_CLASS_S;
    userdsc.dsc$a_pointer = user;
    for (cp = uname, cp1 = user; *cp; cp++, cp1++) *cp1 = _toupper(*cp);

    uailst[0].bufsiz = sizeof(owner);
    uailst[0].itmcod = UAI$_OWNER;
    uailst[0].bufadr = owner;
    uailst[0].retlen = 0;
    uailst[1].bufsiz = uailst[1].itmcod = 0;
    status = sys$getuai(0,0,&userdsc,uailst,0,0,0);
    if ($VMS_STATUS_SUCCESS(status)) {
    	i = (unsigned char) owner[0];
    	if (i >= text_size) i = text_size - 1;
    	strncpy(text, owner + 1, i);
    	text[i] = '\0';
    }

    return status;

} /* vrfy_lookup_v32 */

/*
**++
**  ROUTINE:	expn_lookup_v32
**
**  FUNCTIONAL DESCRIPTION:
**
**  	This routine is called when the SMTP server is presented with
**  an EXPN command for expanding a local alias.  By default, it uses
**  the local alias expansion routines in this module to perform the
**  expansion.
**
**  	You may modify this routine to return any information you wish
**  about local aliases or mailing lists.  If you do not wish to have
**  such information divulged, simply have the routine return a non-success
**  status on the first call.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	expn_lookup_v32(char *mailbox, struct ACTX **ctx, char *remhost,
**  	    	    	    char *result, int result_size,
**  	    	    	    void (*Dprintf)(), void (*Remove_Local_Hostname)())
**
**  mailbox:  	ASCIZ_string, read only, by reference
**  	    	Local mailbox name to be expanded.
**
**  ctx:    	user_arg, modify, by reference
**  	    	Context variable for use by this routine.
**
**  remhost:	ASCIZ_string, read only, by reference
**  	    	Inquiring host's IP address, in dotted-decimal format.
**
**  result: 	ASCIZ_string, write only, by reference
**  	    	Buffer for returning the resulting expansion
**  	    	(which should be a null-terminated string).
**
**  result_size: longword_unsigned, read only, by value
**  	    	Size of result buffer.
**
**  Dprintf:	procedure, read only, by reference
**  	    	printf-like routine for sending OPCOM messages.
**
**  Remove_Local_Hostname: procedure, read only, by reference
**  	    	Routine to remove local hostname references from addresses.
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	See alias_lookup_v33.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
unsigned int expn_lookup_v32(char *mailbox, struct ACTX **ctx, char *remhost,
    	    	    	    char *result, int result_size, void (*Dprintf)(),
    	    	    	    void (*Remove_Local_Hostname)()) {

    int is_list;
    unsigned int status;
    static char owner_flag[] = " (list owner)";

    status = alias_lookup_v33(mailbox, ctx, Dprintf, Remove_Local_Hostname,
    	    	result, result_size, &is_list);
    if ($VMS_STATUS_SUCCESS(status) && is_list) {
    	if (strlen(result) < result_size-sizeof(owner_flag)) {
    	    strcat(result, owner_flag);
    	}
    }

    return status;

} /* expn_lookup_v32 */

/*
**++
**  ROUTINE:	Alias_Lookup
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Recursive alias table search routine.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	Alias_Lookup(char *name, struct ACTX **ctx, void (*Dprintf)(),
**  	    	    	void (*Remove_Local_Hostname)(),
**  	    	    	char *result, int result_size, int *is_list)
**
**  name:   	    ASCIZ_string, read only, by reference
**  ctx:    	    varying_arg, modify, by reference
**  Dprintf:	    procedure, read only, by reference
**  Remove_Local_Hostname: procedure, read only, by reference
**  result: 	    ASCIZ_string, write only, by reference
**  result_size:    longword, read only, by value
**  is_list:	    longword (boolean), write only, by reference
**
**  IMPLICIT INPUTS:	AliasTable.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	success:    	alias found, result is valid
**  	non-success:	no result
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static unsigned int Alias_Lookup(char *name, struct ACTX **xctx,
    	    	    	void (*Dprintf)(), void (*Remove_Local_Hostname)(),
    	    	    	char *result, int result_size, int *is_list) {

    struct ACTX *ctx, *myctx, *a;
    struct ALIAS *p;
    struct EXPAN *exp;
    char tmp[256], *cp;
    unsigned int status;
    int len, i, j;


    *is_list = 0;

/*
**  Clean up after "fake" context returned by DECnet check code below.
*/

    if (*xctx == (struct ACTX *) -1) return 0;

/*
**  The initial lookup
*/
    if (*xctx == 0) {
    	ctx = 0;
    	i = hash(name);
    	for (p = AliasTable[i].head;
    	    	p != (struct ALIAS *) &AliasTable[i]; p = p->flink) {
    	    if (streql_case_blind(name, p->alias)) {
    	    	if (p->expansion_in_progress) break;
    	    	p->expansion_in_progress = 1;
    	    	ctx = mem_alloc(sizeof(struct ACTX));
    	    	if (ctx == 0) {
    	    	    (*Dprintf)("SMTP: alias_lookup: memory allocation failure\n");
    	    	    return 0;
    	    	}
    	    	ctx->alias = p;
    	    	*xctx = ctx;
/*
**  If it's a mailing list, we set the "is_list" flag and return the
**  list owner first (if there is a list owner).  We do this only if
**  we can successfully open the file holding the mailing list.
*/
    	    	if (p->mailing_list) {
    	    	    exp = p->expansion_list.head;
    	    	    status = open_file(exp->expansion, &ctx->fab, &ctx->rab);
    	    	    if (OK(status)) {
    	    	    	len = min(result_size-1, p->owner_len);
    	    	    	if (len > 0) strncpy(result, p->list_owner, len);
    	    	    	result[len] = '\0';
    	    	    	*is_list = 1;
    	    	    	return SS$_NORMAL;
    	    	    } else {
    	    	    	(*Dprintf)("SMTP: alias_lookup: could not open mailing list file %s for alias %s\n",
    	    	    	   exp->expansion, p->alias);
    	    	    	p->expansion_in_progress = 0;
    	    	    	mem_free(ctx);
    	    	    	*xctx = 0;
    	    	    	return 0;
    	    	    }
    	    	}
    	    	break;
    	    }
    	}
/*
**  If we get to this point, it's either because we found a non-mailing-list
**  alias, or because we didn't find a thing (in which case ctx will be 0).
*/
    	if (ctx == 0) {
/*
**  If a local user, see if we should tack on "@mailhub"
*/
    	    if (is_local_address(name)) {
    	    	if (get_logical("MULTINET_SMTP_LOCAL_TO_MAILHUB",
    	    	    	    	tmp, sizeof(tmp), 0)) {
    	    	    if (strchr("YyTt1", tmp[0]) != 0) {
    	    	    	len = strlen(name);
    	    	    	strcpy(result, name);
    	    	    	result[len++] = '@';
    	    	    	if (get_logical("MULTINET_SMTP_MAILHUB", result+len,
    	    	    	    	    	result_size-len, 0)) {
    	    	    	    *xctx = (struct ACTX *) -1;
    	    	    	    return SS$_NORMAL;
    	    	    	}
    	    	    }
    	    	}
    	    	return 0;
    	    } else {
    	    	register char *cp1, *cp2, *cp3;
/*
**  Check to see if the address is part of the "DECnet" mail domain - form:
**
**  	user@node[.node...].decnet-domain
**  	(e.g., madison@node2.node1.dnet.tgv.com)
**
**  If it is of that form, convert it back into a DECnet-style local address
**
**  	(e.g., "node1::node2::madison")
*/
    	    	if (get_logical("MULTINET_SMTP_DECNET_DOMAIN", tmp, sizeof(tmp), 0)) {
    	    	    cp = strchr(name, '@');
    	    	    if (cp == 0) return 0;
    	    	    cp1 = cp + strlen(cp) - strlen(tmp);
    	    	    if (streql_case_blind(cp1, tmp)) {
    	    	    	cp2 = result;
    	    	    	*cp2++ = '"';
    	    	    	len = 1;
    	    	    	while (--cp1 > cp) {
    	    	    	    for (cp3 = cp1; cp3 > cp+1 && *(cp3-1) != '.'; cp3--);
    	    	    	    if (len + (cp1-cp3) > result_size-4) return 0;
    	    	    	    strncpy(cp2, cp3, cp1-cp3);
    	    	    	    cp2 += (cp1-cp3);
    	    	    	    *cp2++ = ':';
    	    	    	    *cp2++ = ':';
    	    	    	    len += (cp1-cp3) + 2;
    	    	    	    cp1 = cp3;
    	    	    	}
    	    	    	if (len + (cp-name) > result_size-2) return 0;
    	    	    	strncpy(cp2, name, cp-name);
    	    	    	cp2 += (cp-name);
    	    	    	*cp2++ = '"';
    	    	    	*cp2 = '\0';
    	    	    	*xctx = (struct ACTX *) -1;
    	    	    	return SS$_NORMAL;
    	    	    }
    	    	}
    	    }

    	    return 0;

    	}

/*
**  Return the first address in the expansion list.  If there are none,
**  display a message and treat as non-alias.
*/
    	ctx->expansion = p->expansion_list.head;
    	if (ctx->expansion == (struct EXPAN *) &ctx->alias->expansion_list) {
    	    (*Dprintf)("SMTP: alias_lookup: alias %s has no expansion list\n",
    	    	    	    p->alias);
    	    ctx->alias->expansion_in_progress = 0;
    	    mem_free(ctx);
    	    return 0;
    	}
    	if (strchr(ctx->expansion->expansion, '@') == 0) {
    	    myctx = 0;
    	    status = Alias_Lookup(ctx->expansion->expansion, &myctx,
    	    	    	Dprintf, Remove_Local_Hostname, result, result_size, &i);
    	    if (OK(status) && i)
    	    	status = Alias_Lookup(ctx->expansion->expansion, &myctx,
    	    	    	    Dprintf, Remove_Local_Hostname, result,
    	    	    	    result_size, &i);
    	    if (OK(status)) {
    	    	if (myctx == (struct ACTX *) -1) {
    	    	    *xctx = myctx;
    	    	} else {
    	    	    for (a = myctx; a->flink != 0; a = a->flink);
    	    	    a->flink = ctx;
    	    	    *xctx = myctx;
    	    	}
    	    	return SS$_NORMAL;
    	    }
    	}
    	len = min(result_size-1, ctx->expansion->explen);
    	strncpy(result, ctx->expansion->expansion, len);
    	result[len] = '\0';
    	*xctx = ctx;
    	*is_list = 0;
    	return SS$_NORMAL;
    }

/*
**  We get here when an expansion is in progress (non-null ctx).
*/
    ctx = *xctx;

    while (ctx != 0) {

/*
**  Read the next mailing list entry, or fetch the next expansion from
**  the expansion list.
*/
    	if (ctx->alias->mailing_list) {
    	    while (1) {
    	    	if (ctx->bufp == 0) {
    	    	    status = read_file(&ctx->rab);
    	    	    if (!OK(status)) {
    	    	    	close_file(&ctx->fab, &ctx->rab);
    	    	    	len = 0;
    	    	    	break;
    	    	    }
    	    	    ctx->bufp = ctx->rab.rab$l_ubf;
    	    	}
    	    	j = get_token(&ctx->bufp, tmp, sizeof(tmp), &len);
    	    	if (j == 0) {
    	    	    (*Remove_Local_Hostname)(tmp);
    	    	    len = strlen(tmp);
    	    	    break;
    	    	}
    	    	if (j > 0) ctx->bufp = 0;
    	    	else ctx->bufp++;
    	    }

    	    if (len > 0) status = SS$_NORMAL;
    	    else status = 0;

    	} else {
    	    ctx->expansion = ctx->expansion->flink;
    	    if (ctx->expansion == (struct EXPAN *)&ctx->alias->expansion_list) {
    	    	status = 0;
    	    } else {
    	    	len = min(sizeof(tmp)-1, ctx->expansion->explen);
    	    	strncpy(tmp, ctx->expansion->expansion, len);
    	    	tmp[len] = '\0'; 
    	    	status = SS$_NORMAL;
    	    }
    	}

    	if (OK(status)) break;
/*
**  If there are no more expansions for this alias, pop the current context
**  off the stack and continue at the next level, if there is one.
*/

    	ctx->alias->expansion_in_progress = 0;
    	*xctx = ctx->flink;
    	mem_free(ctx);
    	ctx = *xctx;

    }

/*
**  End of the line?
*/
    if (ctx == 0) return 0;

/*
**  We have an expansion of some kind.  If it does not contain an @ sign,
**  it's a local mailbox which might actually be another alias, so we
**  look it up.
*/
    if (strchr(tmp, '@') == 0) {
    	myctx = 0;
    	status = Alias_Lookup(tmp, &myctx, Dprintf, Remove_Local_Hostname,
    	    	    	    	result, result_size, &i);

/*
**  If it's a mailing list, we don't care about the list owner, so just
**  move on to the first entry.
*/
    	if (OK(status) && i)
    	    status = Alias_Lookup(tmp, &myctx, Dprintf, Remove_Local_Hostname,
    	    	    	    	result, result_size, &i);
/*
**  It's an alias all right.  Push the current context on the stack and
**  return the first expansion from the recursive call.
*/
    	if (OK(status)) {
    	    for (a = myctx; a->flink != 0; a = a->flink);
    	    a->flink = ctx;
    	    *xctx = myctx;
    	    return SS$_NORMAL;
    	}
    }

/*
**  Not an alias, just an address.  Return it to the caller.
*/
    len = min(len, result_size-1);
    strncpy(result, tmp, len);
    result[len] = '\0';

    return SS$_NORMAL;

} /* Alias_Lookup */

/*
**++
**  ROUTINE:	Fill_Alias_Table
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Loads the alias table.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	Fill_Alias_Table(void (*Remove_Local_Hostname)(), void (*Dprintf)())
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**
**
**  SIDE EFFECTS:   	AliasTable modified.
**
**--
*/
static void Fill_Alias_Table(void (*Remove_Local_Hostname)(), void (*Dprintf)()) {

#define AFILE_MAX 16

    static int Initialized = 0, file_count = 0;
    static TIME HoldTime;

    static struct AFILE {
    	TIME LoadTime;
    	char FileName[256];
    } file_list[AFILE_MAX];

    char tmp[1024], *cp, *cp1;
    struct EXPAN *exp;
    TIME now, xtime, ytime;
    int i, j, len, reload, reload_this;
    unsigned int status;

    sys$gettim(&now);

    if (Initialized) {
    	lib$add_times(&HoldTime, &one_minute, &xtime);
    	if (OK(lib$sub_times(&xtime, &now, &ytime))) return;
    }
    HoldTime = now;

    reload = 0;
    for (i = 0; i < AFILE_MAX; i++) {
    	reload_this = 0;
    	if (!get_logical("MULTINET_ALIAS_FILE", tmp, sizeof(tmp), i)) {
    	    if (i == 0) {
    	    	strcpy(tmp, "MULTINET:SMTP_ALIASES.");
    	    } else break;
    	}
    	if (i >= file_count) {
    	    reload_this = 1;
    	    strcpy(file_list[i].FileName, tmp);
    	    get_file_rdt(file_list[i].FileName, &file_list[i].LoadTime);
    	    file_count++;
    	} else if (streql_case_blind(file_list[i].FileName, tmp)) {
    	    reload_this = !OK(get_file_rdt(file_list[i].FileName, &xtime));
    	    if (!reload_this) reload_this = !TIMEQL(file_list[i].LoadTime, xtime);
    	    if (reload_this) file_list[i].LoadTime = xtime;
    	} else {
    	    reload_this = 1;
    	    strcpy(file_list[i].FileName, tmp);
    	    get_file_rdt(file_list[i].FileName, &file_list[i].LoadTime);
    	}

    	if (!reload) reload = reload_this;

    }

    if (!reload) return;

    if (!Initialized) {
    	Initialized = 1;
    	for (i = 0; i < HASH_SIZE; i++)
    	    AliasTable[i].head = AliasTable[i].tail = &AliasTable[i];
    }

    for (i = 0; i < HASH_SIZE; i++) {
    	struct ALIAS *p;
    	struct EXPAN *e;
    	while (queue_remove(AliasTable[i].head, &p)) {
    	    while (queue_remove(p->expansion_list.head, &e)) mem_free(e);
    	    if (p->list_owner) mem_free(p->list_owner);
    	    mem_free(p);
    	}
    }

    for (i = 0; i < file_count; i++) {
    	struct FAB fab;
    	struct RAB rab;
    	struct ALIAS *alias;
    	int line_count;

    	status = open_file(file_list[i].FileName, &fab, &rab);
    	if (!OK(status)) continue;
    	line_count = 0;
    	while (OK(read_file(&rab))) {
    	    line_count++;
    	    for (cp = rab.rab$l_ubf; isspace(*cp); cp++);
    	    if (*cp == '\0' || *cp == '#') continue;
    	    alias = mem_alloc(sizeof(struct ALIAS));
    	    if (alias == 0) {
    	    	(*Dprintf)("SMTP: alias_load: memory allocation failure\n");
    	    	break;
    	    }
    	    alias->expansion_list.head = alias->expansion_list.tail =
    	    	    &alias->expansion_list;
    	    for (cp1 = alias->alias, j = 80; j > 0 && *cp && *cp != ':';
    	    	    j--, cp++) if (!isspace(*cp)) *cp1++ = _tolower(*cp);
    	    *cp1 = '\0';
    	    if (*cp != ':') {
    	    	(*Dprintf)("SMTP: alias file %s has bad format, line %d\n",
    	    	    	file_list[i].FileName, line_count);
    	    	mem_free(alias);
    	    	continue;
    	    }
    	    cp++;
/*
**  For a mailing list, format is
**
**  	name :: owner-address, filename;
**
**  where the owner-address will get used as the error return path, and
**  the list members' addresses are stored in the file.
*/
    	    if (*cp == ':') {
    	    	alias->mailing_list = 1;
    	    	cp++;
    	    }

    	    while (1) {
    	    	j = get_token(&cp, tmp, sizeof(tmp), &len);
    	    	if (j > 0) {	   /* at end of line, get next one */
    	    	    while (1) {
    	    	    	if (!OK(read_file(&rab))) {
    	    	    	    j = -1;
    	    	    	    break;
    	    	    	}
    	    	    	line_count++;
    	    	    	for (cp = rab.rab$l_ubf; isspace(*cp); cp++);
    	    	    	if (!(*cp == '\0' || *cp == '#')) break;
    	    	    }
    	    	}
    	    	if (j < 0) break;  /* hit the semicolon or end-of-file */
    	    	if (j == 0) {
    	    	    if (alias->mailing_list) {
    	    	    	if (alias->list_owner == 0) {
    	    	    	    (*Remove_Local_Hostname)(tmp);
    	    	    	    alias->owner_len = strlen(tmp);
    	    	    	    alias->list_owner = mem_alloc(alias->owner_len+1);
    	    	    	    if (alias->list_owner == 0) {
    	    	    	    	(*Dprintf)("alias_load: memory allocation failure\n");
    	    	    	    	break;
    	    	    	    }
    	    	    	    strcpy(alias->list_owner, tmp);
    	    	    	    continue;
    	    	    	}
    	    	    } else {
    	    	    	(*Remove_Local_Hostname)(tmp);
    	    	    	len = strlen(tmp);
    	    	    }
    	    	    exp = mem_alloc(sizeof(struct EXPAN) + len);
    	    	    if (exp == 0) {
    	    	    	(*Dprintf)("alias_load: memory allocation failure\n");
    	    	    	break;
    	    	    }
    	    	    exp->explen = len;
    	    	    strcpy(exp->expansion, tmp);
    	    	    queue_insert(exp, alias->expansion_list.tail);
    	    	}
    	    }

    	    queue_insert(alias, AliasTable[hash(alias->alias)].tail);

    	}

    	close_file(&fab, &rab);

    }

    return;

} /* Fill_Alias_Table */

/*
**++
**  ROUTINE:	open_file
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Opens a file using RMS.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	open_file(char *fspec, struct FAB *fab, struct RAB *rab)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	any from RMS routines.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static unsigned int open_file(char *fspec, struct FAB *fab, struct RAB *rab) {

    struct XABFHC xabfhc;
    unsigned int status;

    *fab = cc$rms_fab;
    *rab = cc$rms_rab;
    xabfhc = cc$rms_xabfhc;
    fab->fab$l_fna = fspec;
    fab->fab$b_fns = strlen(fspec);
    fab->fab$l_xab = (void *) &xabfhc;
    fab->fab$b_fac = FAB$M_GET;
    status = sys$open(fab, 0, 0);
    if (!OK(status)) return status;
    rab->rab$l_fab = fab;
    status = sys$connect(rab, 0, 0);
    if (!OK(status)) {
    	sys$close(fab, 0, 0);
    	return status;
    }
    rab->rab$w_usz = (fab->fab$w_mrs == 0) ?
    	    ((xabfhc.xab$w_lrl == 0) ? 32768 : xabfhc.xab$w_lrl+1)
    	    	    	: (fab->fab$w_mrs + 1);
    rab->rab$l_ubf = mem_alloc(rab->rab$w_usz);
    if (rab->rab$l_ubf == 0) {
    	sys$disconnect(rab,0,0);
    	sys$close(fab,0,0);
    	return SS$_INSFMEM;
    }

    return SS$_NORMAL;

} /* open_file */

/*
**++
**  ROUTINE:	close_file
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Closes a file.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	close_file(struct FAB *fab, struct RAB *rab);
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	any from RMS routines.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static unsigned int close_file(struct FAB *fab, struct RAB *rab) {

    if (rab->rab$l_ubf) mem_free(rab->rab$l_ubf);
    sys$disconnect(rab, 0, 0);
    sys$close(fab, 0, 0);
    return SS$_NORMAL;

} /* close_file */

/*
**++
**  ROUTINE:	read_file
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Reads a record from a file.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	read_file(struct FAB *fab, struct RAB *rab);
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	any from RMS routines.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static unsigned int read_file(struct RAB *rab) {

    unsigned int status;

    status = sys$get(rab, 0, 0);
    if (OK(status)) rab->rab$l_ubf[rab->rab$w_rsz] = '\0';
    return status;

} /* read_file */

/*
**++
**  ROUTINE:	get_token
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Fetches a token from a line.  A comma after token indicates
**  that there is another token in the list.  A semicolon after a token
**  indicates the end of a list.  The pound sign is used to begin a comment.
**
**  RETURNS:	int
**
**  PROTOTYPE:
**
**  	get_token(char **bufp, char *result, int result_size, int *len)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	< 0  :  hit a semicolon
**  	= 0  :  result is valid
**  	> 0  :  hit end of buffer
**
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static int get_token(char **bufp, char *result, int result_size, int *len) {

    char *cp, *cp1;
    int inq;

    cp = *bufp;
    cp1 = result;

    while (*cp && (isspace(*cp) || *cp == ',')) cp++;

    if (*cp == '\0' || *cp == '#') {
    	*bufp = cp;
    	return 1;
    }

    if (*cp == ';') {
    	*bufp = cp;
    	return -1;
    }

    inq = 0;

#define ADD(c) {if (result_size > 1) {*cp1++ = c; result_size--;}}

    while (1) {
    	if (inq) {
    	    if (*cp == '\\') {
    	    	ADD(*cp++); ADD(*cp);
    	    } else if (*cp == '"') {
    	    	ADD(*cp);
    	    	inq = 0;
    	    } else ADD(*cp);
    	} else {
    	    if (*cp == '\0' || *cp == '#' || *cp == ';' || *cp == ',') break;
    	    if (*cp == '"') inq = 1;
    	    if (!isspace(*cp)) *cp1++ = *cp;

    	}
    	cp++;
    }

    *cp1 = '\0';
    *len = cp1-result;
    *bufp = cp;
    return 0;

} /* get_token */
    

/*
**++
**  ROUTINE:	queue_insert
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Inserts an entry into a queue.  The QUE structure isn't
**  required, as long as the first two longwords of the structure
**  being used is a queue header.
**
**  USED ONLY WITH GNU C.  VAX C and DEC C use the appropriate builtins.
**
**  RETURNS:	void
**
**  PROTOTYPE:
**
**  	queue_insert(item, pred)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	None.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
#ifdef __GNUC__
static void queue_insert(struct QUEUE *item, struct QUEUE *pred) {

    item->head = pred->head;
    item->tail = pred;
    ((struct QUEUE *)(pred->head))->tail = item;
    pred->head = item;
}
#endif

/*
**++
**  ROUTINE:	queue_remove
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Removes an entry from a queue, if there is one.
**
**  USED ONLY WITH GNU C.  VAX C and DEC C use the appropriate builtins.
**
**  RETURNS:	int
**
**  PROTOTYPE:
**
**  	queue_remove(struct QUEUE *entry, void **addr);
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	1: Remove successful.
**  	0: No entry to remove.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
#ifdef __GNUC__
static int queue_remove(struct QUEUE *entry, void **addr) {

    if (entry->head == entry) return 0;
    ((struct QUEUE *)(entry->tail))->head = entry->head;
    ((struct QUEUE *)(entry->head))->tail = entry->tail;
    *addr = entry;
    return 1;
}
#endif

/*
**++
**  ROUTINE:	mem_alloc
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Reentrant memory allocation routine based on LIB$GET_VM.
**
**  RETURNS:	void *
**
**  PROTOTYPE:
**
**  	mem_alloc(int size)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	    0:	error!
**  	non-0:	address of allocated block
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static void *mem_alloc(int size) {

    unsigned int status;
    struct MBUF *m;

    if (vm_zone == 0) {
    	unsigned int alg = LIB$K_VM_FIRST_FIT;
    	unsigned int flags = LIB$M_VM_BOUNDARY_TAGS|LIB$M_VM_GET_FILL0|
    	    	    	    	LIB$M_VM_FREE_FILL1;
    	static $DESCRIPTOR(zone_name, "USMTPD_ZONE");
    	status = lib$create_vm_zone(&vm_zone, &alg, 0, &flags,
    	    0, 0, 0, 0, 0, 0, &zone_name);
    	if (!$VMS_STATUS_SUCCESS(status)) return 0;
    }
    size += sizeof(struct MBUF);
    status = lib$get_vm(&size, &m, &vm_zone);
    if ($VMS_STATUS_SUCCESS(status)) {
    	m->size = size - sizeof(struct MBUF);
    	m->real_size = size;
    	return (void *) (m + 1);
    }

    return 0;

} /* mem_alloc */


/*
**++
**  ROUTINE:	mem_free
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Frees memory allocated with mem_alloc.
**
**  RETURNS:	void
**
**  PROTOTYPE:
**
**  	mem_free(void *ptr)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:	None.
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static void mem_free(void *p) {

    struct MBUF *m;
    unsigned int size;

    m = (struct MBUF *) ((char *) p - sizeof(struct MBUF));

    if (m->real_size == m->size + sizeof(struct MBUF)) {
    	size = m->real_size;
    	lib$free_vm(&size, &m, &vm_zone);
    }

    return;
} /* mem_free */

/*
**++
**  ROUTINE:	get_logical
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Translates a logical name.  Can be used to translate
**  search list logicals by passing in a non-zero index value.
**
**  RETURNS:	boolean
**
**  PROTOTYPE:
**
**  	get_logical(char *lognam, char *eqvnam, int eqvsiz, int indx)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	    1:	success
**  	    0:  failure
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static int get_logical(char *lognam, char *eqvnam, int eqvsiz, int indx) {

    static $DESCRIPTOR(tabnam,"LNM$FILE_DEV");
    static unsigned int attr = LNM$M_CASE_BLIND;
    struct dsc$descriptor lnmdsc;
    unsigned short len;
    struct ITMLST lnmlst[3];
    unsigned int status;

    lnmlst[0].bufsiz = sizeof(indx);
    lnmlst[0].itmcod = LNM$_INDEX;
    lnmlst[0].bufadr = &indx;
    lnmlst[0].retlen = 0;
    lnmlst[1].bufsiz = eqvsiz-1 > 255 ? 255 : eqvsiz-1;
    lnmlst[1].itmcod = LNM$_STRING;
    lnmlst[1].bufadr = eqvnam;
    lnmlst[1].retlen = &len;
    lnmlst[2].itmcod = lnmlst[2].bufsiz = 0;
    lnmdsc.dsc$b_dtype = DSC$K_DTYPE_T;
    lnmdsc.dsc$b_class = DSC$K_CLASS_S;
    lnmdsc.dsc$a_pointer = lognam;
    lnmdsc.dsc$w_length = strlen(lognam);
    status = sys$trnlnm(&attr,&tabnam,&lnmdsc,0,lnmlst);
    if ($VMS_STATUS_SUCCESS(status) && len > 0) {
    	eqvnam[len] = '\0';
    	return 1;
    }

    return 0;

} /* get_logical */

/*
**++
**  ROUTINE:	streql_case_blind
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Case-blind string comparison
**
**  RETURNS:	boolean
**
**  PROTOTYPE:
**
**  	streql_case_blind(char *str1, char *str2)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	    1: 	equal
**  	    0:	not equal
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static int streql_case_blind(char *s1, char *s2) {

    register char c1, c2;

    while (1) {
        c1 = _toupper(*s1);
        c2 = _toupper(*s2);
        if (c1 != c2) break;
        if (c1 == '\0') return 1;
        s1++; s2++;
    }

    return 0;

} /* streql_case_blind */

/*
**++
**  ROUTINE:	hash
**
**  FUNCTIONAL DESCRIPTION:
**
**  	tbs
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	tbs
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static int hash(char *s) {

    int hashval;
    register char *cp;

    hashval = 0;

    for (cp = s; *cp != '\0'; cp++)
    	hashval += (unsigned char ) _tolower(*cp);

    return hashval % HASH_SIZE;

}

/*
**++
**  ROUTINE:	get_file_rdt
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Fetches the RDT for a file.
**
**  RETURNS:	cond_value, longword (unsigned), write only, by value
**
**  PROTOTYPE:
**
**  	get_file_rdt(fspec, rdt)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static unsigned int get_file_rdt(char *fspec, TIME *rdt) {

    struct FAB fab;
    struct XABRDT xabrdt;
    unsigned int status;

    fab = cc$rms_fab;
    xabrdt = cc$rms_xabrdt;
    fab.fab$l_fna = fspec;
    fab.fab$b_fns = strlen(fspec);
    fab.fab$b_fac = FAB$M_GET;
    fab.fab$l_xab = (char *) &xabrdt;
    status = sys$open(&fab,0,0);
    if ($VMS_STATUS_SUCCESS(status)) {
    	memcpy(rdt, &xabrdt.xab$q_rdt, 8);
    	sys$close(&fab,0,0);
    } else {
    	rdt->long0 = rdt->long1 = 0;
    }

    return status;

} /* get_file_rdt */

/*
**++
**  ROUTINE:	is_local_address
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Checks to see if an address is for a local user (looks for @host-name)
**
**  RETURNS:	int
**
**  PROTOTYPE:
**
**  	is_local_address(char *address)
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**  	    1:	    	is local
**  	    0:	    	is remote
**
**  SIDE EFFECTS:   	None.
**
**--
*/
static int is_local_address(char *address) {

    char *cp;
    int inquotes;

    inquotes = 0;
    for (cp = address; *cp != '\0'; cp++) {
    	if (inquotes) {
    	    if (*cp == '\\') {
    	    	cp++;
    	    	if (*cp == '\0') break;
    	    } else if (*cp == '"') {
    	    	inquotes = 0;
    	    }
    	} else {
    	    if (*cp == '\\') {
    	    	cp++;
    	    	if (*cp == '\0') break;
    	    } else if (*cp == '"') {
    	    	inquotes = 1;
    	    } else if (*cp == '@') return 0;
    	}
    }

    return 1;

} /* is_local_address */
