/* Copyright 1989 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include "notice.h"

/*
 *------------------------------------------------------------------
 *
 * $Source: $
 * $Revision: $
 * $Date: $
 * $State: $
 * $Author: $
 * $Locker: $
 *
 * $Log: $
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_time_c = "$Header: $";
#endif	lint

#include	<types.h>
#include	<sys.h>
#include	<sysext.h>
#include	<gw/src/const.h>
#include	<gw/src/param.h>
#include	<gw/src/defs.h>
#include	<gw/src/macs.h>
#include	<gw/src/net.h>
#include	<gw/src/ext.h>
#include	<gw/opcon/opcon.h>
#include	"in.h"
#include	"inext.h"
#include	"inudp.h"
#include	"inrte.h"
#include        "inparam.h"

/* Interface to command interpreter. */
void time_ctl();
#ifndef NULL
#define NULL 0
#endif

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

static subcommand time_scmd[] = {
    { "get", "Get time from host", time_ctl, 0 },
    { "set", "Get time from host and set clock", time_ctl, 1 },
    { "show", "Show time and where it came from", time_ctl, 2},
    { NULL, NULL, NULL, 0 },
};

static command time_cmd = { NULL, "time",
			  "Time", time_scmd };

static unsl time_offset;

static inaddr last_set_addr;
static unsl last_set_time;
static unsl last_time_offset;

unsl time()
{
  return (time_offset + systod);
}

#define MAX_TRIES 10
static inaddr pending;
static unsb n_tries;
static unsb set_clock;

ext inaddr time_server;

TIME(arg)
  long arg;
{
  void gettime();

  pending.i_long = 0;
  gettime (&(time_server.i_long), TRUE);

  if (!arg) opcon_register(&time_cmd);

  stime (TIME /* hack */, 1 /* hee, hee */, 
	 60 * 60 /* once an hour */, INPPRI + 10);
}

time_in (iob)
     iorb *iob;
{
  inpkt  *ipkt;
  udppkt *upkt;
  unsl   *time_pkt;
  inaddr *src;
  unsl   t;

  ipkt = mkin(iob->i_addr);
  src = &ipkt->i_src;
  upkt = (udppkt *)((byte *)ipkt + sizeof(inpkt));

  niflog(L_TRCO)
    dolog("TIME packet from %d.%d.%d.%d\n", mksina(src));

  time_pkt = (unsl *)((byte *)ipkt + sizeof(inpkt) + sizeof(udppkt));

  switch (swab (upkt->u_len) - sizeof(udppkt)) {
  case  0: /* Time query */
  case 40: /* Broken version of gettime sends 40 bogus bytes */
    if (time_offset == 0) {
      /* don't know what time it is */
      niflog (L_ERRU)
	dolog ("TIME request from %d.%d.%d.%d discarded because we don't know the time.\n",
	       mksina (src));
      freebuf (iob);
      return;
    }
    else {
      /* set up return packet */
      *time_pkt = swabl(time());  
      mkrinpkt (iob, sizeof(udppkt) + sizeof(*time_pkt));
      upkt->u_dst = upkt->u_src;
      upkt->u_src = swab(TIME_PORT);
      upkt->u_len = swab(sizeof(udppkt) + sizeof(*time_pkt));
      upkt->u_chk = udp_cksum(iob->i_addr);
      niflog (L_INFC) 
	dolog ("TIME  query from %d.%d.%d.%d, responding\n", mksina(src));
      inxaddq(iob);
      return;
    }
  case sizeof(*time_pkt):
    if (pending.i_long != src->i_long) {
      niflog (L_ERRU) 
	dolog ("TIME unsolicited packet from %d.%d.%d.%d\n", mksina(src));
      freebuf (iob);
      return;
    }
    if (pending.i_long == src->i_long) {
      pending.i_long = 0;
      n_tries = 0;
    }
    if (!set_clock) {
      t = swabl(*time_pkt);
      niflog (L_INFU)
	dolog ("TIME got time %s from %d.%d.%d.%d, off by %d seconds but not setting.\n", 
	       asctime(localtime(&t)), mksina(src), t - time());
      freebuf (iob);
      return;
    }
    last_time_offset = swabl(*time_pkt) - time();
    time_offset = swabl(*time_pkt) - systod;
    t = time();
    last_set_addr.i_long = src->i_long;
    last_set_time = t;
    niflog (L_INFU)
      dolog ("TIME setting time to %s from %d.%d.%d.%d, off by %d seconds.\n", 
	     asctime(localtime(&t)), mksina(src), last_time_offset);
    freebuf (iob);
    return;
  default:
    niflog (L_ERRU) 
      dolog ("TIME recieved bad packet from %d.%d.%d.%d, length %d\n.",
	     mksina(src), swab(upkt->u_len));
    freebuf (iob);
  }
}

ntp_in (iob)
     iorb *iob;
{
  freebuf (iob);
}

check_time_host (dummy)
     unsl dummy;
{
  reg int i;

  if (pending.i_long != 0) {
    if (++n_tries == MAX_TRIES) {
      niflog (L_ERRU) 
	dolog ("TIME no response from %d.%d.%d.%d", 
	       mksina(&pending));
      pending.i_long = 0;
      n_tries = 0;
      set_clock = FALSE;
      return;
    }
    send_time_query (&pending);
  }
}

gettime (timehost, doit)
     inaddr *timehost;
     unsb doit;
{
  reg int i;

  if (pending.i_long == timehost->i_long) return;
  if (pending.i_long == 0) {
      pending.i_long = timehost->i_long;
      n_tries = 1;
      set_clock = doit;
      send_time_query (timehost);
      return;
    }
}

send_time_query (timehost)
	  inaddr *timehost;
{
  reg iorb	*iob;
  reg udppkt	*upkt;
      
  if ((iob = getbuf()) == NULL) {
    niflog(L_ERRU)
      dolog("TIME couldn't allocate packet\n");
    return;
  }
    
  mkinpkt(iob, mkinia(iniatbl[0].inia_addr), timehost, INUDP,
	  sizeof(udppkt));

  upkt = mkudp(iob->i_addr + sizeof(inpkt));
  upkt->u_src = swab(TIME_PORT);  /* why not */
  upkt->u_dst = swab(TIME_PORT);
  upkt->u_len = swab(sizeof(udppkt));   /* send zero length request, just
					   get back the time */
  upkt->u_chk = 0;    /* no data so checksum */

  inxaddq(iob);

  /* try again in a second */
  stime (check_time_host, 0, 1 /* second */, INPPRI + 10);
}

ext int good_password;
ext long time_zone_offset;

void time_ctl(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
  inaddr addr;
  long getdmch();
  unsl t;

  switch (arg) {
  case 0:  /* gettime */
  case 1:  /* and set */
    if (!good_password && arg) {
      fprintf (devp, "Must give password first.\n");
      return;
    }
    addr.i_long = getdmch(jcl);
    if (addr.i_long == 0) {
      fprintf (devp, "Bad address.\n");
      return;
    }
    gettime (&addr, arg /* hack, hack! */);
    break;
  case 2:  /* show */
    t = time();
    fprintf (devp, "Time is %s. Time zone %d.\n",
	     asctime(localtime(&t)), time_zone_offset / (60 * 60));
    fprintf (devp, "Last set by %d.%d.%d.%d at %s, off by %d seconds.\n",
	     mksina(&last_set_addr), asctime(localtime(&last_set_time)),
	     last_time_offset);
    break;
  }    
}
