ftp.nice.ch/pub/next/connectivity/infosystems/Archie.2.18.s.tar.gz#/Archie/prospero.subproj/dirsend.c

This is dirsend.c in view mode; [Download] [Up]

/*
 * Copyright (c) 1989, 1990, 1991 by the University of Washington
 *
 * For copying and distribution information, please see the file
 * <copyright.h>.
 *
 * xarchie v2.0 - (gf)     - Sync with archie-1.4.1
 * v1.2.3 - 11/04/91 (bcn) - removed host comparison and replaced with check
 			     for connection id (undoes effect of v1.2.2.).
 * v1.2.2 - 11/02/91 (gf)  - removed extra inet_ntoa() calls and stuff for
			     multi-interface nets (lmjm@doc.imperial.ac.uk)
 * v1.2.1 - 10/20/91 (gf)  - asynch implementation
 * v1.2.0 - 09/17/91 (bpk) - added BULL & USG stuff, thanks to Jim Sillas
 * v1.1.2 - 08/30/91 (bpk) - added VMS support
 * v1.1.1 - 08/29/91 (bcn) - changed backoff handling
 * v1.1.0 - 08/13/91 (gf)  - added XArchie status calls
 *
 * gf: 20 Oct 1991:
 *  Broken into pieces so that under X dirsend() doesn't block in select()
 *  but rather uses Xt calls to allow continued event processing. If
 *  XARCHIE is not defined, can still be used since processEvent() will
 *  use select() in this case.
 *
 * bpk: For archie client v1.3.2:
 * If you're going to hack on this, I'd suggest using unifdef with -UCUTCP
 * and possibly -UVMS, for your working copy.  When you've got your changes
 * done, come back and add them into this main file.  It's getting pretty
 * nasty down there.
 */

#include <copyright.h>
#include <stdio.h>
#include <errno.h>

/* Status stuff */
#include "query.h"
static StatusMsgPtr msg;

/*
 * Complaints or suggestions regarding the portability or lack thereof
 * of these includes and defines should be directed to Brendan Kehoe,
 * brendan@cs.widener.edu.
 */
#ifdef VMS
# ifdef WOLLONGONG
#  include "twg$tcp:[netdist.include]netdb.h"
# else /* not Wollongong */
#  ifdef UCX
#   include <netdb.h>
#  else /* Multinet */
#   include "multinet_root:[multinet.include]netdb.h"
#  endif
# endif
# include <vms.h>
#else /* not VMS */
# include <sys/types.h> /* this may/will define FD_SET etc */
# ifdef u3b2
#  include <sys/inet.h> /* THIS does FD_SET etc on AT&T 3b2s.  */
# endif /* u3b2 */
# ifdef PCNFS
#  include <tklib.h>
#  include <tk_errno.h>
#  include <sys/nfs_time.h>
# endif
# include <pmachine.h>
# if defined(NEED_TIME_H) && !defined(AUX)
#  include <time.h>
# else
#  include <sys/time.h>
# endif
# ifdef WANT_BOTH_TIME
#  include <sys/time.h>
# endif
# ifdef NEED_STRING_H
#  include <string.h>
# else
#  include <strings.h>
# endif
# ifdef CUTCP
#  include <msdos/cutcp.h>
#  include <msdos/netevent.h>
#  include <msdos/hostform.h>
# else /* not CUTCP */
#  include <netdb.h>
#  include <sys/socket.h>
# endif
# ifdef NEED_SELECT_H
#  include <sys/select.h>
# endif /* NEED_SELECT_H */
# ifndef IN_H
#  include <netinet/in.h>
#  define IN_H
# endif
# if !defined(hpux) && !defined(PCNFS)
#  include <arpa/inet.h>
# endif
#endif /* !VMS */

/* Interactive UNIX keeps some of the socket definitions in funny places.  */
#ifdef ISC
# include <net/errno.h>
#endif /* ISC */
/* PC-NFS Toolkit 4.0 keeps important forward definitions here. */
#ifdef PCNFS
# include <in_addr.h>
#endif

#include <pfs.h>
#include <pprot.h>
#include <pcompat.h>
#include <perrno.h>

/* gf: Removed SUN_GNU_FIX stuff since inet_ntoa.c is now included. */

static int notprived = 0;
#ifndef MSDOS
extern int errno;
#endif
extern int perrno;
#ifdef NeXT
extern int pfs_debug;
/* The fd for the read end of the pipe that NeXTArchie uses to signal
	that the user has aborted the select() system call.  It is set by the
	QueryThreadProc() by calling SetThreadAbortFD(). */
extern int threadAbortFD;
#endif
extern int pfs_disable_flag;

char	*nlsindex();

#define max(X, Y)  ((X) > (Y) ? (X) : (Y))

static int		dir_udp_port = 0;	/* Remote UDP port number */

#ifdef CUTCP
# define	NS_TIMEOUT	15
#endif

static unsigned short	next_conn_id = 0;

/* Always needed externally */
int client_dirsrv_timeout = CLIENT_DIRSRV_TIMEOUT;
int client_dirsrv_retry = CLIENT_DIRSRV_RETRY; 
int rdgram_priority = 0;			/* gf: was in rdgram.h */

