/* made to run from inetd */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/ioctl.h>
#include <ndbm.h>
#include <errno.h>
#include "v.h"

/* added for solaris port */
#include <ndbm.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/filio.h>
#include <sys/uio.h>
#include <pwd.h>
/* end additions */

#define MAIL_COMMAND "/bin/mail testtrackers"
#define ZEPHYR_COMMAND "/usr/athena/bin/zwrite -c testtrackers -i panic"
#define SERVER_NAME "vserv"
#define DEBUG_FILE "/tmp/vserv.debug" /* "/dev/null" */

static FILE *debug_file;
static char *log_dir;
/*
 * Sending packets...
 */

static void send_response (struct v_pkt *pkt,
			   struct sockaddr_in *cl,
			   int size)
{
  int s, rc;
  struct sockaddr_in ad;
  struct v_info info;
 
  fprintf(debug_file,"parsing packet..."); fflush(debug_file);
  v_parse_pkt(pkt, &info);		
  fprintf(debug_file,"Sending message %s for %s %s (%s) \n",
	  (ntohs(pkt->op_code)==V_OK) ? info.message : "ERROR",
	  info.appl_name, 
	  info.appl_vers,
	  info.platform); fflush(debug_file);
  /*** Set up socket and transmit return info ***/
  s = socket(AF_INET, SOCK_DGRAM, 0);
  memset(&ad, 0, sizeof(ad));
  ad.sin_family = AF_INET;
  ad.sin_port = htons(VERSION_CLIENT_PORT);
  if (bind(s, (struct sockaddr *) &ad, sizeof(ad))) 
    syslog(LOG_ALERT,"can't bind to port: %m");
  else
    if ((rc = sendto(s, (char *) pkt, size,
		     0, (struct sockaddr *) cl, sizeof(struct sockaddr_in))) < 0)
      syslog(LOG_ALERT,"can't send reply packet: %m");
}


/*
 * Actually log a packet from the given socket address to the given
 * file
 */

do_log (char *fname, enum v_op_code op_code,
	struct v_info *info, struct sockaddr_in *cl)
{
  FILE *log_file;
  char log_fname[BUFSIZ];
  char *nowstring;
  time_t now;

  sprintf(log_fname, "%s%s",log_dir, fname);
  fprintf(debug_file,"logging to file %s\n", log_fname); fflush(debug_file);
  if ((log_file = fopen(log_fname, "a+")) == NULL) 
    {
      syslog(LOG_ALERT, "can't open log file %s: %m", log_fname);
      exit(1);
    }
  time(&now);
  nowstring = ctime(&now);
  nowstring[24] = 0;	/* Kill a pesky newline */
  fprintf(log_file, "%s: %s %s (%s) %s from %s",
	  nowstring, info->appl_name, info->appl_vers, info->platform,
	  (op_code == V_CHECK_AND_LOG ? "has been launched" :
	   (op_code == V_LOG ? "is logging" :
	    (op_code == V_CHECK ? "is checking version" :
	     (op_code == V_LOG_PANIC ? "PANIC" : "sent me a bad op code")))),
	  inet_ntoa(cl->sin_addr));
  if (*info->message) 
    fprintf(log_file,": %s", info->message);
  fprintf(log_file,"\n");
  fclose(log_file);
}

/*
 * Main program...
 */

