/*
 * This module provides routines for managing a file for mapping X509
 * distinquished names to VMS identifiers.
 *
 * The file is an indexed file with 2 keys and the following record
 * layout:
 *    1-4	Primary key, integer.  The key value is a unique mapping
 *		number for the record.  Key value 0 is reserved.
 *    5-8	Secondary key, integer.  The key value is a hash of the
 *              DN (subject fields) being indexed by the record.
 *    9-12      Integer field, record number of mapping for issuer of
 *              certificate.  For self-signed certificates, this number
 *              is it's own record number and matches bytes 1-4.  If
 *		unknown, field is 0.
 *    13-16	Longword, VMS identifier assigned to this certificate.
 *    17-18	Word, length of subject name, which is hash to get secondary
 *		key.
 *    19-20	Word, length of serial number for certificate, stored as
 *		a big-endian integer of an arbitrary number of bytes.
 *    21-1024	String storage, concatenation of subject name and serial
 *		number.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <descrip.h>		/* VMS string descriptors */
#include <string.h>
#include <rms.h>
#include <starlet.h>
#include <ssdef.h>
#include <fdldef.h>
#include "pthread_1c_np.h"
#include "tutil.h"

#include "cert_map.h"

static char *fdl_desc_fmt = "\
  FILE; ORGANIZATION indexed; PROTECTION (system:RWED,owner:RWED,group,world);\
  RECORD; CARRIAGE_CONTROL carriage_return; FORMAT variable; SIZE %d;\
  KEY 0; CHANGES no; PROLOG 3; SEG0_LENGTH 4; SEG0_POSITION 0; TYPE int4;\
  KEY 1; DUPLICATES yes; SEG0_LENGTH 4; SEG0_POSITION 4; TYPE int4;";
static $DESCRIPTOR(fdl_desc,"");
int FDL$CREATE();
/*
 * Define structures used internally, external callers see the addresses
 * of the structures as void * pointers.
 */
typedef struct
{
    int status;
    struct FAB fab;		/* RMS file access block */
    struct RAB rab;		/* RMS record access block */

    int master_last_alloc;
    struct certmap_db_rec master;	/* Copy of db 'master' (rec_num=0) record. */

    pthread_mutex_t lock;

    struct db_search_result *free_results;
    
} db_context;

struct db_search_result {
    struct db_search_result *next;
    struct certmap_db_rec rec;
};
/*
 * Search context states:
 *    results    previous
 *    non-null   non-null	previous->next is next record to return.
 *    null        N/A		Prior call returned last record.
 */
typedef struct
{
    struct db_search_result *results;		/* linked list */
    struct db_search_result *previous;		/* position in list */
} db_search_context;
/*
 * Setup pthread structures to allow non-blocking RMS calls from threads.
 */
static pthread_once_t module_setup = PTHREAD_ONCE_INIT;
static pthread_mutex_t rms_io, access_lock;
static pthread_cond_t rms_io_done;
static void synch_ast ( struct RAB *rab )
{
    pthread_cond_signal_int_np ( &rms_io_done );
}
static int synch_rms ( int status, unsigned int *sts )
{
    /*
     * Loop until predicate (iosb nozero) is true.
     */
    if ( (status == SS$_SYNCH) || (status == RMS$_SYNCH) ) {
	status = *sts;
    } else if ( ((status&1) == 1) || (status==0) ) {
	do {
	    pthread_cond_wait ( &rms_io_done, &rms_io );
	} while ( *sts == 0 );
	status = *sts;
    }
    pthread_mutex_unlock ( &rms_io );
    return status;
}
static void module_init ()
{
    INITIALIZE_MUTEX ( &access_lock );
    SET_MUTEX_NAME ( &access_lock,"OSU access DB lock" )
    INITIALIZE_MUTEX ( &rms_io );
    SET_MUTEX_NAME ( &rms_io, "OSU access DB RMS" )
    INITIALIZE_CONDITION ( &rms_io_done );
    SET_COND_NAME ( &rms_io_done,"OSU access DB RMS" )
}
/*
 * Note: this hash function is not meant to be a secure hash, it simply
 * wants to hash the name field across a large range.
 */
