/*
 * Copyright (c) 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "milko_locl.h"
#include "ptserver.h"
#include "pts.ss.h"

RCSID("$Id: ptserver.c,v 1.7 1998/10/25 22:14:39 art Exp $");

#define PRSERVERPORT      htons(7002)

#define N_SECURITY_OBJECTS 3

static struct rx_service *prservice;
static struct rx_securityClass *(securityObjects[N_SECURITY_OBJECTS]);

int pr_database;
prheader pr_header;
off_t file_length;

char pr_header_ydr[PRHEADER_SIZE];

void
write_header()
{
    off_t pos;
    int length = PRHEADER_SIZE;

    if (ydr_encode_prheader(&pr_header, pr_header_ydr, &length) == NULL)
	err(1, "write_header");

    pos = lseek(pr_database, 0, SEEK_SET);
    assert(pos == 0);

    length = write(pr_database, pr_header_ydr, PRHEADER_SIZE);
    assert (length == PRHEADER_SIZE);
}

void
read_header()
{
    char pr_header_ydr[PRHEADER_SIZE];
    int length = PRHEADER_SIZE;

    if (lseek(pr_database, 0, SEEK_SET) == -1)
	err(1, "lseek");

    length = read(pr_database, pr_header_ydr, PRHEADER_SIZE);
    if (length == -1)
	err(1, "read");
    if (length != PRHEADER_SIZE)
	errx(1, "read_header read failed");
    
    if (ydr_decode_prheader(&pr_header, pr_header_ydr, &length) == NULL)
	err(1, "write_header");
}

void
get_file_length()
{
    file_length = lseek(pr_database, 0, SEEK_END);
    if (file_length == -1) {
	err(1, "lseek");
    }
}

void
create_database()
{
    pr_header.version = 0;
    pr_header.headerSize = PRHEADER_SIZE;
    pr_header.freePtr = 0;
    pr_header.eofPtr = PRHEADER_SIZE;
    pr_header.maxGroup = -210; /* XXX */
    pr_header.maxID = 0;
    pr_header.maxForeign = 65536; /* XXX */
    pr_header.maxInst = 0;
    pr_header.orphan = 0;
    pr_header.usercount = 0;
    pr_header.groupcount = 0;
    pr_header.foreigncount = 0;
    pr_header.instcount = 0;
    memset(pr_header.reserved, 0, 5 * sizeof(int32_t));
    memset(pr_header.nameHash, 0, 8191 * sizeof(int32_t));
    memset(pr_header.idHash, 0, 8191 * sizeof(int32_t));
    write_header();
    get_file_length();
}

off_t
find_first_free()
{
    off_t pos;

    if (pr_header.freePtr == 0) { /* if there are no free entries */
	pos = lseek(pr_database, 0, SEEK_END);
	if (pos == -1)
	    err(1, "lseek");
	if (ftruncate(pr_database, pos + PRENTRY_DISK_SIZE) == -1)
	    err(1, "ftruncate");
	return pos;
    } else { /* there are free entries */
	/* Not implemented yet */
	assert(0);
    }
    return 0;
}

int
write_entry(off_t offset, prentry *pr_entry)
{
    off_t pos;
    char pr_entry_disk_ydr[PRENTRY_DISK_SIZE];
    int length = PRENTRY_DISK_SIZE;

    if (ydr_encode_prentry_disk((prentry_disk *) pr_entry, pr_entry_disk_ydr, &length) == NULL)
	err(1, "write_entry");

    pos = lseek(pr_database, offset, SEEK_SET);
    assert(pos == offset);

    length = write(pr_database, pr_entry_disk_ydr, PRENTRY_DISK_SIZE);
    assert (length == PRENTRY_DISK_SIZE);

    return 0;
}

int
read_entry(off_t offset, prentry *pr_entry)
{
    off_t pos;
    char pr_entry_disk_ydr[PRENTRY_DISK_SIZE];
    int length = PRENTRY_DISK_SIZE;

    pos = lseek(pr_database, offset, SEEK_SET);
    assert(pos == offset);

    length = read(pr_database, pr_entry_disk_ydr, PRENTRY_DISK_SIZE);
    assert (length == PRENTRY_DISK_SIZE);

    if (ydr_decode_prentry_disk((prentry_disk *) pr_entry, pr_entry_disk_ydr, &length) == NULL)
	err(1, "write_entry");

    return 0;
}