/* These were parameters to dirsend() */
static PTEXT pkt;
static char *hostname;
static struct sockaddr_in *hostaddr;

/* These were locals in dirsend(). Note that the initializations here
 * are really meaningless since we have to redo them for each call to
 * dirsend() since they were formerly automatically initialized.
 */
static PTEXT		first = NULL;	/* First returned packet	 */
static PTEXT		next;		/* The one we are waiting for 	 */
static PTEXT		vtmp;           /* For reorganizing linked list  */
static PTEXT		comp_thru;	/* We have all packets though    */
static int		lp = -1;	/* Opened UDP port	         */
static int maxFD;	/* The maximum fd in readfds */
static int		hdr_len;	/* Header Length                 */
static int		nd_pkts;	/* Number of packets we want     */
static int		no_pkts;	/* Number of packets we have     */
static int		pkt_cid;        /* Packet connection identifier  */
static unsigned short	this_conn_id;	/* Connection ID we are using    */
static unsigned short	recvd_thru;	/* Received through              */
static short		priority;	/* Priority for request          */
static short		one = 0;	/* Pointer to value 1            */
static short		zero = 0;	/* Pointer to value 0		 */
static char		*seqtxt;	/* Pointer to text w/ sequence # */
static struct sockaddr_in  us;		/* Our address                   */
static struct sockaddr_in  to;		/* Address to send query	 */
static struct sockaddr_in  from;	/* Reply received from		 */
static int		from_sz;	/* Size of from structure	 */
static struct hostent	*host;		/* Host info from gethostbyname  */
static long		newhostaddr;    /* New host address from *host   */
static int		req_udp_port=0; /* Requested port (optional)     */
static char		*openparen;	/* Delimits port in name         */
static char		hostnoport[500];/* Host name without port        */
static int		ns;		/* Number of bytes actually sent */
static int		nr;		/* Number of bytes received      */
static SELECTARG	readfds;	/* Used for select		 */
static int		tmp;
static char		*ctlptr;	/* Pointer to control field      */
static short		stmp;		/* Temp short for conversions    */
static int		backoff;	/* Server requested backoff      */
static unsigned char	rdflag11;	/* First byte of flags (bit vect)*/
static unsigned char	rdflag12;	/* Second byte of flags (int)    */
static int		scpflag = 0;	/* Set if any sequencd cont pkts */
static int		ackpend = 0;    /* Acknowledgement pending      */
static int		gaps = 0;	/* Gaps present in recvd pkts   */
static struct timeval	timeout;	/* Time to wait for response    */
static struct timeval	ackwait;	/* Time to wait before acking   */
static struct timeval	gapwait;	/* Time to wait b4 filling gaps */
static struct timeval	*selwait;	/* Time to wait for select      */
static int		retries;	/* was = client_dirsrv_retry    */
char   to_hostname[512];		/* lmjm: saves inet_ntoa() str  */

/* These are added so dirsend() "blocks" properly */
static PTEXT dirsendReturn;
static int dirsendDone;

/* And here are the values for dirsendDone */
#define DSRET_DONE		1
#define DSRET_SEND_ERROR	-1
#define DSRET_RECV_ERROR	-2
#define DSRET_SELECT_ERROR	-3
#define DSRET_TIMEOUT		-4
#define DSRET_ABORTED		-5

/* New procedures to break up dirsend() */
static int initDirsend();
static void retryDirsend(), keepWaitingDirsend();
static void timeoutProc();
static void readProc();

/* Wrappers around X calls to allow non-X usage */
static void addInputSource(), removeInputSource();
static void addTimeOut(), removeTimeOut();
static void processEvent();

#ifndef NeXT
/* External status procedures */
extern void status0(),status1(),status2();
#endif

#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
static int packetCounter;
#endif

/* Extra stuff for the asynchronous X version of dirsend() */
#ifdef XARCHIE
#include <X11/Intrinsic.h>
extern XtAppContext appContext;
#else /*!XARCHIE*/
#include "xtypes.h"
#endif
static XtInputId inputId;
static XtIntervalId timerId = (XtIntervalId)0;

/*
 * dirsend - send packet and receive response
 *
 *   DIRSEND takes a pointer to a structure of type PTEXT, a hostname,
 *   and a pointer to a host address.  It then sends the supplied
 *   packet off to the directory server on the specified host.  If
 *   hostaddr points to a valid address, that address is used.  Otherwise,
 *   the hostname is looked up to obtain the address.  If hostaddr is a
 *   non-null pointer to a 0 address, then the address will be replaced
 *   with that found in the hostname lookup.
 *
 *   DIRSEND will wait for a response and retry an appropriate
 *   number of times as defined by timeout and retries (both static
 *   variables).  It will collect however many packets form the reply, and
 *   return them in a structure (or structures) of type PTEXT.
 *
 *   DIRSEND will free the packet that it is presented as an argument.
 *   The packet is freed even if dirsend fails.
 */
PTEXT
dirsend(pkt_p,hostname_p,hostaddr_p, msgArg)
    PTEXT pkt_p;
    char *hostname_p;
    struct sockaddr_in	*hostaddr_p;