#define hash_mask 0x7fffffff
static int hash_name ( struct certmap_db_var *subject )
{
    int hash, i, j;

    for ( j = hash = 0; j < subject->name_len; j++ ) {
	i = (unsigned) subject->s[j];
	hash = hash_mask & (i*577 ^ hash*3);
    }
    if ( hash == 0 ) hash = (subject->s[0]&127) + 37;

    return hash;
}
/*
 * Wrappers for RMS calls...
 */
static int put_record ( db_context *dbc, struct certmap_db_rec *rec )
{
    int status, vlen;

    vlen = rec->var.name_len + rec->var.serial_len;
    if ( (rec->var.name_len < 0) || (rec->var.serial_len < 0) ||
	(vlen > sizeof(rec->var.s)) ) return SS$_BADPARAM;

    dbc->rab.rab$b_rac = RAB$C_KEY;		/* Write by key */
    dbc->rab.rab$l_rbf = (char *) rec;
    dbc->rab.rab$w_rsz = sizeof(struct certmap_db_rec) - 
		sizeof(rec->var.s) + vlen;
    dbc->rab.rab$l_rop = RAB$M_ASY | RAB$M_WAT;
    pthread_mutex_lock ( &rms_io );
    status = sys$put ( &dbc->rab, synch_ast, synch_ast );
    dbc->status = synch_rms ( status, &dbc->rab.rab$l_sts );

    return dbc->status;
}

static int delete_record ( db_context *dbc )
{
    int status;
    dbc->rab.rab$b_rac = RAB$C_KEY;		/* Write by key */
    dbc->rab.rab$l_rop = RAB$M_ASY;
    pthread_mutex_lock ( &rms_io );
    status = sys$delete ( &dbc->rab, synch_ast, synch_ast );
    dbc->status = synch_rms ( status, &dbc->rab.rab$l_sts );

    return dbc->status;
}

static int update_record ( db_context *dbc, struct certmap_db_rec *rec )
{
    int status, vlen;
    /*
     * Compute length of variable portion.
     */
    vlen = rec->var.name_len + rec->var.serial_len;
    if ( (rec->var.name_len < 0) || (rec->var.serial_len < 0) ||
	(vlen > sizeof(rec->var.s)) ) return SS$_BADPARAM;

    dbc->rab.rab$b_rac = RAB$C_KEY;		/* Write by key */
    dbc->rab.rab$l_rbf = (char *) rec;
    dbc->rab.rab$w_rsz = sizeof(struct certmap_db_rec) - 
		sizeof(rec->var.s) + vlen;
    dbc->rab.rab$l_rop = RAB$M_ASY;
    pthread_mutex_lock ( &rms_io );
    status = sys$update ( &dbc->rab, synch_ast, synch_ast );
    dbc->status = synch_rms ( status, &dbc->rab.rab$l_sts );

    return dbc->status;
}

static int get_record ( db_context *dbc, int key, int keynum,
	int for_update, struct certmap_db_rec *rec )
{
    int status, dn_len;
    dbc->rab.rab$l_kbf = (void *) &key;
    dbc->rab.rab$b_ksz = sizeof(long);
    if ( keynum < 0 ) {
	/* sequential read, limit to same key. */
	dbc->rab.rab$b_rac = RAB$C_SEQ;
        dbc->rab.rab$l_rop = RAB$M_ASY | RAB$M_WAT | RAB$M_LIM | 
		(for_update) ? RAB$M_RLK : 0;
    } else {
	dbc->rab.rab$b_krf = key;
        dbc->rab.rab$b_rac = RAB$C_KEY;
        dbc->rab.rab$l_rop = RAB$M_ASY | RAB$M_WAT | (for_update) ? RAB$M_RLK : 0;
    }

    dbc->rab.rab$l_ubf = (char *) rec;
    dbc->rab.rab$w_usz = sizeof(struct certmap_db_rec);
    dbc->rab.rab$b_krf = keynum;		/* primary key */

    pthread_mutex_lock ( &rms_io );
    status = sys$get ( &dbc->rab, synch_ast, synch_ast );
    dbc->status = synch_rms ( status, &dbc->rab.rab$l_sts );
    if ( (dbc->status&1) == 1 ) {
	/*
	 * Compute the DN length and check for internal consistency.
	 */
	dn_len = dbc->rab.rab$w_rsz -
		(sizeof(struct certmap_db_rec) - sizeof(rec->var.s) );
	if ( (rec->var.name_len+rec->var.serial_len) > dn_len ) {
	     if ( dn_len >= rec->var.name_len ) {
		rec->var.serial_len = 0;
	     } else {
		rec->var.name_len;
		rec->var.serial_len = 0;
	     }
	}
    } else {
	/*
	 * Record read error.
 	 */
	rec->var.name_len = 0;
	rec->var.serial_len = 0;
    }
    status = dbc->status;
    return status;
}
/*
 * Convert VMS error status to text.
 */
