File:  [Public] / libwww / Library / src / HTTCP.c
Revision 2.38: download - view: text, annotated - select for diffs
Tue Mar 21 22:43:17 1995 UTC (29 years, 2 months ago) by frystyk
Branches: MAIN
CVS tags: v3/0, HEAD
Last attempt before release

/*									HTTCP.c
**	GENERIC COMMUNICATION CODE
**
**	(c) COPYRIGHT CERN 1994.
**	Please first read the full copyright statement in the file COPYRIGH.
**
**	This code is in common between client and server sides.
**
**	16 Jan 92  TBL	Fix strtol() undefined on CMU Mach.
**	25 Jun 92  JFG  Added DECNET option through TCP socket emulation.
**	13 Sep 93  MD   Added correct return of vmserrorno for HTInetStatus.
**			Added decoding of vms error message for MULTINET.
**	31 May 94  HF	Added cache on host id's; now use inet_ntoa() to
**			HTInetString and some other fixes. Added HTDoConnect
**			and HTDoAccept
*/

/* Library include files */
#include "tcp.h"
#include "HTUtils.h"
#include "HTString.h"
#include "HTAtom.h"
#include "HTList.h"
#include "HTParse.h"
#include "HTAccess.h"
#include "HTError.h"
#include "HTThread.h"
#include "HTTCP.h"					 /* Implemented here */

#ifdef VMS 
#include "HTVMSUtils.h"
#endif /* VMS */

#ifdef SHORT_NAMES
#define HTInetStatus		HTInStat
#define HTErrnoString		HTErrnoS
#define HTInetString 		HTInStri
#define HTParseInet		HTPaInet
#endif


/* VMS stuff */
#ifdef VMS
#ifndef MULTINET
#define FD_SETSIZE 32
#else /* Multinet */
#define FD_SETSIZE 256
#endif /* Multinet */
#endif /* VMS */

/* Macros and other defines */
/* x seconds penalty on a multi-homed host if IP-address is down */
#define TCP_PENALTY		1200

/* x seconds penalty on a multi-homed host if IP-address is timed out */
#define TCP_DELAY		600

/* Max number of non-blocking accepts */
#define MAX_ACCEPT_POLL		30
#define FCNTL(r, s, t)		fcntl(r, s, t)

#ifndef RESOLV_CONF
#define RESOLV_CONF "/etc/resolv.conf"
#endif

/* Globals */
PUBLIC unsigned int	HTConCacheSize = 512;	 /* Number of cached servers */

/* Type definitions and global variables etc. local to this module */

/* This structure is a cache of hosts to whom we have connected over time.
   The structure contains the necessary parts from hostent. For Internet host
   hostent->h_addr_list is not an array of char pointers but an array of 
   pointers of type in_addr. */
typedef struct _host_info {
    HTAtom *		hostname;		    /* Official name of host */
    int			hits;		/* Total number of hits on this host */
    int			addrlength;	       /* Length of address in bytes */
    int			homes;	       /* Number of IP addresses on the host */
    int			offset;         /* Offset value of active IP address */
    char **		addrlist;      /* List of addresses from name server */
    double *		weight;			   /* Weight on each address */
} host_info;

PRIVATE char *hostname = NULL;			    /* The name of this host */
PRIVATE char *mailaddress = NULL;		     /* Current mail address */
PRIVATE HTList *hostcache = NULL;  /* List of servers that we have talked to */
PRIVATE unsigned int HTCacheSize = 0;		    /* Current size of cache */

/* ------------------------------------------------------------------------- */

/*
**	Returns the string equivalent to the errno passed in the argument.
**	We can't use errno directly as we have both errno and socerrno. The
**	result is a static buffer.
*/
PUBLIC CONST char * HTErrnoString ARGS1(int, errornumber)
{
#if defined(NeXT) || defined(THINK_C)
    return strerror(errornumber);
#else
#ifdef VMS
    static char buf[60];
    sprintf(buf,"Unix errno = %ld dec, VMS error = %lx hex", errornumber,
	    vaxc$errno);
    return buf;
#else
    return (errornumber < sys_nerr ? sys_errlist[errornumber]:"Unknown error");
#endif
#endif
}


#ifdef OLD_CODE
/*	Debug error message
*/
PUBLIC int HTInetStatus ARGS1(char *, where)
{
#ifndef VMS

    if (PROT_TRACE)
	fprintf(TDEST, "TCP errno... %d after call to %s() failed.\n............ %s\n", errno, where, HTErrnoString(errornumber));

#else /* VMS */

    if (PROT_TRACE) fprintf(TDEST, "         Unix error number          = %ld dec\n", errno);
    if (PROT_TRACE) fprintf(TDEST, "         VMS error                  = %lx hex\n", vaxc$errno);

#ifdef MULTINET
    if (PROT_TRACE) fprintf(TDEST, "         Multinet error             = %lx hex\n", socket_errno); 
    if (PROT_TRACE) fprintf(TDEST, "         Error String               = %s\n", vms_errno_string());
#endif /* MULTINET */

#endif /* VMS */

#ifdef VMS
    /* errno happen to be zero if vaxc$errno <> 0 */
    return -vaxc$errno;
#else
    return -errno;
#endif
}
#endif


