diff --git a/include/libssh2.h b/include/libssh2.h index 551a29dc..63c44c88 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -146,6 +146,7 @@ #define LIBSSH2_DISCONNECT_FUNC(name) void name(LIBSSH2_SESSION *session, int reason, const char *message, int message_len, const char *language, int language_len, void **abstract) #define LIBSSH2_PASSWD_CHANGEREQ_FUNC(name) void name(LIBSSH2_SESSION *session, char **newpw, int *newpw_len, void **abstract) #define LIBSSH2_MACERROR_FUNC(name) int name(LIBSSH2_SESSION *session, const char *packet, int packet_len, void **abstract) +#define LIBSSH2_X11_OPEN_FUNC(name) void name(LIBSSH2_SESSION *session, LIBSSH2_CHANNEL *channel, char *shost, int sport, void **abstract) #define LIBSSH2_CHANNEL_CLOSE_FUNC(name) void name(LIBSSH2_SESSION *session, void **session_abstract, LIBSSH2_CHANNEL *channel, void **channel_abstract) @@ -154,6 +155,7 @@ #define LIBSSH2_CALLBACK_DEBUG 1 #define LIBSSH2_CALLBACK_DISCONNECT 2 #define LIBSSH2_CALLBACK_MACERROR 3 +#define LIBSSH2_CALLBACK_X11 4 /* libssh2_session_method_pref() constants */ #define LIBSSH2_METHOD_KEX 0 @@ -288,6 +290,9 @@ LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varnam LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, int term_len, char *modes, int modes_len, int width, int height, int width_px, int height_px); #define libssh2_channel_request_pty(channel, term) libssh2_channel_request_pty_ex((channel), (term), strlen(term), NULL, 0, LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT, LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX) +LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, char *auth_proto, char *auth_cookie, int screen_number); +#define libssh2_channel_x11_req(channel, screen_number) libssh2_channel_x11_req_ex((channel), 0, NULL, NULL, (screen_number)) + LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, char *request, int request_len, char *message, int message_len); #define libssh2_channel_shell(channel) libssh2_channel_process_startup((channel), "shell", sizeof("shell") - 1, NULL, 0) #define libssh2_channel_exec(channel, command) libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, (command), strlen(command)) diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h index 50bff0a1..6ed2587e 100644 --- a/include/libssh2_priv.h +++ b/include/libssh2_priv.h @@ -58,6 +58,7 @@ session->ssh_msg_disconnect((session), (reason), (message), (message_len), (language), (language_len), &(session)->abstract) #define LIBSSH2_MACERROR(session, data, datalen) session->macerror((session), (data), (datalen), &(session)->abstract) +#define LIBSSH2_X11_OPEN(channel, shost, sport) channel->session->x11(((channel)->session), (channel), (shost), (sport), (&(channel)->session->abstract)) #define LIBSSH2_CHANNEL_CLOSE(session, channel) channel->close_cb((session), &(session)->abstract, (channel), &(channel)->abstract) @@ -173,6 +174,7 @@ struct _LIBSSH2_SESSION { LIBSSH2_DEBUG_FUNC((*ssh_msg_debug)); LIBSSH2_DISCONNECT_FUNC((*ssh_msg_disconnect)); LIBSSH2_MACERROR_FUNC((*macerror)); + LIBSSH2_X11_OPEN_FUNC((*x11)); /* Method preferences -- NULL yields "load order" */ char *kex_prefs; diff --git a/src/channel.c b/src/channel.c index 669b2d31..e7394cff 100644 --- a/src/channel.c +++ b/src/channel.c @@ -36,6 +36,7 @@ */ #include "libssh2_priv.h" +#include #ifndef WIN32 #include #endif @@ -576,6 +577,77 @@ LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *t } /* }}} */ +/* {{{ libssh2_channel_x11_req_ex + * Request X11 forwarding + */ +LIBSSH2_API int libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, char *auth_proto, char *auth_cookie, int screen_number) +{ + LIBSSH2_SESSION *session = channel->session; + unsigned char *s, *packet; + unsigned long proto_len = auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1); + unsigned long cookie_len = auth_cookie ? strlen(auth_cookie) : 32; + unsigned long packet_len = proto_len + cookie_len + 41; /* packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) + want_reply(1) + + single_cnx(4) + proto_len(4) + cookie_len(4) + screen_num(4) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0); + return -1; + } + + *(s++) = SSH_MSG_CHANNEL_REQUEST; + libssh2_htonu32(s, channel->remote.id); s += 4; + libssh2_htonu32(s, sizeof("x11-req") - 1); s += 4; + memcpy(s, "x11-req", sizeof("x11-req") - 1); s += sizeof("x11-req") - 1; + + *(s++) = 0xFF; /* want_reply */ + *(s++) = single_connection ? 0xFF : 0x00; + + libssh2_htonu32(s, proto_len); s += 4; + memcpy(s, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", proto_len); + s += proto_len; + + libssh2_htonu32(s, cookie_len); + if (auth_cookie) { + memcpy(s, auth_cookie, cookie_len); + } else { + RAND_bytes(s, cookie_len); + } + s += cookie_len; + + libssh2_htonu32(s, screen_number); s += 4; + + if (libssh2_packet_write(session, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + while (1) { + unsigned char *data; + unsigned long data_len; + unsigned char local_channel[4]; + + libssh2_htonu32(local_channel, channel->local.id); + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_SUCCESS, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + return 0; + } + + if (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_FAILURE, &data, &data_len, 1, local_channel, 4, 1) == 0) { + LIBSSH2_FREE(session, data); + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel x11-req", 0); + return -1; + } + } + + /* Never reached, just giving the compiler something to not complain about */ + return -1; +} +/* }}} */ + /* {{{ libssh2_channel_process_startup * Primitive for libssh2_channel_(shell|exec|subsystem) */ diff --git a/src/packet.c b/src/packet.c index fe42d4e3..ebd256a9 100644 --- a/src/packet.c +++ b/src/packet.c @@ -99,7 +99,7 @@ inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session, unsigned char channel->session = session; channel->channel_type_len = sizeof("forwarded-tcpip") - 1; channel->channel_type = LIBSSH2_ALLOC(session, channel->channel_type_len + 1); - if (!channel) { + if (!channel->channel_type) { 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 */ @@ -169,6 +169,105 @@ inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session, unsigned char } /* }}} */ +/* {{{ libssh2_packet_x11_open + * Accept a forwarded X11 connection + */ +inline int libssh2_packet_x11_open(LIBSSH2_SESSION *session, unsigned char *data, unsigned long datalen) +{ + int failure_code = 2; /* SSH_OPEN_CONNECT_FAILED */ + unsigned char *s = data + (sizeof("x11") - 1) + 5; + unsigned long packet_len = 17 + (sizeof("X11 Forward Unavailable") - 1); + unsigned char *p, packet[17 + (sizeof("X11 Forward Unavailable") - 1)]; + /* packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + LIBSSH2_CHANNEL *channel; + unsigned long sender_channel, initial_window_size, packet_size; + unsigned char *shost; + unsigned long sport, shost_len; + + sender_channel = libssh2_ntohu32(s); s += 4; + initial_window_size = libssh2_ntohu32(s); s += 4; + packet_size = libssh2_ntohu32(s); s += 4; + shost_len = libssh2_ntohu32(s); s += 4; + shost = s; s += shost_len; + sport = libssh2_ntohu32(s); s += 4; + + if (session->x11) { + 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 */ + goto x11_exit; + } + memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); + + channel->session = session; + channel->channel_type_len = sizeof("x11") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, channel->channel_type_len + 1); + if (!channel->channel_type) { + 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 */ + goto x11_exit; + } + memcpy(channel->channel_type, "x11", 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 session */ + if (session->channels.tail) { + session->channels.tail->next = channel; + channel->prev = session->channels.tail; + } else { + session->channels.head = channel; + channel->prev = NULL; + } + channel->next = NULL; + session->channels.tail = channel; + + /* Pass control to the callback, they may turn right around and free the channel, or actually use it */ + LIBSSH2_X11_OPEN(channel, shost, sport); + + return 0; + } else { + failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */ + } + + x11_exit: + 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("X11 Forward Unavailable") - 1); p += 4; + memcpy(s, "X11 Forward Unavailable", sizeof("X11 Forward Unavailable") - 1); p += sizeof("X11 Forward Unavailable") - 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 @@ -367,6 +466,14 @@ static int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, siz LIBSSH2_FREE(session, data); return retval; } + if ((datalen >= (sizeof("x11") + 4)) && + ((sizeof("x11")-1) == libssh2_ntohu32(data + 1)) && + (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { + int retval = libssh2_packet_x11_open(session, data, datalen); + + LIBSSH2_FREE(session, data); + return retval; + } break; } diff --git a/src/session.c b/src/session.c index 0c016d63..341b3325 100644 --- a/src/session.c +++ b/src/session.c @@ -222,6 +222,11 @@ LIBSSH2_API void* libssh2_session_callback_set(LIBSSH2_SESSION *session, int cbt session->macerror = callback; return oldcb; break; + case LIBSSH2_CALLBACK_X11: + oldcb = session->x11; + session->x11 = callback; + return oldcb; + break; } return NULL;