static int format_error ( const char *intro, int code, char buffer[256] )
{
    int flags, status, SYS$GETMSG(), msglen, info, intro_len;
    struct dsc$descriptor buf;

    tu_strnzcpy ( buffer, intro, 252 );
    intro_len = tu_strlen ( buffer );
    buffer[intro_len++] = ':';
    buffer[intro_len++] = ' ';

    buf.dsc$b_dtype = DSC$K_DTYPE_T;		/* text data */
    buf.dsc$b_class = DSC$K_CLASS_S;		/* fixed (Static) */
    buf.dsc$w_length = 255-intro_len;
    buf.dsc$a_pointer = &buffer[intro_len];
    flags = 0;

    pthread_lock_global_np();
    msglen = 0;
    status = SYS$GETMSG ( code, &msglen, &buf, flags, &info );
    pthread_unlock_global_np();

    if ( (status&1) == 1 ) buffer[intro_len+msglen] = '\0';
    else buffer[intro_len] = '\0';
    return status;
}
/*****************************************************************************/
/* Public functions.
 */
struct certmap_db_var *certmap_set_var ( struct certmap_db_var *var, 
	const unsigned char *name, short name_len,
	const unsigned char *serial, short serial_len )
{
    var->name_len = name_len;
    var->serial_len = serial_len;
    memcpy ( var->s, name, name_len );
    memcpy ( &var->s[name_len], serial, serial_len );

    return var;
}

int certmap_close ( void *db )
{
    int status;
    db_context *dbc;
    dbc = (db_context *) db;

    status = sys$close ( &dbc->fab );
    pthread_mutex_destroy ( &dbc->lock );
    LOCK_C_RTL
    free ( dbc );
    UNLOCK_C_RTL;
    return status;

}

int certmap_rewrite ( void *db, struct certmap_db_rec *rec )
{
    int status, vlen;
    db_context *dbc;

    dbc = (db_context *) db;
    /*
     * Compute length of variable portion.
     */
    vlen = rec->var.name_len + rec->var.serial_len;
    if ( (rec->var.name_len < 0) || (rec->var.serial_len < 0) ||
	(vlen > sizeof(rec->var.s)) ) return SS$_BADPARAM;
    /*
     * Call internal routine.
     */
    pthread_mutex_lock ( &dbc->lock );
    status = update_record ( dbc, rec );
    pthread_mutex_unlock ( &dbc->lock );
    return status;
}

int certmap_delete ( void *db )
{
    int status;
    db_context *dbc;

    dbc = (db_context *) db;
    /*
     * Call internal routine.
     */
    pthread_mutex_lock ( &dbc->lock );
    status = delete_record ( dbc );
    pthread_mutex_unlock ( &dbc->lock );
    return status;
}

int certmap_get ( void *db, int pkey, int for_update,
	struct certmap_db_rec *rec )
{
    int status;
    db_context *dbc;

    dbc = (db_context *) db;
    /*
     * serialize access to record stream and call internal routine.
     */
    pthread_mutex_lock ( &dbc->lock );
    status = get_record ( dbc, pkey, 0, for_update, rec );
    pthread_mutex_unlock ( &dbc->lock );
    return status;
}

