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