First attempt at a Unix port of Plink. Seems to basically work;
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 31 Oct 2002 19:49:52 +0000 (19:49 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 31 Oct 2002 19:49:52 +0000 (19:49 +0000)
doesn't yet use the SSH agent, no way to specify arbitrary config
options, no manpage yet, couple of other fiddly things need doing,
but it makes SSH connections and doesn't fall over horribly so I say
it's a good start. Now to run it under valgrind...

git-svn-id: svn://svn.tartarus.org/sgt/putty@2165 cda61777-01e9-0310-a592-d414129be87e

Recipe
unix/pterm.c
unix/unix.h
unix/uxagentc.c [new file with mode: 0644]
unix/uxcons.c [new file with mode: 0644]
unix/uxnet.c [new file with mode: 0644]
unix/uxnoise.c [new file with mode: 0644]
unix/uxplink.c [new file with mode: 0644]
unix/uxstore.c

diff --git a/Recipe b/Recipe
index b52dc66..fcf6e46 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -96,16 +96,19 @@ GUITERM  = window windlg winctrls terminal sizetip wcwidth unicode ldiscucs
 NONSSH   = telnet raw rlogin ldisc
 
 # SSH back end (putty, plink, pscp, psftp).
-SSH      = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf noise
-         + sshdh sshcrcda sshpubk pageantc sshzlib sshdss x11fwd portfwd
+SSH      = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
+         + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
          + sshaes sshsh512 sshbn
+WINSSH   = SSH noise pageantc
+UXSSH    = SSH uxnoise uxagentc
 
 # SFTP implementation (pscp, psftp).
 SFTP     = sftp int64 logging
 
 # Miscellaneous objects appearing in all the network utilities (not
 # Pageant or PuTTYgen).
-MISC     = misc version winstore settings tree234 winnet proxy cmdline
+WINMISC  = misc version winstore settings tree234 winnet proxy cmdline
+UXMISC   = misc version uxstore settings tree234 uxnet proxy cmdline
 
 # Standard libraries, and the same with WinSocks 1 and 2.
 LIBS     = advapi32.lib user32.lib gdi32.lib comctl32.lib comdlg32.lib
@@ -118,11 +121,12 @@ LIBS2    = LIBS ws2_32.lib
 # keywords [G] for Windows GUI app, [C] for Console app, [X] for
 # X/GTK Unix app.
 
-putty    : [G] GUITERM NONSSH SSH be_all MISC win_res.res LIBS1
-puttytel : [G] GUITERM NONSSH be_nossh MISC win_res.res LIBS1
-plink    : [C] plink console NONSSH SSH be_all logging MISC plink.res LIBS2
-pscp     : [C] scp console SSH be_none SFTP wildcard MISC scp.res LIBS1
-psftp    : [C] psftp console SSH be_none SFTP MISC scp.res LIBS1
+putty    : [G] GUITERM NONSSH WINSSH be_all WINMISC win_res.res LIBS1
+puttytel : [G] GUITERM NONSSH be_nossh WINMISC win_res.res LIBS1
+plink    : [C] plink console NONSSH WINSSH be_all logging WINMISC
+         + plink.res LIBS2
+pscp     : [C] scp console WINSSH be_none SFTP wildcard WINMISC scp.res LIBS1
+psftp    : [C] psftp console WINSSH be_none SFTP WINMISC scp.res LIBS1
 
 pageant  : [G] pageant sshrsa sshpubk sshdes sshbn sshmd5 version tree234
          + misc sshaes sshsha pageantc sshdss sshsh512 winutils
@@ -134,3 +138,5 @@ puttygen : [G] puttygen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
 
 pterm    : [X] pterm terminal wcwidth uxucs uxmisc tree234 misc ldisc ldiscucs
          + logging uxprint settings pty be_none uxstore
+
+plink    : [X] uxplink uxcons NONSSH UXSSH be_all logging UXMISC
index e53499d..cc45962 100644 (file)
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
 
 #define PUTTY_DO_GLOBALS              /* actually _define_ globals */
 #include "putty.h"
@@ -68,6 +69,13 @@ struct draw_ctx {
 
 static int send_raw_mouse;
 
+static char *app_name = "pterm";
+
+char *x_get_default(char *key)
+{
+    return XGetDefault(GDK_DISPLAY(), app_name, key);
+}
+
 void ldisc_update(void *frontend, int echo, int edit)
 {
     /*
@@ -1726,8 +1734,6 @@ char *get_x_display(void *frontend)
     return gdk_get_display();
 }
 
-char *app_name = "pterm";
-
 static void help(FILE *fp) {
     if(fprintf(fp,
 "pterm option summary:\n"
index f31de3c..534bf92 100644 (file)
@@ -35,11 +35,16 @@ char *get_x_display(void *frontend);
 int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */
 
 /* Things uxstore.c needs from pterm.c */
-char *app_name;                               /* for doing resource lookups */
+char *x_get_default(char *key);
 
 /* Things uxstore.c provides to pterm.c */
 void provide_xrm_string(char *string);
 
+/* Things uxnet.c provides to the front end */
+int select_result(int fd, int event);
+int first_socket(int *state, int *rwx);
+int next_socket(int *state, int *rwx);
+
 #define DEFAULT_CODEPAGE 0            /* FIXME: no idea how to do this */
 
 #define strnicmp strncasecmp
diff --git a/unix/uxagentc.c b/unix/uxagentc.c
new file mode 100644 (file)
index 0000000..5eeb0b1
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SSH agent client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "misc.h"
+#include "puttymem.h"
+
+#define GET_32BIT(cp) \
+    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+    ((unsigned long)(unsigned char)(cp)[3]))
+
+int agent_exists(void)
+{
+    return FALSE;                     /* FIXME */
+}
+
+void agent_query(void *in, int inlen, void **out, int *outlen)
+{
+    /* FIXME */
+}
diff --git a/unix/uxcons.c b/unix/uxcons.c
new file mode 100644 (file)
index 0000000..844b674
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * uxcons.c: various interactive-prompt routines shared between the
+ * Unix console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+
+int console_batch_mode = FALSE;
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+    /*
+     * Clean up.
+     */
+    sk_cleanup();
+    exit(code);
+}
+
+void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+                        char *keystr, char *fingerprint)
+{
+    int ret;
+
+    static const char absentmsg_batch[] =
+       "The server's host key is not cached. You have no guarantee\n"
+       "that the server is the computer you think it is.\n"
+       "The server's key fingerprint is:\n"
+       "%s\n"
+       "Connection abandoned.\n";
+    static const char absentmsg[] =
+       "The server's host key is not cached. You have no guarantee\n"
+       "that the server is the computer you think it is.\n"
+       "The server's key fingerprint is:\n"
+       "%s\n"
+       "If you trust this host, enter \"y\" to add the key to\n"
+       "PuTTY's cache and carry on connecting.\n"
+       "If you want to carry on connecting just once, without\n"
+       "adding the key to the cache, enter \"n\".\n"
+       "If you do not trust this host, press Return to abandon the\n"
+       "connection.\n"
+       "Store key in cache? (y/n) ";
+
+    static const char wrongmsg_batch[] =
+       "WARNING - POTENTIAL SECURITY BREACH!\n"
+       "The server's host key does not match the one PuTTY has\n"
+       "cached. This means that either the server administrator\n"
+       "has changed the host key, or you have actually connected\n"
+       "to another computer pretending to be the server.\n"
+       "The new key fingerprint is:\n"
+       "%s\n"
+       "Connection abandoned.\n";
+    static const char wrongmsg[] =
+       "WARNING - POTENTIAL SECURITY BREACH!\n"
+       "The server's host key does not match the one PuTTY has\n"
+       "cached. This means that either the server administrator\n"
+       "has changed the host key, or you have actually connected\n"
+       "to another computer pretending to be the server.\n"
+       "The new key fingerprint is:\n"
+       "%s\n"
+       "If you were expecting this change and trust the new key,\n"
+       "enter \"y\" to update PuTTY's cache and continue connecting.\n"
+       "If you want to carry on connecting but without updating\n"
+       "the cache, enter \"n\".\n"
+       "If you want to abandon the connection completely, press\n"
+       "Return to cancel. Pressing Return is the ONLY guaranteed\n"
+       "safe choice.\n"
+       "Update cached key? (y/n, Return cancels connection) ";
+
+    static const char abandoned[] = "Connection abandoned.\n";
+
+    char line[32];
+
+    /*
+     * Verify the key.
+     */
+    ret = verify_host_key(host, port, keytype, keystr);
+
+    if (ret == 0)                     /* success - key matched OK */
+       return;
+
+    if (ret == 2) {                   /* key was different */
+       if (console_batch_mode) {
+           fprintf(stderr, wrongmsg_batch, fingerprint);
+           cleanup_exit(1);
+       }
+       fprintf(stderr, wrongmsg, fingerprint);
+       fflush(stderr);
+    }
+    if (ret == 1) {                   /* key was absent */
+       if (console_batch_mode) {
+           fprintf(stderr, absentmsg_batch, fingerprint);
+           cleanup_exit(1);
+       }
+       fprintf(stderr, absentmsg, fingerprint);
+       fflush(stderr);
+    }
+
+    {
+       struct termios oldmode, newmode;
+       tcgetattr(0, &oldmode);
+       newmode = oldmode;
+       newmode.c_lflag |= ECHO | ISIG | ICANON;
+       tcsetattr(0, TCSANOW, &newmode);
+       line[0] = '\0';
+       read(0, line, sizeof(line) - 1);
+       tcsetattr(0, TCSANOW, &oldmode);
+    }
+
+    if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
+       if (line[0] == 'y' || line[0] == 'Y')
+           store_host_key(host, port, keytype, keystr);
+    } else {
+       fprintf(stderr, abandoned);
+       cleanup_exit(0);
+    }
+}
+
+/*
+ * Ask whether the selected cipher is acceptable (since it was
+ * below the configured 'warn' threshold).
+ * cs: 0 = both ways, 1 = client->server, 2 = server->client
+ */
+void askcipher(void *frontend, char *ciphername, int cs)
+{
+    static const char msg[] =
+       "The first %scipher supported by the server is\n"
+       "%s, which is below the configured warning threshold.\n"
+       "Continue with connection? (y/n) ";
+    static const char msg_batch[] =
+       "The first %scipher supported by the server is\n"
+       "%s, which is below the configured warning threshold.\n"
+       "Connection abandoned.\n";
+    static const char abandoned[] = "Connection abandoned.\n";
+
+    char line[32];
+
+    if (console_batch_mode) {
+       fprintf(stderr, msg_batch,
+               (cs == 0) ? "" :
+               (cs == 1) ? "client-to-server " : "server-to-client ",
+               ciphername);
+       cleanup_exit(1);
+    }
+
+    fprintf(stderr, msg,
+           (cs == 0) ? "" :
+           (cs == 1) ? "client-to-server " : "server-to-client ",
+           ciphername);
+    fflush(stderr);
+
+    {
+       struct termios oldmode, newmode;
+       tcgetattr(0, &oldmode);
+       newmode = oldmode;
+       newmode.c_lflag |= ECHO | ISIG | ICANON;
+       tcsetattr(0, TCSANOW, &newmode);
+       line[0] = '\0';
+       read(0, line, sizeof(line) - 1);
+       tcsetattr(0, TCSANOW, &oldmode);
+    }
+
+    if (line[0] == 'y' || line[0] == 'Y') {
+       return;
+    } else {
+       fprintf(stderr, abandoned);
+       cleanup_exit(0);
+    }
+}
+
+/*
+ * Ask whether to wipe a session log file before writing to it.
+ * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
+ */
+int askappend(void *frontend, char *filename)
+{
+    static const char msgtemplate[] =
+       "The session log file \"%.*s\" already exists.\n"
+       "You can overwrite it with a new session log,\n"
+       "append your session log to the end of it,\n"
+       "or disable session logging for this session.\n"
+       "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
+       "or just press Return to disable logging.\n"
+       "Wipe the log file? (y/n, Return cancels logging) ";
+
+    static const char msgtemplate_batch[] =
+       "The session log file \"%.*s\" already exists.\n"
+       "Logging will not be enabled.\n";
+
+    char line[32];
+
+    if (cfg.logxfovr != LGXF_ASK) {
+       return ((cfg.logxfovr == LGXF_OVR) ? 2 : 1);
+    }
+    if (console_batch_mode) {
+       fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename);
+       fflush(stderr);
+       return 0;
+    }
+    fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
+    fflush(stderr);
+
+    {
+       struct termios oldmode, newmode;
+       tcgetattr(0, &oldmode);
+       newmode = oldmode;
+       newmode.c_lflag |= ECHO | ISIG | ICANON;
+       tcsetattr(0, TCSANOW, &newmode);
+       line[0] = '\0';
+       read(0, line, sizeof(line) - 1);
+       tcsetattr(0, TCSANOW, &oldmode);
+    }
+
+    if (line[0] == 'y' || line[0] == 'Y')
+       return 2;
+    else if (line[0] == 'n' || line[0] == 'N')
+       return 1;
+    else
+       return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ * 
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+    static const char message[] =
+       "You are loading an SSH 2 private key which has an\n"
+       "old version of the file format. This means your key\n"
+       "file is not fully tamperproof. Future versions of\n"
+       "PuTTY may stop supporting this private key format,\n"
+       "so we recommend you convert your key to the new\n"
+       "format.\n"
+       "\n"
+       "Once the key is loaded into PuTTYgen, you can perform\n"
+       "this conversion simply by saving it again.\n";
+
+    fputs(message, stderr);
+}
+
+void logevent(void *frontend, char *string)
+{
+}
+
+int console_get_line(const char *prompt, char *str,
+                    int maxlen, int is_pw)
+{
+    struct termios oldmode, newmode;
+    int i;
+
+    if (console_batch_mode) {
+       if (maxlen > 0)
+           str[0] = '\0';
+    } else {
+       tcgetattr(0, &oldmode);
+       newmode = oldmode;
+       newmode.c_lflag |= ISIG | ICANON;
+       if (is_pw)
+           newmode.c_lflag &= ~ECHO;
+       else
+           newmode.c_lflag |= ECHO;
+       tcsetattr(0, TCSANOW, &newmode);
+
+       fputs(prompt, stdout);
+       fflush(stdout);
+       i = read(0, str, maxlen - 1);
+
+       tcsetattr(0, TCSANOW, &oldmode);
+
+       if (i > 0 && str[i-1] == '\n')
+           i--;
+       str[i] = '\0';
+
+       if (is_pw)
+           fputs("\r\n", stdout);
+    }
+    return 1;
+}
+
+void frontend_keypress(void *handle)
+{
+    /*
+     * This is nothing but a stub, in console code.
+     */
+    return;
+}
diff --git a/unix/uxnet.c b/unix/uxnet.c
new file mode 100644 (file)
index 0000000..ad25ef5
--- /dev/null
@@ -0,0 +1,1021 @@
+/*
+ * Unix networking abstraction.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+
+struct Socket_tag {
+    struct socket_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+    char *error;
+    int s;
+    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
+                         * notification while we were frozen */
+    int localhost_only;                       /* for listening sockets */
+    char oobdata[1];
+    int sending_oob;
+    int oobpending;                   /* is there OOB data available to read? */
+    int oobinline;
+    int pending_error;                /* in case send() returns error */
+    int listener;
+};
+
+/*
+ * We used to typedef struct Socket_tag *Socket.
+ *
+ * Since we have made the networking abstraction slightly more
+ * abstract, Socket no longer means a tcp socket (it could mean
+ * an ssl socket).  So now we must use Actual_Socket when we know
+ * we are talking about a tcp socket.
+ */
+typedef struct Socket_tag *Actual_Socket;
+
+struct SockAddr_tag {
+    char *error;
+    /* address family this belongs to, AF_INET for IPv4, AF_INET6 for IPv6. */
+    int family;
+    unsigned long address;            /* Address IPv4 style. */
+#ifdef IPV6
+    struct addrinfo *ai;              /* Address IPv6 style. */
+#endif
+};
+
+static tree234 *sktree;
+
+static int cmpfortree(void *av, void *bv)
+{
+    Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
+    int as = a->s, bs = b->s;
+    if (as < bs)
+       return -1;
+    if (as > bs)
+       return +1;
+    return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+    Actual_Socket b = (Actual_Socket) bv;
+    int as = (int) av, bs = b->s;
+    if (as < bs)
+       return -1;
+    if (as > bs)
+       return +1;
+    return 0;
+}
+
+void sk_init(void)
+{
+    sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+    Actual_Socket s;
+    int i;
+
+    if (sktree) {
+       for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+           close(s->s);
+       }
+    }
+}
+
+char *error_string(int error)
+{
+    switch (error) {
+      case EACCES:
+       return "Network error: Permission denied";
+      case EADDRINUSE:
+       return "Network error: Address already in use";
+      case EADDRNOTAVAIL:
+       return "Network error: Cannot assign requested address";
+      case EAFNOSUPPORT:
+       return
+           "Network error: Address family not supported by protocol family";
+      case EALREADY:
+       return "Network error: Operation already in progress";
+      case ECONNABORTED:
+       return "Network error: Software caused connection abort";
+      case ECONNREFUSED:
+       return "Network error: Connection refused";
+      case ECONNRESET:
+       return "Network error: Connection reset by peer";
+      case EDESTADDRREQ:
+       return "Network error: Destination address required";
+      case EFAULT:
+       return "Network error: Bad address";
+      case EHOSTDOWN:
+       return "Network error: Host is down";
+      case EHOSTUNREACH:
+       return "Network error: No route to host";
+      case EINPROGRESS:
+       return "Network error: Operation now in progress";
+      case EINTR:
+       return "Network error: Interrupted function call";
+      case EINVAL:
+       return "Network error: Invalid argument";
+      case EISCONN:
+       return "Network error: Socket is already connected";
+      case EMFILE:
+       return "Network error: Too many open files";
+      case EMSGSIZE:
+       return "Network error: Message too long";
+      case ENETDOWN:
+       return "Network error: Network is down";
+      case ENETRESET:
+       return "Network error: Network dropped connection on reset";
+      case ENETUNREACH:
+       return "Network error: Network is unreachable";
+      case ENOBUFS:
+       return "Network error: No buffer space available";
+      case ENOPROTOOPT:
+       return "Network error: Bad protocol option";
+      case ENOTCONN:
+       return "Network error: Socket is not connected";
+      case ENOTSOCK:
+       return "Network error: Socket operation on non-socket";
+      case EOPNOTSUPP:
+       return "Network error: Operation not supported";
+      case EPFNOSUPPORT:
+       return "Network error: Protocol family not supported";
+      case EPROTONOSUPPORT:
+       return "Network error: Protocol not supported";
+      case EPROTOTYPE:
+       return "Network error: Protocol wrong type for socket";
+      case ESHUTDOWN:
+       return "Network error: Cannot send after socket shutdown";
+      case ESOCKTNOSUPPORT:
+       return "Network error: Socket type not supported";
+      case ETIMEDOUT:
+       return "Network error: Connection timed out";
+      case EWOULDBLOCK:
+       return "Network error: Resource temporarily unavailable";
+      default:
+       return "Unknown network error";
+    }
+}
+
+SockAddr sk_namelookup(char *host, char **canonicalname)
+{
+    SockAddr ret = smalloc(sizeof(struct SockAddr_tag));
+    unsigned long a;
+    struct hostent *h = NULL;
+    char realhost[8192];
+
+    /* Clear the structure and default to IPv4. */
+    memset(ret, 0, sizeof(struct SockAddr_tag));
+    ret->family = 0;                  /* We set this one when we have resolved the host. */
+    *realhost = '\0';
+    ret->error = NULL;
+
+    if ((a = inet_addr(host)) == (unsigned long) INADDR_NONE) {
+#ifdef IPV6
+       if (getaddrinfo(host, NULL, NULL, &ret->ai) == 0) {
+           ret->family = ret->ai->ai_family;
+       } else
+#endif
+       {
+           /*
+            * Otherwise use the IPv4-only gethostbyname... (NOTE:
+            * we don't use gethostbyname as a fallback!)
+            */
+           if (ret->family == 0) {
+               /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
+               if ( (h = gethostbyname(host)) )
+                   ret->family = AF_INET;
+           }
+           if (ret->family == 0)
+               ret->error = (h_errno == HOST_NOT_FOUND ||
+                             h_errno == NO_DATA ||
+                             h_errno == NO_ADDRESS ? "Host does not exist" :
+                             h_errno == TRY_AGAIN ?
+                             "Temporary name service failure" :
+                             "gethostbyname: unknown error");
+       }
+
+#ifdef IPV6
+       /* If we got an address info use that... */
+       if (ret->ai) {
+
+           /* 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)
+               memcpy(&a,
+                      (char *) &((struct sockaddr_in *) ret->ai->
+                                 ai_addr)->sin_addr, sizeof(a));
+
+           /* Now let's find that canonicalname... */
+           if (getnameinfo((struct sockaddr *) ret->ai->ai_addr,
+                           ret->family ==
+                           AF_INET ? sizeof(struct sockaddr_in) :
+                           sizeof(struct sockaddr_in6), realhost,
+                           sizeof(realhost), NULL, 0, 0) != 0) {
+               strncpy(realhost, host, sizeof(realhost));
+           }
+       }
+       /* We used the IPv4-only gethostbyname()... */
+       else
+#endif
+       {
+           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));
+       }
+    } else {
+       /*
+        * This must be a numeric IPv4 address because it caused a
+        * success return from inet_addr.
+        */
+       ret->family = AF_INET;
+       strncpy(realhost, host, sizeof(realhost));
+    }
+    ret->address = ntohl(a);
+    realhost[lenof(realhost)-1] = '\0';
+    *canonicalname = smalloc(1+strlen(realhost));
+    strcpy(*canonicalname, realhost);
+    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
+}
+
+int sk_addrtype(SockAddr addr)
+{
+    return (addr->family == AF_INET ? ADDRTYPE_IPV4 : ADDRTYPE_IPV6);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+#ifdef IPV6
+    if (addr->family == AF_INET) {
+#endif
+       struct in_addr a;
+       a.s_addr = htonl(addr->address);
+       memcpy(buf, (char*) &a.s_addr, 4);
+#ifdef IPV6
+    } else {
+       memcpy(buf, (char*) addr->ai, 16);
+    }
+#endif
+}
+
+void sk_addr_free(SockAddr addr)
+{
+    sfree(addr);
+}
+
+static Plug sk_tcp_plug(Socket sock, Plug p)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    Plug ret = s->plug;
+    if (p)
+       s->plug = p;
+    return ret;
+}
+
+static void sk_tcp_flush(Socket s)
+{
+    /*
+     * We send data to the socket as soon as we can anyway,
+     * so we don't need to do anything here.  :-)
+     */
+}
+
+static void sk_tcp_close(Socket s);
+static int sk_tcp_write(Socket s, char *data, int len);
+static int sk_tcp_write_oob(Socket s, char *data, int len);
+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);
+static char *sk_tcp_socket_error(Socket s);
+
+Socket sk_register(void *sock, Plug plug)
+{
+    static struct socket_function_table fn_table = {
+       sk_tcp_plug,
+       sk_tcp_close,
+       sk_tcp_write,
+       sk_tcp_write_oob,
+       sk_tcp_flush,
+       sk_tcp_set_private_ptr,
+       sk_tcp_get_private_ptr,
+       sk_tcp_set_frozen,
+       sk_tcp_socket_error
+    };
+
+    Actual_Socket ret;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = smalloc(sizeof(struct Socket_tag));
+    ret->fn = &fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    bufchain_init(&ret->output_data);
+    ret->writable = 1;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 1;
+    ret->frozen_readable = 0;
+    ret->localhost_only = 0;          /* unused, but best init anyway */
+    ret->pending_error = 0;
+    ret->oobpending = FALSE;
+    ret->listener = 0;
+
+    ret->s = (int)sock;
+
+    if (ret->s < 0) {
+       ret->error = error_string(errno);
+       return (Socket) ret;
+    }
+
+    ret->oobinline = 0;
+
+    add234(sktree, ret);
+
+    return (Socket) ret;
+}
+
+Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
+             int nodelay, Plug plug)
+{
+    static struct socket_function_table fn_table = {
+       sk_tcp_plug,
+       sk_tcp_close,
+       sk_tcp_write,
+       sk_tcp_write_oob,
+       sk_tcp_flush,
+       sk_tcp_set_private_ptr,
+       sk_tcp_get_private_ptr,
+       sk_tcp_set_frozen,
+       sk_tcp_socket_error
+    };
+
+    int s;
+#ifdef IPV6
+    struct sockaddr_in6 a6;
+#endif
+    struct sockaddr_in a;
+    int err;
+    Actual_Socket ret;
+    short localport;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = smalloc(sizeof(struct Socket_tag));
+    ret->fn = &fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    bufchain_init(&ret->output_data);
+    ret->connected = 0;                       /* to start with */
+    ret->writable = 0;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 0;
+    ret->frozen_readable = 0;
+    ret->localhost_only = 0;          /* unused, but best init anyway */
+    ret->pending_error = 0;
+    ret->oobpending = FALSE;
+    ret->listener = 0;
+
+    /*
+     * Open socket.
+     */
+    s = socket(addr->family, SOCK_STREAM, 0);
+    ret->s = s;
+
+    if (s < 0) {
+       ret->error = error_string(errno);
+       return (Socket) ret;
+    }
+
+    ret->oobinline = oobinline;
+    if (oobinline) {
+       int b = TRUE;
+       setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+    }
+
+    if (nodelay) {
+       int b = TRUE;
+       setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+    }
+
+    /*
+     * Bind to local address.
+     */
+    if (privport)
+       localport = 1023;              /* count from 1023 downwards */
+    else
+       localport = 0;                 /* just use port 0 (ie kernel picks) */
+
+    /* Loop round trying to bind */
+    while (1) {
+       int retcode;
+
+#ifdef IPV6
+       if (addr->family == AF_INET6) {
+           memset(&a6, 0, sizeof(a6));
+           a6.sin6_family = AF_INET6;
+/*a6.sin6_addr      = in6addr_any; *//* == 0 */
+           a6.sin6_port = htons(localport);
+       } else
+#endif
+       {
+           a.sin_family = AF_INET;
+           a.sin_addr.s_addr = htonl(INADDR_ANY);
+           a.sin_port = htons(localport);
+       }
+#ifdef IPV6
+       retcode = bind(s, (addr->family == AF_INET6 ?
+                          (struct sockaddr *) &a6 :
+                          (struct sockaddr *) &a),
+                      (addr->family ==
+                       AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+       retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+       if (retcode >= 0) {
+           err = 0;
+           break;                     /* done */
+       } else {
+           err = errno;
+           if (err != EADDRINUSE)     /* failed, for a bad reason */
+               break;
+       }
+
+       if (localport == 0)
+           break;                     /* we're only looping once */
+       localport--;
+       if (localport == 0)
+           break;                     /* we might have got to the end */
+    }
+
+    if (err) {
+       ret->error = error_string(err);
+       return (Socket) ret;
+    }
+
+    /*
+     * Connect to remote address.
+     */
+#ifdef IPV6
+    if (addr->family == AF_INET6) {
+       memset(&a, 0, sizeof(a));
+       a6.sin6_family = AF_INET6;
+       a6.sin6_port = htons((short) port);
+       a6.sin6_addr =
+           ((struct sockaddr_in6 *) addr->ai->ai_addr)->sin6_addr;
+    } else
+#endif
+    {
+       a.sin_family = AF_INET;
+       a.sin_addr.s_addr = htonl(addr->address);
+       a.sin_port = htons((short) port);
+    }
+
+    if ((
+#ifdef IPV6
+           connect(s, ((addr->family == AF_INET6) ?
+                       (struct sockaddr *) &a6 : (struct sockaddr *) &a),
+                   (addr->family == AF_INET6) ? sizeof(a6) : sizeof(a))
+#else
+           connect(s, (struct sockaddr *) &a, sizeof(a))
+#endif
+       ) < 0) {
+       /*
+        * FIXME: We are prepared to receive EWOULDBLOCK here,
+        * because we might want the connection to be made
+        * asynchronously; but how do we actually arrange this in
+        * Unix? I forget.
+        */
+       if ( errno != EWOULDBLOCK ) {
+           ret->error = error_string(errno);
+           return (Socket) ret;
+       }
+    } else {
+       /*
+        * If we _don't_ get EWOULDBLOCK, the connect has completed
+        * and we should set the socket as connected and writable.
+        */
+       ret->connected = 1;
+       ret->writable = 1;
+    }
+
+    add234(sktree, ret);
+
+    return (Socket) ret;
+}
+
+Socket sk_newlistener(int port, Plug plug, int local_host_only)
+{
+    static struct socket_function_table fn_table = {
+       sk_tcp_plug,
+       sk_tcp_close,
+       sk_tcp_write,
+       sk_tcp_write_oob,
+       sk_tcp_flush,
+       sk_tcp_set_private_ptr,
+       sk_tcp_get_private_ptr,
+       sk_tcp_set_frozen,
+       sk_tcp_socket_error
+    };
+
+    int s;
+#ifdef IPV6
+    struct sockaddr_in6 a6;
+#endif
+    struct sockaddr_in a;
+    int err;
+    Actual_Socket ret;
+    int retcode;
+    int on = 1;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = smalloc(sizeof(struct Socket_tag));
+    ret->fn = &fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    bufchain_init(&ret->output_data);
+    ret->writable = 0;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 0;
+    ret->frozen_readable = 0;
+    ret->localhost_only = local_host_only;
+    ret->pending_error = 0;
+    ret->oobpending = FALSE;
+    ret->listener = 1;
+
+    /*
+     * Open socket.
+     */
+    s = socket(AF_INET, SOCK_STREAM, 0);
+    ret->s = s;
+
+    if (s < 0) {
+       ret->error = error_string(errno);
+       return (Socket) ret;
+    }
+
+    ret->oobinline = 0;
+
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+#ifdef IPV6
+    if (addr->family == AF_INET6) {
+       memset(&a6, 0, sizeof(a6));
+       a6.sin6_family = AF_INET6;
+       if (local_host_only)
+           a6.sin6_addr = in6addr_loopback;
+       else
+           a6.sin6_addr = in6addr_any;
+       a6.sin6_port = htons(port);
+    } else
+#endif
+    {
+       a.sin_family = AF_INET;
+       if (local_host_only)
+           a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       else
+           a.sin_addr.s_addr = htonl(INADDR_ANY);
+       a.sin_port = htons((short)port);
+    }
+#ifdef IPV6
+    retcode = bind(s, (addr->family == AF_INET6 ?
+                      (struct sockaddr *) &a6 :
+                      (struct sockaddr *) &a),
+                  (addr->family ==
+                   AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+    retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+    if (retcode >= 0) {
+       err = 0;
+    } else {
+       err = errno;
+    }
+
+    if (err) {
+       ret->error = error_string(err);
+       return (Socket) ret;
+    }
+
+
+    if (listen(s, SOMAXCONN) < 0) {
+        close(s);
+       ret->error = error_string(errno);
+       return (Socket) ret;
+    }
+
+    add234(sktree, ret);
+
+    return (Socket) ret;
+}
+
+static void sk_tcp_close(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    del234(sktree, s);
+    close(s->s);
+    sfree(s);
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+void try_send(Actual_Socket s)
+{
+    while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+       int nsent;
+       int err;
+       void *data;
+       int len, urgentflag;
+
+       if (s->sending_oob) {
+           urgentflag = MSG_OOB;
+           len = s->sending_oob;
+           data = &s->oobdata;
+       } else {
+           urgentflag = 0;
+           bufchain_prefix(&s->output_data, &data, &len);
+       }
+       nsent = send(s->s, data, len, urgentflag);
+       noise_ultralight(nsent);
+       if (nsent <= 0) {
+           err = (nsent < 0 ? errno : 0);
+           if (err == EWOULDBLOCK) {
+               /*
+                * Perfectly normal: we've sent all we can for the moment.
+                */
+               s->writable = FALSE;
+               return;
+           } else if (nsent == 0 ||
+                      err == ECONNABORTED || err == ECONNRESET) {
+               /*
+                * If send() returns CONNABORTED or CONNRESET, we
+                * unfortunately can't just call plug_closing(),
+                * because it's quite likely that we're currently
+                * _in_ a call from the code we'd be calling back
+                * to, so we'd have to make half the SSH code
+                * reentrant. Instead we flag a pending error on
+                * the socket, to be dealt with (by calling
+                * plug_closing()) at some suitable future moment.
+                */
+               s->pending_error = err;
+               return;
+           } else {
+               /* We're inside the Unix frontend here, so we know
+                * that the frontend handle is unnecessary. */
+               logevent(NULL, error_string(err));
+               fatalbox("%s", error_string(err));
+           }
+       } else {
+           if (s->sending_oob) {
+               if (nsent < len) {
+                   memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+                   s->sending_oob = len - nsent;
+               } else {
+                   s->sending_oob = 0;
+               }
+           } else {
+               bufchain_consume(&s->output_data, nsent);
+           }
+       }
+    }
+}
+
+static int sk_tcp_write(Socket sock, char *buf, int len)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    /*
+     * Add the data to the buffer list on the socket.
+     */
+    bufchain_add(&s->output_data, buf, len);
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    if (s->writable)
+       try_send(s);
+
+    return bufchain_size(&s->output_data);
+}
+
+static int sk_tcp_write_oob(Socket sock, char *buf, int len)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    /*
+     * Replace the buffer list on the socket with the data.
+     */
+    bufchain_clear(&s->output_data);
+    assert(len <= sizeof(s->oobdata));
+    memcpy(s->oobdata, buf, len);
+    s->sending_oob = len;
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    if (s->writable)
+       try_send(s);
+
+    return s->sending_oob;
+}
+
+int select_result(int fd, int event)
+{
+    int ret;
+    int err;
+    char buf[20480];                  /* nice big buffer for plenty of speed */
+    Actual_Socket s;
+    u_long atmark;
+
+    /* Find the Socket structure */
+    s = find234(sktree, (void *) fd, cmpforsearch);
+    if (!s)
+       return 1;                      /* boggle */
+
+    noise_ultralight(event);
+
+    switch (event) {
+#ifdef FIXME_NONBLOCKING_CONNECTIONS
+      case FIXME:                     /* connected */
+       s->connected = s->writable = 1;
+       break;
+#endif
+      case 4:                         /* exceptional */
+       if (!s->oobinline) {
+           /*
+            * On a non-oobinline socket, this indicates that we
+            * can immediately perform an OOB read and get back OOB
+            * data, which we will send to the back end with
+            * type==2 (urgent data).
+            */
+           ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
+           noise_ultralight(ret);
+           if (ret <= 0) {
+               char *str = (ret == 0 ? "Internal networking trouble" :
+                            error_string(errno));
+               /* We're inside the Unix frontend here, so we know
+                * that the frontend handle is unnecessary. */
+               logevent(NULL, str);
+               fatalbox("%s", str);
+           } else {
+               return plug_receive(s->plug, 2, buf, ret);
+           }
+           break;
+       }
+
+       /*
+        * If we reach here, this is an oobinline socket, which
+        * means we should set s->oobpending and then fall through
+        * to the read case.
+        */
+       s->oobpending = TRUE;
+      case 1:                         /* readable; also acceptance */
+       if (s->listener) {
+           /*
+            * On a listening socket, the readability event means a
+            * connection is ready to be accepted.
+            */
+           struct sockaddr_in isa;
+           int addrlen = sizeof(struct sockaddr_in);
+           int t;  /* socket of connection */
+
+           memset(&isa, 0, sizeof(struct sockaddr_in));
+           err = 0;
+           t = accept(s->s,(struct sockaddr *)&isa,&addrlen);
+           if (t < 0) {
+               break;
+           }
+
+           if (s->localhost_only &&
+               ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) {
+               close(t);              /* someone let nonlocal through?! */
+           } else if (plug_accepting(s->plug, (void*)t)) {
+               close(t);              /* denied or error */
+           }
+           break;
+       }
+
+       /*
+        * If we reach here, this is not a listening socket, so
+        * readability really means readability.
+        */
+
+       /* In the case the socket is still frozen, we don't even bother */
+       if (s->frozen) {
+           s->frozen_readable = 1;
+           break;
+       }
+
+       /*
+        * We have received data on the socket. For an oobinline
+        * socket, this might be data _before_ an urgent pointer,
+        * in which case we send it to the back end with type==1
+        * (data prior to urgent).
+        */
+       if (s->oobinline && s->oobpending) {
+           atmark = 1;
+           if (ioctl(s->s, SIOCATMARK, &atmark) == 0 && atmark)
+               s->oobpending = FALSE; /* clear this indicator */
+       } else
+           atmark = 1;
+
+       ret = recv(s->s, buf, sizeof(buf), 0);
+       noise_ultralight(ret);
+       if (ret < 0) {
+           if (errno == EWOULDBLOCK) {
+               break;
+           }
+       }
+       if (ret < 0) {
+           return plug_closing(s->plug, error_string(errno), errno, 0);
+       } else if (0 == ret) {
+           return plug_closing(s->plug, NULL, 0, 0);
+       } else {
+           return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+       }
+       break;
+      case 2:                         /* writable */
+       {
+           int bufsize_before, bufsize_after;
+           s->writable = 1;
+           bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+           try_send(s);
+           bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+           if (bufsize_after < bufsize_before)
+               plug_sent(s->plug, bufsize_after);
+       }
+       break;
+    }
+
+    return 1;
+}
+
+/*
+ * Deal with socket errors detected in try_send().
+ */
+void net_pending_errors(void)
+{
+    int i;
+    Actual_Socket s;
+
+    /*
+     * This might be a fiddly business, because it's just possible
+     * that handling a pending error on one socket might cause
+     * others to be closed. (I can't think of any reason this might
+     * happen in current SSH implementation, but to maintain
+     * generality of this network layer I'll assume the worst.)
+     * 
+     * So what we'll do is search the socket list for _one_ socket
+     * with a pending error, and then handle it, and then search
+     * the list again _from the beginning_. Repeat until we make a
+     * pass with no socket errors present. That way we are
+     * protected against the socket list changing under our feet.
+     */
+
+    do {
+       for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+           if (s->pending_error) {
+               /*
+                * An error has occurred on this socket. Pass it to the
+                * plug.
+                */
+               plug_closing(s->plug, error_string(s->pending_error),
+                            s->pending_error, 0);
+               break;
+           }
+       }
+    } while (s);
+}
+
+/*
+ * Each socket abstraction contains a `void *' private field in
+ * which the client can keep state.
+ */
+static void sk_tcp_set_private_ptr(Socket sock, void *ptr)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    s->private_ptr = ptr;
+}
+
+static void *sk_tcp_get_private_ptr(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    return s->private_ptr;
+}
+
+/*
+ * Special error values are returned from sk_namelookup and sk_new
+ * if there's a problem. These functions extract an error message,
+ * or return NULL if there's no problem.
+ */
+char *sk_addr_error(SockAddr addr)
+{
+    return addr->error;
+}
+static char *sk_tcp_socket_error(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    return s->error;
+}
+
+static void sk_tcp_set_frozen(Socket sock, int is_frozen)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    if (s->frozen == is_frozen)
+       return;
+    s->frozen = is_frozen;
+    if (!is_frozen && s->frozen_readable) {
+       char c;
+       recv(s->s, &c, 1, MSG_PEEK);
+    }
+    s->frozen_readable = 0;
+}
+
+/*
+ * For Unix select()-based frontends: enumerate all sockets
+ * currently active, and state whether we currently wish to receive
+ * select events on them for reading, writing and exceptional
+ * status.
+ */
+static void set_rwx(Actual_Socket s, int *rwx)
+{
+    int val = 0;
+    if (s->connected && !s->frozen)
+       val |= 1 | 4;                  /* read, except */
+    if (bufchain_size(&s->output_data))
+       val |= 2;                      /* write */
+    if (s->listener)
+       val |= 1;                      /* read == accept */
+    *rwx = val;
+}
+
+int first_socket(int *state, int *rwx)
+{
+    Actual_Socket s;
+    *state = 0;
+    s = index234(sktree, (*state)++);
+    if (s)
+       set_rwx(s, rwx);
+    return s ? s->s : -1;
+}
+
+int next_socket(int *state, int *rwx)
+{
+    Actual_Socket s = index234(sktree, (*state)++);
+    if (s)
+       set_rwx(s, rwx);
+    return s ? s->s : -1;
+}
+
+int net_service_lookup(char *service)
+{
+    struct servent *se;
+    se = getservbyname(service, NULL);
+    if (se != NULL)
+       return ntohs(se->s_port);
+    else
+       return 0;
+}
diff --git a/unix/uxnoise.c b/unix/uxnoise.c
new file mode 100644 (file)
index 0000000..91f9c2b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Noise generation for PuTTY's cryptographic random number
+ * generator.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+
+/*
+ * FIXME. This module currently depends critically on /dev/urandom,
+ * because it has no fallback mechanism for doing anything else.
+ */
+
+static void read_dev_urandom(char *buf, int len)
+{
+    int fd;
+    int ngot, ret;
+
+    fd = open("/dev/urandom", O_RDONLY);
+    if (fd < 0) {
+       perror("/dev/urandom: open");
+       exit(1);
+    }
+
+    ngot = 0;
+    while (ngot < len) {
+       ret = read(fd, buf+ngot, len-ngot);
+       if (ret < 0) {
+           perror("/dev/urandom: read");
+           exit(1);
+       }
+       ngot += ret;
+    }
+}
+
+/*
+ * This function is called once, at PuTTY startup. Currently it
+ * will read 32 bytes out of /dev/urandom and seed the internal
+ * generator with them.
+ */
+
+void noise_get_heavy(void (*func) (void *, int))
+{
+    char buf[32];
+    read_dev_urandom(buf, sizeof(buf));
+    func(buf, sizeof(buf));
+}
+
+void random_save_seed(void)
+{
+    /* Currently we do nothing here. FIXME? */
+}
+
+/*
+ * This function is called every time the urandom pool needs
+ * stirring, and will acquire the system time.
+ */
+void noise_get_light(void (*func) (void *, int))
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    func(&tv, sizeof(tv));
+}
+
+/*
+ * This function is called on a timer, and it will just pull some
+ * stuff out of /dev/urandom. FIXME: really I suspect we ought not
+ * to deplete /dev/urandom like this. Better to grab something more
+ * harmless.
+ */
+void noise_regular(void)
+{
+    char buf[4];
+    read_dev_urandom(buf, sizeof(buf));
+    random_add_noise(buf, sizeof(buf));
+}
+
+/*
+ * This function is called on every keypress or mouse move, and
+ * will add the current time to the noise pool. It gets the scan
+ * code or mouse position passed in, and adds that too.
+ */
+void noise_ultralight(unsigned long data)
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    random_add_noise(&tv, sizeof(tv));
+    random_add_noise(&data, sizeof(data));
+}
diff --git a/unix/uxplink.c b/unix/uxplink.c
new file mode 100644 (file)
index 0000000..8f828a5
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+ * PLink - a command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+
+/* More helpful version of the FD_SET macro, to also handle maxfd. */
+#define FD_SET_MAX(fd, max, set) do { \
+    FD_SET(fd, &set); \
+    if (max < fd + 1) max = fd + 1; \
+} while (0)
+
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#define MAX_STDIN_BACKLOG 4096
+
+void fatalbox(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    cleanup_exit(1);
+}
+void modalfatalbox(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    cleanup_exit(1);
+}
+void connection_fatal(void *frontend, char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    cleanup_exit(1);
+}
+void cmdline_error(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "plink: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+struct termios orig_termios;
+
+static Backend *back;
+static void *backhandle;
+
+char *x_get_default(char *key)
+{
+    return NULL;                      /* this is a stub */
+}
+int term_ldisc(Terminal *term, int mode)
+{
+    return FALSE;
+}
+void ldisc_update(void *frontend, int echo, int edit)
+{
+    /* Update stdin read mode to reflect changes in line discipline. */
+    struct termios mode;
+
+    mode = orig_termios;
+
+    if (echo)
+       mode.c_lflag |= ECHO;
+    else
+       mode.c_lflag &= ~ECHO;
+
+    if (edit)
+       mode.c_lflag |= ISIG | ICANON;
+    else
+       mode.c_lflag &= ~(ISIG | ICANON);
+
+    tcsetattr(0, TCSANOW, &mode);
+}
+
+void cleanup_termios(void)
+{
+    tcsetattr(0, TCSANOW, &orig_termios);
+}
+
+bufchain stdout_data, stderr_data;
+
+void try_output(int is_stderr)
+{
+    bufchain *chain = (is_stderr ? &stderr_data : &stdout_data);
+    int fd = (is_stderr ? 2 : 1);
+    void *senddata;
+    int sendlen, ret;
+
+    bufchain_prefix(chain, &senddata, &sendlen);
+    ret = write(fd, senddata, sendlen);
+    if (ret > 0)
+       bufchain_consume(chain, ret);
+    else if (ret < 0) {
+       perror(is_stderr ? "stderr: write" : "stdout: write");
+       exit(1);
+    }
+}
+
+int from_backend(void *frontend_handle, int is_stderr, char *data, int len)
+{
+    int osize, esize;
+
+    assert(len > 0);
+
+    if (is_stderr) {
+       bufchain_add(&stderr_data, data, len);
+       try_output(1);
+    } else {
+       bufchain_add(&stdout_data, data, len);
+       try_output(0);
+    }
+
+    osize = bufchain_size(&stdout_data);
+    esize = bufchain_size(&stderr_data);
+
+    return osize + esize;
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+    printf("PuTTY Link: command-line connection utility\n");
+    printf("%s\n", ver);
+    printf("Usage: plink [options] [user@]host [command]\n");
+    printf("       (\"host\" can also be a PuTTY saved session name)\n");
+    printf("Options:\n");
+    printf("  -v        show verbose messages\n");
+    printf("  -load sessname  Load settings from saved session\n");
+    printf("  -ssh -telnet -rlogin -raw\n");
+    printf("            force use of a particular protocol (default SSH)\n");
+    printf("  -P port   connect to specified port\n");
+    printf("  -l user   connect with specified username\n");
+    printf("  -m file   read remote command(s) from file\n");
+    printf("  -batch    disable all interactive prompts\n");
+    printf("The following options only apply to SSH connections:\n");
+    printf("  -pw passw login with specified password\n");
+    printf("  -L listen-port:host:port   Forward local port to "
+          "remote address\n");
+    printf("  -R listen-port:host:port   Forward remote port to"
+          " local address\n");
+    printf("  -X -x     enable / disable X11 forwarding\n");
+    printf("  -A -a     enable / disable agent forwarding\n");
+    printf("  -t -T     enable / disable pty allocation\n");
+    printf("  -1 -2     force use of particular protocol version\n");
+    printf("  -C        enable compression\n");
+    printf("  -i key    private key file for authentication\n");
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    int sending;
+    int portnumber = -1;
+    int *sklist;
+    int socket;
+    int i, skcount, sksize, socketstate;
+    int connopen;
+    int exitcode;
+    void *logctx;
+    void *ldisc;
+
+    ssh_get_line = console_get_line;
+
+    sklist = NULL;
+    skcount = sksize = 0;
+    /*
+     * Initialise port and protocol to sensible defaults. (These
+     * will be overridden by more or less anything.)
+     */
+    default_protocol = PROT_SSH;
+    default_port = 22;
+
+    flags = FLAG_STDERR;
+    /*
+     * Process the command line.
+     */
+    do_defaults(NULL, &cfg);
+    default_protocol = cfg.protocol;
+    default_port = cfg.port;
+    {
+       /*
+        * Override the default protocol if PLINK_PROTOCOL is set.
+        */
+       char *p = getenv("PLINK_PROTOCOL");
+       int i;
+       if (p) {
+           for (i = 0; backends[i].backend != NULL; i++) {
+               if (!strcmp(backends[i].name, p)) {
+                   default_protocol = cfg.protocol = backends[i].protocol;
+                   default_port = cfg.port =
+                       backends[i].backend->default_port;
+                   break;
+               }
+           }
+       }
+    }
+    while (--argc) {
+       char *p = *++argv;
+       if (*p == '-') {
+           int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), 1);
+           if (ret == -2) {
+               fprintf(stderr,
+                       "plink: option \"%s\" requires an argument\n", p);
+           } else if (ret == 2) {
+               --argc, ++argv;
+           } else if (ret == 1) {
+               continue;
+           } else if (!strcmp(p, "-batch")) {
+               console_batch_mode = 1;
+           }
+       } else if (*p) {
+           if (!*cfg.host) {
+               char *q = p;
+               /*
+                * If the hostname starts with "telnet:", set the
+                * protocol to Telnet and process the string as a
+                * Telnet URL.
+                */
+               if (!strncmp(q, "telnet:", 7)) {
+                   char c;
+
+                   q += 7;
+                   if (q[0] == '/' && q[1] == '/')
+                       q += 2;
+                   cfg.protocol = PROT_TELNET;
+                   p = q;
+                   while (*p && *p != ':' && *p != '/')
+                       p++;
+                   c = *p;
+                   if (*p)
+                       *p++ = '\0';
+                   if (c == ':')
+                       cfg.port = atoi(p);
+                   else
+                       cfg.port = -1;
+                   strncpy(cfg.host, q, sizeof(cfg.host) - 1);
+                   cfg.host[sizeof(cfg.host) - 1] = '\0';
+               } else {
+                   char *r;
+                   /*
+                    * Before we process the [user@]host string, we
+                    * first check for the presence of a protocol
+                    * prefix (a protocol name followed by ",").
+                    */
+                   r = strchr(p, ',');
+                   if (r) {
+                       int i, j;
+                       for (i = 0; backends[i].backend != NULL; i++) {
+                           j = strlen(backends[i].name);
+                           if (j == r - p &&
+                               !memcmp(backends[i].name, p, j)) {
+                               default_protocol = cfg.protocol =
+                                   backends[i].protocol;
+                               portnumber =
+                                   backends[i].backend->default_port;
+                               p = r + 1;
+                               break;
+                           }
+                       }
+                   }
+
+                   /*
+                    * Three cases. Either (a) there's a nonzero
+                    * length string followed by an @, in which
+                    * case that's user and the remainder is host.
+                    * Or (b) there's only one string, not counting
+                    * a potential initial @, and it exists in the
+                    * saved-sessions database. Or (c) only one
+                    * string and it _doesn't_ exist in the
+                    * database.
+                    */
+                   r = strrchr(p, '@');
+                   if (r == p)
+                       p++, r = NULL; /* discount initial @ */
+                   if (r == NULL) {
+                       /*
+                        * One string.
+                        */
+                       Config cfg2;
+                       do_defaults(p, &cfg2);
+                       if (cfg2.host[0] == '\0') {
+                           /* No settings for this host; use defaults */
+                           strncpy(cfg.host, p, sizeof(cfg.host) - 1);
+                           cfg.host[sizeof(cfg.host) - 1] = '\0';
+                           cfg.port = default_port;
+                       } else {
+                           cfg = cfg2;
+                           cfg.remote_cmd_ptr = cfg.remote_cmd;
+                       }
+                   } else {
+                       *r++ = '\0';
+                       strncpy(cfg.username, p, sizeof(cfg.username) - 1);
+                       cfg.username[sizeof(cfg.username) - 1] = '\0';
+                       strncpy(cfg.host, r, sizeof(cfg.host) - 1);
+                       cfg.host[sizeof(cfg.host) - 1] = '\0';
+                       cfg.port = default_port;
+                   }
+               }
+           } else {
+               char *command;
+               int cmdlen, cmdsize;
+               cmdlen = cmdsize = 0;
+               command = NULL;
+
+               while (argc) {
+                   while (*p) {
+                       if (cmdlen >= cmdsize) {
+                           cmdsize = cmdlen + 512;
+                           command = srealloc(command, cmdsize);
+                       }
+                       command[cmdlen++]=*p++;
+                   }
+                   if (cmdlen >= cmdsize) {
+                       cmdsize = cmdlen + 512;
+                       command = srealloc(command, cmdsize);
+                   }
+                   command[cmdlen++]=' '; /* always add trailing space */
+                   if (--argc) p = *++argv;
+               }
+               if (cmdlen) command[--cmdlen]='\0';
+                                      /* change trailing blank to NUL */
+               cfg.remote_cmd_ptr = command;
+               cfg.remote_cmd_ptr2 = NULL;
+               cfg.nopty = TRUE;      /* command => no terminal */
+
+               break;                 /* done with cmdline */
+           }
+       }
+    }
+
+    if (!*cfg.host) {
+       usage();
+    }
+
+    /*
+     * Trim leading whitespace off the hostname if it's there.
+     */
+    {
+       int space = strspn(cfg.host, " \t");
+       memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
+    }
+
+    /* See if host is of the form user@host */
+    if (cfg.host[0] != '\0') {
+       char *atsign = strchr(cfg.host, '@');
+       /* Make sure we're not overflowing the user field */
+       if (atsign) {
+           if (atsign - cfg.host < sizeof cfg.username) {
+               strncpy(cfg.username, cfg.host, atsign - cfg.host);
+               cfg.username[atsign - cfg.host] = '\0';
+           }
+           memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
+       }
+    }
+
+    /*
+     * Perform command-line overrides on session configuration.
+     */
+    cmdline_run_saved();
+
+    /*
+     * Trim a colon suffix off the hostname if it's there.
+     */
+    cfg.host[strcspn(cfg.host, ":")] = '\0';
+
+    /*
+     * Remove any remaining whitespace from the hostname.
+     */
+    {
+       int p1 = 0, p2 = 0;
+       while (cfg.host[p2] != '\0') {
+           if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
+               cfg.host[p1] = cfg.host[p2];
+               p1++;
+           }
+           p2++;
+       }
+       cfg.host[p1] = '\0';
+    }
+
+    if (!*cfg.remote_cmd_ptr)
+       flags |= FLAG_INTERACTIVE;
+
+    /*
+     * Select protocol. This is farmed out into a table in a
+     * separate file to enable an ssh-free variant.
+     */
+    {
+       int i;
+       back = NULL;
+       for (i = 0; backends[i].backend != NULL; i++)
+           if (backends[i].protocol == cfg.protocol) {
+               back = backends[i].backend;
+               break;
+           }
+       if (back == NULL) {
+           fprintf(stderr,
+                   "Internal fault: Unsupported protocol found\n");
+           return 1;
+       }
+    }
+
+    /*
+     * Select port.
+     */
+    if (portnumber != -1)
+       cfg.port = portnumber;
+
+    sk_init();
+
+    /*
+     * Start up the connection.
+     */
+    {
+       char *error;
+       char *realhost;
+       /* nodelay is only useful if stdin is a terminal device */
+       int nodelay = cfg.tcp_nodelay && isatty(0);
+
+       error = back->init(NULL, &backhandle, cfg.host, cfg.port,
+                          &realhost, nodelay);
+       if (error) {
+           fprintf(stderr, "Unable to open connection:\n%s", error);
+           return 1;
+       }
+       logctx = log_init(NULL);
+       back->provide_logctx(backhandle, logctx);
+       ldisc = ldisc_create(NULL, back, backhandle, NULL);
+       sfree(realhost);
+    }
+    connopen = 1;
+
+    /*
+     * Set up the initial console mode. We don't care if this call
+     * fails, because we know we aren't necessarily running in a
+     * console.
+     */
+    tcgetattr(0, &orig_termios);
+    atexit(cleanup_termios);
+    ldisc_update(NULL, 1, 1);
+    sending = FALSE;
+
+    while (1) {
+       fd_set rset, wset, xset;
+       int maxfd;
+       int rwx;
+       int ret;
+
+       FD_ZERO(&rset);
+       FD_ZERO(&wset);
+       FD_ZERO(&xset);
+       maxfd = 0;
+
+       if (connopen && !sending &&
+           back->socket(backhandle) != NULL &&
+           back->sendok(backhandle) &&
+           back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
+           /* If we're OK to send, then try to read from stdin. */
+           FD_SET_MAX(0, maxfd, rset);
+       }
+
+       if (bufchain_size(&stdout_data) > 0) {
+           /* If we have data for stdout, try to write to stdout. */
+           FD_SET_MAX(1, maxfd, wset);
+       }
+
+       if (bufchain_size(&stderr_data) > 0) {
+           /* If we have data for stderr, try to write to stderr. */
+           FD_SET_MAX(2, maxfd, wset);
+       }
+
+       /* Count the currently active sockets. */
+       i = 0;
+       for (socket = first_socket(&socketstate, &rwx); socket >= 0;
+            socket = next_socket(&socketstate, &rwx)) i++;
+
+       /* Expand the sklist buffer if necessary. */
+       if (i > sksize) {
+           sksize = i + 16;
+           sklist = srealloc(sklist, sksize * sizeof(*sklist));
+       }
+
+       /*
+        * Add all currently open sockets to the select sets, and
+        * store them in sklist as well.
+        */
+       skcount = 0;
+       for (socket = first_socket(&socketstate, &rwx); socket >= 0;
+            socket = next_socket(&socketstate, &rwx)) {
+           sklist[skcount++] = socket;
+           if (rwx & 1)
+               FD_SET_MAX(socket, maxfd, rset);
+           if (rwx & 2)
+               FD_SET_MAX(socket, maxfd, wset);
+           if (rwx & 4)
+               FD_SET_MAX(socket, maxfd, xset);
+       }
+
+       ret = select(maxfd, &rset, &wset, &xset, NULL);
+
+       if (ret < 0) {
+           perror("select");
+           exit(1);
+       }
+
+       for (i = 0; i < skcount; i++) {
+           socket = sklist[i];
+           if (FD_ISSET(socket, &rset))
+               select_result(socket, 1);
+           if (FD_ISSET(socket, &wset))
+               select_result(socket, 2);
+           if (FD_ISSET(socket, &xset))
+               select_result(socket, 4);
+       }
+
+       if (FD_ISSET(0, &rset)) {
+           char buf[4096];
+           int ret;
+
+           if (connopen && back->socket(backhandle) != NULL) {
+               ret = read(0, buf, sizeof(buf));
+               if (ret < 0) {
+                   perror("stdin: read");
+                   exit(1);
+               } else if (ret == 0) {
+                   back->special(backhandle, TS_EOF);
+                   sending = FALSE;   /* send nothing further after this */
+               } else {
+                   back->send(backhandle, buf, ret);
+               }
+           }
+       }
+
+       if (FD_ISSET(1, &wset)) {
+           try_output(0);
+       }
+
+       if (FD_ISSET(2, &wset)) {
+           try_output(1);
+       }
+
+       if ((!connopen || back->socket(backhandle) == NULL) &&
+           bufchain_size(&stdout_data) == 0 &&
+           bufchain_size(&stderr_data) == 0)
+           break;                     /* we closed the connection */
+    }
+    exitcode = back->exitcode(backhandle);
+    if (exitcode < 0) {
+       fprintf(stderr, "Remote process exit code unavailable\n");
+       exitcode = 1;                  /* this is an error condition */
+    }
+    return exitcode;
+}
index 06f6d3f..9047988 100644 (file)
@@ -6,9 +6,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
-#include <gtk/gtk.h>
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include "putty.h"
 #include "storage.h"
 #include "tree234.h"