/*	Parse a cardinal value				       parse_cardinal()
**	----------------------
**
** On entry,
**	*pp	    points to first character to be interpreted, terminated by
**		    non 0:9 character.
**	*pstatus    points to status already valid
**	maxvalue    gives the largest allowable value.
**
** On exit,
**	*pp	    points to first unread character
**	*pstatus    points to status updated iff bad
*/

PUBLIC unsigned int HTCardinal ARGS3
	(int *,		pstatus,
	char **,	pp,
	unsigned int,	max_value)
{
    unsigned int n=0;
    if ( (**pp<'0') || (**pp>'9')) {	    /* Null string is error */
	*pstatus = -3;  /* No number where one expeceted */
	return 0;
    }
    while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';

    if (n>max_value) {
	*pstatus = -4;  /* Cardinal outside range */
	return 0;
    }

    return n;
}

/* ------------------------------------------------------------------------- */
/*	       		        SIGNAL HANDLING 			     */
/* ------------------------------------------------------------------------- */

#ifdef WWWLIB_SIG
/*								    HTSetSignal
**  This function sets up signal handlers. This might not be necessary to
**  call if the application has its own handlers.
*/
#include <signal.h>
PUBLIC void HTSetSignal NOARGS
{
    /* On some systems (SYSV) it is necessary to catch the SIGPIPE signal
    ** when attemting to connect to a remote host where you normally should
    ** get `connection refused' back
    */
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
	if (PROT_TRACE) fprintf(TDEST, "HTSignal.... Can't catch SIGPIPE\n");
    } else {
	if (PROT_TRACE) fprintf(TDEST, "HTSignal.... Ignoring SIGPIPE\n");
    }
}
#endif /* WWWLIB_SIG */

/* ------------------------------------------------------------------------- */
/*	       		     HOST CACHE MANAGEMENT 			     */
/* ------------------------------------------------------------------------- */

/*                                                     	HTTCPCacheRemoveElement
**
**	Remove the element specified from the cache
*/
PRIVATE void HTTCPCacheRemoveElement ARGS1(host_info *, element)
{
    if (!hostcache) {
        if (PROT_TRACE)
            fprintf(TDEST, "HostCache... Remove not done, no cache\n");
        return;
    }
    if (PROT_TRACE) fprintf(TDEST, "HostCache... Remove `%s' from cache\n",
			    HTAtom_name(element->hostname));
    HTList_removeObject(hostcache, (void *) element);
    if (*element->addrlist)
	free(*element->addrlist);
    if (element->addrlist)
	free(element->addrlist);
    if (element->weight)
	free(element->weight);
    free(element);
}


/*                                                     	HTTCPCacheRemoveAll
**
**	Cleans up the memory. Called by HTLibTerminate
*/
PUBLIC void HTTCPCacheRemoveAll NOARGS
{
    if (hostcache) {
	HTList *cur = hostcache;
	host_info *pres;
	while ((pres = (host_info *) HTList_nextObject(cur))) {
	    if (*pres->addrlist)
		free(*pres->addrlist);
	    if (pres->addrlist)
		free(pres->addrlist);
	    if (pres->weight)
		free(pres->weight);
	    free(pres);
	}
	HTList_delete(hostcache);
	hostcache = NULL;
    }
}


/*                                                     HTTCPCacheRemoveHost
**
**	Removes the corresponding entrance in the cache
*/
PRIVATE void HTTCPCacheRemoveHost ARGS1(char *, host)
{
    HTAtom *hostatom = HTAtom_for(host);
    HTList *cur = hostcache;
    host_info *pres = NULL;
    if (!hostcache) {
	if (PROT_TRACE)
	    fprintf(TDEST, "HostCache... Remove host not done, no cache\n");
	return;
    }
    while ((pres = (host_info *) HTList_nextObject(cur)) != NULL) {
	if (pres->hostname == hostatom) {
	    break;
	}
    }
    if (pres)
	HTTCPCacheRemoveElement(pres);
}


/*                                                     HTTCPCacheGarbage
**
**	Remove the element with the lowest hit rate
*/
PRIVATE void HTTCPCacheGarbage NOARGS
{
    HTList *cur = hostcache;
    host_info *pres, *worst_match = NULL;
    unsigned int worst_hits = 30000;		  /* Should use UINT_MAX :-( */
    if (!hostcache) {
	if (PROT_TRACE)
	    fprintf(TDEST, "HostCache... Garbage collection not done, no cache\n");
	return;
    }

    /* Seek for worst element */
    while ((pres = (host_info *) HTList_nextObject(cur))) {
	if (!worst_match || pres->hits <= worst_hits) {
	    worst_match = pres;
	    worst_hits = pres->hits;
	}
    }
    if (worst_match)
	HTTCPCacheRemoveElement(worst_match);
}


