1
1

Add agent forwarding implementation (#219)

files: channel.c, test_agent_forward_succeeds.c, libssh2_priv.h, libssh2.h, ssh2_agent_forwarding.c

notes:
* Adding SSH agent forwarding.
* Fix agent forwarding message, updated example.
Added integration test code and cmake target. Added example to cmake list.

credit: 
pkittenis
Этот коммит содержится в:
Panos 2019-08-12 22:23:19 +01:00 коммит произвёл Will Cosgrove
родитель b0bf6602b3
Коммит 378ffa9ba8
10 изменённых файлов: 531 добавлений и 2 удалений

22
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.

1
example/.gitignore поставляемый
Просмотреть файл

@ -20,6 +20,7 @@ sftp_write_nonblock
config.h.in config.h.in
ssh2_exec ssh2_exec
ssh2_agent ssh2_agent
ssh2_agent_forwarding
libssh2_config.h libssh2_config.h
libssh2_config.h.in libssh2_config.h.in
stamp-h2 stamp-h2

Просмотреть файл

@ -57,6 +57,7 @@ set(EXAMPLES
sftpdir_nonblock sftpdir_nonblock
ssh2_exec ssh2_exec
ssh2_agent ssh2_agent
ssh2_agent_forwarding
ssh2_echo ssh2_echo
sftp_append sftp_append
subsystem_netconf subsystem_netconf

Просмотреть файл

@ -6,8 +6,8 @@ EXTRA_DIST = libssh2_config.h.in libssh2_config_cmake.h.in CMakeLists.txt
noinst_PROGRAMS = direct_tcpip ssh2 scp scp_nonblock scp_write \ noinst_PROGRAMS = direct_tcpip ssh2 scp scp_nonblock scp_write \
scp_write_nonblock sftp sftp_nonblock sftp_write sftp_write_nonblock \ scp_write_nonblock sftp sftp_nonblock sftp_write sftp_write_nonblock \
sftp_mkdir sftp_mkdir_nonblock sftp_RW_nonblock sftp_write_sliding \ sftp_mkdir sftp_mkdir_nonblock sftp_RW_nonblock sftp_write_sliding \
sftpdir sftpdir_nonblock ssh2_exec ssh2_agent ssh2_echo sftp_append \ sftpdir sftpdir_nonblock ssh2_exec ssh2_agent ssh2_agent_forwarding \
subsystem_netconf tcpip-forward ssh2_echo sftp_append subsystem_netconf tcpip-forward
if HAVE_SYS_UN_H if HAVE_SYS_UN_H
noinst_PROGRAMS += x11 noinst_PROGRAMS += x11

292
example/ssh2_agent_forwarding.c Обычный файл
Просмотреть файл

@ -0,0 +1,292 @@
/*
* 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 example uses agent authentication to ensure an agent to forward
* is running.
*
* Run it like this:
*
* $ ./ssh2_agent_forwarding 127.0.0.1 user "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
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#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 = NULL;
unsigned long hostaddr;
int sock;
struct sockaddr_in sin;
LIBSSH2_SESSION *session;
LIBSSH2_CHANNEL *channel;
LIBSSH2_AGENT *agent = NULL;
struct libssh2_agent_publickey *identity, *prev_identity = NULL;
int rc;
int exitcode;
char *exitsignal = (char *)"none";
int bytecount = 0;
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(2, 0), &wsadata);
#endif
if(argc < 2) {
fprintf(stderr, "At least IP and username arguments are required.\n");
return 1;
}
/* must be ip address only */
hostname = argv[1];
username = argv[2];
if(argc > 3) {
commandline = argv[3];
}
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;
if(libssh2_session_handshake(session, sock) != 0) {
fprintf(stderr, "Failure establishing SSH session: %d\n", rc);
return -1;
}
/* Connect to the ssh-agent */
agent = libssh2_agent_init(session);
if(!agent) {
fprintf(stderr, "Failure initializing ssh-agent support\n");
rc = 1;
goto shutdown;
}
if(libssh2_agent_connect(agent)) {
fprintf(stderr, "Failure connecting to ssh-agent\n");
rc = 1;
goto shutdown;
}
if(libssh2_agent_list_identities(agent)) {
fprintf(stderr, "Failure requesting identities to ssh-agent\n");
rc = 1;
goto shutdown;
}
while(1) {
rc = libssh2_agent_get_identity(agent, &identity, prev_identity);
if(rc == 1)
break;
if(rc < 0) {
fprintf(stderr,
"Failure obtaining identity from ssh-agent support\n");
rc = 1;
goto shutdown;
}
if(libssh2_agent_userauth(agent, username, identity)) {
fprintf(stderr, "\tAuthentication with username %s and "
"public key %s failed!\n",
username, identity->comment);
}
else {
fprintf(stderr, "\tAuthentication with username %s and "
"public key %s succeeded!\n",
username, identity->comment);
break;
}
prev_identity = identity;
}
if(rc) {
fprintf(stderr, "Couldn't continue authentication\n");
goto shutdown;
}
#if 0
libssh2_trace(session, ~0);
#endif
/* Set session to non-blocking */
libssh2_session_set_blocking(session, 0);
/* 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, error code %d.\n",
rc);
exit(1);
}
else {
fprintf(stdout, "\tAgent forwarding request succeeded!\n");
}
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;
}

Просмотреть файл

@ -761,6 +761,8 @@ LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel,
(unsigned int)strlen(varname), (value), \ (unsigned int)strlen(varname), (value), \
(unsigned int)strlen(value)) (unsigned int)strlen(value))
LIBSSH2_API int libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel);
LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel,
const char *term, const char *term,
unsigned int term_len, unsigned int term_len,

Просмотреть файл

@ -1022,6 +1022,158 @@ static int channel_request_pty(LIBSSH2_CHANNEL *channel,
"channel request-pty"); "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@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@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 * libssh2_channel_request_pty_ex
* Duh... Request a PTY * Duh... Request a PTY

Просмотреть файл

@ -452,6 +452,13 @@ struct _LIBSSH2_CHANNEL
/* State variables used in libssh2_channel_handle_extended_data2() */ /* State variables used in libssh2_channel_handle_extended_data2() */
libssh2_nonblocking_states extData2_state; 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 struct _LIBSSH2_LISTENER

Просмотреть файл

@ -118,6 +118,7 @@ set(TESTS
public_key_auth_succeeds_with_correct_encrypted_rsa_key public_key_auth_succeeds_with_correct_encrypted_rsa_key
keyboard_interactive_auth_fails_with_wrong_response keyboard_interactive_auth_fails_with_wrong_response
keyboard_interactive_auth_succeeds_with_correct_response keyboard_interactive_auth_succeeds_with_correct_response
agent_forward_succeeds
) )
if(CRYPTO_BACKEND STREQUAL "OpenSSL") if(CRYPTO_BACKEND STREQUAL "OpenSSL")

51
tests/test_agent_forward_succeeds.c Обычный файл
Просмотреть файл

@ -0,0 +1,51 @@
#include "session_fixture.h"
#include <libssh2.h>
#include <stdio.h>
const char *USERNAME = "libssh2"; /* configured in Dockerfile */
const char *KEY_FILE_PRIVATE = "key_rsa";
const char *KEY_FILE_PUBLIC = "key_rsa.pub"; /* configured in Dockerfile */
int test(LIBSSH2_SESSION *session)
{
int rc;
LIBSSH2_CHANNEL *channel;
const char *userauth_list =
libssh2_userauth_list(session, USERNAME, strlen(USERNAME));
if (userauth_list == NULL) {
print_last_session_error("libssh2_userauth_list");
return 1;
}
if (strstr(userauth_list, "publickey") == NULL) {
fprintf(stderr, "'publickey' was expected in userauth list: %s\n",
userauth_list);
return 1;
}
rc = libssh2_userauth_publickey_fromfile_ex(
session, USERNAME, strlen(USERNAME), KEY_FILE_PUBLIC, KEY_FILE_PRIVATE,
NULL);
if (rc != 0) {
print_last_session_error("libssh2_userauth_publickey_fromfile_ex");
return 1;
}
channel = libssh2_channel_open_session(session);
/* if (channel == NULL) { */
/* printf("Error opening channel\n"); */
/* return 1; */
/* } */
rc = libssh2_channel_request_auth_agent(channel);
if (rc != 0) {
fprintf(stderr, "Auth agent request for agent forwarding failed, error code %d\n",
rc);
return 1;
}
return 0;
}