Subject: [PATCH] libssh2_channel_request_auth_agent

[PATCH] libssh2_channel_request_auth_agent

From: Mitchell Hashimoto <mitchell.hashimoto_at_gmail.com>
Date: Sat, 17 Mar 2012 10:49:57 -0600

This method will request SSH agent forwarding for the SSH session.
The method is on the channel itself because it must be sent over
a channel, but this simply starts the SSH agent receiver on the remote
side.

This includes the new API, documentation, and a new example.

---
 docs/libssh2_channel_request_auth_agent.3 |   22 ++
 example/.gitignore                        |    1 +
 example/Makefile.am                       |    4 +-
 example/ssh2_agent_forwarding.c           |  331 +++++++++++++++++++++++++++++
 include/libssh2.h                         |    2 +
 src/channel.c                             |  152 +++++++++++++
 src/libssh2_priv.h                        |    7 +
 7 files changed, 517 insertions(+), 2 deletions(-)
 create mode 100644 docs/libssh2_channel_request_auth_agent.3
 create mode 100644 example/ssh2_agent_forwarding.c
diff --git a/docs/libssh2_channel_request_auth_agent.3 b/docs/libssh2_channel_request_auth_agent.3
new file mode 100644
index 0000000..ea76a48
--- /dev/null
+++ b/docs/libssh2_channel_request_auth_agent.3
@@ -0,0 +1,22 @@
+.TH libssh2_channel_request_auth_agent 3 "1 Jun 2007" "libssh2 0.15" "libssh2 manual"
+.SH NAME
+libssh2_channel_request_auth_agent - request agent forwarding for a session
+.SH SYNOPSIS
+#include <libssh2.h>
+
+int
+libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel);
+
+.SH DESCRIPTION
+Request that agent forwarding be enabled for this SSH session. This sends the
+request over this specific channel, which causes the agent listener to be
+started on the remote side upon success. This agent listener will then run
+for the duration of the SSH session.
+
+\fIchannel\fP - Previously opened channel instance such as returned by
+.BR libssh2_channel_open_ex(3)
+
+.SH RETURN VALUE
+Return 0 on success or negative on failure.  It returns
+LIBSSH2_ERROR_EAGAIN when it would otherwise block. While
+LIBSSH2_ERROR_EAGAIN is a negative number, it isn't really a failure per se.
diff --git a/example/.gitignore b/example/.gitignore
index 1344819..e3c6337 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -20,6 +20,7 @@ sftp_write_nonblock
 config.h.in
 ssh2_exec
 ssh2_agent
+ssh2_agent_forwarding
 libssh2_config.h
 libssh2_config.h.in
 stamp-h2
diff --git a/example/Makefile.am b/example/Makefile.am
index 8c6636f..bd4eefd 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -6,8 +6,8 @@ EXTRA_DIST = libssh2_config.h.in
 noinst_PROGRAMS = direct_tcpip ssh2 scp scp_nonblock scp_write		\
  scp_write_nonblock sftp sftp_nonblock sftp_write sftp_write_nonblock	\
  sftp_mkdir sftp_mkdir_nonblock sftp_RW_nonblock sftp_write_sliding	\
- sftpdir sftpdir_nonblock ssh2_exec ssh2_agent ssh2_echo sftp_append	\
- subsystem_netconf tcpip-forward
+ sftpdir sftpdir_nonblock ssh2_exec ssh2_agent ssh2_agent_forwarding \
+ ssh2_echo sftp_append subsystem_netconf tcpip-forward
 
 if HAVE_SYS_UN_H
 noinst_PROGRAMS += x11