main(argc,argv)
int argc;
char *argv[];
{
  DBM            *db;
  char           keystr[KEY_SIZE];
  struct v_pkt    pkt, resp;
  struct v_info	info;
  int             rc, size;
  struct sockaddr_in cl;
  datum           rec, key;
  enum v_op_code  op;
  int		  one = 0;   /******** ST - was 1 (blocking?)***/
  char		  *oldinfo;
  
if (argc > 1)
  log_dir = argv[1];
else
  log_dir =VERSION_LOG_DIR;
#ifdef DEC
  openlog(SERVER_NAME, 0);
#else
  openlog(SERVER_NAME, 0, LOG_CLASS);
#endif
  if ((debug_file = (FILE *) fopen(DEBUG_FILE, "a+")) == NULL) 
    {
      syslog(LOG_ALERT,"can't open log file: %m");
      exit(1);
    }
  db = dbm_open(VERSION_DB_FILE, O_RDONLY, 0);
  if (!db) 
    {  
      syslog(LOG_ALERT,"can't open version database: %m");
      exit(1);
    }
  /*** Receive packet ***/
  
  size = sizeof(cl);
  ioctl(0, FIONBIO, &one);
  if ((rc = recvfrom(0, (char *) &pkt, sizeof(pkt), 0, (struct sockaddr *) &cl, &size)) < 1)
    {
      syslog(LOG_ERR, "can't recvfrom packet: %m");
      exit(1);
    }
  fprintf(debug_file, "\nGot a %d-byte packet as %d: %s: opcode = %d\n", rc,
	  getuid(), pkt.data, ntohs(pkt.op_code)); fflush(debug_file);
  
  /* Form a packet to be sent if error */
  
  memset(&resp, 0, sizeof(resp));
  resp.protocol_version = htons(VERSION_PROTOCOL_VERSION);
  resp.packet_number = htons(1);
  resp.number_of_packets = htons(1);
  resp.seq = pkt.seq;
  
  /*** parse data from packet  ***/
  
  v_parse_pkt(&pkt, &info);
  op = (enum v_op_code) ntohs(pkt.op_code);
  fprintf(debug_file,"packet parsed: %s\n", info.appl_name); fflush(debug_file);
  
  /*** build key & fetch record ***/
  
  sprintf(keystr, "%s%s%s", 
	  info.appl_name, 
	  info.appl_vers,
	  info.platform);
  key.dptr = keystr;
  key.dsize = strlen(key.dptr) + 1;
  rec = dbm_fetch(db, key);
  if (rec.dptr) 
    fprintf (debug_file,"key %s found\n", keystr);
  else 
    {
      fprintf (debug_file,"key %s NOT FOUND\n", keystr);
      syslog (LOG_ERR, "Request from unknown application %s", keystr);
      key.dptr = info.appl_name;
      key.dsize = strlen(key.dptr) + 1;
      rec = dbm_fetch(db, key);
      /* do_log (rec.dptr ? info.appl_name : VERSION_ERROR_LOG,
		op, &info, &cl); */
    }
  fflush(debug_file);
  dbm_close(db);


  
  /*** Actually do what the client wants ***/
  
  if (!rec.dptr || (op != V_CHECK &&
		    op != V_CHECK_AND_LOG &&
		    op != V_LOG &&
		    op != V_LOG_PANIC))
    {      
      fprintf (debug_file,"Sending error response\n"); fflush(debug_file);
      resp.op_code = htons(V_ERROR);
      send_response(&resp, &cl, V_BASE_SIZE); /* Send error message and you're done */
    }
  else 
    {
      if (op == V_CHECK ||	/* Opcodes that require checking */
	  op == V_CHECK_AND_LOG)
	{
	  fprintf(debug_file, "responding to request\n"); fflush(debug_file);
	  
	  resp.op_code = htons(V_OK);	/*** Finish response packet ***/
	  if (rec.dsize != 0) {
	       memmove(resp.data, rec.dptr, rec.dsize);
	       send_response(&resp, &cl, V_BASE_SIZE + rec.dsize);
	  } else {			/*** Null response ***/
	       oldinfo = (char *) malloc(sizeof(char)*(strlen(info.message)+1));
	       strcpy(oldinfo,info.message);
	       info.message = ""; 
	       send_response(&resp, &cl, v_assemble_pkt(&resp, &info));
               info.message = (char *) malloc(sizeof(char)*(strlen(oldinfo)+1));
               strcpy(info.message,oldinfo);
               free(oldinfo);
	  }
	}
      if (op == V_CHECK_AND_LOG || /* Opcodes that require logging */
	  op == V_LOG ||
	  op == V_LOG_PANIC) 
	{
	  do_log(info.appl_name, op, &info, &cl);
	}
      if (op == V_LOG_PANIC)	/* Special stuff for panic condition */
	{
	  FILE *proc_pipe;
	  
	  fprintf(debug_file,"panic condition ... sending mail and zephyr\n"); fflush(debug_file);
	  if ((proc_pipe = popen(MAIL_COMMAND, "w")) == NULL) 
	    {
	      syslog(LOG_ALERT, "can't invoke mailer for panic notification: %m");
	      exit(1);
	    }
	  fprintf(proc_pipe,"\nPanic condition reported from %s %s (%s):\n%s\n",
		  info.appl_name, info.appl_vers, info.platform, info.message);
	  fclose(proc_pipe);
	  
	  if ((proc_pipe = popen(ZEPHYR_COMMAND, "w")) == NULL) 
	    syslog(LOG_ALERT, "can't invoke zephyr for panic notification: %m");
	  else
	    {
	      fprintf(proc_pipe,"\nPanic condition reported from %s %s (%s)\n.\n",
		      info.appl_name, info.appl_vers, info.platform);
	      fclose(proc_pipe);
	    }
	}
    }
  fprintf(debug_file,"normal exit\n"); 
  fclose(debug_file);
  exit(0);
}
