Add an assortment of missing consts I've just noticed.
[u/mdw/putty] / windows / winnet.c
index f8838be..4fddd72 100644 (file)
@@ -34,6 +34,19 @@ const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
  */
 typedef struct Socket_tag *Actual_Socket;
 
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+    struct addrinfo *ai;              /* steps along addr->ais */
+#endif
+    int curraddr;
+};
+
 struct Socket_tag {
     const struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
@@ -51,7 +64,9 @@ struct Socket_tag {
     char oobdata[1];
     int sending_oob;
     int oobinline, nodelay, keepalive, privport;
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
     SockAddr addr;
+    SockAddrStep step;
     int port;
     int pending_error;                /* in case send() returns error */
     /*
@@ -64,28 +79,44 @@ struct Socket_tag {
 };
 
 struct SockAddr_tag {
+    int refcount;
     char *error;
-    /* 
-     * Which address family this address belongs to. AF_INET for
-     * IPv4; AF_INET6 for IPv6; AF_UNSPEC indicates that name
-     * resolution has not been done and a simple host name is held
-     * in this SockAddr structure.
-     * The hostname field is also used when the hostname has both
-     * an IPv6 and IPv4 address and the IPv6 connection attempt
-     * fails. We then try the IPv4 address.
-     * This 'family' should become an option in the GUI and
-     * on the commandline for selecting a default protocol.
-     */
-    int family;
+    int resolved;
 #ifndef NO_IPV6
     struct addrinfo *ais;             /* Addresses IPv6 style. */
-    struct addrinfo *ai;              /* steps along the linked list */
 #endif
     unsigned long *addresses;         /* Addresses IPv4 style. */
-    int naddresses, curraddr;
+    int naddresses;
     char hostname[512];                       /* Store an unresolved host name. */
 };
 
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
+ * not been done and a simple host name is held in this SockAddr
+ * structure.
+ */
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+    (!(addr)->resolved ? AF_UNSPEC : \
+     (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+#define SOCKADDR_FAMILY(addr, step) \
+    (!(addr)->resolved ? AF_UNSPEC : AF_INET)
+#endif
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+    ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+    ((step).curraddr = 0)
+#endif
+
 static tree234 *sktree;
 
 static int cmpfortree(void *av, void *bv)
@@ -96,6 +127,10 @@ static int cmpfortree(void *av, void *bv)
        return -1;
     if (as > bs)
        return +1;
+    if (a < b)
+       return -1;
+    if (a > b)
+       return +1;
     return 0;
 }
 
@@ -110,66 +145,52 @@ static int cmpforsearch(void *av, void *bv)
     return 0;
 }
 
-#define NOTHING
-#define DECL_WINSOCK_FUNCTION(linkage, rettype, name, params) \
-    typedef rettype (WINAPI *t_##name) params; \
-    linkage t_##name p_##name
-#define GET_WINSOCK_FUNCTION(module, name) \
-    p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL
-
-DECL_WINSOCK_FUNCTION(NOTHING, int, WSAAsyncSelect,
-                     (SOCKET, HWND, u_int, long));
-DECL_WINSOCK_FUNCTION(NOTHING, int, WSAEventSelect, (SOCKET, WSAEVENT, long));
-DECL_WINSOCK_FUNCTION(NOTHING, int, select,
-                     (int, fd_set FAR *, fd_set FAR *,
-                      fd_set FAR *, const struct timeval FAR *));
-DECL_WINSOCK_FUNCTION(NOTHING, int, WSAGetLastError, (void));
-DECL_WINSOCK_FUNCTION(NOTHING, int, WSAEnumNetworkEvents,
-                     (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
-DECL_WINSOCK_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
-DECL_WINSOCK_FUNCTION(static, int, WSACleanup, (void));
-DECL_WINSOCK_FUNCTION(static, int, closesocket, (SOCKET));
-DECL_WINSOCK_FUNCTION(static, u_long, ntohl, (u_long));
-DECL_WINSOCK_FUNCTION(static, u_long, htonl, (u_long));
-DECL_WINSOCK_FUNCTION(static, u_short, htons, (u_short));
-DECL_WINSOCK_FUNCTION(static, u_short, ntohs, (u_short));
-DECL_WINSOCK_FUNCTION(static, struct hostent FAR *, gethostbyname,
+DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
+DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
+DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
+DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short));
+DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short));
+DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
+DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
                      (const char FAR *));
-DECL_WINSOCK_FUNCTION(static, struct servent FAR *, getservbyname,
+DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
                      (const char FAR *, const char FAR *));