unsigned long
get_id_hash(long id)
{
    return ((unsigned long) id) % HASHSIZE;
}

unsigned long
get_name_hash(const char *name)
{
    int i;
    unsigned long hash = 0x47114711;

    for (i = 0; name[i] && i < 32; i++)
	hash *= name[i];

    return hash % HASHSIZE;
}

int
get_first_id_entry(unsigned long hash_id, prentry *pr_entry)
{
    off_t offset = pr_header.idHash[hash_id];
    int status;

    fprintf(stderr, "  get_first_id_entry hash_id: %lu offset: %ld\n", hash_id, offset);
    if (offset == 0)
	return PRNOENT;

    status = read_entry(offset, pr_entry);

    return status;
}

int
get_first_name_entry(unsigned long hash_name, prentry *pr_entry)
{
    off_t offset = pr_header.nameHash[hash_name];
    int status;

    fprintf(stderr, "  get_first_name_entry hash_name: %lu offset: %ld\n", hash_name, offset);
    if (offset == 0)
	return PRNOENT;

    status = read_entry(offset, pr_entry);

    return status;
}

int
insert_entry(prentry *pr_entry)
{
    off_t offset;
    int status;
    unsigned long hash_id, hash_name;
    prentry first_id_entry;
    prentry first_name_entry;
    
    /* Allokera plats i filen */
    offset = find_first_free();

    /* Hitta plats i hashtabell */
    hash_id = get_id_hash(pr_entry->id);
    hash_name = get_name_hash(pr_entry->name);

    status = get_first_id_entry(hash_id, &first_id_entry);
    pr_entry->nextID = status ? 0 : first_id_entry.nextID;

    status = get_first_name_entry(hash_name, &first_name_entry);
    pr_entry->nextName = status ? 0 : first_name_entry.nextName;

    /* XXX: uppdatera owned och nextOwned */

    /* Lägg in entryt i filen */
    status = write_entry(offset, pr_entry);
    if (status)
	return status;
    
    /* Uppdatera hashtabell */
    pr_header.idHash[hash_id] = offset;
    pr_header.nameHash[hash_name] = offset;
    write_header();
    return 0;
}

int
create_group(const char *name,
	     int32_t id,
	     int32_t owner,
	     int32_t creator)
{
    int status;
    prentry pr_entry;
    
    memset(&pr_entry, 0, sizeof(pr_entry));
    pr_entry.flags = PRGRP;
    pr_entry.id = id;
    pr_entry.owner = owner;
    pr_entry.creator = creator;
    strncpy(pr_entry.name, name, PR_MAXNAMELEN);

    status = insert_entry(&pr_entry);
    if (status)
	return status;

    return 0;
}

int
create_user(const char *name,
	    int32_t id,
	    int32_t owner,
	    int32_t creator)
{
    int status;
    prentry pr_entry;
    
    memset(&pr_entry, 0, sizeof(pr_entry));
    pr_entry.flags = 0;
    pr_entry.id = id;
    pr_entry.owner = owner;
    pr_entry.creator = creator;
    strncpy(pr_entry.name, name, PR_MAXNAMELEN);

    status = insert_entry(&pr_entry);
    if (status)
	return status;

    return 0;
}

int
get_pr_entry_by_id(int id, prentry *pr_entry)
{
    unsigned long hash_id = get_id_hash(id);
    int status;

    fprintf(stderr, "  get_pr_entry_by_id id:%d hash_id: %ld\n", id, hash_id);

    status = get_first_id_entry(hash_id, pr_entry);
    fprintf(stderr, "  get_pr_entry_by_id status:%d\n", status);
    if (status)
	return PRNOENT;

    while (pr_entry->id != id) {
	if (pr_entry->nextID == 0)
	    return PRNOENT;
	status = read_entry(pr_entry->nextID, pr_entry);
    }

    fprintf(stderr, "  pr_entry->id: %d\n", pr_entry->id);
    fprintf(stderr, "  pr_entry->owner: %d\n", pr_entry->owner);
    fprintf(stderr, "  pr_entry->creator: %d\n", pr_entry->creator);
    fprintf(stderr, "  pr_entry->name: %s\n", pr_entry->name);

    return 0;
}

