#include <stdio.h>
#include <strings.h>
#include <ndbm.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/types.h>

#include "inquir.h"

extern void CheckError();
extern char *Lookup(), *Upcase(), *GetFirstWord();
extern void Config(), Update();
extern void Delete(), error(), WriteEntry();
extern char *sys_errlist[];
extern int errno;

#ifdef reality
struct stringlist
    {
    char *string;
    struct stringlist *next;
    };
#endif

struct keylist
    {
    char *key, *data;
    struct keylist *next;
    };

char *GetLine();
char ermes[200];

FILE *cbfp;

cblog( str )
char *str;
{
	fprintf( cbfp, "%s", str );
	fflush(cbfp);
}


main(argc, argv)
int argc;
char **argv;
    {
    char *uname, *entry, *temp, *pname;

    if( (cbfp = fopen( "/tmp/cblog", "w" )) == NULL )
	exit(1);

    setup_strings();
    pname = rindex(argv[0], '/');
    if (pname == NULL)
	pname = argv[0];
    else
	pname++;

    if (freopen(Log, "a", stderr) == NULL)
	{
	printf("Error freopen(%s):  %s\n",Log,sys_errlist[errno]);
	exit(-1);
	}
    if (strcmp(pname, "inquir-lookup") == 0)
	{
	do
	    {
	    uname = Upcase(GetLine(stdin));
	    if (uname == NULL) exit(-1);
	    while (*uname == ' ') uname++;
	    } while (*uname == '\0');

	if (index(uname, '\r') != 0)
	    *(index(uname, '\r')) = '\0';
	if (index(uname, '\n') != 0)
	    *(index(uname, '\n')) = '\0';
	entry = Lookup(uname);

	if (entry == NULL)
	    printf("Unknown UNAME \"%s\"\r\n",Upcase(uname));
	else
	    while (strlen(entry) != 0)
	    {   /* note: returned lines are crlf'd */
	    printf("%s\t:", GetFirstWord(entry, "\3772"));
	    temp = GetFirstWord(entry, "\3772");
		while(1)
		{
		printf("%s\r\n", GetFirstWord(temp, "\n"));
		if (strlen(temp) == 0)
		    break;
		printf("\t:");
		}
	    }
	}
    else if (strcmp(pname, "inquir-config") == 0)
	Config();
    else if (strcmp(pname, "inquir-update") == 0)
	{
	if (argc <= 1)
	    Update("NOBODY");
	else
	    Update(Upcase(argv[1]));
	}
    fclose(stderr);
    fclose(cbfp);
    exit(0);
    }

char *Lookup(uname)
char *uname;
    {
    DBM *db;
    datum user, entry;
    char *realent;
    int tomalloc;

    db = dbm_open(Inquirdata, O_RDONLY, 0);
    if (db == NULL)
	CheckError("Inquir: Lookup can't open data file %s at %d:  %E",
	    Inquirdata,__LINE__);
    user.dptr = uname;
    user.dsize = strlen(uname);
    entry = dbm_fetch(db, user);
    if (dbm_error(db) != 0)
	CheckError("Inquir: Lookup dbm_fetch(%s) failed at %d:  %E",
	    uname,__LINE__);
    if (entry.dptr == NULL)
	{
	dbm_close(db);
	return(NULL);
	}
    tomalloc = entry.dsize + 1;
    realent = (char *)malloc(tomalloc);
    if (realent == NULL)
	CheckError("Inquir: Lookup malloc(%d) failed at %d:  %E",
	    tomalloc,__LINE__);
    strncpy (realent, entry.dptr, entry.dsize);
    dbm_close(db);
    *(realent + entry.dsize) = '\0';

    /* should this check for legitimate fields? */

    /* the current server returns lines with only lf for lookups -- it
    should probably return crlf'd lines. */

    return(realent);
    }

void Config()
    {
    FILE *fd;
    char *confline;

    fd = fopen(Inqconfig, "r");
    if (fd == NULL)
	CheckError("Inquir: Config fopen(%s) failed at %d:  %E",
	    Inqconfig,__LINE__);
    confline = GetLine(fd);
    if (confline == NULL) exit(-1);
    while (*confline != '\0')
	{
	printf("%s\r\n", confline);  /* this ends each line with a crlf,
		      which the current server does */
	confline = GetLine(fd);
	if (confline == NULL) exit(-1);
	}
    return;
    }