-DECL_WINSOCK_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
-DECL_WINSOCK_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
-DECL_WINSOCK_FUNCTION(static, int, connect,
+DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
+DECL_WINDOWS_FUNCTION(static, int, connect,
                      (SOCKET, const struct sockaddr FAR *, int));
-DECL_WINSOCK_FUNCTION(static, int, bind,
+DECL_WINDOWS_FUNCTION(static, int, bind,
                      (SOCKET, const struct sockaddr FAR *, int));
-DECL_WINSOCK_FUNCTION(static, int, setsockopt,
+DECL_WINDOWS_FUNCTION(static, int, setsockopt,
                      (SOCKET, int, int, const char FAR *, int));
-DECL_WINSOCK_FUNCTION(static, SOCKET, socket, (int, int, int));
-DECL_WINSOCK_FUNCTION(static, int, listen, (SOCKET, int));
-DECL_WINSOCK_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
-DECL_WINSOCK_FUNCTION(static, int, ioctlsocket,
+DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int));
+DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
                      (SOCKET, long, u_long FAR *));
-DECL_WINSOCK_FUNCTION(static, SOCKET, accept,
+DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
                      (SOCKET, struct sockaddr FAR *, int FAR *));
-DECL_WINSOCK_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
-DECL_WINSOCK_FUNCTION(static, int, WSAIoctl,
+DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
                      (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
                       LPDWORD, LPWSAOVERLAPPED,
                       LPWSAOVERLAPPED_COMPLETION_ROUTINE));
 #ifndef NO_IPV6
-DECL_WINSOCK_FUNCTION(static, int, getaddrinfo,
+DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
                      (const char *nodename, const char *servname,
                       const struct addrinfo *hints, struct addrinfo **res));
-DECL_WINSOCK_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
-DECL_WINSOCK_FUNCTION(static, int, getnameinfo,
+DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
+DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
                      (const struct sockaddr FAR * sa, socklen_t salen,
                       char FAR * host, size_t hostlen, char FAR * serv,
                       size_t servlen, int flags));
-DECL_WINSOCK_FUNCTION(static, char *, gai_strerror, (int ecode));
-DECL_WINSOCK_FUNCTION(static, int, WSAAddressToStringA,
+DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
+DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
                      (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
-                      LPTSTR, LPDWORD));
+                      LPSTR, LPDWORD));
 #endif
 
 static HMODULE winsock_module = NULL;
@@ -208,9 +229,9 @@ void sk_init(void)
 #ifndef NO_IPV6
     winsock2_module =
 #endif
-        winsock_module = LoadLibrary("WS2_32.DLL");
+        winsock_module = load_system32_dll("ws2_32.dll");
     if (!winsock_module) {
-       winsock_module = LoadLibrary("WSOCK32.DLL");
+       winsock_module = load_system32_dll("wsock32.dll");
     }
     if (!winsock_module)
        fatalbox("Unable to load any WinSock library");
@@ -221,60 +242,62 @@ void sk_init(void)
 #ifdef NET_SETUP_DIAGNOSTICS
        logevent(NULL, "Native WinSock IPv6 support detected");
 #endif
-       GET_WINSOCK_FUNCTION(winsock_module, getaddrinfo);
-       GET_WINSOCK_FUNCTION(winsock_module, freeaddrinfo);
-       GET_WINSOCK_FUNCTION(winsock_module, getnameinfo);
-       GET_WINSOCK_FUNCTION(winsock_module, gai_strerror);
+       GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo);
+       GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo);
+       GET_WINDOWS_FUNCTION(winsock_module, getnameinfo);
+       GET_WINDOWS_FUNCTION(winsock_module, gai_strerror);
     } else {
        /* Fall back to wship6.dll for Windows 2000 */
-       wship6_module = LoadLibrary("wship6.dll");
+       wship6_module = load_system32_dll("wship6.dll");
        if (wship6_module) {
 #ifdef NET_SETUP_DIAGNOSTICS
            logevent(NULL, "WSH IPv6 support detected");
 #endif
-           GET_WINSOCK_FUNCTION(wship6_module, getaddrinfo);
-           GET_WINSOCK_FUNCTION(wship6_module, freeaddrinfo);
-           GET_WINSOCK_FUNCTION(wship6_module, getnameinfo);
-           GET_WINSOCK_FUNCTION(wship6_module, gai_strerror);
+           GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo);
+           GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
+           GET_WINDOWS_FUNCTION(wship6_module, getnameinfo);
+           GET_WINDOWS_FUNCTION(wship6_module, gai_strerror);
        } else {
 #ifdef NET_SETUP_DIAGNOSTICS
            logevent(NULL, "No IPv6 support detected");
 #endif
        }
     }
