/* inquir:/u/sra/INQUIR/emacs-ui/client.c, 5-Sep-1990 01:05:24, sra 
 * krb error fixed, client renamed to /usr/site/watson and 
 * source moved to /usr/src/site/inquir/inquir.c 3-9-92 jhbrown
 *
 * lots and lots of piddly changes, improved error-handling, 
 * name changed to inquir-backend.c and now lives in /common/inquir/src/
 * A million thanks to metcalf for all his debugging help.
 * - jhbrown 7-31-92
 *
 * Debugging improved to make this work with inquir server on python.
 * - Chris@NDA.COM 8-30-93
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>

/* rob: I know this is gross, but I'm not sure what to do and how much
   cleanliness is valued in this perl-db hack to start with  -gdt */

#ifdef INQUIR_KRB
#include <krb.h>
static char krberr[2048] = "";
static struct sockaddr_in serveraddr;
static struct sockaddr_in myaddr;
extern char *getenv(const char *);

#define INQUIR_PRINCIPAL "inquir"
#define INQUIR_INSTANCE "inquir"
#endif INQUIR_KRB

#ifndef	INQUIR_HOST
#define	INQUIR_HOST	"inquir"
#endif

#define BUFSIZE	32768

static FILE *neti = NULL, *neto = NULL;
static char *progname = NULL;
extern char *strrchr();

static void usage()
    {
    printf("- Usage: %s [-c] [-l uname] [-u] [-n]\r\n", progname);
    exit(1);
    }

static void fatal(char *msg)
    {
    perror(msg);
    exit(1);
    }

static void tcpmux(char *contact)
    {
    struct hostent *hp;
    struct servent *sp;
    struct sockaddr_in sin;
    int s, lost, c;

    if ((hp = gethostbyname(INQUIR_HOST)) == NULL)
	fatal("gethostbyname");
    bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
    if ((sp = getservbyname("tcpmux", "tcp")) == NULL)
	sin.sin_port = htons(1);
    else
	sin.sin_port = sp->s_port;

#ifdef INQUIR_KRB
    bcopy(&sin, &serveraddr, sizeof(struct sockaddr_in));
#endif

    if ((s = socket((sin.sin_family = hp->h_addrtype), SOCK_STREAM, 0)) < 0)
	fatal("socket");
    if (connect(s, (char *) &sin, sizeof(sin)) < 0)
	fatal("connect");
    if ((neti = fdopen(s,"r")) == NULL)
	fatal("fdopen");
    if ((s = dup(s)) < 0)
	fatal("dup");
    if ((neto = fdopen(dup(s),"w")) == NULL)
	fatal("fdopen");
    if (fprintf(neto, "%s\r\n", contact) == EOF)
	fatal("fprintf");
    if (fflush(neto) == EOF)
	fatal("fflush");
    if ((lost = (c = getc(neti)) != '+') && c != EOF)
	putchar(c);
    while ((c = getc(neti)) != EOF)
	{
	if (lost) putchar(c);
	if (c == '\n') break;
	}
    if (lost)
	fatal("lost");
    }

static void tcpmuxclose()
    {
    if ( neti ) fclose(neti);
    if ( neto ) fclose(neto);
    neti = neto = NULL;
    }

/* 
 * Try to do update using kerberos, and fall back to regular way
 * if this fails.
 */
