/*
 * transd.c
 *
 */
#include "transd.h"
#include <gss.h>

/* globals set at startup */

int			VERBOSE;
int			DEBUG;
char			INI[1024];
int			FACILITY;
int			PRIORITY;
char			PROGNAME[256];
char			HOSTNAME[256];
char			SERVICE[1024];
char			MECHANISM[1024];
char			DBNAME[256];
time_t			STARTTIME;
int			PID;
struct sockaddr_in	TRANSD_ADDR;
gss_cred_id_t		SERVER_CREDS;
struct sockaddr_in	SPROFILE_ADDR;
char			SPROFILE_SERVICE[1024];
char			SPROFILE_MECHANISM[1024];


/*******************************************************************************
 *
 * Debugging code
 *
 ******************************************************************************/

void logger(int priority, char *msg, ...)
{
  va_list args;

  va_start(args, msg);

  if (priority <= PRIORITY)
    vsyslog(priority, msg, args);

  if (VERBOSE) {
    time_t now = time(NULL);
    char buf[0x10000], *p=buf;
    p += sprintf(p, "%.*s %s transd[%d]: ", 15, ctime(&now)+4, HOSTNAME, PID);
    p += vsprintf(p, msg, args);
    p += sprintf(p, "\n");
    fprintf(stderr, buf);
    fflush(stderr);
  }

  va_end(args);
}

#define DIE() __die(__FILE__,__LINE__)
static int __die(char *file, int line)
{
  printf("Died in \"%s\" on line %d\n", file, line);
  perror(NULL);
  exit(1);
}



/*******************************************************************************
 *
 * startup utility functions
 *
 ******************************************************************************/

static void setup(int argc, char *argv[], char *env[])
{
  struct hostent	*h;

  int			retval;
  char			c, *errmsg;

  profile_t		profile;
  char			**values;
  const char		*names[3];

  /* must be root */
  if (geteuid()) {
    printf("Must be root!\n");
    exit(2);
  }

  /* remember our program name */
  strcpy(PROGNAME, argv[0]);

  /* get our pid */
  PID = getpid();

  /* remember our startup time */
  STARTTIME = time(NULL);

  /* set defaults */
  VERBOSE = 0;
  DEBUG = 0;
  strcpy(INI, "/etc/ecat.conf");

  /* get options */
  while((c = getopt(argc, argv, "di:v")) != EOF)
    switch (c) {
    case 'd':
      ++DEBUG;
      break;
    case 'i':
      strcpy(INI, optarg);
      break;
    case 'v':
      ++VERBOSE;
      break;
    default:
      goto Usage;
    }
  if (optind != argc) goto Usage;

  /* detach from shell process if not debugging */
  if (!DEBUG) detach();

  /* open INI file */
  initialize_prof_error_table();

  names[0] = INI;
  names[1] = NULL;
  if (retval = profile_init(names, &profile)) goto INIerror;

  /* read transd section in INI file */
  names[0] = "transd";
  names[1] = "hostname";
  names[2] = NULL;
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(HOSTNAME, *values);
  h = gethostbyname(*values);
  if (!h) goto INIerror;
  memset(&TRANSD_ADDR, '\0', sizeof(TRANSD_ADDR));
  TRANSD_ADDR.sin_family = AF_INET;
  TRANSD_ADDR.sin_addr.s_addr = htonl(((struct in_addr *)(h->h_addr_list[0]))->s_addr);
  free(*values);
  free(values);

  names[1] = "port";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  TRANSD_ADDR.sin_port = atoi(*values);
  free(*values);
  free(values);

  names[1] = "service";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(SERVICE, *values);
  free(*values);
  free(values);

  names[1] = "mechanism";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(MECHANISM, *values);
  free(*values);
  free(values);

  names[1] = "facility";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  FACILITY = (16+atoi(*values))<<3;
  free(*values);
  free(values);

  names[1] = "priority";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  PRIORITY = atoi(*values);
  free(*values);
  free(values);

  names[1] = "database";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(DBNAME, *values);
  free(*values);
  free(values);


  /* read sprofile section in INI file */
  names[0] = "sprofile";
  names[1] = "hostname";
  names[2] = NULL;
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  h = gethostbyname(*values);
  if (!h) goto INIerror;
  memset(&SPROFILE_ADDR, '\0', sizeof(SPROFILE_ADDR));
  SPROFILE_ADDR.sin_family = AF_INET;
  SPROFILE_ADDR.sin_addr.s_addr = htonl(((struct in_addr *)(h->h_addr_list[0]))->s_addr);
  free(*values);
  free(values);

  names[1] = "port";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  SPROFILE_ADDR.sin_port = htons(atoi(*values));
  free(*values);
  free(values);

  names[1] = "service";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(SPROFILE_SERVICE, *values);
  free(*values);
  free(values);

  names[1] = "mechanism";
  if (retval = profile_get_values(profile, names, &values)) goto INIerror;
  strcpy(SPROFILE_MECHANISM, *values);
  free(*values);
  free(values);

  /* close INI file */
  profile_release(profile);


  /* start logging */
  openlog("transd", LOG_PID|LOG_CONS|LOG_NDELAY, FACILITY);

  /* cd to DBNAME since we're using a directory as our "database" */
  if (chdir(DBNAME)) DIE();
  getcwd(DBNAME, sizeof(DBNAME));

  /* GSS initialization */
  errmsg = server_acquire_creds(SERVICE, MECHANISM, GSS_C_BOTH, &SERVER_CREDS);
  if (errmsg) {
    logger(LOG_ALERT, "%s: %s", SERVICE, errmsg);
    goto Usage;
  }

  /* return */
  return;

  /* print usage and die */
 Usage:
  fprintf(stderr, "usage: %s [Options]\n", PROGNAME);
  fprintf(stderr, "\nOptions:\n");
  fprintf(stderr, "\t-i %%s\tprofile [\"%s\"]\n", "/etc/ecat.conf");
  fprintf(stderr, "\t-v\tverbose\n");
  exit(2);

  /* error with INI file */
 INIerror:
  com_err(argv[0], retval, "while initializing profile.");
  exit(3);
}