/*                                                     HTTCPCacheAddElement
**
**	Add an element to the cache of visited hosts. Note that this function
**	requires the system implemented structure hostent and not our own
**	host_info. The homes variable indicates the number of
**	IP addresses found.
**
**      Returns new element if OK NULL if error
*/
PRIVATE host_info *HTTCPCacheAddElement ARGS2(HTAtom *, host,
					      struct hostent *, element)
{
    host_info *newhost;
    char *addr;
    char **index = element->h_addr_list;
    int cnt = 1;
    if (!host || !element) {
	if (PROT_TRACE)
	    fprintf(TDEST, "HostCache... Bad argument to add to cache\n");
	return NULL;
    }
    while(*index++)
	cnt++;
    if ((newhost = (host_info *) calloc(1, sizeof(host_info))) == NULL ||
	(newhost->addrlist = (char **) calloc(1, cnt*sizeof(char*))) == NULL ||
	(addr = (char *) calloc(1, cnt*element->h_length)) == NULL)
	outofmem(__FILE__, "HTTCPCacheAddElement");
    newhost->hostname = host;
    index = element->h_addr_list;
    cnt = 0;
    while (*index) {
	*(newhost->addrlist+cnt) = addr+cnt*element->h_length;
	memcpy((void *) *(newhost->addrlist+cnt++), *index++,
	       element->h_length);
    }
    newhost->homes = cnt;
    if ((newhost->weight = (double *) calloc(newhost->homes,
					    sizeof(double))) == NULL)
	outofmem(__FILE__, "HTTCPCacheAddElement");

    newhost->addrlength = element->h_length;
    if (!hostcache)
	hostcache = HTList_new();

    if (PROT_TRACE) {
	if (newhost->homes == 1)
	    fprintf(TDEST, "HostCache... Adding single-homed host `%s'\n",
		    HTAtom_name(host));
	else
	    fprintf(TDEST, "HostCache... Adding host `%s' with %d homes\n",
		    HTAtom_name(host), newhost->homes);
    }
    HTList_addObject(hostcache, (void *) newhost);
    HTCacheSize++;				/* Update number of elements */
    return newhost;
}


/*                                                     	      HTTCPAddrWeights
**
**	This function calculates the weights of the different IP addresses
**	on a multi homed host. Each weight is calculated as
**
**		w(n+1) = w(n)*a + (1-a) * deltatime
**		a = exp(-1/Neff)
**		Neff is the effective number of samples used
**		deltatime is time spend on making a connection
**
**	A short window (low Neff) gives a high sensibility, but this is
**	required as we can't expect a lot of data to test on.
**
*/
PUBLIC void HTTCPAddrWeights ARGS2(char *, host, time_t, deltatime)
{
    HTAtom *hostatom = HTAtom_for(host);
    HTList *cur = hostcache;
    host_info *pres = NULL;
    if (!hostcache) {
	fprintf(TDEST, "HostCache... Weights not calculated, no cache\n");
	return;
    }
    /* Skip any port number from host name */
    if (strchr(host, ':')) {
	char *newhost = NULL;
	char *strptr;
	StrAllocCopy(newhost, host);
	strptr = strchr(newhost, ':');
	*strptr = '\0';
	hostatom = HTAtom_for(newhost);
	free(newhost);
    } else
	hostatom = HTAtom_for(host);
    
    while ((pres = (host_info *) HTList_nextObject(cur)) != NULL) {
	if (pres->hostname == hostatom) {
	    break;
	}
    }
    if (pres) {
	int cnt;
	CONST double passive = 0.9; 	  /* Factor for all passive IP_addrs */
#if 0
	CONST int Neff = 3;
	CONST double alpha = exp(-1.0/Neff);
#else
	CONST double alpha = 0.716531310574;	/* Doesn't need the math lib */
#endif
	for (cnt=0; cnt<pres->homes; cnt++) {
	    if (cnt == pres->offset) {
		*(pres->weight+pres->offset) = *(pres->weight+pres->offset)*alpha + (1.0-alpha)*deltatime;
	    } else {
		*(pres->weight+cnt) = *(pres->weight+cnt) * passive;
	    }
	    if (PROT_TRACE)
		fprintf(TDEST, "AddrWeights. Home %d has weight %4.2f\n", cnt,
			*(pres->weight+cnt));
	}
    } else if (PROT_TRACE) {
	fprintf(TDEST, "HostCache... Weights not calculated, host not found in cache: `%s\'\n", host);
    }
}

/* ------------------------------------------------------------------------- */
/*	       		     HOST NAME FUNCTIONS 			     */
/* ------------------------------------------------------------------------- */

#ifndef DECNET  /* Function only used below for a trace message */

/*	Produce a string for an Internet address
**	----------------------------------------
**
** On exit,
**	returns	a pointer to a static string which must be copied if
**		it is to be kept.
*/

PUBLIC CONST char * HTInetString ARGS1(SockA *, sin)
{
#if 0
    /* This dumps core on some Sun systems :-(. The problem is now, that 
       the current implememtation only works for IP-addresses and not in
       other address spaces. */
    return inet_ntoa(sin->sin_addr);
#endif
    static char string[16];
    sprintf(string, "%d.%d.%d.%d",
	    (int)*((unsigned char *)(&sin->sin_addr)+0),
	    (int)*((unsigned char *)(&sin->sin_addr)+1),
	    (int)*((unsigned char *)(&sin->sin_addr)+2),
	    (int)*((unsigned char *)(&sin->sin_addr)+3));
    return string;
}
#endif /* Decnet */


