/*
**++
**  FACILITY:	SMTP
**
**  ABSTRACT:	Personal alias lookup routines.
**
**  MODULE DESCRIPTION:
**
**  	Alias lookup routines for per-user alias files.
**
**  AUTHOR: 	    M. Madison
**  	    	    COPYRIGHT © 1993, 1994  TGV, INC.  ALL RIGHTS RESERVED.
**
**  CREATION DATE:  15-OCT-1993
**
**  MODIFICATION HISTORY:
**
**  	15-OCT-1993 V1.0    Madison 	Initial coding (from USER_SMTP_DISPATCH).
**  	26-OCT-1993 V1.1    Madison 	Add nosignal argument.
**  	22-NOV-1993 V1.1-1  Madison 	Fix handling of blanks in alias strings.
**  	30-JUN-1994 V1.1-2  Madison 	Updated for new source tree layout.
**  	02-AUG-1995 V1.2    Madison 	Sanitize.
**--
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <descrip.h>
#include <uaidef.h>
#include <stsdef.h>
#include <lnmdef.h>
#include <rms.h>
#include <libvmdef.h>
#include <ssdef.h>
#include <lib$routines.h>
#include <starlet.h>

#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 */

/*
**  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;
    	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 personal_alias_lookup(char *, struct ACTX **, char *, int, int);

/*
**  Support routines
*/
    static unsigned int Alias_Lookup(char *, struct ACTX **, char *, int, int);
    static unsigned int Fill_Alias_Table(int);
    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 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 *);


/*
**++
**  ROUTINE:	personal_alias_lookup
**
**  FUNCTIONAL DESCRIPTION:
**
**  	Expands an alias into one or more addresses.  Addresses are
**  maintained in the alias file(s) defined by the logical name
**  MULTINET_PERSONAL_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:
**
**  	personal_alias_lookup(char *mailbox, struct ACTX **ctx, char *result,
**  	    	    	    int result_size);
**
**  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.
**
**  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.
**
**  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 personal_alias_lookup(char *mailbox, struct ACTX **xctx,
    	    	    char *result, int result_size, int nosignal)    	{

    struct ACTX *ctx;
    unsigned int status;

/*
**  Check to see if the alias table needs reloading only if we're not
**  in the middle of an expansion.
*/
    if (*xctx == 0) {
    	status = Fill_Alias_Table(nosignal);
    	if (!OK(status)) return status;
    }

    return Alias_Lookup(mailbox, xctx, result, result_size, nosignal);

} /* personal_alias_lookup */

/*
**++
**  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,
**  	    	    	char *result, int result_size)
**
**  name:   	    ASCIZ_string, read only, by reference
**  ctx:    	    varying_arg, modify, by reference
**  result: 	    ASCIZ_string, write only, by reference
**  result_size:    longword, read only, by value
**
**  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,
    	    	    	char *result, int result_size, int nosignal) {

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

/*
**  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 = malloc(sizeof(struct ACTX));
    	    	if (ctx == 0) return SS$_INSFMEM;
    	    	ctx->alias = p;
    	    	*xctx = ctx;
    	    	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) return SS$_ENDOFFILE;

/*
**  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) {
    	    ctx->alias->expansion_in_progress = 0;
    	    if (!nosignal) printf("? no expansion for %.*s\n",
    	    	    strlen(ctx->alias->alias), ctx->alias->alias);
    	    free(ctx);
    	    return SS$_ENDOFFILE;
    	}
    	if (strchr(ctx->expansion->expansion, '@') == 0) {
    	    myctx = 0;
    	    status = Alias_Lookup(ctx->expansion->expansion, &myctx, result, result_size, nosignal);
    	    if (OK(status)) {
    	    	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;
    	return SS$_NORMAL;
    }

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

    while (ctx != 0) {

/*
**  Fetch the next expansion from the expansion list.
*/
    	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;
    	free(ctx);
    	ctx = *xctx;

    }

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

/*
**  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, result, result_size, nosignal);
/*
**  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()
**
**  IMPLICIT INPUTS:	None.
**
**  IMPLICIT OUTPUTS:	None.
**
**  COMPLETION CODES:
**
**
**  SIDE EFFECTS:   	AliasTable modified.
**
**--
*/
static unsigned int Fill_Alias_Table(int nosignal) {

#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;
    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++) {
    	if (!get_logical("MULTINET_PERSONAL_ALIAS_FILE", tmp, sizeof(tmp), i)) {
    	    if (i == 0) {
    	    	strcpy(tmp, "SYS$LOGIN:SMTP_ALIASES.");
    	    } else break;
    	}
    	if (i >= file_count) {
    	    reload = 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 = !OK(get_file_rdt(file_list[i].FileName, &xtime));
    	    if (!reload) reload = !TIMEQL(file_list[i].LoadTime, xtime);
    	    if (reload) file_list[i].LoadTime = xtime;
    	} else {
    	    reload = 1;
    	    strcpy(file_list[i].FileName, tmp);
    	    get_file_rdt(file_list[i].FileName, &file_list[i].LoadTime);
    	}
    }

    if (!reload) return SS$_NORMAL;

    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)) free(e);
    	    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 = malloc(sizeof(struct ALIAS));
    	    if (alias == 0) {
    	    	close_file(&fab, &rab);
    	    	return SS$_INSFMEM;
    	    }
    	    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 != ':') {
    	    	free(alias);
    	    	printf("? alias file syntax error at line %d in %.*s\n",
    	    	    line_count,
    	    	    strlen(file_list[i].FileName), file_list[i].FileName);
    	    	continue;
    	    }
    	    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) {
    	    	    len = strlen(tmp);
    	    	    exp = malloc(sizeof(struct EXPAN) + len);
    	    	    if (exp == 0) {
    	    	    	close_file(&fab, &rab);
    	    	    	return SS$_INSFMEM;
    	    	    }
    	    	    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 SS$_NORMAL;

} /* 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 = malloc(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) 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;
    	    ADD(*cp);
    	}
    	cp++;
    }

/*
**  Trim trailing blanks
*/
    while (cp1 > result && isspace(*(cp1-1))) cp1--;

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

} /* get_token */
    

/*
**++
**  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 += _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);
    }
    return status;

} /* get_file_rdt */