char *GetSecureProfile(char *username, char ***sprofile)
{
  int			sock;
  OM_uint32		maj_stat, min_stat, ignore;
  int			conf_state, n;
  char			*p, *q, **pp;
  gss_buffer_desc	username_buf;

  /* initialize */
  gss_buffer_desc	cleartext = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc	encrypted = GSS_C_EMPTY_BUFFER;
  gss_ctx_id_t		context = GSS_C_NO_CONTEXT;
  char			*text = NULL;

  *sprofile		= NULL;

  /* connect to server */
  text = connect_socket(&sock, &SPROFILE_ADDR);
  if (text) {
    goto Exit;
  }

  /* establish GSSAPI context */
  text = client_establish_context(sock, SERVER_CREDS, SPROFILE_SERVICE, SPROFILE_MECHANISM, &context);
  if (text != NULL) {
    goto Exit;
  }

  /* Assemble and seal the message */
  username_buf.value = username;
  username_buf.length = strlen(username_buf.value);
  maj_stat = gss_seal(&min_stat, context, 1, GSS_C_QOP_DEFAULT,
		      &username_buf, &conf_state, &encrypted);
  if (maj_stat != GSS_S_COMPLETE) {
    text = gss_errmsg("sealing message", maj_stat, min_stat);
    goto Exit;
  }

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

  /* receive text */
  if (recv_token(sock, &encrypted) < 0) {
    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) {
    text = gss_errmsg("unsealing message", maj_stat, min_stat);
    goto Exit;
  }
  (void)gss_release_buffer(&ignore, &encrypted);

  /* set up profile array */
  for(n=1, p=(char*)cleartext.value, q=p+cleartext.length; p<q; ++p)
    if (*p == '\n')
      ++n;

  *sprofile = pp = (char **)malloc((n+1)*sizeof(char*) + cleartext.length+1);
  if (!pp) {
    text = unix_errmsg("allocating memory", errno);
    goto Exit;
  }

  pp[0] = (char*)(pp + (n+1));
  memcpy(pp[0], cleartext.value, cleartext.length);
  pp[0][cleartext.length] = '\0';

  for(n=0; pp[n]; ++n) {
    p = (char*)strchr(pp[n], '\n');	/* where's the EOL ? */
    if (!p) {
      pp[n+1] = NULL;			/* last line */
    } else {
      *p++ = '\0';			/* next line */
      pp[n+1] = p;
    }
    p = (char*)strchr(pp[n], '\t');	/* find name/value separator */
    if (!p) {
      pp[n] = NULL;			/* if not found, terminate list */
      break;
    } else {
      *p++ = '\0';			/* if found, divide pair */
    }
  }

  /* 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;
}


/*******************************************************************************
 *
 * thr_rqst()
 *
 * Start routine for an thr_rqst thread.
 *
 ******************************************************************************/

void* thr_rqst(void* vsock)
{
  int			sock = (int)vsock;
  struct transd_rqst {
    long		procnum;
    char		orderno[32-sizeof(long)];
  }			rqst;
  struct transd_reply {
    long		status;
    char		text[4];
  }			*reply;
  char			username[1024];
  OM_uint32		maj_stat, min_stat, ignore;
  int			conf_state;
  long			status;

  /* initialize */
  gss_ctx_id_t		context = GSS_C_NO_CONTEXT;
  gss_buffer_desc	encrypted = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc	cleartext = GSS_C_EMPTY_BUFFER;
  char			*msg = NULL;
  char			*errmsg = NULL;

  /* make our socket non-blocking */
  if (fcntl(sock, F_SETFL, O_NDELAY|fcntl(sock, F_GETFL))) DIE();

  /* Establish a context with the client */
  errmsg = server_establish_context(sock, SERVER_CREDS, &context, &cleartext);
  if (errmsg) {
    logger(LOG_ERR, "%s", errmsg);
    goto Bail;
  }

  /* note the user name */
  memcpy(username, cleartext.value, cleartext.length);
  username[cleartext.length] = '\0';
  (void)gss_release_buffer(&ignore, &cleartext);

  /* Receive the sealed message token */
  if (recv_token(sock, &encrypted) < 0) {
    logger(LOG_ERR, "%s", errmsg = unix_errmsg("receiving order", errno));
    goto Bail;
  }

  /* Unseal the message token */
  maj_stat = gss_unseal(&min_stat, context, &encrypted, &cleartext, NULL, NULL);
  if (maj_stat != GSS_S_COMPLETE) {
    logger(LOG_ERR, "%s", errmsg = gss_errmsg("unsealing message", maj_stat, min_stat));
    goto Bail;
  }
  (void)gss_release_buffer(&ignore, &encrypted);


  /* note the request */
  memcpy((char*)&rqst, (char*)cleartext.value, min(cleartext.length, sizeof(rqst)));
  (void)gss_release_buffer(&ignore, &cleartext);
  rqst.procnum = ntohl(rqst.procnum);
  logger(LOG_DEBUG, "thread %d: read %02d: \"%s\"", SELF(), rqst.procnum, rqst.orderno);

  /* call the appropriate vendor procedure */
  switch(rqst.procnum) {
  case 1:
    status = OrderText(rqst.orderno, username, &msg);
    break;
  case 2:
    status = OrderAccept(rqst.orderno, username, &msg);
    break;
  default:
    sprintf(msg = (char*)malloc(128), "Illegal request: procnum=%d, orderno=\"%s\"", rqst.procnum, rqst.orderno);
    break;
  }

  /* seal the reply */
  cleartext.length = (int)(((struct transd_reply *)NULL)->text) + strlen(msg) + 1;
  cleartext.value = (char*)(reply = (struct transd_reply *)alloca(cleartext.length));
  reply->status = htonl(status);
  strcpy(reply->text, msg);
  maj_stat = gss_seal(&min_stat, context, 1, GSS_C_QOP_DEFAULT,
		      &cleartext, &conf_state, &encrypted);
  if (maj_stat != GSS_S_COMPLETE) {
    logger(LOG_ERR, "%s", errmsg = gss_errmsg("sealing message", maj_stat, min_stat));
    goto Bail;
  }

  /* Send the reply message to the client */
  if (send_token(sock, &encrypted) < 0) {
    logger(LOG_ERR, "%s", errmsg = unix_errmsg("sending reply", errno));
    goto Bail;
  }

  logger(LOG_DEBUG, "thread %d: wrote reply of %d bytes", SELF(), encrypted.length);

  /* Free memory, close sock and return */
 Bail:
  (void)gss_release_buffer(&ignore, &encrypted);
  if (context != GSS_C_NO_CONTEXT) {
    (void)gss_delete_sec_context(&ignore, &context, &encrypted);
    /* N.b.: output token is not sent, since client exits.... */
    (void)gss_release_buffer(&ignore, &encrypted);
  }
  free(msg);
  free(errmsg);
  close(sock);
  return NULL;
}


/*******************************************************************************
 *
 * main()
 *
 * Start routine for the main thread.
 *
 ******************************************************************************/

main(int argc, char *argv[], char *env[])
{
  int			sock, nsock, n;
  struct sockaddr_in	client;
  fd_set		fdset;
  sigset_t		sigmask;
  char			*errmsg;

  /* boilerplate setup */
  setup(argc, argv, env);

  /* create a socket */
  sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  if (sock < 0) DIE();
  if (fcntl(sock, F_SETFL, O_NDELAY|fcntl(sock, F_GETFL))) DIE();

  /* allow reuse of port */
  n = 1;
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof(n))) DIE();

  /* bind our name to socket */
  if (bind(sock, (struct sockaddr *)&TRANSD_ADDR, sizeof(TRANSD_ADDR)) < 0) DIE();

  /* say hello */
  logger(LOG_INFO, "Transaction daemon starting up....\n"
	 "Date\t\t%s"
	 "Hostname\t%s\n"
	 "Port\t\t%d\n"
	 "Service\t\t%s\n"
	 "Mechanism\t%s\n"
	 "Directory\t%s\n"
	 "Process id\t%d\n"
	 "Main tid\t%d\n",
	 ctime(&STARTTIME), HOSTNAME, TRANSD_ADDR.sin_port, SERVICE, MECHANISM, DBNAME, PID, SELF());


  /* start the signal catcher */
  sigemptyset(&sigmask);
  sigaddset(&sigmask, SIGHUP);
  sigaddset(&sigmask, SIGINT);
  sigaddset(&sigmask, SIGQUIT);
  sigaddset(&sigmask, SIGTERM);
  if (spawnCatcher(&sigmask)) DIE();

  /* specify backlog limit for incoming connnections */
  if (listen(sock, SOMAXCONN) < 0) DIE();

  /* initialize vendor order module */
  if (OrderInit()) DIE();

  /* go into infinite dispatch loop */
  logger(LOG_DEBUG, "Main thread (tid %d) entering dispatch loop", SELF());

  for(FD_ZERO(&fdset);;) {
    FD_SET(sock, &fdset);
    if (select(sock+1, &fdset, NULL, NULL, NULL) != 1) continue;

    /* accept the connection */
    n = sizeof(client);
    nsock = accept(sock, (struct sockaddr *)&client, &n);
    if (nsock < 0) {
	    errmsg = unix_errmsg("accept", errno);
	    logger(LOG_ERR, "thread %d: error in %s", SELF(), errmsg);
	    free(errmsg);
	    continue;
    }
    logger(LOG_DEBUG, "thread %d: accepted connection from %s:%d on socket %d",
	   SELF(), inet_ntoa(client.sin_addr), client.sin_port, nsock);

    /* create the thread to handle connection */
    if (spawnThread(thr_rqst, nsock)) DIE();
  }
}