/*	                                                     HTGetHostByName
**
**	Searched first the local cache then asks the DNS for an address of
**	the host specified.
**
**      Returns:	>0 if OK the number of homes are returned
**		     	-1 if error
*/
PUBLIC int HTGetHostByName ARGS3(char *, host, SockA *, sin,
				 BOOL, use_cur)
{
    HTAtom *hostatom = HTAtom_for(host);
    host_info *pres = NULL;
    if (!hostcache)
	hostcache = HTList_new(); 	     	       /* First time through */
    else {
	HTList *cur = hostcache;       		 	     /* Search cache */
	while ((pres = (host_info *) HTList_nextObject(cur)) != NULL) {
	    if (pres->hostname == hostatom) {
		if (PROT_TRACE)
		    fprintf(TDEST, "HostByName.. Host `%s\' found in cache.\n", host);
		break;
	    }
	}
    }
    
    /* If the host was not found in the cache, then do gethostbyname.
       If we are talking to a multi homed host then take the IP address with
       the lowest weight. If `use_cur'=YES then use current IP-address */
    if (pres) {
	if (pres->homes > 1 && !use_cur) {
	    int cnt;
	    double best_weight = 1e30;		    /* Should be FLT_MAX :-( */
	    for (cnt=0; cnt<pres->homes; cnt++) {
		if (*(pres->weight+cnt) < best_weight) {
		    best_weight = *(pres->weight+cnt);
		    pres->offset = cnt;
		}
	    }
	}
	pres->hits++;	 	 /* Update total number of hits on this host */
    } else {						/* Go and ask for it */
	struct hostent *hostelement;			      /* see netdb.h */
#ifdef MVS	/* Outstanding problem with crash in MVS gethostbyname */
	if (PROT_TRACE)
	    fprintf(TDEST, "HTTCP on MVS gethostbyname(%s)\n", host);
#endif
	if ((hostelement = gethostbyname(host)) == NULL) {
	    if (PROT_TRACE)
		fprintf(TDEST, "HostByName.. Can't find internet node name `%s'.\n", host);
	    return -1;
	}
	
	/* Add element to the cache and maybe do garbage collection */
	if (HTCacheSize >= HTConCacheSize)
	    HTTCPCacheGarbage();
	if ((pres = HTTCPCacheAddElement(hostatom, hostelement)) == NULL) {
	    return -1;
	}
    }
    
    /* Update socket structure using the element with the lowest weight. On
       single homed hosts it means the first value */
    memcpy(&sin->sin_addr, *(pres->addrlist+pres->offset), pres->addrlength);
    return pres->homes;
}


/*
**	Get host name of the machine on the other end of a socket.
**
*/
PUBLIC char * HTGetHostBySock ARGS1(int, soc)
{
    struct sockaddr addr;
    int len = sizeof(struct sockaddr);
    struct in_addr *iaddr;
    struct hostent * phost;		/* Pointer to host -- See netdb.h */
    char *name = NULL;

#ifdef DECNET  /* Decnet ain't got no damn name server 8#OO */
    return NULL;
#else
    if (getpeername(soc, &addr, &len) < 0)
	return NULL;

    iaddr = &(((struct sockaddr_in *)&addr)->sin_addr);
    phost=gethostbyaddr((char*)iaddr,
			sizeof(struct in_addr),
			AF_INET);
    if (!phost) {
	if (PROT_TRACE)
	    fprintf(TDEST, "TCP......... Can't find internet node name for peer!!\n");
	return NULL;
    }
    StrAllocCopy(name, phost->h_name);
    if (PROT_TRACE) fprintf(TDEST, "TCP......... Peer name is `%s'\n", name);

    return name;

#endif	/* not DECNET */
}


/*	Parse a network node address and port
**	-------------------------------------
**
** On entry,
**	str	points to a string with a node name or number,
**		with optional trailing colon and port number.
**	sin	points to the binary internet or decnet address field.
**
** On exit,	-1	If error
**		>0	If OK the number of homes on the host
**	*sin	is filled in. If no port is specified in str, that
**		field is left unchanged in *sin.
**
** NOTE:	It is assumed that any portnumber and numeric host address
**		is given in decimal notation. Separation character is '.'
*/
PUBLIC int HTParseInet ARGS3(SockA *, sin, CONST char *, str,
			     BOOL, use_cur)
{
    char *host = NULL;
    int status = 0;
    StrAllocCopy(host, str);		      /* Take a copy we can mutilate */

    /* Parse port number if present. */    
    {
	char *port;
	if ((port=strchr(host, ':'))) {
	    *port++ = 0;			       	    /* Chop off port */
	    if (isdigit(*port)) {

#ifdef DECNET
		sin->sdn_objnum = (unsigned char)(strtol(port, (char**)0, 10));
#else /* Internet */
		sin->sin_port = htons(atol(port));
#endif
	    } else {
		if (PROT_TRACE)
		    fprintf(TDEST, "ParseInet... No port indicated\n");
		free(host);
		return -1;
	    }
	}
    }

    /* Parse Internet host */
#ifdef DECNET
    /* read Decnet node name. @@ Should know about DECnet addresses, but it's
       probably worth waiting until the Phase transition from IV to V. */

    sin->sdn_nam.n_len = min(DN_MAXNAML, strlen(host));  /* <=6 in phase 4 */
    strncpy (sin->sdn_nam.n_name, host, sin->sdn_nam.n_len + 1);

    if (PROT_TRACE) fprintf(TDEST,  
	"DECnet: Parsed address as object number %d on host %.6s...\n",
		      sin->sdn_objnum, host);

#else /* Internet */

    /*	Parse host number if present */
    {
	BOOL numeric = YES;
	char *strptr = host;
	while (*strptr) {
	    if (!isdigit(*strptr) && *strptr != '.') {
		numeric = NO;
		break;
	    }
	    ++strptr;
	}
	if (numeric) {
	    sin->sin_addr.s_addr = inet_addr(host);	  /* See arpa/inet.h */
	} else {
	    if ((status = HTGetHostByName(host, sin, use_cur)) < 0) {
		free(host);
		return -1;
	    }
	}
	if (PROT_TRACE) {
	    fprintf(TDEST, "ParseInet... Parsed address as port %d on %s\n",
		    (int) ntohs(sin->sin_port),
		    HTInetString(sin));
	}
    }
#endif  /* Internet vs. Decnet */
    free(host);
    return status;
}


