/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* * generalized portable TCP routines * * Handles calls to async connect and dns routines. * * Designed and originally implemented by Lou Montulli '94 * Modified by Judson Valeski '97 */ #include "rosetta.h" #include "mkutils.h" #include "netutils.h" #include "prerror.h" #include "prsystem.h" #include "prefapi.h" #include "mkpadpac.h" #include "mkprefs.h" #if defined(XP_WIN) #define ASYNC_DNS #endif #include "mktcp.h" #include "mkparse.h" #include "mkgeturl.h" /* for error codes, and some util functions */ #include "fe_proto.h" /* for externs */ #ifdef MOZILLA_CLIENT #include "secnav.h" #endif /* MOZILLA_CLIENT */ #include "merrors.h" #include "ssl.h" #include "xp_error.h" #if defined(XP_OS2) /*DSR072196 - use os2sock.h*/ #include "os2sock.h" #endif /* XP_OS2 */ #include "timing.h" #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) #include "progress.h" #endif #ifdef XP_UNIX /* #### WARNING, this is duplicated in mksockrw.c */ # include /* * mcom_db.h is only included here to set BYTE_ORDER !!! * MAXDNAME is pilfered right out of arpa/nameser.h. */ # include "mcom_db.h" # if defined(__hpux) || defined(_HPUX_SOURCE) # define BYTE_ORDER BIG_ENDIAN # define MAXDNAME 256 /* maximum domain name */ # else # include # endif #include #if !defined(__osf__) && !defined(AIXV3) && !defined(_HPUX_SOURCE) && !defined(__386BSD__) && !defined(__linux) && !defined(SCO_SV) && !defined(DGUX) #include #endif #endif /* XP_UNIX */ /* for XP_GetString() */ #include "xpgetstr.h" extern int MK_CONNECTION_REFUSED; extern int MK_CONNECTION_TIMED_OUT; extern int MK_OUT_OF_MEMORY; extern int MK_UNABLE_TO_CONNECT; extern int MK_UNABLE_TO_CREATE_SOCKET; extern int MK_UNABLE_TO_LOCATE_HOST; extern int MK_UNABLE_TO_LOCATE_SOCKS_HOST; extern int XP_ERRNO_EALREADY; extern int XP_ERRNO_ECONNREFUSED; extern int XP_ERRNO_EINPROGRESS; extern int XP_ERRNO_EISCONN; extern int XP_ERRNO_ETIMEDOUT; extern int XP_ERRNO_EWOULDBLOCK; extern int XP_PROGRESS_CONTACTHOST; extern int XP_PROGRESS_LOOKUPHOST; extern int XP_PROGRESS_NOCONNECTION; extern int XP_PROGRESS_UNABLELOCATE; extern int MK_PORT_ACCESS_NOT_ALLOWED; /* An awful hack. setupSocks gets set to true after the first call to BeginConnect. * We don't want to be setting up the socks host/port (a call to gethostbyname) * until we've gone all the way through the netscape initialization. */ static setupSocks=FALSE; typedef enum { NET_TCP_FINISH_DNS_LOOKUP, NET_TCP_FINISH_CONNECT } TCPStatesEnum; struct _TCP_ConData { TCPStatesEnum next_state; /* states of the machine */ PRNetAddr net_addr; HG93765 time_t begin_time; }; /* MAX_DNS_LIST_SIZE controls the cache of resolved hosts. * If it is undefined, the cache will grow without bound. * If it is defined and >0, then the cache will be limited * to that many entries. If it is 0, there will be no cache. * * also needed for DNS failover... */ # define MAX_DNS_LIST_SIZE 10 /* Global TCP connect variables */ PUBLIC unsigned long NET_SocksHost=0; PUBLIC short NET_SocksPort=0; PUBLIC char * NET_SocksHostName=0; PUBLIC PRBool socksFailure=FALSE; PUBLIC int NET_InGetHostByName = FALSE; /* global semaphore */ /* The struct used to define a DNS cache entry */ typedef struct _DNSEntry { char *hostname; PRUint32 *ips; int32 addressCount; int h_length; time_t expirationTime; } DNSEntry; PRIVATE XP_List * dns_list=0; PRIVATE int32 dnsCacheExpiration=0; #define DEFAULT_TCP_CONNECT_TIMEOUT 35 /* seconds */ PRIVATE uint32 net_tcp_connect_timeout = DEFAULT_TCP_CONNECT_TIMEOUT;/*seconds*/ /* set the number of seconds for the tcp connect timeout * * this timeout is used to end connections that do not * timeout on there own */ PUBLIC void NET_SetTCPConnectTimeout(uint32 seconds) { net_tcp_connect_timeout = seconds; } /* socks support function * * if set to NULL socks is off. If set to non null string the string will be * used as the socks host and socks will be used for all connections. * * returns 0 if the numonic hostname given is invalid or gethostbyname * returns host unknown * */ int NET_SetSocksHost(char * host) { PRBool is_numeric_ip; char *host_cp; #ifdef MOZ_OFFLINE if (NET_IsOffline() || !setupSocks) { /* Either we're offline, or this is the very first call ot setsockshost. */ socksFailure=FALSE; return 1; } #endif /* MOZ_OFFLINE */ if(host && *host) { char *cp; TRACEMSG(("Trying to set Socks host: %s\n", host)); /* If there's no port or it's zero, fail out so user gets * an error and checks his configuration. */ if ( ((cp = PL_strrchr(host, ':')) != NULL) && (*(cp+1) != '\0') && (*(cp+1) != '0') ) { *cp = 0; NET_SocksPort = atoi(cp+1); } else { NET_SocksHost = 0; NET_SocksPort = 0; PR_FREEIF(NET_SocksHostName); NET_SocksHostName = 0; TRACEMSG(("Couldn't find a socks port. :(\n")); socksFailure=TRUE; return 0; /* Fail? */ } is_numeric_ip = TRUE; /* init positive */ for(host_cp = host; *host_cp; host_cp++) if(!NET_IS_DIGIT(*host_cp) && *host_cp != '.') { is_numeric_ip = FALSE; break; } if (is_numeric_ip) /* Numeric node address */ { PRNetAddr netAddr; PRStatus status; TRACEMSG(("Socks host is numeric")); /* some systems return a number instead of a struct */ status = PR_StringToNetAddr(host, &netAddr); if (PR_SUCCESS == status) { NET_SocksHost = netAddr.inet.ip; } if (NET_SocksHostName) PR_Free (NET_SocksHostName); NET_SocksHostName = PL_strdup(host); } else /* name not number */ { PRHostEnt *hp; PRHostEnt hpbuf; char dbbuf[PR_NETDB_BUF_SIZE]; PRStatus rv; NET_InGetHostByName++; /* global semaphore */ rv = PR_GetHostByName(host, dbbuf, sizeof(dbbuf), &hpbuf); hp = (rv == PR_SUCCESS ? &hpbuf : NULL); NET_InGetHostByName--; /* global semaphore */ if (!hp) { TRACEMSG(("mktcp.c: Can't find Socks host name `%s'\n", host)); NET_SocksHost = 0; if (NET_SocksHostName) PR_Free (NET_SocksHostName); NET_SocksHostName = 0; TRACEMSG(("Socks host is bad. :(\n")); if (cp) { *cp = ':'; } socksFailure=TRUE; return 0; /* Fail? */ } memcpy(&NET_SocksHost, hp->h_addr_list[0], hp->h_length); } if (cp) { *cp = ':'; } } else { NET_SocksHost = 0; if (NET_SocksHostName) PR_Free (NET_SocksHostName); NET_SocksHostName = NULL; NET_SocksPort = 0; TRACEMSG(("Clearing Socks Host\n")); } socksFailure=FALSE; return(1); } /* Used internally as a utility function. */ PRIVATE void NET_FreeDNSStruct(DNSEntry * theVictim) { if(theVictim) { FREE(theVictim->ips); FREEIF(theVictim->hostname); FREE(theVictim); } } /* Used when user turns the cache off (dnsCacheExpiration is set to 0). */ PRIVATE void NET_DeleteDNSList(void) { XP_List * list_obj = dns_list; DNSEntry * dns_entry; if(!list_obj) return; while((dns_entry = (DNSEntry *)XP_ListNextObject(list_obj)) != 0) { XP_ListRemoveObject(dns_list, dns_entry); NET_FreeDNSStruct(dns_entry); } XP_ListDestroy(dns_list); return; } PUBLIC void NET_SetDNSExpirationPref(int32 n) { if((dnsCacheExpiration = n) <= 0) NET_DeleteDNSList(); } MODULE_PRIVATE int PR_CALLBACK NET_DNSExpirationPrefChanged(const char * newpref, void * data) { int32 n; if (PREF_OK != PREF_GetIntPref(pref_dnsExpiration, &n) ) n = DEF_DNS_EXPIRATION; NET_SetDNSExpirationPref((int32)n); return PREF_NOERROR; } /* the dnsCacheExpiration units are seconds */ PRIVATE int32 net_GetDNSExpiration(void) { return dnsCacheExpiration; } /* net_CacheDNSEntry * * caches the results of a dns lookup for fast * retrieval */ PRIVATE void net_CacheDNSEntry(char * hostname, PRHostEnt * host_pointer, int32 addrCount) { #if defined(MAX_DNS_LIST_SIZE) && (MAX_DNS_LIST_SIZE == 0) /* If MAX_DNS_LIST_SIZE is defined, and 0, don't cache anything. */ return; #else /* !defined(MAX_DNS_LIST_SIZE) || (MAX_DNS_LIST_SIZE > 0) */ /* Otherwise, MAX_DNS_LIST_SIZE is either the number of entries to cache, or is undefined, meaning "unlimited." */ int32 i; DNSEntry * new_entry; /* Make sure incomming data is valid. */ if(!hostname || !host_pointer) return; /* Determine whether or not we've got a list going yet. If not, get one */ if(!dns_list) dns_list = XP_ListNew(); if(!dns_list) return; /* Create our DNSEntry object for caching. */ if(!(new_entry = PR_NEW(DNSEntry))) return; /* Copy the host name. */ new_entry->hostname = 0; StrAllocCopy(new_entry->hostname, hostname); if(!new_entry->hostname) { PR_Free(new_entry); return; } /* Copy all the ip addresses. */ if( !(new_entry->ips = (PRUint32 *) PR_Malloc(sizeof(PRUint32) * addrCount)) ) { PR_Free(new_entry->hostname); PR_Free(new_entry); return; } PR_ASSERT(host_pointer->h_length == 4); for(i=0; i < addrCount; i++) { memcpy(&new_entry->ips[i], host_pointer->h_addr_list[i], 4); } /* Copy all the other data. */ new_entry->addressCount = addrCount; new_entry->h_length = host_pointer->h_length; new_entry->expirationTime = time(NULL) + (net_GetDNSExpiration()); /* Current time plus expiration */ XP_ListAddObject(dns_list, new_entry); # ifdef MAX_DNS_LIST_SIZE /* check to make sure the list is not overflowing the maximum size. */ if(XP_ListCount(dns_list) > MAX_DNS_LIST_SIZE) { DNSEntry * first_entry = (DNSEntry *) XP_ListRemoveEndObject(dns_list); NET_FreeDNSStruct(first_entry); } # endif /* defined(MAX_DNS_LIST_SIZE) */ #endif /* !defined(MAX_DNS_LIST_SIZE) || (MAX_DNS_LIST_SIZE > 0) */ return; } /* net_CheckDNSCache * * checks the list of cached dns entries and returns * the dns info in the form of struct hostent if a match is * found for the passed in hostname */ PRIVATE DNSEntry * net_CheckDNSCache(CONST char * hostname) { XP_List *list_obj = dns_list; DNSEntry * dns_entry; if(!hostname || !dns_list) return(0); while((dns_entry = (DNSEntry *)XP_ListNextObject(list_obj)) != 0) { TRACEMSG(("net_CheckDNSCache: comparing %s and %s", hostname, dns_entry->hostname)); if(dns_entry->hostname && !PL_strcasecmp(hostname, dns_entry->hostname)) { /* See if the dns entry has expired, if so, get rid of it */ if(dns_entry->expirationTime < time(NULL)) { XP_ListRemoveObject(dns_list, dns_entry); NET_FreeDNSStruct(dns_entry); return 0; } return(dns_entry); } } return(0); } /* a list of dis-allowed ports for gopher connections for security reasons */ PRIVATE int net_bad_ports_table[] = { 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 135, 143, 389, 512, 513, 514, 515, 526, 530, 531, 532, 540, 556, 601, 6000, 0 }; /* Lookup a host name. If ASYNC_DNS is defined a platform specific async * lookup will occur. * * return: 0 on sucess, -1 on failure. MK_WAITING_FOR_LOOKUP is always * returned after first call when ASYNC_DNS is defined (you have to call * it again). * * On success hostEnt is not null. */ #if defined(XP_WIN) && defined(MODULAR_NETLIB) extern int NET_AsyncDNSLookup(void* aContext, const char* aHostPort, PRHostEnt** aHoststructPtrPtr, PRFileDesc* aSocket); #endif PRIVATE int #ifndef ASYNC_DNS net_dns_lookup(char *host, PRHostEnt **hostEnt, char *dbbuf, PRHostEnt *hpbuf) #else net_dns_lookup(MWContext *windowID, char *host, PRHostEnt **hostEnt, PRFileDesc *socket) #endif /* ASYNC_DNS */ { int status; NET_InGetHostByName++; PR_ASSERT(host); PR_ASSERT(hostEnt); #ifndef ASYNC_DNS PR_ASSERT(dbbuf); PR_ASSERT(hpbuf); /* Not asyncronus, completes a full lookup before returing. */ status = PR_GetHostByName(host, dbbuf, PR_NETDB_BUF_SIZE, hpbuf); if(status == PR_SUCCESS) { /* Success, hpbuf points to a valid hostent. */ NET_InGetHostByName--; *hostEnt=hpbuf; return status; } #else /* ASYNC_DNS */ PR_ASSERT(windowID); PR_ASSERT(socket); /* FE_StartAsyncDNSLookup should fill in the hoststruct * or leave zero the pointer if not found * it can also return MK_WAITING_FOR_LOOKUP * if it's not done yet. * * dbbuf and hpbuf are not needed in ASYNC_DNS case. */ #ifdef MODULAR_NETLIB status = NET_AsyncDNSLookup(windowID, host, hostEnt, socket); #else status = FE_AsyncDNSLookup(windowID, host, hostEnt, socket); #endif if(status == MK_WAITING_FOR_LOOKUP) { /* Always get here after first call to FE_Async.. for a * particular host name. This ensures execution doesn't * block. */ NET_InGetHostByName--; return status; } else if(status == 0) { /* Success, hostEnt points to a valid hostent. */ NET_InGetHostByName--; return status; } #endif /* ASYNC_DNS */ /* FAIL */ NET_InGetHostByName--; /* must set this pointer to NULL if we fail. FE_AsyncDNSLookup does * this but PR_GetHostByName doesn't, so we cover our bases here. */ *hostEnt = NULL; return -1; } /* find the ip address of a host and set sin->sin_addr to that address * return 0 on success */ PRIVATE int net_FindAddress (const char *host_ptr, PRNetAddr *net_addr, MWContext *window_id, PRFileDesc *sock) { PRHostEnt *hoststruct_pointer; /* Pointer to host - See netdb.h */ DNSEntry * cache_pointer; char *port, *host_port=0, *host_cp; /* acts as a flag to determine whether or not this is the first failure. If it is, we want to sanityCheckDNS which determines whether or not we've got a good net connection, sort of */ static XP_Bool first_dns_failure=TRUE; static int random_host_number = -1; static time_t random_host_expiration = 0; static XP_Bool tryPAD=TRUE; XP_Bool is_numeric_ip; if(!host_ptr || !*host_ptr) return -1; /* Proxy Auto-Discovery (PAD) */ if(tryPAD && MK_PadEnabled && MK_padPacURL && *MK_padPacURL) { PRHostEnt *hostEnt=NULL; static PRFileDesc *socket=NULL; if(!PL_strncasecmp(MK_padPacURL, "file:", 5)) { /* Don't allow file urls because they're hard to figure out in an xp way. */ tryPAD=FALSE; foundPADPAC=FALSE; } else { if(socket == NULL) socket=PR_NewTCPSocket(); if(socket != NULL) { char *padHost=NET_ParseURL(MK_padPacURL, GET_HOST_PART); if(padHost && *padHost) { char *colon=PL_strchr(padHost, ':'); int status; #ifndef ASYNC_DNS char dbbuf[PR_NETDB_BUF_SIZE]; PRHostEnt dpbuf; #endif if(colon) *colon='\0'; #ifndef ASYNC_DNS status = net_dns_lookup(padHost, &hostEnt, dbbuf, &dpbuf); #else status = net_dns_lookup(window_id, padHost, &hostEnt, socket); #endif /* ASYNC_DNS */ if(status != MK_WAITING_FOR_LOOKUP) { PR_Close(socket); socket=NULL; if(hostEnt != NULL) /* We found the padpac */ foundPADPAC=TRUE; tryPAD=FALSE; } if(colon) *colon=':'; } else { PR_Close(socket); socket=NULL; tryPAD=FALSE; } PR_FREEIF(padHost); } /* End socket not null */ } } /* End tryPAD */ StrAllocCopy(host_port, host_ptr); /* Make a copy we can mess with */ if (!host_port) return -1; /* Parse port number if present */ port = PL_strchr(host_port, ':'); if (port) { *port++ = 0; if (NET_IS_DIGIT(*port)) { unsigned short port_num = (unsigned short) atol(port); int i; /* check for illegal ports */ /* if the explicit port number equals * the default port number let it through */ if(port_num != PR_ntohs(net_addr->inet.port)) { /* disallow well known ports */ for(i=0; net_bad_ports_table[i]; i++) if(port_num == net_bad_ports_table[i]) { char *error_msg = PL_strdup(XP_GetString(MK_PORT_ACCESS_NOT_ALLOWED)); if(error_msg) { FE_Alert(window_id, error_msg); PR_Free(error_msg); } /* return special error code * that NET_BeginConnect knows * about. */ PR_Free(host_port); return(MK_UNABLE_TO_CONNECT); } net_addr->inet.port = PR_htons(port_num); } } } /* see if this host entry is already cached */ if( (cache_pointer = net_CheckDNSCache(host_port)) != NULL ) { /* call FE_ClearDNSSelect to catch the case of a cache hit before * a successfull lookup response for this particular socket. * This happens when a previous socket lookup for the same host * succeeded and propagated the cache entry before this one * finished. */ NET_ClearDNSSelect(window_id, sock); net_addr->inet.ip = cache_pointer->ips[0]; PR_Free(host_port); return(0); /* FOUND OK */ } /* Determine whether or not we're dealing with an ip address as the host */ is_numeric_ip = TRUE; /* init positive */ for(host_cp = host_port; *host_cp; host_cp++) if(!NET_IS_DIGIT(*host_cp) && *host_cp != '.') { is_numeric_ip = FALSE; break; } /* Parse host number if present. */ if (is_numeric_ip) { PRUint16 port = net_addr->inet.port; /* save a copy */ if(PR_SUCCESS != PR_StringToNetAddr(host_port, net_addr)) { PR_ASSERT(0); PR_Free(host_port); return(-1); /* fail */ } net_addr->inet.port = port; /* StringToNetAddr overites the port num */ /* name not number */ } else { int32 addressCount=0; char *remapped_host_port=0; /* randomly remap home.netscape.com to home1.netscape.com through * home32.netscape.com * cache the original name not the new one. */ if((!PL_strncasecmp(host_port, "home.", 5) || !PL_strncasecmp(host_port, "rl.", 3)) && (PL_strcasestr(host_port+2, ".netscape.com") || PL_strcasestr(host_port+2, ".mcom.com"))) { time_t cur_time = time(NULL); char temp_string[32]; XP_Bool is_rl_host; *temp_string = '\0'; is_rl_host = !PL_strncasecmp(host_port, "rl.", 3); if(random_host_number == -1 || random_host_expiration < cur_time) { /* pick a new random number */ random_host_expiration = cur_time + (60 * 5); /* five min */ random_host_number = (XP_RANDOM() & 31); } if(is_rl_host) PR_snprintf(temp_string, sizeof(temp_string), "rl%d%s", random_host_number+1, host_port+2); else PR_snprintf(temp_string, sizeof(temp_string), "home%d%s", random_host_number+1, host_port+4); StrAllocCopy(remapped_host_port, temp_string); TRACEMSG(("Remapping %s to %s", host_port, remapped_host_port)); } #ifdef XP_UNIX { /* Implement a terrible hack just for unix. If the environment * variable SOCKS_NS is defined, stomp on the host that the DNS * code uses for host lookup to be a specific ip address. */ static char firstTime = 1; if (firstTime) { PRNetAddr netAddr; PRStatus status; char *ns = getenv("SOCKS_NS"); firstTime = 0; if (ns && ns[0]) { /* Use a specific host for dns lookups */ extern int res_init (void); res_init(); status = PR_StringToNetAddr(ns, &netAddr); if (PR_SUCCESS == status) { _res.nsaddr_list[0].sin_addr.s_addr = netAddr.inet.ip; } _res.nscount = 1; } } } #endif { int status; #ifndef ASYNC_DNS char dbbuf[PR_NETDB_BUF_SIZE]; PRHostEnt dpbuf; #endif /* malloc the string to prevent overflow */ char *msg = PR_smprintf(XP_GetString(XP_PROGRESS_LOOKUPHOST), host_port); if(msg) { #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) PM_Status(window_id, NULL, msg); #else NET_Progress(window_id, msg); #endif PR_Free(msg); } TIMING_STARTCLOCK_NAME("dns:lookup", (remapped_host_port ? remapped_host_port : host_port)); #ifndef ASYNC_DNS status = net_dns_lookup( (remapped_host_port ? remapped_host_port : host_port), &hoststruct_pointer, dbbuf, &dpbuf); #else status = net_dns_lookup(window_id, (remapped_host_port ? remapped_host_port : host_port), &hoststruct_pointer, sock); #endif /* ASYNC_DNS */ /* sucess if hoststruct_pointer is not null */ if(status == MK_WAITING_FOR_LOOKUP) { PR_Free(host_port); return status; } } TIMING_STOPCLOCK_NAME("dns:lookup", (remapped_host_port ? remapped_host_port : host_port), window_id, (hoststruct_pointer ? "ok" : "error")); if (!hoststruct_pointer) { if(first_dns_failure) { first_dns_failure = FALSE; /* On the proxy causes confusing messages. * This function is only implemented on for XP_UNIX. */ NET_SanityCheckDNS(window_id); } TRACEMSG(("mktcp.c: Can't find host name `%s'. Errno #%d\n", host_port, PR_GetError())); PR_Free(host_port); TRACEMSG(("gethostbyname failed with error: %d\n", PR_GetError())); return -1; /* Fail? */ } /* Count the number of addresses returned in the A record. */ for (addressCount=0; hoststruct_pointer->h_addr_list[addressCount]; addressCount++) {;} /* If the addressCount is zero we've got a problem */ if (addressCount == 0) { PR_ASSERT(0); PR_Free(host_port); return -1; } /* Copy the first address in the list to the sin. char ** h_addr_list */ PR_ASSERT(hoststruct_pointer->h_length == 4); memcpy(&net_addr->inet.ip, hoststruct_pointer->h_addr_list[0], 4); /* if NET_GetDNSExpiration() returns 0 we are considering the cache disabled */ if(net_GetDNSExpiration() > 0) net_CacheDNSEntry(host_port, hoststruct_pointer, addressCount); } /* end name not number else*/ #ifdef DEBUG { char str[64]; PR_NetAddrToString(net_addr, str, sizeof(str)); TRACEMSG(("TCP.c: Found address %s and port %d", str, (int)PR_ntohs(net_addr->inet.port))); } #endif PR_Free(host_port); return(0); /* OK, we found an address */ } /* FREE left over tcp connection data if there is any */ MODULE_PRIVATE void NET_FreeTCPConData(TCP_ConData * data) { TRACEMSG(("Freeing TCPConData...Done with connect (i hope)")); FREEIF(data) } PRIVATE int net_start_first_connect(const char *host, PRFileDesc *sock, MWContext *window_id, TCP_ConData *tcp_con_data, char **error_msg, PRUint32 localIP) { /* malloc the string to prevent overflow */ int32 len = PL_strlen(XP_GetString(XP_PROGRESS_CONTACTHOST)); char * buf; len += PL_strlen(host); buf = (char *)PR_Malloc((len+10)*sizeof(char)); if(buf) { PR_snprintf(buf, (len+10)*sizeof(char), XP_GetString(XP_PROGRESS_CONTACTHOST), host); #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) PM_Status(window_id, NULL, buf); #else NET_Progress(window_id, buf); #endif FREE(buf); } HG26300 /* set the begining time to be the current time. * the timeout value will be compared to this * later */ tcp_con_data->begin_time = time(NULL); #define CONNECT_TIMEOUT 0 /* Bind to a user specified local IP address if specified. * Netlib does not check the validity of this address. * The user is guaranteing it's existence and usability. */ if ( localIP > 0 ) { PRStatus status; PRErrorCode errorCode; /* see http://www.mozilla.org/docs/refList/refNSPR/prerr.htm#1027954 */ PRNetAddr *addr = (PRNetAddr *)PR_Malloc(sizeof(PRNetAddr)); if (!addr) return MK_UNABLE_TO_CONNECT; status = PR_InitializeNetAddr(PR_IpAddrNull, 0, addr); if (status != PR_SUCCESS) { errorCode = PR_GetError(); } else { addr->inet.ip = localIP; status = PR_Bind(sock, addr); if (status != PR_SUCCESS) { errorCode = PR_GetError(); } } PR_Free(addr); } /* if it's not equal to PR_SUCCESS something went wrong * * PRNetAddr is binary compatible with struct sockaddr */ if(PR_SUCCESS != PR_Connect (sock, &tcp_con_data->net_addr, CONNECT_TIMEOUT)) { int rv = PR_GetError(); #if !defined(XP_MAC) && !defined(XP_WIN16) PR_ASSERT(rv != PR_WOULD_BLOCK_ERROR); /* should never happen */ #endif if (rv == PR_IN_PROGRESS_ERROR || rv == PR_WOULD_BLOCK_ERROR) { tcp_con_data->next_state = NET_TCP_FINISH_CONNECT; return(MK_WAITING_FOR_CONNECTION); /* not connected yet */ } else if (rv == PR_IS_CONNECTED_ERROR) { return(MK_CONNECTED); /* already connected */ } else { PR_Close(sock); if(rv == PR_CONNECT_REFUSED_ERROR) { *error_msg = NET_ExplainErrorDetails(MK_CONNECTION_REFUSED, host); TRACEMSG(("connect: refused\n")); return(MK_CONNECTION_REFUSED); } else if(rv == PR_CONNECT_TIMEOUT_ERROR) { *error_msg = NET_ExplainErrorDetails(MK_CONNECTION_TIMED_OUT); TRACEMSG(("connect: timed out\n")); return(MK_CONNECTION_TIMED_OUT); } else { *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONNECT, rv); TRACEMSG(("connect: unable to connect %i\n", rv)); return (MK_UNABLE_TO_CONNECT); } } } /* else good connect */ return(MK_CONNECTED); } /* Finds the DNSEntry with hostname in it, removes it if it only has one address, otherwise removes the first address and shifts the other(s) forward so they can be tried. */ PRIVATE XP_Bool net_connection_failed(CONST char *hostname) { DNSEntry *dns_entry = 0; char *hostCopy=NULL, *port=NULL; if(!hostname) return FALSE; StrAllocCopy(hostCopy, hostname); if(!hostCopy) return FALSE; /* Look for a port */ if( (port = PL_strchr(hostCopy, ':')) != NULL ) *port = '\0'; dns_entry = net_CheckDNSCache(hostCopy); FREE(hostCopy); /* If we find the cached dns entry pull the address off the top because it is the one that failed. Then shift the others up to the front of the list. */ if (dns_entry) { /* If there is only one (most common case) ip address associated with this host, blow the entire entry away */ if (dns_entry->addressCount == 1) { XP_ListRemoveObject(dns_list, dns_entry); NET_FreeDNSStruct(dns_entry); return FALSE; } else /* Failover case */ { dns_entry->addressCount--; /* Shift addresses up one, overwriting the first one */ memmove(dns_entry->ips, &dns_entry->ips[1], sizeof(PRUint32) * dns_entry->addressCount ); /* Null terminate the array */ dns_entry->ips[dns_entry->addressCount] = 0; return TRUE; } } /* If the host wasn't in the cache then caching is off or an ip address was sent in here and we don't want to keep trying this address so return false. */ return FALSE; } /* * Non blocking connect begin function. It will most likely * return negative. If it does, NET_ConnectFinish() will * need to be called repeatably until a connect is established. * * return's negative on error * return's MK_WAITING_FOR_CONNECTION when continue is neccessary * return's MK_CONNECTED on true connect */ MODULE_PRIVATE int NET_BeginConnect (CONST char *url, char *ip_address_string, char *prot_name, int def_port, PRFileDesc **sock, HG92743 TCP_ConData **tcp_con_data, MWContext *window_id, char **error_msg, unsigned long socks_host, short socks_port, PRUint32 localIP) { char * proxy=NULL; int32 iPort=0; char text[MAXHOSTNAMELEN + 8]; CONST char *host; char *althost=0; int status; PRFileDesc *fd; char *host_string=0; PRSocketOptionData opt; #ifdef MOZ_OFFLINE if (NET_IsOffline()) /* if we're offline, ret error - dmb 11/25/96 */ return MK_OFFLINE; #endif /* MOZ_OFFLINE */ TRACEMSG(("NET_BeginConnect called for url: %s", url)); /* One time startup flag. If this is the first time in BeginConnect, * make sure the socks is setup if it needs to be. */ if(!setupSocks) { setupSocks=TRUE; if (NET_GetProxyStyle() == PROXY_STYLE_MANUAL) { if ( (PREF_OK != PREF_CopyCharPref(pref_socksServer,&proxy)) || !proxy || !*proxy ) { NET_SetSocksHost(NULL); /* NULL is ok */ } else { if ( PREF_OK == PREF_GetIntPref(pref_socksPort,&iPort) ) { PR_snprintf(text, sizeof(text), "%s:%d", proxy, iPort); NET_SetSocksHost(text); } } } } /* in case finish connect calls us */ if(*tcp_con_data) NET_FreeTCPConData(*tcp_con_data); /* construct state table data */ *tcp_con_data = PR_NEW(TCP_ConData); if(!*tcp_con_data) { *error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); return(MK_OUT_OF_MEMORY); } memset(*tcp_con_data, 0, sizeof(TCP_ConData)); /* Set up Internet defaults and port*/ PR_InitializeNetAddr(PR_IpAddrNull, (PRUint16) def_port, &(*tcp_con_data)->net_addr); if(NET_URL_Type(url)) { host_string = NET_ParseURL(url, GET_HOST_PART); host = host_string; } else { /* assume that a hostname was passed directly * instead of a URL */ host = url; } /* build a socket */ *sock = PR_NewTCPSocket(); HG59609 if(*sock == NULL) { int error = PR_GetError(); TRACEMSG(("NETSOCKET call returned INVALID_SOCKET: %d", error)); NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CREATE_SOCKET, error); FREE_AND_CLEAR(host_string); return(MK_UNABLE_TO_CREATE_SOCKET); } HG40252 /* make the socket non-blocking */ opt.option = PR_SockOpt_Nonblocking; opt.value.non_blocking = PR_TRUE; PR_SetSocketOption(*sock, &opt); #if defined(XP_WIN16) || (defined(XP_OS2) && !defined(XP_OS2_DOUGSOCK)) opt.option = PR_SockOpt_Linger; opt.value.linger.polarity = PR_TRUE; opt.value.linger.linger = PR_INTERVAL_NO_WAIT; PR_SetSocketOption(*sock, &opt); #endif /* XP_WIN16 */ /* copy the sock no */ fd = *sock; HG28879 { /* if the socks host is invalid */ if ( socksFailure && (NET_GetProxyStyle() == PROXY_STYLE_MANUAL) ) { char * prefSocksHost = NULL; /* Tell the FE about the failure */ int32 len = PL_strlen(XP_GetString(XP_PROGRESS_UNABLELOCATE)); char * buf; if( (PREF_OK == PREF_CopyCharPref(pref_socksServer,&prefSocksHost)) && prefSocksHost) { len += PL_strlen(prefSocksHost); buf = (char *)PR_Malloc((len+10)*sizeof(char)); if(buf) { PR_snprintf(buf, (len+10)*sizeof(char), XP_GetString(XP_PROGRESS_UNABLELOCATE), prefSocksHost); #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) PM_Status(window_id, NULL, buf); #else NET_Progress(window_id, buf); #endif FREE(buf); } /* Tell the user about the failure */ *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LOCATE_SOCKS_HOST, prefSocksHost); /* clean up prior to leaving */ NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; PR_Close(*sock); *sock = NULL; FREE_AND_CLEAR(host_string); PR_FREEIF(prefSocksHost); } return MK_UNABLE_TO_LOCATE_HOST; } } HG71089 /* if an IP address string was passed in, then use it instead of the * hostname to do the DNS lookup. */ if ( ip_address_string ) { char *port; StrAllocCopy(althost, ip_address_string); port = PL_strchr(host, ':'); if ( port ) { StrAllocCat(althost, port); } } status = net_FindAddress(althost?althost:host, &((*tcp_con_data)->net_addr), window_id, *sock); if (althost) { PR_Free(althost); } if(status == MK_WAITING_FOR_LOOKUP) { (*tcp_con_data)->next_state = NET_TCP_FINISH_DNS_LOOKUP; FREE_AND_CLEAR(host_string); return(MK_WAITING_FOR_CONNECTION); /* not connected yet */ } else if (status < 0) { { /* malloc the string to prevent overflow */ int32 len = PL_strlen(XP_GetString(XP_PROGRESS_UNABLELOCATE)); char * buf; len += PL_strlen(host); buf = (char *)PR_Malloc((len+10)*sizeof(char)); if(buf) { PR_snprintf(buf, (len+10)*sizeof(char), XP_GetString(XP_PROGRESS_UNABLELOCATE), host); #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) PM_Status(window_id, NULL, buf); #else NET_Progress(window_id, buf); #endif FREE(buf); } } NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; PR_Close(*sock); *sock = NULL; /* @@@@@ HACK! If we detect an illegal port in * net_FindAddress we put up an error message * and return MK_UNABLE_TO_CONNECT. Don't * print another message */ if(status != MK_UNABLE_TO_CONNECT) *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LOCATE_HOST, *host ? host : "(no name specified)"); FREE_AND_CLEAR(host_string); return MK_UNABLE_TO_LOCATE_HOST; } TIMING_STARTCLOCK_NAME("tcp:connect", url); status = net_start_first_connect(host, *sock, window_id, *tcp_con_data, error_msg, localIP); if(status != MK_WAITING_FOR_CONNECTION) { TIMING_STOPCLOCK_NAME("tcp:connect", url, window_id, "error"); NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; } else { (*tcp_con_data)->next_state = NET_TCP_FINISH_CONNECT; /* save in case we need it */ HG83665 } if(status < 0) { net_connection_failed(host); PR_Close(*sock); *sock = NULL; } FREE_AND_CLEAR(host_string); return(status); } /* * Non blocking connect finishing function. This routine polls * the socket until a connection finally takes place or until * an error occurs. NET_ConnectFinish() will need to be called * repeatably until a connect is established. * * return's negative on error * return's MK_WAITING_FOR_CONNECTION when continue is neccessary * return's MK_CONNECTED on true connect */ PUBLIC int NET_FinishConnect (CONST char *url, char *prot_name, int def_port, PRFileDesc **sock, TCP_ConData **tcp_con_data, MWContext *window_id, char **error_msg, PRUint32 localIP) { int status; char *host=NULL; TRACEMSG(("NET_FinishConnect called for url: %s", url)); if(!*tcp_con_data) /* safty valve */ { *error_msg = NET_ExplainErrorDetails(MK_OUT_OF_MEMORY); return(MK_OUT_OF_MEMORY); } switch((*tcp_con_data)->next_state) { case NET_TCP_FINISH_DNS_LOOKUP: { char * host_string=NULL; const char * host = NULL; if(NET_URL_Type(url)) { host_string = NET_ParseURL(url, GET_HOST_PART); host = host_string; } else { /* assume that a hostname was passed directly * instead of a URL */ host = url; } status = net_FindAddress(host, &((*tcp_con_data)->net_addr), window_id, *sock); if(status == MK_WAITING_FOR_LOOKUP) { (*tcp_con_data)->next_state = NET_TCP_FINISH_DNS_LOOKUP; FREE_AND_CLEAR(host_string); return(MK_WAITING_FOR_CONNECTION); /* not connected yet */ } else if (status < 0) { { /* malloc the string to prevent overflow */ int32 len = PL_strlen(XP_GetString(XP_PROGRESS_UNABLELOCATE)); char * buf; len += PL_strlen(host); buf = (char *)PR_Malloc((len+10)*sizeof(char)); if(buf) { PR_snprintf(buf, (len+10)*sizeof(char), XP_GetString(XP_PROGRESS_UNABLELOCATE), host); #if defined(SMOOTH_PROGRESS) && !defined(MODULAR_NETLIB) PM_Status(window_id, NULL, buf); #else NET_Progress(window_id, buf); #endif FREE(buf); } } NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_LOCATE_HOST, host); FREE_AND_CLEAR(host_string); return MK_UNABLE_TO_LOCATE_HOST; } TIMING_STARTCLOCK_NAME("tcp:connect", url); status = net_start_first_connect(host, *sock, window_id, *tcp_con_data, error_msg, localIP); if(status != MK_WAITING_FOR_CONNECTION) { TIMING_STOPCLOCK_NAME("tcp:connect", url, window_id, "error"); NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; } else { (*tcp_con_data)->next_state = NET_TCP_FINISH_CONNECT; } if(status < 0) { net_connection_failed(host); TRACEMSG(("mktcp.c: Error during connect %d\n", PR_GetError())); PR_Close(*sock); *sock = NULL; } FREE_AND_CLEAR(host_string); return(status); } /* end of this part of the case */ case NET_TCP_FINISH_CONNECT: { PRPollDesc pd; pd.fd = *sock; pd.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT; /* do another connect here to see if we are actually connected * * PRNetAddr is binary compatible with struct sockaddr */ if(PR_Poll(&pd, 1, PR_INTERVAL_NO_TIMEOUT) < 1) { PR_ASSERT(0); return MK_WAITING_FOR_CONNECTION; /* perhaps we should error here */ } #if defined(XP_WIN16) /* Bind to a user specified local IP address if specified. * Netlib does not check the validity of this address. * The user is guaranteing it's existence and usability. */ if ( localIP > 0 ) { PRStatus status; PRErrorCode errorCode; /* see http://www.mozilla.org/docs/refList/refNSPR/prerr.htm#1027954 */ PRNetAddr *addr = (PRNetAddr *)PR_Malloc(sizeof(PRNetAddr)); if (!addr) return MK_UNABLE_TO_CONNECT; status = PR_InitializeNetAddr(PR_IpAddrNull, 0, addr); if (status != PR_SUCCESS) { errorCode = PR_GetError(); } else { addr->inet.ip = localIP; status = PR_Bind(sock, addr); if (status != PR_SUCCESS) { errorCode = PR_GetError(); } } PR_Free(addr); } if(PR_SUCCESS != PR_Connect (*sock, &(*tcp_con_data)->net_addr, CONNECT_TIMEOUT)) #else if(PR_SUCCESS != PR_GetConnectStatus(&pd)) #endif { int error; error = PR_GetError(); #if !defined(XP_MAC) && !defined(XP_WIN16) PR_ASSERT(error != PR_WOULD_BLOCK_ERROR); /* should never happen */ #endif if (error == PR_IN_PROGRESS_ERROR || error == PR_WOULD_BLOCK_ERROR) { /* check the begin time against the current time minus * the timeout value. Error out if past the timeout */ if((time_t)(time(NULL)-net_tcp_connect_timeout) > (*tcp_con_data)->begin_time) { error = XP_ERRNO_ETIMEDOUT; goto error_out; } /* still waiting */ return MK_WAITING_FOR_CONNECTION; } else if(error != PR_IS_CONNECTED_ERROR) { error_out: /* if EISCONN then we actually are connected * otherwise return an error */ /* At this point we know there was a problem with the ip address we tried. */ TRACEMSG(("mktcp.c: Error during connect: %d\n", error)); host = NET_ParseURL(url, GET_HOST_PART); if(!host) return -1; /* In the case when the url is a host, no protocol, NET_ParseURL returns null byte. We run up against this case when a proxy was placed into the url instead of the url itself.*/ if(*host == '\0') { FREE(host); host = NULL; StrAllocCopy(host, url); } /* If we should try other addresses with this tcpConnection, do it */ if (net_connection_failed(host)) { int status; if(*host == '\0') { /* the url is a hostname */ FREE(host); if( !(host = PL_strdup(url)) ) return -1; } /* Go get another of the ip addresses */ status = net_FindAddress(host, &((*tcp_con_data)->net_addr), window_id, *sock); FREE(host); if(status == 0) return(NET_BeginConnect (url, NULL, prot_name, def_port, sock, HG98376 tcp_con_data, window_id, error_msg, 0, 0, localIP)); /* else fall through to the error */ } else FREE(host); TIMING_STOPCLOCK_NAME("tcp:connect", url, window_id, "error"); HG92362 if (error == PR_CONNECT_REFUSED_ERROR) { char * host = NET_ParseURL(url, GET_HOST_PART); *error_msg = NET_ExplainErrorDetails( MK_CONNECTION_REFUSED, host); FREE(host); NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; return MK_CONNECTION_REFUSED; } else if (error == PR_CONNECT_TIMEOUT_ERROR) { NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; *error_msg = NET_ExplainErrorDetails(MK_CONNECTION_TIMED_OUT); return MK_CONNECTION_TIMED_OUT; } else { NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONNECT, error); return MK_UNABLE_TO_CONNECT; } } } TIMING_STOPCLOCK_NAME("tcp:connect", url, window_id, "ok"); TRACEMSG(("mktcp.c: Successful connection (message 1)")); NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; return MK_CONNECTED; } default: NET_FreeTCPConData(*tcp_con_data); *tcp_con_data = 0; TRACEMSG(("mktcp.c: bad state during connect")); assert(0); /* should never happen */ *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONNECT, 0); return(MK_UNABLE_TO_CONNECT); } /* end switch on next_state */ PR_ASSERT(0); /* should never get here */ *error_msg = NET_ExplainErrorDetails(MK_UNABLE_TO_CONNECT, 0); return(MK_UNABLE_TO_CONNECT); } /* Free any memory used up */ MODULE_PRIVATE void NET_CleanupTCP(void) { if(dns_list) { DNSEntry * tmp_entry; while((tmp_entry = (DNSEntry *)XP_ListRemoveTopObject(dns_list)) != NULL) { NET_FreeDNSStruct(tmp_entry); } XP_ListDestroy(dns_list); dns_list = 0; } /* we really should free the socket_buffer but * that is in the other module as a private :( */ return; }