/*
 *	Copyright (c) 1999-2003 Smithsonian Astrophysical Observatory
 */

#include <xpap.h>

/*
 *----------------------------------------------------------------------------
 *
 *
 * 			Private Routines and Data
 *
 *
 *----------------------------------------------------------------------------
 */

/* this is the head of the global list -- too lazy to do anything more */
static XPA xpahead=NULL;

/* globals for the xpa environment, controlled by environment variables */
static int stimeout=XPA_SHORT_TIMEOUT; 	/* select, gets timeout in secs */
static int ltimeout=XPA_LONG_TIMEOUT; 	/* fillbuf timeout in secs */
static int ctimeout=XPA_CONNECT_TIMEOUT;/* local xpans connect */
static int verbosity=XPA_VERBOSITY;	/* 0=quiet, 1=normal, 2=full */
static int guseacl=1;		/* 0=don't use acls, 1=enable acls */
static int etimestamp=0;	/* 0=don't timestamp errors, 1=do timestamp */
static int nsregister=1;	/* 0=don't register with xpans, 1=register */
static int sigusr1=1;		/* 0=don't use sigusr1, 1=enable sigusr1 */
static int vercheck=1;		/* 0=don't check version, 1=check version */
static char *tmpdir=NULL;  	/* temporary dir for logs. etc. */
#if HAVE_ATEXIT
static int atexitinit=0;	/* whether atexit has been registered */
#endif

/* variables used by all XPAs in this process */
static char activefds[FD_SETSIZE];
static char nsmethod[SZ_LINE];
static char nsusers[SZ_LINE];

/* global method type: unix or inet */
static int mtype=0;

/* width of select channel flag */
static int swidth=FD_SETSIZE;

/* defined in tcp.c */
extern int use_localhost;

/* erro code strings -- must match the error codes in xpap.h */
char *xpaMessbuf[] = {
  "oh, what a mess we've made!",
  "authentication failed",
  "xpa connection refused",
  "can't resolve host name for xpa",
  "can't read initialization info for xpa",
  "invalid xpa command in initialization string",
  "no receive function defined for this xpa",
  "no send function defined for this xpa",
  "no info function defined for this xpa",
  "undefined command for this xpa",
  "missing command for this xpa",
  "name does not match target template",
  "can't create data channel socket",
  "could not read data buf (possible timeout)",
  "illegal command or command switch"
};

/* temp static time buffer */
static char ctimebuf[SZ_LINE];

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAProxyConnect
 *
 * Purpose:	connect to a client (in proxy mode)
 *
 * Returns:	0 if successfull, otherwise -1
 *
 *----------------------------------------------------------------------------
 */

#ifdef ANSI_FUNC
static int 
XPAProxyConnect(XPA xpa, char *method, 
		unsigned int *rip, unsigned short *rport, char *rname)
#else
static int XPAProxyConnect(xpa, method, rip, rport, rname)
     XPA xpa;
     char *method;
     unsigned int *rip;
     unsigned short *rport; 
     char *rname;