#ifdef OLD_CODE
/*	Derive the name of the host on which we are
**	-------------------------------------------
**
*/
PRIVATE void get_host_details NOARGS

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64		/* Arbitrary limit */
#endif

{
    char name[MAXHOSTNAMELEN+1];	/* The name of this host */
    struct hostent * phost;		/* Pointer to host -- See netdb.h */
    int namelength = sizeof(name);
    
    if (hostname) return;		/* Already done */
    gethostname(name, namelength);	/* Without domain */
    if (PROT_TRACE) fprintf(TDEST, "TCP......... Local host name is %s\n", name);
    StrAllocCopy(hostname, name);

#ifndef DECNET  /* Decnet ain't got no damn name server 8#OO */
    phost=gethostbyname(name);		/* See netdb.h */
    if (!phost) {
	if (PROT_TRACE) fprintf(TDEST, 
		"TCP......... Can't find my own internet node address for `%s'!!\n",
		name);
	return;  /* Fail! */
    }
    StrAllocCopy(hostname, phost->h_name);
    if (PROT_TRACE)
	fprintf(TDEST, "TCP......... Full local host name is %s\n", hostname);

#ifdef NEED_HOST_ADDRESS		/* no -- needs name server! */
    memcpy(&HTHostAddress, &phost->h_addr, phost->h_length);
    if (PROT_TRACE) fprintf(TDEST, "     Name server says that I am `%s' = %s\n",
	    hostname, HTInetString(&HTHostAddress));
#endif /* NEED_HOST_ADDRESS */

#endif /* not Decnet */
}
#endif /* OLD_CODE */


/*								HTGetDomainName
**	Returns the current domain name without the local host name.
**	The response is pointing to a static area that might be changed
**	using HTSetHostName().
**
**	Returns NULL on error, "" if domain name is not found
*/
PUBLIC CONST char *HTGetDomainName NOARGS
{
    CONST char *host = HTGetHostName();
    char *domain;
    if (host && *host) {
	if ((domain = strchr(host, '.')) != NULL)
	    return ++domain;
	else
	    return "";
    } else
	return NULL;
}



/*								HTSetHostName
**	Sets the current hostname inclusive domain name.
**	If this is not set then the default approach is used using
**	HTGetHostname().
*/
PUBLIC void HTSetHostName ARGS1(char *, host)
{
    if (host && *host) {
	char *strptr;
	StrAllocCopy(hostname, host);
	strptr = hostname;
	while (*strptr) {
	    *strptr = TOLOWER(*strptr);
	    strptr++;
	}
	if (*(hostname+strlen(hostname)-1) == '.')    /* Remove trailing dot */
	    *(hostname+strlen(hostname)-1) = '\0';
    } else {
	if (PROT_TRACE) fprintf(TDEST, "SetHostName. Bad argument ignored\n");
    }
}