StatusMsgPtr msgArg;
{
    /* copy parameters to globals since other routines use them */
    pkt = pkt_p;
    hostname = hostname_p;
    hostaddr = hostaddr_p;
    /* Do the initializations of formerly auto variables */
    first = NULL;
    lp = -1;
    one = 0;
    zero = 0;
    req_udp_port=0;
    scpflag = 0;
    ackpend = 0;
    gaps = 0;
    retries = client_dirsrv_retry;
	msg = msgArg;

    if (initDirsend() < 0)
	return(NULL);
    addInputSource();
    /* set the first timeout */
    retryDirsend();

    dirsendReturn = NULL;
    dirsendDone = 0;
    /* Until one of the callbacks says to return, keep processing events */
    while (!dirsendDone)
	processEvent();
    /* Clean up event generators */
    removeTimeOut();
    removeInputSource();
#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
    /* Set status if needed (has to be outside of loop or X will crash) */
    switch (dirsendDone) {
	case DSRET_SEND_ERROR: status0("Send error"); break;
	case DSRET_RECV_ERROR: status0("Recv error"); break;
        case DSRET_TIMEOUT:
		status1("Connection to %s timed out",to_hostname);
		break;
        case DSRET_ABORTED: status0("Aborted"); break;
    }
#endif
    /* Return whatever we're supposed to */
    return(dirsendReturn);
}


/*	-	-	-	-	-	-	-	-	*/
/* This function does all the initialization that used to be done at the
 * start of dirsend(), including opening the socket descriptor "lp". It
 * returns the descriptor if successful, otherwise -1 to indicate that
 * dirsend() should return NULL immediately.
 */
