Fix the licence again. (Despite the copyright holders being more
[u/mdw/putty] / winnet.c
index 1a80a80..b671047 100644 (file)
--- a/winnet.c
+++ b/winnet.c
@@ -63,6 +63,7 @@ struct Socket_tag {
     Plug plug;
     void *private_ptr;
     bufchain output_data;
+    int connected;
     int writable;
     int frozen; /* this causes readability notifications to be ignored */
     int frozen_readable; /* this means we missed at least one readability
@@ -338,6 +339,21 @@ SockAddr sk_namelookup(char *host, char **canonicalname)
     return ret;
 }
 
+void sk_getaddr(SockAddr addr, char *buf, int buflen)
+{
+#ifdef IPV6
+    if (addr->family == AF_INET) {
+#endif
+       struct in_addr a;
+       a.s_addr = htonl(addr->address);
+       strncpy(buf, inet_ntoa(a), buflen);
+#ifdef IPV6
+    } else {
+       FIXME; /* I don't know how to get a text form of an IPv6 address. */
+    }
+#endif
+}
+
 void sk_addr_free(SockAddr addr)
 {
     sfree(addr);
@@ -448,7 +464,8 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
-    ret->writable = 1;                /* to start with */
+    ret->connected = 0;                       /* to start with */
+    ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
     ret->frozen = 0;
     ret->frozen_readable = 0;
@@ -543,6 +560,15 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        a.sin_addr.s_addr = htonl(addr->address);
        a.sin_port = htons((short) port);
     }
+
+    /* Set up a select mechanism. This could be an AsyncSelect on a
+     * window, or an EventSelect on an event object. */
+    errstr = do_select(s, 1);
+    if (errstr) {
+       ret->error = errstr;
+       return (Socket) ret;
+    }
+
     if ((
 #ifdef IPV6
            connect(s, ((addr->family == AF_INET6) ?
@@ -553,16 +579,22 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
 #endif
        ) == SOCKET_ERROR) {
        err = WSAGetLastError();
-       ret->error = winsock_error_string(err);
-       return (Socket) ret;
-    }
-
-    /* Set up a select mechanism. This could be an AsyncSelect on a
-     * window, or an EventSelect on an event object. */
-    errstr = do_select(s, 1);
-    if (errstr) {
-       ret->error = errstr;
-       return (Socket) ret;
+       /*
+        * We expect a potential EWOULDBLOCK here, because the
+        * chances are the front end has done a select for
+        * FD_CONNECT, so that connect() will complete
+        * asynchronously.
+        */
+       if ( err != WSAEWOULDBLOCK ) {
+           ret->error = winsock_error_string(err);
+           return (Socket) ret;
+       }
+    } else {
+       /*
+        * If we _don't_ get EWOULDBLOCK, the connect has completed
+        * and we should set the socket as writable.
+        */
+       ret->writable = 1;
     }
 
     add234(sktree, ret);
@@ -713,7 +745,6 @@ void try_send(Actual_Socket s)
            urgentflag = 0;
            bufchain_prefix(&s->output_data, &data, &len);
        }
-
        nsent = send(s->s, data, len, urgentflag);
        noise_ultralight(nsent);
        if (nsent <= 0) {
@@ -733,17 +764,30 @@ void try_send(Actual_Socket s)
            } else if (nsent == 0 ||
                       err == WSAECONNABORTED || err == WSAECONNRESET) {
                /*
-                * FIXME. This will have to be done better when we
-                * start managing multiple sockets (e.g. SSH port
-                * forwarding), because if we get CONNRESET while
-                * trying to write a particular forwarded socket
-                * then it isn't necessarily the end of the world.
-                * Ideally I'd like to pass the error code back to
-                * somewhere the next select_result() will see it,
-                * but that might be hard. Perhaps I should pass it
-                * back to be queued in the Windows front end bit.
+                * ASSUMPTION:
+                * 
+                * I'm assuming here that if a TCP connection is
+                * reset or aborted once established, we will be
+                * notified by a select event rather than a
+                * CONNABORTED or CONNRESET from send(). In other
+                * words, I'm assuming CONNABORTED and CONNRESET
+                * don't come back from a _nonblocking_ send(),
+                * because the local side doesn't know they've
+                * happened until it waits for a response to its
+                * TCP segment - so the error will arrive
+                * asynchronously.
+                * 
+                * If I'm wrong, this will be a really nasty case,
+                * because we can't necessarily call plug_closing()
+                * without having to make half the SSH code
+                * reentrant; so instead we'll have to schedule a
+                * call to plug_closing() for some suitable future
+                * time.
                 */
-               fatalbox(winsock_error_string(err));
+               fatalbox("SERIOUS NETWORK INTERNAL ERROR: %s\n"
+                        "Please report this immediately to "
+                        "<putty@projects.tartarus.org>.",
+                        winsock_error_string(err));
            } else {
                fatalbox(winsock_error_string(err));
            }
@@ -825,6 +869,9 @@ int select_result(WPARAM wParam, LPARAM lParam)
     noise_ultralight(lParam);
 
     switch (WSAGETSELECTEVENT(lParam)) {
+      case FD_CONNECT:
+       s->connected = s->writable = 1;
+       break;
       case FD_READ:
        /* In the case the socket is still frozen, we don't even bother */
        if (s->frozen) {