/*								HTGetHostName
**	Returns the name of this host. It uses the following algoritm:
**
**	1) gethostname()
**	2) if the hostname doesn't contain any '.' try to read
**	   /etc/resolv.conf. If there is no domain line in this file then
**	3) Try getdomainname and do as the man pages say for resolv.conf (sun)
**		If there is no domain line in this file, then it is derived
**		from the domain name set by the domainname(1) command, usually
**		by removing the first component. For example, if the domain-
**		name is set to ``foo.podunk.edu'' then the default domain name
**		used will be ``pudunk.edu''.
**
**	This is the same procedure as used by res_init() and sendmail.
**
**	Return: hostname on success else NULL
*/
PUBLIC CONST char * HTGetHostName NOARGS
{
    BOOL got_it = NO;
    FILE *fp;
    char name[MAXHOSTNAMELEN+1];
    if (hostname) {		       			  /* If already done */
	if (*hostname)
	    return hostname;
	else
	    return NULL;		    /* We couldn't get the last time */
    }
    *(name+MAXHOSTNAMELEN) = '\0';
    if (gethostname(name, MAXHOSTNAMELEN)) { 	     /* Maybe without domain */
	if (PROT_TRACE)
	    fprintf(TDEST, "HostName.... Can't get host name\n");
	return NULL;
    }
    if (PROT_TRACE)
	fprintf(TDEST, "HostName.... Local host name is  `%s\'\n", name);
    StrAllocCopy(hostname, name);
    {
	char *strptr = strchr(hostname, '.');
	if (strptr != NULL)				   /* We have it all */
	    got_it = YES;
    }

#ifndef VMS
    /* Now try the resolver config file */
    if (!got_it && (fp = fopen(RESOLV_CONF, "r")) != NULL) {
	char buffer[80];
	*(buffer+79) = '\0';
	while (fgets(buffer, 79, fp)) {
	    if (!strncasecomp(buffer, "domain", 6)) {	
		char *domainstr = buffer+6;
		char *end;
		while (*domainstr == ' ' || *domainstr == '\t')
		    domainstr++;
		end = domainstr;
		while (*end && !isspace(*end))
		    end++;
		*end = '\0';
		if (*domainstr) {
		    StrAllocCat(hostname, ".");
		    StrAllocCat(hostname, domainstr);
		    got_it = YES;
		    break;
		}
	    }
	}
	fclose(fp);
    }

    /* If everything else has failed then try getdomainname */
#ifndef NO_GETDOMAINNAME
    if (!got_it) {
	if (getdomainname(name, MAXHOSTNAMELEN)) {
	    if (PROT_TRACE)
		fprintf(TDEST, "HostName.... Can't get domain name\n");
	    StrAllocCopy(hostname, "");
	    return NULL;
	}

	/* If the host name and the first part of the domain name are different
	   then use the former as it is more exact (I guess) */
	if (strncmp(name, hostname, (int) strlen(hostname))) {
	    char *domain = strchr(name, '.');
	    if (domain)
		StrAllocCat(hostname, domain);
	}
    }
#endif /* NO_GETDOMAINNAME */
#endif /* not VMS */

    {
	char *strptr = hostname;
	while (*strptr) {	    
	    *strptr = TOLOWER(*strptr);
	    strptr++;
	}
	if (*(hostname+strlen(hostname)-1) == '.')    /* Remove trailing dot */
	    *(hostname+strlen(hostname)-1) = '\0';
    }
    if (PROT_TRACE)
	fprintf(TDEST, "HostName.... Full host name is `%s\'\n", hostname);
    return hostname;

#ifndef DECNET  /* Decnet ain't got no damn name server 8#OO */
#ifdef OLD_CODE
			      /* Now we try to get information on the domain */
    {
	struct hostent *hostelement;
	if ((hostelement = gethostbyname(hostname)) == NULL) {
	    if (PROT_TRACE)
		fprintf(TDEST, "HostName.... Can't find host name on DNS\n");
	    FREE(hostname);
	    return NULL;
	}
	StrAllocCopy(hostname, (char *) hostelement->h_name);
    }
#endif /* OLD_CODE */
#endif /* not Decnet */
}


/*
**	Free the host name. Called from HTLibTerminate
*/
PUBLIC void HTFreeHostName NOARGS
{
    FREE(hostname);
}


/*							       HTSetMailAddress
**	Sets the current mail address plus host name and domain name.
**	If this is not set then the default approach is used using
**	HTGetMailAddress(). If the argument is NULL or "" then HTGetMailAddress
**	returns NULL on a succeding request.
*/
PUBLIC void HTSetMailAddress ARGS1(char *, address)
{
    if (!address || !*address)
	StrAllocCopy(mailaddress, "");
    else
	StrAllocCopy(mailaddress, address);
    if (TRACE)
	fprintf(TDEST, "SetMailAdr.. Set mail address to `%s\'\n",
		mailaddress);
}


/*							       HTGetMailAddress
**
**	Get the mail address of the current user on the current host. The
**	domain name used is the one initialized in HTSetHostName or
**	HTGetHostName. The login name is determined using (ordered):
**
**		getlogin
**		getpwuid(getuid())
**
**	The weakness about the last attempt is if the user has multiple
**	login names each with the same user ID. If this fails as well then:
**
**		LOGNAME environment variable
**		USER environment variable
**
**	Returns NULL if error else pointer to static string
*/
PUBLIC CONST char * HTGetMailAddress NOARGS
{
    char *login;
    CONST char *domain;
    struct passwd *pw_info;
    if (mailaddress) {
	if (*mailaddress)
	    return mailaddress;
	else
	    return NULL;       /* No luck the last time so we wont try again */
    }

#ifdef VMS
    if ((login = (char *) cuserid(NULL)) == NULL) {
        if (PROT_TRACE) fprintf(TDEST, "MailAddress. cuserid returns NULL\n");
    }

#else
#ifdef _WINDOWS
    login = "PCUSER";				  /* @@@ COULD BE BETTER @@@ */
#else /* Unix like... */
    if ((login = (char *) getlogin()) == NULL) {
	if (PROT_TRACE)
	    fprintf(TDEST, "MailAddress. getlogin returns NULL\n");
	if ((pw_info = getpwuid(getuid())) == NULL) {
	    if (PROT_TRACE)
		fprintf(TDEST, "MailAddress. getpwid returns NULL\n");
	    if ((login = getenv("LOGNAME")) == NULL) {
		if (PROT_TRACE)
		    fprintf(TDEST, "MailAddress. LOGNAME not found\n");
		if ((login = getenv("USER")) == NULL) {
		    if (PROT_TRACE)
			fprintf(TDEST,"MailAddress. USER not found\n");
		    return NULL;		/* I GIVE UP */
		}
	    }
	} else
	    login = pw_info->pw_name;
    }
#endif
#endif

    if (login) {
	StrAllocCopy(mailaddress, login);
	StrAllocCat(mailaddress, "@");
	if ((domain = HTGetHostName()) != NULL)
	    StrAllocCat(mailaddress, domain);
	else {
	    *mailaddress = '\0';
	    return NULL;			/* Domain name not available */
	}
	return mailaddress;
    }
    return NULL;
}


/*
**	Free the mail address. Called from HTLibTerminate
*/
PUBLIC void HTFreeMailAddress NOARGS
{
    FREE(mailaddress);
}


