/* Generic Communication Code HTTCP.c ** ========================== ** ** 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. */ #define HTTCP_WHOI_VERSION "HTTCP_whoi version 1.4b 5 Jul 2005" /* 5 Jul 05 WJS include unistd.h because of warnings on gb6. Include it unconditionally because we're lazy and also don't know what's going on on other OSes. In any case, this line should be in either tcp.h or HTUtils.h, but that would mean maintenance of another altered HTxxx file. See "lazy", above 23 Apr 04 WJS Change name of global version string so it won't show up in "grep version" output. 3 Dec 03 WJS Replace sys_errlist/sys_nerr w/strerror. Old stuff caused trouble w/linuxes Add version defn + non-executed code to try to get it into binary image. If we call HTTCP from jgofs 1.5 release 1.0, then there was the Cygwin emulator (say, 1.1), Bob's mod of Cygwin for his linux box (say 1.2 = 1.1+), and Cyndy's mod for her linux box (say 1.3 = 1.0+) 13 Oct 98 WJS Cygwin emulator. Looks for CYGWIN defn 1) Wants some SVR4 code but not its include file 2) Cygwin 'select' call does not seem to wait until EISCONN */ #include "HTUtils.h" #include "HTParse.h" #include "HTAlert.h" #include "HTAccess.h" #include "tcp.h" /* Defines SHORT_NAMES if necessary */ #include "unistd.h" /* For gb6 (Solaris 2.9. See comments, top */ #ifdef SHORT_NAMES #define HTInetStatus HTInStat #define HTInetString HTInStri #define HTParseInet HTPaInet #endif #ifdef __STDC__ #include #endif /* #define TRACE 1 */ #if defined(SVR4) && !defined(SCO) #include #endif /* Apparently needed for AIX 3.2. */ #ifndef FD_SETSIZE #define FD_SETSIZE 256 #endif /* Module-Wide variables */ PRIVATE char *hostname=0; /* The name of this host */ char httcp_whoi_vers[] = HTTCP_WHOI_VERSION; /* PUBLIC VARIABLES */ /* PUBLIC SockA HTHostAddress; */ /* The internet address of the host */ /* Valid after call to HTHostName() */ /* Encode INET status (as in sys/errno.h) inet_status() ** ------------------ ** ** On entry, ** where gives a description of what caused the error ** global errno gives the error number in the unix way. ** ** On return, ** returns a negative status in the unix way. */ #ifndef errno extern int errno; #endif /* errno */ /* Report Internet Error ** --------------------- */ #ifdef __STDC__ PUBLIC int HTInetStatus(char *where) #else PUBLIC int HTInetStatus(where) char *where; #endif { char *s; if ( (s = strerror(errno)) == NULL ) s = "unknown error"; CTRACE(tfp, "TCP: Error %d in `errno' after call to %s() failed.\n\t%s\n", errno, where, s); return -1; } /* 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) { int n; if ( (**pp<'0') || (**pp>'9')) { /* Null string is error */ *pstatus = -3; /* No number where one expeceted */ return 0; } n=0; while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0'; if (n>max_value) { *pstatus = -4; /* Cardinal outside range */ return 0; } return n; } /* 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) { 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; } /* 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, ** *sin is filled in. If no port is specified in str, that ** field is left unchanged in *sin. */ PUBLIC int HTParseInet ARGS2(SockA *,sin, CONST char *,str) { char *port; char host[256]; struct hostent *phost; /* Pointer to host - See netdb.h */ int numeric_addr; char *tmp; static char *cached_host = NULL; static char *cached_phost_h_addr = NULL; static int cached_phost_h_length = 0; strcpy(host, str); /* Take a copy we can mutilate */ /* Parse port number if present */ if (port=strchr(host, ':')) { *port++ = 0; /* Chop off port */ if (port[0]>='0' && port[0]<='9') { sin->sin_port = htons(atol(port)); } } /* Parse host number if present. */ numeric_addr = 1; for (tmp = host; *tmp; tmp++) { /* If there's a non-numeric... */ if ((*tmp < '0' || *tmp > '9') && *tmp != '.') { numeric_addr = 0; goto found_non_numeric_or_done; } } found_non_numeric_or_done: if (numeric_addr) { /* Numeric node address: */ sin->sin_addr.s_addr = inet_addr(host); /* See arpa/inet.h */ } else { /* Alphanumeric node name: */ if (cached_host && (strcmp (cached_host, host) == 0)) { #if 0 fprintf (stderr, "=-= Matched '%s' and '%s', using cached_phost.\n", cached_host, host); #endif memcpy(&sin->sin_addr, cached_phost_h_addr, cached_phost_h_length); } else { #if 0 fprintf (stderr, "=+= Fetching on '%s'\n", host); #endif phost = gethostbyname (host); if (!phost) { if (TRACE) fprintf (stderr, "HTTPAccess: Can't find internet node name `%s'.\n",host); return -1; /* Fail? */ } /* Free previously cached strings. */ if (cached_host) free (cached_host); if (cached_phost_h_addr) free (cached_phost_h_addr); /* Cache new stuff. */ cached_host = strdup (host); cached_phost_h_addr = (char *)calloc (phost->h_length + 1, 1); memcpy (cached_phost_h_addr, phost->h_addr, phost->h_length); #if 0 cached_phost_h_addr = strdup (phost->h_addr); #endif cached_phost_h_length = phost->h_length; memcpy(&sin->sin_addr, phost->h_addr, phost->h_length); } } if (TRACE) fprintf(stderr, "TCP: Parsed address as port %d, IP address %d.%d.%d.%d\n", (int)ntohs(sin->sin_port), (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 0; /* OK */ } /* Derive the name of the host on which we are ** ------------------------------------------- ** */ #ifdef __STDC__ PRIVATE void get_host_details(void) #else PRIVATE void get_host_details() #endif #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 /* Arbitrary limit */ #endif { char name[MAXHOSTNAMELEN+1]; /* The name of this host */ #ifdef NEED_HOST_ADDRESS /* no -- needs name server! */ struct hostent * phost; /* Pointer to host -- See netdb.h */ #endif int namelength = sizeof(name); if (hostname) return; /* Already done */ gethostname(name, namelength); /* Without domain */ CTRACE(tfp, "TCP: Local host name is %s\n", name); StrAllocCopy(hostname, name); #ifdef NEED_HOST_ADDRESS /* no -- needs name server! */ phost=gethostbyname(name); /* See netdb.h */ if (!phost) { if (TRACE) fprintf(stderr, "TCP: Can't find my own internet node address for `%s'!!\n", name); return; /* Fail! */ } StrAllocCopy(hostname, phost->h_name); memcpy(&HTHostAddress, &phost->h_addr, phost->h_length); if (TRACE) fprintf(stderr, " Name server says that I am `%s' = %s\n", hostname, HTInetString(&HTHostAddress)); #endif } #ifdef __STDC__ PUBLIC char * HTHostName(void) #else PUBLIC char * HTHostName() #endif { get_host_details(); return hostname; } #ifdef __STDC__ PUBLIC int HTDoConnect (char *url, char *protocol, int default_port, int *s) #else PUBLIC int HTDoConnect (url, protocol, default_port, s) char *url; char *protocol; int default_port; int *s; #endif { struct sockaddr_in soc_address; struct sockaddr_in *sin = &soc_address; int status; /* Set up defaults: */ sin->sin_family = AF_INET; sin->sin_port = htons(default_port); /* Get node name and optional port number: */ { char line[256]; char *p1 = HTParse(url, "", PARSE_HOST); int status; sprintf (line, "Looking up %s.", p1); HTProgress (line); status = HTParseInet(sin, p1); if (status) { sprintf (line, "Unable to locate remote host %s.", p1); HTProgress(line); free (p1); return HT_NO_DATA; } sprintf (line, "Making %s connection to %s.", protocol, p1); HTProgress (line); free (p1); } /* Now, let's get a socket set up from the server for the data: */ *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* * Make the socket non-blocking, so the connect can be canceled. * This means that when we issue the connect we should NOT * have to wait for the accept on the other end. */ { int ret; int val = 1; char line[256]; ret = ioctl(*s, FIONBIO, &val); if (ret == -1) { sprintf (line, "Could not make connection non-blocking."); HTProgress(line); } } HTClearActiveIcon(); /* * Issue the connect. Since the server can't do an instantaneous accept * and we are non-blocking, this will almost certainly return a negative * status. */ status = connect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address)); /* * 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. */ #if defined(SVR4) || defined(CYGWIN) if ((status < 0) && ((errno == EINPROGRESS)||(errno == EAGAIN))) #else if ((status < 0) && (errno == EINPROGRESS)) #endif /* SVR4 */ { #ifdef CYGWIN int loopct; #endif int ret; int ok_errno; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100000; #ifdef CYGWIN loopct = 0; /* Test suggested that MAXLOOP of 17 would work on my Dell */ #define MAXLOOP 1000 #endif ret = 0; while (ret <= 0) { fd_set writefds; int intr; FD_ZERO(&writefds); FD_SET(*s, &writefds); #ifdef __hpux ret = select(FD_SETSIZE, NULL, (int *)&writefds, NULL, &timeout); #else ret = select(FD_SETSIZE, NULL, &writefds, NULL, &timeout); #endif /* * Again according to the Sun and Motorola man pagse for connect: * EALREADY The socket is non-blocking and a previ- * ous connection attempt has not yet been * completed. * Thus if the errno is NOT EALREADY we have a real error, and * should break out here and return that error. * Otherwise if it is EALREADY keep on trying to complete the * connection. */ if ( (ret < 0) && (errno != EALREADY) ) { status = ret; break; } else if (ret > 0) { /* * Extra check here for connection success, if we try to connect * again, and get EISCONN, it means we have a successful * connection. */ /* Under Cygwin, am getting EALREADY after following connect unless * running under debugger, in which case am getting EISCONN. * Seems to me that in this area of code, the "final" connect * should either give EISCONN or normal status; eg, we should poll * until that happens. Maybe that's the idea of the timeout in * the select; however, that's not how this block is coded, and * under Cygwin anyway, select finishes normally but seems not to * wait. I decided to code the poll. WJS */ status = connect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address)); #ifdef CYGWIN if ( (status < 0) && (errno == EALREADY) ) if (loopct++ <= MAXLOOP) ret = 0; /* If sanity check OK, */ /* Set up to try again */ #endif if ( (status < 0) && (errno == EISCONN) ) status = 0; if (ret != 0) break; } else { /* * The select says we aren't ready yet. * Try to connect again to make sure. If we don't get EALREADY * or EISCONN, something has gone wrong. Break out and report it. * For some reason SVR4 returns EAGAIN here instead of EALREADY, * even though the man page says it should be EALREADY. */ status = connect(*s, (struct sockaddr*)&soc_address, sizeof(soc_address)); ok_errno = (errno==EALREADY) || (errno==EISCONN); #if defined(SVR4) || defined(CYGWIN) ok_errno = ok_errno || (errno==EAGAIN); #endif /* SVR4 */ if ( (status < 0) && ! ok_errno) break; } intr = HTCheckActiveIcon(1); if (intr) { if (TRACE) fprintf (stderr, "*** INTERRUPTED in middle of connect.\n"); status = HT_INTERRUPTED; errno = EINTR; break; } } } /* * Make the socket blocking again on good connect */ if (status >= 0) { int ret; int val = 0; char line[256]; ret = ioctl(*s, FIONBIO, &val); if (ret == -1) { sprintf (line, "Could not restore socket to blocking."); HTProgress(line); } } /* * Else the connect attempt failed or was interrupted. * so close up the socket. */ else { close(*s); } return status; } /* This is so interruptible reads can be implemented cleanly. */ #ifdef __STDC__ int HTDoRead (int fildes, void *buf, unsigned nbyte) #else int HTDoRead (fildes, buf, nbyte) int fildes; void *buf; unsigned nbyte; #endif { int ready, ret, intr; fd_set readfds; struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 100000; ready = 0; while (!ready) { FD_ZERO(&readfds); FD_SET(fildes, &readfds); #ifdef __hpux ret = select(FD_SETSIZE, (int *)&readfds, NULL, NULL, &timeout); #else ret = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); #endif if (ret < 0) { return -1; } else if (ret > 0) { ready = 1; } else { intr = HTCheckActiveIcon(1); if (intr) { return HT_INTERRUPTED; } } } return read (fildes, buf, nbyte); }