/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#ifdef BSD4_4
#include <sys/param.h>
#include <sys/sysctl.h>
#endif

#include <netatalk/at.h>
#include <netatalk/endian.h>
#include <atalk/dsi.h>
#include <atalk/atp.h>
#include <atalk/asp.h>

#include "globals.h"  /* includes <netdb.h> */
#include "status.h"
#include "config.h"
#include "icon.h"

static void status_flags(char *data, const DSI *dsi, 
			 const unsigned char passwdbits)
{
    struct afp_status	*status;

    status = (struct afp_status *)data;

    status->as_flags = AFPSRVRINFO_COPY;
#if (defined( AFS ) && defined( UAM_AFSKRB )) || defined(USE_PAM) || \
    defined(UAM_RNDNUM)
    if (passwdbits & PASSWD_SET)
      status->as_flags |= AFPSRVRINFO_PASSWD;
#endif AFS UAM_AFSKRB
    if (passwdbits & PASSWD_NOSAVE)
      status->as_flags |= AFPSRVRINFO_NOSAVEPASSWD;
    status->as_flags |= AFPSRVRINFO_SRVSIGNATURE;
    /* only advertise tcp/ip if we have a valid address */
    if (dsi && dsi->server.sin_addr.s_addr)
      status->as_flags |= AFPSRVRINFO_TCPIP;
    status->as_flags |= AFPSRVRINFO_SRVMSGS; 
    status->as_flags |= AFPSRVRINFO_SRVNOTIFY;
    status->as_flags |= AFPSRVRINFO_FASTBOZO;
    status->as_flags = htons(status->as_flags);
}

static int status_server(char *data, const char *server)
{
    struct afp_status	*status;
    char                *Obj, *Type, *Zone;
    int			len;

    status = (struct afp_status *) data;

    /* make room for all offsets before server name */
    data += sizeof(struct afp_status);

    /* extract the obj part of the server */
    Obj = server;
    nbp_name(server, &Obj, &Type, &Zone);
    len = strlen(Obj);
    *data++ = len;
    bcopy( Obj, data, len );
    if ((len + 1) & 1) /* pad server name and length byte to even boundary */
      len++;
    data += len;

    /* make room for signature and net address offset. save location of 
     * signature offset. */
    len = data - (char *) status;
    status->as_machoff = htons( len + sizeof(struct afp_poststatus));
    return len; /* return the offset to beginning of signature offset */
}

static void status_machine(char *data)
{
    struct afp_status	*status;
    int			len;
#ifdef AFS
    char		*machine = "afs";
#else !AFS
    char		*machine = "unix";
#endif

    status = (struct afp_status *)data;
    data += ntohs( status->as_machoff );
    len = strlen( machine );
    *data++ = len;
    bcopy( machine, data, len );
    data += len;
    status->as_versoff = htons( data - (char *)status );
}


/* server signature is a 16-byte quantity */
static int status_signature(char *data, int servoffset, DSI *dsi, 
			    const char *hostname)
{
  char                 *status; 
  int                  i;
  u_int16_t            offset;
  long                 hostid;
  static int           id = 0;
#ifdef BSD4_4
  int                  mib[2];
  size_t               len;
#endif

  status = data;

  /* get server signature offset */
  memcpy(&offset, data + servoffset, sizeof(offset));
  offset = ntohs(offset);

  /* jump to server signature offset */
  data += offset;

  /* 16-byte signature consists of copies of the hostid */
#ifdef BSD4_4
  mib[0] = CTL_KERN;
  mib[1] = KERN_HOSTID;
  len = sizeof(hostid);
  sysctl(mib, 2, &hostid, &len, NULL, 0);
#else
  hostid = gethostid();
#endif
  if (!hostid) {
    if (dsi)
      hostid = dsi->server.sin_addr.s_addr;
    else {
      struct hostent *host;
      
      if (host = gethostbyname(hostname))
	hostid = ((struct in_addr *) host->h_addr)->s_addr;
    }
  }

  /* it turns out that a server signature screws up separate 
   * servers running on the same machine. to work around that, 
   * i add in an increment */
  hostid += id;
  id++;
  for (i = 0; i < 16; i += sizeof(hostid)) {
    memcpy(data, &hostid, sizeof(hostid));
    data += sizeof(hostid);
  }

  /* calculate net address offset */
  servoffset += sizeof(offset);
  offset = htons(data - status);
  memcpy(status + servoffset, &offset, sizeof(offset));

  return servoffset;
}

