/* 
 * Copyright (c) 1999 Adrian Sun (asun@zoology.washington.edu)
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 *
 * modified from main.c. this handles afp over tcp.
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <syslog.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>

#include <atalk/dsi.h>
#include <atalk/compat.h>
#include "globals.h"
#include "switch.h"
#include "auth.h"

extern struct oforks	*writtenfork;

#define CHILD_DIE         (1 << 0)
#define CHILD_RUNNING     (1 << 1)

static struct {
  DSI *dsi;
  unsigned char tickle, flags;
} child;


static __inline__ void afp_dsi_close(DSI *dsi)
{
  syslog(LOG_INFO, "%.2fKB read, %.2fKB written",
	 child.dsi->read_count/1024.0, child.dsi->write_count/1024.0);
#ifdef USE_PAM
    if (pamh) {
      pam_close_session(pamh, 0);
      pam_end(pamh, 0);
    }
#endif
  dsi_close(dsi);
}

/* a little bit of code duplication. */
static void afp_dsi_die(int sig)
{
    dsi_attention(child.dsi, AFPATTN_SHUTDOWN);
    afp_dsi_close(child.dsi);
    if (sig == SIGTERM || sig == SIGALRM)
      exit( 0 );
    else
      exit(sig);
}

static void afp_dsi_timedown()
{
    struct sigaction	sv;
    struct itimerval	it;

    child.flags |= CHILD_DIE;
    it.it_interval.tv_sec = 0;
    it.it_interval.tv_usec = 0;
    it.it_value.tv_sec = 300;
    it.it_value.tv_usec = 0;
    if ( setitimer( ITIMER_REAL, &it, 0 ) < 0 ) {
	syslog( LOG_ERR, "afp_timedown: setitimer: %m" );
	afp_dsi_die(1);
    }

    /* shutdown and don't reconnect. server going down in 5 minutes. */
    setmessage("The server is going down for maintenance.");
    dsi_attention(child.dsi, AFPATTN_SHUTDOWN | AFPATTN_NORECONNECT |
		  AFPATTN_MESG | AFPATTN_TIME(5));

    sv.sa_handler = afp_dsi_die;
    sigemptyset( &sv.sa_mask );
    sigaddset(&sv.sa_mask, SIGHUP);
    sigaddset(&sv.sa_mask, SIGTERM);
    sv.sa_flags = SA_RESTART;
    if ( sigaction( SIGALRM, &sv, 0 ) < 0 ) {
	syslog( LOG_ERR, "afp_timedown: sigaction: %m" );
	afp_dsi_die(1);
    }
}

static void alarm_handler()
{
  /* if we're in the midst of processing something,
     don't die. we'll allow 3 missed tickles before we die (2 minutes) */
  if ((child.flags & CHILD_RUNNING) || (child.tickle++ < 4)) {
    dsi_tickle(child.dsi);
  } else { /* didn't receive a tickle. close connection */
    syslog(LOG_ERR, "afp_alarm: child timed out");
    afp_dsi_die(1);
  }
}