int certmap_open ( const char *fname, const char *defname,
	const char *mode, void **db, 
	char errmsg[256] )
{
    db_context *dbc;
    int status;
    /*
     * Allocate db structure and initialize..
     */
    pthread_once ( &module_setup, module_init );
    errmsg[0] = '\0';
    dbc = (db_context *) malloc(sizeof(db_context));
    if ( !dbc ) {
	tu_strcpy ( errmsg, "Malloc failure" );
	return 0;
    }
    *db = (void *) dbc;
    dbc->free_results = (struct db_search_result *) 0;
    dbc->fab = cc$rms_fab;
    if (tu_strncmp(mode,"r", 2) == 0) dbc->fab.fab$b_fac = FAB$M_GET;
    else if (strncmp(mode,"r+", 3) == 0)
          dbc->fab.fab$b_fac = 
		FAB$M_DEL | FAB$M_GET | FAB$M_PUT | FAB$M_UPD;
    else {
	sprintf(errmsg,"invalid mode \"%s\"",mode);
	free ( dbc );
	return 0;
    }
    dbc->fab.fab$l_fna = (char *) fname;
    dbc->fab.fab$b_fns = tu_strlen(fname);
    dbc->fab.fab$l_dna = (char *) defname;
    dbc->fab.fab$b_dns = tu_strlen ( defname );
    dbc->fab.fab$b_shr = FAB$V_MSE | FAB$V_SHRPUT | FAB$V_SHRGET |
    FAB$V_SHRDEL | FAB$V_SHRUPD;
    /*
     * Attempt to open file.
     */
    dbc->status = sys$open(&(dbc->fab),0, 0);
    if ( (dbc->status & 1) == 0 ) {
	*db = (void *) 0;
	status = dbc->status;
	LOCK_C_RTL
	free(dbc);
	UNLOCK_C_RTL
	format_error ( "sys$open failure", status, errmsg );
	return status;
    }
    INITIALIZE_MUTEX ( &dbc->lock );

    dbc->rab = cc$rms_rab;
    dbc->rab.rab$l_fab = &(dbc->fab);
    dbc->status = sys$connect(&(dbc->rab),0,0);
    if ( (dbc->status&1) == 0 ) {
	*db = (void *) 0;
	status = dbc->status;
	format_error ( "sys$connect failure", status, errmsg );
	certmap_close ( dbc );
        return status;
    }
    /*
     * Read the master record.
     */
    status = certmap_get ( *db, 0, 0, &dbc->master );
    if ( (status&1) == 0 ) {
	/*
	 * Create initial master record.
	 */
	dbc->master.rec_num = 0;
	dbc->master.rec_hash = 0;
	dbc->master.parent = 0;
	dbc->master.identifier = 0;
	dbc->master.var.name_len = 4;
	dbc->master.var.serial_len = 0;
        dbc->master.var.s[0] = 0;
        dbc->master.var.s[1] = 0;
        dbc->master.var.s[2] = 0;
        dbc->master.var.s[3] = 0;
	pthread_mutex_lock ( &dbc->lock );
	status = put_record ( dbc, &dbc->master );
	pthread_mutex_unlock ( &dbc->lock );
	if ( (status&1) == 0 ) {
	    format_error ( "master record create failure", status, errmsg );
	    return status;
	}
    }

    return dbc->status;
    
}
/*
 * Use FDL library function to create indexed file.
 */
int certmap_create ( const char *fname, const char *defname, char errmsg[256] )
{
    int status, flags, sts, stv, flen;
    static $DESCRIPTOR(filename,"");
    static $DESCRIPTOR(default_name,"");
    /*
     * Set descriptors to point to passed strings and call FDL$CREATE.
     * Dynamically generate FDL to set record length to size
     * of record structure.
     */
    pthread_once ( &module_setup, module_init );
    pthread_mutex_lock ( &access_lock );
    filename.dsc$w_length = tu_strlen ( fname );
    filename.dsc$a_pointer = (char *) fname;
    default_name.dsc$w_length = tu_strlen(defname);
    default_name.dsc$a_pointer = (char *) defname;
    flen = strlen ( fdl_desc_fmt );
    fdl_desc.dsc$a_pointer = malloc ( flen + 100 );
    sprintf ( fdl_desc.dsc$a_pointer, fdl_desc_fmt, 
		sizeof(struct certmap_db_rec) );
    fdl_desc.dsc$w_length = strlen ( fdl_desc.dsc$a_pointer );
    flags = FDL$M_FDL_STRING;

    sts = stv = 0;
    status = FDL$CREATE (
	&fdl_desc, 		/* FDL description (string) */
	&filename, 		/* Name of file to create */
	&default_name, 		/* Default output file name */
	0, 0,			/* Resulting filename/FID, not interested */
	&flags,			/* Option flags */
	0, 0,			/* Statement number and result fn len */
	&sts, &stv);		/* Status codes */

    free ( fdl_desc.dsc$a_pointer );	/* clean up */
    fdl_desc.dsc$w_length = 0;
    fdl_desc.dsc$a_pointer = "";
    if ( (status&1) == 0 ) format_error ( "fdl$create failure", 
		(sts ? sts : status), errmsg );
    else errmsg[0] = '\0';
    pthread_mutex_unlock ( &access_lock );

    return status;
}