#endif
{
  int ofd;
  int tries=0;
  int keep_alive=1;
  unsigned int ip;
  unsigned short port;
  struct sockaddr_in sock_in;
#if HAVE_SYS_UN_H
  struct sockaddr_un sock_un;
#endif

  FPRINTF((stderr, "%sXPAProxyConnect to %s\n", _sp, method));
  /* initialize results */
  if( rip )   *rip = 0;
  if( rport ) *rport = 0;
  if( rname ) *rname = '\0';

  switch(XPAMethod(method)){
  case XPA_INET:
again1:
    if( !XPAParseIpPort(method, &ip, &port) ){
      goto error;
    }
    /* use $localhost over $host (we don't trust host to be correct) */
    if( (ip == gethostip("$host")) && (tries == 0) ){
      ip = gethostip("$localhost");
    }
    /* connect to the server before we go further */
    if( (ofd = xsocket(AF_INET, SOCK_STREAM, 0)) < 0 ){
      PERROR(("XPAProxyConnect: socket()"));
      goto error;
    }
    setsockopt(ofd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_in.sin_family = AF_INET;
    sock_in.sin_addr.s_addr = htonl(ip);
    sock_in.sin_port = htons(port);
    /* make the connection with the server */
    if(connect(ofd, (struct sockaddr *)&sock_in, sizeof(sock_in))<0){
      xclose(ofd);
      /* if localhost doesn't work, make one try with the host ip */
      /* we also try again just in case there was an odd error such
	 as "permission denied", which we have seen once or twice */
      if( tries < 2 ){
	tries++;
	goto again1;
      }
      /* give up */
      else{
	PERROR(("XPAProxyConnect: connect()"));
	goto error;
      }
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
again2:
    ip = 0;
    port = 0;
    /* open a socket and fill in socket information */
    if( (ofd = xsocket(AF_UNIX, SOCK_STREAM, 0)) < 0 ){
      goto error;
    }
    setsockopt(ofd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    memset((char *)&sock_un, 0, sizeof(sock_un));
    sock_un.sun_family = AF_UNIX;
    strcpy(sock_un.sun_path, method);
    /* make the connection with the server */
    if(connect(ofd, (struct sockaddr *)&sock_un, sizeof(sock_un))<0){
      xclose(ofd);
      /* Unix sockets get ECONNREFUSED when the listen queue is full,
	 so we try a few times to give the server a chance to recover */
      if( (xerrno == ECONNREFUSED) && (tries < XPA_RETRIES) ){
	tries++;
	XPASleep(10);
	goto again2;
      }
      /* give up */
      else{
	goto error;
      }
    }
    break;
#endif
  default:
    goto error;
  }

  /* fill in blansk */
  if( rip )   *rip   = ip;
  if( rport ) *rport = port;
  if( rname ){
    strncpy(rname, method, SZ_LINE-1);
    rname[SZ_LINE-1] = '\0';
  }
  return(ofd);

error:
  return -1;
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	CommNew
 *
 * Purpose:	allocate a new comm record and add to end of list
 *
 * Returns:	allocated comm record
 *
 *----------------------------------------------------------------------------
 */

#ifdef ANSI_FUNC
static XPAComm 
CommNew (XPA xpa, int fd, unsigned int ip, int port, char *name, NS ns)
#else
static XPAComm CommNew(xpa, fd, ip, port, name, ns)
     XPA xpa;
     int fd;
     unsigned int ip;
     int port;
     char *name;
     NS ns;
#endif
{
  XPAComm comm, cur;
  int i;
  socklen_t slen;
  struct sockaddr_in sock_in;
#if HAVE_SYS_UN_H
  struct sockaddr_un sock_un;
#endif

  /* create the comm struct */
  if( (comm = (XPAComm)xcalloc(1, sizeof(XPACommRec))) == NULL )
    return(NULL);
  
  /* if fd < 0, we accept a connection to get the fd */
  if( fd < 0 ){
    /* accept the connection */
    switch(mtype){
    case XPA_INET:
      while( 1 ){
	slen = sizeof(struct sockaddr_in);
        if((comm->cmdfd=xaccept(xpa->fd,(struct sockaddr *)&sock_in,&slen))>=0){
	  comm->cmdip = ntohl(sock_in.sin_addr.s_addr);
	  comm->cmdport = ntohs(sock_in.sin_port);
	  break;
	}
	else{
	  if( xerrno == EINTR )
	    continue;
	  else{
	    xfree(comm);
	    return(NULL);
	  }
	}
      }
      break;
#if HAVE_SYS_UN_H
    case XPA_UNIX:
      while( 1 ){
	slen = sizeof(struct sockaddr_un);
	if((comm->cmdfd=xaccept(xpa->fd,(struct sockaddr *)&sock_un,&slen))>=0){
	  comm->cmdname = xstrdup(sock_un.sun_path);
	  break;
	}
	else{
	  if( xerrno == EINTR )
	    continue;
	  else{
	    xfree(comm);
	    return(NULL);
	  }
	}
      }
      break;
#endif
    default:
      xfree(comm);
      return(NULL);
    }
  }
  /* all info is supplied */
  else{
    switch(mtype){
    case XPA_INET:
      comm->cmdip = ip;
      comm->cmdport = port;
      break;
    case XPA_UNIX:
      comm->cmdname = xstrdup(name);
      break;
    }
    comm->cmdfd = fd;
    /* store name server record */
    comm->ns = ns;
  }

  /* set back pointer */
  /* make sure we close on exec */
  xfcntl(comm->cmdfd, F_SETFD, FD_CLOEXEC);
  /* mark data socket with impossible value */
  comm->datafd = -1;
  /* default is to ack */
  comm->ack = 1;
  /* set default cendian flag */
  comm->cendian = "?";
  /* clear the acl flags */
  for(i=0; i<XPA_CMDS+1; i++){
    comm->acl[i] = -1;
  }

  /* add this comm to end of list of comms */
  if( xpa->commhead == NULL ){
    xpa->commhead = comm;
  }
  else{
    for(cur=xpa->commhead; cur->next!=NULL; cur=cur->next){
      ;
    }
    cur->next = comm;
  }

  /* we might have to add this fd specially to a non-select event loop */
  if( xpa->seladd )
    comm->selcptr = (xpa->seladd)(xpa, comm->cmdfd);

  /* make this fd active */
  XPAActive(xpa, comm, 1);

  FPRINTF((stderr, "%sCommNew: ip=%x port=%d fd=%d\n", _sp,
	   comm->cmdip, comm->cmdport, comm->cmdfd));

  /* return the good news */
  return(comm);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	CommFree
 *
 * Purpose:	free a comm record and remove from list
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
static void 
CommFree (XPA xpa, XPAComm comm, int flag)
#else
static void CommFree(xpa, comm, flag)
     XPA xpa;
     XPAComm comm;
     int flag;
#endif
{
  XPAComm cur;

  if( !comm )
    return;

  FPRINTF((stderr, "%sCommFree: ip=%x port=%d fd=%d dfd=%d\n", _sp,
	   comm->cmdip, comm->cmdport, comm->cmdfd, comm->datafd));
  /* remove from list of this xpa's comms */
  if( xpa ){
    if( xpa->commhead ){
      if( xpa->commhead == comm ){
	xpa->commhead = comm->next;
      }
      else{
	for(cur=xpa->commhead; cur!=NULL; cur=cur->next){
	  if( cur->next == comm ){
	    cur->next = comm->next;
	    break;
	  }
	}
      }
    }
  }
  /* must check all xpas */
  else{
    for(xpa=xpahead; xpa!=NULL; xpa=xpa->next){
      if( xpa->commhead ){
	if( xpa->commhead == comm ){
	  xpa->commhead = comm->next;
	}
	else{
	  for(cur=xpa->commhead; cur!=NULL; cur=cur->next){
	    if( cur->next == comm ){
	      cur->next = comm->next;
	      break;
	    }
	  }
	}
      }
    }
  }

  /* close socket connections */
  if( flag && (comm->cmdfd >= 0) ){
    FPRINTF((stderr, "%sCommFree closing cmd fd: %d\n", _sp, comm->cmdfd));
    /* remove from active */
    if( comm->cmdfd < FD_SETSIZE )
	activefds[comm->cmdfd] = 0;
    /* delete the comm cmd fd specially from a non-select event loop */
    if( xpa && xpa->seldel && comm->selcptr ){
      (xpa->seldel)(comm->selcptr);
      comm->selcptr = NULL;
    }
    /* close file */
    xclose(comm->cmdfd);
  }
  /* close data channel */
  XPACloseData(xpa, comm);
  /* if we have a file name (unix sockets), free it */
  if( comm->cmdname != NULL ){
    unlink(comm->cmdname);
    xfree(comm->cmdname);
  }
  if( comm->dataname != NULL ){
    unlink(comm->dataname);
    xfree(comm->dataname);
  }

  /* free up the space */
  if( comm->id != NULL )        xfree(comm->id);
  if( comm->info != NULL )      xfree(comm->info);
  if( comm->target != NULL )    xfree(comm->target);
  if( comm->paramlist != NULL ) xfree(comm->paramlist);

  /* this comm is no longer associated with an ns */
  if( comm->ns ){
    comm->ns->nproxy -= 1;
  }

  /* disassociate from parent xpa */
  if( xpa ){
    xpa->comm = NULL;
  }

  /* free up structure */
  xfree(comm);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSOpen
 *
 * Purpose:	open a connection to the name service
 *
 * Returns:	connection fd on success, -1 on failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
static NS
XPANSOpen (XPA xpa, char *host, int force)
#else
static NS XPANSOpen(xpa, host, force)
     XPA xpa;
     char *host;
     int force;
#endif
{
  int i;
  int status;
  int xnsfd=0;
  int keep_alive=1;
  int tries=0;
  int ip=0;
  int dowarn=0;
  int contmode=XPA_CONNECT_TIMEOUT_MODE;
  unsigned short xnsport=0;
  unsigned int xnsip=0;
  static int errinit=0;
  char *s;
  char *path;
  char *method;
  char nscmd[SZ_LINE];
  char tbuf[SZ_LINE];
  char tbuf2[SZ_LINE];
  struct sockaddr_in sock_in;
  struct passwd *pw;
#if HAVE_SYS_UN_H
  struct sockaddr_un sock_un;
#endif
  char *findname=NULL;
  NS ns, cur;

  /* get name server method */
  method = XPANSMethod(host, 0);

  /* if the name service already is open, just return fd */
  if( xpa ){
    for(ns=xpa->nshead; ns!=NULL; ns=ns->next){
      if( !strcmp(ns->method, method) ){
	/* if forcing, see if the connection is valid */
	if( force >= 0 ){
	  if( (XPAPuts(xpa, ns->fd, "status\n", stimeout) >0)     &&
	      (XPAGets(xpa, ns->fd, tbuf, SZ_LINE, stimeout) >0)  && 
	      !strncmp(tbuf, "XPA$OK", 6)		     ){
	    FPRINTF((stderr, "%sXPANSOpen: found existing ns: %s\n",
		     _sp, ns->method));
	    return ns;
	  }
	  /* found the ns, but its not open */
	  else{
	    FPRINTF((stderr, "%sXPANSOpen: existing ns is dead: %s\n",
		     _sp, ns->method));
	    XPANSClose(xpa, ns);
	    break;
	  }
	}
	/* just return whatever we have if we are not forcing */
	else{
	  return ns;
	}
      }
    }
  }

  /* if no existing ns and flag is negative, we are done */
  if( force == -1 )
    return(NULL);

  /* we always make up the command afresh */
  *nscmd = '\0';

  /* get users for this user */
  if( (s=(char *)getenv("XPA_NSUSERS")) != NULL )
    strncpy(nsusers, s, SZ_LINE-1);
  /* default is just this use's userrname, from the environment */
  else if( (s=(char *)getenv("LOGNAME")) != NULL )
    strncpy(nsusers, s, SZ_LINE-1);
#if HAVE_MINGW32==0
  /* this is a last resort */
  else if( (pw=getpwuid(geteuid())) )
    strncpy(nsusers, pw->pw_name, SZ_LINE-1);
#endif
  /* if nothing good has happened, make it "all" */
  if( *nsusers == '\0' )
    strcpy(nsusers, "*");
  /* null-terminate string */
  nsusers[SZ_LINE-1] = '\0';

  /* set up communications socket for this method */
  switch(mtype){
  case XPA_INET:
again1:
    XPAParseIpPort(method, &xnsip, &xnsport);
    /* use $localhost over $host (we do not trust host to be correct) */
    if( (xnsip == gethostip("$host")) && (tries == 0) ){
      xnsip = gethostip("$localhost");
    }
    if( xnsip == 0 ){
      fprintf(stderr,
	      "XPA$ERROR: invalid host name specified: %s.\n", method);
      return(NULL);
    }
    if( (xnsfd = xsocket(AF_INET, SOCK_STREAM, 0)) < 0 )
      goto nons;
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_in.sin_family = AF_INET;
    sock_in.sin_addr.s_addr = htonl(xnsip);
    sock_in.sin_port = htons(xnsport);
    /* try connecting to the name server */
    if( ctimeout <= 0 ) contmode = 0;
    switch(contmode){
    case 1:
#if HAVE_MINGW32==0
      status=alrmconnect(xnsfd, (void *)&sock_in, sizeof(sock_in), ctimeout);
#else
      status=noblkconnect(xnsfd, (void *)&sock_in, sizeof(sock_in), ctimeout);
#endif
      break;
    case 2:
      status=noblkconnect(xnsfd, (void *)&sock_in, sizeof(sock_in), ctimeout);
      break;
    default:
      status=connect(xnsfd, (struct sockaddr *)&sock_in, sizeof(sock_in));
      break;
    }
    if( status == 0 ){
      FPRINTF((stderr, "%sXPANSOpen: connect to xpans\n", _sp));
      goto okns;
    }
    else{
      xclose(xnsfd);
      /* if localhost doesn't work, make one try with the host ip */
      if( (xerrno == ECONNREFUSED) && (tries < 1) ){
	tries++;
	goto again1;
      }
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
again2:
    /* open a socket and fill in socket information */
    if( (xnsfd = xsocket(AF_UNIX, SOCK_STREAM, 0)) < 0 )
      goto nons;
    memset((char *)&sock_un, 0, sizeof(sock_un));
    sock_un.sun_family = AF_UNIX;
    strcpy(sock_un.sun_path, method);
    xnsip = 0;
    /* try connecting to the name server */
    status=connect(xnsfd, (struct sockaddr *)&sock_un, sizeof(sock_un));
    if( status == 0 ){
      FPRINTF((stderr, "%sXPANSOpen: connect to xpans\n", _sp));
      goto okns;
    }
    else{
      xclose(xnsfd);
      /* Unix sockets get ECONNREFUSED when the listen queue is full,
	 so we try a few times to give the server a chance to recover */
      if( (xerrno == ECONNREFUSED) && (tries < XPA_RETRIES) ){
	tries++;
	XPASleep(10);
	goto again2;
      }
    }
    break;
#endif
  }

  /* if force is set, we try to start up the name server */
  if( force == 0 )
    goto noforce;

  /* make up the xpans command we will use */
  if( *nscmd == '\0' ){
    if(	(mtype == XPA_UNIX) || LOCALIP(xnsip) ){
      path = (char *)getenv("PATH");
      findname = (char *)Find(XPANSNAME, "x", NULL, path);
#if HAVE_CYGWIN
      /* this will help start up xpans under Windows */
      if( !findname ) findname = (char *)Find(XPANSNAME, "x", NULL, ".");
#endif
    }
    if( findname != NULL ){
      switch(mtype){
      case XPA_INET:
#if USE_LAUNCH
	/* change spaces to special launch space */
	for(i=0; i<strlen(findname); i++){
	  if( findname[i] == ' '){
	    findname[i] = LAUNCH_SPACE;
	  }
	}
	snprintf(nscmd, SZ_LINE, "%s -e -p %d -l %s/xpans_%d.log",
		 findname, xnsport, tmpdir, xnsport);
#else
	snprintf(nscmd, SZ_LINE, "\"%s\" -e -p %d -l %s/xpans_%d.log &\n",
		 findname, xnsport, tmpdir, xnsport);
#endif
	break;
#if HAVE_SYS_UN_H
      case XPA_UNIX:
#if USE_LAUNCH
	/* change spaces to special launch space */
	for(i=0; i<strlen(findname); i++){
	  if( findname[i] == ' '){
	    findname[i] = LAUNCH_SPACE;
	  }
	}
	snprintf(nscmd, SZ_LINE, "%s -e -f %s -l %s.log",
		 findname, method, method);
#else
	snprintf(nscmd, SZ_LINE, "\"%s\" -e -f %s -l %s.log &\n",
		 findname, method, method);
#endif
	break;
#endif
      }
    }
    else{
      *nscmd = '\0';
    }
  }

  /* did not find the name server -- start it up if we can, i.e.,
     if its on the same machine as we are on and we found it in the path */
  if((*nscmd != '\0') && ((mtype == XPA_UNIX) || LOCALIP(xnsip)) ){
    FPRINTF((stderr, "%sLaunching: %s\n", _sp, nscmd));
#if USE_LAUNCH
    if( Launch(nscmd, 0, NULL, NULL) != 0 )
      goto nons;
#else
    if( system(nscmd) != 0 )
      goto nons;
#endif
    /* enter loop looking to connect */
    for(tries=0; tries<XPA_RETRIES; tries++){
      switch(mtype){
      case XPA_INET:
	/* use $localhost alternately with $host */
	if( (xnsip == gethostip("$host")) && (tries % 2 == 0) ){
	  xnsip = gethostip("$localhost");
	}
	if( xnsip == 0 ){
	  fprintf(stderr,
		  "XPA$ERROR: invalid host name specified: %s.\n", method);
	  return(NULL);
	}
	if( (xnsfd = xsocket(AF_INET, SOCK_STREAM, 0)) < 0 )
	  goto nons;
	memset((char *)&sock_in, 0, sizeof(sock_in));
	sock_in.sin_family = AF_INET;
	sock_in.sin_addr.s_addr = htonl(xnsip);
	sock_in.sin_port = htons(xnsport);
	FPRINTF((stderr, "%sXPANSOPEN: attempting connect to %x\n",
		 _sp, xnsip));
	if(connect(xnsfd, (struct sockaddr *)&sock_in, sizeof(sock_in)) ==0)
	  goto okns;
	break;
#if HAVE_SYS_UN_H
      case XPA_UNIX:
	if( (xnsfd = xsocket(AF_UNIX, SOCK_STREAM, 0)) < 0 )
	  goto nons;
	if(connect(xnsfd, (struct sockaddr *)&sock_un, sizeof(sock_un)) ==0)
	  goto okns;
	break;
#endif
      }
      xclose(xnsfd);
      XPASleep(XPA_NSDELAY);
    }
    /* if we got here, we did not connect */
    goto nons;
  }
  /* name server is on a remote machine, so there is no hope */
  else{
    goto nons;
  }

okns:
  /* make sure we close on exec */
  xfcntl(xnsfd, F_SETFD, FD_CLOEXEC);
  setsockopt(xnsfd, SOL_SOCKET, SO_KEEPALIVE,
	     (char *)&keep_alive, sizeof(keep_alive));
  /* fill in new record */
  if( (ns = (NS)xcalloc(1, sizeof(NSRec))) == NULL ){
    xclose(xnsfd);
    return NULL;
  }
  ns->method = xstrdup(method);
  ns->host = xstrdup(host);
  ns->fd = xnsfd;
  FPRINTF((stderr, "%sXPANSOpen: host %s opened on fd %d\n", _sp, 
	   host?host:"<unknown>", xnsfd));
  switch(mtype){
  case XPA_INET:
    ns->ip = xnsip;
    ns->port = xnsport;
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    ns->name = xstrdup(method);
    break;
#endif	
  }
  if( xpa ){
    /* add to list of name servers */
    if( xpa->nshead == NULL ){
      xpa->nshead = ns;
    }
    else{
      for(cur=xpa->nshead; cur->next!=NULL; cur=cur->next)
	;
      cur->next = ns;
    }
  }
  /* check version with xpans */
  snprintf(tbuf, SZ_LINE, "version %s\n", XPA_VERSION);
  if( (XPAPuts(xpa, ns->fd, tbuf, stimeout) >0)           &&
      (XPAGets(xpa, ns->fd, tbuf, SZ_LINE, stimeout) >0)  ){
    if( word(tbuf, tbuf2, &ip) ){
      if( !strcmp(tbuf2, "XPA$VERSION") ){
	if( word(tbuf, tbuf2, &ip) ){
	  /* version check: our server version should be <= xpans version */
	  dowarn = (XPAVersionCheck(XPA_VERSION, tbuf2)>0);
	}
	else{
	  strcpy(tbuf2, "unknown/pre-2.1 (noversion)");
	  dowarn = 1;
	}
      }
      else{
	strcpy(tbuf2, "unknown/pre-2.1 (badformat)");
	dowarn = 1;
      }
    }
    FPRINTF((stderr, "%sXPANSOpen: version info: %s\n", _sp, tbuf));
    if( dowarn ){
      XPAVersionWarn(XPA_VERSION, tbuf2);
    }
  }

  /* clean up */
  if( findname != NULL ) xfree(findname);
  return(ns);

nons:
  /* if we specified an explicit port, we don't need the name server,
     so don't bother with any warning or error messages */
  if( XPAPort(xpa) >0 )
    return NULL;
  switch(mtype){
  case XPA_INET:
    if( !errinit && verbosity ){
      if( LOCALIP(xnsip) ){
	fprintf(stderr,
		"XPA$WARNING: xpans needs to be running on this machine.\n");
      }
      else{
	fprintf(stderr,
		"XPA$WARNING: xpans needs to be running on machine: ");
	fprintf(stderr, "%s\n", getiphost(xnsip));
      }
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    if( !errinit && verbosity ){
      fprintf(stderr,
	      "XPA$WARNING: xpans needs to be running on this machine.\n");
    }
    break;
#endif
  default:
    break;
  }
  if( xpa && verbosity ){
    if( !errinit ){
      /* make up the command users will need to start xpans */
      if( *nscmd == '\0' ){
	switch(mtype){
	case XPA_INET:
	  snprintf(nscmd, SZ_LINE, "xpans -e -p %d -l %s/xpans_%d.log", 
		   xnsport, tmpdir, xnsport);
	  break;
#if HAVE_SYS_UN_H
	case XPA_UNIX:
	  snprintf(nscmd, SZ_LINE, "xpans -e -f %s -l %s.log", method, method);
	  break;
#endif
	}
      }
      fprintf(stderr, "Please start xpans using the command:\n\t%s\n", nscmd);
      fprintf(stderr, "Once xpans is running, register all xpas in this process using:\n");
      fprintf(stderr, "\txpaset -p %s -nsconnect\n", xpa->method);
    }
    fprintf(stderr, "For now, contact %s using:", xpa->name);
    fprintf(stderr, " xpaset %s .. or xpaget %s ..\n",
	    xpa->method, xpa->method);
  }
  errinit++;

noforce:
  if( findname != NULL ) xfree(findname);
  return(NULL);
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	_XPAFree
 *
 * Purpose: 	free up memory in the XPA record structure
 *		(internal version)
 *
 * Results:	0 on success, -1 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
static int 
_XPAFree (XPA xpa)
#else
static int _XPAFree(xpa)
     XPA xpa;
#endif
{
  char tbuf[SZ_LINE];
  XPACmd cmd, tcmd;
  XPAComm comm, tcomm;
  XPAClip clip, tclip;
  NS ns, tns;

  /* make sure we have something to do */
  if( xpa == NULL )
    return(-1);

  FPRINTF((stderr, "%s_XPAFree: freeing xpa struct\n", _sp));
  /* remove this xpa from public availability */
  if( xpa->type != NULL )
    XPANSDel(xpa, NULL, NULL);

  /* free all sub-commands */
  for(cmd=xpa->commands; cmd!=NULL; ){
    tcmd = cmd->next;
    XPACmdDel(xpa, cmd);
    cmd = tcmd;
  }

  /* remove from list of xpas */
  XPAListDel(&xpahead, xpa);

  /* close down listening socket */
  if( xpa->fd >= 0 )
    xclose(xpa->fd);

  /* perform method-specific cleanup */
  switch(mtype){
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    /* delete the unix socket files */
    unlink(xpa->method);
    snprintf(tbuf, SZ_LINE, "%s_data", xpa->method);
    unlink(tbuf);
    break;
#endif
  default:
    break;
  }

  /* free up space */
  if( xpa->version )	xfree(xpa->version);
  if( xpa->type )	xfree(xpa->type);
  if( xpa->method )	xfree(xpa->method);
  if( xpa->xclass ) 	xfree(xpa->xclass);
  if( xpa->name )	xfree(xpa->name);
  if( xpa->help )	xfree(xpa->help);
  if( xpa->sendian )	xfree(xpa->sendian);

  /* call the select free routine for the listening socket and loop type.
     we use an indirect routine to avoid having to link Xt, Tcl, etc. */
  if( xpa->seldel && xpa->selptr ){
    (xpa->seldel)(xpa->selptr);
    xpa->selptr = NULL;
  }

  /* close communication channels */
  for(comm=xpa->commhead; comm!=NULL; ){
    tcomm = comm->next;
    CommFree(xpa, comm, 1);
    comm = tcomm;
  }

  /* free up clipboards */
  for(clip=xpa->cliphead; clip!=NULL; ){
    tclip = clip->next;
    ClipBoardFree(xpa, clip);
    clip = tclip;
  }

  /* close down the name server and all of the remotes for this xpa */
  for(ns=xpa->nshead; ns!=NULL; ){
    tns = ns->next;
    XPANSClose(xpa, ns);
    ns = tns;
  }

  /* free up record struct */
  xfree((char *)xpa);

  return(0);
}

#if HAVE_ATEXIT
/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	_XPAAtExit
 *
 * Purpose: 	XPA cleanup on exit
 *		The main purpose of this routine is to try to delete the
 *		unix socket files when an XPA server exits.
 *
 * Results:	none
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
static void
_XPAAtExit (void)
#else
static void _XPAAtExit()
#endif
{
  XPA xpa, txpa;
  static int done=0;

  if( !done ){
    /* return if we were not initialized */
    if( !atexitinit ) return;
    /* return if I am not the process who initialized (I'm a child) */
    if( atexitinit != getpid() ) return;
    FPRINTF((stderr, "calling XPAAtExit\n"));
    for(xpa=xpahead; xpa!=NULL; ){
      /* use temp in case we destroy structure in the cleanup */
      txpa = xpa->next;
      _XPAFree(xpa);
      xpa = txpa;
    }
    /* platform-dependent cleanup */
    xsocketcleanup();
    /* done with cleanup */
    done++;
  }
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	XPAAtExit
 *
 * Purpose: 	set up XPA cleanup on exit
 *
 * Results:	none
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void
XPAAtExit (void)
#else
void XPAAtExit()
#endif
{
  if( !atexitinit ){
    atexit(_XPAAtExit);
    atexitinit = getpid();
  }
}

#endif

/*
 *----------------------------------------------------------------------------
 *
 *
 *			Semi-Public Routines
 *			(mainly used by xpaset and xpaget)
 *
 *
 *----------------------------------------------------------------------------
 */

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	_XPAValid
 *
 * Purpose: 	see if the xpa struct is valid
 *
 * Results:	1 on success, 0 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
_XPAValid (XPA head, XPA xpa, char *type)
#else
int _XPAValid(head, xpa, type)
     XPA head;
     XPA xpa;
     char *type;
#endif
{
  XPA cur;

  if( xpa == NULL )
    return(0);
  for(cur=head; cur!=NULL; cur=cur->next){
    if( (cur == xpa) && !strcspn(cur->type, type) ){
      return(1);
    }
  }
  return(0);
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	XPAValid
 *
 * Purpose: 	see if the xpa struct is valid
 *
 * Results:	1 on success, 0 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAValid (XPA xpa)
#else
int XPAValid(xpa)
     XPA xpa;
#endif
{
  if( _XPAValid(xpahead, xpa, XPA_ACLS) )
    return(1);
  else
    return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAEndian
 *
 * Purpose:	semi-public routine to return the endian-ness of this
 *		machine
 *
 * Results:	0 if little endian, 1 if bigendian
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAEndian(void)
#else
int XPAEndian()
#endif
{
  union
  {
    long l;
    char c[sizeof (long)];
  } u;
  u.l = 1;
  return(u.c[sizeof (long) - 1] == 1);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAListHead
 *
 * Purpose:	semi-public routine to return the head of the xpa list
 *
 * Results:	XPA list pointer on success
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
XPA 
XPAListHead (void)
#else
XPA XPAListHead()
#endif
{
  return(xpahead);
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	XPAListAdd
 *
 * Purpose: 	add a member of an xpa list
 *
 * Results:	1 on success, 0 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void
XPAListAdd (XPA *head, XPA xpa)
#else
void XPAListAdd(head, xpa)
     XPA *head;
     XPA xpa;
#endif
{
  XPA cur;

  if( *head == NULL ){
    *head = xpa;
  }
  else{
    for(cur=*head; cur->next!=NULL; cur=cur->next)
      ;
    cur->next = xpa;
  }
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	XPAListDel
 *
 * Purpose: 	remove a member of an xpa list
 *
 * Results:	1 on success, 0 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void
XPAListDel (XPA *head, XPA xpa)
#else
void XPAListDel(head, xpa)
     XPA *head;
     XPA xpa;
#endif
{
  XPA cur;

  /* remove from list of xpas */
  if( *head ){
    if( *head == xpa ){
      *head = xpa->next;
    }
    else{
      for(cur=*head; cur!=NULL; cur=cur->next){
	if( cur->next == xpa ){
	  cur->next = xpa->next;
	  break;
	}
      }
    }
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAActive
 *
 * Purpose:	make the xpa, cmd and data fds active or inactive for select
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAActive (XPA xpa, XPAComm comm, int flag)
#else
int XPAActive(xpa, comm, flag)
     XPA xpa;
     XPAComm comm;
     int flag;
#endif
{
  int prev=0;

  /* sanity check */
  if( !xpa ) return(0);

  switch(flag){
  /* remove this xpa from the active list */
  case 0:
    if( (xpa->fd >= 0) && (xpa->fd < FD_SETSIZE) ){
      FPRINTF((stderr, "%sXPAActive: clearing fd %d\n", _sp, xpa->fd));
      prev = activefds[xpa->fd];
      activefds[xpa->fd] = 0;
      if( xpa->seloff && xpa->selptr )
	(xpa->seloff)(xpa->selptr);
    }
    if( comm ){
      if( (comm->cmdfd >= 0) && (comm->cmdfd < FD_SETSIZE) ){
	activefds[comm->cmdfd] = 0;
	if( xpa->seloff && comm->selcptr )
	  (xpa->seloff)(comm->selcptr);
      }
      if( (comm->datafd >= 0) && (comm->datafd < FD_SETSIZE) ){
	activefds[comm->datafd] = 0;
	if( xpa->seloff && comm->seldptr )
	  (xpa->seloff)(comm->seldptr);
      }
    }
    break;
  /* add this xpa/comm to the active list */
  case 1:
    if( (xpa->fd >= 0) && (xpa->fd < FD_SETSIZE) ){
      FPRINTF((stderr, "%sXPAActive: activating fd %d\n", _sp, xpa->fd));
      prev = activefds[xpa->fd];
      activefds[xpa->fd] = 1;
      if( xpa->selon && xpa->selptr )
	(xpa->selon)(xpa->selptr);
    }
    if( comm ){
      if( (comm->cmdfd >= 0) && (comm->cmdfd < FD_SETSIZE) ){
	activefds[comm->cmdfd] = 1;
	if( xpa->selon && comm->selcptr )
	  (xpa->selon)(comm->selcptr);
      }
      if( (comm->datafd >= 0) && (comm->datafd < FD_SETSIZE) ){
	activefds[comm->datafd] = 1;
	if( xpa->selon && comm->seldptr )
	  (xpa->selon)(comm->seldptr);
      }
    }
    break;
  default:
    break;
  }
  return(prev);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAActiveFd
 *
 * Purpose:	semi-public routine to return flag if fd is active
 *
 * Results:	1 is active, 0 is inactive
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAActiveFd (int fd)
#else
int XPAActiveFd(fd)
     int fd;
#endif
{
  if( (fd >= 0) && (fd < FD_SETSIZE) && (activefds[fd] > 0) )
    return(1);
  else
    return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAAddSelect
 *
 * Purpose:	add one or more xpa sockets to the select flags
 *
 * Return:	number of xpas that were added to the select flags
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAAddSelect (XPA xpa, fd_set *readfdsptr)
#else
int XPAAddSelect(xpa, readfdsptr)
     XPA xpa;
     fd_set *readfdsptr;
#endif
{
  XPA cur;
  XPAComm comm;
  int got=0;

  /* better have some place to set the flags */
  if( readfdsptr == NULL )
    return(0);

  /* if a specific xpa was specified, just set its select flags */
  if( xpa != NULL ){
    if( XPAActiveFd(xpa->fd) ){
      FD_SET(xpa->fd, readfdsptr);
      got++;
      /* note that we only select on coms if the main fd is active */
      for(comm=xpa->commhead; comm!=NULL; comm=comm->next){
	if( XPAActiveFd(comm->cmdfd) ){
	  FD_SET(comm->cmdfd, readfdsptr);
	  got++;
	}
	if( XPAActiveFd(comm->datafd) && (comm->datafd != comm->cmdfd) ){
	  FD_SET(comm->datafd, readfdsptr);
	  got++;
	}
      }
    }
  }
  /* otherwise set select flags for all xpas */
  else{
    for(cur=xpahead; cur!=NULL; cur=cur->next){
      if( XPAActiveFd(cur->fd) ){
	FPRINTF((stderr, "%sXPAAddSelect: adding fd %d\n", _sp, cur->fd));
	FD_SET(cur->fd, readfdsptr);
	got++;
	/* note that we only select on coms if the main fd is active */
	for(comm=cur->commhead; comm!=NULL; comm=comm->next){
	  if( XPAActiveFd(comm->cmdfd) ){
	    FPRINTF((stderr, "%sXPAAddSelect: adding cmdfd %d\n",
		     _sp, comm->cmdfd));
	    FD_SET(comm->cmdfd, readfdsptr);
	    got++;
	  }
	  if( XPAActiveFd(comm->datafd) && (comm->datafd != comm->cmdfd) ){
	    FPRINTF((stderr, "%sXPAAddSelect: adding datafd %d\n",
		     _sp, comm->datafd));
	    FD_SET(comm->datafd, readfdsptr);
	    got++;
	  }
	}
      }
    }
  }
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAProcessSelect
 *
 * Purpose:	process xpas that have pending reads or writes
 *
 * Return:	number of xpas processed
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAProcessSelect (fd_set *readfdsptr, int maxreq)
#else
int XPAProcessSelect(readfdsptr, maxreq)
     fd_set *readfdsptr;
     int maxreq;
#endif
{
  int got=0;
  XPA xpa;
  XPAComm comm;
#ifdef OLD
  XPA txpa;
  XPAComm tcomm;
#endif

  /* <= 0 means do all of them */
  if( maxreq < 0 ){
    maxreq = 0;
  }

again:
  for(xpa=xpahead; xpa!=NULL; xpa=xpa->next){
    /* handle command requests */
    for(comm=xpa->commhead; comm!=NULL; comm=comm->next){
      if( (comm->cmdfd >=0) && FD_ISSET(comm->cmdfd, readfdsptr) ){
	FD_CLR(comm->cmdfd, readfdsptr);
	XPAHandler(xpa, comm->cmdfd);
	got++;
	if( maxreq && (got >= maxreq) ) return(got);
	goto again;
      }
    }
    /* handle data requests */
    for(comm=xpa->commhead; comm!=NULL; comm=comm->next){
      if( (comm->datafd >=0) && FD_ISSET(comm->datafd, readfdsptr) ){
	FD_CLR(comm->datafd, readfdsptr);
	XPAHandler(xpa, comm->datafd);
	got++;
	if( maxreq && (got >= maxreq) ) return(got);
	goto again;
      }
    }
    /* handle new requests */
    if( (xpa->fd >= 0) && FD_ISSET(xpa->fd, readfdsptr) ){
      FD_CLR(xpa->fd, readfdsptr);
      XPAHandler(xpa, xpa->fd);
      got++;
      if( maxreq && (got >= maxreq) ) return(got);
      goto again;
    }
  }

#ifdef OLD
  for(xpa=xpahead; xpa!=NULL; ){
    txpa = xpa->next;
    /* handle command requests */
    for(comm=xpa->commhead; comm!=NULL; ){
      tcomm = comm->next;
      if( (comm->cmdfd >=0) && FD_ISSET(comm->cmdfd, readfdsptr) ){
	FD_CLR(comm->cmdfd, readfdsptr);
	FPRINTF((stderr, "%sXPAProcessSelect: cmd %d\n", _sp, comm->cmdfd));
	/* if we got an error on this xpa, skip processing rest of it */
	if( XPAHandler(xpa, comm->cmdfd) != XPA_RTN_OK ){
	  goto nextxpa;
	}
	got++;
	/* if we freed this xpa, skip processing rest of it */
	if( !_XPAValid(xpahead, xpa, XPA_ACLS) ){
	  goto nextxpa;
	}
	/* if we are have processed the max reqests, we are done */
	if( maxreq && (got >= maxreq) ){
	  return(got);
	}
      }
      comm = tcomm;
    }
    /* handle data requests */
    for(comm=xpa->commhead; comm!=NULL; ){
      tcomm = comm->next;
      if( (comm->datafd >=0) && FD_ISSET(comm->datafd, readfdsptr) ){
	FD_CLR(comm->datafd, readfdsptr);
	FPRINTF((stderr, "%sXPAProcessSelect: data %d\n", _sp, comm->datafd));
	/* if we got an error on this xpa, skip processing rest of it */
	if( XPAHandler(xpa, comm->datafd) != XPA_RTN_OK ){
	  goto nextxpa;
	}
	got++;
	/* if we freed this xpa, skip processing rest of it */
	if( !_XPAValid(xpahead, xpa, XPA_ACLS) ){
	  goto nextxpa;
	}
	/* if we are have processed the max reqests, we are done */
	if( maxreq && (got >= maxreq) ){
	  return(got);
	}
      }
      comm = tcomm;
    }
    /* handle new requests */
    if( (xpa->fd >= 0) && FD_ISSET(xpa->fd, readfdsptr) ){
      FD_CLR(xpa->fd, readfdsptr);
      FPRINTF((stderr, "%sXPAProcessSelect: xpa %d\n", _sp, xpa->fd));
      /* if we got an error on this xpa, skip processing rest of it */
      if( XPAHandler(xpa, xpa->fd) != XPA_RTN_OK ){
	goto nextxpa;
      }
      got++;
      /* if we freed this xpa, skip processing rest of it */
      if( !_XPAValid(xpahead, xpa, XPA_ACLS) ){
	goto nextxpa;
      }
      /* if we are have processed the max reqests, we are done */
      if( maxreq && (got >= maxreq) ){
	return(got);
      }
    }

nextxpa:
    /* must check to see if last xpa freed the next xpa */
    if( _XPAValid(xpahead, txpa, XPA_ACLS) )
      xpa = txpa;
    else
      break;
  }
#endif
  FPRINTF((stderr, "%sXPAProcessSelect: returns %d\n", _sp, got));
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPACloseData
 *
 * Purpose:	close data fd if its not the cmd fd
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void 
XPACloseData (XPA xpa, XPAComm comm)
#else
void XPACloseData(xpa, comm)
     XPA xpa;
     XPAComm comm;
#endif
{
  /* we close the data channel if its not the command channel */
  if( comm && (comm->datafd >=0)  ){
    if( comm->cmdfd != comm->datafd ){ 
      FPRINTF((stderr, "%sXPACloseData: close fd %d for cmd fd %d\n", _sp, 
	       comm->datafd, comm->cmdfd));
      /* remove from active */
      if( comm->datafd < FD_SETSIZE )
	activefds[comm->datafd] = 0;
      /* delete the comm data fd specially from a non-select event loop */
      if( xpa && xpa->seldel && comm->seldptr ){
	(xpa->seldel)(comm->seldptr);
	comm->seldptr = NULL;
      }
      /* close file */
      xclose(comm->datafd);
    }
    /* reset data channel to impossible value */
    comm->datafd = -1;
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAHandler
 *
 * Purpose:	handle one request for an xpaset or xpaget
 *
 * Return:	0 on success, xpa error code on failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAHandler (XPA xpa, int fd)
#else
int XPAHandler(xpa, fd)
     XPA xpa;
     int fd;
#endif
{
  char tbuf[SZ_LINE];
  char tbuf2[SZ_LINE];
  char lbuf[SZ_LINE];
  char cmd[SZ_LINE];
  char target[SZ_LINE];
  char method[SZ_LINE];
  char ctmpl[SZ_LINE];
  char ntmpl[SZ_LINE];
  char *paramlist=NULL;
  char *acl;
  int save_ack;
  int tcmd;
  int got=0;
  int lp=0;
  int cmdfd=-1;
  unsigned short port=0;
  unsigned int ip=0;
  struct timeval tv;
  XPAComm comm=NULL, tcomm=NULL, xcomm=NULL, ocomm=NULL;
  XPA txpa=NULL;
  NS ns=NULL;
  fd_set readfds;
  FPRINTF((stderr, "%sXPAHandler: entering with fd %d\n", _sp, fd));

  /*  this is a defensive measure: we have to guard against an external
      loop calling the XPA handler with a bogus XPA record. This has been
      seen with the Tcl event loop.
   */
  for(txpa=xpahead; txpa!=NULL; txpa=txpa->next){
    if( txpa == xpa ) break;
  }
  if( txpa == NULL ){
    FPRINTF((stderr, "%sXPAHandler: xpa record %p is not in list\n",
	     _sp, xpa));
    return(XPA_RTN_NOCMD);
  }

  /*  this is a defensive measure: we have to ensure that there really
   *  is a request read.  It is possible that a user-defined select loop
   *  might call us to handle a request that we had already handled
   *  (i.e. we handle a request but can't reset someone else's select flags)
   */
  FD_ZERO(&readfds);
  FD_SET(fd, &readfds);
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  while( xselect(fd+1, &readfds, NULL, NULL, &tv) <=0 ){
    if( xerrno == EINTR )
      continue;
    FPRINTF((stderr, "%sXPAHandler: xpa fd %d is not ready\n", _sp, fd));
    return(XPA_RTN_NOCMD);
  }

  /* if this is a first connection, we create a new comm channel and exit */
  if( fd == xpa->fd ){
    if( (comm = CommNew(xpa, -1, 0, 0, NULL, NULL)) == NULL )
      return(XPA_RTN_NOCONN);
    /* Return to prevent xpa from finishing before other xpa's are started.
       Otherwise, each xpa's associated with a target template will be
       processed serially, which will defeat all the hard work done on
       the client side to send data to servers when they ask for it.
    */
    else{
      FPRINTF((stderr, "%sXPAHandler: returning after CommNew on fd %d\n",
	      _sp, fd));
      got = XPA_RTN_OK;
      goto end;
    }
  }

  /* look for a currently active comm channel: cmd or data */
  for(comm=xpa->commhead; comm!= NULL; comm=comm->next){
    if( (fd == comm->cmdfd) || (fd == comm->datafd) ){
      break;
    }
  }
  /* make sure we have something */
  if( comm == NULL ){
    /* read extraenous message */
    if( XPAGets(xpa, fd, tbuf, SZ_LINE, 1) >0 ){
      got = XPA_RTN_UNCMD;
      FPRINTF((stderr, "%sXPAHandler: fd %d received extra message:\n%s\n",
	      _sp,  fd, tbuf));
    }
    else{
      FPRINTF((stderr, "%sXPAHandler: no active comm record for fd %d\n",
	    _sp, fd));
      got = XPA_RTN_NOCMD2;
    }
    goto error;
  }
  /* but don't recurse */
  if( comm->status & XPA_STATUS_ACTIVE ){
    FPRINTF((stderr, "%sXPAHandler: fd %d returning to avoid recursion\n", 
	    _sp, fd));
    got = 0;
    goto end;
  }
  /* set current comm for this xpa */
  ocomm = xpa->comm;
  xpa->comm = comm;
  /* no message sent yet */
  comm->message = 0;

  /* data ready on data channel: go right to data handling section */
  if( fd == comm->datafd ){
    FPRINTF((stderr, "%sXPAHandler: jumping to cb for %d\n", _sp, fd));
    goto cb;
  }

  /* cmd channel: we are processing a new command */
retry:
  /* reset line buffer pointer for parsing */
  lp = 0;
  /* read next command */
  if( XPAGets(xpa, comm->cmdfd, lbuf, SZ_LINE, stimeout) <=0 ){
    FPRINTF((stderr, "%sXPAHandler: fd %d read EOF\n", _sp, comm->cmdfd));
    got = XPA_RTN_OK;
    goto eof;
  }
  /* new-lines imply we entered telnet mode on local host */
  else if( (*lbuf == '\n') || (*lbuf == '\r') || !strcmp(lbuf, "telnet") ){
    if( (mtype == XPA_UNIX) || LOCALIP(comm->cmdip) ){
      if( comm->telnet == 0 )
	XPAPuts(xpa, comm->cmdfd, "Entering telnet mode ...\n", stimeout);
      comm->telnet = 1;
      comm->ack = 0;
      comm->datafd = comm->cmdfd;
      stimeout = -1;
      goto retry;
    }
    else{
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
      got = XPA_RTN_NOCMD;
      goto error;
    }
  }

  FPRINTF((stderr, "%sXPAHandler: fd %d read command: %s",
	  _sp, comm->cmdfd, lbuf));

  /* validate the command */
  if( !word(lbuf, cmd, &lp) ){
    FPRINTF((stderr, "%sXPAHandler: missing target for fd %d\n",
	     _sp, comm->cmd));
    XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
    got = XPA_RTN_NOCMD;
    goto error;
  }

  /* send help message (local support only) */
  if( !strcmp(cmd, "help") ){
    if( (mtype == XPA_UNIX) || LOCALIP(comm->cmdip) ){
      XPAPuts(xpa, comm->cmdfd,
	   "xpaset|xpaget|xpainfo|xpaaccess [switches] class:name [params]\n",
	    stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "switches:\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-a\t\tclient wants to accept() data connection\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-e <b|l>\tclient endian-ness: big(b) or little(l)\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-i id\t\tclient id string\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-p <proxyinfo>\t\tfrom xpans (e.g., for proxy processing)\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-n\t\tdon't ack back to client\n",
	      stimeout);
      XPAPuts(xpa, comm->cmdfd,
	      "\t-t\t\tenter telnet mode (local only)\n",
	      stimeout);
      /* we must be in telnet mode */
      comm->telnet = 1;
      comm->ack = 0;
      goto retry;
    }
    else{
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
      got = XPA_RTN_NOCMD;
      goto error;
    }
  }

  /* determine which command was specified */
  if( !strcmp(cmd, "xpaset") )
    tcmd = XPA_SET;
  else if( !strcmp(cmd, "xpaget") )
    tcmd = XPA_GET;
  else if( !strcmp(cmd, "xpainfo") )
    tcmd = XPA_INFO;
  else if( !strcmp(cmd, "xpaaccess") )
    tcmd = XPA_ACCESS;
  else if( !strcmp(cmd, "xpadata") )
    tcmd = XPA_DATA;
  else if( !strcmp(cmd, "xpaaccept") )
    tcmd = XPA_ACCEPT;
  else if( !strcmp(cmd, "xpanagle") || !strcmp(cmd, "XPA$OK") )
    tcmd = XPA_NAGLE;
  else{
    FPRINTF((stderr, "%sXPAHandler: unknown command '%s' for fd %d\n",
	     _sp, cmd, comm->cmdfd));
    XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
    got = XPA_RTN_NOCMD;
    goto error;
  }

  /* process switches */
  while( word(lbuf, tbuf, &lp) ){
    if( *tbuf != '-' )
      break;
    if( !strcmp(tbuf, "-a") ){
      comm->mode |= COMM_CONNECT;
    }
    else if( !strcmp(tbuf, "-e") ){
      if( word(lbuf, tbuf, &lp) ){
	if( *tbuf == 'b' )
	  comm->cendian = "big";
	else if( *tbuf == 'l' )
	  comm->cendian = "little";
      }
      else{
	got = XPA_RTN_ILLCMD;
	goto error;
      }
    }
    else if( !strcmp(tbuf, "-f") ){
      /* B.Schoenhammer@bit-field.de 2009-09-21 */
#if HAVE_MINGW32==0
      if( !word(lbuf, tbuf, &lp) || (sscanf(tbuf, "%p", &xcomm) != 1) ){
#else
      if( !word(lbuf, tbuf, &lp) || (sscanf(tbuf, "%x", &xcomm) != 1) ){
#endif
	got = XPA_RTN_ILLCMD;
	goto done;
      }
      if( !word(lbuf, tbuf, &lp) || ((cmdfd = atoi(tbuf)) < 0) ){
	got = XPA_RTN_ILLCMD;
	goto done;
      }
    }
    else if( !strcmp(tbuf, "-i") ){
      if( word(lbuf, tbuf, &lp) ){
	if( comm->id != NULL ) xfree(comm->id);
	comm->id = xstrdup(tbuf);
      }
      else{
	XPAError(xpa, xpaMessbuf[XPA_RTN_ILLCMD]);
	got = XPA_RTN_ILLCMD;
	goto error;
      }
    }
    else if( !strcmp(tbuf, "-n") ){
      comm->ack = 0;
    }
    else if( !strcmp(tbuf, "-p") ){
      if( word(lbuf, tbuf, &lp) ){
	if( comm->info != NULL ) xfree(comm->info);
	comm->info = xstrdup(tbuf);
      }
      else{
	XPAError(xpa, xpaMessbuf[XPA_RTN_ILLCMD]);
	got = XPA_RTN_ILLCMD;
	goto error;
      }
    }
    else if( !strcmp(tbuf, "-t") ){
      comm->telnet = 1;
    }
    else{
      break;
    }
  }

  /* make sure we have some sort of id */
  if( comm->id == NULL ){
    comm->id = xstrdup("?");
  }

  /* process nagle ack */
  if( tcmd == XPA_NAGLE ){
    /* if data is forthcoming, exit and wait for data */
    if( comm->usebuf ){
      got = XPA_RTN_OK;
      goto end;
    }
    /* go process callback */
    else{
      goto cb;
    }
  }

  /* connect (proxy) request: connect back to client */
  if( tcmd == XPA_ACCEPT ){
    FPRINTF((stderr, "%scmd is xpaaccept from client %d: %s", _sp, fd, lbuf));
    /* syntax: xpaaccept <method> ... */
    lp = 0;
    if( !word(lbuf, tbuf, &lp)      ||
	strcmp(tbuf, "xpaaccept")   ||
	!word(lbuf, method, &lp)    ){
      got = -1;
      goto error;
    }
    if( (cmdfd=XPAProxyConnect(xpa, method, &ip, &port, tbuf)) <0 ){
      FPRINTF((stderr, "%sXPAProxyConnect failed for: %d\n", _sp, fd));
      got = -1;
      goto error;
    }
    if( (tcomm = CommNew(xpa, cmdfd, ip, port, method, NULL)) == NULL ){
      got = XPA_RTN_NOCONN;
      goto error;
    } 
    else{
      /* now exit and wait for client to send command */
      FPRINTF((stderr,
	       "%sXPAHandler: fd %d exiting after proxy connect\n", _sp,
	       tcomm->cmdfd));
      got = XPA_RTN_OK;
      goto end;
    }
  }

  /* data request: find associated command request, and process the command */
  if( tcmd == XPA_DATA ){
    FPRINTF((stderr, "%sXPAHandler: processing data fd %d\n",
	     _sp, comm->cmdfd));
    /* find the cmd record with which this data is associated */
    for(tcomm=xpa->commhead; tcomm!= NULL; tcomm=tcomm->next){
      if( (tcomm == xcomm) && (tcomm->cmdfd == cmdfd) ){
	break;
      }
    }
    /* if we found an associated command, copy in data info and process */
    if( tcomm ){
      /* fill in command comm */
      tcomm->datafd = comm->cmdfd;
      tcomm->seldptr = comm->selcptr;
      if( tcomm->dataname ) xfree(tcomm->dataname);
      tcomm->dataname = xstrdup(comm->cmdname);
      /* done with data comm */
      CommFree(xpa, comm, 0);
      /* reset comm pointers */
      comm = tcomm;
      xpa->comm = comm;
      FPRINTF((stderr, "%sXPAHandler: found cmd fd %d for this data fd %d\n",
	       _sp, comm->cmdfd, comm->datafd));
      if( comm->cmd == XPA_SET ){
	FPRINTF((stderr, "%sXPAHandler: data %d returning await xpaset\n",
		 _sp, comm->datafd));
	got = XPA_RTN_OK;
	goto end;
      }
      else{
	FPRINTF((stderr, "%sXPAHandler: data %d going on to process data\n",
		 _sp, comm->datafd));
	goto cb;
      }
    }
    else{
      FPRINTF((stderr, "%sXPAHandler: data fd has no corresponding cmd %d\n",
	       _sp, comm->cmdfd));
      XPAError(xpa, xpaMessbuf[XPA_RTN_ILLCMD]);
      got = XPA_RTN_ILLCMD;
      goto error;
    }
  }

  /* for a command, the first non-switch word we found was the target */
  if( *tbuf == '\0' ){
    FPRINTF((stderr, "%sXPAHandler: missing target for fd %d\n",
	     _sp, comm->cmdfd));
    XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
    got = XPA_RTN_NOCMD;
    goto error;
  }
  else{
    strcpy(target, tbuf);
  }

  /* validate the target (except for xpans for use as proxy) */
  if( strcmp(xpa->xclass, XPANS_CLASS) || strcmp(xpa->name, XPANS_NAME) ){
    XPAParseName(target, ctmpl, ntmpl, SZ_LINE);
    if( (strcmp(ctmpl,"?") && !tmatch(xpa->xclass, ctmpl)) || 
	(strcmp(ntmpl,"?") && !tmatch(xpa->name, ntmpl))   ){
      got = XPA_RTN_NOTARG;
      goto error;
    }
  }
  if( comm->target != NULL ) xfree(comm->target);
  comm->target = xstrdup(target);

  /* the rest of the input string is paramlist */
  paramlist = &(lbuf[lp]);
  nowhite(paramlist, paramlist);
  if( comm->paramlist != NULL ) xfree(comm->paramlist);
  comm->paramlist = xstrdup(paramlist);
  /* save command */
  comm->cmd = tcmd;

  FPRINTF((stderr,
	   "%sXPAHandler: processing command fd %d for target %s (%d)\n",
	   _sp, comm->cmdfd, target, comm->cmd));

  /* a reserved command can only be called from the same host as the server,
     or from local (unix) sockets */
  lp = 0;
  if( XPACmdLookupReserved(xpa, comm->paramlist, &lp) ){
    comm->mode |= COMM_RESERVED;
  }

  /* set up command-specific info */
  switch(comm->cmd){
  case XPA_SET:
    if( comm->mode & COMM_RESERVED ){
      comm->usebuf = 1;
      comm->useacl = guseacl && (mtype != XPA_UNIX);
      acl = "s";
    }
    else if( xpa->receive_callback ){
      comm->usebuf = (xpa->receive_mode & XPA_MODE_BUF);
      comm->useacl = guseacl && 
	(xpa->receive_mode & XPA_MODE_ACL) && (mtype != XPA_UNIX);
      acl = "s";
    }
    else{
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOREC]);
      got = XPA_RTN_NOREC;
      goto error;
    }
    break;
  case XPA_GET:
    if( comm->mode & COMM_RESERVED ){
      comm->usebuf = 1;
      comm->useacl = guseacl && (mtype != XPA_UNIX);
      acl = "g";
    }
    else if( xpa->send_callback ){
      comm->usebuf = (xpa->send_mode & XPA_MODE_BUF);
      comm->useacl = guseacl &&
	(xpa->send_mode & XPA_MODE_ACL) && (mtype != XPA_UNIX);
      acl = "g";
    }
    else{
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOSEND]);
      got = XPA_RTN_NOSEND;
      goto error;
    }
    break;
  case XPA_INFO:
    if( xpa->info_callback ){
      comm->usebuf = 0;
      comm->useacl = guseacl &&
	(xpa->info_mode & XPA_MODE_ACL) && (mtype != XPA_UNIX);
      acl = "i";
    }
    else{
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOINFO]);
      got = XPA_RTN_NOINFO;
      goto error;
    }
    break;
  case XPA_ACCESS:
    comm->usebuf = 0;
    comm->useacl = guseacl && (mtype != XPA_UNIX);
    /* may as well check access mode as well */
    snprintf(tbuf2, SZ_LINE, "%sa%s", _sp, comm->paramlist?comm->paramlist:"");
    acl = tbuf2;
    break;
  default:
    FPRINTF((stderr, "%sXPAHandler: invalid access control check for fd %d\n",
	     _sp, comm->cmdfd));
    XPAError(xpa, xpaMessbuf[XPA_RTN_NOCMD]);
    got = XPA_RTN_NOCMD;
    goto error;
  }

  /* perform access authentication */
  if( comm->useacl ){
    /* determine acl for this ip, if necessary */
    if( comm->acl[comm->cmd] < 0 ){
      comm->acl[comm->cmd] = XPAAclCheck(xpa, comm->cmdip, acl);
    }
    /* check acl */
    if( comm->acl[comm->cmd] <= 0 ){
      FPRINTF((stderr, "%sXPAHandler: fd %d FAILED acl check\n",
	       _sp, comm->cmdfd));
      XPAError(xpa, xpaMessbuf[XPA_RTN_NOAUTH]);
      got = XPA_RTN_NOAUTH;
      goto error;
    }
    FPRINTF((stderr, "%sXPAHandler: fd %d passed acl check\n",
	     _sp, comm->cmdfd));
  }

  /* for telnet mode, just use cmdfd for data */
  if( comm->telnet ){
    comm->datafd = comm->cmdfd;
  }
  /* for xpainfo and no ack, just go to the vallback */
  else if( (comm->cmd == XPA_INFO) && !comm->ack ){
    goto cb;
  }
  /* in general, we must tell the client how we will handle data */
  else{
    if( comm->usebuf ){
      if( comm->mode & COMM_CONNECT ){
	snprintf(lbuf, SZ_LINE, "%s XPA$DATA accept %p %d (%s:%s %s)\n",
		 comm->id, comm,  comm->cmdfd,
		 xpa->xclass, xpa->name, xpa->method);
      }
      else{
	snprintf(lbuf, SZ_LINE, "%s XPA$DATA connect %p %d (%s:%s %s)\n",
		 comm->id, comm, comm->cmdfd,
		 xpa->xclass, xpa->name, xpa->method);
      }
    }
    /* no data channel being set up */
    else{
      snprintf(lbuf, SZ_LINE, "%s XPA$NODATA (%s:%s %s)\n",
	       comm->id, xpa->xclass, xpa->name, xpa->method);
    }
    FPRINTF((stderr, "%sXPAHandler: fd %d sends string: %s",
	     _sp, comm->cmdfd, lbuf));
    if( XPAPuts(xpa, comm->cmdfd, lbuf, stimeout) <= 0 ){
      FPRINTF((stderr, "%sXPAHandler: fd %d couldn't send string: %s",
	       _sp, comm->cmdfd, lbuf));
      got = -1;
      goto error;
    }
    /* now exit and wait for nagle ack and for client to send data */
    FPRINTF((stderr,
	     "%sXPAHandler: fd %d exiting to wait for nagle or connect req\n",
	     _sp, comm->cmdfd));
    got = XPA_RTN_OK;
    goto end;
  }

  /* data channel complete (or no data): ready to execute the user callback */
cb:  
  /* we are now active */
  comm->status |= XPA_STATUS_ACTIVE;

  /* remove the current comm from the list of active fds,
     in case the server callback re-enters the event loop */
  XPAActive(xpa, comm, 0);

  /* zero out buf and len, just to make sure (don't free buf, in case
     application is using previous) */
  comm->buf = NULL;
  comm->len = 0;

  /* if we are not ack'ing after callback, do it now so client can exit */
  /* but don't do this for xpainfo/noack */
  if( !comm->ack && (comm->cmd != XPA_INFO) ){
    FPRINTF((stderr, "%sXPAHandler: sending OK to non-acking client %d %d\n",
	     _sp,  comm->cmdfd, comm->datafd));
    comm->ack = 1;
    XPAOK(xpa);
    comm->ack = 0;
  }

  /* process command */
  switch(comm->cmd){
  case XPA_GET:
    FPRINTF((stderr,
	     "%sXPAHandler: processing xpaget for %d %d\n", _sp,
	     comm->cmdfd, comm->datafd));
    /* check for a reserved command */
    if( comm->mode & COMM_RESERVED ){
      got = XPASendCommands(xpa->send_data, xpa, comm->paramlist,
			    &(comm->buf), &(comm->len));
    }
    else{
      got = (xpa->send_callback)(xpa->send_data, xpa, comm->paramlist,
				  &(comm->buf), &(comm->len));
    }
    if( (got == 0) && (comm->buf != NULL) && (comm->len > 0) ){
      if( XPAPutBuf(xpa, comm->datafd, comm->buf, comm->len, ltimeout) < 0 ){
	PERROR(("XPAHandler write buf"));
	XPAError(xpa, "XPA could not write data to client");
	goto done;
      }
    }
    if( (xpa->send_mode & XPA_MODE_FREEBUF) && (comm->buf != NULL) ){
      if( xpa->comm->myfree != NULL ){
	if( xpa->comm->myfree_ptr != NULL ){
	  xpa->comm->myfree(xpa->comm->myfree_ptr);
	}
	else{
	  xpa->comm->myfree(comm->buf);
	}
      }
      else{
	xfree(comm->buf);
      }
    }
    /* send client a message, unless its already been done */
    if( !comm->message ){
      if( got )
	XPAError(xpa, "error detected in send callback routine");
      else
	XPAOK(xpa);
    }
    FPRINTF((stderr,
	     "%sXPAHandler: finished xpaget for %d %d\n", _sp,
	     comm->cmdfd, comm->datafd));
    break;
  case XPA_SET:
    FPRINTF((stderr,
	     "%sXPAHandler: processing xpaset for %d %d\n", _sp,
	     comm->cmdfd, comm->datafd));
    /* check for a reserved command */
    if( comm->mode & COMM_RESERVED ){
      got=XPAReceiveCommands(xpa->receive_data, xpa,
			     comm->paramlist, NULL, 0);
    }
    else{
      /* fill buf if necessary */
      if( (comm->datafd >= 0) &&
	  comm->usebuf && (xpa->receive_mode & XPA_MODE_FILLBUF) ){
	if(XPAGetBuf(xpa, comm->datafd, &(comm->buf), &(comm->len), -1) <0){
	  XPAError(xpa, xpaMessbuf[XPA_RTN_NODATA]);
	  FPRINTF((stderr, "%sXPAHandler: no data for XPAGetBuf on %d\n", _sp,
		   comm->datafd));
	  got = -1;
	  goto done;
	}
	else{
	  /* close the data fd now that we are done with it */
	  XPACloseData(xpa, comm);
	}
      }
      /* execute the receive callback */
      got = (xpa->receive_callback)(xpa->receive_data, xpa,
				     comm->paramlist, comm->buf, comm->len);
    }
    if( (xpa->receive_mode & XPA_MODE_FREEBUF) && (comm->buf != NULL) ){
      if( xpa->comm->myfree != NULL ){
	if( xpa->comm->myfree_ptr != NULL ){
	  xpa->comm->myfree(xpa->comm->myfree_ptr);
	}
	else{
	  xpa->comm->myfree(comm->buf);
	}
      }
      else{
	xfree(comm->buf);
      }
    }
    /* send client an error message, unless its already been done */
    if( !comm->message ){
      if( got ){
	XPAError(xpa, "error detected in receive callback routine");
      }
      else{
	XPAOK(xpa);
      }
    }
    FPRINTF((stderr,
	     "%sXPAHandler: finished xpaset for %d %d\n", _sp,
	     comm->cmdfd, comm->datafd));
    break;
  case XPA_INFO:
    /* send OK before callback because we do not want the client waiting */
    if( comm->ack )
      XPAOK(xpa);
    save_ack = comm->ack;
    /* don't ever ack in callback */
    comm->ack = 0;
    /* execute the info callback -- don't bother checking for errors */
    (xpa->info_callback)(xpa->info_data, xpa, comm->paramlist);
    comm->ack = save_ack;
    break;
  case XPA_ACCESS:
    /* return errors -- that how we know if we have access */
    comm->ack = 1;
    /* type is in paramlist */
    if( comm->paramlist && *comm->paramlist ){
      char *s;
      for(s=comm->paramlist; *s; s++){
	switch(*s){
	case 'g':
	  if( !xpa->send_callback ){
	    XPAError(xpa, "no send callback (i.e., can't xpaget)");
	    goto done;
	  }
	  break;
	case 'i':
	  if( !xpa->info_callback ){
	    XPAError(xpa, "no info callback (i.e., can't xpainfo)");
	    goto done;
	  }
	  break;
	case 's':
	  if( !xpa->receive_callback ){
	    XPAError(xpa, "no receive callback (i.e., can't xpaset)");
	    goto done;
	  }
	  break;
	case 'a':
	  break;
	default:
	  XPAError(xpa, "unknown xpa access type");
	  goto done;
	}
      }
      /* if we got here, its OK */
      XPAOK(xpa);
    }
    /* no type, anything is OK */
    else
      XPAOK(xpa);
    break;
  default:
    /* something is really wrong if we have no command  */
    FPRINTF((stderr, "%sXPAHandler: invalid command #%d for fd %d\n",
	     _sp, comm->cmd, comm->cmdfd));
    got = XPA_RTN_NOCMD;
    goto error;
    break;
  }

done:
  FPRINTF((stderr,
	   "%sXPAHandler: finished processing %d with status %d\n", _sp,
	   fd, got));
  /* add xpa back to list of active ones */
  XPAActive(xpa, comm, 1);

  /* finalize comm record */
  if( comm ){
    /* close data channel */
    XPACloseData(xpa, comm);
    /* xpa is no longer active */
    comm->status &= ~XPA_STATUS_ACTIVE;
  }

  /* join common code */
  goto end;

eof:
  /* on eof, free up comm */
  if( comm ){
    ns = comm->ns;
    CommFree(xpa, comm, 1);
    comm = NULL;
    if( ns && !ns->nproxy && !ns->nxpa ){
      FPRINTF((stderr, "%sXPAHandler: closing ns %s\n", _sp, ns->name));
      XPANSClose(xpa, ns);
    }
  }
  /* join common code */
  goto end;
  

error:
  FPRINTF((stderr, "%sXPAHandler ERROR: return code %d on %d\n",
	   _sp, got, fd));
  /* add xpa back to list of active ones (but not comm) */
  XPAActive(xpa, comm, 1);
  /* xpa is no longer using this comm */
  xpa->comm = NULL;
  /* don't accidentally close ns on error */
  if( comm ){
    if( comm->ns )
      CommFree(xpa, comm, 0);
    else
      CommFree(xpa, comm, 1);
    comm = NULL;
  }
  /* join common code */
  goto end;

end:
  /* if a free was requested by the callback, do it now when its safe */
  if( xpa->status & XPA_STATUS_FREE ){
    XPAFree(xpa);
  }
  /* restore old value of comm */
  else{
    xpa->comm = ocomm;
  }

  /* return the status */
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAMode
 *
 * Purpose:	parse the mode string and set flags
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void 
XPAMode (char *mode, int *flag, char *name, int mask, int def)
#else
void XPAMode(mode, flag, name, mask, def)
     char *mode;
     int *flag;
     char *name;
     int mask;
     int def;
#endif
{
  char tbuf[SZ_LINE];
  char s[SZ_LINE];

  /* keyword routine requires an input buffer that can be modified */
  if( mode && *mode ){
    strncpy(s, mode, SZ_LINE-1);
    s[SZ_LINE-1] = '\0';
  }
  else
    goto error;
  /* look for the keyword=<value> string */
  if( keyword(s, name, tbuf, SZ_LINE) ){
    if( istrue(tbuf) )
      *flag |= mask;
    else
      *flag &= ~mask;
    return;
  }
  else
    goto error;

error:
  if( def )
    *flag |= mask;
  else
    *flag &= ~mask;
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAShortTimeout
 *
 * Purpose:	return short (select, gets) timeout value
 *
 * Return:	timeout in seconds
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAShortTimeout (void)
#else
int XPAShortTimeout()
#endif
{
  return(stimeout);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPALongTimeout
 *
 * Purpose:	return long (fillbuf) timeout value
 *
 * Return:	timeout in seconds
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPALongTimeout (void)
#else
int XPALongTimeout()
#endif
{
  return(ltimeout);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAReceiveLTimeout
 *
 * Purpose:	modify long timeout value for this process
 *
 * Returns:	0 for success, -1 for failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAReceiveLTimeout (void *client_data, void *call_data, char *paramlist,
	       char *buf, int len)
#else
int XPAReceiveLTimeout(client_data, call_data, paramlist, buf, len)
     void *client_data;
     void *call_data;
     char *paramlist;
     char *buf;
     int len;
#endif
{
  XPA xpa = (XPA)call_data;
  char tbuf[SZ_LINE];
  char *s;

  if( paramlist && *paramlist ){
    strncpy(tbuf, paramlist, SZ_LINE-1);
    tbuf[SZ_LINE-1] = '\0';
    nocr(tbuf);
    culc(tbuf);
    if( !strcmp(tbuf, "reset") ){
      ltimeout = XPA_LONG_TIMEOUT;
      if( (s=(char *)getenv("XPA_LONG_TIMEOUT")) != NULL )
	ltimeout = atoi(s);
    }
    else{
      ltimeout = atoi(tbuf);
    }
    return(0);
  }
  else{
    XPAError(xpa, "missing long timeout value");
    return(-1);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASendLTimeout
 *
 * Purpose:	return the long timeout for this process
 *
 * Returns:	0 for success, -1 for failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPASendLTimeout (void *client_data, void *call_data, char *paramlist,
	    char **buf, int *len)
#else
int XPASendLTimeout(client_data, call_data, paramlist, buf, len)
     void *client_data;
     void *call_data;
     char *paramlist;
     char **buf;
     int *len;
#endif
{
  XPA xpa = (XPA)call_data;
  char tbuf[SZ_LINE];

  snprintf(tbuf, SZ_LINE, "%d\n", ltimeout);
  send(xpa_datafd(xpa), tbuf, strlen(tbuf), 0);
  return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAReceiveSTimeout
 *
 * Purpose:	modify short timeout value for this process
 *
 * Returns:	0 for success, -1 for failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAReceiveSTimeout (void *client_data, void *call_data, char *paramlist,
	       char *buf, int len)
#else
int XPAReceiveSTimeout(client_data, call_data, paramlist, buf, len)
     void *client_data;
     void *call_data;
     char *paramlist;
     char *buf;
     int len;
#endif
{
  XPA xpa = (XPA)call_data;
  char tbuf[SZ_LINE];
  char *s;

  if( paramlist && *paramlist ){
    strncpy(tbuf, paramlist, SZ_LINE-1);
    tbuf[SZ_LINE-1] = '\0';
    nocr(tbuf);
    culc(tbuf);
    if( !strcmp(tbuf, "reset") ){
      stimeout = XPA_SHORT_TIMEOUT;
      if( (s=(char *)getenv("XPA_SHORT_TIMEOUT")) != NULL )
	stimeout = atoi(s);
    }
    else{
      stimeout = atoi(tbuf);
    }
    return(0);
  }
  else{
    XPAError(xpa, "missing short timeout value");
    return(-1);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASendSTimeout
 *
 * Purpose:	return the short timeout for this process
 *
 * Returns:	0 for success, -1 for failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPASendSTimeout (void *client_data, void *call_data, char *paramlist,
	    char **buf, int *len)
#else
int XPASendSTimeout(client_data, call_data, paramlist, buf, len)
     void *client_data;
     void *call_data;
     char *paramlist;
     char **buf;
     int *len;
#endif
{
  XPA xpa = (XPA)call_data;
  char tbuf[SZ_LINE];

  snprintf(tbuf, SZ_LINE, "%d\n", stimeout);
  send(xpa_datafd(xpa), tbuf, strlen(tbuf), 0);
  return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAVerbosity
 *
 * Purpose:	return verbosity value
 *
 * Return:	verbosity value (0, 1, 2)
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAVerbosity (void)
#else
int XPAVerbosity()
#endif
{
  return(verbosity);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASigusr1
 *
 * Purpose:	return flag for using sigusr1
 *
 * Return:	0 or 1
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPASigusr1 (void)
#else
int XPASigusr1()
#endif
{
  return(sigusr1);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAInitEnv
 *
 * Purpose:	initialize the xpa environment
 *
 * Return:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void 
XPAInitEnv (void)
#else
void XPAInitEnv()
#endif
{
  char *s;

  if( !tmpdir ){
    /* determine the communication method */
    mtype=XPAMethod(NULL);
    /* enable access controls and port management */
    if( mtype != XPA_UNIX ){
      XPAAclNew(NULL, 0);
      XPAPortNew(NULL, 0);
    }
    /* set short (select,gets) timeout, if necessary */
    if( (s=(char *)getenv("XPA_SHORT_TIMEOUT")) != NULL )
      stimeout = atoi(s);
    /* set long (fillbuf) timeout, if necessary */
    if( (s=(char *)getenv("XPA_LONG_TIMEOUT")) != NULL )
      ltimeout = atoi(s);
    /* set xpans connect timeout, if necessary */
    if( (s=(char *)getenv("XPA_CONNECT_TIMEOUT")) != NULL )
      ctimeout = atoi(s);
    /* set verbosity level, if necessary */
    if( (s=(char *)getenv("XPA_VERBOSITY")) != NULL ){
      if( istrue(s) )
	verbosity = XPA_VERBOSITY;
      else if( isfalse(s) )
	verbosity = 0;
      else
	verbosity = atoi(s);
      if( verbosity < 0 )
	verbosity = 0;
    }
    /* check for acl enable/disable */
    if( (s=(char *)getenv("XPA_ACL")) != NULL )
      guseacl = istrue(s);
    /* check for timestamp on errors */
    if( (s=(char *)getenv("XPA_TIMESTAMP_ERRORS")) != NULL )
      etimestamp = istrue(s);
    /* check for xpans register flag */
    if( (s=(char *)getenv("XPA_NSREGISTER")) != NULL )
      nsregister = istrue(s);
    /* check for use of siguser1 */
    if( (s=(char *)getenv("XPA_SIGUSR1")) != NULL )
      sigusr1 = istrue(s);
    /* check for version checking */
    if( (s=(char *)getenv("XPA_VERSIONCHECK")) != NULL ){
      if( istrue(s) )
	vercheck = 1;
      else if( isfalse(s) )
	vercheck = 0;
      else
	vercheck = atoi(s);
    }
    /* check for io loop calling xpa */
    if( (s=(char *)getenv("XPA_IOCALLSXPA")) != NULL ){
      if( istrue(s) ){
	XPAIOCallsXPA(1);
      }
      else if( isfalse(s) ){
	XPAIOCallsXPA(0);
      }
    }
    /* make sure we have a temp dir */
    if( tmpdir == NULL ){
      if( (s=(char *)getenv("XPA_TMPDIR")) != NULL )
	tmpdir = xstrdup(s);
      else if( (s=(char *)getenv("TMPDIR")) != NULL )
	tmpdir = xstrdup(s);
      else if( (s=(char *)getenv("TMP")) != NULL )
	tmpdir = xstrdup(s);
      else
	tmpdir = xstrdup(XPA_TMPDIR);
    }
    /* create directory, if necessary */
    xmkdir(tmpdir, 0777);
    xchmod(tmpdir, 0777);
#if HAVE_MINGW32==0
    /* Disable SIGPIPE so we do not die if the client dies.
     * Rather, we will get an EOF on reading or writing.
     */
    xsignal_sigpipe();
#endif
    /* platform-dependent startup */
    xsocketstartup();
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSAdd
 *
 * Purpose:	add this XPA to the name service
 *
 * Returns:	0 on success, -1 on failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPANSAdd (XPA xpa, char *host, char *mode)
#else
int XPANSAdd(xpa, host, mode)
     XPA xpa;
     char *host;
     char *mode;
#endif
{
  char username[SZ_LINE];
  char tbuf[SZ_LINE];
  char xmode[SZ_LINE];
  char *cmd="add";
  char *s;
  NS ns;
  struct passwd *pw;

  /* sanity check */
  if( !xpa )
    return(0);

  /* handle special case of the name server itself -- its a known entry */
  if( !strcmp(xpa->name, XPANS_NAME) ){
    return(0);
  }

  /* look for the proxy=<true|false> string */
  if( mode ){
    strncpy(xmode, mode, SZ_LINE-1);
    xmode[SZ_LINE-1] = '\0';
    if( keyword(xmode, "proxy", tbuf, SZ_LINE) && istrue(tbuf) ){
      cmd="addproxy";
    }
  }

  /* open a connection to the name service */
  if( (ns=XPANSOpen(xpa, host, 1)) != NULL ){
    /* get the user name, from the environment */
    if( (s=(char *)getenv("XPA_LOGNAME")) != NULL )
      strncpy(username, s, SZ_LINE-1);
    else if( (s=(char *)getenv("LOGNAME")) != NULL )
      strncpy(username, s, SZ_LINE-1);
#if HAVE_MINGW32==0
    /* this is a last resort */
    else if( (pw=getpwuid(geteuid())) )
      strncpy(username, pw->pw_name, SZ_LINE-1);
#endif
    /* if nothing good has happened, make it "unknown" */
    if( *username == '\0' )
      strcpy(username, "unknown");
    /* null-terminate string */
    username[SZ_LINE-1] = '\0';

    /* write the command to add this xpa */
    snprintf(tbuf, SZ_LINE, "%s %s %s:%s %s %s\n",
	     cmd, xpa->method, xpa->xclass, xpa->name, xpa->type, username);
    if( XPAPuts(xpa, ns->fd, tbuf, stimeout) < 0 ){
      return(-1);
    }
    /* get result */
    if( XPAGets(xpa, ns->fd, tbuf, SZ_LINE, stimeout) >0 ){
      if( !strncmp(tbuf, "XPA$OK", 6) ){
	/* for proxy, we now must listen for xpa requests on this ns */
	if( !strcmp(cmd, "addproxy") && xpa ){
	  if( CommNew(xpa, ns->fd, ns->ip, ns->port, ns->name, ns) ){
	    /* one more proxy is using this name server */
	    ns->nproxy += 1;
	  }
	}
	else{
	  /* one more access point is using this name server */
	  ns->nxpa += 1;
	}
	return(0);
      }
      else if( !strncmp(tbuf, "XPA$EXISTS", 10) )
	return(0);
      else
	return(-1);
    }
    else{
      return(-1);
    }
  }
  else{
    return(-1);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSDel
 *
 * Purpose:	remove public knowledge of this XPA from the name service
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPANSDel (XPA xpa, char *host, char *mode)
#else
int XPANSDel(xpa, host, mode)
     XPA xpa;
     char *host;
     char *mode;
#endif
{
  int got=0;
  char tbuf[SZ_LINE];
  char xmode[SZ_LINE];
  char *cmd="del";
  NS ns;

  /* sanity check */
  if( !xpa )
    return(0);

  /* handle special case of the name server itself -- its a known entry */
  if( xpa->name && !strcmp(xpa->name, XPANS_NAME) ){
    return(0);
  }

  /* if there is no method, just return */
  if( (xpa->method == NULL) || (*xpa->method == '\0') ){
    return(0);
  }

  /* look for the proxy=<true|false> string */
  if( mode ){
    strncpy(xmode, mode, SZ_LINE-1);
    xmode[SZ_LINE-1] = '\0';
    if( keyword(xmode, "proxy", tbuf, SZ_LINE) && istrue(tbuf) ){
      cmd="delproxy";
    }
  }

  /* open a connection to the name service */
  if( (ns=XPANSOpen(xpa, host, -1)) != NULL ){
    /* write the command to delete this xpa */
    snprintf(tbuf, SZ_LINE, "%s %s\n", cmd, xpa->method);
    XPAPuts(xpa, ns->fd, tbuf, stimeout);
    /* get result */
    if( XPAGets(xpa, ns->fd, tbuf, SZ_LINE, stimeout) >0 ){
      if( !strncmp(tbuf, "XPA$OK", 6) ){
	/* one less access point is using this name server */
	ns->nxpa -= 1;
	/* if there are no more access points using this xpans, close it */
	if( !ns->nxpa && !ns->nproxy ){
	  XPANSClose(xpa, ns);
	}
      }
      else{
	got = -1;
      }
    }
    else{
      got = -1;
    }
  }
  else
    got = -1;
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAVersionCheck
 *
 * Purpose:	check our version vs. xpans version
 *
 * Returns:	-1,0,1 for our<=>xpans
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAVersionCheck (char *serv, char *nsv)
#else
int XPAVersionCheck(serv, nsv)
     char *serv;
     char *nsv;
#endif
{
  int i;
  int ip1=0;
  int ip2=0;
  int v1=0;
  int v2=0;
  int got=0;
  int vsize=2;
  char s1[SZ_LINE];
  char s2[SZ_LINE];

  /* return if not checking version */
  if( vercheck <=0 )
    return(0);
  /* if either does not exist, its a mismatch */
  if( !word(serv, s1, &ip1) || !word(nsv, s2, &ip2) )
    return(1);
  /* if strings are equal, versions are equal */
  if( !strcasecmp(s1, s2) )
    return(0);

  /* format for version is maj.min.patch[be]beta */
  newdtable(".be");
  /* we check only the major and minor version for incompatibilities */
  for(i=0; i<vsize; i++){
    if( !word(serv, s1, &ip1) || !word(nsv, s2, &ip2) ){
      break;
    }
    v1 = atoi(s1);
    v2 = atoi(s2);
    if( v1 > v2 ){
      got = 1;
      break;
    }
    if( v1 < v2 ){
      got = -1;
      break;
    }
  }

  freedtable();
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAVersionWarn
 *
 * Purpose:	warn about mismatched versions
 *
 * Returns:	NONE
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void
XPAVersionWarn (char *serv, char *nsv)
#else
void XPAVersionWarn(serv, nsv)
     char *serv;
     char *nsv;
#endif
{
  /* return if not checking version */
  if( vercheck <=0 )
    return;

  /* output warning */
  fprintf(stderr,
	  "XPA$WARNING: version mismatch detected between XPA-enabled server (%s)\n", serv?serv:"unknown");
  fprintf(stderr, "and xpans (%s).", nsv?nsv:"unknown");
  fprintf(stderr, " You probably will get bad results.\n");
  fprintf(stderr, "Please consider updating XPA to match the XPA-enabled server you are running.\n");

  /* we did it */
  vercheck--;
}

/*
 *----------------------------------------------------------------------------
 *
 *
 *			Public Routines
 *
 *
 *----------------------------------------------------------------------------
 */

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAMethod
 *
 * Purpose:	return communication method type
 *
 * Returns:	XPA__INET, XPA_UNIX
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAMethod (char *method)
#else
int XPAMethod(method)
     char *method;
#endif
{
  char *s1;

  /* if no method is passed, we return the default method type */
  if( method == NULL ){
    if( mtype == 0 ){
      s1 = (char *)getenv("XPA_METHOD");
      if( s1 == NULL )
	mtype = XPA_INET;
      else if( !strcasecmp(s1, "inet") )
	mtype = XPA_INET;
      else if( !strcasecmp(s1, "localhost") ){
	mtype = XPA_INET;
	use_localhost = 1;
      }
      else if( !strcasecmp(s1, "unix") ){
#if HAVE_SYS_UN_H
	mtype = XPA_UNIX;
#else
        mtype = XPA_INET;
	use_localhost = 1;
#endif
      }
      else if( !strcasecmp(s1, "local") ){
#if HAVE_SYS_UN_H
	mtype = XPA_UNIX;
#else
        mtype = XPA_INET;
	use_localhost = 1;

#endif
      }
      else
	mtype = XPA_INET;
    }
    return(mtype);
  }
  /* otherwise, we analyze the input method to get the type */
  else{
    /* inet is ip:port, else unix filename */
    if( strchr(method, ':') != NULL )
      return(XPA_INET);
    else
#if HAVE_SYS_UN_H
      return(XPA_UNIX);
#else
      return(XPA_INET);
#endif
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSMethod
 *
 * Purpose:	return string containing name server method
 *
 * Returns:	name server method string
 *
 *----------------------------------------------------------------------------
 */

#ifdef ANSI_FUNC
char * 
XPANSMethod (char *host, int flag)
#else
char *XPANSMethod(host, flag)
     char *host;
     int flag;
#endif
{
  char *s, *t;
  char tbuf[SZ_LINE];
  int i;
  int ip;
  int port;
  unsigned int bip;
  unsigned short bport;

  switch( XPAMethod(host) ){
  case XPA_INET:
    if( (host != NULL) && (*host != '\0') )
      strncpy(nsmethod, host, SZ_LINE-1);
    else if( (s=(char *)getenv("XPA_NSINET")) != NULL )
      strncpy(nsmethod, s, SZ_LINE-1);
    else
      strncpy(nsmethod, XPA_NSINET, SZ_LINE-1);
    /* always null-terminate */
    nsmethod[SZ_LINE-1] = '\0';
    /* if flag, we want the XPA access ip and port, not the communication
       channel between xpa servers and the name service */
    if( flag ){
      if( (s=strrchr(nsmethod, ':')) != NULL ){
	/* this is where we will overwrite the port */
	t = s+1;
	/* get base port for default */
	XPAParseIpPort(nsmethod, &bip, &bport);
	newdtable(",");
	for(ip=0, i=0; i<=flag; i++){
	  if( !word(t, tbuf, &ip) ){
	    *tbuf = '\0';
	    break;
	  }
	}
	freedtable();
	if( *tbuf )
	  port = atoi(tbuf);
	else
	  port = bport + flag;
	snprintf(t, SZ_LINE, "%d", port);
      }
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    if( host != NULL )
      strncpy(nsmethod, host, SZ_LINE-1);
    else if( (s=(char *)getenv("XPA_NSUNIX")) != NULL )
      strncpy(nsmethod, s, SZ_LINE-1);
    else
      snprintf(nsmethod, SZ_LINE, "%s/%s", tmpdir, XPA_NSUNIX);
    /* always null-terminate */
    nsmethod[SZ_LINE-1] = '\0';
    /* if flag is set, we are getting the XPA access file, not the
       socket file and we have to change the name slightly */
    if( flag ){
      /* replace the ending, if possible */
      s = strrchr(nsmethod, '.');
      t = strrchr(nsmethod, '/');
      if( (s != NULL) && (s > t) )
	*s = '\0';
      snprintf(tbuf, SZ_LINE, ".xpa-%d", flag);
      strcat(nsmethod, tbuf);
    }
    break;
#endif
  default:
    if( (s=(char *)getenv("XPA_NSINET")) != NULL )
      strncpy(nsmethod, s, SZ_LINE-1);
    else
      strncpy(nsmethod, XPA_NSINET, SZ_LINE-1);
    /* always null-terminate */
    nsmethod[SZ_LINE-1] = '\0';
    break;
  }

  /* return the static method string */
  return(nsmethod);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAParseName
 *
 * Purpose:	split the xpaname into a class and name string
 *
 * Results:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void 
XPAParseName (char *xpaname, char *xclass, char *name, int len)
#else
void XPAParseName(xpaname, xclass, name, len)
     char *xpaname;
     char *xclass;
     char *name;
     int len;
#endif
{
  char *s;
  char *t;
  char *cptr=NULL;
  char *nptr=NULL;

  /* if nothing is passed to us, allow everything */
  if( (xpaname == NULL) || (*xpaname == '\0') ){
    strncpy(xclass, "*", len-1);
    strncpy(name, "*", len-1);
    return;
  }
    
  /* split the xpaname into class and name */
  s = xstrdup(xpaname);
  if( (t=(char *)strchr(s, ':')) != NULL ){
    cptr = s;
    *t = '\0';
    nptr = t+1;
  }
  else{
    nptr = s;
    cptr = "*";
  }
  if( *cptr == '\0' )
    cptr = "*";
  if( *nptr == '\0' )
    nptr = "*";

  strncpy(xclass, cptr, len-1);
  strncpy(name, nptr, len-1);
  xfree(s);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAParseIpPort
 *
 * Purpose:	split the host into ip and port
 *
 * Results:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAParseIpPort (char *host, unsigned int *ip, unsigned short *port)
#else
int XPAParseIpPort(host, ip, port)
     char *host;
     unsigned int *ip;
     unsigned short *port;
#endif
{
  char *s1, *s2, *s3, *p1, *p2;
  int got;

  /* make sure we have something to work with */
  if( (host == NULL) || (*host == '\0') )
    return(0);

  /* parse up the string and look for a port number (which is essential) */
  s1 = xstrdup(host);
  /* if we have a ",", null it out since what comes after is aux info */
  if( (p1 = (char *)strchr(s1, ',')) != NULL ){
    *p1 = '\0';
  }
  /* if we have a ":", we will null it out (so that what comes before is
     the host name) and bump past it to point to the port */
  if( (p1 = (char *)strchr(s1, ':')) == NULL ){
    /* there is no ":", so the whole string is the port */
    p1 = s1;
    s2 = NULL;
  } else {
    /* null out ':' and bump port pointer */
    *p1 = '\0';
    p1++;
    s2 = s1;
  }

  /* get port */
  p2 = NULL;
  if( p1 && !strcmp(p1, "$port") )
    *port = XPA_NSPORT;
  /* NB: port number might be followed by other stuff */
  else
    *port = (unsigned short)strtol(p1, &p2, 0);
  /* check for bad port number -- we lose */
  if( *port <=0 || (p1 == p2) || (p2 && (*p2 != '\0')) ){
    *ip = 0;
    *port = 0;
    got = 0;
    goto done;
  }

  /* get ip */
  if( s2 && *s2 ){
    /* see if this already is a hex address in network byte order */
    *ip = strtoul16(s2, &s3);
    if( *s3 == '\0' ){
      got = 1;
      goto done;
    }
  }
  /* not hex or proxy -- convert ip string to an ip address */
  if( (*ip = gethostip(s2)) == 0 ){
    *port = 0;
    got = 0;
  }
  else{
    got = 1;
  }

done:
  xfree(s1);
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAParseUnixSocket
 *
 * Purpose:	see if host is actually a unix socket file
 *
 * Results:	1 if its a socket, 0 otherwise
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAParseUnixSocket (char *host)
#else
int XPAParseUnixSocket(host)
     char *host;
#endif
{
  struct stat buf;

  /* see if its a file in the right directory */
  if( !strncmp(host, tmpdir, strlen(tmpdir)) &&
      !stat(host, &buf) ){
    return(1);
  }
  else{
    return(0);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAOK
 *
 * Purpose:	send an XPA OK message to the client
 *
 * Returns:	0 on success, -1 on error
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAOK (XPA xpa)
#else
int XPAOK(xpa)
     XPA xpa;
#endif
{
  int len;
  int status=0;
  char tbuf[SZ_LINE];

  /* make sure we have a valid struct */
  if( (xpa == NULL) || (xpa_cmdfd(xpa) <0) )
    return(-1);

  /* send message, if necessary */
  if( !(xpa_status(xpa) & XPA_STATUS_ACTIVE) || (xpa_ack(xpa) == 1) ){
    snprintf(tbuf, SZ_LINE, "%s XPA$OK (%s:%s %s)\n",
	     xpa_id(xpa), xpa_class(xpa), xpa_name(xpa), xpa_method(xpa));
    len = strlen(tbuf);
    if( XPAPuts(xpa, xpa_cmdfd(xpa), tbuf, stimeout) != len ){
      status = -1;
    }
  }

  /* flag that there was a message sent for this xpa */
  xpa->comm->message = 1;

  /* return status */
  return(status);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPATimestamp
 *
 * Purpose:	generate string with current date/time
 *
 * Returns:	time string (in static buffer)
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
char *
XPATimestamp(void)
#else
char *XPATimestamp()
#endif
{
  time_t t;
  struct tm *lt;

  *ctimebuf = '\0';
  if( etimestamp ){
    if( (t = time(NULL)) != (time_t)-1 ){
      if( (lt = localtime(&t)) != NULL ){
	snprintf(ctimebuf, SZ_LINE, " %02d/%02d/%d:%d:%d:%d",
		 lt->tm_mday, lt->tm_mon+1, lt->tm_year+1900,
		 lt->tm_hour, lt->tm_min, lt->tm_sec);
      }
    }
  }
  return ctimebuf;
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAError
 *
 * Purpose:	send an XPA error message to the client
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAError (XPA xpa, char *s)
#else
int XPAError(xpa, s)
     XPA xpa;
     char *s;
#endif
{
  int status=0;
  int ip=0;
  char tbuf[SZ_LINE];
  char *t;
  char *u;

  /* make sure we have a valid struct */
  if( (xpa == NULL) || (xpa_cmdfd(xpa) <0) )
    return(-1);

  /* send message, if necessary */
  if( !(xpa_status(xpa) & XPA_STATUS_ACTIVE) || (xpa_ack(xpa) == 1) ){
    t = xstrdup(s);
    /* get rid of CR in message -- we add one at the end */
    nowhite(t, t);
    if( !strncmp(t, "XPA$", 4) )
      word(t, tbuf, &ip);
    u = (char *)xcalloc(strlen(t)+SZ_LINE, sizeof(char));
    /* package up and write the message */
    snprintf(u, SZ_LINE, "%s XPA$ERROR %s (%s:%s %s%s)\n",
	     xpa_id(xpa),
	     &t[ip], xpa_class(xpa), xpa_name(xpa), xpa_method(xpa),
	     XPATimestamp());
    if( XPAPuts(xpa, xpa_cmdfd(xpa), u, stimeout) != (int)strlen(u) ){
      status = -1;
    }
    if( t )
      xfree(t);
    if( u )
      xfree(u);
  }

  /* flag that there was a message sent for this xpa */
  xpa->comm->message = 1;

  /* return status */
  return(status);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAMessage
 *
 * Purpose:	send an XPA message to the client
 *
 * Returns:	0 on success, -1 on error
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAMessage (XPA xpa, char *s)
#else
int XPAMessage(xpa, s)
     XPA xpa;
     char *s;
#endif
{
  int status=0;
  int ip=0;
  char tbuf[SZ_LINE];
  char *t;
  char *u;

  /* make sure we have a valid struct */
  if( (xpa == NULL) || (xpa_cmdfd(xpa) <0) )
    return(-1);

  /* send message, if necessary */
  if( !(xpa_status(xpa) & XPA_STATUS_ACTIVE) || (xpa_ack(xpa) == 1) ){
    t = xstrdup(s);
    /* get rid of CR in message -- we add one at the end */
    nowhite(t, t);
    if( !strncmp(t, "XPA$", 4) )
      word(t, tbuf, &ip);
    u = (char *)xcalloc(strlen(t)+SZ_LINE, sizeof(char));
    /* package up and write the message */
    snprintf(u, SZ_LINE, "%s XPA$MESSAGE %s (%s:%s %s%s)\n",
	     xpa_id(xpa),
	     &t[ip], xpa_class(xpa), xpa_name(xpa), xpa_method(xpa),
	     XPATimestamp());
    if( XPAPuts(xpa, xpa_cmdfd(xpa), u, stimeout) != (int)strlen(u) ){
      status = -1;
    }
    if( t )
      xfree(t);
    if( u )
      xfree(u);
  }

  /* flag that there was a message sent for this xpa */
  xpa->comm->message = 1;

  /* return status */
  return(status);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAArgvParamlist
 *
 * Purpose:	generate a paramlist string from an argv
 *
 * Results:	allocated paramlist string
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
char *
XPAArgvParamlist (int argc, char **argv, int start)
#else
char *XPAArgvParamlist(argc, argv, start)
     int argc;
     char **argv;
     int start;
#endif
{
  int plen;
  int i;
  char *paramlist;

  /* get length of paramlist */
  for(plen=0, i=start; i<argc; i++){
    plen += (strlen(argv[i])+1);
  }

  /* allocate enough space for it */
  if( (paramlist = (char *)xcalloc(plen+1, sizeof(char))) == NULL ){
    return(NULL);
  }

  /* gather up the paramlist */
  for(i=start; i<argc; i++){
    strcat(paramlist, argv[i]);
    strcat(paramlist, " ");
  }

  /* remove whitespace from beginning and end */
  nowhite(paramlist, paramlist);

  /* return paramlist */
  return(paramlist);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSLookup
 *
 * Purpose:	get name server matches
 *
 * Returns:	number of matches
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPANSLookup (XPA xpa, char *tname, char *ttype,
	     char ***xclasses, char ***names, char ***methods, char ***infos)
#else
int XPANSLookup(xpa, tname, ttype, xclasses, names, methods, infos)
     XPA xpa;
     char *tname;
     char *ttype;
     char ***xclasses;
     char ***names;
     char ***methods;
     char ***infos;
#endif
{
  unsigned short port;
  unsigned int ip;
  int own;
  int lp=0;
  int got=0;
  int nentry=100;
  char *xtype;
  char xclass[SZ_LINE];
  char name[SZ_LINE];
  char method[SZ_LINE];
  char info[SZ_LINE];
  char user[SZ_LINE];
  char type[SZ_LINE];
  char tbuf[SZ_LINE];
  char lbuf[SZ_LINE];
  NS ns;
  XPA txpa;

  /* do some initialization */
  XPAInitEnv();

  /* use "*" as default if no type was specified */
  if( (ttype == NULL) || (*ttype == '\0') )
    xtype = "*";
  /* type 'a' means we are only trying to access */
  else if( *ttype == 'a' )
    xtype = "*";
  else
    xtype = ttype;

  /* special case of name server */
  if( !strcmp(tname, XPANS_NAME) ){
    *xclasses  = (char **)xmalloc(sizeof(char *));
    *names     = (char **)xmalloc(sizeof(char *));
    *methods   = (char **)xmalloc(sizeof(char *));
    *infos     = (char **)xmalloc(sizeof(char *));
    (*xclasses)[0] = xstrdup(XPANS_CLASS);
    (*names)[0]   = xstrdup(XPANS_NAME);
    (*methods)[0]   = xstrdup(XPANSMethod(NULL, 1));
    (*infos)[0] = xstrdup(XPA_DEF_CLIENT_INFO);
    return(1);
  }

  /* special case of a string containing ip:port -- avoid trip to ns */
  if( XPAParseIpPort(tname, &ip, &port) ){
    *xclasses   = (char **)xmalloc(sizeof(char *));
    *names      = (char **)xmalloc(sizeof(char *));
    *methods    = (char **)xmalloc(sizeof(char *));
    *infos      = (char **)xmalloc(sizeof(char *));
    (*xclasses)[0] = xstrdup("?");
    (*names)[0]   = xstrdup("?");
    (*methods)[0] = xstrdup(tname);
    (*infos)[0] = xstrdup(XPA_DEF_CLIENT_INFO);
    return(1);
  }

  /* special case of a string containing unix socket -- avoid trip to ns */
  if( XPAParseUnixSocket(tname) ){
    *xclasses   = (char **)xmalloc(sizeof(char *));
    *names      = (char **)xmalloc(sizeof(char *));
    *methods    = (char **)xmalloc(sizeof(char *));
    *infos      = (char **)xmalloc(sizeof(char *));
    (*xclasses)[0] = xstrdup("?");
    (*names)[0]   = xstrdup("?");
    (*methods)[0] = xstrdup(tname);
    (*infos)[0] = xstrdup(XPA_DEF_CLIENT_INFO);
    return(1);
  }

  /* pre-allocate the various arrays to an absurd number */
  *xclasses = (char **)xmalloc(nentry * sizeof(char *));
  *names    = (char **)xmalloc(nentry * sizeof(char *));
  *methods  = (char **)xmalloc(nentry * sizeof(char *));
  *infos    = (char **)xmalloc(nentry * sizeof(char *));

  /* open a connection to the name service */
  if( (ns=XPANSOpen(xpa, NULL, 0)) != NULL ){
    while( word(tname, lbuf, &lp) ){
      XPAParseName(lbuf, xclass, name, SZ_LINE);
      /* write the command to add this xpa */
      snprintf(tbuf, SZ_LINE, "lookup %s:%s %s %s\n",
	       xclass, name, xtype, nsusers);
      FPRINTF((stderr, "%sXPANSLookup: sending command %s", _sp, tbuf));
      XPAPuts(xpa, ns->fd, tbuf, stimeout);
      /* read matches from the name server */
      while( 1 ){
	if( XPAGets(xpa, ns->fd, tbuf, SZ_LINE, stimeout) <=0 ){
	  FPRINTF((stderr, "%sXPANSLookup: unexpected EOF from xpans\n", _sp));
	  break;
	}
	FPRINTF((stderr, "%sXPANSLookup: receiving %s", _sp, tbuf));
	/* XPA$<result> signals end of input */
	if( !strncmp(tbuf, "XPA$", 4) )
	  break;
	/* otherwise scan next line */
	if( sscanf(tbuf, "%s %s %s %s %s %s\n", 
		   xclass, name, type, method, user, info) != EOF ){
	  /* make sure this entry is not in the current process
	     (i.e., we can't ever xpa ourselves!) */
	  for(own=0, txpa=xpahead; txpa!=NULL; txpa=txpa->next){
	    if( !strcmp(txpa->xclass, xclass) && !strcmp(txpa->name, name) &&
		!strcmp(txpa->method, method)				   ){
	      own = 1;
	      break;
	    }
	  }
	  /* if this xpa is in the current process, skip it */
	  if( own )
	    continue;
	  /* make sure we have enough space */
	  if( got >= nentry ){
	    nentry *= 2;
	    *xclasses = (char **)xrealloc(*xclasses, nentry * sizeof(char *));
	    *names    = (char **)xrealloc(*names, nentry * sizeof(char *));
	    *methods  = (char **)xrealloc(*methods, nentry * sizeof(char *));
	    *infos    = (char **)xrealloc(*infos, nentry * sizeof(char *));
	  }
	  /* add this entry to the list */
	  (*xclasses)[got] = xstrdup(xclass);
	  (*names)[got] = xstrdup(name);
	  (*methods)[got] = xstrdup(method);
	  (*infos)[got] = xstrdup(info);
	  got++;
	}
      }
    }
    /* if we did not add to an xpa record, close up here */
    if( xpa == NULL ){
      XPANSClose(NULL, ns);
    }
  }
  /* reallocate the exact number of buffers we have */
  if( got > 0 ){
    *xclasses = (char **)xrealloc(*xclasses, got * sizeof(char *));
    *names    = (char **)xrealloc(*names, got * sizeof(char *));
    *methods  = (char **)xrealloc(*methods, got * sizeof(char *));
    *infos    = (char **)xrealloc(*infos, got * sizeof(char *));
  }
  else{
    xfree(*xclasses);
    xfree(*names);
    xfree(*methods);
    xfree(*infos);
  }
  FPRINTF((stderr, "%sXPANSLookup: found %d entries\n", _sp, got));
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSClose
 *
 * Purpose:	close connection to the name service
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPANSClose (XPA xpa, NS ns)
#else
int XPANSClose(xpa, ns)
     XPA xpa;
     NS ns;
#endif
{
  NS cur;
  XPAComm comm, tcomm;

  if( !ns )
    return(-1);

  /* remove from xpa list */
  if( xpa ){
    if( xpa->nshead ){
      if( xpa->nshead == ns ){
	xpa->nshead = ns->next;
      }
      else{
	for(cur=xpa->nshead; cur!=NULL; cur=cur->next){
	  if( cur->next == ns ){
	    cur->next = ns->next;
	    break;
	  }
	}
      }
    }
    /* close comms associated with this ns */
    for(comm=xpa->commhead; comm!=NULL; ){
      tcomm = comm->next;
      if( comm->ns == ns ){
	CommFree(xpa, comm, 0);
      }
      comm = tcomm;
    }
  }

  /* close socket */
  if( ns->fd >=0 ){
    xclose(ns->fd);
  }
  /* free up space */
  if( ns->method) xfree(ns->method);
  if( ns->host )  xfree(ns->host);
  if( ns->name )  xfree(ns->name);
  if( ns )        xfree(ns);
  return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANSKeepAlive
 *
 * Purpose:	send a 1-byte keep-alive packet to each name server
 *
 * Returns:	-1 on error, else 1
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPANSKeepAlive (XPA xpa, int type)
#else
  int XPANSKeepAlive(xpa, type)
     XPA xpa;
     int type;
#endif
{
  NS ns;
  int got=0;

  /* sanity check */
  if( !xpa )
    return(-1);

  /* use default type, if none specified */
  if( !type )
    type=DEF_KA_TYPE;

  /* send keep-alive to deserving xpans instances */
  for(ns=xpa->nshead; ns!=NULL; ns=ns->next){ 
    if( ((type&1) && (ns->nxpa>0)) || ((type&2) && (ns->nproxy>0)) ){
      FPRINTF((stderr, "%sXPANSKeepAlive: sending ka to %d\n", _sp, ns->fd));
#if USE_KA_OOB
      /* send as out of band data to avoid mixing with normal data stream */
      got = send(ns->fd, "\n", 1, MSG_OOB);
#else
      /* cisco routers can clear the URG flag by default, so use in-band */
      got = send(ns->fd, "\n", 1, 0);
#endif
    }
  }
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPANew
 *
 * Purpose:	add a new xpa name to a process to allow external
 * 		process to read/write data associated with this name.
 *
 * If the send callback is defined, it can send to an external process.
 * If the receive callback is defined, it can receive from an external process.
 *
 * Returns: xpa handle associated with this class.name or NULL
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
XPA 
XPANew (char *xclass, char *name, char *help,
	SendCb send_callback, void *send_data, char *send_mode,
	ReceiveCb rec_callback, void *rec_data, char *rec_mode)
#else
XPA XPANew(xclass, name, help,
	   send_callback, send_data, send_mode,
	   rec_callback, rec_data, rec_mode)
     char *xclass;
     char *name;
     char *help;
     SendCb send_callback;
     void *send_data;
     char *send_mode;
     ReceiveCb rec_callback;
     void *rec_data;
     char *rec_mode;
#endif
{
  unsigned short port;
  unsigned int ip;
  int got;
  int oum;
  int keep_alive=1;
  int reuse_addr=1;
  char tbuf[SZ_LINE];
  char tbuf2[SZ_LINE];
  char tfile[SZ_LINE];
  char *tfptr;
  XPA xpa;
  socklen_t slen = sizeof(struct sockaddr_in);
  struct sockaddr_in sock_in;
#if HAVE_SYS_UN_H
  struct sockaddr_un sock_un;
#endif

  /* do some initialization */
  XPAInitEnv();
  /* init the list of reserved commands */
  XPAInitReserved();

  /* we need a name, but no ":" allowed in the name */
  if( (name == NULL) || (*name == '\0') || strchr(name, ':') )
    return(NULL);
  
  /* limit the size of the xclass and name designation */
  if( xclass && *xclass && (strlen(xclass) > XPA_NAMELEN) ){
    if( verbosity )
      fprintf(stderr, "XPA$ERROR: class designator too long\n");
    return(NULL);
  }
  if( strlen(name) > XPA_NAMELEN ){
    if( verbosity )
      fprintf(stderr, "XPA$ERROR: name designator too long\n");
    return(NULL);
  }

  /* we need either a send or a receive or both */
  if( (send_callback == NULL) && (rec_callback == NULL ) ){
    if( verbosity )
      fprintf(stderr, "XPA$ERROR: requires send and/or receive callback\n");
    return(NULL);
  }

  /* allocate xpa struct */
  if( (xpa = (XPA)xcalloc(1, sizeof(XPARec))) == NULL )
    return(NULL);

  /* fill in the blanks */
  xpa->version = xstrdup(XPA_VERSION);
  xpa->type  = (char *)xcalloc(10, sizeof(char));
  if( xclass && *xclass )
    xpa->xclass = xstrdup(xclass);
  else
    xpa->xclass = xstrdup("*");
  xpa->name  = xstrdup(name);
  xpa->help  = xstrdup(help);
  /* set the value of the server big-endian-ness */
  xpa->sendian = XPAEndian() ? xstrdup("big") : xstrdup("little");

  /* fill in send information */
  if( send_callback != NULL ){
    xpa->send_callback = send_callback;
    xpa->send_data = send_data;
    strcat(xpa->type, "g");
    /* process the mode string */
    xpa->send_mode = XPA_DEF_MODE_SEND;
    XPAMode(send_mode, &(xpa->send_mode), "freebuf", XPA_MODE_FREEBUF,1);
    XPAMode(send_mode, &(xpa->send_mode), "acl", XPA_MODE_ACL, 1);
  }

  /* fill in receive information */
  if( rec_callback != NULL ){
    xpa->receive_callback = rec_callback;
    xpa->receive_data = rec_data;
    strcat(xpa->type, "s");
    /* process the mode string */
    xpa->receive_mode = XPA_DEF_MODE_REC;
    XPAMode(rec_mode, &(xpa->receive_mode), "buf", XPA_MODE_BUF, 1);
    XPAMode(rec_mode, &(xpa->receive_mode), "fillbuf", XPA_MODE_FILLBUF, 1);
    XPAMode(rec_mode, &(xpa->receive_mode), "freebuf", XPA_MODE_FREEBUF, 1);
    XPAMode(rec_mode, &(xpa->receive_mode), "acl", XPA_MODE_ACL, 1);
  }

  /* set up communication method */
  switch(mtype){
  case XPA_INET:
    /* open a socket and fill in socket information */
    if( (xpa->fd = xsocket(AF_INET, SOCK_STREAM, 0)) < 0 )
      goto error;
    setsockopt(xpa->fd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    setsockopt(xpa->fd, SOL_SOCKET, SO_REUSEADDR,
	       (char *)&reuse_addr, sizeof(reuse_addr));
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_in.sin_family = AF_INET;
    /* localhost only */
    if( use_localhost )
      sock_in.sin_addr.s_addr = htonl(gethostip("$localhost"));
    /* any address will do */
    else
      sock_in.sin_addr.s_addr = htonl(INADDR_ANY);
    /* handle special case of xpans port */
    if( !strcmp(xpa->name, XPANS_NAME) ){
      XPAParseIpPort(XPANSMethod(NULL, 1), &ip, &port);
      sock_in.sin_port = htons(port);
    }
    else{
      sock_in.sin_port = htons(XPAPort(xpa));
    }
    /* bind to the ip:port */
    if( xbind(xpa->fd, (struct sockaddr *)&sock_in, sizeof(sock_in)) < 0 )
      goto error;
    /* we now can determine which port the system assigned */
    if( getsockname(xpa->fd, (struct sockaddr *)&sock_in, &slen) < 0 )
      goto error;
    else{
      /* ip:port is the method */
      gethost(tbuf2, SZ_LINE);
      snprintf(tbuf, SZ_LINE, "%x:%d",
	       gethostip(tbuf2), ntohs(sock_in.sin_port));
      xpa->method = xstrdup(tbuf);
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    /* open a socket and fill in socket information */
    if( (xpa->fd = xsocket(AF_UNIX, SOCK_STREAM, 0)) < 0 )
      goto error;
    setsockopt(xpa->fd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    setsockopt(xpa->fd, SOL_SOCKET, SO_REUSEADDR,
	       (char *)&reuse_addr, sizeof(reuse_addr));
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_un.sun_family = AF_UNIX;
    /* handle special case of xpans */
    if( !strcmp(xpa->name, XPANS_NAME) ){
      strcpy(tbuf, XPANSMethod(NULL, 1));
    }
    else{
      /* get filename part, composed of class and name and unique id */
      snprintf(tfile, SZ_LINE, "%s_%s.%d",
	       xpa->xclass, xpa->name, (int)getpid());
      /* change "/" to "_" for filename */
      for(tfptr = tfile; *tfptr != '\0'; tfptr++){
	if( *tfptr == '/' )
	  *tfptr = '_';
      }
      /* create full pathname */
      snprintf(tbuf, SZ_LINE, "%s/%s", tmpdir, tfile);
    }
    /* delete old copy */
    unlink (tbuf);
    strcpy(sock_un.sun_path, tbuf);
    /* unset umask so that everyone can read and write */
    oum = umask(0);
    /* bind to the file */
    got = xbind(xpa->fd, (struct sockaddr *)&sock_un, sizeof(sock_un));
    /* reset umask */
    umask(oum);
    /* now check for bind error */
    if( got < 0 )
      goto error;
    /* path is the method */
    xpa->method = xstrdup(tbuf);
    break;
#endif
  default:
    goto error;
  }

  /* listen for connections */
  if( listen(xpa->fd, XPA_MAXLISTEN) < 0 )
    goto error;

  /* make sure we close on exec */
  xfcntl(xpa->fd, F_SETFD, FD_CLOEXEC);

  /* add this xpa to end of list of xpas */
  XPAListAdd(&xpahead, xpa);

  /* publish this entry to the world */
  if( nsregister )
    XPANSAdd(xpa, NULL, NULL);

  /* make it active */
  XPAActive(xpa, NULL, 1);

#if NO_AUTOMATIC_HAVE_ATEXIT
  /* register XPA atexit funtion */
  if( !atexitinit ){
    atexit(_XPAAtExit);
    atexitinit = getpid();
  }
#endif

  /* return good news */
  return(xpa);

error:
  if( verbosity ){
    perror("XPANew");
  }
  _XPAFree(xpa);
  return(NULL);
}

/*
 *---------------------------------------------------------------------------
 *
 * Routine: 	XPAFree
 *
 * Purpose: 	free up alloc'ed memory in the XPA record structure
 *
 * Results:	0 on success, -1 for failure
 *
 *---------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPAFree (XPA xpa)
#else
int XPAFree(xpa)
     XPA xpa;
#endif
{
  /* if status is active, just flag eventual need to free and exit */
  if( _XPAValid(xpahead, xpa, XPA_ACLS) ){
    if( xpa_status(xpa) & XPA_STATUS_ACTIVE ){
      xpa->status |= XPA_STATUS_FREE;
      FPRINTF((stderr, "%sXPAFree: marking xpa struct for later free'ing\n",
	       _sp));
      return(0);
    }
    else{
      /* call the primitive routine */
      FPRINTF((stderr, "%sXPAFree: freeing xpa struct\n", _sp));
      return(_XPAFree(xpa));
    }
  }
  else{
    return(-1);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAInfoNew
 *
 * Purpose:	add a new xpa name to a process to allow external
 * 		process to send info messages
 *

 * Returns: xpa handle associated with this class.name or NULL
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
XPA 
XPAInfoNew (char *xclass, char *name,
	    InfoCb info_callback, void *info_data, char *info_mode)
#else
XPA XPAInfoNew(xclass, name, info_callback, info_data, info_mode)
     char *xclass;
     char *name;
     InfoCb info_callback;
     void *info_data;
     char *info_mode;
#endif
{
  int got;
  int oum;
  int keep_alive=1;
  int reuse_addr=1;
  char tbuf[SZ_LINE];
  char tbuf2[SZ_LINE];
  char tfile[SZ_LINE];
  char *tfptr;
  XPA xpa;
  socklen_t slen = sizeof(struct sockaddr_in);
  struct sockaddr_in sock_in;
#if HAVE_SYS_UN_H
  struct sockaddr_un sock_un;
#endif

  /* do some initialization */
  XPAInitEnv();
  /* init the list of reserved commands */
  XPAInitReserved();

  /* we need a name, but no ":" allowed in the name */
  if( (name == NULL) || (*name == '\0') || strchr(name, ':') )
    return(NULL);
  
  /* we need an info callback */
  if( info_callback == NULL ){
    if( verbosity ){
      fprintf(stderr, "XPA$ERROR: requires info callback\n");
    }
    return(NULL);
  }

  /* allocate xpa struct */
  if( (xpa = (XPA)xcalloc(1, sizeof(XPARec))) == NULL )
    return(NULL);

  xpa->version = xstrdup(XPA_VERSION);
  xpa->type  = (char *)xcalloc(10, sizeof(char));
  /* fill in the blanks */
  if( xclass != NULL )
    xpa->xclass = xstrdup(xclass);
  else
    xpa->xclass = xstrdup("*");
  xpa->name  = xstrdup(name);

  /* fill in send information */
  xpa->info_callback = info_callback;
  xpa->info_data = info_data;
  strcat(xpa->type, "i");
  /* process the mode string */
  xpa->info_mode = XPA_DEF_MODE_INFO;
  XPAMode(info_mode, &(xpa->info_mode), "acl", XPA_MODE_ACL, 1);

  /* set up communication method */
  switch(mtype){
  case XPA_INET:
    /* open a socket and fill in socket information */
    if( (xpa->fd = xsocket(AF_INET, SOCK_STREAM, 0)) < 0 )
      goto error;
    setsockopt(xpa->fd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    setsockopt(xpa->fd, SOL_SOCKET, SO_REUSEADDR,
	       (char *)&reuse_addr, sizeof(reuse_addr));
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_in.sin_family = AF_INET;
    /* localhost only */
    if( use_localhost )
      sock_in.sin_addr.s_addr = htonl(gethostip("$localhost"));
    /* any address will do */
    else
      sock_in.sin_addr.s_addr = htonl(INADDR_ANY);
    sock_in.sin_port = htons(XPAPort(xpa));
    /* bind to the ip:port */
    if( xbind(xpa->fd, (struct sockaddr *)&sock_in, sizeof(sock_in)) < 0 )
      goto error;
    /* we now can determine which port the system assigned */
    if( getsockname(xpa->fd, (struct sockaddr *)&sock_in, &slen) < 0 )
      goto error;
    else{
      /* ip:port is the method */
      gethost(tbuf2, SZ_LINE);
      snprintf(tbuf, SZ_LINE, "%x:%d",
	       gethostip(tbuf2), ntohs(sock_in.sin_port));
      xpa->method = xstrdup(tbuf);
    }
    break;
#if HAVE_SYS_UN_H
  case XPA_UNIX:
    /* get filename part, composed of class and name and unique id */
    snprintf(tfile, SZ_LINE, "%s_%s.%d",
	     xpa->xclass, xpa->name, (int)getpid());
    /* change "/" to "_" for filename */
    for(tfptr = tfile; *tfptr != '\0'; tfptr++){
      if( *tfptr == '/' )
	*tfptr = '_';
    }
    /* create full pathname */
    snprintf(tbuf, SZ_LINE, "%s/%s", tmpdir, tfile);
    /* delete old copy */
    unlink (tbuf);
    /* open a socket and fill in socket information */
    if( (xpa->fd = xsocket(AF_UNIX, SOCK_STREAM, 0)) < 0 )
      goto error;
    setsockopt(xpa->fd, SOL_SOCKET, SO_KEEPALIVE,
	       (char *)&keep_alive, sizeof(keep_alive));
    memset((char *)&sock_in, 0, sizeof(sock_in));
    sock_un.sun_family = AF_UNIX;
    strcpy(sock_un.sun_path, tbuf);
    /* unset umask so that everyone can read and write */
    oum = umask(0);
    /* bind to the file */
    got = xbind(xpa->fd, (struct sockaddr *)&sock_un, sizeof(sock_un));
    /* reset umask */
    umask(oum);
    /* now check for bind error */
    if( got < 0 )
      goto error;
    /* path is the method */
    xpa->method = xstrdup(tbuf);
    break;
#endif
  default:
    goto error;
  }

  /* listen for connections */
  if( listen(xpa->fd, XPA_MAXLISTEN) < 0 )
    goto error;

  /* make sure we close on exec */
  xfcntl(xpa->fd, F_SETFD, FD_CLOEXEC);

  /* add this xpa to end of list of xpas */
  XPAListAdd(&xpahead, xpa);

  /* publish this entry to the world */
  if( nsregister )
    XPANSAdd(xpa, NULL, NULL);

  /* make it active */
  XPAActive(xpa, NULL, 1);

#if NO_AUTOMATIC_HAVE_ATEXIT
  /* register XPA atexit funtion */
  if( !atexitinit ){
    atexit(_XPAAtExit);
    atexitinit = getpid();
  }
#endif

  /* return good news */
  return(xpa);

error:
  XPAFree(xpa);
  return(NULL);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAPoll
 *
 * Purpose:	non-blocking handling of XPA access points
 *		timeout in millisecs, but if negative, no timeout is used
 *
 * Returns:	number of requests processed (if maxreq >=0)
 *		number of requests pending   (if maxreq <0)
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAPoll (int msec, int maxreq)
#else
int XPAPoll(msec, maxreq)
     int msec;
     int maxreq;
#endif
{
  int sgot;
  int got=0;
  fd_set readfds;
  struct timeval tv;
  struct timeval *tvp;

again:
  if( msec >= 0 ){
    tv.tv_sec = msec / 1000;
    tv.tv_usec = (msec % 1000) * 1000;
    tvp = &tv;
  }
  /* negative value means just block and wait */
  else{
    tvp = NULL;
  }
  FD_ZERO(&readfds);
  if( XPAAddSelect(NULL, &readfds) ){
    sgot = xselect(swidth, &readfds, NULL, NULL, tvp);
    /* error -- what should we do? */
    if( sgot < 0 ){
      if( xerrno == EINTR )
	goto again;
      if( verbosity ){
	perror("XPAPoll() select");
      }
      exit(1);
    }
    /* timeout -- just return */
    else if( sgot == 0 )
      ;
    /* finally ... something to do */
    else{
      /* if maxreq < 0, just return how many are ready */
      if( maxreq < 0 )
	return(sgot);
      else
	got = XPAProcessSelect(&readfds, maxreq);
    }
  }
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAMainLoop
 *
 * Purpose:	non-X programs event loop for handling XPA
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAMainLoop (void)
#else
int XPAMainLoop()
#endif
{
  int sgot;
  int got=0;
  fd_set readfds;


  FD_ZERO(&readfds);
  while( XPAAddSelect(NULL, &readfds) ){
    FPRINTF((stderr, "%sXPAMainLoop: waiting for select() ...\n", _sp));
    sgot = xselect(swidth, &readfds, NULL, NULL, NULL);
    FPRINTF((stderr, "%sXPAMainLoop: select() returned: %d\n", _sp, sgot));
    /* error -- what should we do? */
    if( sgot < 0 ){
      if( xerrno == EINTR ){
	FD_ZERO(&readfds);
	continue;
      }
      if( verbosity ){
	perror("XPAMainLoop() select");
      }
      exit(1);
    }
    /* can't happen, since we have no timeout */
    else if( sgot == 0 )
      ;
    /* finally ... something to do */
    else
      got += XPAProcessSelect(&readfds, 0);
    FD_ZERO(&readfds);
  }
  return(got);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASleep
 *
 * Purpose:	sleep for specified milliseconds
 *
 * Returns:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void 
XPASleep (int msec)
#else
void XPASleep(msec)
     int msec;
#endif
{
  struct timeval tv;

  if( msec > 0 ){
    tv.tv_sec = msec / 1000;
    tv.tv_usec = (msec % 1000) * 1000;
    xselect(1, NULL, NULL, NULL, &tv);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPAListHead
 *
 * Purpose:	semi-public routine to return the head of the xpa list
 *
 * Results:	XPA list pointer on success
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int
XPAMtype (void)
#else
int XPAMtype()
#endif
{
  return(mtype);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPATmpdir
 *
 * Purpose:	semi-public routine to return the tmpdir value
 *
 * Results:	tmpdir
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
char *
XPATmpdir (void)
#else
char * XPATmpdir()
#endif
{
  return(tmpdir);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASetBuf
 *
 * Purpose:	set the return buffer in a server send callback
 *		(use mainly by tcl to transfer tcl buffers to C)
 *
 * Returns:	0 on success, -1 on failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPASetBuf (XPA xpa, char *buf, int len, int xcopy)
#else
int XPASetBuf(xpa, buf, len, xcopy)
     XPA xpa;
     char *buf;
     int len;
     int xcopy;
#endif
{
  /* sanity check */
  if( !xpa || !xpa->comm )
    return(-1);
  /* xcopy >0 => make a copy of the data, else just assign */
  if( xcopy ){
    xpa->comm->len = len;
    if( (xpa->comm->buf = (char *)xmalloc(len)) == NULL )
      return(-1);
    memcpy(xpa->comm->buf, buf, len);
  }
  else{
    xpa->comm->len = len;
    xpa->comm->buf = buf;
  }
  return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPASetBuf
 *
 * Purpose:	set the return buffer in a server send callback
 *		(use mainly by tcl to transfer tcl buffers to C)
 *
 * Returns:	0 on success, -1 on failure
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
int 
XPASetFree (XPA xpa, MyFree myfree, void *myfree_ptr)
#else
  int XPASetFree(xpa, myfree, myfree_ptr)
     XPA xpa;
     MyFree myfree;
     void *myfree_ptr;
#endif
{
  /* sanity check */
  if( !xpa || !xpa->comm ) return(-1);
  /* set the free routine and data pointer */
  xpa->comm->myfree = myfree;
  xpa->comm->myfree_ptr = myfree_ptr;
  return(0);
}

/*
 *----------------------------------------------------------------------------
 *
 * Routine:	XPACleanup
 *
 * Purpose:	make valgrind happy by freeing memory 
 *
 * Results:	none
 *
 *----------------------------------------------------------------------------
 */
#ifdef ANSI_FUNC
void XPACleanup (void)
#else
void XPACleanup ()
#endif
{
  XPAFreeReserved();
  XPAAclFree();
  if( tmpdir ){
    xfree(tmpdir);
    tmpdir = NULL;
  }
}

#ifdef ANSI_FUNC
void XPASaveJmp(void *env)
#else
void XPASaveJmp(env)
     void *env;
#endif
{
#if HAVE_SETJMP_H
  xalloc_savejmp((jmp_buf *)env);
#else
  return;
#endif
}
