]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Provide a Unix port of Pageant.
authorSimon Tatham <anakin@pobox.com>
Tue, 5 May 2015 19:16:23 +0000 (20:16 +0100)
committerSimon Tatham <anakin@pobox.com>
Tue, 5 May 2015 19:16:23 +0000 (20:16 +0100)
This is much more like ssh-agent than the Windows version is - it sets
SSH_AUTH_SOCK and SSH_AGENT_PID as its means of being found by other
processes, rather than Windows Pageant's approach of establishing
itself in a well-known location. But the actual agent code is the same
as Windows Pageant.

For the moment, this is an experimental utility and I don't expect it
to be useful to many people; its immediate use to me is that it
provides a way to test and debug the agent code on Unix, and also to
use the agent interface as a convenient way to exercise public key
functions I want to debug. And of course it means I can be constantly
using and testing my own code, on whatever platform I happen to be
using. In the further future, I have a list of possible features I
might add to it, but I don't know which ones I'll decide are
worthwhile.

One feature I've already put in is a wider range of lifetime
management options than ssh-agent: the -X mode causes Pageant to make
a connection to your X display, and automatically terminate when that
connection closes, so that it has the same lifetime as your X session
without having to do the cumbersome trick of exec()ing the subsequent
session-management process.

Recipe
unix/uxpgnt.c [new file with mode: 0644]

diff --git a/Recipe b/Recipe
index fa8a1a44316d2dce2aeff6d0ce7e0bc7a7b86f7b..69d8431fe5fee931ada8451a5c8943ae7c55e7dc 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -303,5 +303,9 @@ puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
 pscp     : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
 psftp    : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
 
+pageant  : [U] uxpgnt pageant sshrsa sshpubk sshdes sshbn sshmd5 version
+        + tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 sshecc
+        + conf uxsignal nocproxy nogss be_none x11fwd ux_x11 UXMISC LIBS
+
 PuTTY    : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH
          + ux_x11 uxpty uxsignal testback putty.icns info.plist
diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c
new file mode 100644 (file)
index 0000000..7d1a680
--- /dev/null
@@ -0,0 +1,517 @@
+/*
+ * Unix Pageant, more or less similar to ssh-agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "pageant.h"
+
+SockAddr unix_sock_addr(const char *path);
+Socket new_unix_listener(SockAddr listenaddr, Plug plug);
+
+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);
+    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);
+    exit(1);
+}
+void nonfatal(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+}
+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);
+    exit(1);
+}
+void cmdline_error(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "pageant: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+int pageant_logging = FALSE;
+void pageant_log(void *ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (!pageant_logging)
+        return;
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+    va_end(ap);
+}
+
+/*
+ * In Pageant our selects are synchronous, so these functions are
+ * empty stubs.
+ */
+int uxsel_input_add(int fd, int rwx) { return 0; }
+void uxsel_input_remove(int id) { }
+
+/*
+ * More stubs.
+ */
+void logevent(void *frontend, const char *string) {}
+void random_save_seed(void) {}
+void random_destroy_seed(void) {}
+void noise_ultralight(unsigned long data) {}
+char *platform_default_s(const char *name) { return NULL; }
+int platform_default_i(const char *name, int def) { return def; }
+FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
+Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
+char *x_get_default(const char *key) { return NULL; }
+void old_keyfile_warning(void) {}
+void timer_change_notify(unsigned long next) {}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+    printf("Pageant: SSH agent\n");
+    printf("%s\n", ver);
+    printf("FIXME\n");
+    exit(1);
+}
+
+static void version(void)
+{
+    printf("pageant: %s\n", ver);
+    exit(1);
+}
+
+void keylist_update(void)
+{
+    /* Nothing needs doing in Unix Pageant */
+}
+
+#define PAGEANT_DIR_PREFIX "/tmp/pageant"
+
+const char *const appname = "Pageant";
+
+Conf *conf;
+
+char *platform_get_x_display(void) {
+    return dupstr(getenv("DISPLAY"));
+}
+int sshfwd_write(struct ssh_channel *c, char *data, int len) { return 0; }
+void sshfwd_write_eof(struct ssh_channel *c) { /* FIXME: notify main loop instead */ exit(0); }
+void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { /* FIXME: notify main loop instead */ exit(1); }
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) {}
+Conf *sshfwd_get_conf(struct ssh_channel *c) { return conf; }
+void sshfwd_x11_sharing_handover(struct ssh_channel *c,
+                                 void *share_cs, void *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len) {}
+void sshfwd_x11_is_local(struct ssh_channel *c) {}
+static void x11_log(Plug p, int type, SockAddr addr, int port,
+                   const char *error_msg, int error_code) {}
+static int x11_closing(Plug plug, const char *error_msg, int error_code,
+                      int calling_back) { /* FIXME: notify main loop instead */ exit(0); }
+static int x11_receive(Plug plug, int urgent, char *data, int len) { return 0; }
+static void x11_sent(Plug plug, int bufsize) {}
+struct X11Connection {
+    const struct plug_function_table *fn;
+};
+
+char *socketname;
+void pageant_print_env(int pid)
+{
+    printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
+           "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
+           socketname, (int)pid);
+}
+
+void pageant_fork_and_print_env(void)
+{
+    pid_t pid = fork();
+    if (pid == -1) {
+        perror("fork");
+        exit(1);
+    } else if (pid != 0) {
+        pageant_print_env(pid);
+        exit(0);
+    }
+
+    /*
+     * Having forked off, we now daemonise ourselves as best we can.
+     * It's good practice in general to setsid() ourself out of any
+     * process group we didn't want to be part of, and to chdir("/")
+     * to avoid holding any directories open that we don't need in
+     * case someone wants to umount them; also, we should definitely
+     * close standard output (because it will very likely be pointing
+     * at a pipe from which some parent process is trying to read our
+     * environment variable dump, so if we hold open another copy of
+     * it then that process will never finish reading). We close
+     * standard input too on general principles, but not standard
+     * error, since we might need to shout a panicky error message
+     * down that one.
+     */
+    if (chdir("/") < 0) {
+        /* should there be an error condition, nothing we can do about
+         * it anyway */
+    }
+    close(0);
+    close(1);
+    setsid();
+}
+
+int signalpipe[2];
+
+void sigchld(int signum)
+{
+    if (write(signalpipe[1], "x", 1) <= 0)
+        /* not much we can do about it */;
+}
+
+int main(int argc, char **argv)
+{
+    int *fdlist;
+    int fd;
+    int i, fdcount, fdsize, fdstate;
+    int errors;
+    unsigned long now;
+    char *username, *socketdir;
+    const char *err;
+    struct pageant_listen_state *pl;
+    Socket sock;
+    enum {
+        LIFE_UNSPEC, LIFE_X11, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
+    } life = LIFE_UNSPEC;
+    const char *display = NULL;
+    int doing_opts = TRUE;
+    char **exec_args = NULL;
+    int termination_pid = -1;
+
+    fdlist = NULL;
+    fdcount = fdsize = 0;
+    errors = FALSE;
+
+    /*
+     * Process the command line.
+     */
+    while (--argc) {
+       char *p = *++argv;
+       if (*p == '-' && doing_opts) {
+            if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
+                version();
+           } else if (!strcmp(p, "--help")) {
+                usage();
+                exit(0);
+            } else if (!strcmp(p, "-X")) {
+                life = LIFE_X11;
+            } else if (!strcmp(p, "--debug")) {
+                life = LIFE_DEBUG;
+            } else if (!strcmp(p, "--permanent")) {
+                life = LIFE_PERM;
+            } else if (!strcmp(p, "--exec")) {
+                life = LIFE_EXEC;
+            } else if (!strcmp(p, "--")) {
+                doing_opts = FALSE;
+            }
+        } else {
+            if (life == LIFE_EXEC) {
+                exec_args = argv;
+                break; /* everything else is now args to the exec command */
+            } else {
+                fprintf(stderr, "pageant: unexpected argument '%s'\n", p);
+                exit(1);
+            }
+        }
+    }
+
+    if (errors)
+       return 1;
+
+    if (life == LIFE_UNSPEC) {
+        fprintf(stderr, "pageant: expected a lifetime option\n");
+        exit(1);
+    }
+    if (life == LIFE_EXEC && !exec_args) {
+        fprintf(stderr, "pageant: expected a command with --exec\n");
+        exit(1);
+    }
+
+    /*
+     * Block SIGPIPE, so that we'll get EPIPE individually on
+     * particular network connections that go wrong.
+     */
+    putty_signal(SIGPIPE, SIG_IGN);
+
+    sk_init();
+    uxsel_init();
+
+    /*
+     * Set up a listening socket and run Pageant on it.
+     */
+    username = get_username();
+    socketdir = dupprintf("%s.%s", PAGEANT_DIR_PREFIX, username);
+    sfree(username);
+    assert(*socketdir == '/');
+    if ((err = make_dir_and_check_ours(socketdir)) != NULL) {
+        fprintf(stderr, "pageant: %s: %s\n", socketdir, err);
+        exit(1);
+    }
+    socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid());
+
+    pageant_init();
+    pl = pageant_listener_new(NULL, pageant_log);
+    sock = new_unix_listener(unix_sock_addr(socketname), (Plug)pl);
+    if ((err = sk_socket_error(sock)) != NULL) {
+        fprintf(stderr, "pageant: %s: %s\n", socketname, err);
+        exit(1);
+    }
+    pageant_listener_got_socket(pl, sock);
+
+    conf = conf_new();
+    conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
+
+    /*
+     * Lifetime preparations.
+     */
+    signalpipe[0] = signalpipe[1] = -1;
+    if (life == LIFE_X11) {
+        struct X11Display *disp;
+        void *greeting;
+        int greetinglen;
+        Socket s;
+        struct X11Connection *conn;
+
+        static const struct plug_function_table fn_table = {
+            x11_log,
+            x11_closing,
+            x11_receive,
+            x11_sent,
+            NULL
+        };
+
+        if (!display)
+            display = getenv("DISPLAY");
+        if (!display) {
+            fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
+            exit(1);
+        }
+        disp = x11_setup_display(display, conf);
+
+        conn = snew(struct X11Connection);
+        conn->fn = &fn_table;
+        s = new_connection(sk_addr_dup(disp->addr),
+                           disp->realhost, disp->port,
+                           0, 1, 0, 0, (Plug)conn, conf);
+        if ((err = sk_socket_error(s)) != NULL) {
+            fprintf(stderr, "pageant: unable to connect to X server: %s", err);
+            exit(1);
+        }
+        greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
+                                     disp->localauthdata,
+                                     disp->localauthdatalen,
+                                     NULL, 0, &greetinglen);
+        sk_write(s, greeting, greetinglen);
+        smemclr(greeting, greetinglen);
+        sfree(greeting);
+
+        pageant_fork_and_print_env();
+    } else if (life == LIFE_PERM) {
+        pageant_fork_and_print_env();
+    } else if (life == LIFE_DEBUG) {
+        pageant_print_env(getpid());
+        pageant_logging = TRUE;
+    } else if (life == LIFE_EXEC) {
+        pid_t agentpid, pid;
+
+        agentpid = getpid();
+
+        /*
+         * Set up the pipe we'll use to tell us about SIGCHLD.
+         */
+        if (pipe(signalpipe) < 0) {
+            perror("pipe");
+            exit(1);
+        }
+        putty_signal(SIGCHLD, sigchld);
+
+        pid = fork();
+        if (pid < 0) {
+            perror("fork");
+            exit(1);
+        } else if (pid == 0) {
+            setenv("SSH_AUTH_SOCK", socketname, TRUE);
+            setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), TRUE);
+            execvp(exec_args[0], exec_args);
+            perror("exec");
+            _exit(127);
+        } else {
+            termination_pid = pid;
+        }
+    }
+    /*
+     * FIXME: and if life == LIFE_EXEC, then we use our own pid, but
+     * don't print_env - instead, fork, stuff it in the real
+     * environment, and exec our child.
+     *
+     * FIXME: and hang on, perhaps LIFE_PARENT won't work after all,
+     * because after we fork, of course, our ppid _isn't_ an indicator
+     * of whether the target process has gone away.
+     */
+
+    now = GETTICKCOUNT();
+
+    while (1) {
+       fd_set rset, wset, xset;
+       int maxfd;
+       int rwx;
+       int ret;
+        unsigned long next;
+
+       FD_ZERO(&rset);
+       FD_ZERO(&wset);
+       FD_ZERO(&xset);
+       maxfd = 0;
+
+        if (signalpipe[0] >= 0) {
+            FD_SET_MAX(signalpipe[0], maxfd, rset);
+        }
+
+       /* Count the currently active fds. */
+       i = 0;
+       for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+            fd = next_fd(&fdstate, &rwx)) i++;
+
+       /* Expand the fdlist buffer if necessary. */
+       if (i > fdsize) {
+           fdsize = i + 16;
+           fdlist = sresize(fdlist, fdsize, int);
+       }
+
+       /*
+        * Add all currently open fds to the select sets, and store
+        * them in fdlist as well.
+        */
+       fdcount = 0;
+       for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+            fd = next_fd(&fdstate, &rwx)) {
+           fdlist[fdcount++] = fd;
+           if (rwx & 1)
+               FD_SET_MAX(fd, maxfd, rset);
+           if (rwx & 2)
+               FD_SET_MAX(fd, maxfd, wset);
+           if (rwx & 4)
+               FD_SET_MAX(fd, maxfd, xset);
+       }
+
+        if (toplevel_callback_pending()) {
+            struct timeval tv;
+            tv.tv_sec = 0;
+            tv.tv_usec = 0;
+            ret = select(maxfd, &rset, &wset, &xset, &tv);
+        } else if (run_timers(now, &next)) {
+            unsigned long then;
+            long ticks;
+            struct timeval tv;
+
+            then = now;
+            now = GETTICKCOUNT();
+            if (now - then > next - then)
+                ticks = 0;
+            else
+                ticks = next - now;
+            tv.tv_sec = ticks / 1000;
+            tv.tv_usec = ticks % 1000 * 1000;
+            ret = select(maxfd, &rset, &wset, &xset, &tv);
+            if (ret == 0)
+                now = next;
+            else
+                now = GETTICKCOUNT();
+        } else {
+            ret = select(maxfd, &rset, &wset, &xset, NULL);
+        }
+
+        if (ret < 0 && errno == EINTR)
+            continue;
+
+       if (ret < 0) {
+           perror("select");
+           exit(1);
+       }
+
+       for (i = 0; i < fdcount; i++) {
+           fd = fdlist[i];
+            /*
+             * We must process exceptional notifications before
+             * ordinary readability ones, or we may go straight
+             * past the urgent marker.
+             */
+           if (FD_ISSET(fd, &xset))
+               select_result(fd, 4);
+           if (FD_ISSET(fd, &rset))
+               select_result(fd, 1);
+           if (FD_ISSET(fd, &wset))
+               select_result(fd, 2);
+       }
+
+        if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
+            char c[1];
+            if (read(signalpipe[0], c, 1) <= 0)
+                /* ignore error */;
+            /* ignore its value; it'll be `x' */
+            while (1) {
+                int status;
+                pid_t pid;
+                pid = waitpid(-1, &status, WNOHANG);
+                if (pid == 0)
+                    break;
+                if (pid == termination_pid)
+                    exit(0);
+            }
+        }
+
+        run_toplevel_callbacks();
+    }
+
+    return 0;
+}