-    GET_WINSOCK_FUNCTION(winsock2_module, WSAAddressToStringA);
+    GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA);
 #else
 #ifdef NET_SETUP_DIAGNOSTICS
     logevent(NULL, "PuTTY was built without IPv6 support");
 #endif
 #endif
 
-    GET_WINSOCK_FUNCTION(winsock_module, WSAAsyncSelect);
-    GET_WINSOCK_FUNCTION(winsock_module, WSAEventSelect);
-    GET_WINSOCK_FUNCTION(winsock_module, select);
-    GET_WINSOCK_FUNCTION(winsock_module, WSAGetLastError);
-    GET_WINSOCK_FUNCTION(winsock_module, WSAEnumNetworkEvents);
-    GET_WINSOCK_FUNCTION(winsock_module, WSAStartup);
-    GET_WINSOCK_FUNCTION(winsock_module, WSACleanup);
-    GET_WINSOCK_FUNCTION(winsock_module, closesocket);
-    GET_WINSOCK_FUNCTION(winsock_module, ntohl);
-    GET_WINSOCK_FUNCTION(winsock_module, htonl);
-    GET_WINSOCK_FUNCTION(winsock_module, htons);
-    GET_WINSOCK_FUNCTION(winsock_module, ntohs);
-    GET_WINSOCK_FUNCTION(winsock_module, gethostbyname);
-    GET_WINSOCK_FUNCTION(winsock_module, getservbyname);
-    GET_WINSOCK_FUNCTION(winsock_module, inet_addr);
-    GET_WINSOCK_FUNCTION(winsock_module, inet_ntoa);
-    GET_WINSOCK_FUNCTION(winsock_module, connect);
-    GET_WINSOCK_FUNCTION(winsock_module, bind);
-    GET_WINSOCK_FUNCTION(winsock_module, setsockopt);
-    GET_WINSOCK_FUNCTION(winsock_module, socket);
-    GET_WINSOCK_FUNCTION(winsock_module, listen);
-    GET_WINSOCK_FUNCTION(winsock_module, send);
-    GET_WINSOCK_FUNCTION(winsock_module, ioctlsocket);
-    GET_WINSOCK_FUNCTION(winsock_module, accept);
-    GET_WINSOCK_FUNCTION(winsock_module, recv);
-    GET_WINSOCK_FUNCTION(winsock_module, WSAIoctl);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect);
+    GET_WINDOWS_FUNCTION(winsock_module, select);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
+    GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
+    GET_WINDOWS_FUNCTION(winsock_module, closesocket);
+    GET_WINDOWS_FUNCTION(winsock_module, ntohl);
+    GET_WINDOWS_FUNCTION(winsock_module, htonl);
+    GET_WINDOWS_FUNCTION(winsock_module, htons);
+    GET_WINDOWS_FUNCTION(winsock_module, ntohs);
+    GET_WINDOWS_FUNCTION(winsock_module, gethostname);
+    GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
+    GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
+    GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
+    GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
+    GET_WINDOWS_FUNCTION(winsock_module, connect);
+    GET_WINDOWS_FUNCTION(winsock_module, bind);
+    GET_WINDOWS_FUNCTION(winsock_module, setsockopt);
+    GET_WINDOWS_FUNCTION(winsock_module, socket);
+    GET_WINDOWS_FUNCTION(winsock_module, listen);
+    GET_WINDOWS_FUNCTION(winsock_module, send);
+    GET_WINDOWS_FUNCTION(winsock_module, shutdown);
+    GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
+    GET_WINDOWS_FUNCTION(winsock_module, accept);
+    GET_WINDOWS_FUNCTION(winsock_module, recv);
+    GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
 
     /* Try to get the best WinSock version we can get */
     if (!sk_startup(2,2) &&
@@ -299,7 +322,8 @@ void sk_cleanup(void)
        sktree = NULL;
     }
 
-    p_WSACleanup();
+    if (p_WSACleanup)
+       p_WSACleanup();
     if (winsock_module)
        FreeLibrary(winsock_module);
 #ifndef NO_IPV6
@@ -308,8 +332,38 @@ void sk_cleanup(void)
 #endif
 }
 