/* ------------------------------------------------------------------------- */
/*	       	      CONNECTION ESTABLISHMENT MANAGEMENT 		     */
/* ------------------------------------------------------------------------- */

/*								HTDoConnect()
**
**	Note: Any port indication in URL, e.g., as `host:port' overwrites
**	the default_port value.
**
**	Returns 0 if OK, -1 on error
*/
PUBLIC int HTDoConnect ARGS5(HTNetInfo *, net, char *, url,
			     u_short, default_port, u_long *, addr,
			     BOOL, use_cur)
{
    int status;
    char *p1 = HTParse(url, "", PARSE_HOST);
    char *at_sign;
    char *host;

    /* if theres an @ then use the stuff after it as a hostname */
    if((at_sign = strchr(p1, '@')) != NULL)
	host = at_sign+1;
    else
	host = p1;
    if (!*host) {
	HTErrorAdd(net->request, ERR_FATAL, NO, HTERR_NO_HOST,
		   NULL, 0, "HTDoConnect");
	free(p1);
	return -1;
    } else {
	if (PROT_TRACE)
	    fprintf(TDEST, "HTDoConnect. Looking up `%s\'\n", host);
    }

   /* Set up defaults */
    if (net->sockfd==INVSOC) {
	memset((void *) &net->sock_addr, '\0', sizeof(net->sock_addr));
#ifdef DECNET
	net->sock_addr.sdn_family = AF_DECnet;/* Family = DECnet, host order */
	net->sock_addr.sdn_objnum = DNP_OBJ;  /* Default: http object number */
#else  /* Internet */
	net->sock_addr.sin_family = AF_INET;
	net->sock_addr.sin_port = htons(default_port);
#endif
    }

    /* If we are trying to connect to a multi-homed host then loop here until
       success or we have tried all IP-addresses */
    do {
	if (net->sockfd==INVSOC) {
	    int hosts;
	    if ((hosts = HTParseInet(&net->sock_addr, host, use_cur)) < 0) {
		if (PROT_TRACE)
		    fprintf(TDEST, "HTDoConnect. Can't locate remote host `%s\'\n", host);
		HTErrorAdd(net->request, ERR_FATAL, NO, HTERR_NO_REMOTE_HOST,
			   (void *) host, strlen(host), "HTDoConnect");
		break;
	    };
	    if (!net->addressCount && hosts > 1)
		net->addressCount = hosts;
#ifdef DECNET
	    if ((net->sockfd=socket(AF_DECnet, SOCK_STREAM, 0))==INVSOC)
#else
	    if ((net->sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVSOC)
#endif
	    {
		HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO, "socket");
		break;
	    }
	    if (addr)
		*addr = ntohl(net->sock_addr.sin_addr.s_addr);
	    if (PROT_TRACE)
		fprintf(TDEST, "HTDoConnect. Created socket number %d\n",
			net->sockfd);

	    /* If non-blocking protocol then change socket status
	    ** I use FCNTL so that I can ask the status before I set it.
	    ** See W. Richard Stevens (Advan. Prog. in UNIX environment, p.364)
	    ** Be CAREFULL with the old `O_NDELAY' - it will not work as read()
	    ** returns 0 when blocking and NOT -1. FNDELAY is ONLY for BSD and
	    ** does NOT work on SVR4 systems. O_NONBLOCK is POSIX.
	    */
	    if (!HTProtocolBlocking(net->request)) {
#if defined(_WINDOWS) || defined(VMS)
		{
		    int enable = 1;
		    status = IOCTL(net->sockfd, FIONBIO, &enable);
		}
#else
		if((status = FCNTL(net->sockfd, F_GETFL, 0)) != -1) {
		    status |= O_NONBLOCK;			    /* POSIX */
		    status = FCNTL(net->sockfd, F_SETFL, status);
		}
#endif
		if (status == -1) {
		    if (PROT_TRACE)
			fprintf(TDEST, "HTDoConnect. Can NOT make socket non-blocking\n");
		} else if (PROT_TRACE)
		    fprintf(TDEST, "HTDoConnect. Using NON_BLOCKING I/O\n");
	    }
	    
	    /* If multi-homed host then start timer on connection */
	    if (net->addressCount >= 1)
		net->connecttime = time(NULL);
	}

	/* Check for interrupt */
	if (HTThreadIntr(net->sockfd)) {
	    if (NETCLOSE(net->sockfd) < 0)
		HTErrorSysAdd(net->request, ERR_FATAL, socerrno,NO,"NETCLOSE");
	    HTThreadState(net->sockfd, THD_CLOSE);
	    net->sockfd = INVSOC;
	    free(p1);
	    return HT_INTERRUPTED;
	}

	/* Do a connect */
	status = connect(net->sockfd, (struct sockaddr *) &net->sock_addr,
			 sizeof(net->sock_addr));
	/*
	 * According to the Sun man page for connect:
	 *     EINPROGRESS         The socket is non-blocking and the  con-
	 *                         nection cannot be completed immediately.
	 *                         It is possible to select(2) for  comple-
	 *                         tion  by  selecting the socket for writ-
	 *                         ing.
	 * According to the Motorola SVR4 man page for connect:
	 *     EAGAIN              The socket is non-blocking and the  con-
	 *                         nection cannot be completed immediately.
	 *                         It is possible to select for  completion
	 *                         by  selecting  the  socket  for writing.
	 *                         However, this is only  possible  if  the
	 *                         socket  STREAMS  module  is  the topmost
	 *                         module on  the  protocol  stack  with  a
	 *                         write  service  procedure.  This will be
	 *                         the normal case.
	 */
#ifdef EAGAIN
	if ((status < 0) && ((socerrno==EINPROGRESS) || (socerrno==EAGAIN)))
#else
	if ((status < 0) && (socerrno == EINPROGRESS))
#endif /* EAGAIN */
	{
	    if (PROT_TRACE)
		fprintf(TDEST, "HTDoConnect. WOULD BLOCK `%s'\n", host);
	    HTThreadState(net->sockfd, THD_SET_WRITE);
	    free(p1);
	    return HT_WOULD_BLOCK;
	}
	if (net->addressCount >= 1) {
	    net->connecttime = time(NULL) - net->connecttime;
	    if (status < 0) {
		if (socerrno==EISCONN) {  /* connect multi after would block */
		    HTThreadState(net->sockfd, THD_CLR_WRITE);
		    HTTCPAddrWeights(host, net->connecttime);
		    free(p1);
		    net->addressCount = 0;
		    return 0;
		}
		HTErrorSysAdd(net->request, ERR_NON_FATAL, socerrno, NO,
			      "connect");

		/* I have added EINVAL `invalid argument' as this is what I 
		   get back from a non-blocking connect where I should 
		   get `connection refused' on SVR4 */
		if (socerrno==ECONNREFUSED || socerrno==ETIMEDOUT ||
		    socerrno==ENETUNREACH || socerrno==EHOSTUNREACH ||
#ifdef __srv4__
		    socerrno==EHOSTDOWN || socerrno==EINVAL)
#else
		    socerrno==EHOSTDOWN)
#endif
		    net->connecttime += TCP_DELAY;
		else
		    net->connecttime += TCP_PENALTY;
		if (NETCLOSE(net->sockfd) < 0)
		    HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO, 
				  "NETCLOSE");
		HTThreadState(net->sockfd, THD_CLOSE);
		net->sockfd = INVSOC;
		HTTCPAddrWeights(host, net->connecttime);
	    } else {				   /* Connect on multi-homed */
		HTTCPAddrWeights(host, net->connecttime);
		free(p1);
		net->addressCount = 0;
		return 0;
	    }
	} else if (status < 0) {
	    if (socerrno==EISCONN) {     /* Connect single after would block */
		HTThreadState(net->sockfd, THD_CLR_WRITE);
		net->addressCount = 0;
		free(p1);
		return 0;
	    } else {
		HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO,
			      "connect");
		HTTCPCacheRemoveHost(host);
		if (NETCLOSE(net->sockfd) < 0)
		    HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO,
				  "NETCLOSE");
		HTThreadState(net->sockfd, THD_CLOSE);
		break;
	    }
	} else {				  /* Connect on single homed */
	    free(p1);
	    net->addressCount = 0;
	    return 0;
	}
    } while (--net->addressCount);

    if (PROT_TRACE)
	fprintf(TDEST, "HTDoConnect. Connect failed\n");
    free (p1);
    net->addressCount = 0;
    net->sockfd = INVSOC;
    return -1;
}