/* afp over dsi. this never returns. */
void afp_over_dsi(AFPObj *obj)
{
  DSI *dsi = (DSI *) obj->handle;
  u_int32_t err, cmd;
  u_int8_t function;
  struct sigaction action;

  obj->exit = afp_dsi_die;
  obj->reply = (int (*)()) dsi_cmdreply;
  obj->attention = (int (*)(void *, AFPUserBytes)) dsi_attention;
  
  child.dsi = dsi;
  child.tickle = child.flags = 0;

  /* tickle handler */
  action.sa_handler = alarm_handler;
  sigemptyset(&action.sa_mask);
  sigaddset(&action.sa_mask, SIGHUP);
  sigaddset(&action.sa_mask, SIGTERM);
  action.sa_flags = SA_RESTART;

  if ((setitimer(ITIMER_REAL, &dsi->timer, NULL) < 0) ||
      sigaction(SIGALRM, &action, NULL) < 0) {
    afp_dsi_die(1);
  }

  /* now install SIGTERM and SIGHUP */
  action.sa_handler = afp_dsi_timedown;
  sigemptyset( &action.sa_mask );
  sigaddset(&action.sa_mask, SIGALRM);
  sigaddset(&action.sa_mask, SIGTERM);
  action.sa_flags = SA_RESTART;
  if ( sigaction( SIGHUP, &action, 0 ) < 0 ) {
    syslog( LOG_ERR, "afp_over_dsi: sigaction: %m" );
    afp_dsi_die(1);
  }

  action.sa_handler = afp_dsi_die;
  sigemptyset( &action.sa_mask );
  sigaddset(&action.sa_mask, SIGALRM);
  sigaddset(&action.sa_mask, SIGHUP);
  action.sa_flags = SA_RESTART;
  if ( sigaction( SIGTERM, &action, 0 ) < 0 ) {
    syslog( LOG_ERR, "afp_over_dsi: sigaction: %m" );
    afp_dsi_die(1);
  }

  /* get stuck here until the end */
  while (cmd = dsi_receive(dsi)) {
    child.tickle = 0;

    if (cmd == DSIFUNC_TICKLE) {
      /* so we don't get killed on the client side. */
      if (child.flags & CHILD_DIE) 
	dsi_tickle(dsi);
      continue;
    } else if (!(child.flags & CHILD_DIE)) /* reset tickle timer */
      setitimer(ITIMER_REAL, &dsi->timer, NULL);

    switch(cmd) {
    case DSIFUNC_CLOSE:
      afp_dsi_close(dsi);
      syslog(LOG_INFO, "done");
      if (obj->options.debug) 
	printf("done\n");
      return;
      break;

    case DSIFUNC_CMD:
#ifdef AFS
      if ( writtenfork ) {
	if ( flushfork( writtenfork ) < 0 ) {
	  syslog( LOG_ERR, "main flushfork: %m" );
	}
	writtenfork = NULL;
      }
#endif AFS

      function = (u_char) dsi->commands[0];
      if (obj->options.debug) {
	printf("command: %d\n", function);
	bprint(dsi->commands, dsi->cmdlen);
      }

      /* send off an afp command. in a couple cases, we take advantage
       * of the fact that we're a stream-based protocol. */
      if (afp_switch[function]) {
	dsi->datalen = DSI_DATASIZ;
	child.flags |= CHILD_RUNNING;
	err = (*afp_switch[function])(obj,
				      dsi->commands, dsi->cmdlen,
				      dsi->data, &dsi->datalen);
	child.flags &= ~CHILD_RUNNING;
      } else {
	syslog(LOG_ERR, "bad function %X", function);
	dsi->datalen = 0;
	err = AFPERR_NOOP;
      }

      /* single shot toggle that gets set by dsi_readinit. */
      if (dsi->noreply) {
	dsi->noreply = 0;
	break;
      }

      if (obj->options.debug) {
	printf( "reply: %d, %d\n", err, dsi->clientID);
	bprint(dsi->data, dsi->datalen);
      }

      if (!dsi_cmdreply(dsi, err)) {
	syslog(LOG_ERR, "dsi_cmdreply(%d): %m", dsi->socket);
	afp_dsi_die(1);
      }
      break;

    case DSIFUNC_WRITE: /* FPWrite and FPAddIcon */
      function = (u_char) dsi->commands[0];
      if ( obj->options.debug ) {
	printf("(write) command: %d, %d\n", function, dsi->cmdlen);
	bprint(dsi->commands, dsi->cmdlen);
      }

      if ( afp_switch[ function ] != NULL ) {
	dsi->datalen = DSI_DATASIZ;
	child.flags |= CHILD_RUNNING;
	err = (*afp_switch[function])(obj, dsi->commands, dsi->cmdlen,
				      dsi->data, &dsi->datalen);
	child.flags &= ~CHILD_RUNNING;
      } else {
	syslog( LOG_ERR, "(write) bad function %x", function);
	dsi->datalen = 0;
	err = AFPERR_NOOP;
      }

      if (obj->options.debug) {
	printf( "(write) reply code: %d, %d\n", err, dsi->clientID);
	bprint(dsi->data, dsi->datalen);
      }

      if (!dsi_wrtreply(dsi, err)) {
	syslog( LOG_ERR, "dsi_wrtreply: %m" );
	afp_dsi_die(1);
      }
      break;

    case DSIFUNC_ATTN: /* attention replies */
      continue;
      break;

      /* error. this usually implies a mismatch of some kind
       * between server and client */
    default: 
      syslog(LOG_INFO,"afp_dsi: spurious command %d", cmd);
    }
        
    if ( obj->options.debug ) {
#ifdef notdef
      pdesc( stdout );
#endif notdef
      pforkdesc( stdout );
      fflush( stdout );
    }
  }

  /* error */
  afp_dsi_die(1);
}
