diff options
Diffstat (limited to 'WWW/Library/Implementation/HTTCP.c')
-rw-r--r-- | WWW/Library/Implementation/HTTCP.c | 2623 |
1 files changed, 2623 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTTCP.c b/WWW/Library/Implementation/HTTCP.c new file mode 100644 index 0000000..a3bfda6 --- /dev/null +++ b/WWW/Library/Implementation/HTTCP.c @@ -0,0 +1,2623 @@ +/* + * $LynxId: HTTCP.c,v 1.163 2022/04/01 23:18:35 Rajeev.V.Pillai Exp $ + * + * 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. + * 13 Sep 93 MD Added correct return of vmserrorno for HTInetStatus. + * Added decoding of vms error message for MULTINET. + * 7-DEC-1993 Bjorn S. Nilsson, ALEPH, CERN, VMS UCX ioctl() changes + * (done of Mosaic) + * 19 Feb 94 Danny Mayer Added Bjorn Fixes to Lynx version + * 7 Mar 94 Danny Mayer Added Fix UCX version for full domain name + * 20 May 94 Andy Harper Added support for CMU TCP/IP transport + * 17 Nov 94 Andy Harper Added support for SOCKETSHR transport + * 16 Jul 95 S. Bjorndahl added kluge to deal with LIBCMU bug + */ + +#define LYNX_ADDRINFO struct addrinfo +#define LYNX_HOSTENT struct hostent + +#include <HTUtils.h> +#include <HTParse.h> +#include <HTAlert.h> +#include <HTTCP.h> +#include <LYGlobalDefs.h> /* added for no_suspend */ +#include <LYUtils.h> + +#ifdef NSL_FORK +#include <signal.h> +#include <www_wait.h> +#define FREE_NSL_FORK(p) { FREE(p); } +#elif defined(_WINDOWS_NSL) +#define FREE_NSL_FORK(p) if ((p) == gbl_phost) { FREE(p); } +#else +#define FREE_NSL_FORK(p) /* nothing */ +#endif /* NSL_FORK */ + +#ifdef HAVE_RESOLV_H +#include <resolv.h> +#endif + +#ifdef __DJGPP__ +#include <netdb.h> +#endif /* __DJGPP__ */ + +#define OK_HOST(p) ((p) != 0 && ((p)->h_length) != 0) + +#ifdef SVR4_BSDSELECT +int BSDselect(int nfds, + fd_set * readfds, + fd_set * writefds, + fd_set * exceptfds, + struct timeval *select_timeout); + +#ifdef select +#undef select +#endif /* select */ +#define select BSDselect +#ifdef SOCKS +#ifdef Rselect +#undef Rselect +#endif /* Rselect */ +#define Rselect BSDselect +#endif /* SOCKS */ +#endif /* SVR4_BSDSELECT */ + +#include <LYLeaks.h> + +/* + * Module-Wide variables + */ +static char *hostname = NULL; /* The name of this host */ + +/* + * PUBLIC VARIABLES + */ +#ifdef SOCKS +unsigned long socks_bind_remoteAddr; /* for long Rbind */ +#endif /* SOCKS */ + +/* 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. + */ + +#ifdef DECL_SYS_ERRLIST +extern char *sys_errlist[]; /* see man perror on cernvax */ +extern int sys_nerr; +#endif /* DECL_SYS_ERRLIST */ + +#ifdef __DJGPP__ +static int ResolveYield(void) +{ + return HTCheckForInterrupt()? 0 : 1; +} +#endif + +#if defined(VMS) && defined(UCX) +/* + * A routine to mimic the ioctl function for UCX. + * Bjorn S. Nilsson, 25-Nov-1993. Based on an example in the UCX manual. + */ +#include <HTioctl.h> + +int HTioctl(int d, + int request, + int *argp) +{ + int sdc, status; + unsigned short fun, iosb[4]; + char *p5, *p6; + struct comm { + int command; + char *addr; + } ioctl_comm; + struct it2 { + unsigned short len; + unsigned short opt; + struct comm *addr; + } ioctl_desc; + + if ((sdc = vaxc$get_sdc(d)) == 0) { + set_errno(EBADF); + return -1; + } + ioctl_desc.opt = UCX$C_IOCTL; + ioctl_desc.len = sizeof(struct comm); + + ioctl_desc.addr = &ioctl_comm; + if (request & IOC_OUT) { + fun = IO$_SENSEMODE; + p5 = 0; + p6 = (char *) &ioctl_desc; + } else { + fun = IO$_SETMODE; + p5 = (char *) &ioctl_desc; + p6 = 0; + } + ioctl_comm.command = request; + ioctl_comm.addr = (char *) argp; + status = sys$qiow(0, sdc, fun, iosb, 0, 0, 0, 0, 0, 0, p5, p6); + if (!(status & 01)) { + set_errno(status); + return -1; + } + if (!(iosb[0] & 01)) { + set_errno(iosb[0]); + return -1; + } + return 0; +} +#endif /* VMS && UCX */ + +#define MY_FORMAT "TCP: Error %d in `SOCKET_ERRNO' after call to %s() failed.\n\t%s\n" + /* third arg is transport/platform specific */ + +/* Report Internet Error + * --------------------- + */ +int HTInetStatus(const char *where) +{ + int status; + int saved_errno = errno; + +#ifdef VMS +#ifdef MULTINET + SOCKET_ERRNO = vmserrno; +#endif /* MULTINET */ +#endif /* VMS */ + +#ifdef VM + CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where, + "(Error number not translated)")); /* What Is the VM equiv? */ +#define ER_NO_TRANS_DONE +#endif /* VM */ + +#ifdef VMS +#ifdef MULTINET + CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where, + vms_errno_string())); +#else + CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where, + ((SOCKET_ERRNO > 0 && SOCKET_ERRNO <= 65) ? + strerror(SOCKET_ERRNO) : "(Error number not translated)"))); +#endif /* MULTINET */ +#define ER_NO_TRANS_DONE +#endif /* VMS */ + +#ifdef HAVE_STRERROR + CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where, + strerror(SOCKET_ERRNO))); +#define ER_NO_TRANS_DONE +#endif /* HAVE_STRERROR */ + +#ifndef ER_NO_TRANS_DONE + CTRACE((tfp, MY_FORMAT, SOCKET_ERRNO, where, + (SOCKET_ERRNO < sys_nerr ? + sys_errlist[SOCKET_ERRNO] : "Unknown error"))); +#endif /* !ER_NO_TRANS_DONE */ + +#ifdef VMS +#ifndef MULTINET + CTRACE((tfp, + " Unix error number (SOCKET_ERRNO) = %ld dec\n", + SOCKET_ERRNO)); + CTRACE((tfp, + " VMS error (vaxc$errno) = %lx hex\n", + vaxc$errno)); +#endif /* MULTINET */ +#endif /* VMS */ + + set_errno(saved_errno); + +#ifdef VMS + /* + * uerrno and errno happen to be zero if vmserrno <> 0 + */ +#ifdef MULTINET + status = -vmserrno; +#else + status = -vaxc$errno; +#endif /* MULTINET */ +#else + status = -SOCKET_ERRNO; +#endif /* VMS */ + return status; +} + +/* 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 + */ +unsigned int HTCardinal(int *pstatus, + char **pp, + unsigned int max_value) +{ + unsigned int n; + + if ((**pp < '0') || (**pp > '9')) { /* Null string is error */ + *pstatus = -3; /* No number where one expected */ + return 0; + } + + n = 0; + while ((**pp >= '0') && (**pp <= '9')) + n = n * 10 + (unsigned) (*((*pp)++) - '0'); + + if (n > max_value) { + *pstatus = -4; /* Cardinal outside range */ + return 0; + } + + return n; +} + +#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. + */ +const char *HTInetString(LY_SOCKADDR * soc_A) +{ +#ifdef INET6 + static char hostbuf[MAXHOSTNAMELEN]; + struct sockaddr *soc_addr = &(soc_A->soc_address); + + getnameinfo(soc_addr, + SA_LEN(soc_addr), + hostbuf, (socklen_t) sizeof(hostbuf), + NULL, 0, + NI_NUMERICHOST); + return hostbuf; +#else + struct sockaddr_in *soc_in = &(soc_A->soc_in); + static char string[20]; + + sprintf(string, "%d.%d.%d.%d", + (int) *((unsigned char *) (&soc_in->sin_addr) + 0), + (int) *((unsigned char *) (&soc_in->sin_addr) + 1), + (int) *((unsigned char *) (&soc_in->sin_addr) + 2), + (int) *((unsigned char *) (&soc_in->sin_addr) + 3)); + return string; +#endif /* INET6 */ +} +#endif /* !DECNET */ + +/* Check whether string is a valid Internet hostname - kw + * ------------------------------------------------- + * + * Checks whether + * - contains only valid chars for domain names (actually, the + * restrictions are somewhat relaxed), + * - no leading dots or empty segments, + * - no segment starts with '-' or '+' [this protects telnet command], + * - max. length of dot-separated segment <= 63 (RFC 1034,1035), + * - total length <= 254 (if it ends with dot) or 253 (otherwise) + * [an interpretation of RFC 1034,1035, although RFC 1123 + * suggests 255 as limit - kw]. + * + * Note: user (before '@') and port (after ':') components from + * host part of URL should be already stripped (if appropriate) + * from the input string. + * + * On exit, + * returns 1 if valid, otherwise 0. + */ +BOOL valid_hostname(char *name) +{ + int i = 1, iseg = 0; + char *cp = name; + + if (!(name && *name)) + return NO; + for (; (*cp && i <= 253); cp++, i++) { + if (*cp == '.') { + if (iseg == 0) { + return NO; + } else { + iseg = 0; + continue; + } + } else if (iseg == 0 && (*cp == '-' || *cp == '+')) { + return NO; + } else if (++iseg > 63) { + return NO; + } + if (!isalnum(UCH(*cp)) && + *cp != '-' && *cp != '_' && + *cp != '$' && *cp != '+') { + return NO; + } + } + return (BOOL) (*cp == '\0' || (*cp == '.' && iseg != 0 && cp[1] == '\0')); +} + +/* for transfer of status from child to parent: */ +typedef struct _statuses { + size_t rehostentlen; + int h_length; + int child_errno; /* sometimes useful to pass this on */ + int child_h_errno; + BOOL h_errno_valid; +} STATUSES; + +/* + * Function to allow us to be killed with a normal signal (not + * SIGKILL), but don't go through normal libc exit() processing, which + * would screw up parent's stdio. -BL + */ +#ifdef NSL_FORK +static void quench(int sig GCC_UNUSED) +{ + _exit(2); +} +#endif + +int lynx_nsl_status = HT_OK; + +#define DEBUG_HOSTENT /* disable in case of problems */ +#define DEBUG_HOSTENT_CHILD /* for NSL_FORK, may screw up trace file */ + +/* + * dump_hostent - dumps the contents of a LYNX_HOSTENT to the + * trace log or stderr, including all pointer values, strings, and + * addresses, in a format inspired by gdb's print format. - kw + */ +static void dump_hostent(const char *msgprefix, + const void *data) +{ + if (TRACE) { + int i; + char **pcnt; + const LYNX_HOSTENT *phost = data; + + CTRACE((tfp, "%s: %p ", msgprefix, (const void *) phost)); + if (phost) { + CTRACE((tfp, "{ h_name = %p", (void *) phost->h_name)); + if (phost->h_name) { + CTRACE((tfp, " \"%s\",", phost->h_name)); + } else { + CTRACE((tfp, ",")); + } + CTRACE((tfp, "\n\t h_aliases = %p", (void *) phost->h_aliases)); + if (phost->h_aliases) { + CTRACE((tfp, " {")); + for (pcnt = phost->h_aliases; *pcnt; pcnt++) { + CTRACE((tfp, "%s %p \"%s\"", + (pcnt == phost->h_aliases ? " " : ", "), + (void *) *pcnt, *pcnt)); + } + CTRACE((tfp, "%s0x0 },\n\t", + (*phost->h_aliases ? ", " : " "))); + } else { + CTRACE((tfp, ",\n\t")); + } + CTRACE((tfp, " h_addrtype = %d,", phost->h_addrtype)); + CTRACE((tfp, " h_length = %d,\n\t", phost->h_length)); + CTRACE((tfp, " h_addr_list = %p", (void *) phost->h_addr_list)); + if (phost->h_addr_list) { + CTRACE((tfp, " {")); + for (pcnt = phost->h_addr_list; *pcnt; pcnt++) { + CTRACE((tfp, "%s %p", + (pcnt == phost->h_addr_list ? "" : ","), + (void *) *pcnt)); + for (i = 0; i < phost->h_length; i++) { + CTRACE((tfp, "%s%d%s", (i == 0 ? " \"" : "."), + (int) *((unsigned char *) (*pcnt) + i), + (i + 1 == phost->h_length ? "\"" : ""))); + } + } + if (*phost->h_addr_list) { + CTRACE((tfp, ", 0x0 } }")); + } else { + CTRACE((tfp, " 0x0 } }")); + } + } else { + CTRACE((tfp, "}")); + } + } + CTRACE((tfp, "\n")); + fflush(tfp); + } +} + +#ifdef NSL_FORK + +/* + * Even though it is a small amount, we cannot count on reading the whole + * struct via a pipe in one read -TD + */ +static unsigned read_bytes(int fd, char *buffer, size_t length) +{ + unsigned result = 0; + + while (length != 0) { + unsigned got = (unsigned) read(fd, buffer, length); + + if ((int) got > 0) { + result += got; + buffer += got; + length -= got; + } else { + break; + } + } + return result; +} + +static unsigned read_hostent(int fd, char *buffer, size_t length) +{ + unsigned have = read_bytes(fd, buffer, length); + + if (have) { + LYNX_HOSTENT *data = (LYNX_HOSTENT *) (void *) buffer; + char *next_char = (char *) data + sizeof(*data); + char **next_ptr = (char **) (void *) next_char; + long offset = 0; + int n; + int num_addrs = 0; + int num_aliases = 0; + + if (data->h_addr_list) { + data->h_addr_list = next_ptr; + while (next_ptr[num_addrs] != 0) { + ++num_addrs; + } + next_ptr += (num_addrs + 1); + next_char += (size_t) (num_addrs + 1) * sizeof(data->h_addr_list[0]); + } + + if (data->h_aliases) { + data->h_aliases = next_ptr; + while (next_ptr[num_aliases] != 0) { + ++num_aliases; + } + next_char += (size_t) (num_aliases + 1) * sizeof(data->h_aliases[0]); + } + + if (data->h_name) { + offset = next_char - data->h_name; + data->h_name = next_char; + } else if (data->h_addr_list) { + offset = next_char - (char *) data->h_addr_list[0]; + } else if (data->h_aliases) { + offset = next_char - (char *) data->h_aliases[0]; + } + + if (data->h_addr_list) { + for (n = 0; n < num_addrs; ++n) { + data->h_addr_list[n] += offset; + } + } + + if (data->h_aliases) { + for (n = 0; n < num_aliases; ++n) { + data->h_aliases[n] += offset; + } + } + } + + return have; +} +#endif /* NSL_FORK */ + +/* + * fill_rehostent - copies as much as possible relevant content from + * the LYNX_HOSTENT pointed to by phost to the char buffer given + * by rehostent, subject to maximum output length rehostentsize, + * following pointers and building self-contained output which can be + * cast to a LYNX_HOSTENT. - kw + * See also description of LYGetHostByName. + */ +#if defined(NSL_FORK) || defined(_WINDOWS_NSL) + +#define REHOSTENT_SIZE 128 /* not bigger than pipe buffer! */ + +typedef struct { + LYNX_HOSTENT h; + char rest[REHOSTENT_SIZE]; +} AlignedHOSTENT; + +static size_t fill_rehostent(void **rehostent, + const LYNX_HOSTENT *phost) +{ + static const char *this_func = "fill_rehostent"; + + LYNX_HOSTENT *data = 0; + int num_addrs = 0; + int num_aliases = 0; + char *result = 0; + char *p_next_char; + char **p_next_charptr; + size_t name_len = 0; + size_t need = sizeof(LYNX_HOSTENT); + int n; + + if (!phost) + return 0; + + if (phost->h_name) { + name_len = strlen(phost->h_name); + need += name_len + 1; + } + if (phost->h_addr_list) { + while (phost->h_addr_list[num_addrs]) { + num_addrs++; + } + need += ((size_t) num_addrs + 1) * ((size_t) phost->h_length + + sizeof(phost->h_addr_list[0])); + } + if (phost->h_aliases) { + while (phost->h_aliases[num_aliases]) { + need += strlen(phost->h_aliases[num_aliases]) + 1; + num_aliases++; + } + need += ((size_t) num_aliases + 1) * sizeof(phost->h_aliases[0]); + } + + if ((result = calloc(need, sizeof(char))) == 0) + outofmem(__FILE__, this_func); + + *rehostent = result; + + data = (LYNX_HOSTENT *) (void *) result; + + data->h_addrtype = phost->h_addrtype; + data->h_length = phost->h_length; + + p_next_char = result + sizeof(LYNX_HOSTENT); + + p_next_charptr = (char **) (void *) p_next_char; + if (phost->h_addr_list) + p_next_char += (size_t) (num_addrs + 1) * sizeof(phost->h_addr_list[0]); + if (phost->h_aliases) + p_next_char += (size_t) (num_aliases + 1) * sizeof(phost->h_aliases[0]); + + if (phost->h_name) { + data->h_name = p_next_char; + strcpy(p_next_char, phost->h_name); + p_next_char += name_len + 1; + } + + if (phost->h_addr_list) { + data->h_addr_list = p_next_charptr; + for (n = 0; n < num_addrs; ++n) { + MemCpy(p_next_char, phost->h_addr_list[n], phost->h_length); + *p_next_charptr++ = p_next_char; + p_next_char += phost->h_length; + } + ++p_next_charptr; + } + + if (phost->h_aliases) { + data->h_aliases = p_next_charptr; + for (n = 0; n < num_aliases; ++n) { + strcpy(p_next_char, phost->h_aliases[n]); + *p_next_charptr++ = p_next_char; + p_next_char += strlen(phost->h_aliases[n]) + 1;; + } + } + return need; +} +#endif /* NSL_FORK */ + +/* + * This chunk of code is used in both win32 and cygwin. + */ +#if defined(_WINDOWS_NSL) +static LYNX_HOSTENT *gbl_phost; /* Pointer to host - See netdb.h */ + +#if !(defined(__CYGWIN__) && defined(NSL_FORK)) +static int donelookup; + +static unsigned long __stdcall _fork_func(void *arg) +{ + const char *host = (const char *) arg; + static AlignedHOSTENT aligned_full_rehostent; + char *rehostent = (char *) &aligned_full_rehostent; + size_t rehostentlen = 0; + +#ifdef SH_EX + unsigned long addr; + + addr = (unsigned long) inet_addr(host); + if (addr != INADDR_NONE) + gbl_phost = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); + else + gbl_phost = gethostbyname(host); +#else + gbl_phost = gethostbyname(host); +#endif + + if (gbl_phost) { + rehostentlen = fill_rehostent((void **) &rehostent, gbl_phost); + if (rehostentlen == 0) { + gbl_phost = (LYNX_HOSTENT *) NULL; + } else { + gbl_phost = (LYNX_HOSTENT *) rehostent; + } + } + + donelookup = TRUE; + return (unsigned long) (gbl_phost); +} +#endif /* __CYGWIN__ */ +#endif /* _WINDOWS_NSL */ + +#ifdef NSL_FORK + +#ifndef HAVE_H_ERRNO +#undef h_errno +#define h_errno my_errno +static int my_errno; + +#else /* we do HAVE_H_ERRNO: */ +#ifndef h_errno /* there may be a macro as well as the extern data */ +extern int h_errno; +#endif +#endif + +static BOOL setup_nsl_fork(void (*really) (const char *, + const char *, + STATUSES *, + void **), + unsigned (*readit) (int, char *, size_t), + void (*dumpit) (const char *, const void *), + const char *host, + const char *port, + void **rehostent) +{ + static const char *this_func = "setup_nsl_fork"; + + STATUSES statuses; + + /* + * fork-based gethostbyname() with checks for interrupts. + * - Tom Zerucha (tz@execpc.com) & FM + */ + int got_rehostent = 0; + +#if HAVE_SIGACTION + sigset_t old_sigset; + sigset_t new_sigset; +#endif + /* + * Pipe, child pid, status buffers, start time, select() control + * variables. + */ + int fpid, waitret; + int pfd[2], selret; + unsigned readret; + +#ifdef HAVE_TYPE_UNIONWAIT + union wait waitstat; + +#else + int waitstat = 0; +#endif + time_t start_time = time((time_t *) 0); + fd_set readfds; + struct timeval one_second; + long dns_patience = 30; /* how many seconds will we wait for DNS? */ + int child_exited = 0; + + memset(&statuses, 0, sizeof(STATUSES)); + statuses.h_errno_valid = NO; + + /* + * Reap any children that have terminated since last time through. + * This might include children that we killed, then waited with WNOHANG + * before they were actually ready to be reaped. (Should be max of 1 + * in this state, but the loop is safe if waitpid() is implemented + * correctly: returns 0 when children exist but none have exited; -1 + * with errno == ECHILD when no children.) -BL + */ + do { + waitret = waitpid(-1, 0, WNOHANG); + } while (waitret > 0 || (waitret == -1 && errno == EINTR)); + waitret = 0; + + IGNORE_RC(pipe(pfd)); + +#if HAVE_SIGACTION + /* + * Attempt to prevent a rare situation where the child could execute + * the Lynx signal handlers because it gets killed before it even has a + * chance to reset its handlers, resulting in bogus 'Exiting via + * interrupt' message and screen corruption or worse. + * Should that continue to be reported, for systems without + * sigprocmask(), we need to find a different solutions for those. - + * kw 19990430 + */ + sigemptyset(&new_sigset); + sigaddset(&new_sigset, SIGTERM); + sigaddset(&new_sigset, SIGINT); +#ifndef NOSIGHUP + sigaddset(&new_sigset, SIGHUP); +#endif /* NOSIGHUP */ +#ifdef SIGTSTP + sigaddset(&new_sigset, SIGTSTP); +#endif /* SIGTSTP */ +#ifdef SIGWINCH + sigaddset(&new_sigset, SIGWINCH); +#endif /* SIGWINCH */ + sigprocmask(SIG_BLOCK, &new_sigset, &old_sigset); +#endif /* HAVE_SIGACTION */ + + if ((fpid = fork()) == 0) { + /* + * Child - for the long call. + * + * Make sure parent can kill us at will. -BL + */ + (void) signal(SIGTERM, quench); + + /* + * Also make sure the child does not run one of the signal handlers + * that may have been installed by Lynx if one of those signals + * occurs. For example we don't want the child to remove temp + * files on ^C, let the parent deal with that. - kw + */ + (void) signal(SIGINT, quench); +#ifndef NOSIGHUP + (void) signal(SIGHUP, quench); +#endif /* NOSIGHUP */ +#ifdef SIGTSTP + if (no_suspend) + (void) signal(SIGTSTP, SIG_IGN); + else + (void) signal(SIGTSTP, SIG_DFL); +#endif /* SIGTSTP */ +#ifdef SIGWINCH + (void) signal(SIGWINCH, SIG_IGN); +#endif /* SIGWINCH */ +#ifndef __linux__ +#ifndef DOSPATH + signal(SIGBUS, SIG_DFL); +#endif /* DOSPATH */ +#endif /* !__linux__ */ + signal(SIGSEGV, SIG_DFL); + signal(SIGILL, SIG_DFL); + +#if HAVE_SIGACTION + /* Restore signal mask to whatever it was before the fork. -kw */ + sigprocmask(SIG_SETMASK, &old_sigset, NULL); +#endif /* HAVE_SIGACTION */ + + /* + * Child won't use read side. -BL + */ + close(pfd[0]); +#ifdef HAVE_H_ERRNO + /* to detect cases when it doesn't get set although it should */ + h_errno = -2; +#endif + set_errno(0); + really(host, port, &statuses, rehostent); + /* + * Send variables indicating status of lookup to parent. That + * includes rehostentlen, which the parent will use as the size for + * the second read (if > 0). + */ + if (!statuses.child_errno) + statuses.child_errno = errno; + IGNORE_RC(write(pfd[1], &statuses, sizeof(statuses))); + + if (statuses.rehostentlen) { + /* + * Return our resulting rehostent through pipe... + */ + IGNORE_RC(write(pfd[1], *rehostent, statuses.rehostentlen)); + close(pfd[1]); + _exit(0); + } else { + /* + * ... or return error as exit code. + */ + _exit(1); + } + } +#if HAVE_SIGACTION + /* + * (parent) Restore signal mask to whatever it was before the fork. - + * kw + */ + sigprocmask(SIG_SETMASK, &old_sigset, NULL); +#endif /* HAVE_SIGACTION */ + + /* + * (parent) Wait until lookup finishes, or interrupt, or cycled too + * many times (just in case) -BL + */ + + close(pfd[1]); /* parent won't use write side -BL */ + + if (fpid < 0) { /* fork failed */ + close(pfd[0]); + goto failed; + } + + while (child_exited || (long) (time((time_t *) 0) - start_time) < dns_patience) { + + FD_ZERO(&readfds); + /* + * This allows us to abort immediately, not after 1-second timeout, + * when user hits abort key. Can't do this when using SLANG (or at + * least I don't know how), so SLANG users must live with up-to-1s + * timeout. -BL + * + * Whoops -- we need to make sure stdin is actually selectable! + * /dev/null isn't, on some systems, which makes some useful Lynx + * invocations fail. -BL + */ + { + int kbd_fd = LYConsoleInputFD(TRUE); + + if (kbd_fd != INVSOC) { + FD_SET(kbd_fd, &readfds); + } + } + + one_second.tv_sec = 1; + one_second.tv_usec = 0; + FD_SET(pfd[0], &readfds); + + /* + * Return when data received, interrupted, or failed. If nothing + * is waiting, we sleep for 1 second in select(), to be nice to the + * system. -BL + */ +#ifdef SOCKS + if (socks_flag) + selret = Rselect(pfd[0] + 1, &readfds, NULL, NULL, &one_second); + else +#endif /* SOCKS */ + selret = select(pfd[0] + 1, &readfds, NULL, NULL, &one_second); + + if ((selret > 0) && FD_ISSET(pfd[0], &readfds)) { + /* + * First get status, including length of address. -BL, kw + */ + readret = read_bytes(pfd[0], (char *) &statuses, sizeof(statuses)); + if (readret == sizeof(statuses)) { + h_errno = statuses.child_h_errno; + set_errno(statuses.child_errno); +#ifdef HAVE_H_ERRNO + if (statuses.h_errno_valid) { + lynx_nsl_status = HT_H_ERRNO_VALID; + /* + * If something went wrong in the child process other + * than normal lookup errors, and it appears that we + * have enough info to know what went wrong, generate + * diagnostic output. ENOMEM observed on linux in + * processes constrained with ulimit. It would be too + * unkind to abort the session, access to local files + * or through a proxy may still work. - kw + */ + if ( +#ifdef NETDB_INTERNAL /* linux glibc: defined in netdb.h */ + (errno && h_errno == NETDB_INTERNAL) || +#endif + (errno == ENOMEM && + statuses.rehostentlen == 0 && + /* should probably be NETDB_INTERNAL if child + memory exhausted, but we may find that + h_errno remains unchanged. - kw */ + h_errno == -2)) { +#ifndef MULTINET + HTInetStatus("CHILD gethostbyname"); +#endif + HTAlert(LYStrerror(statuses.child_errno)); + if (errno == ENOMEM) { + /* + * Not much point in continuing, right? Fake a + * 'z', should shorten pointless guessing + * cycle. - kw + */ + LYFakeZap(YES); + } + } + } +#endif /* HAVE_H_ERRNO */ + if (statuses.rehostentlen != 0) { + /* + * Then get the full reorganized hostent. -BL, kw + */ + if ((*rehostent = malloc(statuses.rehostentlen)) == 0) + outofmem(__FILE__, this_func); + readret = (*readit) (pfd[0], *rehostent, statuses.rehostentlen); +#ifdef DEBUG_HOSTENT + dumpit("Read from pipe", *rehostent); +#endif + if (readret == statuses.rehostentlen) { + got_rehostent = 1; + lynx_nsl_status = HT_OK; + } else if (!statuses.h_errno_valid) { + lynx_nsl_status = HT_INTERNAL; + } + } + } else { + lynx_nsl_status = HT_ERROR; + } + /* + * Make sure child is cleaned up. -BL + */ + if (!child_exited) + waitret = waitpid(fpid, &waitstat, WNOHANG); + if (!WIFEXITED(waitstat) && !WIFSIGNALED(waitstat)) { + kill(fpid, SIGTERM); + waitret = waitpid(fpid, &waitstat, WNOHANG); + } + break; + } + + /* + * Clean up if child exited before & no data received. -BL + */ + if (child_exited) { + waitret = waitpid(fpid, &waitstat, WNOHANG); + break; + } + /* + * If child exited, loop once more looking for data. -BL + */ + if ((waitret = waitpid(fpid, &waitstat, WNOHANG)) > 0) { + /* + * Data will be arriving right now, so make sure we don't + * short-circuit out for too many loops, and skip the interrupt + * check. -BL + */ + child_exited = 1; + continue; + } + + /* + * Abort if interrupt key pressed. + */ + if (HTCheckForInterrupt()) { + CTRACE((tfp, "%s: INTERRUPTED gethostbyname.\n", this_func)); + kill(fpid, SIGTERM); + waitpid(fpid, NULL, WNOHANG); + close(pfd[0]); + lynx_nsl_status = HT_INTERRUPTED; + return FALSE; + } + } + close(pfd[0]); + if (waitret <= 0) { + kill(fpid, SIGTERM); + waitret = waitpid(fpid, &waitstat, WNOHANG); + } + if (waitret > 0) { + if (WIFEXITED(waitstat)) { + CTRACE((tfp, + "%s: NSL_FORK child %d exited, status 0x%x.\n", + this_func, (int) waitret, WEXITSTATUS(waitstat))); + } else if (WIFSIGNALED(waitstat)) { + CTRACE((tfp, + "%s: NSL_FORK child %d got signal, status 0x%x!\n", + this_func, (int) waitret, WTERMSIG(waitstat))); +#ifdef WCOREDUMP + if (WCOREDUMP(waitstat)) { + CTRACE((tfp, + "%s: NSL_FORK child %d dumped core!\n", + this_func, (int) waitret)); + } +#endif /* WCOREDUMP */ + } else if (WIFSTOPPED(waitstat)) { + CTRACE((tfp, + "%s: NSL_FORK child %d is stopped, status 0x%x!\n", + this_func, (int) waitret, WSTOPSIG(waitstat))); + } + } + if (!got_rehostent) { + goto failed; + } + return TRUE; + failed: + return FALSE; +} + +/* + * This is called via the child-side of the fork. + */ +static void really_gethostbyname(const char *host, + const char *port GCC_UNUSED, + STATUSES * statuses, + void **rehostent) +{ + LYNX_HOSTENT *phost; /* Pointer to host - See netdb.h */ + LYNX_HOSTENT *result = 0; + + (void) port; + + phost = gethostbyname(host); + statuses->rehostentlen = 0; + statuses->child_errno = errno; + statuses->child_h_errno = h_errno; +#ifdef HAVE_H_ERRNO + statuses->h_errno_valid = YES; +#endif +#ifdef MVS + CTRACE((tfp, "really_gethostbyname() returned %d\n", phost)); +#endif /* MVS */ + +#ifdef DEBUG_HOSTENT_CHILD + dump_hostent("CHILD gethostbyname", phost); +#endif + if (OK_HOST(phost)) { + statuses->rehostentlen = fill_rehostent(rehostent, phost); + result = (LYNX_HOSTENT *) (*rehostent); +#ifdef DEBUG_HOSTENT_CHILD + dump_hostent("CHILD fill_rehostent", result); +#endif + } + if (statuses->rehostentlen <= sizeof(LYNX_HOSTENT) || !OK_HOST(result)) { + statuses->rehostentlen = 0; + statuses->h_length = 0; + } else { + statuses->h_length = result->h_length; +#ifdef HAVE_H_ERRNO + if (h_errno == -2) /* success, but h_errno unchanged? */ + statuses->h_errno_valid = NO; +#endif + } +} +#endif /* NSL_FORK */ + +/* Resolve an internet hostname, like gethostbyname + * ------------------------------------------------ + * + * On entry, + * host points to the given host name, not numeric address, + * without colon or port number. + * + * On exit, + * returns a pointer to a LYNX_HOSTENT in static storage, + * or NULL in case of error or user interruption. + * + * The interface is intended to be exactly the same as for (Unix) + * gethostbyname(), except for the following: + * + * If NSL_FORK is not used, the result of gethostbyname is returned + * directly. Otherwise: + * All lists, addresses, and strings referred to by pointers in the + * returned struct are located, together with the returned struct + * itself, in a buffer of size REHOSTENT_SIZE. If not everything fits, + * some info is omitted, but the function is careful to still return + * a valid structure, without truncating strings; it tries to return, + * in order of decreasing priority, the first address (h_addr_list[0]), the + * official name (h_name), the additional addresses, then alias names. + * + * If NULL is returned, the reason is made available in the global + * variable lynx_nsl_status, with one of the following values: + * HT_INTERRUPTED Interrupted by user + * HT_NOT_ACCEPTABLE Hostname detected as invalid + * (also sets h_errno) + * HT_H_ERRNO_VALID An error occurred, and h_errno holds + * an appropriate value + * HT_ERROR Resolver error, reason not known + * HT_INTERNAL Internal error + */ +static LYNX_HOSTENT *LYGetHostByName(char *host) +{ + static const char *this_func = "LYGetHostByName"; + +#ifdef NSL_FORK + /* for transfer of result between from child to parent: */ + LYNX_HOSTENT *rehostent = 0; +#endif /* NSL_FORK */ + + LYNX_HOSTENT *result_phost = NULL; + +#ifdef __DJGPP__ + _resolve_hook = ResolveYield; +#endif + + if (!host) { + CTRACE((tfp, "%s: Can't parse `NULL'.\n", this_func)); + lynx_nsl_status = HT_INTERNAL; + return NULL; + } + CTRACE((tfp, "%s: parsing `%s'.\n", this_func, host)); + + /* Could disable this if all our callers already check - kw */ + if (HTCheckForInterrupt()) { + CTRACE((tfp, "%s: INTERRUPTED for '%s'.\n", this_func, host)); + lynx_nsl_status = HT_INTERRUPTED; + return NULL; + } + + if (!valid_hostname(host)) { + lynx_nsl_status = HT_NOT_ACCEPTABLE; +#ifdef NO_RECOVERY +#ifdef _WINDOWS + WSASetLastError(NO_RECOVERY); +#else + h_errno = NO_RECOVERY; +#endif +#endif + return NULL; + } +#ifdef MVS /* Outstanding problem with crash in MVS gethostbyname */ + CTRACE((tfp, "%s: Calling gethostbyname(%s)\n", this_func, host)); +#endif /* MVS */ + + CTRACE_FLUSH(tfp); /* so child messages will not mess up parent log */ + + lynx_nsl_status = HT_INTERNAL; /* should be set to something else below */ + +#ifdef NSL_FORK + if (!setup_nsl_fork(really_gethostbyname, + read_hostent, + dump_hostent, + host, NULL, (void **) &rehostent)) { + goto failed; + } + result_phost = rehostent; +#else /* Not NSL_FORK: */ + +#ifdef _WINDOWS_NSL + { + HANDLE hThread; + DWORD dwThreadID; + +#ifndef __CYGWIN__ + if (!system_is_NT) { /* for Windows9x */ + unsigned long t; + + t = (unsigned long) inet_addr(host); + if (t != INADDR_NONE) + gbl_phost = gethostbyaddr((char *) &t, sizeof(t), AF_INET); + else + gbl_phost = gethostbyname(host); + } else { /* for Windows NT */ +#endif /* !__CYGWIN__ */ + gbl_phost = (LYNX_HOSTENT *) NULL; + donelookup = FALSE; + +#if defined(__CYGWIN__) || defined(USE_WINSOCK2_H) + SetLastError(WSAHOST_NOT_FOUND); +#else + WSASetLastError(WSAHOST_NOT_FOUND); +#endif + + hThread = CreateThread(NULL, 4096UL, _fork_func, host, 0UL, + &dwThreadID); + if (!hThread) + MessageBox(NULL, "CreateThread", + "CreateThread Failed", 0L); + + while (!donelookup) { + if (HTCheckForInterrupt()) { + /* Note that host is a character array and is not freed */ + /* to avoid possible subthread problems: */ + if (!CloseHandle(hThread)) { + MessageBox((void *) NULL, + "CloseHandle", "CloseHandle Failed", 0L); + } + lynx_nsl_status = HT_INTERRUPTED; + return NULL; + } + } +#ifndef __CYGWIN__ + } +#endif /* !__CYGWIN__ */ + if (gbl_phost) { + lynx_nsl_status = HT_OK; + result_phost = gbl_phost; + } else { + lynx_nsl_status = HT_ERROR; + goto failed; + } + } + +#else /* !NSL_FORK, !_WINDOWS_NSL: */ + { + LYNX_HOSTENT *phost; + + phost = gethostbyname(host); /* See netdb.h */ +#ifdef MVS + CTRACE((tfp, "%s: gethostbyname() returned %d\n", this_func, phost)); +#endif /* MVS */ + if (phost) { + lynx_nsl_status = HT_OK; + result_phost = phost; + } else { + lynx_nsl_status = HT_H_ERRNO_VALID; + goto failed; + } + } +#endif /* !NSL_FORK, !_WINDOWS_NSL */ +#endif /* !NSL_FORK */ + +#ifdef DEBUG_HOSTENT + dump_hostent(this_func, result_phost); + CTRACE((tfp, "%s: Resolved name to a hostent.\n", this_func)); +#endif + + return result_phost; /* OK */ + + failed: + CTRACE((tfp, "%s: Can't find internet node name `%s'.\n", this_func, host)); + return NULL; +} + +BOOLEAN LYCheckHostByName(char *host) +{ + LYNX_HOSTENT *data = LYGetHostByName(host); + BOOLEAN result = (data != NULL); + + FREE_NSL_FORK(data); + return result; +} + +/* 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. + * soc_in points to the binary internet or decnet address field. + * + * On exit, + * *soc_in is filled in. If no port is specified in str, that + * field is left unchanged in *soc_in. + */ +#ifndef INET6 +static int HTParseInet(SockA *soc_in, const char *str) +{ + static const char *this_func = "HTParseInet"; + + char *port; + int dotcount_ip = 0; /* for dotted decimal IP addr */ + char *strptr; + char *host = NULL; + + if (!str) { + CTRACE((tfp, "%s: Can't parse `NULL'.\n", this_func)); + return -1; + } + CTRACE((tfp, "%s: parsing `%s'.\n", this_func, str)); + if (HTCheckForInterrupt()) { + CTRACE((tfp, "%s: INTERRUPTED for '%s'.\n", this_func, str)); + return -1; + } + StrAllocCopy(host, str); /* Make a copy we can mutilate */ + /* + * Parse port number if present. + */ + if ((port = StrChr(host, ':')) != NULL) { + *port++ = 0; /* Chop off port */ + strptr = port; + if (port[0] >= '0' && port[0] <= '9') { +#ifdef UNIX + soc_in->sin_port = (PortNumber) htons(strtol(port, &strptr, 10)); +#else /* VMS: */ +#ifdef DECNET + soc_in->sdn_objnum = (unsigned char) (strtol(port, &strptr, 10)); +#else + soc_in->sin_port = htons((PortNumber) strtol(port, &strptr, 10)); +#endif /* Decnet */ +#endif /* Unix vs. VMS */ +#ifdef SUPPRESS /* 1. crashes!?!. 2. URL syntax has number not name */ + } else { + struct servent *serv = getservbyname(port, (char *) 0); + + if (serv) { + soc_in->sin_port = serv->s_port; + } else { + CTRACE((tfp, "TCP: Unknown service %s\n", port)); + } +#endif /* SUPPRESS */ + } + if (strptr && *strptr != '\0') { + FREE(host); + HTAlwaysAlert(NULL, gettext("Address has invalid port")); + return -1; + } + } +#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. + */ + soc_in->sdn_nam.n_len = min(DN_MAXNAML, strlen(host)); /* <=6 in phase 4 */ + StrNCpy(soc_in->sdn_nam.n_name, host, soc_in->sdn_nam.n_len + 1); + CTRACE((tfp, + "DECnet: Parsed address as object number %d on host %.6s...\n", + soc_in->sdn_objnum, host)); +#else /* parse Internet host: */ + + if (*host >= '0' && *host <= '9') { /* Test for numeric node address: */ + strptr = host; + while (*strptr) { + if (*strptr == '.') { + dotcount_ip++; + } else if (!isdigit(UCH(*strptr))) { + break; + } + strptr++; + } + if (*strptr) { /* found non-numeric, assume domain name */ + dotcount_ip = 0; + } + } + + /* + * Parse host number if present. + */ + if (dotcount_ip == 3) /* Numeric node address: */ + { +#ifdef GUSI + soc_in->sin_addr = inet_addr(host); /* See netinet/in.h */ +#else +#ifdef HAVE_INET_ATON + if (!inet_aton(host, &(soc_in->sin_addr))) { + CTRACE((tfp, "inet_aton(%s) returns error\n", host)); + FREE(host); + return -1; + } +#else + soc_in->sin_addr.s_addr = inet_addr(host); /* See arpa/inet.h */ +#endif /* HAVE_INET_ATON */ +#endif /* GUSI */ + FREE(host); + } else { /* Alphanumeric node name: */ + +#ifdef MVS /* Outstanding problem with crash in MVS gethostbyname */ + CTRACE((tfp, "%s: Calling LYGetHostByName(%s)\n", this_func, host)); +#endif /* MVS */ + +#ifdef _WINDOWS_NSL + gbl_phost = LYGetHostByName(host); /* See above */ + if (!gbl_phost) + goto failed; + MemCpy((void *) &soc_in->sin_addr, gbl_phost->h_addr_list[0], gbl_phost->h_length); + FREE(gbl_phost); +#else /* !_WINDOWS_NSL */ + { + LYNX_HOSTENT *phost; + + phost = LYGetHostByName(host); /* See above */ + + if (!phost) + goto failed; + if (phost->h_length != sizeof soc_in->sin_addr) { + HTAlwaysAlert(host, gettext("Address length looks invalid")); + } + MemCpy((void *) &soc_in->sin_addr, phost->h_addr_list[0], phost->h_length); + FREE_NSL_FORK(phost); + } +#endif /* _WINDOWS_NSL */ + + FREE(host); + } /* Alphanumeric node name */ + + CTRACE((tfp, + "%s: Parsed address as port %d, IP address %d.%d.%d.%d\n", + this_func, + (int) ntohs(soc_in->sin_port), + (int) *((unsigned char *) (&soc_in->sin_addr) + 0), + (int) *((unsigned char *) (&soc_in->sin_addr) + 1), + (int) *((unsigned char *) (&soc_in->sin_addr) + 2), + (int) *((unsigned char *) (&soc_in->sin_addr) + 3))); +#endif /* Internet vs. Decnet */ + + return 0; /* OK */ + + failed: + CTRACE((tfp, "%s: Can't find internet node name `%s'.\n", + this_func, host)); + FREE(host); + switch (lynx_nsl_status) { + case HT_NOT_ACCEPTABLE: + case HT_INTERRUPTED: + return lynx_nsl_status; + default: + return -1; + } +} +#endif /* !INET6 */ + +#ifdef INET6 + +static void dump_addrinfo(const char *tag, const void *data) +{ + const LYNX_ADDRINFO *res; + int count = 0; + + CTRACE((tfp, "dump_addrinfo %s:\n", tag)); + for (res = (const LYNX_ADDRINFO *) data; res; res = res->ai_next) { + char hostbuf[1024], portbuf[1024]; + + ++count; + hostbuf[0] = '\0'; + portbuf[0] = '\0'; + getnameinfo(res->ai_addr, res->ai_addrlen, + hostbuf, (socklen_t) sizeof(hostbuf), + portbuf, (socklen_t) sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + + CTRACE((tfp, + "\t[%d] family %d, socktype %d, protocol %d addr %s port %s\n", + count, + res->ai_family, + res->ai_socktype, + res->ai_protocol, + hostbuf, + portbuf)); + } +} + +#if defined(NSL_FORK) + +/* + * Copy the relevant information (on the child-side). + */ +static size_t fill_addrinfo(void **buffer, + const LYNX_ADDRINFO *phost) +{ + static const char *this_func = "fill_addinfo"; + + const LYNX_ADDRINFO *q; + LYNX_ADDRINFO *actual; + LYNX_ADDRINFO *result; + int count = 0; + int limit = 0; + size_t need = sizeof(LYNX_ADDRINFO); + char *heap; + + CTRACE((tfp, "filladdr_info %p\n", (const void *) phost)); + for (q = phost; q != 0; q = q->ai_next) { + ++limit; + need += phost->ai_addrlen; + need += sizeof(LYNX_ADDRINFO); + } + CTRACE((tfp, "...fill_addrinfo %d:%lu\n", limit, (unsigned long) need)); + + if ((result = (LYNX_ADDRINFO *) calloc(1, need)) == 0) + outofmem(__FILE__, this_func); + + *buffer = actual = result; + heap = ((char *) actual) + ((size_t) limit * sizeof(LYNX_ADDRINFO)); + + for (count = 0; count < limit; ++count) { + + /* + * copying the whole structure seems simpler but because it is not + * packed, uninitialized gaps make it hard to analyse with valgrind. + */ + /* *INDENT-EQLS* */ + actual->ai_flags = phost->ai_flags; + actual->ai_family = phost->ai_family; + actual->ai_socktype = phost->ai_socktype; + actual->ai_protocol = phost->ai_protocol; + actual->ai_addrlen = phost->ai_addrlen; + actual->ai_addr = (struct sockaddr *) (void *) heap; + + MemCpy(heap, phost->ai_addr, phost->ai_addrlen); + heap += phost->ai_addrlen; + + phost = phost->ai_next; + + actual->ai_next = ((count + 1 < limit) + ? (actual + 1) + : 0); + ++actual; + } + return (size_t) (heap - (char *) result); +} + +/* + * Read data, repair pointers as done in fill_addrinfo(). + */ +static unsigned read_addrinfo(int fd, char *buffer, size_t length) +{ + unsigned result = read_bytes(fd, buffer, length); + LYNX_ADDRINFO *actual = (LYNX_ADDRINFO *) (void *) buffer; + LYNX_ADDRINFO *res; + int count = 0; + int limit; + char *heap; + + CTRACE((tfp, "read_addrinfo length %lu\n", (unsigned long) length)); + for (limit = 0; actual[limit].ai_next; ++limit) { + } + ++limit; + heap = (char *) (actual + limit); + CTRACE((tfp, "...read_addrinfo %d items\n", limit)); + + for (res = actual, count = 0; count < limit; ++count) { + res->ai_addr = (struct sockaddr *) (void *) heap; + heap += res->ai_addrlen; + if (count < limit - 1) { + res->ai_next = (res + 1); + ++res; + } else { + res->ai_next = 0; + } + } + +#ifdef DEBUG_HOSTENT + dump_addrinfo("read_addrinfo", buffer); +#endif + return result; +} + +/* + * This is called via the child-side of the fork. + */ +static void really_getaddrinfo(const char *host, + const char *port, + STATUSES * statuses, + void **result) +{ + LYNX_ADDRINFO hints, *res = 0; + int error; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res); + if (error) { + CTRACE((tfp, "HTGetAddrInfo: getaddrinfo(%s, %s): %s\n", host, port, + gai_strerror(error))); + } else { + statuses->child_errno = errno; + statuses->child_h_errno = h_errno; +#ifdef HAVE_H_ERRNO + statuses->h_errno_valid = YES; +#endif + +#ifdef DEBUG_HOSTENT_CHILD + dump_addrinfo("CHILD getaddrinfo", res); +#endif + statuses->rehostentlen = fill_addrinfo(result, res); +#ifdef DEBUG_HOSTENT_CHILD + dump_addrinfo("CHILD fill_addrinfo", (const LYNX_ADDRINFO *) (*result)); +#endif + if (statuses->rehostentlen <= sizeof(LYNX_ADDRINFO) || (*result) == NULL) { + statuses->rehostentlen = 0; + statuses->h_length = 0; + } else { + statuses->h_length = (int) (((LYNX_ADDRINFO *) (*result))->ai_addrlen); + } + freeaddrinfo(res); + } +} +#endif /* NSL_FORK */ + +static LYNX_ADDRINFO *HTGetAddrInfo(const char *str, + const int defport) +{ +#ifdef NSL_FORK + /* for transfer of result between from child to parent: */ + void *readdrinfo = 0; + +#else + LYNX_ADDRINFO hints; + int error; +#endif /* NSL_FORK */ + LYNX_ADDRINFO *res; + char *p; + char *s = NULL; + char *host, *port; + char pbuf[80]; + + StrAllocCopy(s, str); + + if (s[0] == '[' && (p = StrChr(s, ']')) != NULL) { + *p++ = '\0'; + host = s + 1; + } else { + p = s; + host = &s[0]; + } + port = strrchr(p, ':'); + if (port) { + *port++ = '\0'; + } else { + sprintf(pbuf, "%d", defport); + port = pbuf; + } + +#ifdef NSL_FORK + if (setup_nsl_fork(really_getaddrinfo, + read_addrinfo, + dump_addrinfo, + host, port, &readdrinfo)) { + res = readdrinfo; + } else { + res = NULL; + } +#else + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res); + if (error || !res) { + CTRACE((tfp, "HTGetAddrInfo: getaddrinfo(%s, %s): %s\n", host, port, + gai_strerror(error))); + res = NULL; + } +#endif + + free(s); +#ifdef DEBUG_HOSTENT + dump_addrinfo("HTGetAddrInfo", res); +#endif + return res; +} + +BOOLEAN HTCheckAddrInfo(const char *str, const int defport) +{ + LYNX_ADDRINFO *data = HTGetAddrInfo(str, defport); + BOOLEAN result = (data != 0); + + FREE_NSL_FORK(data); + return result; +} +#endif /* INET6 */ + +#ifdef LY_FIND_LEAKS +/* Free our name for the host on which we are - FM + * ------------------------------------------- + * + */ +static void free_HTTCP_hostname(void) +{ + FREE(hostname); +} +#endif /* LY_FIND_LEAKS */ + +/* Derive the name of the host on which we are + * ------------------------------------------- + * + */ +static void get_host_details(void) +{ + char name[MAXHOSTNAMELEN + 1]; /* The name of this host */ + +#ifdef UCX + char *domain_name; /* The name of this host domain */ +#endif /* UCX */ +#ifdef NEED_HOST_ADDRESS /* no -- needs name server! */ +#ifdef INET6 + LYNX_ADDRINFO hints, *res; + int error; + +#else + LYNX_HOSTENT *phost; /* Pointer to host -- See netdb.h */ +#endif /* INET6 */ +#endif /* NEED_HOST_ADDRESS */ + size_t namelength = sizeof(name); + + if (hostname) + return; /* Already done */ + gethostname(name, namelength); /* Without domain */ + StrAllocCopy(hostname, name); +#ifdef LY_FIND_LEAKS + atexit(free_HTTCP_hostname); +#endif +#ifdef UCX + /* + * UCX doesn't give the complete domain name. Get rest from UCX$BIND_DOM + * logical. + */ + if (StrChr(hostname, '.') == NULL) { /* Not full address */ + domain_name = LYGetEnv("UCX$BIND_DOMAIN"); + if (domain_name == NULL) + domain_name = LYGetEnv("TCPIP$BIND_DOMAIN"); + if (domain_name != NULL) { + StrAllocCat(hostname, "."); + StrAllocCat(hostname, domain_name); + } + } +#endif /* UCX */ + CTRACE((tfp, "TCP: Local host name is %s\n", hostname)); + +#ifndef DECNET /* Decnet ain't got no damn name server 8#OO */ +#ifdef NEED_HOST_ADDRESS /* no -- needs name server! */ +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + error = getaddrinfo(name, NULL, &hints, &res); + if (error || !res || !res->ai_canonname) { + CTRACE((tfp, "TCP: %s: `%s'\n", gai_strerror(error), name)); + if (res) + freeaddrinfo(res); + return; /* Fail! */ + } + StrAllocCopy(hostname, res->ai_canonname); + MemCpy(&HTHostAddress, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); +#else + phost = gethostbyname(name); /* See netdb.h */ + if (!OK_HOST(phost)) { + CTRACE((tfp, + "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_list[0], phost->h_length); +#endif /* INET6 */ + CTRACE((tfp, " Name server says that I am `%s' = %s\n", + hostname, HTInetString(&HTHostAddress))); +#endif /* NEED_HOST_ADDRESS */ + +#endif /* !DECNET */ +} + +const char *HTHostName(void) +{ + get_host_details(); + return hostname; +} + +#ifdef _WINDOWS +#define SET_EINTR WSASetLastError(EINTR) +#else +#define SET_EINTR SOCKET_ERRNO = EINTR +#endif + +static BOOL HTWasInterrupted(int *status) +{ + BOOL result = FALSE; + + if (HTCheckForInterrupt()) { + result = TRUE; + *status = HT_INTERRUPTED; + SET_EINTR; + } + return result; +} + +#define TRIES_PER_SECOND 10 + +/* + * Set the select-timeout to 0.1 seconds. + */ +static void set_timeout(struct timeval *timeoutp) +{ + timeoutp->tv_sec = 0; + timeoutp->tv_usec = 100000; +} + +#ifndef MULTINET /* SOCKET_ERRNO != errno ? */ +#if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */ +#define SOCKET_DEBUG_TRACE /* show errno status after some system calls */ +#endif /* UCX && VAXC */ +#endif /* MULTINET */ +/* + * Interruptible connect as implemented for Mosaic by Marc Andreesen + * and hacked in for Lynx years ago by Lou Montulli, and further + * modified over the years by numerous Lynx lovers. - FM + */ +int HTDoConnect(const char *url, + const char *protocol, + int default_port, + int *s) +{ + char *socks5_host = NULL; + unsigned socks5_host_len = 0; + int socks5_port; + const char *socks5_orig_url; + char *socks5_new_url = NULL; + char *socks5_protocol = NULL; + int status = HT_OK; + char *line = NULL; + char *p1 = NULL; + char *host = NULL; + char const *emsg; + +#ifdef INET6 + LYNX_ADDRINFO *res = 0, *res0 = 0; + +#else + struct sockaddr_in sock_A; + struct sockaddr_in *soc_in = &sock_A; +#endif + + *s = -1; /* nothing is open yet */ + + /* In case of a present SOCKS5 proxy, marshal */ + if (socks5_proxy == NULL) + socks5_proxy = LYGetEnv("SOCKS5_PROXY"); + if ((socks5_orig_url = socks5_proxy) != NULL) { + int xport; + + xport = default_port; + socks5_orig_url = url; + StrAllocCopy(socks5_new_url, url); + + /* Get node name and optional port number of wanted URL */ + if ((p1 = HTParse(socks5_new_url, "", PARSE_HOST)) != NULL) { + StrAllocCopy(socks5_host, p1); + strip_userid(socks5_host, FALSE); + FREE(p1); + } + + if (isEmpty(socks5_host)) { + emsg = gettext("SOCKS5: no hostname found."); + status = HT_ERROR; + goto report_error; + } + + if (strlen(socks5_host) > 255) { + emsg = gettext("SOCKS5: hostname too long."); + status = HT_ERROR; + goto report_error; + } + socks5_host_len = (unsigned) strlen(socks5_host); + + if (HTParsePort(socks5_new_url, &socks5_port) == NULL) + socks5_port = xport; + FREE(socks5_new_url); + + /* And switch over to our SOCKS5 config; in order to embed that into + * lynx environment, prepend protocol prefix */ + default_port = 1080; /* RFC 1928 */ + HTSACat(&socks5_new_url, "socks://"); + HTSACat(&socks5_new_url, socks5_proxy); + url = socks5_new_url; + + HTSprintf0(&socks5_protocol, + gettext("(for %s at %s) SOCKS5"), + protocol, socks5_host); + protocol = socks5_protocol; + } +#ifndef INET6 + /* + * Set up defaults. + */ + memset(soc_in, 0, sizeof(*soc_in)); + soc_in->sin_family = AF_INET; + soc_in->sin_port = htons((PortNumber) default_port); +#endif /* INET6 */ + + /* + * Get node name and optional port number. + */ + p1 = HTParse(url, "", PARSE_HOST); + StrAllocCopy(host, p1); + strip_userid(host, FALSE); + FREE(p1); + + HTSprintf0(&line, "%s%s", WWW_FIND_MESSAGE, host); + _HTProgress(line); +#ifdef INET6 + /* HTParseInet() is useless! */ + res0 = HTGetAddrInfo(host, default_port); + if (res0 == NULL) { + HTSprintf0(&line, gettext("Unable to locate remote host %s."), host); + _HTProgress(line); + status = HT_NO_DATA; + goto cleanup; + } +#else + status = HTParseInet(soc_in, host); + if (status) { + if (status != HT_INTERRUPTED) { + if (status == HT_NOT_ACCEPTABLE) { + /* Not HTProgress, so warning won't be overwritten immediately; + * but not HTAlert, because typically there will be other + * alerts from the callers. - kw + */ + HTUserMsg2(gettext("Invalid hostname %s"), host); + } else { + HTSprintf0(&line, + gettext("Unable to locate remote host %s."), host); + _HTProgress(line); + } + status = HT_NO_DATA; + } + goto cleanup; + } +#endif /* INET6 */ + + HTSprintf0(&line, gettext("Making %s connection to %s"), protocol, host); + _HTProgress(line); + + /* + * Now, let's get a socket set up from the server for the data. + */ +#ifndef INET6 + *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (*s == -1) { + status = HT_NO_DATA; + emsg = gettext("socket failed."); + goto report_error; + } +#else + for (res = res0; res; res = res->ai_next) { + *s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (*s == -1) { + char hostbuf[1024], portbuf[1024]; + + getnameinfo(res->ai_addr, res->ai_addrlen, + hostbuf, (socklen_t) sizeof(hostbuf), + portbuf, (socklen_t) sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + HTSprintf0(&line, + gettext("socket failed: family %d addr %s port %s."), + res->ai_family, hostbuf, portbuf); + _HTProgress(line); + continue; + } +#endif /* INET6 */ + +#if !defined(DOSPATH) || defined(__DJGPP__) +#if !defined(NO_IOCTL) || defined(USE_FCNTL) + /* + * 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. + */ + { +#ifdef USE_FCNTL + int ret = fcntl(*s, F_SETFL, O_NONBLOCK); + +#else + int val = 1; + int ret = IOCTL(*s, FIONBIO, &val); +#endif /* USE_FCNTL */ + if (ret == -1) + _HTProgress(gettext("Could not make connection non-blocking.")); + } +#endif /* !NO_IOCTL || USE_FCNTL */ +#endif /* !DOSPATH || __DJGPP__ */ + + /* + * 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. + */ +#ifdef SOCKS + if (socks_flag) { +#ifdef INET6 + status = Rconnect(*s, res->ai_addr, res->ai_addrlen); +#else + status = Rconnect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A)); +#endif /* INET6 */ + } else +#endif /* SOCKS */ +#ifdef INET6 + status = connect(*s, res->ai_addr, res->ai_addrlen); +#else + status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A)); +#endif /* INET6 */ + + /* + * 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. + */ + CTRACE((tfp, "connect(): status: %d, SOCK_ERRNO: %d\n", status, SOCKET_ERRNO)); + + if ((status < 0) && + (SOCKET_ERRNO == EINPROGRESS +#ifdef EAGAIN + || SOCKET_ERRNO == EAGAIN +#endif + )) { + struct timeval select_timeout; + int ret; + int tries = 0; + +#ifdef SOCKET_DEBUG_TRACE + if (SOCKET_ERRNO != EINPROGRESS) { + HTInetStatus("this socket's first connect"); + } +#endif /* SOCKET_DEBUG_TRACE */ + ret = 0; + while (ret <= 0) { + fd_set writefds; + + /* + * Protect against an infinite loop. + */ + if ((tries++ / TRIES_PER_SECOND) >= connect_timeout) { + HTAlert(gettext("Connection failed (too many retries).")); +#ifdef INET6 +#ifndef NSL_FORK + if (res0) + freeaddrinfo(res0); +#endif +#endif /* INET6 */ + status = HT_NO_DATA; + goto cleanup; + } + set_timeout(&select_timeout); + FD_ZERO(&writefds); + FD_SET((LYNX_FD) *s, &writefds); +#ifdef SOCKS + if (socks_flag) + ret = Rselect(*s + 1, NULL, + &writefds, NULL, &select_timeout); + else +#endif /* SOCKS */ + ret = select(*s + 1, + NULL, + &writefds, + NULL, + &select_timeout); + +#ifdef SOCKET_DEBUG_TRACE + if (tries == 1) { + if (SOCKET_ERRNO != EINPROGRESS) { + HTInetStatus("this socket's first select"); + } + } +#endif /* SOCKET_DEBUG_TRACE */ + /* + * If we suspend, then it is possible that select will be + * interrupted. Allow for this possibility. - JED + */ + if ((ret == -1) && (SOCKET_ERRNO == EINTR)) + continue; + +#ifdef SOCKET_DEBUG_TRACE + if (ret < 0) { + HTInetStatus("failed select"); + } +#endif /* SOCKET_DEBUG_TRACE */ + /* + * Again according to the Sun and Motorola man pages for + * connect: + * EALREADY The socket is non-blocking and a previ- + * ous connection attempt has not yet been + * completed. + * Thus if the SOCKET_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) && (SOCKET_ERRNO != EALREADY)) { + status = ret; + break; + } else if (((SOCKET_ERRNO == EALREADY) || + (SOCKET_ERRNO == EINPROGRESS)) && + HTCheckForInterrupt()) { + status = HT_INTERRUPTED; + 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. But don't check with SOCKS. + */ +#ifdef SOCKS + if (socks_flag) { + status = 0; + } else { +#endif /* SOCKS */ +#ifdef INET6 + status = connect(*s, res->ai_addr, res->ai_addrlen); +#else + status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A)); +#endif /* INET6 */ +#ifdef UCX + /* + * A UCX feature: Instead of returning EISCONN UCX + * returns EADDRINUSE. Test for this status also. + */ + if ((status < 0) && ((SOCKET_ERRNO == EISCONN) || + (SOCKET_ERRNO == EADDRINUSE))) +#else + if ((status < 0) && (SOCKET_ERRNO == EISCONN)) +#endif /* UCX */ + { + status = 0; + } + + if (status && (SOCKET_ERRNO == EALREADY)) /* new stuff LJM */ + ret = 0; /* keep going */ + else { +#ifdef SOCKET_DEBUG_TRACE + if (status < 0) { + HTInetStatus("confirm-ready connect"); + } +#endif /* SOCKET_DEBUG_TRACE */ + break; + } +#ifdef SOCKS + } +#endif /* SOCKS */ + } +#ifdef SOCKS + else if (!socks_flag) +#else + else +#endif /* SOCKS */ + { + /* + * 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. + * + * For some reason, UCX pre 3 apparently returns errno = + * 18242 instead of EALREADY or EISCONN. + */ +#ifdef INET6 + status = connect(*s, res->ai_addr, res->ai_addrlen); +#else + status = connect(*s, SOCKADDR_OF(sock_A), sizeof(sock_A)); +#endif /* INET6 */ + if ((status < 0) && + (SOCKET_ERRNO != EALREADY +#ifdef EAGAIN + && SOCKET_ERRNO != EAGAIN +#endif + ) && +#ifdef UCX + (SOCKET_ERRNO != 18242) && +#endif /* UCX */ + (SOCKET_ERRNO != EISCONN)) { +#ifdef SOCKET_DEBUG_TRACE + HTInetStatus("confirm-not-ready connect"); +#endif /* SOCKET_DEBUG_TRACE */ + break; + } + } + if (HTWasInterrupted(&status)) { + CTRACE((tfp, "*** INTERRUPTED in middle of connect.\n")); + break; + } + } + } +#ifdef SOCKET_DEBUG_TRACE + else if (status < 0) { + HTInetStatus("this socket's first and only connect"); + } +#endif /* SOCKET_DEBUG_TRACE */ +#ifdef INET6 + if (status < 0) { + NETCLOSE(*s); + *s = -1; + if (status != HT_INTERRUPTED) + continue; + } + break; + } +#endif /* INET6 */ + +#ifdef INET6 + if (*s < 0) +#else + if (status < 0) +#endif /* INET6 */ + { + /* + * The connect attempt failed or was interrupted, so close up the + * socket. + */ + NETCLOSE(*s); + } +#if !defined(DOSPATH) || defined(__DJGPP__) +#if !defined(NO_IOCTL) || defined(USE_FCNTL) + else { + /* + * Make the socket blocking again on good connect. + */ +#ifdef USE_FCNTL + int ret = fcntl(*s, F_SETFL, 0); + +#else + int val = 0; + int ret = IOCTL(*s, FIONBIO, &val); +#endif /* USE_FCNTL */ + if (ret == -1) + _HTProgress(gettext("Could not restore socket to blocking.")); + } +#endif /* !NO_IOCTL || USE_FCNTL */ +#endif /* !DOSPATH || __DJGPP__ */ + +#ifdef INET6 +#ifdef NSL_FORK + FREE_NSL_FORK(res0); +#else + if (res0) + freeaddrinfo(res0); +#endif +#endif /* INET6 */ + + /* Now if this was a SOCKS5 proxy connection, go for the real one */ + if (status >= 0 && socks5_orig_url != NULL) { + unsigned char pbuf[4 + 1 + 255 + 2]; + unsigned i; + + /* RFC 1928: version identifier/method selection message */ + pbuf[0] = 0x05; /* VER: protocol version: X'05' */ + pbuf[1] = 0x01; /* NMETHODS: 1 */ + pbuf[2] = 0x00; /* METHOD: X'00' NO AUTHENTICATION REQUIRED */ + if (NETWRITE(*s, (char *) pbuf, 3) != 3) { + goto report_system_err; + } else if (HTDoRead(*s, pbuf, 2) != 2) { + goto report_system_err; + } else if (pbuf[0] != 0x05 || pbuf[1] != 0x00) { + goto report_unexpected_reply; + } + + /* RFC 1928: CONNECT request */ + HTSprintf0(&line, gettext("SOCKS5: connecting to %s"), socks5_host); + _HTProgress(line); + pbuf[0] = 0x05; /* VER: protocol version: X'05' */ + pbuf[1] = 0x01; /* CMD: CONNECT X'01' */ + pbuf[2] = 0x00; /* RESERVED */ + pbuf[3] = 0x03; /* ATYP: domain name */ + pbuf[4] = (unsigned char) socks5_host_len; + memcpy(&pbuf[i = 5], socks5_host, socks5_host_len); + i += socks5_host_len; + /* C99 */ { + unsigned short x; /* XXX 16-bit? */ + + x = htons((PortNumber) socks5_port); + memcpy(&pbuf[i], (unsigned char *) &x, sizeof x); + i += (unsigned) sizeof(x); + } + if ((size_t) NETWRITE(*s, (char *) pbuf, i) != i) { + goto report_system_err; + } else if ((unsigned) HTDoRead(*s, pbuf, 4) != 4) { + goto report_system_err; + } + /* Version 5, reserved must be 0 */ + if (pbuf[0] == 0x05 && pbuf[2] == 0x00) { + /* Result */ + switch (pbuf[1]) { + case 0x00: + emsg = NULL; + break; + case 0x01: + emsg = gettext("SOCKS server failure"); + break; + case 0x02: + emsg = gettext("connection not allowed by ruleset"); + break; + case 0x03: + emsg = gettext("network unreachable"); + break; + case 0x04: + emsg = gettext("host unreachable"); + break; + case 0x05: + emsg = gettext("connection refused"); + break; + case 0x06: + emsg = gettext("TTL expired"); + break; + case 0x07: + emsg = gettext("command not supported"); + break; + case 0x08: + emsg = gettext("address type not supported"); + break; + default: + emsg = gettext("unknown SOCKS error code"); + break; + } + if (emsg != NULL) { + goto report_no_connection; + } + } else { + goto report_unexpected_reply; + } + + /* Address type variable; read the BND.PORT with it. + * This is actually false since RFC 1928 says that the BND.ADDR reply + * to CONNECT contains the IP address, so only 0x01 and 0x04 are + * allowed */ + switch (pbuf[3]) { + case 0x01: + i = 4; + break; + case 0x03: + i = 1; + break; + case 0x04: + i = 16; + break; + default: + goto report_unexpected_reply; + } + i += (unsigned) sizeof(unsigned short); + + if ((size_t) HTDoRead(*s, pbuf, i) != i) { + goto report_system_err; + } else if (i == 1 + sizeof(unsigned short)) { + i = pbuf[0]; + if ((size_t) HTDoRead(*s, pbuf, i) != i) { + goto report_system_err; + } + } + } + goto cleanup; + + report_system_err: + emsg = LYStrerror(errno); + goto report_no_connection; + + report_unexpected_reply: + emsg = gettext("unexpected reply\n"); + /* FALLTHRU */ + + report_no_connection: + status = HT_NO_CONNECTION; + /* FALLTHRU */ + + report_error: + HTAlert(emsg); + if (*s != -1) { + NETCLOSE(*s); + } + + cleanup: + if (socks5_proxy != NULL) { + FREE(socks5_new_url); + FREE(socks5_protocol); + FREE(socks5_host); + } + FREE(host); + FREE(line); + return status; +} + +/* + * This is interruptible so reads can be implemented cleanly. + */ +int HTDoRead(int fildes, + void *buf, + unsigned nbyte) +{ + int result; + BOOL ready; + +#if !defined(NO_IOCTL) + int ret; + fd_set readfds; + struct timeval select_timeout; + int tries = 0; + +#ifdef USE_READPROGRESS + int otries = 0; + time_t otime = time((time_t *) 0); + time_t start = otime; +#endif +#endif /* !NO_IOCTL */ + +#if defined(UNIX) && !defined(__BEOS__) + if (fildes == 0) { + /* + * 0 can be a valid socket fd, but if it's a tty something must have + * gone wrong. - kw + */ + if (isatty(fildes)) { + CTRACE((tfp, "HTDoRead - refusing to read fd 0 which is a tty!\n")); + return -1; + } + } else +#endif + if (fildes <= 0) { + CTRACE((tfp, "HTDoRead - no file descriptor!\n")); + return -1; + } + + if (HTWasInterrupted(&result)) { + CTRACE((tfp, "HTDoRead - interrupted before starting!\n")); + return (result); + } +#if defined(NO_IOCTL) + ready = TRUE; +#else + ready = FALSE; + while (!ready) { + /* + * Protect against an infinite loop. + */ + if ((tries++ / TRIES_PER_SECOND) >= reading_timeout) { + HTAlert(gettext("Socket read failed (too many tries).")); + SET_EINTR; + result = HT_INTERRUPTED; + break; + } +#ifdef USE_READPROGRESS + if (tries - otries > TRIES_PER_SECOND) { + time_t t = time((time_t *) 0); + + otries = tries; + if (t - otime >= 5) { + otime = t; + HTReadProgress((off_t) (-1), (off_t) 0); /* Put "stalled" message */ + } + } +#endif + + /* + * If we suspend, then it is possible that select will be interrupted. + * Allow for this possibility. - JED + */ + do { + set_timeout(&select_timeout); + FD_ZERO(&readfds); + FD_SET((LYNX_FD) fildes, &readfds); +#ifdef SOCKS + if (socks_flag) + ret = Rselect(fildes + 1, + &readfds, NULL, NULL, &select_timeout); + else +#endif /* SOCKS */ + ret = select(fildes + 1, + &readfds, NULL, NULL, &select_timeout); + } while ((ret == -1) && (errno == EINTR)); + + if (ret < 0) { + result = -1; + break; + } else if (ret > 0) { + ready = TRUE; + } else if (HTWasInterrupted(&result)) { + break; + } + } +#endif /* !NO_IOCTL */ + + if (ready) { +#if defined(UCX) && defined(VAXC) + /* + * VAXC and UCX problem only. + */ + errno = vaxc$errno = 0; + result = SOCKET_READ(fildes, buf, nbyte); + CTRACE((tfp, + "Read - result,errno,vaxc$errno: %d %d %d\n", result, errno, vaxc$errno)); + if ((result <= 0) && TRACE) + perror("HTTCP.C:HTDoRead:read"); /* RJF */ + /* + * An errno value of EPIPE and result < 0 indicates end-of-file on VAXC. + */ + if ((result <= 0) && (errno == EPIPE)) { + result = 0; + set_errno(0); + } +#else +#ifdef UNIX + while ((result = (int) SOCKET_READ(fildes, buf, nbyte)) == -1) { + if (errno == EINTR) + continue; +#ifdef ERESTARTSYS + if (errno == ERESTARTSYS) + continue; +#endif /* ERESTARTSYS */ + HTInetStatus("read"); + break; + } +#else /* UNIX */ + result = NETREAD(fildes, (char *) buf, nbyte); +#endif /* !UNIX */ +#endif /* UCX && VAXC */ + } +#ifdef USE_READPROGRESS + CTRACE2(TRACE_TIMING, (tfp, "...HTDoRead returns %d (%" PRI_time_t + " seconds)\n", + result, CAST_time_t (time((time_t *)0) - start))); +#endif + return result; +} + +#ifdef SVR4_BSDSELECT +/* + * This is a fix for the difference between BSD's select() and + * SVR4's select(). SVR4's select() can never return a value larger + * than the total number of file descriptors being checked. So, if + * you select for read and write on one file descriptor, and both + * are true, SVR4 select() will only return 1. BSD select in the + * same situation will return 2. + * + * Additionally, BSD select() on timing out, will zero the masks, + * while SVR4 does not. This is fixed here as well. + * + * Set your tabstops to 4 characters to have this code nicely formatted. + * + * Jerry Whelan, guru@bradley.edu, June 12th, 1993 + */ +#ifdef select +#undef select +#endif /* select */ + +#ifdef SOCKS +#ifdef Rselect +#undef Rselect +#endif /* Rselect */ +#endif /* SOCKS */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/select.h> + +int BSDselect(int nfds, + fd_set * readfds, + fd_set * writefds, + fd_set * exceptfds, + struct timeval *select_timeout) +{ + int rval, i; + +#ifdef SOCKS + if (socks_flag) + rval = Rselect(nfds, readfds, writefds, exceptfds, select_timeout); + else +#endif /* SOCKS */ + rval = select(nfds, readfds, writefds, exceptfds, select_timeout); + + switch (rval) { + case -1: + return (rval); + + case 0: + if (readfds != NULL) + FD_ZERO(readfds); + if (writefds != NULL) + FD_ZERO(writefds); + if (exceptfds != NULL) + FD_ZERO(exceptfds); + return (rval); + + default: + for (i = 0, rval = 0; i < nfds; i++) { + if ((readfds != NULL) && FD_ISSET(i, readfds)) + rval++; + if ((writefds != NULL) && FD_ISSET(i, writefds)) + rval++; + if ((exceptfds != NULL) && FD_ISSET(i, exceptfds)) + rval++; + + } + return (rval); + } +/* Should never get here */ +} +#endif /* SVR4_BSDSELECT */ |