/*								HTDoAccept()
**
**	This function makes a non-blocking accept on a port and polls every
**	second until MAX_ACCEPT_POLL or interrupted by user.
**
**	BUGS Interrupted is not yet implemented!!!
**
**	Returns  HT_WOULD_BLOCK 	if waiting
**		 0		if OK,
**		 -1		on error
*/
PUBLIC int HTDoAccept ARGS1(HTNetInfo *, net)
{
    SockA soc_address;				/* SockA is defined in tcp.h */
    int status;
    int cnt;
    int soc_addrlen = sizeof(soc_address);
    if (net->sockfd==INVSOC) {
	if (PROT_TRACE) fprintf(TDEST, "HTDoAccept.. Bad socket number\n");
	return -1;
    }

    /* First make the socket non-blocking */
#if defined(_WINDOWS) || defined(VMS)
    {
	int enable = 1;		/* Need the variable! */
	status = IOCTL(net->sockfd, FIONBIO, enable);
    }
#else
    if((status = FCNTL(net->sockfd, F_GETFL, 0)) != -1) {
	status |= O_NONBLOCK;	/* POSIX */
	status = FCNTL(net->sockfd, F_SETFL, status);
    }
#endif
    if (status == -1) {
	HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO, "IOCTL");
	return -1;
    }

    /* Now poll every sekund */
    for(cnt=0; cnt<MAX_ACCEPT_POLL; cnt++) {
	if ((status = accept(net->sockfd, (struct sockaddr*) &soc_address,
			     &soc_addrlen)) != INVSOC) {
	    if (PROT_TRACE) fprintf(TDEST,
			       "HTDoAccept.. Accepted new socket %d\n",	
			       status);
	    return status;
	} else
	    HTErrorSysAdd(net->request, ERR_WARN, socerrno, YES, "accept");
	SLEEP(1);
    }
    
    /* If nothing has happened */    
    if (PROT_TRACE)
	fprintf(TDEST, "HTDoAccept.. Timed out, no connection!\n");
    HTErrorAdd(net->request, ERR_FATAL, NO, HTERR_TIME_OUT, NULL, 0,
	       "HTDoAccept");
    return -1;
}



Webmaster