+struct errstring {
+    int error;
+    char *text;
+};
+
+static int errstring_find(void *av, void *bv)
+{
+    int *a = (int *)av;
+    struct errstring *b = (struct errstring *)bv;
+    if (*a < b->error)
+        return -1;
+    if (*a > b->error)
+        return +1;
+    return 0;
+}
+static int errstring_compare(void *av, void *bv)
+{
+    struct errstring *a = (struct errstring *)av;
+    return errstring_find(&a->error, bv);
+}
+
+static tree234 *errstrings = NULL;
+
 char *winsock_error_string(int error)
 {
+    const char prefix[] = "Network error: ";
+    struct errstring *es;
+
+    /*
+     * Error codes we know about and have historically had reasonably
+     * sensible error messages for.
+     */
     switch (error) {
       case WSAEACCES:
        return "Network error: Permission denied";
@@ -382,9 +436,53 @@ char *winsock_error_string(int error)
        return "Network error: Resource temporarily unavailable";
       case WSAEDISCON:
        return "Network error: Graceful shutdown in progress";
-      default:
-       return "Unknown network error";
     }
+
+    /*
+     * Generic code to handle any other error.
+     *
+     * Slightly nasty hack here: we want to return a static string
+     * which the caller will never have to worry about freeing, but on
+     * the other hand if we call FormatMessage to get it then it will
+     * want to either allocate a buffer or write into one we own.
+     *
+     * So what we do is to maintain a tree234 of error strings we've
+     * already used. New ones are allocated from the heap, but then
+     * put in this tree and kept forever.
+     */
+
+    if (!errstrings)
+        errstrings = newtree234(errstring_compare);
+
+    es = find234(errstrings, &error, errstring_find);
+
+    if (!es) {
+        int bufsize, bufused;
+
+        es = snew(struct errstring);
+        es->error = error;
+        /* maximum size for FormatMessage is 64K */
+        bufsize = 65535 + sizeof(prefix);
+        es->text = snewn(bufsize, char);
+        strcpy(es->text, prefix);
+        bufused = strlen(es->text);
+        if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
+                            FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error,
+                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                           es->text + bufused, bufsize - bufused, NULL)) {
+            sprintf(es->text + bufused,
+                    "Windows error code %d (and FormatMessage returned %d)", 
+                    error, GetLastError());
+        } else {
+            int len = strlen(es->text);
+            if (len > 0 && es->text[len-1] == '\n')
+                es->text[len-1] = '\0';
+        }
+        es->text = sresize(es->text, strlen(es->text) + 1, char);
+        add234(errstrings, es);
+    }
+
+    return es->text;
 }
 
 SockAddr sk_namelookup(const char *host, char **canonicalname,
@@ -392,26 +490,29 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
 {
     SockAddr ret = snew(struct SockAddr_tag);
     unsigned long a;
-    struct hostent *h = NULL;
     char realhost[8192];
-    int ret_family;
-    int err;
+    int hint_family;
 
-    /* Clear the structure and default to IPv4. */
-    memset(ret, 0, sizeof(struct SockAddr_tag));
-    ret->family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+    /* Default to IPv4. */
+    hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
 #ifndef NO_IPV6
                   address_family == ADDRTYPE_IPV6 ? AF_INET6 :
 #endif
                   AF_UNSPEC);
+
+    /* Clear the structure and default to IPv4. */
+    memset(ret, 0, sizeof(struct SockAddr_tag));
 #ifndef NO_IPV6
-    ret->ai = ret->ais = NULL;
+    ret->ais = NULL;
 #endif
     ret->addresses = NULL;
-    ret_family = AF_UNSPEC;
+    ret->resolved = FALSE;
+    ret->refcount = 1;
     *realhost = '\0';
 
     if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
+       struct hostent *h = NULL;
+       int err;
 #ifndef NO_IPV6
        /*
         * Use getaddrinfo when it's available
@@ -422,11 +523,10 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
            logevent(NULL, "Using getaddrinfo() for resolving");
 #endif
            memset(&hints, 0, sizeof(hints));
-           hints.ai_family = ret->family;
+           hints.ai_family = hint_family;
            hints.ai_flags = AI_CANONNAME;
            if ((err = p_getaddrinfo(host, NULL, &hints, &ret->ais)) == 0)
-               ret_family = ret->ais->ai_family;
-           ret->ai = ret->ais;
+               ret->resolved = TRUE;
        } else
 #endif
        {
@@ -438,12 +538,12 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
             * (NOTE: we don't use gethostbyname as a fallback!)
             */
            if ( (h = p_gethostbyname(host)) )
-               ret_family = AF_INET;
+               ret->resolved = TRUE;
            else
                err = p_WSAGetLastError();
        }
 