@@ -46,8 +47,6 @@ void close_settings_w(void *handle)
  * immediately work out.
  */
 
-static Display *display;
-
 struct xrm_string {
     char *key;
     char *value;
@@ -104,17 +103,13 @@ char *get_setting(char *key)
        if (ret)
            return ret->value;
     }
-    return XGetDefault(display, app_name, key);
+    return x_get_default(key);
 }
 
 void *open_settings_r(char *sessionname)
 {
     static int thing_to_return_an_arbitrary_non_null_pointer_to;
-    display = GDK_DISPLAY();
-    if (!display)
-       return NULL;
-    else
-       return &thing_to_return_an_arbitrary_non_null_pointer_to;
+    return &thing_to_return_an_arbitrary_non_null_pointer_to;
 }
 
 char *read_setting_s(void *handle, char *key, char *buffer, int buflen)
@@ -160,13 +155,144 @@ void enum_settings_finish(void *handle)
 {
 }
 
+enum {
+    INDEX_DIR, INDEX_HOSTKEYS
+};
+
+static void make_filename(char *filename, int index)
+{
+    char *home;
+    int len;
+    home = getenv("HOME");
+    strncpy(filename, home, FILENAME_MAX);
+    len = strlen(filename);
+    strncpy(filename + len,
+           index == INDEX_DIR ? "/.putty" :
+           index == INDEX_HOSTKEYS ? "/.putty/sshhostkeys" :
+           "/.putty/ERROR", FILENAME_MAX - len);
+    filename[FILENAME_MAX-1] = '\0';
+}
+
+/*
+ * Read an entire line of text from a file. Return a buffer
+ * malloced to be as big as necessary (caller must free).
+ */
+static char *fgetline(FILE *fp)
+{
+    char *ret = smalloc(512);
+    int size = 512, len = 0;
+    while (fgets(ret + len, size - len, fp)) {
+       len += strlen(ret + len);
+       if (ret[len-1] == '\n')
+           break;                     /* got a newline, we're done */
+       size = len + 512;
+       ret = srealloc(ret, size);
+    }
+    if (len == 0) {                   /* first fgets returned NULL */
+       sfree(ret);
+       return NULL;
+    }
+    ret[len] = '\0';
+    return ret;
+}
+
+/*
+ * Lines in the host keys file are of the form
+ * 
+ *   type@port:hostname keydata
+ * 
+ * e.g.
+ * 
+ *   rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
+ */
 int verify_host_key(char *hostname, int port, char *keytype, char *key)
 {
-    return 1;                         /* key does not exist in registry */
+    FILE *fp;
+    char filename[FILENAME_MAX];
+    char *line;
+    int ret;
+
+    make_filename(filename, INDEX_HOSTKEYS);
+    fp = fopen(filename, "r");
+    if (!fp)
+       return 1;                      /* key does not exist */
+
+    ret = 1;
+    while ( (line = fgetline(fp)) ) {
+       int i;
+       char *p = line;
+       char porttext[20];
+
+       line[strcspn(line, "\n")] = '\0';   /* strip trailing newline */
+
+       i = strlen(keytype);
+       if (strncmp(p, keytype, i))
+           goto done;
+       p += i;
+
+       if (*p != '@')
+           goto done;
+       p++;
+
+       sprintf(porttext, "%d", port);
+       i = strlen(porttext);
+       if (strncmp(p, porttext, i))
+           goto done;
+       p += i;
+
+       if (*p != ':')
+           goto done;
+       p++;
+
+       i = strlen(hostname);
+       if (strncmp(p, hostname, i))
+           goto done;
+       p += i;
+
+       if (*p != ' ')
+           goto done;
+       p++;
+
+       /*
+        * Found the key. Now just work out whether it's the right
+        * one or not.
+        */
+       if (!strcmp(p, key))
+           ret = 0;                   /* key matched OK */
+       else
+           ret = 2;                   /* key mismatch */
+
+       done:
+       sfree(line);
+       if (ret != 1)
+           break;
+    }
+
+    return ret;
 }
 
 void store_host_key(char *hostname, int port, char *keytype, char *key)
 {
+    FILE *fp;
+    int fd;
+    char filename[FILENAME_MAX];
+
+    make_filename(filename, INDEX_HOSTKEYS);
+    fd = open(filename, O_CREAT | O_APPEND | O_RDWR, 0600);
+    if (fd < 0) {
+       char dir[FILENAME_MAX];
+
+       make_filename(dir, INDEX_DIR);
+       mkdir(dir, 0700);
+       fd = open(filename, O_CREAT | O_APPEND | O_RDWR, 0600);
+    }
+    if (fd < 0) {
+       perror(filename);
+       exit(1);
+    }
+    fp = fdopen(fd, "a");
+    fprintf(fp, "%s@%d:%s %s\n", keytype, port, hostname, key);
+    fclose(fp);
 }
 
 void read_random_seed(noise_consumer_t consumer)