/*
 * Search for database record by subject DN.  Subject string is hashed
 * and file records that match that hash value are read until one with
 * matching suject and issuer DNs is encountered.
 */
static int get_records ( db_context *dbc, db_search_context *ctx,
	struct certmap_db_var *subject, int parent )
{
    int hash_val, status, dn_len, subj_len, matches;
    struct db_search_result *res;
    hash_val = hash_name ( subject );

    res = dbc->free_results;
    if ( res ) dbc->free_results = res->next;
    else res = malloc ( sizeof(struct db_search_result) );
    res->next = (struct db_search_result *) 0;

    status = get_record ( dbc, hash_val, 1, 0, &res->rec );
    while ( status&1 ) {
	/*
	 * See if record with this hash value has right subject name,
	 * serial string, and parent record number.
	 */
	matches = 1;
	if ( parent && res->rec.parent ) { 
	    /* Only test parent field if both non-zero */
	    if ( parent != res->rec.parent ) matches = 0;
	}
	if ( subject->name_len != res->rec.var.name_len ) matches = 0;
	else if ( subject->serial_len > 0 ) {
	    if ( subject->serial_len == res->rec.var.serial_len ) {
		if ( memcmp ( &subject->s[subject->name_len],
			&res->rec.var.s[subject->name_len], 
			subject->serial_len ) != 0 ) matches = 0;
	    } else matches = 0;		/* lengths differ */
	}
	if ( matches ) {
	    if ( memcmp ( subject->s, res->rec.var.s, 
		subject->name_len ) != 0 ) matches = 0;
	}
	/*
	 * If record matches, append to list in context and allocate new record.
	 */
	if ( matches ) {
	    if ( !ctx->results ) ctx->results = res;
	    else ctx->previous->next = res;
	    ctx->previous = res;

	    res = dbc->free_results;
	    if ( res ) dbc->free_results = res->next;
	    else res = malloc ( sizeof(struct db_search_result) );
	    res->next = (struct db_search_result *) 0;
	}
	/*
	 * Read sequentially (key=-1) to next record with previous key.
	 */
	status = get_record ( dbc, hash_val, -1, 0, &res->rec );
    }
    /*
     * Put record for failed read back on lookaside list.
     */
    res = dbc->free_results;
    dbc->free_results = res;