static int status_netaddress(char *data, const int servoffset, 
			     const ASP asp, const DSI *dsi)
{
    char               *begin;
    u_int16_t          offset;
    
    begin = data;

    /* get net address offset */
    memcpy(&offset, data + servoffset, sizeof(offset));
    data += ntohs(offset);

    /* format: 
       Address count (byte) 
       len (byte = sizeof(length + address type + address) 
       address type (byte, ip address = 0x01, ip + port = 0x02,
                           ddp address = 0x03) 
       address (up to 254 bytes, ip = 4, ip + port = 6, ddp = 4)
       */

    /* number of addresses. this currently screws up if we have a dsi
       connection, but we don't have the ip address. to get around this,
       we turn off the status flag for tcp/ip. */
    *data++ = (dsi ? 2 : 0) + (asp ? 1 : 0);

    /* ip address */
    if (dsi) {
      const struct sockaddr_in *inaddr = &dsi->server;

      /* ip address + port */
      *data++ = 8; 
      *data++ = 0x02; /* ip address with port */ 
      memcpy(data, &inaddr->sin_addr.s_addr, sizeof(inaddr->sin_addr.s_addr));
      data += sizeof(inaddr->sin_addr.s_addr);
      memcpy(data, &inaddr->sin_port, sizeof(inaddr->sin_port));
      data += sizeof(inaddr->sin_port);

      *data++ = 6; /* length */
      *data++ = 0x01; /* basic ip address */
      memcpy(data, &inaddr->sin_addr.s_addr, sizeof(inaddr->sin_addr.s_addr));
      data += sizeof(inaddr->sin_addr.s_addr);
    }

#ifndef NO_AFPASP
    if (asp) {
      const struct sockaddr_at *ddpaddr = atp_sockaddr(asp->asp_atp);
      
      /* ddp address */
      *data++ = 6;
      *data++ = 0x03; /* ddp address */
      memcpy(data, &ddpaddr->sat_addr.s_net, sizeof(ddpaddr->sat_addr.s_net));
      data += sizeof(ddpaddr->sat_addr.s_net);
      memcpy(data, &ddpaddr->sat_addr.s_node, 
	     sizeof(ddpaddr->sat_addr.s_node));
      data += sizeof(ddpaddr->sat_addr.s_node);
      memcpy(data, &ddpaddr->sat_port, sizeof(ddpaddr->sat_port));
      data += sizeof(ddpaddr->sat_port);
    }
#endif

    /* return length of buffer */
    return (data - begin);
}

static void status_icon(char *data, const int sigoffset)
{
    struct afp_status	*status;
    char                *sigdata;
    u_int16_t		ret;

    status = (struct afp_status *)data;

    /* get location for signature offset */
    sigdata = data + sigoffset;

    if ( icon == NULL ) {
	ret = status->as_iconoff;
	status->as_iconoff = 0;
    } else {
	data += ntohs( status->as_iconoff );
	bcopy( icon, data, sizeof( icon ));
	data += sizeof( icon );
	ret = htons(data - (char *)status );
    }
    
    /* put in signature offset */
    memcpy(sigdata, &ret, sizeof(ret));
}

int status_init(void *status, ASP asp, DSI *dsi, 
		const struct afp_options *options)
{
  int c;

  /*
   * These routines must be called in order -- earlier calls
   * set the offsets for later calls.
   *
   * using structs is a bad idea, but that's what the original code
   * does.
   *
   * reply block layout (offsets are 16-bit quantities):
   * machine type offset -> AFP version count offset ->
   * UAM count offset -> vol icon/mask offset -> flags ->
   *
   * server name [padded to even boundary] -> signature offset ->
   * network address offset ->
   *
   * at the appropriate offsets:
   * machine type, afp versions, uams, server signature
   * (16-bytes), network addresses, volume icon/mask 
   */

  status_flags(status, dsi, options->passwdbits);
  /* returns offset to signature offset */
  c = status_server(status, options->server ? options->server :
		    options->hostname); 
  status_machine(status);
  status_versions(status);
  status_uams(status, options->authbits);
  status_icon(status, c); 
  /* returns offset to net offset */
  c = status_signature(status, c, dsi, options->hostname); 
  c = status_netaddress(status, c, asp, dsi); /* returns length */

#ifndef NO_AFPASP
  if (asp) 
    asp_setstatus(asp, status, c);
#endif

  if (dsi)
    dsi_setstatus(dsi, status, c);

  return c;
}

/* this is the same as asp/dsi_getstatus */
afp_getsrvrinfo(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    AFPConfig *config = obj->config;

    memcpy(rbuf, config->status, config->statuslen);
    *rbuflen = config->statuslen;
    return AFP_OK;
}