int
get_pr_entry_by_name(const char *name, prentry *pr_entry)
{
    int hash_name = get_name_hash(name);
    int status;

    status = get_first_name_entry(hash_name, pr_entry);
    if (status)
	return PRNOENT;

    while (strcmp(pr_entry->name, name)) {
	if (pr_entry->nextName == 0)
	    return PRNOENT;
	status = read_entry(pr_entry->nextName, pr_entry);
    }

    fprintf(stderr, "  pr_entry->id: %d\n", pr_entry->id);
    fprintf(stderr, "  pr_entry->owner: %d\n", pr_entry->owner);
    fprintf(stderr, "  pr_entry->creator: %d\n", pr_entry->creator);
    fprintf(stderr, "  pr_entry->name: %s\n", pr_entry->name);

    return 0;
}

int
conv_name_to_id(const char *name, int *id)
{
    prentry pr_entry;
    int status;

    status = get_pr_entry_by_name(name, &pr_entry);
    if (status)
	return status;
    *id = pr_entry.id;
    return 0;
}

int
conv_id_to_name(int id, char *name)
{
    prentry pr_entry;
    int status;

    status = get_pr_entry_by_id(id, &pr_entry);
    if (status)
	return status;
    strncpy(name, pr_entry.name, PR_MAXNAMELEN);
    return 0;
}

int
next_free_group_id()
{
    pr_header.maxGroup--; /* XXX */
    write_header();
    return pr_header.maxGroup;
}

int
next_free_user_id()
{
    pr_header.maxID++; /* XXX */
    write_header();
    return pr_header.maxID;
}

int
prserver_init()
{
    int status;

    pr_database = open("pr_database",
		       O_RDWR | O_CREAT,
		       S_IRWXU);
    if (pr_database == -1)
	err(1, "open");

    get_file_length();
    if (file_length == 0) {
	fprintf(stderr, "Database not found, creating a new.\n");
	create_database();
	status = create_group("system:administrators",
			      SYSADMINID,
			      SYSADMINID,
			      SYSADMINID);
	if (status)
	    return status;
    } else {
	fprintf(stderr, "Loading database.\n");
	read_header();
	get_file_length();
    }

    return 0;
}

#ifdef KERBEROS
static int
ptserver_get_key(void *appl, int kvno, des_cblock *key)
{
    /* 
     * XXX this is not really good, since I think read_service_key
     * does not exist in MIT-kerberos
     */
    return read_service_key("afs", "ptstest.stacken.kth.se", "STACKEN.KTH.SE", 
			    0, "/etc/srvtab", (char *)key);
}
#endif

static void
network_init(void)
{
    int maxsec = 1;

    if (rx_Init(0) != 0) 
	errx(1, "Cant open serverport port\n") ;
    
    securityObjects[0] = rxnull_NewServerSecurityObject();   /* XXX 0 */
    if (securityObjects[0] == NULL ) 
	errx(1, "cant create security object") ;

#ifdef KERBEROS
    maxsec = 3;
    securityObjects[1] = NULL;
    securityObjects[2] = rxkad_NewServerSecurityObject(rxkad_clear,
						       NULL,
						       ptserver_get_key,
						       NULL);
    if (securityObjects[2] == NULL)
	errx(1, "init_network: can't init rxkad server-security-object");
#endif

    prservice = rx_NewService(PRSERVERPORT, PR_SERVICE_ID, "pr", 
			      securityObjects, maxsec, 
			      PR_ExecuteRequest);
    
    if (prservice == NULL) 
	errx(1, "Cant create PR service");
}

int main(void) 
{
    int status;

    status = prserver_init();
    if (status) {
	printf("prserver_init: error %d\n", status);
	exit(1);
    }

    network_init();

    printf("started\n"); fflush(stdout);

    rx_SetMaxProcs(prservice,5) ;
    rx_StartServer(1) ;

    /* NOT REACHED */
    return 0;
}
