/* * messages.c - message parsion for the server * * This file is part of the SSH Library * * Copyright (c) 2003-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 * the Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * The SSH Library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SSH Library; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ /** \defgroup ssh_messages SSH Messages * this file contains the Message parsing utilities for server programs using * libssh. The main loop of the program will call ssh_message_get(session) to * get messages as they come. they are not 1-1 with the protocol messages. * then, the user will know what kind of a message it is and use the appropriate * functions to handle it (or use the default handlers if she doesn't know what to * do * \addtogroup ssh_messages * @{ */ #include #include #ifndef _WIN32 #include #endif #include "libssh/libssh.h" #include "libssh/priv.h" #include "libssh/ssh2.h" #include "libssh/buffer.h" #include "libssh/packet.h" #include "libssh/channels.h" #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/keys.h" #include "libssh/dh.h" #include "libssh/messages.h" static ssh_message ssh_message_new(ssh_session session){ ssh_message msg = malloc(sizeof(struct ssh_message_struct)); if (msg == NULL) { return NULL; } ZERO_STRUCTP(msg); msg->session = session; return msg; } SSH_PACKET_CALLBACK(ssh_packet_service_request){ ssh_string service = NULL; char *service_c = NULL; ssh_message msg=NULL; enter_function(); (void)type; (void)user; service = buffer_get_ssh_string(packet); if (service == NULL) { ssh_set_error(session, SSH_FATAL, "Invalid SSH_MSG_SERVICE_REQUEST packet"); goto error; } service_c = string_to_char(service); if (service_c == NULL) { goto error; } ssh_log(session, SSH_LOG_PACKET, "Received a SERVICE_REQUEST for service %s", service_c); msg=ssh_message_new(session); if(!msg){ SAFE_FREE(service_c); goto error; } msg->type=SSH_REQUEST_SERVICE; msg->service_request.service=service_c; error: string_free(service); if(msg != NULL) ssh_message_queue(session,msg); leave_function(); return SSH_PACKET_USED; } /** @internal * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a * SSH Message */ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ ssh_string user_s = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_message msg = NULL; char *service_c = NULL; char *method_c = NULL; uint32_t method_size = 0; enter_function(); (void)user; (void)type; msg = ssh_message_new(session); if (msg == NULL) { ssh_set_error_oom(session); goto error; } user_s = buffer_get_ssh_string(packet); if (user_s == NULL) { goto error; } service = buffer_get_ssh_string(packet); if (service == NULL) { goto error; } method = buffer_get_ssh_string(packet); if (method == NULL) { goto error; } msg->type = SSH_REQUEST_AUTH; msg->auth_request.username = string_to_char(user_s); if (msg->auth_request.username == NULL) { goto error; } string_free(user_s); user_s = NULL; service_c = string_to_char(service); if (service_c == NULL) { goto error; } method_c = string_to_char(method); if (method_c == NULL) { goto error; } method_size = string_len(method); string_free(service); service = NULL; string_free(method); method = NULL; ssh_log(session, SSH_LOG_PACKET, "Auth request for service %s, method %s for user '%s'", service_c, method_c, msg->auth_request.username); if (strncmp(method_c, "none", method_size) == 0) { msg->auth_request.method = SSH_AUTH_METHOD_NONE; SAFE_FREE(service_c); SAFE_FREE(method_c); goto end; } if (strncmp(method_c, "password", method_size) == 0) { ssh_string pass = NULL; uint8_t tmp; msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; SAFE_FREE(service_c); SAFE_FREE(method_c); buffer_get_u8(packet, &tmp); pass = buffer_get_ssh_string(packet); if (pass == NULL) { goto error; } msg->auth_request.password = string_to_char(pass); string_burn(pass); string_free(pass); pass = NULL; if (msg->auth_request.password == NULL) { goto error; } goto end; } if (strncmp(method_c, "publickey", method_size) == 0) { ssh_string algo = NULL; ssh_string publickey = NULL; uint8_t has_sign; msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; SAFE_FREE(method_c); buffer_get_u8(packet, &has_sign); algo = buffer_get_ssh_string(packet); if (algo == NULL) { goto error; } publickey = buffer_get_ssh_string(packet); if (publickey == NULL) { string_free(algo); algo = NULL; goto error; } msg->auth_request.public_key = publickey_from_string(session, publickey); string_free(algo); algo = NULL; string_free(publickey); publickey = NULL; if (msg->auth_request.public_key == NULL) { goto error; } msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; // has a valid signature ? if(has_sign) { SIGNATURE *signature = NULL; ssh_public_key public_key = msg->auth_request.public_key; ssh_string sign = NULL; ssh_buffer digest = NULL; sign = buffer_get_ssh_string(packet); if(sign == NULL) { ssh_log(session, SSH_LOG_PACKET, "Invalid signature packet from peer"); msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; goto error; } signature = signature_from_string(session, sign, public_key, public_key->type); digest = ssh_userauth_build_digest(session, msg, service_c); if ((digest == NULL || signature == NULL) || (digest != NULL && signature != NULL && sig_verify(session, public_key, signature, buffer_get(digest), buffer_get_len(digest)) < 0)) { ssh_log(session, SSH_LOG_PACKET, "Wrong signature from peer"); string_free(sign); sign = NULL; buffer_free(digest); digest = NULL; signature_free(signature); signature = NULL; msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; goto error; } else ssh_log(session, SSH_LOG_PACKET, "Valid signature received"); buffer_free(digest); digest = NULL; string_free(sign); sign = NULL; signature_free(signature); signature = NULL; msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; } SAFE_FREE(service_c); goto end; } msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; SAFE_FREE(method_c); goto end; error: string_free(user_s); string_free(service); string_free(method); SAFE_FREE(method_c); SAFE_FREE(service_c); ssh_message_free(msg); leave_function(); return SSH_PACKET_USED; end: ssh_message_queue(session,msg); leave_function(); return SSH_PACKET_USED; } SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_message msg = NULL; ssh_string type_s = NULL, originator = NULL, destination = NULL; char *type_c = NULL; uint32_t sender, window, packet_size, originator_port, destination_port; enter_function(); (void)type; (void)user; msg = ssh_message_new(session); if (msg == NULL) { ssh_set_error_oom(session); goto error; } msg->type = SSH_REQUEST_CHANNEL_OPEN; type_s = buffer_get_ssh_string(packet); if (type_s == NULL) { ssh_set_error_oom(session); goto error; } type_c = string_to_char(type_s); if (type_c == NULL) { ssh_set_error_oom(session); goto error; } ssh_log(session, SSH_LOG_PACKET, "Clients wants to open a %s channel", type_c); string_free(type_s); type_s=NULL; buffer_get_u32(packet, &sender); buffer_get_u32(packet, &window); buffer_get_u32(packet, &packet_size); msg->channel_request_open.sender = ntohl(sender); msg->channel_request_open.window = ntohl(window); msg->channel_request_open.packet_size = ntohl(packet_size); if (strcmp(type_c,"session") == 0) { msg->channel_request_open.type = SSH_CHANNEL_SESSION; SAFE_FREE(type_c); goto end; } if (strcmp(type_c,"direct-tcpip") == 0) { destination = buffer_get_ssh_string(packet); if (destination == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request_open.destination = string_to_char(type_s); if (msg->channel_request_open.destination == NULL) { ssh_set_error_oom(session); string_free(destination); goto error; } string_free(destination); buffer_get_u32(packet, &destination_port); msg->channel_request_open.destination_port = ntohl(destination_port); originator = buffer_get_ssh_string(packet); if (originator == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request_open.originator = string_to_char(type_s); if (msg->channel_request_open.originator == NULL) { ssh_set_error_oom(session); string_free(originator); goto error; } string_free(originator); buffer_get_u32(packet, &originator_port); msg->channel_request_open.originator_port = ntohl(originator_port); msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP; goto end; } if (strcmp(type_c,"forwarded-tcpip") == 0) { destination = buffer_get_ssh_string(packet); if (destination == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request_open.destination = string_to_char(type_s); if (msg->channel_request_open.destination == NULL) { ssh_set_error_oom(session); string_free(destination); goto error; } string_free(destination); buffer_get_u32(packet, &destination_port); msg->channel_request_open.destination_port = ntohl(destination_port); originator = buffer_get_ssh_string(packet); if (originator == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request_open.originator = string_to_char(type_s); if (msg->channel_request_open.originator == NULL) { ssh_set_error_oom(session); string_free(originator); goto error; } string_free(originator); buffer_get_u32(packet, &originator_port); msg->channel_request_open.originator_port = ntohl(originator_port); msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP; goto end; } if (strcmp(type_c,"x11") == 0) { originator = buffer_get_ssh_string(packet); if (originator == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request_open.originator = string_to_char(type_s); if (msg->channel_request_open.originator == NULL) { ssh_set_error_oom(session); string_free(originator); goto error; } string_free(originator); buffer_get_u32(packet, &originator_port); msg->channel_request_open.originator_port = ntohl(originator_port); msg->channel_request_open.type = SSH_CHANNEL_X11; goto end; } msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN; goto end; error: ssh_message_free(msg); msg=NULL; end: if(type_s != NULL) string_free(type_s); SAFE_FREE(type_c); if(msg != NULL) ssh_message_queue(session,msg); leave_function(); return SSH_PACKET_USED; } /* TODO: make this function accept a ssh_channel */ ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { ssh_session session = msg->session; ssh_channel chan = NULL; enter_function(); if (msg == NULL) { leave_function(); return NULL; } chan = channel_new(session); if (chan == NULL) { leave_function(); return NULL; } chan->local_channel = ssh_channel_new_id(session); chan->local_maxpacket = 35000; chan->local_window = 32000; chan->remote_channel = msg->channel_request_open.sender; chan->remote_maxpacket = msg->channel_request_open.packet_size; chan->remote_window = msg->channel_request_open.window; chan->state = SSH_CHANNEL_STATE_OPEN; if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) < 0) { goto error; } if (buffer_add_u32(session->out_buffer, htonl(chan->remote_channel)) < 0) { goto error; } if (buffer_add_u32(session->out_buffer, htonl(chan->local_channel)) < 0) { goto error; } if (buffer_add_u32(session->out_buffer, htonl(chan->local_window)) < 0) { goto error; } if (buffer_add_u32(session->out_buffer, htonl(chan->local_maxpacket)) < 0) { goto error; } ssh_log(session, SSH_LOG_PACKET, "Accepting a channel request_open for chan %d", chan->remote_channel); if (packet_send(session) != SSH_OK) { goto error; } leave_function(); return chan; error: channel_free(chan); leave_function(); return NULL; } /** @internal * @brief This function parses the last end of a channel request packet, * which is normally converted to a SSH message and placed on the queue. * @param session SSH session. * @param channel Channel the request is made on. * @param packet rest of the packet to be parsed. * @param request type of request. * @param want_reply want_reply field from the request * @returns SSH_OK or SSH_ERROR. */ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, const char *request, uint8_t want_reply) { ssh_message msg = NULL; enter_function(); msg = ssh_message_new(session); if (msg == NULL) { ssh_set_error_oom(session); goto error; } ssh_log(session, SSH_LOG_PACKET, "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", request, channel->local_channel, channel->remote_channel, want_reply); msg->type = SSH_REQUEST_CHANNEL; msg->channel_request.channel = channel; msg->channel_request.want_reply = want_reply; if (strcmp(request, "pty-req") == 0) { ssh_string term = NULL; char *term_c = NULL; term = buffer_get_ssh_string(packet); if (term == NULL) { ssh_set_error_oom(session); goto error; } term_c = string_to_char(term); if (term_c == NULL) { ssh_set_error_oom(session); string_free(term); goto error; } string_free(term); msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; msg->channel_request.TERM = term_c; buffer_get_u32(packet, &msg->channel_request.width); buffer_get_u32(packet, &msg->channel_request.height); buffer_get_u32(packet, &msg->channel_request.pxwidth); buffer_get_u32(packet, &msg->channel_request.pxheight); msg->channel_request.width = ntohl(msg->channel_request.width); msg->channel_request.height = ntohl(msg->channel_request.height); msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); msg->channel_request.modes = buffer_get_ssh_string(packet); if (msg->channel_request.modes == NULL) { SAFE_FREE(term_c); goto error; } goto end; } if (strcmp(request, "window-change") == 0) { msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; buffer_get_u32(packet, &msg->channel_request.width); buffer_get_u32(packet, &msg->channel_request.height); buffer_get_u32(packet, &msg->channel_request.pxwidth); buffer_get_u32(packet, &msg->channel_request.pxheight); msg->channel_request.width = ntohl(msg->channel_request.width); msg->channel_request.height = ntohl(msg->channel_request.height); msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); goto end; } if (strcmp(request, "subsystem") == 0) { ssh_string subsys = NULL; char *subsys_c = NULL; subsys = buffer_get_ssh_string(packet); if (subsys == NULL) { ssh_set_error_oom(session); goto error; } subsys_c = string_to_char(subsys); if (subsys_c == NULL) { ssh_set_error_oom(session); string_free(subsys); goto error; } string_free(subsys); msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; msg->channel_request.subsystem = subsys_c; goto end; } if (strcmp(request, "shell") == 0) { msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; goto end; } if (strcmp(request, "exec") == 0) { ssh_string cmd = NULL; cmd = buffer_get_ssh_string(packet); if (cmd == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; msg->channel_request.command = string_to_char(cmd); string_free(cmd); if (msg->channel_request.command == NULL) { goto error; } goto end; } if (strcmp(request, "env") == 0) { ssh_string name = NULL; ssh_string value = NULL; name = buffer_get_ssh_string(packet); if (name == NULL) { ssh_set_error_oom(session); goto error; } value = buffer_get_ssh_string(packet); if (value == NULL) { ssh_set_error_oom(session); string_free(name); goto error; } msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; msg->channel_request.var_name = string_to_char(name); msg->channel_request.var_value = string_to_char(value); if (msg->channel_request.var_name == NULL || msg->channel_request.var_value == NULL) { string_free(name); string_free(value); goto error; } string_free(name); string_free(value); goto end; } msg->channel_request.type = SSH_CHANNEL_UNKNOWN; end: ssh_message_queue(session,msg); leave_function(); return SSH_OK; error: ssh_message_free(msg); leave_function(); return SSH_ERROR; } int ssh_message_channel_request_reply_success(ssh_message msg) { uint32_t channel; if (msg == NULL) { return SSH_ERROR; } if (msg->channel_request.want_reply) { channel = msg->channel_request.channel->remote_channel; ssh_log(msg->session, SSH_LOG_PACKET, "Sending a channel_request success to channel %d", channel); if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_CHANNEL_SUCCESS) < 0) { return SSH_ERROR; } if (buffer_add_u32(msg->session->out_buffer, htonl(channel)) < 0) { return SSH_ERROR; } return packet_send(msg->session); } ssh_log(msg->session, SSH_LOG_PACKET, "The client doesn't want to know the request succeeded"); return SSH_OK; } /** @brief Retrieves a SSH message from SSH session * @param session SSH session * @return The SSH message received, NULL in case of error. * @warning This function blocks until a message has been received. Better * set up a callback if this behavior is unwanted. */ ssh_message ssh_message_get(ssh_session session) { ssh_message msg = NULL; enter_function(); do { if (ssh_handle_packets(session,-1) == SSH_ERROR) { leave_function(); return NULL; } msg=ssh_list_pop_head(ssh_message, session->ssh_message_list); } while(msg==NULL); msg=ssh_message_pop_head(session); leave_function(); return msg; } int ssh_message_type(ssh_message msg) { if (msg == NULL) { return -1; } return msg->type; } int ssh_message_subtype(ssh_message msg) { if (msg == NULL) { return -1; } switch(msg->type) { case SSH_REQUEST_AUTH: return msg->auth_request.method; case SSH_REQUEST_CHANNEL_OPEN: return msg->channel_request_open.type; case SSH_REQUEST_CHANNEL: return msg->channel_request.type; } return -1; } void ssh_message_free(ssh_message msg){ if (msg == NULL) { return; } switch(msg->type) { case SSH_REQUEST_AUTH: SAFE_FREE(msg->auth_request.username); if (msg->auth_request.password) { memset(msg->auth_request.password, 0, strlen(msg->auth_request.password)); SAFE_FREE(msg->auth_request.password); } publickey_free(msg->auth_request.public_key); break; case SSH_REQUEST_CHANNEL_OPEN: SAFE_FREE(msg->channel_request_open.originator); SAFE_FREE(msg->channel_request_open.destination); break; case SSH_REQUEST_CHANNEL: SAFE_FREE(msg->channel_request.TERM); SAFE_FREE(msg->channel_request.modes); SAFE_FREE(msg->channel_request.var_name); SAFE_FREE(msg->channel_request.var_value); SAFE_FREE(msg->channel_request.command); SAFE_FREE(msg->channel_request.subsystem); break; case SSH_REQUEST_SERVICE: SAFE_FREE(msg->service_request.service); break; } ZERO_STRUCTP(msg); SAFE_FREE(msg); } /** @internal * @brief adds the message to the current queue of messages to be parsed * @param session SSH session * @param message message to add to the queue */ void ssh_message_queue(ssh_session session, ssh_message message){ if(message){ if(session->ssh_message_list == NULL){ session->ssh_message_list=ssh_list_new(); } ssh_list_append(session->ssh_message_list, message); } } /** @internal * @brief Pops one message from the message list and dequeue it. * @param session SSH session. * @returns The head message, or NULL if it doesn't exist */ ssh_message ssh_message_pop_head(ssh_session session){ ssh_message msg=NULL; struct ssh_iterator *i; if(session->ssh_message_list == NULL) return NULL; i=ssh_list_get_iterator(session->ssh_message_list); if(i != NULL){ msg=ssh_iterator_value(ssh_message,i); ssh_list_remove(session->ssh_message_list,i); } return msg; } /** * @} */ /* vim: set ts=2 sw=2 et cindent: */