static int
initDirsend()
{
#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
    status0("Initializing");
#endif

    if(one == 0) one = htons((short) 1);

    priority = htons(rdgram_priority);

    timeout.tv_sec = client_dirsrv_timeout;
    timeout.tv_usec = 0;

    ackwait.tv_sec = 0;
    ackwait.tv_usec = 500000;

    gapwait.tv_sec = (client_dirsrv_timeout < 5 ? client_dirsrv_timeout : 5);
    gapwait.tv_usec = 0;

    comp_thru = NULL;
    perrno = 0;
    nd_pkts = 0;
    no_pkts = 0;
    pkt_cid = 0;

    /* Find first connection ID */
    if(next_conn_id == 0) {
	srand(getpid()+time(0)); /* XXX: arg ok, but not right type. */
	next_conn_id = rand();
    }


    /* If necessary, find out what udp port to send to */
    if (dir_udp_port == 0) {
        register struct servent *sp;
	tmp = pfs_enable; pfs_enable = PMAP_DISABLE;
#ifdef USE_ASSIGNED_PORT
	/* UCX needs 0 & -1 */
	sp = getservbyname("prospero","udp");
	if (sp == (struct servent *)0 || sp == (struct servent *)-1) {
#ifdef DEBUG
	    if (pfs_debug)
		fprintf(stderr, "dirsrv: udp/prospero unknown service - using %d\n", 
			PROSPERO_PORT);
#endif
	    dir_udp_port = htons((u_short) PROSPERO_PORT);
        }
#else
	/* UCX needs 0 & -1 */
	sp = getservbyname("dirsrv","udp");
	if (sp == (struct servent *)0 || sp == (struct servent *)-1) {
#ifdef DEBUG
	    if (pfs_debug)
		fprintf(stderr, "dirsrv: udp/dirsrv unknown service - using %d\n", 
			DIRSRV_PORT);
#endif
	    dir_udp_port = htons((u_short) DIRSRV_PORT);
        }
#endif
	else dir_udp_port = sp->s_port;
	pfs_enable = tmp;
#ifdef DEBUG
        if (pfs_debug > 3)
            fprintf(stderr,"dir_udp_port is %d\n", ntohs(dir_udp_port));
#endif
    }

    /* If we were given the host address, then use it.  Otherwise  */
    /* lookup the hostname.  If we were passed a host address of   */
    /* 0, we must lookup the host name, then replace the old value */
    if(!hostaddr || (hostaddr->sin_addr.s_addr == 0)) {
	/* I we have a null host name, return an error */
	if((hostname == NULL) || (*hostname == '\0')) {
#ifdef DEBUG
            if (pfs_debug)
                fprintf(stderr, "dirsrv: Null hostname specified\n");
#endif
	    perrno = DIRSEND_BAD_HOSTNAME;
	    ptlfree(pkt);
            /* return(NULL); */
	    return(-1);
	}
	/* If a port is included, save it away */
	if(openparen = index(hostname,'(')) {
	    sscanf(openparen+1,"%d",&req_udp_port);
	    strncpy(hostnoport,hostname,400);
	    if((openparen - hostname) < 400) {
		*(hostnoport + (openparen - hostname)) = '\0';
		hostname = hostnoport;
	    }
	}
#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
	status1("Getting address for host \"%s\"",hostname);
#endif
	tmp = pfs_enable; pfs_enable = PMAP_DISABLE;
	if((host = gethostbyname(hostname)) == NULL) {
	    pfs_enable = tmp;
	    /* Check if a numeric address */
	    newhostaddr = inet_addr(hostname);
	    if(newhostaddr == -1) {
#ifdef DEBUG
		if (pfs_debug)
		  fprintf(stderr, "dirsrv: Can't resolve host %s\n",hostname);
#endif
		perrno = DIRSEND_BAD_HOSTNAME;
		ptlfree(pkt);
		/* return(NULL); */
		return(-1);
	    }
	    bzero((char *)&to, S_AD_SZ);
	    to.sin_family = AF_INET;
	    bcopy((char *) &newhostaddr, (char *)&to.sin_addr, 4);
	    if(hostaddr) bcopy(&to,hostaddr, S_AD_SZ);
	}
	else {
	    pfs_enable = tmp;
	    bzero((char *)&to, S_AD_SZ);
	    to.sin_family = host->h_addrtype;
#ifdef CUTCP
	    bcopy((char *) &host->h_addr, (char *)&to.sin_addr, host->h_length);
#else
	    bcopy(host->h_addr, (char *)&to.sin_addr, host->h_length);
#endif
	    if(hostaddr) bcopy(&to,hostaddr, S_AD_SZ);
	}
    }
    else bcopy(hostaddr,&to, S_AD_SZ);
    /* lmjm: Save away the hostname */
    strncpy(to_hostname,inet_ntoa(to.sin_addr),sizeof(to_hostname)-1);

    if(req_udp_port) to.sin_port = htons(req_udp_port);
    else to.sin_port = dir_udp_port;

    /* If a port was specified in hostaddr, use it, otherwise fill it in */
    if(hostaddr) {
	if(hostaddr->sin_port) to.sin_port = hostaddr->sin_port;
	else hostaddr->sin_port = to.sin_port;
    }

#ifndef CUTCP
    /* Must open a new port each time. we do not want to see old */
    /* responses to messages we are done with                    */
    if ((lp = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
#ifdef DEBUG
        if (pfs_debug)
            fprintf(stderr,"dirsrv: Can't open socket\n");
#endif
	perrno = DIRSEND_UDP_CANT;
	ptlfree(pkt);
        /* return(NULL); */
	return(-1);
    }
	maxFD = lp;
#endif /* not CUTCP */

    /* Try to bind it to a privileged port - loop through candidate */
    /* ports trying to bind.  If failed, that's OK, we will let the */
    /* system assign a non-privileged port later                    */
#ifndef CUTCP
    if(!notprived) {
	for(tmp = PROS_FIRST_PRIVP; tmp < PROS_FIRST_PRIVP+PROS_NUM_PRIVP; 
	    tmp++) {
#endif
	    bzero((char *)&us, sizeof(us));
	    us.sin_family = AF_INET;
#ifndef CUTCP
	    us.sin_port = htons((u_short) tmp);
	    if (bind(lp, (struct sockaddr *)&us, sizeof(us))) {
		if(errno != EADDRINUSE) {
		    notprived++;
		    break;
		}
	    }
	    else break;
	}
    }
#else
    us.sin_port = htons(PROS_FIRST_PRIVP);
    netulisten(PROS_FIRST_PRIVP);
#endif

#ifndef USE_V3_PROT
    /* Add header */
    if(rdgram_priority) {
	pkt->start -= 15;
	pkt->length += 15;
	*(pkt->start) = (char) 15;
	bzero(pkt->start+9,4);
	*(pkt->start+11) = 0x02;
	bcopy(&priority,pkt->start+13,2);
    }
    else {
	pkt->start -= 9;
	pkt->length += 9;
	*(pkt->start) = (char) 9;
    }
    this_conn_id = htons(next_conn_id++);
    if(next_conn_id == 0) next_conn_id++;
    bcopy(&this_conn_id,pkt->start+1,2);
    bcopy(&one,pkt->start+3,2);
    bcopy(&one,pkt->start+5,2);
    bzero(pkt->start+7,2);
#endif

#ifdef DEBUG
    if (pfs_debug > 2) {
#ifndef USE_V3_PROT
        if (to.sin_family == AF_INET) {
	    if(req_udp_port) 
		fprintf(stderr,"Sending message to %s+%d(%d)...",
			to_hostname, req_udp_port, ntohs(this_conn_id));
	    else fprintf(stderr,"Sending message to %s(%d)...",
			 to_hostname, ntohs(this_conn_id));
	}
#else
        if (to.sin_family == AF_INET) 
	    fprintf(stderr,"Sending message to %s...", to_hostname);
#endif /* USE_V3_PROT */
        else
            fprintf(stderr,"Sending message...");
        (void) fflush(stderr);
    }
#endif /* DEBUG */

    first = ptalloc();
    next = first;

#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
    status2("Connecting to %s (%s)", to_hostname, hostname);
    packetCounter = 0;
#endif

#ifndef CUTCP
    return(lp);
#else
    return(1);
#endif /* CUTCP */
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * This used to be a label to goto to retry the last packet. Now we resend
 * the packet and call keepWaitingDirsend() to wait for a reply. (We
 * call keepWaitingDirsend() because formerly the code dropped through
 * the keep_waiting label.)
 */
static void
retryDirsend()
{
#ifdef CUTCP
    int lretry = 3;
#endif
    gaps = ackpend = 0;

#ifndef CUTCP
    ns = sendto(lp,(char *)(pkt->start), pkt->length, 0, (struct sockaddr *)&to, S_AD_SZ);
#else
    while(--lretry) {
	    ns = netusend(&to.sin_addr,ntohs(to.sin_port),ntohs(us.sin_port),
			  (char *) pkt->start, pkt->length);
	    if(!ns)
		break;
	    Stask();
	    Stask();
	    Stask();
    }
#endif /* CUTCP */

#ifndef CUTCP
    if(ns != pkt->length) {
#else
    if(ns != 0) {
#endif
#ifdef DEBUG
	if (pfs_debug) {
    fprintf(stderr,"\nsent only %d/%d: ",ns, pkt->length);
	    perror("");
	}
#endif
	close(lp);
	perrno = DIRSEND_NOT_ALL_SENT;
	ptlfree(first);
	ptlfree(pkt);
	/* return(NULL); */
	dirsendReturn = NULL;
	dirsendDone = DSRET_SEND_ERROR;
    }
#ifdef DEBUG
    if (pfs_debug > 2) fprintf(stderr,"Sent.\n");
#endif
    keepWaitingDirsend();
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * This used to be a label to goto to set the appropriate timeout value
 * and blocked in select(). Now we set selwait and the SELECTARGs to the
 * appropriate values, and in X register a new timeout, then return to
 * allow event processing.
 */
static void
keepWaitingDirsend()
{
    /* We come back to this point (by a goto) if the packet */
    /* received is only part of the response, or if the     */
    /* response came from the wrong host		    */

#ifdef DEBUG
    if (pfs_debug > 2) fprintf(stderr,"Waiting for reply...");
#endif

#ifndef CUTCP
	FD_ZERO(&readfds);
	FD_SET(lp, &readfds);
#	ifdef NeXT
#	ifndef MAX
#		define MAX(a, b) ((a) >= (b) ? (a) : (b))
#	endif
	/* Add the threadAbortFD to the set of fds we want to select() when
		the read status changes */
	FD_SET(threadAbortFD, &readfds);
	maxFD = MAX(lp, threadAbortFD);
#	endif
#endif

    if(ackpend) selwait = &ackwait;
    else if(gaps) selwait = &gapwait;
    else selwait = &timeout;

    addTimeOut();
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * This routine is called when a timeout occurs. It includes the code that
 * was formerly used when select() returned 0 (indicating a timeout).
 */
/*ARGSUSED*/
static void
timeoutProc(client_data,id)
XtPointer client_data;
XtIntervalId *id;
{
    if (gaps || ackpend) { /* Send acknowledgment */
	/* Acks are piggybacked on retries - If we have received */
	/* an ack from the server, then the packet sent is only  */
	/* an ack and the rest of the message will be empty      */
#ifdef DEBUG
	if (pfs_debug > 2) {
            fprintf(stderr,"Acknowledging (%s).\n",
		    (ackpend ? "requested" : "gaps"));
	}	
#endif
	retryDirsend();
	return;
    }

    if (retries-- > 0) {
	timeout.tv_sec = CLIENT_DIRSRV_BACKOFF(timeout.tv_sec);
#ifdef DEBUG
	if (pfs_debug > 2) {
            fprintf(stderr,"Timed out.  Setting timeout to %d seconds.\n",
		    timeout.tv_sec);
	}
#endif
#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
        status1("Timed out -- retrying (%d seconds)",timeout.tv_sec);
#endif
	retryDirsend();
	return;
    }

#ifdef DEBUG
    if (pfs_debug) {
	fprintf(stderr, "select failed(timeoutProc): readfds=%x ",
		readfds);
	perror("");
    }
#endif
#ifndef CUTCP
    close(lp);
#endif
    perrno = DIRSEND_SELECT_FAILED;
    ptlfree(first);
    ptlfree(pkt);
    /* return(NULL); */
    dirsendReturn = NULL;
    dirsendDone = DSRET_TIMEOUT;
}

/*	-	-	-	-	-	-	-	-	*/
/*
 * This function is called whenever there's something to read on the
 * connection. It includes the code that was run when select() returned
 * greater than 0 (indicating read ready).
 */
/*ARGSUSED*/
static void
readProc(client_data,source,id)
XtPointer client_data;
int *source;
XtInputId *id;
{
#ifdef CUTCP
    int lretry = 3;
#endif

    /* We got something to read, so clear the timer */
    removeTimeOut();

    from_sz = sizeof(from);
    next->start = next->dat;

#ifndef CUTCP
    if ((nr = recvfrom(lp, next->start, sizeof(next->dat), 0, (struct sockaddr *)&from, &from_sz)) < 0) {
#else
    nr = neturead(next->start);
    if (nr < 1) {
#endif
#ifdef DEBUG
        if (pfs_debug) perror("recvfrom");
#endif
#ifndef CUTCP
	close(lp);
#endif
	perrno = DIRSEND_BAD_RECV;
	ptlfree(first);
	ptlfree(pkt);
	/* return(NULL) */
	dirsendReturn = NULL;
	dirsendDone = DSRET_RECV_ERROR;
        return;
    }

    next->length = nr;
    next->start[next->length] = 0;

#ifdef DEBUG
    if (pfs_debug > 2)
        fprintf(stderr,"Received packet from %s\n",inet_ntoa(from.sin_addr));
#endif

#if defined(XARCHIE) || defined(VARCHIE) || defined(NeXT)
	if (packetCounter == 0)
	{
		status2("Connected to %s (%s)", to_hostname, hostname);
	}
	else
	{
		status1("Receiving...%d",packetCounter);
	}
    packetCounter += 1;
#endif

    /* For the current format, if the first byte is less than             */
    /* 20, then the first two bits are a version number and the next six  */
    /* are the header length (including the first byte).                  */
    if((hdr_len = (unsigned char) *(next->start)) < 20) {
	ctlptr = next->start + 1;
	next->seq = 0;
	if(hdr_len >= 3) { 	/* Connection ID */
	    bcopy(ctlptr,&stmp,2);
	    if(stmp) pkt_cid = ntohs(stmp);
	    ctlptr += 2;
	}
	if(pkt_cid && this_conn_id && (pkt_cid != ntohs(this_conn_id))) {
	    /* The packet is not for us */
	    /* goto keep_waiting; */
	    keepWaitingDirsend();
	    return;
	}
	if(hdr_len >= 5) {	/* Packet number */
	    bcopy(ctlptr,&stmp,2);
	    next->seq = ntohs(stmp);
	    ctlptr += 2;
	}
	else { /* No packet number specified, so this is the only one */
	    next->seq = 1;
	    nd_pkts = 1;
	}
	if(hdr_len >= 7) {	    /* Total number of packets */
	    bcopy(ctlptr,&stmp,2);  /* 0 means don't know      */
	    if(stmp) nd_pkts = ntohs(stmp);
	    ctlptr += 2;
	}
	if(hdr_len >= 9) {	/* Receievd through */
	    bcopy(ctlptr,&stmp,2);  /* 1 means received request */
#ifndef USE_V3_PROT
	    if((stmp) && (ntohs(stmp) == 1)) {
		/* Future retries will be acks only */
		pkt->length = 9;
		bcopy(&zero,pkt->start+3,2);
#ifdef DEBUG
		if(pfs_debug > 2) 
		    fprintf(stderr,"Server acked request - retries will be acks only\n");
#endif
	    }
#endif
	    ctlptr += 2;
	}
	if(hdr_len >= 11) {	/* Backoff */
	    bcopy(ctlptr,&stmp,2);
	    if(stmp) {
		backoff = (short)ntohs(stmp);
#ifdef DEBUG
		if(pfs_debug > 2) 
		    fprintf(stderr,"Backing off to %d seconds\n", backoff);
#endif
		timeout.tv_sec = backoff;
		if ((backoff > 60) && (first == next) && (no_pkts == 0)) {
		    /* Probably a long queue on the server - don't give up */
		    retries = client_dirsrv_retry;
		}
	    }
	    ctlptr += 2;
	}
	if(hdr_len >= 12) {	/* Flags (1st byte) */
	    bcopy(ctlptr,&rdflag11,1);
	    if(rdflag11 & 0x80) {
#ifdef DEBUG
		if(pfs_debug > 2) 
		    fprintf(stderr,"Ack requested\n");
#endif
		ackpend++;
	    }
	    if(rdflag11 & 0x40) {
#ifdef DEBUG
		if(pfs_debug > 2) 
		    fprintf(stderr,"Sequenced control packet\n");
#endif
		next->length = -1;
		scpflag++;
	    }
	    ctlptr += 1;
	}
	if(hdr_len >= 13) {	/* Flags (2nd byte) */
	    /* Reserved for future use */
	    bcopy(ctlptr,&rdflag12,1);
	    ctlptr += 1;
	}
	if(next->seq == 0) {
	    /* goto keep_waiting; */
	    keepWaitingDirsend();
	    return;
	}
	if(next->length >= 0) next->length -= hdr_len;
	next->start += hdr_len;
	goto done_old;
    }

    pkt_cid = 0;

    /* if intermediate format (between old and new), then process */
    /* and go to done_old                                         */
    ctlptr = next->start + max(0,next->length-20);
    while(*ctlptr) ctlptr++;
    /* Control fields start after the terminating null */
    ctlptr++;
    /* Until old version are gone, must be 4 extra bytes minimum */
    /* When no version 3 servers, can remove the -4              */
    if(ctlptr < (next->start + next->length - 4)) {
	/* Connection ID */
	bcopy(ctlptr,&stmp,2);
	if(stmp) pkt_cid = ntohs(stmp);
	ctlptr += 2;
	if(pkt_cid && this_conn_id && (pkt_cid != ntohs(this_conn_id))) {
	    /* The packet is not for us */
	    /* goto keep_waiting; */
	    keepWaitingDirsend();
	    return;
	}
	/* Packet number */
	if(ctlptr < (next->start + next->length)) {
	    bcopy(ctlptr,&stmp,2);
	    next->seq = ntohs(stmp);
	    ctlptr += 2;
	}
	/* Total number of packets */
	if(ctlptr < (next->start + next->length)) {
	    bcopy(ctlptr,&stmp,2);
	    if(stmp) nd_pkts = ntohs(stmp);
	    ctlptr += 2;
	}
	/* Receievd through */
	if(ctlptr < (next->start + next->length)) {
	    /* Not supported by clients */
	    ctlptr += 2;
	}
	/* Backoff */
	if(ctlptr < (next->start + next->length)) {
	    bcopy(ctlptr,&stmp,2);
	    backoff = ntohs(stmp);
#ifdef DEBUG
	    if(pfs_debug > 2) 
		fprintf(stderr,"Backing off to %d seconds\n", backoff);
#endif
	    if(backoff) timeout.tv_sec = backoff;
	    ctlptr += 2;
	}
	if(next->seq == 0) {
	    /* goto keep_waiting; */
	    keepWaitingDirsend();
	    return;
	}
	goto done_old;

    }

    /* Notes that we have to start searching 11 bytes before the    */
    /* expected start of the MULTI-PACKET line because the message  */
    /* might include up to 10 bytes of data after the trailing null */
    /* The order of those bytes is two bytes each for Connection ID */
    /* Packet-no, of, Received-through, Backoff                     */
    seqtxt = nlsindex(next->start + max(0,next->length - 40),"MULTI-PACKET"); 
    if(seqtxt) seqtxt+= 13;

    if((nd_pkts == 0) && (no_pkts == 0) && (seqtxt == NULL)) goto all_done;

    tmp = sscanf(seqtxt,"%d OF %d", &(next->seq), &nd_pkts);
#ifdef DEBUG    
    if (pfs_debug && (tmp == 0)) 
	fprintf(stderr,"Cant read packet sequence number: %s", seqtxt);    
#endif
 done_old:
#ifdef DEBUG
    if(pfs_debug > 2) fprintf(stderr,"Packet %d of %d\n",next->seq,nd_pkts);
#endif
    if ((first == next) && (no_pkts == 0)) {
	if(first->seq == 1) {
	    comp_thru = first;
	    /* If only one packet, then return it */
	    if(nd_pkts == 1) goto all_done;
	}
	else gaps++;
	no_pkts = 1;
	next = ptalloc();
	/* goto keep_waiting; */
	keepWaitingDirsend();
	return;
    }
	
    if(comp_thru && (next->seq <= comp_thru->seq))
	ptfree(next);
    else if (next->seq < first->seq) {
	vtmp = first;
	first = next;
	first->next = vtmp;
	first->previous = NULL;
	vtmp->previous = first;
	if(first->seq == 1) comp_thru = first;
	no_pkts++;
    }
    else {
	vtmp = (comp_thru ? comp_thru : first);
	while (vtmp->seq < next->seq) {
	    if(vtmp->next == NULL) {
		vtmp->next = next;
		next->previous = vtmp;
		next->next = NULL;
		no_pkts++;
		goto ins_done;
	    }
	    vtmp = vtmp->next;
	}
	if(vtmp->seq == next->seq)
	    ptfree(next);
	else {
	    vtmp->previous->next = next;
	    next->previous = vtmp->previous;
	    next->next = vtmp;
	    vtmp->previous = next;
	    no_pkts++;
	}
    }   

ins_done:
	
    while(comp_thru && comp_thru->next && 
	  (comp_thru->next->seq == (comp_thru->seq + 1))) {
	comp_thru = comp_thru->next;
#ifndef USE_V3_PROT
	recvd_thru = htons(comp_thru->seq);
	bcopy(&recvd_thru,pkt->start+7,2); /* Let server know we got it */
#endif
	/* We've made progress, so reset retry count */
	retries = client_dirsrv_retry;
	/* Also, next retry will be only an acknowledgement */
	/* but for now, we can't fill in the ack field      */
#ifdef DEBUG
	if(pfs_debug > 2) 
	    fprintf(stderr,"Packets now received through %d\n",comp_thru->seq);
#endif
    }

    /* See if there are any gaps */
    if(!comp_thru || comp_thru->next) gaps++;
    else gaps = 0;

    if ((nd_pkts == 0) || (no_pkts < nd_pkts)) {
	next = ptalloc();
	/* goto keep_waiting; */
	keepWaitingDirsend();
	return;
    }

 all_done:
    if(ackpend) { /* Send acknowledgement if requested */
#ifdef DEBUG
	if (pfs_debug > 2) {
	    if (to.sin_family == AF_INET)
		fprintf(stderr,"Acknowledging final packet to %s(%d)\n",
			to_hostname, ntohs(this_conn_id));
            else
                fprintf(stderr,"Acknowledging final packet\n");
	    (void) fflush(stderr);
	}
#endif
#ifndef CUTCP
	ns = sendto(lp,(char *)(pkt->start), pkt->length, 0, (struct sockaddr *)&to, S_AD_SZ);
#else
	while(--lretry) {
	    ns = netusend(&to.sin_addr, ntohs(to.sin_port), ntohs(us.sin_port),(char *) pkt->start, pkt->length);
		if(!ns)
			break;
		Stask();
		Stask();
	}
#endif

#ifndef CUTCP
	if(ns != pkt->length) {
#else
	if(ns != 0) {
#endif

#ifdef DEBUG
	    if (pfs_debug) {
		fprintf(stderr,"\nsent only %d/%d: ",ns, pkt->length);
		perror("");
	    }
#endif
	}

    }
#ifndef CUTCP
    close(lp);
#endif
    ptlfree(pkt);

    /* Get rid of any sequenced control packets */
    if(scpflag) {
	while(first && (first->length < 0)) {
	    vtmp = first;
	    first = first->next;
	    if(first) first->previous = NULL;
	    ptfree(vtmp);
	}
	vtmp = first;
	while(vtmp && vtmp->next) {
	    if(vtmp->next->length < 0) {
		if(vtmp->next->next) {
		    vtmp->next = vtmp->next->next;
		    ptfree(vtmp->next->previous);
		    vtmp->next->previous = vtmp;
		}
		else {
		    ptfree(vtmp->next);
		    vtmp->next = NULL;
		}
	    }
	    vtmp = vtmp->next;
	}
    }

    /* return(first); */
    dirsendReturn = first;
    dirsendDone = DSRET_DONE;

}
/*	-	-	-	-	-	-	-	-	*/
/* These routines allow dirsend() to run with or without X by providing
 * wrappers around the calls that handle the asynchronous communication.
 * All parameters are passed using globals.
 * Under X: The input sources and timeouts are set using Xt calls, and
 *	    processEvent() just calls XtAppProcessEvent().
 * Non-X: None of the input sources and timeouts are used, and
 *	  processEvent() calls select() to handle both timeouts and the
 *	  socket file descriptor. The return value of select() is used
 *	  to determine which callback routine to call.
 */

static void
addInputSource()
{
#ifdef XARCHIE
    inputId = XtAppAddInput(appContext,lp,(XtPointer)XtInputReadMask,
			    readProc,NULL);
#endif
}

static void
removeInputSource()
{
#ifdef XARCHIE
    XtRemoveInput(inputId);
#endif
}

static void
addTimeOut()
{
#ifdef XARCHIE
    unsigned long timeoutLen = selwait->tv_sec*1000 + selwait->tv_usec/1000;

    /* old timeout can still be there if we are being called after the
     * file descriptor was read, so we remove it just to be sure. */
    removeTimeOut();
    timerId = XtAppAddTimeOut(appContext,timeoutLen,timeoutProc,NULL);
#endif
}

static void
removeTimeOut()
{
#ifdef XARCHIE
    if (timerId != (XtIntervalId)0) {
	XtRemoveTimeOut(timerId);
	timerId = (XtIntervalId)0;
    }
#endif
}

/*
 * In X, this just calls the X routine that blocks waiting for an event,
 * timer, or input source and dispatches it.
 *
 * Otherwise, for Unix we call select() with the appropriate arguments,
 * and act on its return calue as follows:
 *  == 0 : The timer expired, call timeoutProc() then return to, presumably,
 *         the loop that calling processEvent() until dirsendDone.
 *  < 0 :  If we were interrupted (errno == EINTR) then don't do anything.
 *         Presumably the signal handler set flags if needed, and we'll
 *         come back to select() again on the next pass of the outer loop
 *         if we're supposed to. Otherwise, puke on the error.
 *  > 0 :  The socket is ready for reading, so call readProc().
 *
 * Otherwise, if we're in MSDOS (CUTCP defined) then I (gf) have no idea
 * what's going on. Ask Brendan.
 */
static void processEvent()
{
#ifdef XARCHIE
    XtAppProcessEvent(appContext,XtIMAll);
#else
    /* select - either recv is ready, or timeout */
    /* see if timeout or error or wrong descriptor */
	tmp = select(maxFD+1, &readfds, (SELECTARG *)0, (SELECTARG *)0, selwait);
	if (tmp == 0) 
	{
		timeoutProc(NULL,&timerId);
	}
	else if (tmp < 0 && errno == EINTR)
	{	/* gf: new for ^C in varchie */
		fprintf(stderr,"select interrupted\n");	/* do nothing, we'll be back */
#ifdef NeXT
		dirsendDone = DSRET_ABORTED;
	}
	else if( tmp > 0 && FD_ISSET(threadAbortFD, &readfds) )
	{	/* The user has aborted the query thread */
		dirsendDone = DSRET_ABORTED;
#endif
	}
	else if ((tmp < 0) || !FD_ISSET(lp,&readfds))
	{
#ifdef DEBUG
		if (pfs_debug)
		{
			fprintf(stderr, "select failed(processEvent): readfds=%x ",
				readfds);
			perror("");
		}
#endif
		close(lp);
		perrno = DIRSEND_SELECT_FAILED;
		ptlfree(first);
		ptlfree(pkt);
		/* return(NULL); */
		dirsendReturn = NULL;
		dirsendDone = DSRET_SELECT_ERROR;
	}
	else
	{
		readProc(NULL,&lp,&inputId);
	}
#endif /* XARCHIE */
}

void
abortDirsend()
{
    if (!dirsendDone) {
#ifndef CUTCP
	close(lp);
#endif
	ptlfree(first);
	ptlfree(pkt);
	dirsendReturn = NULL;
	dirsendDone = DSRET_ABORTED;
    }
    return;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.