/************************************************************
 *     sign.c
 *
 *  Contains code to "sign" order
 *     
 ************************************************************/

#if defined(macintosh) || defined(__MWERKS__)

#include <limits.h>
#include <bsd-mac-compat.h>
short MacOSErr;		//@ bsd.lib needs this

#include<stdlib.h>
#define h_errno 0
#define NO_THREADS

#else

#include <limits.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <alloca.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <fcntl.h>

#endif

#include "sign.h"
#include <gss.h>
#include <transd-err.h>

struct transd_rqst {
  long	procnum;
  char	orderno[32-sizeof(long)];
};

struct transd_reply {
  long	status;
  char	text[4];
};

static struct {
    struct sockaddr_in	xdaddr;
    char		xdname[128];
    char		xdservice[1024];
    char		xdmechanism[1024];
    char		no[128];
} order;


/*
 *	QueryDaemon()
 */

static
char *QueryDaemon(long procnum, char *rqst_text, long *perr) {
  int			sock;
  char			*text;
  struct transd_rqst	rqst;
  struct transd_reply	*reply;
  gss_buffer_desc	cleartext, encrypted;
  OM_uint32		maj_stat, min_stat, ignore;
  int			conf_state;
  gss_ctx_id_t		context;

  /* initialize things we must free */
  context = GSS_C_NO_CONTEXT;
  cleartext.length = 0;
  encrypted.length = 0;

  /* connect to server */
  text = connect_socket(&sock, &order.xdaddr);
  if (text) {
    *perr = TRANSD_TIMEDOUT;
    goto Exit;
  }

  /* establish GSSAPI context */
  text = client_establish_context(sock, GSS_C_NO_CREDENTIAL, order.xdservice, order.xdmechanism, &context);
  if (text != NULL) {
    *perr = TRANSD_NOCRED;
    goto Exit;
  }

  /* Assemble and seal the message */
  cleartext.length = sizeof(rqst);
  cleartext.value = (char*)&rqst;
  rqst.procnum = htonl(procnum);
  strncpy(rqst.orderno, rqst_text, sizeof(rqst.orderno));
  maj_stat = gss_seal(&min_stat, context, 1, GSS_C_QOP_DEFAULT,
		      &cleartext, &conf_state, &encrypted);
  if (maj_stat != GSS_S_COMPLETE) {
    cleartext.length = 0;	/* for automatic storage */
    *perr = TRANSD_NOCRED;
    text = gss_errmsg("sealing message", maj_stat, min_stat);
    goto Exit;
  }

  /* Send to server */
  if (send_token(sock, &encrypted) < 0) {
    *perr = TRANSD_TIMEDOUT;
    text = unix_errmsg("while sending", errno);
    goto Exit;
  }
  (void)gss_release_buffer(&ignore, &encrypted);


  /* receive text */
  if (recv_token(sock, &encrypted) < 0) {
    *perr = TRANSD_TIMEDOUT;
    text = unix_errmsg("while receiving", errno);
    goto Exit;
  }

  /* Unseal the message token */
  maj_stat = gss_unseal(&min_stat, context, &encrypted, &cleartext, NULL, NULL);
  if (maj_stat != GSS_S_COMPLETE) {
    *perr = TRANSD_NOCRED;
    text = gss_errmsg("unsealing message", maj_stat, min_stat);
    goto Exit;
  }
  (void)gss_release_buffer(&ignore, &encrypted);

  /* non-error text */
  reply = (struct transd_reply *)cleartext.value;
  *perr = ntohl(reply->status);
  text = (char *)malloc(strlen(reply->text)+1);
  strcpy(text, reply->text);
  (void)gss_release_buffer(&ignore, &cleartext);

  /* cleanup and text */
 Exit:
  (void)gss_release_buffer(&ignore, &cleartext);
  (void)gss_delete_sec_context(&ignore, &context, &cleartext);
  (void)gss_release_buffer(&ignore, &cleartext);
  (void)gss_release_buffer(&ignore, &encrypted);
  close(sock);
  return text;
}


/*
 *	readOrder()
 */

char *readOrder(char *hostname, int port, char *service,
		char *mechanism, char *orderno, long *perr)
{
    struct hostent *h = gethostbyname(hostname);

    /* if the hostname is bogus formulate a reply */
    if (!h) {
      char *reply = (char *)malloc(128);
      sprintf(reply, "Unknown hostname \"%s\" (h_errno %d)\n", hostname, h_errno);
      return reply;
    }

    /* name that daemon! */
    memset(&order.xdaddr, '\0', sizeof(order.xdaddr));
    order.xdaddr.sin_family = AF_INET;
    order.xdaddr.sin_addr.s_addr = htonl(((struct in_addr *)(h->h_addr_list[0]))->s_addr);
    order.xdaddr.sin_port = htons(port);
    strcpy(order.xdname, h->h_name);
    strcpy(order.xdservice, service);
    strcpy(order.xdmechanism, mechanism);

    /* save order number */
    strcpy(order.no, orderno);

    /* query the transaction daemon for the order text */
    return QueryDaemon(1, order.no, perr);
}


/*
 *	signOrder()
 */

char *signOrder(long *perr)
{
    /* query the transaction daemon for the order confirmation */
    return QueryDaemon(2, order.no, perr);
}