void mail_fix(user, netad)
char *user, *netad;
    {
    char *Downcase(), *duser;
    char sysline[1000];
    FILE *af;
    int fd1;

    cblog( "in mail_fix()\n");

    duser = Downcase(user);
    if (user == NULL)
	{
	error("User is null, mail not updated.");
	exit(-1);
	}

    /* This lock insures INQUIR doesn't grab a file that is in the process
    of being changed */

    if ((fd1 = open(Aliases, O_RDWR, 0664)) < 0)
	{
	error("alias file is unopenable");
	exit(-1);
	}

    if (flock(fd1, LOCK_EX) < 0) 
	{
	error("alias file is unlockable, go figure");
	exit(-1);
	}

    umask(0113);
#ifdef sun 
    sprintf(sysline, "/usr/bin/grep -i -v '^%s:' %s > %s",
#else
    sprintf(sysline, "/usr/ucb/grep -i -v '^%s:' %s > %s",
#endif
	duser, Aliases, Tmpfile);
    system(sysline);

    if (!((netad == NULL) || (netad[0] == 0)))
	{
	if( (af = fopen(Tmpfile, "a")) == NULL )
	    error( Tmpfile );
	fprintf(af, "%s: %s\n", duser, netad);
	fclose(af);
	}

    if (rename(Tmpfile, Aliases) < 0)
	perror("rename failure");

    flock(fd1, LOCK_UN);
    close(fd1);

    /* Now dbmedit to catch everything up - dbmedit is kind enough to handle
    its own locking */

    if ((netad == NULL) || (netad[0] == 0))
	strcpy (sysline, Dbm_delete);
    else
	if ((index(netad, '|') == netad) || index(netad,'<') || 
	    index (netad,'>'))
	    error("Invalid mail format");
	else
	    strcpy (sysline, Dbm_change);

    strcat(sysline, " " );
    strcat(sysline, duser);
    strcat(sysline, " user-aliases.txt ");
    strcat(sysline, netad);
#ifdef NOTDEF
    strcat(sysline, " >> ");
    strcat(sysline, Log);
    strcat(sysline, " 2>&1");
#endif
    fprintf(stderr, "%s\n", sysline);
    fflush(stdout);
    system(sysline);
}

void Update(user)
char *user;
    {
    struct keylist *krbtemp, *newent, *newtemp = NULL, *newtemp2, 
    *onewtemp2, *oldent, *oldtemp;
    char *entline, *old, *uname = NULL, *suname = NULL, *entry, *olddataptr;
    int i;
    char *GetFirstWord(), *Validate(), *Upcase(), *StripLeadingPattern();
    char *StripFirstWord();
    char *tmp = NULL;
    struct keylist *MakeEntry();
    uid_t uid = NULL, uidold, top;
    datum uidkey, uiduser;
    DBM *uiddbase;
    int topuid;
    char *netad, netbuf[500];
    char otheruname[150];
    int tomalloc;
    int tempuid;

    newent = NULL;
    netad = NULL;

    do  {
	tmp = GetLine(stdin);
	if (tmp == NULL) error("Input string is null-1");
	entline = Validate(tmp);
	} while (entline == NULL || strncmp(entline, "BEGIN", 5) != 0);

    for (;;)
	{
	do  {
	    tmp = GetLine(stdin);
	    if (tmp == NULL) error("Input string is null-2");
	    entline = Validate(tmp);
	    } while (entline == NULL);

	if (strncmp(entline, "END:", 4) == 0) break;

	if (*entline == ':')
	    {
	    if (newtemp == NULL)
	    error("Can't continue a non-existent item");
	    tomalloc = strlen(newtemp->data) + strlen(entline) + 1;
	    newtemp->data = (char *)realloc((char *)newtemp->data,tomalloc);
	    if (newtemp->data == NULL)
		CheckError("Inquir: Update realloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    strcat(newtemp->data, "\n");
	    strcat(newtemp->data, (entline + 1));
	    continue;
	    }

	if (strncmp(entline, "SUNAME", 6) == 0)
	    {
	    suname=Upcase(StripLeadingPattern(StripFirstWord(entline,":")," "));
	    continue;
	    }

	if (newent == NULL)
	    {
	    tomalloc = sizeof(struct keylist);
	    newent = (struct keylist *)malloc( tomalloc );
	    if (newent == NULL)
		CheckError("Inquir: Update malloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    newtemp = newent;
	    newtemp->next = NULL;
	    }
	else
	    {
	    tomalloc = sizeof( struct keylist );
	    newtemp->next = (struct keylist *)malloc(tomalloc);
	    if (newtemp->next == NULL)
		CheckError("Inquir: Update malloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    newtemp = newtemp->next;
	    newtemp->next = NULL;
	    }
	newtemp->key = GetFirstWord(entline, ":");
	newtemp->data = (char *)malloc(sizeof(char) * (strlen(entline) + 1));
	strcpy(newtemp->data, entline);

	if (strncmp(newtemp->key, "UNAME", 5) == 0)
	    {
	    newtemp->data = Upcase(StripLeadingPattern(newtemp->data, " "));
	    uname = newtemp->data;
	    }
	if (strncmp(newtemp->key, "OWNER", 5) == 0)
	    newtemp->data = Upcase(newtemp->data);

	if (strncmp(newtemp->key, "UID", 3) == 0)
	    {
	    if (sscanf(newtemp->data, "%d", &tempuid) < 1)
		uid = NULL;
	    else
		uid = tempuid;
	    if (!uid)
		{
		free(newtemp->data);
		newtemp->data = NULL;
		}
	    }

	if (strncmp(newtemp->key, "NETAD", 5) == 0)
	    {
	    netad = netbuf;
	    sscanf(newtemp->data,"%s",netbuf);
	    }
	}

    if (suname == NULL)
	error("No SUNAME given.");

    krbtemp = NULL;
    uidold = NULL;

    if ((uname != NULL) && (strlen(uname) > 0) && (strcmp(suname, uname) != 0))
	if (Lookup(uname))
	    {
	    sprintf(ermes,"%s already exists.",uname);
	    error(ermes);
	    }

    oldent = MakeEntry(suname);
    for (oldtemp = oldent ; oldtemp != NULL ; oldtemp = oldtemp->next)
	{
	if (strcmp(oldtemp->key, "OWNER") == 0)
	    krbtemp=oldtemp;
	if (strcmp(oldtemp->key, "UID") == 0)
	    if (sscanf(oldtemp->data, "%d", &tempuid) < 1)
		uidold = NULL;
	    else
		uidold = tempuid;
	}
    oldtemp = krbtemp;
    if (oldtemp != NULL)
	if (oldtemp->data != NULL)
	    if (*oldtemp->data != '\0' && *oldtemp->data != '\n' &&
		*oldtemp->data != '\r')
		if (strcmp(suname, user) != 0)
		    {
		    olddataptr = oldtemp->data;
		    for (;;)
			{
			olddataptr = index(olddataptr, *user);
			if (olddataptr == 0)
			    {
			    sprintf(ermes, "You are not a Kerberos OWNER of %s.",suname);
			    error(ermes);
			    }
			if (strncmp(olddataptr, user, strlen(user)) == 0)
			    if (*(olddataptr + strlen(user)) == '\0' ||
				*(olddataptr + strlen(user)) == ' ' ||
				*(olddataptr + strlen(user)) == ',')
				break;
			olddataptr++;
			}
		    }

    uiddbase = dbm_open(Uiddbase, (O_RDWR | O_CREAT), 0644);
    if (uiddbase == NULL) 
	CheckError("Inquir: update dbm_open(%s) failed at %d:  %E",
	    Uiddbase,__LINE__);
    uidkey.dsize = sizeof(uid_t);
    uidkey.dptr = (char *) &uidold;

    if (uname == NULL)
	{
	if (uidold != 0)                  /* That is, he had the old uid */
	    dbm_delete(uiddbase, uidkey);   /* so nuke it now. */
	mail_fix(suname, NULL);           
	cblog( "after mail_fix()\n");
	Delete(suname);
	cblog( "after Delete()\n");
	}

    cblog("after Delete()\n" );

    if (oldent != NULL)
	{
	newtemp = newent;
	while (newtemp->next != NULL)
	newtemp = newtemp->next;
	newtemp->next = oldent;
	}
    newtemp = newent;

    cblog("after newtmp()\n" );

    while (newtemp->next != NULL)
	{
	i = strcmp(newtemp->key, newtemp->next->key);
	if (i < 0)
	    newtemp = newtemp->next;
	else
	    if (i == 0)
		{
		newtemp2 = newtemp->next;
		newtemp->next = newtemp->next->next;
		free(newtemp2->key);
		free(newtemp2->data);
		free(newtemp2);
		}
	    else
		{
		newtemp2 = newent;
		onewtemp2 = NULL;
		while (newtemp2 != newtemp->next)
		    {
		    i = strcmp(newtemp->next->key, newtemp2->key);
		    if (i > 0)
			{
			onewtemp2 = newtemp2;
			newtemp2 = newtemp2->next;
			}
		    else if (i == 0)
			{
			newtemp2 = newtemp->next;
			newtemp->next = newtemp->next->next;
			free(newtemp2->key);
			free(newtemp2->data);
			free(newtemp2);
			break;
			}
		    else
			{
			if (onewtemp2 == NULL)
			    newent = newtemp->next;
			else
			    onewtemp2->next = newtemp->next;
			newtemp->next = newtemp->next->next;
			if (onewtemp2 == NULL)
			    newent->next = newtemp2;
			else
			    onewtemp2->next->next = newtemp2;
			break;
			}
		    }
		}
	}
    cblog("after newtmp->next\n" );

    if ((topuid = open(Uidfile, O_RDONLY, 0644)) == -1)
	CheckError("Inquir: update open(%s) failed at %d:  %E",
	    Uidfile,__LINE__);

    cblog("after topuid\n" );

    alarm(60);  /* Wait one minute for a flock, then give up */
    if ((flock(topuid, LOCK_EX)) == -1)
	CheckError("Inquir: update flock failed at %d:  %E",__LINE__);
    alarm(0);  /* We have the lock, let's not panic anymore */
    if (read(topuid, (char *) &top, sizeof(uid_t)) == -1)
	CheckError("Inquir: update read(uid) failed at %d:  %E",__LINE__);

    cblog("after Alarm\n" );

    if (top == uid) 
	error("That UID is reserved for INQUIR, use another.");

    uidkey.dptr = (char *) &uid;
    if (uid != NULL)
	{
	uiduser = dbm_fetch(uiddbase, uidkey);
	if (uiduser.dptr != NULL)
	    if (strncasecmp(uiduser.dptr, suname, uiduser.dsize) != 0) 
		if (uiduser.dsize != strlen(suname))
		    {
		    strncpy(otheruname, uiduser.dptr, uiduser.dsize);
		    otheruname[uiduser.dsize] = 0;
		    sprintf(ermes, "That UID belongs to %s.",otheruname);
		    error(ermes);
		    }
	}

    cblog("after uidkey.dptr\n" );

    if ((uidold != uid) || (strcmp(suname, uname) != 0))
	{
	uidkey.dptr = (char *) &uidold;
	if (uidold)
	    dbm_delete(uiddbase, uidkey);
	uidkey.dptr = (char *) &uid;
	uiduser.dptr = uname;
	uiduser.dsize = strlen(uname);
	if (uid)
	    if (dbm_store(uiddbase, uidkey, uiduser, DBM_REPLACE) == -1)
		CheckError("Inquir: update dbm_store(%d) failed at %d:  %E",
		    uid,__LINE__);
	}

    WriteEntry(uname, newent);

    cblog("after WriteEntry\n" );

    if (strcmp(suname, uname) != 0)
	{
	mail_fix(suname, NULL);
	mail_fix(uname, netad);
	Delete(suname);
	}

    cblog("after Delete\n" );

    if (netad) 
	mail_fix(uname, netad);

    dbm_close(uiddbase);

    cblog("after dbm_close\n" );

    flock (topuid, LOCK_UN);
    close (topuid);
    fflush(stderr);
    fclose(stderr);
    printf("+");
    fflush(stdout);
    exit(0);
    }

struct keylist *MakeEntry(name)
char *name;
    {
    char *entry;
    struct keylist *oldent = NULL, *temp;
    char *Lookup(), *GetFirstWord();
    int tomalloc;

    entry = Lookup(name);
    if (entry == NULL)
	return(NULL);

    while (*entry != '\0')
	{
	if (oldent == NULL)
	    {
	    tomalloc = sizeof(struct keylist);
	    temp = oldent = (struct keylist *)malloc( tomalloc );
	    if (oldent == NULL)
		CheckError("Inquir: MakeEntry malloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    }
	else
	    {
	    tomalloc = sizeof(struct keylist);
	    temp->next = (struct keylist *)malloc(tomalloc);
	    if (temp->next == NULL)
		CheckError("Inquir: MakeEntry malloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    temp = temp->next;
	    }
	temp->next = NULL;
	temp->key = GetFirstWord(entry, "\3772");
	temp->data = GetFirstWord(entry, "\3772");
	}
    return(oldent);
    }

void Delete(key)
char *key;
    {
    DBM *db;
    datum obj;
    void CheckError();

    obj.dptr = key;
    obj.dsize = strlen(key);

    db = dbm_open(Inquirdata,
    O_WRONLY, 0);
    if (db == NULL)
	CheckError("Inquir: delete dbm_open(%s) failed at %d:  %E",
	    Inquirdata,__LINE__);
    dbm_delete(db, obj);
    if (dbm_error(db) != 0)
	CheckError("Inquir: delete dbm_delete(%s) failed at %d:  %E",
	    key,__LINE__);
    dbm_close(db);
    printf("+");
    fflush(stdout);
    exit(0);
    }

void WriteEntry(key, entry)
char *key;
struct keylist *entry;
    {
    DBM *db;
    char *realent = NULL;
    datum obj, content;
    int tomalloc;

    if (entry == NULL)
	error("WriteEntry: Entry is null.  Not written.");

    for (; entry != NULL; entry = entry->next)
	{
	if (realent == NULL)
	    {
	    tomalloc = strlen(entry->key) + strlen(entry->data) + 3;
	    realent = (char *)malloc( tomalloc );
	    if (realent == NULL)
		CheckError("Inquir: WriteEntry malloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    strcpy(realent, entry->key);
	    }
	else
	    {
	    tomalloc = strlen(realent) +
			strlen(entry->key) +
			strlen(entry->data) + 5;
	    realent = (char *)realloc(realent, tomalloc );
	    if (realent == NULL)
		CheckError("Inquir: WriteEntry realloc(%d) failed at %d:  %E",
		    tomalloc,__LINE__);
	    strcat(realent, "\3772");
	    strcat(realent, entry->key);
	    }
	strcat(realent, "\3772");
	strcat(realent, entry->data);
	}

    obj.dptr = key;
    obj.dsize = strlen(key);
    content.dptr = realent;
    content.dsize = strlen(realent);

    db = dbm_open(Inquirdata, O_WRONLY | O_CREAT, 644);
    if (db == NULL)
	CheckError("Inquir: WriteEntry dbm_open(%s) failed at %d:  %E",
	    Inquirdata,__LINE__);
    dbm_store(db, obj, content, DBM_REPLACE);
    if (dbm_error(db) != 0)
	CheckError("Homes: WriteEntry dbm_store(%s) failed at %d:  %E",
	    key,__LINE__);
    dbm_close(db);
    }


void Shutdown()
    {
    }


char *Validate(s)
char *s;
    {
    int i = 0;

    if (index(s, '\r') != 0)
	*(index(s, '\r')) = '\0';
    if (index(s, '\n') != 0)
	*(index(s, '\n')) = '\0';

    while (*(s + i) != ':')
	{
	if (strlen(s + i) == 0)
	    return(NULL);
	if ((*(s + i) == ' ') || (*(s + i) == '	'))
	    {
	    strcpy((s + i), (s + i + 1));
	    i--;
	    }
	i++;
	}
    return(s);
    }