-       if (ret_family == AF_UNSPEC) {
+       if (!ret->resolved) {
            ret->error = (err == WSAENETDOWN ? "Network is down" :
                          err == WSAHOST_NOT_FOUND ? "Host does not exist" :
                          err == WSATRY_AGAIN ? "Host not found" :
@@ -453,20 +553,19 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
                          "gethostbyname: unknown error");
        } else {
            ret->error = NULL;
-           ret->family = ret_family;
 
 #ifndef NO_IPV6
            /* If we got an address info use that... */
-           if (ret->ai) {
+           if (ret->ais) {
                /* Are we in IPv4 fallback mode? */
                /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
-               if (ret->family == AF_INET)
+               if (ret->ais->ai_family == AF_INET)
                    memcpy(&a,
-                          (char *) &((SOCKADDR_IN *) ret->ai->
+                          (char *) &((SOCKADDR_IN *) ret->ais->
                                      ai_addr)->sin_addr, sizeof(a));
 
-               if (ret->ai->ai_canonname)
-                   strncpy(realhost, ret->ai->ai_canonname, lenof(realhost));
+               if (ret->ais->ai_canonname)
+                   strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
                else
                    strncpy(realhost, host, lenof(realhost));
            }
@@ -482,7 +581,6 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
                    memcpy(&a, h->h_addr_list[n], sizeof(a));
                    ret->addresses[n] = p_ntohl(a);
                }
-               ret->curraddr = 0;
                memcpy(&a, h->h_addr, sizeof(a));
                /* This way we are always sure the h->h_name is valid :) */
                strncpy(realhost, h->h_name, sizeof(realhost));
@@ -495,9 +593,8 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
         */
        ret->addresses = snewn(1, unsigned long);
        ret->naddresses = 1;
-       ret->curraddr = 0;
        ret->addresses[0] = p_ntohl(a);
-       ret->family = AF_INET;
+       ret->resolved = TRUE;
        strncpy(realhost, host, sizeof(realhost));
     }
     realhost[lenof(realhost)-1] = '\0';
@@ -510,31 +607,31 @@ SockAddr sk_nonamelookup(const char *host)
 {
     SockAddr ret = snew(struct SockAddr_tag);
     ret->error = NULL;
-    ret->family = AF_UNSPEC;
+    ret->resolved = FALSE;
 #ifndef NO_IPV6
-    ret->ai = ret->ais = NULL;
+    ret->ais = NULL;
 #endif
     ret->addresses = NULL;
     ret->naddresses = 0;
+    ret->refcount = 1;
     strncpy(ret->hostname, host, lenof(ret->hostname));
     ret->hostname[lenof(ret->hostname)-1] = '\0';
     return ret;
 }
 
-int sk_nextaddr(SockAddr addr)
+int sk_nextaddr(SockAddr addr, SockAddrStep *step)
 {
 #ifndef NO_IPV6
-    if (addr->ai) {
-       if (addr->ai->ai_next) {
-           addr->ai = addr->ai->ai_next;
-           addr->family = addr->ai->ai_family;
+    if (step->ai) {
+       if (step->ai->ai_next) {
+           step->ai = step->ai->ai_next;
            return TRUE;
        } else
            return FALSE;
     }
 #endif
-    if (addr->curraddr+1 < addr->naddresses) {
-       addr->curraddr++;
+    if (step->curraddr+1 < addr->naddresses) {
+       step->curraddr++;
        return TRUE;
     } else {
        return FALSE;
@@ -543,19 +640,30 @@ int sk_nextaddr(SockAddr addr)
 
 void sk_getaddr(SockAddr addr, char *buf, int buflen)
 {
+    SockAddrStep step;
+    START_STEP(addr, step);
+
 #ifndef NO_IPV6
-    if (addr->ai) {
+    if (step.ai) {
+       int err = 0;
        if (p_WSAAddressToStringA) {
-           p_WSAAddressToStringA(addr->ai->ai_addr, addr->ai->ai_addrlen,
-                                 NULL, buf, &buflen);
+           DWORD dwbuflen = buflen;
+           err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen,
+                                       NULL, buf, &dwbuflen);
        } else
-           strncpy(buf, "IPv6", buflen);
+           err = -1;
+       if (err) {
+           strncpy(buf, addr->hostname, buflen);
+           if (!buf[0])
+               strncpy(buf, "<unknown>", buflen);
+           buf[buflen-1] = '\0';
+       }
     } else
 #endif
-    if (addr->family == AF_INET) {
+    if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
        struct in_addr a;
-       assert(addr->addresses && addr->curraddr < addr->naddresses);
-       a.s_addr = p_htonl(addr->addresses[addr->curraddr]);
+       assert(addr->addresses && step.curraddr < addr->naddresses);
+       a.s_addr = p_htonl(addr->addresses[step.curraddr]);
        strncpy(buf, p_inet_ntoa(a), buflen);
        buf[buflen-1] = '\0';
     } else {
@@ -564,9 +672,11 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
     }
 }
 
-int sk_hostname_is_local(char *name)
+int sk_hostname_is_local(const char *name)
 {
-    return !strcmp(name, "localhost");
+    return !strcmp(name, "localhost") ||
+          !strcmp(name, "::1") ||
+          !strncmp(name, "127.", 4);
 }
 
 static INTERFACE_INFO local_interfaces[16];
@@ -602,64 +712,86 @@ static int ipv4_is_local_addr(struct in_addr addr)
 
 int sk_address_is_local(SockAddr addr)
 {
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
 #ifndef NO_IPV6
-    if (addr->family == AF_INET6) {
-       return IN6_IS_ADDR_LOOPBACK((const struct in6_addr *)addr->ai->ai_addr);
+    if (family == AF_INET6) {
+       return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr);
     } else
 #endif
-    if (addr->family == AF_INET) {
+    if (family == AF_INET) {
 #ifndef NO_IPV6
-       if (addr->ai) {
-           return ipv4_is_local_addr(((struct sockaddr_in *)addr->ai->ai_addr)
+       if (step.ai) {
+           return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
                                      ->sin_addr);
        } else
 #endif
        {
            struct in_addr a;
-           assert(addr->addresses && addr->curraddr < addr->naddresses);
-           a.s_addr = p_htonl(addr->addresses[addr->curraddr]);
+           assert(addr->addresses && step.curraddr < addr->naddresses);
+           a.s_addr = p_htonl(addr->addresses[step.curraddr]);
            return ipv4_is_local_addr(a);
        }
     } else {
-       assert(addr->family == AF_UNSPEC);
+       assert(family == AF_UNSPEC);
        return 0;                      /* we don't know; assume not */
     }
 }
 
+int sk_address_is_special_local(SockAddr addr)
+{
+    return 0;                /* no Unix-domain socket analogue here */
+}
+
 int sk_addrtype(SockAddr addr)
 {
-    return (addr->family == AF_INET ? ADDRTYPE_IPV4 :
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
+    return (family == AF_INET ? ADDRTYPE_IPV4 :
 #ifndef NO_IPV6
-           addr->family == AF_INET6 ? ADDRTYPE_IPV6 :
+           family == AF_INET6 ? ADDRTYPE_IPV6 :
 #endif
            ADDRTYPE_NAME);
 }
 
 void sk_addrcopy(SockAddr addr, char *buf)
 {
-    assert(addr->family != AF_UNSPEC);
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
+    assert(family != AF_UNSPEC);
 #ifndef NO_IPV6
-    if (addr->ai) {
-       if (addr->family == AF_INET)
-           memcpy(buf, &((struct sockaddr_in *)addr->ai->ai_addr)->sin_addr,
+    if (step.ai) {
+       if (family == AF_INET)
+           memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
                   sizeof(struct in_addr));
-       else if (addr->family == AF_INET6)
-           memcpy(buf, &((struct sockaddr_in6 *)addr->ai->ai_addr)->sin6_addr,
+       else if (family == AF_INET6)
+           memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
                   sizeof(struct in6_addr));
        else
            assert(FALSE);
     } else
 #endif
-    if (addr->family == AF_INET) {
+    if (family == AF_INET) {
        struct in_addr a;
-       assert(addr->addresses && addr->curraddr < addr->naddresses);
-       a.s_addr = p_htonl(addr->addresses[addr->curraddr]);
+       assert(addr->addresses && step.curraddr < addr->naddresses);
+       a.s_addr = p_htonl(addr->addresses[step.curraddr]);
        memcpy(buf, (char*) &a.s_addr, 4);
     }
 }
 
 void sk_addr_free(SockAddr addr)
 {
+    if (--addr->refcount > 0)
+       return;
 #ifndef NO_IPV6
     if (addr->ais && p_freeaddrinfo)
        p_freeaddrinfo(addr->ais);
@@ -669,6 +801,12 @@ void sk_addr_free(SockAddr addr)
     sfree(addr);
 }
 
+SockAddr sk_addr_dup(SockAddr addr)
+{
+    addr->refcount++;
+    return addr;
+}
+
 static Plug sk_tcp_plug(Socket sock, Plug p)
 {
     Actual_Socket s = (Actual_Socket) sock;
@@ -689,6 +827,7 @@ static void sk_tcp_flush(Socket s)
 static void sk_tcp_close(Socket s);
 static int sk_tcp_write(Socket s, const char *data, int len);
 static int sk_tcp_write_oob(Socket s, const char *data, int len);
+static void sk_tcp_write_eof(Socket s);
 static void sk_tcp_set_private_ptr(Socket s, void *ptr);
 static void *sk_tcp_get_private_ptr(Socket s);
 static void sk_tcp_set_frozen(Socket s, int is_frozen);
@@ -703,6 +842,7 @@ Socket sk_register(void *sock, Plug plug)
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -724,6 +864,7 @@ Socket sk_register(void *sock, Plug plug)
     bufchain_init(&ret->output_data);
     ret->writable = 1;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 1;
     ret->frozen_readable = 0;
     ret->localhost_only = 0;          /* unused, but best init anyway */
@@ -776,17 +917,15 @@ static DWORD try_connect(Actual_Socket sock)
     /*
      * Open socket.
      */
-#ifndef NO_IPV6
-    /* Let's default to IPv6, this shouldn't hurt anybody
-     * If the stack supports IPv6 it will also allow IPv4 connections. */
-    if (sock->addr->ai) {
-       family = sock->addr->ai->ai_family;
-    } else
-#endif
-    {
-       /* Default to IPv4 */
-       family = AF_INET;
-    }
+    family = SOCKADDR_FAMILY(sock->addr, sock->step);
+
+    /*
+     * Remove the socket from the tree before we overwrite its
+     * internal socket id, because that forms part of the tree's
+     * sorting criterion. We'll add it back before exiting this
+     * function, whether we changed anything or not.
+     */
+    del234(sktree, sock);
 
     s = p_socket(family, SOCK_STREAM, 0);
     sock->s = s;
@@ -838,11 +977,10 @@ static DWORD try_connect(Actual_Socket sock)
            a.sin_port = p_htons(localport);
        }
 #ifndef NO_IPV6
-       sockcode = p_bind(s, (sock->addr->family == AF_INET6 ?
-                          (struct sockaddr *) &a6 :
-                          (struct sockaddr *) &a),
-                      (sock->addr->family ==
-                       AF_INET6 ? sizeof(a6) : sizeof(a)));
+       sockcode = p_bind(s, (family == AF_INET6 ?
+                             (struct sockaddr *) &a6 :
+                             (struct sockaddr *) &a),
+                         (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
 #else
        sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
 #endif
@@ -871,26 +1009,26 @@ static DWORD try_connect(Actual_Socket sock)
      * Connect to remote address.
      */
 #ifndef NO_IPV6
-    if (sock->addr->ai) {
+    if (sock->step.ai) {
        if (family == AF_INET6) {
            a6.sin6_family = AF_INET6;
            a6.sin6_port = p_htons((short) sock->port);
            a6.sin6_addr =
-               ((struct sockaddr_in6 *) sock->addr->ai->ai_addr)->sin6_addr;
-           a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->addr->ai->ai_addr)->sin6_flowinfo;
-           a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->addr->ai->ai_addr)->sin6_scope_id;
+               ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
+           a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
+           a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
        } else {
            a.sin_family = AF_INET;
            a.sin_addr =
-               ((struct sockaddr_in *) sock->addr->ai->ai_addr)->sin_addr;
+               ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
            a.sin_port = p_htons((short) sock->port);
        }
     } else
 #endif
     {
-       assert(sock->addr->addresses && sock->addr->curraddr < sock->addr->naddresses);
+       assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
        a.sin_family = AF_INET;
-       a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->addr->curraddr]);
+       a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
        a.sin_port = p_htons((short) sock->port);
     }
 
@@ -932,11 +1070,15 @@ static DWORD try_connect(Actual_Socket sock)
        sock->writable = 1;
     }
 
-    add234(sktree, sock);
-
     err = 0;
 
     ret:
+
+    /*
+     * No matter what happened, put the socket back in the tree.
+     */
+    add234(sktree, sock);
+
     if (err)
        plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err);
     return err;
@@ -950,6 +1092,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -971,6 +1114,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->connected = 0;                       /* to start with */
     ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 0;
     ret->frozen_readable = 0;
     ret->localhost_only = 0;          /* unused, but best init anyway */
@@ -982,12 +1126,13 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->privport = privport;
     ret->port = port;
     ret->addr = addr;
+    START_STEP(ret->addr, ret->step);
     ret->s = INVALID_SOCKET;
 
     err = 0;
     do {
         err = try_connect(ret);
-    } while (err && sk_nextaddr(ret->addr));
+    } while (err && sk_nextaddr(ret->addr, &ret->step));
 
     return (Socket) ret;
 }
@@ -1000,6 +1145,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -1031,6 +1177,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
     bufchain_init(&ret->output_data);
     ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 0;
     ret->frozen_readable = 0;
     ret->localhost_only = local_host_only;
@@ -1142,7 +1289,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
 
     if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) {
         p_closesocket(s);
-       ret->error = winsock_error_string(err);
+       ret->error = winsock_error_string(p_WSAGetLastError());
        return (Socket) ret;
     }
 
@@ -1267,12 +1414,23 @@ void try_send(Actual_Socket s)
            }
        }
     }
+
+    /*
+     * If we reach here, we've finished sending everything we might
+     * have needed to send. Send EOF, if we need to.
+     */
+    if (s->outgoingeof == EOF_PENDING) {
+        p_shutdown(s->s, SD_SEND);
+        s->outgoingeof = EOF_SENT;
+    }
 }
 
 static int sk_tcp_write(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Add the data to the buffer list on the socket.
      */
@@ -1291,6 +1449,8 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Replace the buffer list on the socket with the data.
      */
@@ -1308,6 +1468,24 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
     return s->sending_oob;
 }
 
+static void sk_tcp_write_eof(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    assert(s->outgoingeof == EOF_NO);
+
+    /*
+     * Mark the socket as pending outgoing EOF.
+     */
+    s->outgoingeof = EOF_PENDING;
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    if (s->writable)
+       try_send(s);
+}
+
 int select_result(WPARAM wParam, LPARAM lParam)
 {
     int ret, open;
@@ -1333,7 +1511,7 @@ int select_result(WPARAM wParam, LPARAM lParam)
        if (s->addr) {
            plug_log(s->plug, 1, s->addr, s->port,
                     winsock_error_string(err), err);
-           while (s->addr && sk_nextaddr(s->addr)) {
+           while (s->addr && sk_nextaddr(s->addr, &s->step)) {
                err = try_connect(s);
            }
        }
@@ -1473,10 +1651,11 @@ int select_result(WPARAM wParam, LPARAM lParam)
 #ifndef NO_IPV6
             if (isa.ss_family == AF_INET &&
                 s->localhost_only &&
-                !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) {
+                !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
 #else
-           if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) {
+           if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
 #endif
+           {
                p_closesocket(t);      /* dodgy WinSock let nonlocal through */
            } else if (plug_accepting(s->plug, (void*)t)) {
                p_closesocket(t);      /* denied or error */
@@ -1572,6 +1751,17 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
     s->frozen_readable = 0;
 }
 
+void socket_reselect_all(void)
+{
+    Actual_Socket s;
+    int i;
+
+    for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+       if (!s->frozen)
+           do_select(s->s, 1);
+    }
+}
+
 /*
  * For Plink: enumerate all sockets currently active.
  */
@@ -1609,10 +1799,28 @@ int net_service_lookup(char *service)
        return 0;
 }
 
-SockAddr platform_get_x11_unix_address(int displaynum, char **canonicalname)
+char *get_hostname(void)
+{
+    int len = 128;
+    char *hostname = NULL;
+    do {
+       len *= 2;
+       hostname = sresize(hostname, len, char);
+       if (p_gethostname(hostname, len) < 0) {
+           sfree(hostname);
+           hostname = NULL;
+           break;
+       }
+    } while (strlen(hostname) >= (size_t)(len-1));
+    return hostname;
+}
+
+SockAddr platform_get_x11_unix_address(const char *display, int displaynum,
+                                      char **canonicalname)
 {
     SockAddr ret = snew(struct SockAddr_tag);
     memset(ret, 0, sizeof(struct SockAddr_tag));
     ret->error = "unix sockets not supported on this platform";
+    ret->refcount = 1;
     return ret;
 }