    return status;
}
int certmap_search ( void *db, struct certmap_db_var *subject, 
	struct certmap_db_var *issuer,
	void **search_context, struct certmap_db_rec *rec )
{
    int subj_hash, issuer_hash, status, issuer_recnum, dn_len, ilen;
    db_context *dbc;
    db_search_context *ctx;
    struct db_search_result *cur;

    dbc = (db_context *) db;
    ctx = (db_search_context *) *search_context;
    if ( !ctx ) {
	ctx = malloc ( sizeof(db_search_context) );
	ctx->results = (struct db_search_result *) 0;
	ctx->previous = (struct db_search_result *) 0;

	*search_context = (void *) ctx;
    } else if ( ctx->results ) {
        /*
         * Return next result in list and advance in preparation for next call.
         */
	cur = ctx->previous->next;
	ctx->previous = cur;

	if ( cur ) *rec = cur->rec;
	else {
	    return 0;	/* bugcheck */
	}

	if ( !ctx->previous->next ) {
	    /* 
	     * No more records, return results list en masse to free list.
	     */
	    pthread_mutex_lock ( &dbc->lock );
	    cur->next = dbc->free_results;
	    dbc->free_results = ctx->results;
	    pthread_mutex_unlock ( &dbc->lock );
	    ctx->results = (struct db_search_result *) 0;
	}
	return 1;

    } else {
	/*
	 * No more search results.
	 */
	free ( ctx );
	*search_context = (void *) 0;
	return 0;
    }
    /*
     * Initial search.
     */
    subj_hash = hash_name ( subject );
    pthread_mutex_lock ( &dbc->lock );
    if ( issuer ) {
	/*
	 * Add records matching the issuer.
	 */
	status = get_records ( dbc, ctx, issuer, 0 );
	
	if ( ctx->previous ) issuer_recnum = ctx->previous->rec.rec_num;
	else {
	    /*
	     * No matching issuer found.
	     */
	    issuer_recnum = -1;
	}
    } else issuer_recnum = 0;
    /*
     * Fetch main record.
     */
    cur = ctx->previous;	/* save last issuer record added */
    status = get_records ( dbc, ctx, subject, issuer_recnum );
    pthread_mutex_unlock ( &dbc->lock );

    if ( cur == ctx->previous ) {
	/*
	 * No records appended, not found.
	 */
	return ((status&1)==0) ? status : 0;
    }

    if ( !cur ) {
	cur = ctx->results;	/* No issuer checks, first matching record */
    } else {
	cur = cur->next;	/* first record after last issuer record */
    }
    ctx->previous = cur;
    if ( cur ) {
	*rec = cur->rec;
    } else {
	/* bugcheck, should have pointed to next record */
    }

    return 1;
  
}

void certmap_search_end ( void *db, void **search_context )
{
    db_search_context *ctx;
    db_context *dbc;

    dbc = (db_context *) db;
    if ( !search_context ) return;
    ctx = (db_search_context *) *search_context;
    *search_context = (void *) 0;
    
    if ( !ctx ) return;

    pthread_mutex_lock ( &dbc->lock );
    while ( ctx->results ) {
	struct db_search_result *tmp;
	tmp = ctx->results;
	ctx->results = tmp->next;
	tmp->next = dbc->free_results;
	dbc->free_results = tmp;
    }
    pthread_mutex_unlock ( &dbc->lock );

    free ( ctx );
}
/*
 * Add a certificate mapping to the database, returning its unique
 * record ID.  If the issuer DN is the same as the subject DN, the
 * certificate is considered self-signed and the parent is set to
 * the ID of the new record.  If the issuer is different, the record number
 * for issuer is used as the new records 'parent' (add fails if no
 * record for issuer in the database.
 */
