diff options
-rw-r--r-- | include/libssh/callbacks.h | 64 | ||||
-rw-r--r-- | include/libssh/messages.h | 2 | ||||
-rw-r--r-- | include/libssh/priv.h | 22 | ||||
-rw-r--r-- | include/libssh/session.h | 5 | ||||
-rw-r--r-- | libssh/channels.c | 308 | ||||
-rw-r--r-- | libssh/messages.c | 19 | ||||
-rw-r--r-- | libssh/packet.c | 316 | ||||
-rw-r--r-- | libssh/server.c | 4 | ||||
-rw-r--r-- | libssh/session.c | 41 | ||||
-rw-r--r-- | libssh/socket.c | 139 |
10 files changed, 686 insertions, 234 deletions
diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 83ac5eea..fdd20a5b 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -32,7 +32,21 @@ #ifdef __cplusplus extern "C" { #endif +typedef void (*ssh_callback_int) (void *user, int code); +/** @internal + * @brief callback for data received messages. + * @param user user-supplied pointer sent along with all callback messages + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_callback_data) (void *user, const void *data, size_t len); +typedef void (*ssh_callback_int_int) (void *user, int code, int errno_code); +typedef int (*ssh_message_callback) (ssh_session, void *user, ssh_message message); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, void *user, int code); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, void *user, int code, void *data, size_t len); /** * @brief SSH authentication callback. * @@ -68,9 +82,36 @@ struct ssh_callbacks_struct { * of connection steps completed. */ void (*connect_status_function)(void *userdata, float status); +/* To be cleaned up */ + ssh_callback_int connection_progress; + void *connection_progress_user; + ssh_channel_callback_int channel_write_confirm; + void *channel_write_confirm_user; + ssh_channel_callback_data channel_read_available; + void *channel_read_available_user; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/* This are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + ssh_callback_data data; + ssh_callback_int controlflow; + ssh_callback_int_int exception; + ssh_callback_int_int connected; + void *user; }; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; -typedef struct ssh_callbacks_struct * ssh_callbacks; +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK (1<<0) +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK (1<<1) +#define SSH_SOCKET_EXCEPTION_EOF (1<<0) +#define SSH_SOCKET_EXCEPTION_ERROR (1<<1) + +#define SSH_SOCKET_CONNECTED_OK (1<<0) +#define SSH_SOCKET_CONNECTED_ERROR (1<<1) +#define SSH_SOCKET_CONNECTED_TIMEOUT (1<<2) /** Initializes an ssh_callbacks_struct * A call to this macro is mandatory when you have set a new @@ -82,6 +123,21 @@ typedef struct ssh_callbacks_struct * ssh_callbacks; (p)->size=sizeof(*(p)); \ } while(0); +/* These are the callback exported by the packet layer + * and are called each time a packet shows up + * */ +typedef int (*ssh_packet_callback) (ssh_session, void *user, uint8_t code, ssh_buffer packet); + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + u_int8_t start; + /** Number of packets being handled by this callback struct */ + u_int8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + void *user; +}; +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; /** * @brief Set the callback functions. * @@ -106,6 +162,12 @@ typedef struct ssh_callbacks_struct * ssh_callbacks; */ LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 #ifdef __cplusplus } #endif diff --git a/include/libssh/messages.h b/include/libssh/messages.h index 0eb7b64f..e002aa4a 100644 --- a/include/libssh/messages.h +++ b/include/libssh/messages.h @@ -78,7 +78,7 @@ struct ssh_message_struct { }; -void message_handle(ssh_session session, uint32_t type); +//void message_handle(ssh_session session, uint32_t type); int ssh_execute_message_callbacks(ssh_session session); #endif /* MESSAGES_H_ */ diff --git a/include/libssh/priv.h b/include/libssh/priv.h index c8bfee35..8bce949e 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -48,6 +48,7 @@ #include "libssh/libssh.h" #include "libssh/callbacks.h" #include "libssh/crypto.h" + /* some constants */ #define MAX_PACKET_LEN 262144 #define ERROR_BUFFERLEN 1024 @@ -90,7 +91,7 @@ struct ssh_keys_struct { }; struct ssh_message_struct; - +struct ssh_poll_handle_struct; /* server data */ @@ -113,6 +114,14 @@ struct ssh_bind_struct { int toaccept; }; +struct socket; +struct ssh_poll; +void ssh_socket_set_callbacks(struct socket *s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, int fd, int revents, void *s); +void ssh_socket_register_pollcallback(struct socket *s, struct ssh_poll_handle_struct *p); + +int ssh_packet_disconnect_callback(ssh_session session, void *user, u_int8_t type, ssh_buffer packet); +int ssh_packet_ignore_callback(ssh_session session, void *user, u_int8_t type, ssh_buffer packet); /* client.c */ @@ -134,6 +143,11 @@ unsigned char *packet_encrypt(ssh_session session,void *packet,unsigned int len) /* it returns the hmac buffer if exists*/ int packet_hmac_verify(ssh_session session,ssh_buffer buffer,unsigned char *mac); +int ssh_packet_socket_callback(void *user, const void *data, size_t len); +void ssh_packet_register_socket_callback(ssh_session session, struct socket *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, u_int8_t type); /* connect.c */ int ssh_regex_init(void); void ssh_regex_finalize(void); @@ -152,6 +166,11 @@ char **space_tokenize(const char *chain); int ssh_get_kex1(ssh_session session); char *ssh_find_matching(const char *in_d, const char *what_d); +int channel_rcv_change_window(ssh_session session, void *user, uint8_t type, ssh_buffer packet); +int channel_rcv_eof(ssh_session session, void *user, uint8_t type, ssh_buffer packet); +int channel_rcv_close(ssh_session session, void *user, uint8_t type, ssh_buffer packet); +int channel_rcv_request(ssh_session session, void *user, uint8_t type, ssh_buffer packet); +int channel_rcv_data(ssh_session session, void *user, uint8_t type, ssh_buffer packet); /* in base64.c */ ssh_buffer base64_to_bin(const char *source); unsigned char *bin_to_base64(const unsigned char *source, int len); @@ -183,6 +202,7 @@ int channel_write1(ssh_channel channel, const void *data, int len); /* match.c */ int match_hostname(const char *host, const char *pattern, unsigned int len); +int message_handle(ssh_session session, void *user, uint8_t type, ssh_buffer packet); /* log.c */ #ifndef __FUNCTION__ diff --git a/include/libssh/session.h b/include/libssh/session.h index f490b1e4..0a5ad851 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -95,7 +95,9 @@ struct ssh_session_struct { int log_indent; /* indentation level in enter_function logs */ ssh_callbacks callbacks; /* Callbacks to user functions */ - + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; /* options */ #ifdef WITH_PCAP ssh_pcap_context pcap_ctx; /* pcap debugging context */ @@ -114,6 +116,7 @@ struct ssh_session_struct { socket_t fd; int ssh2; int ssh1; + }; int ssh_handle_packets(ssh_session session); diff --git a/libssh/channels.c b/libssh/channels.c index 13ea25f2..8af3a037 100644 --- a/libssh/channels.c +++ b/libssh/channels.c @@ -314,11 +314,13 @@ static ssh_channel channel_from_msg(ssh_session session) { return channel; } -static void channel_rcv_change_window(ssh_session session) { +int channel_rcv_change_window(ssh_session session, void *user, u_int8_t type, ssh_buffer packet) { ssh_channel channel; uint32_t bytes; int rc; - + (void)user; + (void)type; + (void)packet; enter_function(); channel = channel_from_msg(session); @@ -331,7 +333,7 @@ static void channel_rcv_change_window(ssh_session session) { ssh_log(session, SSH_LOG_PACKET, "Error getting a window adjust message: invalid packet"); leave_function(); - return; + return SSH_PACKET_USED; } bytes = ntohl(bytes); @@ -345,22 +347,29 @@ static void channel_rcv_change_window(ssh_session session) { channel->remote_window += bytes; leave_function(); + return SSH_PACKET_USED; } /* is_stderr is set to 1 if the data are extended, ie stderr */ -static void channel_rcv_data(ssh_session session,int is_stderr) { +int channel_rcv_data(ssh_session session, void *user, u_int8_t type, ssh_buffer packet) { ssh_channel channel; ssh_string str; size_t len; - + int is_stderr; + (void)user; + (void)packet; enter_function(); + if(type==SSH2_MSG_CHANNEL_DATA) + is_stderr=0; + else + is_stderr=1; channel = channel_from_msg(session); if (channel == NULL) { ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); leave_function(); - return; + return SSH_PACKET_USED; } if (is_stderr) { @@ -373,7 +382,7 @@ static void channel_rcv_data(ssh_session session,int is_stderr) { if (str == NULL) { ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!"); leave_function(); - return; + return SSH_PACKET_USED; } len = string_len(str); @@ -396,7 +405,7 @@ static void channel_rcv_data(ssh_session session,int is_stderr) { is_stderr) < 0) { string_free(str); leave_function(); - return; + return SSH_PACKET_USED; } if (len <= channel->local_window) { @@ -412,18 +421,21 @@ static void channel_rcv_data(ssh_session session,int is_stderr) { string_free(str); leave_function(); + return SSH_PACKET_USED; } -static void channel_rcv_eof(ssh_session session) { +int channel_rcv_eof(ssh_session session, void *user, u_int8_t type, ssh_buffer packet) { ssh_channel channel; - + (void)user; + (void)type; + (void)packet; enter_function(); channel = channel_from_msg(session); if (channel == NULL) { ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); leave_function(); - return; + return SSH_PACKET_USED; } ssh_log(session, SSH_LOG_PACKET, @@ -434,134 +446,142 @@ static void channel_rcv_eof(ssh_session session) { channel->remote_eof = 1; leave_function(); + return SSH_PACKET_USED; } -static void channel_rcv_close(ssh_session session) { - ssh_channel channel; - - enter_function(); - - channel = channel_from_msg(session); - if (channel == NULL) { - ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - leave_function(); - return; - } - - ssh_log(session, SSH_LOG_PACKET, - "Received close on channel (%d:%d)", - channel->local_channel, - channel->remote_channel); - - if ((channel->stdout_buffer && - buffer_get_rest_len(channel->stdout_buffer) > 0) || - (channel->stderr_buffer && - buffer_get_rest_len(channel->stderr_buffer) > 0)) { - channel->delayed_close = 1; - } else { - channel->open = 0; - } - - if (channel->remote_eof == 0) { - ssh_log(session, SSH_LOG_PACKET, - "Remote host not polite enough to send an eof before close"); - } - channel->remote_eof = 1; - /* - * The remote eof doesn't break things if there was still data into read - * buffer because the eof is ignored until the buffer is empty. - */ - - leave_function(); +int channel_rcv_close(ssh_session session, void *user, u_int8_t type, ssh_buffer packet) { + ssh_channel channel; + (void)user; + (void)type; + (void)packet; + enter_function(); + + channel = channel_from_msg(session); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received close on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if ((channel->stdout_buffer && + buffer_get_rest_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + buffer_get_rest_len(channel->stderr_buffer) > 0)) { + channel->delayed_close = 1; + } else { + channel->open = 0; + } + + if (channel->remote_eof == 0) { + ssh_log(session, SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + channel->remote_eof = 1; + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + + leave_function(); + return SSH_PACKET_USED; } -static void channel_rcv_request(ssh_session session) { - ssh_channel channel; - ssh_string request_s; - char *request; - uint32_t status; - uint32_t startpos = session->in_buffer->pos; - - enter_function(); - - channel = channel_from_msg(session); - if (channel == NULL) { - ssh_log(session, SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); - leave_function(); - return; - } - - request_s = buffer_get_ssh_string(session->in_buffer); - if (request_s == NULL) { - ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - leave_function(); - return; - } - - request = string_to_char(request_s); - string_free(request_s); - if (request == NULL) { - leave_function(); - return; - } - - buffer_get_u8(session->in_buffer, (uint8_t *) &status); - - if (strcmp(request,"exit-status") == 0) { - SAFE_FREE(request); - ssh_log(session, SSH_LOG_PACKET, "received exit-status"); - buffer_get_u32(session->in_buffer, &status); - channel->exit_status = ntohl(status); - - leave_function(); - return ; - } - - if (strcmp(request, "exit-signal") == 0) { - const char *core = "(core dumped)"; - ssh_string signal_s; - char *signal; - uint8_t i; - - SAFE_FREE(request); - - signal_s = buffer_get_ssh_string(session->in_buffer); - if (signal_s == NULL) { - ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - leave_function(); - return; - } - - signal = string_to_char(signal_s); - string_free(signal_s); - if (signal == NULL) { - leave_function(); - return; - } - - buffer_get_u8(session->in_buffer, &i); - if (i == 0) { - core = ""; - } - - ssh_log(session, SSH_LOG_PACKET, - "Remote connection closed by signal SIG %s %s", signal, core); - SAFE_FREE(signal); - - leave_function(); - return; - } - - /* TODO call message_handle since it handles channel requests as messages */ - /* *but* reset buffer before !! */ - - session->in_buffer->pos = startpos; - message_handle(session, SSH2_MSG_CHANNEL_REQUEST); - - ssh_log(session, SSH_LOG_PACKET, "Unknown request %s", request); - SAFE_FREE(request); - - leave_function(); +int channel_rcv_request(ssh_session session, void *user, u_int8_t type, ssh_buffer packet) { + ssh_channel channel; + ssh_string request_s; + char *request; + uint32_t status; + uint32_t startpos = session->in_buffer->pos; + (void)user; + (void)type; + (void)packet; + + enter_function(); + + channel = channel_from_msg(session); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + request_s = buffer_get_ssh_string(session->in_buffer); + if (request_s == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + request = string_to_char(request_s); + string_free(request_s); + if (request == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(session->in_buffer, (uint8_t *) &status); + + if (strcmp(request,"exit-status") == 0) { + SAFE_FREE(request); + ssh_log(session, SSH_LOG_PACKET, "received exit-status"); + buffer_get_u32(session->in_buffer, &status); + channel->exit_status = ntohl(status); + + leave_function(); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + ssh_string signal_s; + char *signal; + uint8_t i; + + SAFE_FREE(request); + + signal_s = buffer_get_ssh_string(session->in_buffer); + if (signal_s == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + signal = string_to_char(signal_s); + string_free(signal_s); + if (signal == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(session->in_buffer, &i); + if (i == 0) { + core = ""; + } + + ssh_log(session, SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", signal, core); + SAFE_FREE(signal); + + leave_function(); + return SSH_PACKET_USED; + } + + /* TODO call message_handle since it handles channel requests as messages */ + /* *but* reset buffer before !! */ + + session->in_buffer->pos = startpos; + message_handle(session, NULL, SSH2_MSG_CHANNEL_REQUEST, session->in_buffer); + + ssh_log(session, SSH_LOG_PACKET, "Unknown request %s", request); + SAFE_FREE(request); + + leave_function(); + return SSH_PACKET_USED; } /* @@ -575,22 +595,12 @@ void channel_handle(ssh_session session, int type){ switch(type) { case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - channel_rcv_change_window(session); - break; + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_EOF: case SSH2_MSG_CHANNEL_DATA: - channel_rcv_data(session,0); - break; case SSH2_MSG_CHANNEL_EXTENDED_DATA: - channel_rcv_data(session,1); - break; - case SSH2_MSG_CHANNEL_EOF: - channel_rcv_eof(session); - break; - case SSH2_MSG_CHANNEL_CLOSE: - channel_rcv_close(session); - break; - case SSH2_MSG_CHANNEL_REQUEST: - channel_rcv_request(session); + ssh_packet_process(session, type); break; default: ssh_log(session, SSH_LOG_FUNCTIONS, diff --git a/libssh/messages.c b/libssh/messages.c index 2b60dfdd..1a3e4ffe 100644 --- a/libssh/messages.c +++ b/libssh/messages.c @@ -856,15 +856,20 @@ void ssh_message_free(ssh_message msg){ * \param type packet type * \returns nothing */ -void message_handle(ssh_session session, uint32_t type){ +int message_handle(ssh_session session, void *user, uint8_t type, ssh_buffer packet){ ssh_message msg=ssh_message_retrieve(session,type); ssh_log(session,SSH_LOG_PROTOCOL,"Stacking message from packet type %d",type); - if(msg){ - if(!session->ssh_message_list){ - session->ssh_message_list=ssh_list_new(); - } - ssh_list_add(session->ssh_message_list,msg); - } + (void)user; + (void)packet; + if(msg){ + if(!session->ssh_message_list){ + session->ssh_message_list=ssh_list_new(); + } + ssh_list_add(session->ssh_message_list,msg); + return SSH_PACKET_USED; + } else { + return SSH_PACKET_NOT_USED; + } } /** diff --git a/libssh/packet.c b/libssh/packet.c index 84c6466a..0c023239 100644 --- a/libssh/packet.c +++ b/libssh/packet.c @@ -40,10 +40,57 @@ #include "libssh/packet.h" #include "libssh/socket.h" #include "libssh/channels.h" +#include "libssh/misc.h" #include "libssh/session.h" #include "libssh/messages.h" #include "libssh/pcap.h" +ssh_packet_callback default_packet_handlers[]= { + + ssh_packet_disconnect_callback, //#define SSH2_MSG_DISCONNECT 1 + ssh_packet_ignore_callback, //#define SSH2_MSG_IGNORE 2 + NULL, //#define SSH2_MSG_UNIMPLEMENTED 3 + ssh_packet_ignore_callback, //#define SSH2_MSG_DEBUG 4 + NULL, //#define SSH2_MSG_SERVICE_REQUEST 5 + NULL, //#define SSH2_MSG_SERVICE_ACCEPT 6 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, // 7-19 + NULL, //#define SSH2_MSG_KEXINIT 20 + NULL, //#define SSH2_MSG_NEWKEYS 21 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //22-29 + NULL, //#define SSH2_MSG_KEXDH_INIT 30 SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 + NULL, // #define SSH2_MSG_KEXDH_REPLY 31 SSH2_MSG_KEX_DH_GEX_GROUP 31 + NULL, //#define SSH2_MSG_KEX_DH_GEX_INIT 32 + NULL, //#define SSH2_MSG_KEX_DH_GEX_REPLY 33 + NULL, //#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 + NULL, NULL, NULL, NULL, NULL, // 35-49 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, //#define SSH2_MSG_USERAUTH_REQUEST 50 + NULL, //#define SSH2_MSG_USERAUTH_FAILURE 51 + NULL, //#define SSH2_MSG_USERAUTH_SUCCESS 52 + NULL, //#define SSH2_MSG_USERAUTH_BANNER 53 + NULL, //#define SSH2_MSG_USERAUTH_PK_OK 60 SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + //SSH2_MSG_USERAUTH_INFO_REQUEST 60 + NULL, //#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //62-79 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, //#define SSH2_MSG_GLOBAL_REQUEST 80 + NULL, //#define SSH2_MSG_REQUEST_SUCCESS 81 + NULL, //#define SSH2_MSG_REQUEST_FAILURE 82 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 83-89 + NULL, //#define SSH2_MSG_CHANNEL_OPEN 90 + NULL, //#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 + NULL, //#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 + channel_rcv_change_window, //#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 + channel_rcv_data, //#define SSH2_MSG_CHANNEL_DATA 94 + channel_rcv_data, //#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 + channel_rcv_eof, //#define SSH2_MSG_CHANNEL_EOF 96 + channel_rcv_close, //#define SSH2_MSG_CHANNEL_CLOSE 97 + channel_rcv_request, //#define SSH2_MSG_CHANNEL_REQUEST 98 + NULL, //#define SSH2_MSG_CHANNEL_SUCCESS 99 + NULL, //#define SSH2_MSG_CHANNEL_FAILURE 100 +}; + /* XXX include selected mac size */ static int macsize=SHA_DIGEST_LEN; @@ -54,6 +101,243 @@ static int macsize=SHA_DIGEST_LEN; #define PACKET_STATE_INIT 0 #define PACKET_STATE_SIZEREAD 1 +#define PACKET_STATE_PROCESSING 2 + +/** @internal + * @handles a data received event. It then calls the handlers for the different packet types + * or and exception handler callback. + * @param user pointer to current ssh_session + * @param data pointer to the data received + * @len length of data received. It might not be enough for a complete packet + * @returns number of bytes read and processed. + */ +int ssh_packet_socket_callback(void *user, const void *data, size_t receivedlen){ + ssh_session session=(ssh_session) user; + unsigned int blocksize = (session->current_crypto ? + session->current_crypto->in_cipher->blocksize : 8); + int current_macsize = session->current_crypto ? macsize : 0; + unsigned char mac[30] = {0}; + char buffer[16] = {0}; + void *packet=NULL; + int to_be_read; + int rc = SSH_ERROR; + uint32_t len; + uint8_t padding; + size_t processed=0; /* number of byte processed from the callback */ + + enter_function(); + + switch(session->packet_state) { + case PACKET_STATE_INIT: + if(receivedlen < blocksize){ + /* We didn't receive enough data to read at least one block size, give up */ + leave_function(); + return 0; + } + memset(&session->in_packet, 0, sizeof(PACKET)); + + if (session->in_buffer) { + if (buffer_reinit(session->in_buffer) < 0) { + goto error; + } + } else { + session->in_buffer = buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + + memcpy(buffer,data,blocksize); + processed += blocksize; + len = packet_decrypt_len(session, buffer); + + if (buffer_add_data(session->in_buffer, buffer, blocksize) < 0) { + goto error; + } + + if(len > MAX_PACKET_LEN) { + ssh_set_error(session, SSH_FATAL, + "read_packet(): Packet len too high(%u %.4x)", len, len); + goto error; + } + + to_be_read = len - blocksize + sizeof(uint32_t); + if (to_be_read < 0) { + /* remote sshd sends invalid sizes? */ + ssh_set_error(session, SSH_FATAL, + "given numbers of bytes left to be read < 0 (%d)!", to_be_read); + goto error; + } + + /* saves the status of the current operations */ + session->in_packet.len = len; + session->packet_state = PACKET_STATE_SIZEREAD; + case PACKET_STATE_SIZEREAD: + len = session->in_packet.len; + to_be_read = len - blocksize + sizeof(uint32_t) + current_macsize; + /* if to_be_read is zero, the whole packet was blocksize bytes. */ + if (to_be_read != 0) { + if(receivedlen - processed < (unsigned int)to_be_read){ + /* give up, not enough data in buffer */ + return processed; + } + rc = SSH_ERROR; + + packet = (unsigned char *)data + processed; +// ssh_socket_read(session->socket,packet,to_be_read-current_macsize); + + ssh_log(session,SSH_LOG_PACKET,"Read a %d bytes packet",len); + + if (buffer_add_data(session->in_buffer, packet, + to_be_read - current_macsize) < 0) { + goto error; + } + processed += to_be_read - current_macsize; + } + + if (session->current_crypto) { + /* + * decrypt the rest of the packet (blocksize bytes already + * have been decrypted) + */ + if (packet_decrypt(session, + ((uint8_t*)buffer_get(session->in_buffer) + blocksize), + buffer_get_len(session->in_buffer) - blocksize) < 0) { + ssh_set_error(session, SSH_FATAL, "Decrypt error"); + goto error; + } + /* copy the last part from the incoming buffer */ + memcpy(mac,(unsigned char *)packet + to_be_read - current_macsize, macsize); + + if (packet_hmac_verify(session, session->in_buffer, mac) < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + + /* skip the size field which has been processed before */ + buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); + + if (buffer_get_u8(session->in_buffer, &padding) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read padding"); + goto error; + } + + ssh_log(session, SSH_LOG_PACKET, + "%hhd bytes padding, %d bytes left in buffer", + padding, buffer_get_rest_len(session->in_buffer)); + + if (padding > buffer_get_rest_len(session->in_buffer)) { + ssh_set_error(session, SSH_FATAL, + "Invalid padding: %d (%d resting)", + padding, + buffer_get_rest_len(session->in_buffer)); +#ifdef DEBUG_CRYPTO + ssh_print_hexa("incrimined packet", + buffer_get(session->in_buffer), + buffer_get_len(session->in_buffer)); +#endif + goto error; + } + buffer_pass_bytes_end(session->in_buffer, padding); + + ssh_log(session, SSH_LOG_PACKET, + "After padding, %d bytes left in buffer", + buffer_get_rest_len(session->in_buffer)); +#if defined(HAVE_LIBZ) && defined(WITH_LIBZ) + if (session->current_crypto && session->current_crypto->do_compress_in) { + ssh_log(session, SSH_LOG_PACKET, "Decompressing in_buffer ..."); + if (decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN) < 0) { + goto error; + } + } +#endif + session->recv_seq++; + /* We don't want to rewrite a new packet while still executing the packet callbacks */ + session->packet_state = PACKET_STATE_PROCESSING; + packet_translate(session); + /* execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + session->packet_state = PACKET_STATE_INIT; + leave_function(); + return processed; + case PACKET_STATE_PROCESSING: + ssh_log(session, SSH_LOG_PACKET, "Nested packet processing. Delaying."); + return 0; + } + + ssh_set_error(session, SSH_FATAL, + "Invalid state into packet_read2(): %d", + session->packet_state); + +error: + leave_function(); + return processed; +} + +void ssh_packet_register_socket_callback(ssh_session session, struct socket *s){ + session->socket_callbacks.data=ssh_packet_socket_callback; + session->socket_callbacks.connected=NULL; + session->socket_callbacks.controlflow=NULL; + session->socket_callbacks.exception=NULL; + session->socket_callbacks.user=session; + ssh_socket_set_callbacks(s,&session->socket_callbacks); +} + +/** @internal + * @brief sets the callbacks for the packet layer + */ +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ + if(session->packet_callbacks == NULL){ + session->packet_callbacks = ssh_list_new(); + } + ssh_list_add(session->packet_callbacks,callbacks); +} + +/** @internal + * @brief sets the default packet handlers + */ +void ssh_packet_set_default_callbacks(ssh_session session){ + session->default_packet_callbacks.start=0; + session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); + session->default_packet_callbacks.user=session; + session->default_packet_callbacks.callbacks=default_packet_handlers; + ssh_packet_set_callbacks(session, &session->default_packet_callbacks); +} + +/** @internal + * @brief dispatch the call of packet handlers callbacks for a received packet + * @param type type of packet + */ +void ssh_packet_process(ssh_session session, u_int8_t type){ + struct ssh_iterator *i; + int r; + ssh_packet_callbacks cb; + enter_function(); + ssh_log(session,SSH_LOG_PACKET, "Dispatching handler for packet type %d",type); + if(session->packet_callbacks == NULL){ + ssh_log(session,SSH_LOG_RARE,"Packet callback is not initialized !"); + goto error; + } + i=ssh_list_get_iterator(session->packet_callbacks); + while(i != NULL){ + cb=ssh_iterator_value(ssh_packet_callbacks,i); + i=i->next; + if(!cb) + continue; + if(cb->start > type) + continue; + if(cb->start + cb->n_callbacks > type) + continue; + if(cb->callbacks[type - cb->start]==NULL) + continue; + r=cb->callbacks[type - cb->start](session,cb->user,type,session->in_buffer); + if(r==SSH_PACKET_USED) + break; + } +error: + leave_function(); +} static int packet_read2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? @@ -624,10 +908,7 @@ int packet_send(ssh_session session) { } void packet_parse(ssh_session session) { - ssh_string error_s = NULL; - char *error = NULL; - uint32_t type = session->in_packet.type; - uint32_t tmp; + uint8_t type = session->in_packet.type; #ifdef WITH_SSH1 if (session->version == 1) { @@ -657,41 +938,20 @@ void packet_parse(ssh_session session) { #endif /* WITH_SSH1 */ switch(type) { case SSH2_MSG_DISCONNECT: - buffer_get_u32(session->in_buffer, &tmp); - error_s = buffer_get_ssh_string(session->in_buffer); - if (error_s == NULL) { - return; - } - error = string_to_char(error_s); - string_free(error_s); - if (error == NULL) { - return; - } - ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT\n"); - ssh_set_error(session, SSH_FATAL, - "Received SSH_MSG_DISCONNECT: %s",error); - - SAFE_FREE(error); - - ssh_socket_close(session->socket); - session->alive = 0; - - return; case SSH2_MSG_CHANNEL_WINDOW_ADJUST: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_REQUEST: case SSH2_MSG_CHANNEL_EOF: case SSH2_MSG_CHANNEL_CLOSE: - channel_handle(session,type); - return; case SSH2_MSG_IGNORE: case SSH2_MSG_DEBUG: + ssh_packet_process(session,type); return; case SSH2_MSG_SERVICE_REQUEST: case SSH2_MSG_USERAUTH_REQUEST: case SSH2_MSG_CHANNEL_OPEN: - message_handle(session,type); + message_handle(session,NULL,type,session->in_buffer); return; default: ssh_log(session, SSH_LOG_RARE, "Received unhandled packet %d", type); @@ -735,7 +995,7 @@ static int packet_wait1(ssh_session session, int type, int blocking) { /* case SSH2_MSG_CHANNEL_CLOSE: packet_parse(session); break;; - */ + */ default: if (type && (type != session->in_packet.type)) { ssh_set_error(session, SSH_FATAL, diff --git a/libssh/server.c b/libssh/server.c index 9de032f2..e0502ccd 100644 --- a/libssh/server.c +++ b/libssh/server.c @@ -857,8 +857,8 @@ char *ssh_message_channel_request_subsystem(ssh_message msg){ * must take care of the response). */ void ssh_set_message_callback(ssh_session session, - int(*ssh_message_callback)(ssh_session session, ssh_message msg)){ - session->ssh_message_callback=ssh_message_callback; + int(*ssh_message_callback_)(ssh_session session, ssh_message msg)){ + session->ssh_message_callback=ssh_message_callback_; } int ssh_execute_message_callbacks(ssh_session session){ diff --git a/libssh/session.c b/libssh/session.c index b2c0368e..ede83ebe 100644 --- a/libssh/session.c +++ b/libssh/session.c @@ -28,10 +28,12 @@ #include "libssh/priv.h" #include "libssh/server.h" #include "libssh/socket.h" +#include "libssh/ssh2.h" #include "libssh/agent.h" #include "libssh/packet.h" #include "libssh/session.h" #include "libssh/misc.h" +#include "libssh/buffer.h" #define FIRST_CHANNEL 42 // why not ? it helps to find bugs. @@ -363,5 +365,44 @@ int ssh_get_version(ssh_session session) { return session->version; } +/** + * @internal + * @brief handles a SSH_DISCONNECT packet + */ +int ssh_packet_disconnect_callback(ssh_session session, void *user, u_int8_t type, ssh_buffer packet){ + u_int32_t code; + char *error; + ssh_string error_s; + (void)user; + (void)type; + buffer_get_u32(packet, &code); + error_s = buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = string_to_char(error_s); + string_free(error_s); + } + ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s",code,error); + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %d:%s",code,error); + SAFE_FREE(error); + + ssh_socket_close(session->socket); + session->alive = 0; + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH_IGNORE and SSH_DEBUG packet + */ +int ssh_packet_ignore_callback(ssh_session session, void *user, u_int8_t type, ssh_buffer packet){ + (void)user; + (void)type; + (void)packet; + ssh_log(session,SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} /** @} */ /* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/socket.c b/libssh/socket.c index 933119f7..cb96e8da 100644 --- a/libssh/socket.c +++ b/libssh/socket.c @@ -3,7 +3,7 @@ * * This file is part of the SSH Library * - * Copyright (c) 2008 by Aris Adamantiadis + * Copyright (c) 2008,2009 by Aris Adamantiadis * * The SSH Library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -34,11 +34,11 @@ #include <sys/un.h> #endif #include "libssh/priv.h" +#include "libssh/callbacks.h" #include "libssh/socket.h" #include "libssh/buffer.h" #include "libssh/poll.h" #include "libssh/session.h" - /** \defgroup ssh_socket SSH Sockets * \addtogroup ssh_socket * @{ @@ -54,8 +54,14 @@ struct socket { ssh_buffer out_buffer; ssh_buffer in_buffer; ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll; }; +static int ssh_socket_unbuffered_read(struct socket *s, void *buffer, uint32_t len); +static int ssh_socket_unbuffered_write(struct socket *s, const void *buffer, + uint32_t len); + /* * \internal * \brief inits the socket system (windows specific) @@ -103,7 +109,78 @@ struct socket *ssh_socket_new(ssh_session session) { return s; } -/* \internal +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event + * @param callbacks a ssh_socket_callback object reference + */ + +void ssh_socket_set_callbacks(struct socket *s, ssh_socket_callbacks callbacks){ + s->callbacks=callbacks; +} + +int ssh_socket_pollcallback(ssh_poll_handle p, int fd, int revents, void *v_s){ + struct socket *s=(struct socket *)v_s; + char buffer[4096]; + int r,w; + (void)fd; + if(revents & POLLERR){ + s->data_except=1; + /* force a read to get an explanation */ + revents |= POLLIN; + } + if(revents & POLLIN){ + s->data_to_read=1; + r=ssh_socket_unbuffered_read(s,buffer,sizeof(buffer)); + if(r<0){ + ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN); + if(s->callbacks){ + s->callbacks->exception(s->callbacks->user, + SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno); + } + } + if(r==0){ + ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLIN); + if(s->callbacks){ + s->callbacks->exception(s->callbacks->user, + SSH_SOCKET_EXCEPTION_EOF, + 0); + } + } + if(r>0){ + /* Bufferize the data and then call the callback */ + buffer_add_data(s->in_buffer,buffer,r); + if(s->callbacks){ + r= s->callbacks->data(s->callbacks->user, + buffer_get_rest(s->in_buffer), buffer_get_rest_len(s->in_buffer)); + buffer_pass_bytes(s->in_buffer,r); + } + } + } + if(revents & POLLOUT){ + s->data_to_write=1; + /* If buffered data is pending, write it */ + if(buffer_get_rest_len(s->out_buffer) > 0){ + w=ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), + buffer_get_rest_len(s->out_buffer)); + } else if(s->callbacks){ + /* Otherwise advertise the upper level that write can be done */ + s->callbacks->controlflow(s->callbacks->user,SSH_SOCKET_FLOW_WRITEWONTBLOCK); + ssh_poll_set_events(p,ssh_poll_get_events(p) & ~POLLOUT); + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + } + return 0; +} + +void ssh_socket_register_pollcallback(struct socket *s, ssh_poll_handle p){ + ssh_poll_set_callback(p,ssh_socket_pollcallback,s); + s->poll=p; +} + +/** \internal * \brief Deletes a socket object */ void ssh_socket_free(struct socket *s){ @@ -225,7 +302,10 @@ static int ssh_socket_unbuffered_write(struct socket *s, const void *buffer, s->last_errno = errno; #endif s->data_to_write = 0; - + /* Reactive the POLLOUT detector in the poll multiplexer system */ + if(s->poll){ + ssh_poll_set_events(s->poll,ssh_poll_get_events(s->poll) | POLLOUT); + } if (w < 0) { s->data_except = 1; } @@ -337,7 +417,6 @@ int ssh_socket_read(struct socket *s, void *buffer, int len){ return SSH_OK; } -#define WRITE_BUFFERING_THRESHOLD 65536 /** \internal * \brief buffered write of data * \returns SSH_OK, or SSH_ERROR @@ -345,22 +424,12 @@ int ssh_socket_read(struct socket *s, void *buffer, int len){ */ int ssh_socket_write(struct socket *s, const void *buffer, int len) { ssh_session session = s->session; - int rc = SSH_ERROR; - enter_function(); - if (buffer_add_data(s->out_buffer, buffer, len) < 0) { return SSH_ERROR; } - - if (buffer_get_rest_len(s->out_buffer) > WRITE_BUFFERING_THRESHOLD) { - rc = ssh_socket_nonblocking_flush(s); - } else { - rc = len; - } - leave_function(); - return rc; + return len; } @@ -509,64 +578,46 @@ int ssh_socket_poll(struct socket *s, int *writeable, int *except) { } /** \internal - * \brief nonblocking flush of the output buffer + * \brief starts a nonblocking flush of the output buffer + * */ int ssh_socket_nonblocking_flush(struct socket *s) { ssh_session session = s->session; - int except; - int can_write; int w; enter_function(); - /* internally sets data_to_write */ - if (ssh_socket_poll(s, &can_write, &except) < 0) { - leave_function(); - return SSH_ERROR; - } - if (!ssh_socket_is_open(s)) { session->alive = 0; /* FIXME use ssh_socket_get_errno */ ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection closed): %s", - strerror(errno)); + strerror(s->last_errno)); leave_function(); return SSH_ERROR; } - while(s->data_to_write && buffer_get_rest_len(s->out_buffer) > 0) { - if (ssh_socket_is_open(s)) { - w = ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), - buffer_get_rest_len(s->out_buffer)); - } else { - /* write failed */ - w =- 1; - } - + if (s->data_to_write && buffer_get_rest_len(s->out_buffer) > 0) { + w = ssh_socket_unbuffered_write(s, buffer_get_rest(s->out_buffer), + buffer_get_rest_len(s->out_buffer)); if (w < 0) { session->alive = 0; ssh_socket_close(s); /* FIXME use ssh_socket_get_errno() */ ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection closed): %s", - strerror(errno)); - + strerror(s->last_errno)); leave_function(); return SSH_ERROR; } - buffer_pass_bytes(s->out_buffer, w); - /* refresh the socket status */ - if (ssh_socket_poll(session->socket, &can_write, &except) < 0) { - leave_function(); - return SSH_ERROR; - } } /* Is there some data pending? */ - if (buffer_get_rest_len(s->out_buffer) > 0) { + if (buffer_get_rest_len(s->out_buffer) > 0 && s->poll) { + /* force the poll system to catch pollout events */ + ssh_poll_set_events(s->poll, ssh_poll_get_events(s->poll) |POLLOUT); leave_function(); return SSH_AGAIN; } |