diff --git a/README b/README index 7697ec7..0aac8c7 100644 --- a/README +++ b/README @@ -8,6 +8,8 @@ Version 0.5 Added DESTDIR support to makefiles (Adam Gołębiowski -- I hope that character set translates right) + Added libssh2_channel_forward_listen_ex(), libssh2_channel_forward_cancel(), and libssh2_channel_forward_accept(). + Version 0.4 ----------- diff --git a/include/libssh2.h b/include/libssh2.h index 4d774f7..b3c51f9 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -159,6 +159,7 @@ typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; +typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; #ifdef WIN_32 #define LIBSSH2_API __declspec(dllexport) @@ -218,6 +219,7 @@ typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; #define LIBSSH2_ERROR_ZLIB -29 #define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 #define LIBSSH2_ERROR_SFTP_PROTOCOL -31 +#define LIBSSH2_ERROR_REQUEST_DENIED -32 /* Session API */ LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); @@ -268,9 +270,17 @@ LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, char *channel_type, int channel_type_len, int window_size, int packet_size, char *message, int message_len); #define libssh2_channel_open_session(session) libssh2_channel_open_ex((session), "session", sizeof("session") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0) + LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, char *host, int port, char *shost, int sport); #define libssh2_channel_direct_tcpip(session, host, port) libssh2_channel_direct_tcpip_ex((session), (host), (port), "127.0.0.1", 22) +LIBSSH2_API LIBSSH2_LISTENER *libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, char *host, int port, int *bound_port, int queue_maxsize); +#define libssh2_channel_forward_listen(session, port) libssh2_channel_forward_listen_ex((session), NULL, (port), NULL, 16) + +LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener); + +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener); + LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, int varname_len, char *value, int value_len); #define libssh2_channel_setenv(channel, varname, value) libssh2_channel_setenv_ex((channel), (varname), strlen(varname), (value), strlen(value)) diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h index 2bc23a6..5138ba3 100644 --- a/include/libssh2_priv.h +++ b/include/libssh2_priv.h @@ -123,6 +123,19 @@ struct _LIBSSH2_CHANNEL_BRIGADE { LIBSSH2_CHANNEL *head, *tail; }; +struct _LIBSSH2_LISTENER { + LIBSSH2_SESSION *session; + + char *host; + int port; + + LIBSSH2_CHANNEL *queue; + int queue_size; + int queue_maxsize; + + LIBSSH2_LISTENER *prev, *next; +}; + typedef struct _libssh2_endpoint_data { unsigned char *banner; @@ -202,6 +215,8 @@ struct _LIBSSH2_SESSION { LIBSSH2_CHANNEL_BRIGADE channels; unsigned long next_channel; + LIBSSH2_LISTENER *listeners; + /* Actual I/O socket */ int socket_fd; int socket_block; @@ -373,6 +388,7 @@ int libssh2_packet_require_ex(LIBSSH2_SESSION *session, unsigned char packet_typ libssh2_packet_require_ex((session), (packet_type), (data), (data_len), 0, NULL, 0) int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data, unsigned long data_len); int libssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange); +unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session); LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id); /* Let crypt.c/hostkey.c/comp.c/mac.c expose their method structs */ diff --git a/src/channel.c b/src/channel.c index 0a48cf0..1a1d76b 100644 --- a/src/channel.c +++ b/src/channel.c @@ -41,7 +41,7 @@ /* {{{ libssh2_channel_nextid * Determine the next channel ID we can use at our end */ -static unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session) +unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session) { unsigned long id = session->next_channel; LIBSSH2_CHANNEL *channel; @@ -257,6 +257,191 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *se } /* }}} */ +/* {{{ libssh2_channel_forward_listen_ex + * Bind a port on the remote host and listen for connections + */ +LIBSSH2_API LIBSSH2_LISTENER *libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, char *host, int port, int *bound_port, int queue_maxsize) +{ + unsigned char *packet, *s; + unsigned long host_len = (host ? strlen(host) : (sizeof("0.0.0.0") - 1)); + unsigned long packet_len = host_len + (sizeof("tcpip-forward") - 1) + 14; + /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); + return NULL; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + libssh2_htonu32(s, sizeof("tcpip-forward") - 1); s += 4; + memcpy(s, "tcpip-forward", sizeof("tcpip-forward") - 1); s += sizeof("tcpip-forward") - 1; + *(s++) = 0xFF; /* want_reply */ + + libssh2_htonu32(s, host_len); s += 4; + memcpy(s, host ? host : "0.0.0.0", host_len); s += host_len; + libssh2_htonu32(s, port); s += 4; + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); + LIBSSH2_FREE(session, packet); + return NULL; + } + LIBSSH2_FREE(session, packet); + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + unsigned char *data; + unsigned long data_len; + + if (libssh2_packet_ask(session, SSH_MSG_REQUEST_SUCCESS, &data, &data_len, 1) == 0) { + LIBSSH2_LISTENER *listener; + + listener = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_LISTENER)); + if (!listener) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0); + LIBSSH2_FREE(session, data); + return NULL; + } + memset(listener, 0, sizeof(LIBSSH2_LISTENER)); + listener->session = session; + listener->host = LIBSSH2_ALLOC(session, host_len + 1); + if (!listener->host) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0); + LIBSSH2_FREE(session, listener); + LIBSSH2_FREE(session, data); + return NULL; + } + memcpy(listener->host, host ? host : "0.0.0.0", host_len); + listener->host[host_len] = 0; + if (data_len >= 5 && !port) { + listener->port = libssh2_ntohu32(data + 1); + } else { + listener->port = port; + } + + listener->queue_size = 0; + listener->queue_maxsize = queue_maxsize; + + listener->next = session->listeners; + listener->prev = NULL; + if (session->listeners) { + session->listeners->prev = listener; + } + session->listeners = listener; + + if (bound_port) { + *bound_port = listener->port; + } + + LIBSSH2_FREE(session, data); + return listener; + } + + if (libssh2_packet_ask(session, SSH_MSG_REQUEST_FAILURE, &data, &data_len, 0) == 0) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_REQUEST_DENIED, "Unable to complete request for forward-listen", 0); + return NULL; + } + } + + return NULL; +} +/* }}} */ + +/* {{{ libssh2_channel_forward_cancel + * Stop listening on a remote port and free the listener + * Toss out any pending (un-accept()ed) connections + */ +LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) +{ + LIBSSH2_SESSION *session = listener->session; + LIBSSH2_CHANNEL *queued = listener->queue; + unsigned char *packet, *s; + unsigned long host_len = strlen(listener->host); + unsigned long packet_len = host_len + 14 + sizeof("cancel-tcpip-forward") - 1; + /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0); + return -1; + } + + *(s++) = SSH_MSG_GLOBAL_REQUEST; + libssh2_htonu32(s, sizeof("cancel-tcpip-forward") - 1); s += 4; + memcpy(s, "cancel-tcpip-forward", sizeof("cancel-tcpip-forward") - 1); s += sizeof("cancel-tcpip-forward") - 1; + *(s++) = 0x00; /* want_reply */ + + libssh2_htonu32(s, host_len); s += 4; + memcpy(s, listener->host, host_len); s += host_len; + libssh2_htonu32(s, listener->port); s += 4; + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + while (queued) { + LIBSSH2_CHANNEL *next = queued->next; + + libssh2_channel_free(queued); + queued = next; + } + LIBSSH2_FREE(session, listener->host); + + if (listener->next) { + listener->next->prev = listener->prev; + } + if (listener->prev) { + listener->prev->next = listener->next; + } else { + session->listeners = listener->next; + } + + LIBSSH2_FREE(session, listener); + + return 0; +} +/* }}} */ + +/* {{{ libssh2_channel_forward_accept + * Accept a connection + */ +LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) +{ + while (libssh2_packet_read(listener->session, 0) > 0); + + if (listener->queue) { + LIBSSH2_SESSION *session = listener->session; + LIBSSH2_CHANNEL *channel; + + channel = listener->queue; + + listener->queue = listener->queue->next; + if (listener->queue) { + listener->queue->prev = NULL; + } + + channel->prev = NULL; + channel->next = session->channels.head; + session->channels.head = channel; + + if (channel->next) { + channel->next->prev = channel; + } else { + session->channels.tail = channel; + } + listener->queue_size--; + + return channel; + } + + return NULL; +} +/* }}} */ + /* {{{ libssh2_channel_setenv_ex * Set an environment variable prior to requesting a shell/program/subsystem */ diff --git a/src/packet.c b/src/packet.c index 0c3aa40..75057d7 100644 --- a/src/packet.c +++ b/src/packet.c @@ -42,6 +42,132 @@ #include #include + +/* {{{ libssh2_packet_queue_listener + * Queue a connection request for a listener + */ +inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen) +{ + /* Look for a matching listener */ + unsigned char *s = data + (sizeof("forwarded-tcpip") - 1) + 5; + unsigned long packet_len = 17 + (sizeof("Forward not requested") - 1); + unsigned char *p, packet[17 + (sizeof("Forward not requested") - 1)]; + /* packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + LIBSSH2_LISTENER *l = session->listeners; + char failure_code = 1; /* SSH_OPEN_ADMINISTRATIVELY_PROHIBITED */ + unsigned long sender_channel, initial_window_size, packet_size; + unsigned char *host, *shost; + unsigned long port, sport, host_len, shost_len; + + sender_channel = libssh2_ntohu32(s); s += 4; + + initial_window_size = libssh2_ntohu32(s); s += 4; + packet_size = libssh2_ntohu32(s); s += 4; + + host_len = libssh2_ntohu32(s); s += 4; + host = s; s += host_len; + port = libssh2_ntohu32(s); s += 4; + + shost_len = libssh2_ntohu32(s); s += 4; + shost = s; s += shost_len; + sport = libssh2_ntohu32(s); s += 4; + + while (l) { + if ((l->port == port) && + (strlen(l->host) == host_len) && + (memcmp(l->host, host, host_len) == 0)) { + /* This is our listener */ + LIBSSH2_CHANNEL *channel, *last_queued = l->queue; + + if (l->queue_maxsize && + (l->queue_maxsize <= l->queue_size)) { + /* Queue is full */ + failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ + break; + } + + channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); + failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ + break; + } + memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); + + channel->session = session; + channel->channel_type_len = sizeof("forwarded-tcpip") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, channel->channel_type_len + 1); + if (!channel) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0); + LIBSSH2_FREE(session, channel); + failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ + break; + } + memcpy(channel->channel_type, "forwarded-tcpip", channel->channel_type_len + 1); + + channel->remote.id = sender_channel; + channel->remote.window_size_initial = LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = libssh2_channel_nextid(session); + channel->local.window_size_initial = initial_window_size; + channel->local.window_size = initial_window_size; + channel->local.packet_size = packet_size; + + p = packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + libssh2_htonu32(p, channel->remote.id); p += 4; + libssh2_htonu32(p, channel->local.id); p += 4; + libssh2_htonu32(p, channel->remote.window_size_initial); p += 4; + libssh2_htonu32(p, channel->remote.packet_size); p += 4; + + if (libssh2_packet_write(session, packet, 17)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel open confirmation", 0); + return -1; + } + + /* Link the channel into the end of the queue list */ + + if (!last_queued) { + l->queue = channel; + return 0; + } + + while (last_queued->next) last_queued = last_queued->next; + + last_queued->next = channel; + channel->prev = last_queued; + + l->queue_size++; + + return 0; + } + + l = l->next; + } + + /* We're not listening to you */ + { + + p = packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + libssh2_htonu32(p, sender_channel); p += 4; + libssh2_htonu32(p, failure_code); p += 4; + libssh2_htonu32(p, sizeof("Forward not requested") - 1); p += 4; + memcpy(s, "Forward not requested", sizeof("Forward not requested") - 1); p += sizeof("Forward not requested") - 1; + libssh2_htonu32(p, 0); + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send open failure", 0); + return -1; + } + return 0; + } +} +/* }}} */ + + /* {{{ libssh2_packet_new * Create a new packet and attach it to the brigade */ @@ -230,6 +356,16 @@ static int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, siz return 0; } break; + case SSH_MSG_CHANNEL_OPEN: + if ((datalen >= (sizeof("forwarded-tcpip") + 4)) && + ((sizeof("forwarded-tcpip")-1) == libssh2_ntohu32(data + 1)) && + (memcmp(data + 5, "forwarded-tcpip", sizeof("forwarded-tcpip") - 1) == 0)) { + int retval = libssh2_packet_queue_listener(session, data, datalen); + + LIBSSH2_FREE(session, data); + return retval; + } + break; } packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); diff --git a/src/session.c b/src/session.c index 58526b9..915aef7 100644 --- a/src/session.c +++ b/src/session.c @@ -315,6 +315,10 @@ LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session) } } + while (session->listeners) { + libssh2_channel_forward_cancel(session->listeners); + } + if (session->newkeys) { /* hostkey */ if (session->hostkey && session->hostkey->dtor) {