diff --git a/example/ssh2_agent_forwarding.c b/example/ssh2_agent_forwarding.c
new file mode 100644
index 0000000..94c7516
--- /dev/null
+++ b/example/ssh2_agent_forwarding.c
@@ -0,0 +1,331 @@
+/*
+ * Sample showing how to use libssh2 to request agent forwarding
+ * on the remote host. The command executed will run with agent forwarded
+ * so you should be able to do things like clone out protected git
+ * repos and such.
+ *
+ * The sample code has fixed values for host name, user name, password
+ * and command to run.
+ *
+ * Run it like this:
+ *
+ * $ ./ssh2_agent_forwarding 127.0.0.1 user password "uptime"
+ *
+ */
+
+#include "libssh2_config.h"
+#include <libssh2.h>
+
+#ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+# ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+
+static int waitsocket(int socket_fd, LIBSSH2_SESSION *session)
+{
+    struct timeval timeout;
+    int rc;
+    fd_set fd;
+    fd_set *writefd = NULL;
+    fd_set *readfd = NULL;
+    int dir;
+
+    timeout.tv_sec = 10;
+    timeout.tv_usec = 0;
+
+    FD_ZERO(&fd);
+
+    FD_SET(socket_fd, &fd);
+
+    /* now make sure we wait in the correct direction */
+    dir = libssh2_session_block_directions(session);
+
+    if(dir & LIBSSH2_SESSION_BLOCK_INBOUND)
+        readfd = &fd;
+
+    if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
+        writefd = &fd;
+
+    rc = select(socket_fd + 1, readfd, writefd, NULL, &timeout);
+
+    return rc;
+}
+
+int main(int argc, char *argv[])
+{
+    const char *hostname = "127.0.0.1";
+    const char *commandline = "uptime";
+    const char *username    = "user";
+    const char *password    = "password";
+    unsigned long hostaddr;
+    int sock;
+    struct sockaddr_in sin;
+    const char *fingerprint;
+    LIBSSH2_SESSION *session;
+    LIBSSH2_CHANNEL *channel;
+    int rc;
+    int exitcode;
+    char *exitsignal=(char *)"none";
+    int bytecount = 0;
+    size_t len;
+    LIBSSH2_KNOWNHOSTS *nh;
+    int type;
+
+#ifdef WIN32
+    WSADATA wsadata;
+    WSAStartup(MAKEWORD(2,0), &wsadata);
+#endif
+    if (argc > 1)
+        /* must be ip address only */
+        hostname = argv[1];
+
+    if (argc > 2) {
+        username = argv[2];
+    }
+    if (argc > 3) {
+        password = argv[3];
+    }
+    if (argc > 4) {
+        commandline = argv[4];
+    }
+
+    rc = libssh2_init (0);
+    if (rc != 0) {
+        fprintf (stderr, "libssh2 initialization failed (%d)\n", rc);
+        return 1;
+    }
+
+    hostaddr = inet_addr(hostname);
+
+    /* Ultra basic "connect to port 22 on localhost"
+     * Your code is responsible for creating the socket establishing the
+     * connection
+     */
+    sock = socket(AF_INET, SOCK_STREAM, 0);
+
+    sin.sin_family = AF_INET;
+    sin.sin_port = htons(22);
+    sin.sin_addr.s_addr = hostaddr;
+    if (connect(sock, (struct sockaddr*)(&sin),
+                sizeof(struct sockaddr_in)) != 0) {
+        fprintf(stderr, "failed to connect!\n");
+        return -1;
+    }
+
+    /* Create a session instance */
+    session = libssh2_session_init();
+    if (!session)
+        return -1;
+
+    /* tell libssh2 we want it all done non-blocking */
+    libssh2_session_set_blocking(session, 0);
+
+    /* ... start it up. This will trade welcome banners, exchange keys,
+     * and setup crypto, compression, and MAC layers
+     */
+    while ((rc = libssh2_session_handshake(session, sock)) ==
+           LIBSSH2_ERROR_EAGAIN);
+    if (rc) {
+        fprintf(stderr, "Failure establishing SSH session: %d\n", rc);
+        return -1;
+    }
+
+    nh = libssh2_knownhost_init(session);
+    if(!nh) {
+        /* eeek, do cleanup here */
+        return 2;
+    }
+
+    /* read all hosts from here */
+    libssh2_knownhost_readfile(nh, "known_hosts",
+                               LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+
+    /* store all known hosts to here */
+    libssh2_knownhost_writefile(nh, "dumpfile",
+                                LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+
+    fingerprint = libssh2_session_hostkey(session, &len, &type);
+    if(fingerprint) {
+        struct libssh2_knownhost *host;
+#if LIBSSH2_VERSION_NUM >= 0x010206
+        /* introduced in 1.2.6 */
+        int check = libssh2_knownhost_checkp(nh, hostname, 22,
+                                             fingerprint, len,
+                                             LIBSSH2_KNOWNHOST_TYPE_PLAIN|
+                                             LIBSSH2_KNOWNHOST_KEYENC_RAW,
+                                             &host);
+#else
+        /* 1.2.5 or older */
+        int check = libssh2_knownhost_check(nh, hostname,
+                                            fingerprint, len,
+                                            LIBSSH2_KNOWNHOST_TYPE_PLAIN|
+                                            LIBSSH2_KNOWNHOST_KEYENC_RAW,
+                                            &host);
+#endif
+        fprintf(stderr, "Host check: %d, key: %s\n", check,
+                (check <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
+                host->key:"<none>");
+
+        /*****
+         * At this point, we could verify that 'check' tells us the key is
+         * fine or bail out.
+         *****/
+    }
+    else {
+        /* eeek, do cleanup here */
+        return 3;
+    }
+    libssh2_knownhost_free(nh);
+
+    if ( strlen(password) != 0 ) {
+        /* We could authenticate via password */
+        while ((rc = libssh2_userauth_password(session, username, password)) ==
+               LIBSSH2_ERROR_EAGAIN);
+        if (rc) {
+            fprintf(stderr, "Authentication by password failed.\n");
+            goto shutdown;
+        }
+    }
+    else {
+        /* Or by public key */
+        while ((rc = libssh2_userauth_publickey_fromfile(session, username,
+                                                         "/home/user/"
+                                                         ".ssh/id_rsa.pub",
+                                                         "/home/user/"
+                                                         ".ssh/id_rsa",
+                                                         password)) ==
+               LIBSSH2_ERROR_EAGAIN);
+        if (rc) {
+            fprintf(stderr, "\tAuthentication by public key failed\n");
+            goto shutdown;
+        }
+    }
+
+#if 0
+    libssh2_trace(session, ~0 );
+#endif
+
+    /* Exec non-blocking on the remove host */
+    while( (channel = libssh2_channel_open_session(session)) == NULL &&
+           libssh2_session_last_error(session,NULL,NULL,0) ==
+           LIBSSH2_ERROR_EAGAIN )
+    {
+        waitsocket(sock, session);
+    }
+    if( channel == NULL )
+    {
+        fprintf(stderr,"Error\n");
+        exit( 1 );
+    }
+    while( (rc = libssh2_channel_request_auth_agent(channel)) ==
+           LIBSSH2_ERROR_EAGAIN )
+    {
+        waitsocket(sock, session);
+    }
+    if ( rc != 0 )
+    {
+        fprintf(stderr, "Error, couldn't request auth agent.\n");
+        exit( 1 );
+    }
+    while( (rc = libssh2_channel_exec(channel, commandline)) ==
+           LIBSSH2_ERROR_EAGAIN )
+    {
+        waitsocket(sock, session);
+    }
+    if( rc != 0 )
+    {
+        fprintf(stderr,"Error\n");
+        exit( 1 );
+    }
+    for( ;; )
+    {
+        /* loop until we block */
+        int rc;
+        do
+        {
+            char buffer[0x4000];
+            rc = libssh2_channel_read( channel, buffer, sizeof(buffer) );
+            if( rc > 0 )
+            {
+                int i;
+                bytecount += rc;
+                fprintf(stderr, "We read:\n");
+                for( i=0; i < rc; ++i )
+                    fputc( buffer[i], stderr);
+                fprintf(stderr, "\n");
+            }
+            else {
+                if( rc != LIBSSH2_ERROR_EAGAIN )
+                    /* no need to output this for the EAGAIN case */
+                    fprintf(stderr, "libssh2_channel_read returned %d\n", rc);
+            }
+        }
+        while( rc > 0 );
+
+        /* this is due to blocking that would occur otherwise so we loop on
+           this condition */
+        if( rc == LIBSSH2_ERROR_EAGAIN )
+        {
+            waitsocket(sock, session);
+        }
+        else
+            break;
+    }
+    exitcode = 127;
+    while( (rc = libssh2_channel_close(channel)) == LIBSSH2_ERROR_EAGAIN )
+        waitsocket(sock, session);
+
+    if( rc == 0 )
+    {
+        exitcode = libssh2_channel_get_exit_status( channel );
+        libssh2_channel_get_exit_signal(channel, &exitsignal,
+                                        NULL, NULL, NULL, NULL, NULL);
+    }
+
+    if (exitsignal)
+        printf("\nGot signal: %s\n", exitsignal);
+    else
+        printf("\nEXIT: %d bytecount: %d\n", exitcode, bytecount);
+
+    libssh2_channel_free(channel);
+    channel = NULL;
+
+shutdown:
+
+    libssh2_session_disconnect(session,
+                               "Normal Shutdown, Thank you for playing");
+    libssh2_session_free(session);
+
+#ifdef WIN32
+    closesocket(sock);
+#else
+    close(sock);
+#endif
+    fprintf(stderr, "all done\n");
+
+    libssh2_exit();
+
+    return 0;
+}
diff --git a/include/libssh2.h b/include/libssh2.h
index 5a22be2..b50a001 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -638,6 +638,8 @@ LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel,
  libssh2_channel_setenv_ex((channel), (varname), strlen(varname), (value), \
                            strlen(value))
 
+LIBSSH2_API int libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel);
+
 LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel,
                                                const char *term,
                                                unsigned int term_len,
diff --git a/src/channel.c b/src/channel.c
index 9e29492..54910ef 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -973,6 +973,158 @@ static int channel_request_pty(LIBSSH2_CHANNEL *channel,
                           "Unable to complete request for channel request-pty");
 }
 
+/**
+ * channel_request_auth_agent
+ * The actual re-entrant method which requests an auth agent.
+ * */
+static int channel_request_auth_agent(LIBSSH2_CHANNEL *channel,
+                                      const char *request_str,
+                                      int request_str_len)
+{
+    LIBSSH2_SESSION *session = channel->session;
+    unsigned char *s;
+    static const unsigned char reply_codes[3] =
+        { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 };
+    int rc;
+
+    if (channel->req_auth_agent_state == libssh2_NB_state_idle) {
+        /* Only valid options are "auth-agent-req" and
+         * "auth-agent-req_at_openssh.com" so we make sure it is not
+         * actually longer than the longest possible. */
+        if(request_str_len > 26) {
+            return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
+                                  "request_str length too large");
+        }
+
+        /*
+         *  Length: 24 or 36 = packet_type(1) + channel(4) + req_len(4) +
+         *    request_str (variable) + want_reply (1) */
+        channel->req_auth_agent_packet_len = 10 + request_str_len;
+
+        /* Zero out the requireev state to reset */
+        memset(&channel->req_auth_agent_requirev_state, 0,
+               sizeof(channel->req_auth_agent_requirev_state));
+
+        _libssh2_debug(session, LIBSSH2_TRACE_CONN,
+                       "Requesting auth agent on channel %lu/%lu",
+                       channel->local.id, channel->remote.id);
+
+        /*
+         *  byte      SSH_MSG_CHANNEL_REQUEST
+         *  uint32    recipient channel
+         *  string    "auth-agent-req"
+         *  boolean   want reply
+         * */
+        s = channel->req_auth_agent_packet;
+        *(s++) = SSH_MSG_CHANNEL_REQUEST;
+        _libssh2_store_u32(&s, channel->remote.id);
+        _libssh2_store_str(&s, (char *)request_str, request_str_len);
+        *(s++) = 0x01;
+
+        channel->req_auth_agent_state = libssh2_NB_state_created;
+    }
+
+    if (channel->req_auth_agent_state == libssh2_NB_state_created) {
+        /* Send the packet, we can use sizeof() on the packet because it
+         * is always completely filled; there are no variable length fields. */
+        rc = _libssh2_transport_send(session, channel->req_auth_agent_packet,
+                                     channel->req_auth_agent_packet_len,
+                                     NULL, 0);
+
+        if (rc == LIBSSH2_ERROR_EAGAIN) {
+            _libssh2_error(session, rc,
+                           "Would block sending auth-agent request");
+        } else if (rc) {
+            channel->req_auth_agent_state = libssh2_NB_state_idle;
+            return _libssh2_error(session, rc,
+                                  "Unable to send auth-agent request");
+        }
+
+        _libssh2_htonu32(channel->req_auth_agent_local_channel,
+                         channel->local.id);
+
+        channel->req_auth_agent_state = libssh2_NB_state_sent;
+    }
+
+    if (channel->req_auth_agent_state == libssh2_NB_state_sent) {
+        unsigned char *data;
+        size_t data_len;
+        unsigned char code;
+
+        rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len,
+                                      1, channel->req_auth_agent_local_channel,
+                                      4, &channel->req_auth_agent_requirev_state);
+
+        if (rc == LIBSSH2_ERROR_EAGAIN) {
+            return rc;
+        } else if (rc) {
+            channel->req_auth_agent_state = libssh2_NB_state_idle;
+            return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
+                                  "Failed to request auth-agent");
+        }
+
+        code = data[0];
+
+        LIBSSH2_FREE(session, data);
+        channel->req_auth_agent_state = libssh2_NB_state_idle;
+
+        if (code == SSH_MSG_CHANNEL_SUCCESS)
+            return 0;
+    }
+
+    return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED,
+                          "Unable to complete request for auth-agent");
+}
+
+/*
+ * libssh2_channel_request_auth_agent
+ * Requests that agent forwarding be enabled for the session. The
+ * request must be sent over a specific channel, which starts the agent
+ * listener on the remote side. Once the channel is closed, the agent
+ * listener continues to exist.
+ * */
+LIBSSH2_API int
+libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel)
+{
+    int rc;
+
+    if (!channel)
+        return LIBSSH2_ERROR_BAD_USE;
+
+    /* The current RFC draft for agent forwarding says you're supposed to
+     * send "auth-agent-req," but most SSH servers out there right now
+     * actually expect "auth-agent-req_at_openssh.com," so we try that
+     * first. */
+    if (channel->req_auth_agent_try_state == libssh2_NB_state_idle) {
+        BLOCK_ADJUST(rc, channel->session,
+                     channel_request_auth_agent(channel,
+                                                "auth-agent-req_at_openssh.com",
+                                                26));
+
+        /* If we failed (but not with EAGAIN), then we move onto
+         * the next step to try another request type. */
+        if (rc != 0 && rc != LIBSSH2_ERROR_EAGAIN)
+            channel->req_auth_agent_try_state = libssh2_NB_state_sent;
+    }
+
+    if (channel->req_auth_agent_try_state == libssh2_NB_state_sent) {
+        BLOCK_ADJUST(rc, channel->session,
+                     channel_request_auth_agent(channel,
+                                                "auth-agent-req", 14));
+
+        /* If we failed without an EAGAIN, then move on with this
+         * state machine. */
+        if (rc != 0 && rc != LIBSSH2_ERROR_EAGAIN)
+            channel->req_auth_agent_try_state = libssh2_NB_state_sent1;
+    }
+
+    /* If things are good, reset the try state. */
+    if (rc == 0)
+        channel->req_auth_agent_try_state = libssh2_NB_state_idle;
+
+    return rc;
+}
+
 /*
  * libssh2_channel_request_pty_ex
  * Duh... Request a PTY
diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h
index c670a16..1f4da40 100644
--- a/src/libssh2_priv.h
+++ b/src/libssh2_priv.h
@@ -428,6 +428,13 @@ struct _LIBSSH2_CHANNEL
     /* State variables used in libssh2_channel_handle_extended_data2() */
     libssh2_nonblocking_states extData2_state;
 
+    /* State variables used in libssh2_channel_request_auth_agent() */
+    libssh2_nonblocking_states req_auth_agent_try_state;
+    libssh2_nonblocking_states req_auth_agent_state;
+    unsigned char req_auth_agent_packet[36];
+    size_t req_auth_agent_packet_len;
+    unsigned char req_auth_agent_local_channel[4];
+    packet_requirev_state_t req_auth_agent_requirev_state;
 };
 
 struct _LIBSSH2_LISTENER
-- 
1.7.8.4
_______________________________________________
libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-devel
Received on 2012-03-17