static void update()
    {
    int i;
    int len, netlen, privlen;
    char c;
    char inbuf[BUFSIZE];
    char netbuf[BUFSIZE];

#ifdef INQUIR_KRB
    char princ[ANAME_SZ+1];
    KTEXT_ST auth_msg;
    CREDENTIALS cred;
    des_key_schedule sched;
    char *tfname = getenv("KRBTKFILE");
    int mylen = sizeof(myaddr);
#endif

    if( (len = fread(inbuf, sizeof(char), BUFSIZE, stdin)) < 0 )
	fatal("fread in update");

#ifdef INQUIR_KRB

    /* check for tickets */
    if (tfname == NULL || krb_get_tf_fullname(tfname, princ, 0, 0) != KSUCCESS)
	{
	printf("krb_get_tf_fullname failed.\n");
	goto non_krb_update;
	}

    tcpmux("inquir-update-krb");

    fflush(neto);			/* we will not use stdio */

    /* build authenticator */
    i = krb_mk_req(&auth_msg,
	INQUIR_PRINCIPAL, INQUIR_INSTANCE,
	krb_realmofhost(INQUIR_HOST), 0);
    if ( i != KSUCCESS ) goto non_krb_update;

    /* send length, and then auth data */
    netlen = htonl(auth_msg.length);
    if( write(fileno(neto), &netlen, sizeof(int)) != sizeof(int) )
	goto non_krb_update;
    if( write(fileno(neto), auth_msg.dat, auth_msg.length) != auth_msg.length )
	goto non_krb_update;

    /* fetch session key */
    i = krb_get_cred(INQUIR_PRINCIPAL, INQUIR_INSTANCE,
	krb_realmofhost(INQUIR_HOST), &cred);
    if ( i != KSUCCESS ) goto non_krb_update;
    des_key_sched(cred.session, sched);

    /* obtain my internet address */
    if (getsockname (fileno(neto), (struct sockaddr *) &myaddr, &mylen) < 0)
	goto non_krb_update;

    /* make private message */
    privlen = krb_mk_priv(inbuf, netbuf, len,
	sched, cred.session,
	&myaddr, &serveraddr);
    if ( privlen < 0 ) goto non_krb_update;

    netlen = htonl(privlen);
    if( write(fileno(neto), &netlen, sizeof(int)) != sizeof(int) )
	goto non_krb_update;
    if( write(fileno(neto), netbuf, privlen) != privlen )
        goto non_krb_update;

    /* check return code */
    if (fflush(neto) == EOF)		fatal("fflush");
    if (shutdown(fileno(neto), 1))	fatal("shutdown");
    if (fclose(neto) == EOF)		fatal("fclose");
    neto = NULL;

    c = getc(neti);
    if ( c == '+' )
	{
	putchar(c);
	while((c = getc(neti)) != EOF)
	    putchar(c);
	tcpmuxclose();
	exit();
	}

    i = 0;
    while ((c=getc(neti)) != EOF)
	krberr[i++] = c;
    /* lost, fall through */
    krberr[i] = 0;

non_krb_update:
    if (!krberr[0]) 
	strcpy(krberr,"-KERBEROS error, unable to authenticate\r\n");

    tcpmuxclose();
#endif

    tcpmux("inquir-update");

    fwrite(inbuf, sizeof(char), len, neto);
    /* deal with return code */
    }

int main(int argc, char *argv[])
    {
    int c;

    if( progname = strrchr(argv[0],'/') )
	progname++;
    else
	progname = argv[0];

    if (argc < 2 || argv[1][0] != '-')
	usage();
    switch (argv[1][1])
	{
	case 'c':	if (argc != 2) usage();
			tcpmux("inquir-config");
			break;

	case 'l':	if (argc != 3) usage();
			tcpmux("inquir-lookup");
			if (fprintf(neto, "%s\r\n", argv[2]) == EOF)
			    fatal("fprintf");
			break;

	case 'u':	if (argc != 2) usage();
			update();
			break;

	case 'n':	if (argc != 2) usage();
			tcpmux("inquir-new-uid");
			break;

	default:	usage();
	}
    /*
    * Just closing neto doesn't send a FIN packet, sigh.  Hit outbound
    * connection with large blunt object, sparing inbound connection.
    */
    if (fflush(neto) == EOF)		fatal("fflush");
    if (shutdown(fileno(neto), 1))	fatal("shutdown");
    if (fclose(neto) == EOF)		fatal("fclose");
    neto = NULL;

    fputc( c = getc(neti), stdout );
#ifdef INQUIR_KRB
    if( krberr[0] )
	{
	printf("KERBEROS update failed with:  %s",krberr);
	printf("Non KERBEROS update %s", (c=='+')?"Succeeded":"Failed with:  ");
	}
#endif

    while((c = getc(neti)) != EOF)
	putchar(c);
    if (fclose(neti) == EOF)
	fatal("fclose");
    neti = NULL;
    return 0;
    }