int certmap_add ( void *db, struct certmap_db_var *subject, 
	struct certmap_db_var *issuer,
	unsigned long identifier, long *rec_num, char errmsg[256] )
{
    struct certmap_db_rec rec;
    int status, next_rec_num, rlen, mlen, i, self_signed;
    unsigned char *root_dn;
    db_context *dbc;
    void *sctx;
    /*
     * Convert issuer DN to its record number if different than subject DN.
     */
    dbc = (db_context *) db;
    self_signed = 0;
    rec.parent = 0;
    if ( !issuer || (issuer->name_len == 0) ) {
	/* No issuer given, leave parent at zero */

    } else if ( (issuer->name_len == subject->name_len) &&
	(memcmp ( subject->s, issuer->s, issuer->name_len ) == 0) ) {
	/* self-signed, use same record number */
	self_signed = 1;
    } else {
	sctx = (void *) 0;
	status = certmap_search ( db, issuer, 
		(struct certmap_db_var *) 0, &sctx, &rec );
	rec.parent = rec.rec_num;
	if ( sctx ) certmap_search_end ( db, sctx );
	if ( (status&1) == 0 ) {
	    tu_strcpy ( errmsg, "Issuer DN not found in database" );
	    return status;
	}
	if ( rec.parent == 0 ) {
	    tu_strcpy ( errmsg, "Bad issuer record - no parent" );
	    return SS$_ABORT;
	}
    }
    /*
     * Read the control record and allocate the next record number.
     */
    pthread_mutex_lock ( &dbc->lock );
    status = get_record ( dbc, 0, 0, 1, &dbc->master );
    if ( (status&1) == 0 ) {
	pthread_mutex_unlock ( &dbc->lock );
	format_error ( "master record lookup failure", status, errmsg );
	return status;
    }
    root_dn = dbc->master.var.s;
    for (mlen = dbc->master.var.name_len; mlen < sizeof(next_rec_num); mlen++)
		root_dn[mlen] = 0;	/* padd out to full length */
    for ( i = sizeof(next_rec_num)-1; i >= 0; --i ) {	
	/* 
	 * Treat as big-endian 28-bit (7 bits/byte) number and increment 
	 */
	if ( root_dn[i] < 128 ) {
	    root_dn[i]++;
	    break;
	} else root_dn[i] = 0;		/* Increment and carry */
    }
    dbc->master.var.name_len = sizeof(next_rec_num);
    memmove ( &next_rec_num, root_dn, sizeof(next_rec_num) );
    status = update_record ( dbc, &dbc->master );
    if ( (status&1) == 0 ) {
	pthread_mutex_unlock ( &dbc->lock );
	format_error ( "master record update failure", status, errmsg );
	return status;
    }
    /*
     * Add record to file.
     */
    rec.rec_num = next_rec_num;
    if ( self_signed ) rec.parent = next_rec_num;
    rec.rec_hash = hash_name ( subject );
    rec.identifier = identifier;
    rec.var = *subject;
    if ( rec_num ) *rec_num = next_rec_num;

    status = put_record ( dbc, &rec );

    pthread_mutex_unlock ( &dbc->lock );
    if ( (status&1) == 0 ) format_error ( "write failure", status, errmsg );
    return status;
}
/****************************************************************************/
/* Rightslist functions.
 */
int certmap_asctoid ( const char *name, unsigned long *id, int *attrib,
	char errmsg[256] )
{
    int status, SYS$ASCTOID();
    struct dsc$descriptor name_dx;

    name_dx.dsc$b_dtype = DSC$K_DTYPE_T;		/* text data */
    name_dx.dsc$b_class = DSC$K_CLASS_S;		/* fixed (Static) */
    name_dx.dsc$w_length = tu_strlen ( name );
    name_dx.dsc$a_pointer = (char *) name;

    pthread_lock_global_np();
    status = SYS$ASCTOID ( &name_dx, id, attrib );
    pthread_unlock_global_np();
    if ( ((status&1) == 0) && errmsg ) format_error ( "", status, errmsg );
    else errmsg[0] = '\0';

    return status;
}

int certmap_idtoasc ( unsigned long id, char *name, size_t name_size,
	unsigned long *result_id, int *attrib, long *context, 
	char errmsg[256] )
{
    int status, SYS$IDTOASC();
    struct dsc$descriptor name_dx;
    unsigned short namlen;

    if ( id == 0 ) {
	/* Special case the null ID */
	name[0] = '\0';
	errmsg[0] = '\0';
	return 1;
    }
    name_dx.dsc$b_dtype = DSC$K_DTYPE_T;		/* text data */
    name_dx.dsc$b_class = DSC$K_CLASS_S;		/* fixed (Static) */
    if ( name_size > 32767 ) {
	name_dx.dsc$w_length = 32767;
    } else {
        name_dx.dsc$w_length = (unsigned short) name_size-1;
    }
    name_dx.dsc$a_pointer = name;

    pthread_lock_global_np();
    namlen = 0;
    status = SYS$IDTOASC ( id, &namlen, &name_dx, result_id, attrib, context );
    pthread_unlock_global_np();
    if ( (status&1) == 0 ) format_error ( "", status, errmsg );
    else {
	errmsg[0] = '\0';
	name[namlen] = '\0';	/* terminate string */
    }

    return status;
}

int certmap_finish_rdb ( long *context )
{
    int status, SYS$FINISH_RDB();

    pthread_lock_global_np();
    status = SYS$FINISH_RDB ( context );
    pthread_unlock_global_np();
    return status;
}
