/* * _libssh2_packet_add * * Create a new packet and attach it to the brigade. Called from the transport * layer when it as received a packet. */ int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, size_t datalen, int macstate) { int rc; if (session->packAdd_state == libssh2_NB_state_idle) { session->packAdd_data_head = 0; /* Zero the whole thing out */ memset(&session->packAdd_key_state, 0, sizeof(session->packAdd_key_state)); /* Zero the whole thing out */ memset(&session->packAdd_Qlstn_state, 0, sizeof(session->packAdd_Qlstn_state)); /* Zero the whole thing out */ memset(&session->packAdd_x11open_state, 0, sizeof(session->packAdd_x11open_state)); _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Packet type %d received, length=%d", (int) data[0], (int) datalen); if (macstate == LIBSSH2_MAC_INVALID) { if (session->macerror) { if (LIBSSH2_MACERROR(session, (char *) data, datalen) == 0) { /* Calling app has given the OK, Process it anyway */ macstate = LIBSSH2_MAC_CONFIRMED; } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } LIBSSH2_FREE(session, data); return -1; } } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } LIBSSH2_FREE(session, data); return -1; } } session->packAdd_state = libssh2_NB_state_allocated; } /* * =============================== NOTE =============================== * I know this is very ugly and not a really good use of "goto", but * this case statement would be even uglier to do it any other way */ if (session->packAdd_state == libssh2_NB_state_jump1) { goto libssh2_packet_add_jump_point1; } else if (session->packAdd_state == libssh2_NB_state_jump2) { goto libssh2_packet_add_jump_point2; } else if (session->packAdd_state == libssh2_NB_state_jump3) { goto libssh2_packet_add_jump_point3; } if (session->packAdd_state == libssh2_NB_state_allocated) { /* A couple exceptions to the packet adding rule: */ switch (data[0]) { case SSH_MSG_DISCONNECT: { char *message, *language; int reason, message_len, language_len; reason = _libssh2_ntohu32(data + 1); message_len = _libssh2_ntohu32(data + 5); /* 9 = packet_type(1) + reason(4) + message_len(4) */ message = (char *) data + 9; language_len = _libssh2_ntohu32(data + 9 + message_len); /* * This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL * (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood * it's zero length anyway) * Store a NULL in the last byte of the packet to terminate * the language string * With the lengths passed this isn't *REALLY* necessary, * but it's "kind" */ message[message_len] = '\0'; language = (char *) data + 9 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len); } _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Disconnect(%d): %s(%s)", reason, message, language); LIBSSH2_FREE(session, data); session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; session->packAdd_state = libssh2_NB_state_idle; return -1; } break; case SSH_MSG_IGNORE: /* As with disconnect, back it up one and add a trailing NULL */ memcpy(data + 4, data + 5, datalen - 5); data[datalen] = '\0'; if (session->ssh_msg_ignore) { LIBSSH2_IGNORE(session, (char *) data + 4, datalen - 5); } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; break; case SSH_MSG_DEBUG: { int always_display = data[0]; char *message, *language; int message_len, language_len; message_len = _libssh2_ntohu32(data + 2); /* 6 = packet_type(1) + display(1) + message_len(4) */ message = (char *) data + 6; language_len = _libssh2_ntohu32(data + 6 + message_len); /* * This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL * (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood * it's zero length anyway) * Store a NULL in the last byte of the packet to terminate * the language string * With the lengths passed this isn't *REALLY* necessary, * but it's "kind" */ message[message_len] = '\0'; language = (char *) data + 6 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_debug) { LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len); } /* * _libssh2_debug will actually truncate this for us so * that it's not an inordinate about of data */ _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Debug Packet: %s", message); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } break; case SSH_MSG_CHANNEL_EXTENDED_DATA: /* streamid(4) */ session->packAdd_data_head += 4; case SSH_MSG_CHANNEL_DATA: /* packet_type(1) + channelno(4) + datalen(4) */ session->packAdd_data_head += 9; session->packAdd_channel = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!session->packAdd_channel) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel, ignoring", 0); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } #ifdef LIBSSH2DEBUG { unsigned long stream_id = 0; if (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) { stream_id = _libssh2_ntohu32(data + 5); } _libssh2_debug(session, LIBSSH2_DBG_CONN, "%d bytes packet_add() for %lu/%lu/%lu", (int) (datalen - session->packAdd_data_head), session->packAdd_channel->local.id, session->packAdd_channel->remote.id, stream_id); } #endif if ((session->packAdd_channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) { /* Pretend we didn't receive this */ LIBSSH2_FREE(session, data); _libssh2_debug(session, LIBSSH2_DBG_CONN, "Ignoring extended data and refunding %d bytes", (int) (datalen - 13)); /* Adjust the window based on the block we just freed */ libssh2_packet_add_jump_point1: session->packAdd_state = libssh2_NB_state_jump1; rc = libssh2_channel_receive_window_adjust(session-> packAdd_channel, datalen - 13, 0); if (rc == PACKET_EAGAIN) { session->socket_block_directions = LIBSSH2_SESSION_BLOCK_OUTBOUND; return PACKET_EAGAIN; } session->packAdd_state = libssh2_NB_state_idle; return 0; } /* * REMEMBER! remote means remote as source of data, * NOT remote window! */ if (session->packAdd_channel->remote.packet_size < (datalen - session->packAdd_data_head)) { /* * Spec says we MAY ignore bytes sent beyond * packet_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "Packet contains more data than we offered" " to receive, truncating", 0); datalen = session->packAdd_channel->remote.packet_size + session->packAdd_data_head; } if (session->packAdd_channel->remote.window_size <= 0) { /* * Spec says we MAY ignore bytes sent beyond * window_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "The current receive window is full," " data ignored", 0); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } /* Reset EOF status */ session->packAdd_channel->remote.eof = 0; if ((datalen - session->packAdd_data_head) > session->packAdd_channel->remote.window_size) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current " "window allows, truncating", 0); datalen = session->packAdd_channel->remote.window_size + session->packAdd_data_head; } else { /* Now that we've received it, shrink our window */ session->packAdd_channel->remote.window_size -= datalen - session->packAdd_data_head; } break; case SSH_MSG_CHANNEL_EOF: { session->packAdd_channel = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!session->packAdd_channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } _libssh2_debug(session, LIBSSH2_DBG_CONN, "EOF received for channel %lu/%lu", session->packAdd_channel->local.id, session->packAdd_channel->remote.id); session->packAdd_channel->remote.eof = 1; LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } break; case SSH_MSG_CHANNEL_REQUEST: { if (_libssh2_ntohu32(data + 5) == sizeof("exit-status") - 1 && !memcmp("exit-status", data + 9, sizeof("exit-status") - 1)) { /* we've got "exit-status" packet. Set the session value */ session->packAdd_channel = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (session->packAdd_channel) { session->packAdd_channel->exit_status = _libssh2_ntohu32(data + 9 + sizeof("exit-status")); _libssh2_debug(session, LIBSSH2_DBG_CONN, "Exit status %lu received for channel %lu/%lu", session->packAdd_channel->exit_status, session->packAdd_channel->local.id, session->packAdd_channel->remote.id); } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } } break; case SSH_MSG_CHANNEL_CLOSE: { session->packAdd_channel = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!session->packAdd_channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } _libssh2_debug(session, LIBSSH2_DBG_CONN, "Close received for channel %lu/%lu", session->packAdd_channel->local.id, session->packAdd_channel->remote.id); session->packAdd_channel->remote.close = 1; session->packAdd_channel->remote.eof = 1; /* TODO: Add a callback for this */ LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; 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)) { libssh2_packet_add_jump_point2: session->packAdd_state = libssh2_NB_state_jump2; rc = packet_queue_listener(session, data, datalen, &session->packAdd_Qlstn_state); if (rc == PACKET_EAGAIN) { session->socket_block_directions = LIBSSH2_SESSION_BLOCK_OUTBOUND; return PACKET_EAGAIN; } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return rc; } if ((datalen >= (sizeof("x11") + 4)) && ((sizeof("x11") - 1) == _libssh2_ntohu32(data + 1)) && (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { libssh2_packet_add_jump_point3: session->packAdd_state = libssh2_NB_state_jump3; rc = packet_x11_open(session, data, datalen, &session->packAdd_x11open_state); if (rc == PACKET_EAGAIN) { session->socket_block_directions = LIBSSH2_SESSION_BLOCK_OUTBOUND; return PACKET_EAGAIN; } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return rc; } break; case SSH_MSG_CHANNEL_WINDOW_ADJUST: { unsigned long bytestoadd = _libssh2_ntohu32(data + 5); session->packAdd_channel = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (session->packAdd_channel && bytestoadd) { session->packAdd_channel->local.window_size += bytestoadd; } _libssh2_debug(session, LIBSSH2_DBG_CONN, "Window adjust received for channel %lu/%lu, adding %lu bytes, new window_size=%lu", session->packAdd_channel->local.id, session->packAdd_channel->remote.id, bytestoadd, session->packAdd_channel->local.window_size); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } break; } session->packAdd_state = libssh2_NB_state_sent; } if (session->packAdd_state == libssh2_NB_state_sent) { session->packAdd_packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); if (!session->packAdd_packet) { _libssh2_debug(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for LIBSSH2_PACKET"); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return -1; } memset(session->packAdd_packet, 0, sizeof(LIBSSH2_PACKET)); session->packAdd_packet->data = data; session->packAdd_packet->data_len = datalen; session->packAdd_packet->data_head = session->packAdd_data_head; session->packAdd_packet->mac = macstate; session->packAdd_packet->brigade = &session->packets; session->packAdd_packet->next = NULL; if (session->packets.tail) { session->packAdd_packet->prev = session->packets.tail; session->packAdd_packet->prev->next = session->packAdd_packet; session->packets.tail = session->packAdd_packet; } else { session->packets.head = session->packAdd_packet; session->packets.tail = session->packAdd_packet; session->packAdd_packet->prev = NULL; } session->packAdd_state = libssh2_NB_state_sent1; } if ((data[0] == SSH_MSG_KEXINIT && !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) || (session->packAdd_state == libssh2_NB_state_sent2)) { if (session->packAdd_state == libssh2_NB_state_sent1) { /* * Remote wants new keys * Well, it's already in the brigade, * let's just call back into ourselves */ _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Renegotiating Keys"); session->packAdd_state = libssh2_NB_state_sent2; } /* * The KEXINIT message has been added to the queue. The packAdd and * readPack states need to be reset because libssh2_kex_exchange * (eventually) calls upon _libssh2_transport_read to read the rest of * the key exchange conversation. */ session->readPack_state = libssh2_NB_state_idle; session->packet.total_num = 0; session->packAdd_state = libssh2_NB_state_idle; session->fullpacket_state = libssh2_NB_state_idle; /* * Also, don't use packAdd_key_state for key re-exchange, * as it will be wiped out in the middle of the exchange. * How about re-using the startup_key_state? */ memset(&session->startup_key_state, 0, sizeof(key_exchange_state_t)); /* * If there was a key reexchange failure, let's just hope we didn't * send NEWKEYS yet, otherwise remote will drop us like a rock */ rc = libssh2_kex_exchange(session, 1, &session->startup_key_state); if (rc == PACKET_EAGAIN) { return PACKET_EAGAIN; } } session->packAdd_state = libssh2_NB_state_idle; return 0; }
/* {{{ libssh2_packet_new * Create a new packet and attach it to the brigade */ static int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, size_t datalen, int macstate) { LIBSSH2_PACKET *packet; unsigned long data_head = 0; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Packet type %d received, length=%d", (int)data[0], (int)datalen); #endif if (macstate == LIBSSH2_MAC_INVALID) { if (session->macerror) { if (LIBSSH2_MACERROR(session, data, datalen) == 0) { /* Calling app has given the OK, Process it anyway */ macstate = LIBSSH2_MAC_CONFIRMED; } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } return -1; } } else { libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0); if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0); } return -1; } } /* A couple exceptions to the packet adding rule: */ switch (data[0]) { case SSH_MSG_DISCONNECT: { char *message, *language; int reason, message_len, language_len; reason = libssh2_ntohu32(data + 1); message_len = libssh2_ntohu32(data + 5); message = data + 9; /* packet_type(1) + reason(4) + message_len(4) */ language_len = libssh2_ntohu32(data + 9 + message_len); /* This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood it's zero length anyway * Store a NULL in the last byte of the packet to terminate the language string * With the lengths passed this isn't *REALLY* necessary, but it's "kind" */ message[message_len] = '\0'; language = data + 9 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len); } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Disconnect(%d): %s(%s)", reason, message, language); #endif LIBSSH2_FREE(session, data); session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; return -1; } break; case SSH_MSG_IGNORE: /* As with disconnect, back it up one and add a trailing NULL */ memcpy(data + 4, data + 5, datalen - 5); data[datalen] = '\0'; if (session->ssh_msg_ignore) { LIBSSH2_IGNORE(session, data + 4, datalen - 5); } LIBSSH2_FREE(session, data); return 0; break; case SSH_MSG_DEBUG: { int always_display = data[0]; char *message, *language; int message_len, language_len; message_len = libssh2_ntohu32(data + 2); message = data + 6; /* packet_type(1) + display(1) + message_len(4) */ language_len = libssh2_ntohu32(data + 6 + message_len); /* This is where we hack on the data a little, * Use the MSB of language_len to to a terminating NULL (In all liklihood it is already) * Shift the language tag back a byte (In all likelihood it's zero length anyway * Store a NULL in the last byte of the packet to terminate the language string * With the lengths passed this isn't *REALLY* necessary, but it's "kind" */ message[message_len] = '\0'; language = data + 6 + message_len + 3; if (language_len) { memcpy(language, language + 1, language_len); } language[language_len] = '\0'; if (session->ssh_msg_debug) { LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len); } #ifdef LIBSSH2_DEBUG_TRANSPORT /* _libssh2_debug will actually truncate this for us so that it's not an inordinate about of data */ _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Debug Packet: %s", message); #endif LIBSSH2_FREE(session, data); return 0; } break; case SSH_MSG_CHANNEL_EXTENDED_DATA: data_head += 4; /* streamid(4) */ case SSH_MSG_CHANNEL_DATA: data_head += 9; /* packet_type(1) + channelno(4) + datalen(4) */ { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel, ignoring", 0); LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION { unsigned long stream_id = 0; if (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) { stream_id = libssh2_ntohu32(data + 5); } _libssh2_debug(session, LIBSSH2_DBG_CONN, "%d bytes received for channel %lu/%lu stream #%lu", (int)(datalen - data_head), channel->local.id, channel->remote.id, stream_id); } #endif if ((channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) { /* Pretend we didn't receive this */ LIBSSH2_FREE(session, data); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Ignoring extended data and refunding %d bytes", (int)(datalen - 13)); #endif /* Adjust the window based on the block we just freed */ libssh2_channel_receive_window_adjust(channel, datalen - 13, 0); return 0; } /* REMEMBER! remote means remote as source of data, NOT remote window! */ if (channel->remote.packet_size < (datalen - data_head)) { /* Spec says we MAY ignore bytes sent beyond packet_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "Packet contains more data than we offered to receive, truncating", 0); datalen = channel->remote.packet_size + data_head; } if (channel->remote.window_size <= 0) { /* Spec says we MAY ignore bytes sent beyond window_size */ libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "The current receive window is full, data ignored", 0); LIBSSH2_FREE(session, data); return 0; } /* Reset EOF status */ channel->remote.eof = 0; if ((datalen - data_head) > channel->remote.window_size) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current window allows, truncating", 0); datalen = channel->remote.window_size + data_head; } else { /* Now that we've received it, shrink our window */ channel->remote.window_size -= datalen - data_head; } } break; case SSH_MSG_CHANNEL_EOF: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "EOF received for channel %lu/%lu", channel->local.id, channel->remote.id); #endif channel->remote.eof = 1; LIBSSH2_FREE(session, data); return 0; } break; case SSH_MSG_CHANNEL_REQUEST: { if (libssh2_ntohu32(data+5) == sizeof("exit-status") - 1 && !memcmp("exit-status", data + 9, sizeof("exit-status") - 1)) { /* we've got "exit-status" packet. Set the session value */ LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data+1)); if (channel) { channel->exit_status = libssh2_ntohu32(data + 9 + sizeof("exit-status")); #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Exit status %lu received for channel %lu/%lu", channel->exit_status, channel->local.id, channel->remote.id); #endif } LIBSSH2_FREE(session, data); return 0; } } break; case SSH_MSG_CHANNEL_CLOSE: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); if (!channel) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); return 0; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Close received for channel %lu/%lu", channel->local.id, channel->remote.id); #endif channel->remote.close = 1; /* TODO: Add a callback for this */ LIBSSH2_FREE(session, data); 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; } 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; case SSH_MSG_CHANNEL_WINDOW_ADJUST: { LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1)); unsigned long bytestoadd = libssh2_ntohu32(data + 5); if (channel && bytestoadd) { channel->local.window_size += bytestoadd; } #ifdef LIBSSH2_DEBUG_CONNECTION _libssh2_debug(session, LIBSSH2_DBG_CONN, "Window adjust received for channel %lu/%lu, adding %lu bytes, new window_size=%lu", channel->local.id, channel->remote.id, bytestoadd, channel->local.window_size); #endif LIBSSH2_FREE(session, data); return 0; } break; } packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); memset(packet, 0, sizeof(LIBSSH2_PACKET)); packet->data = data; packet->data_len = datalen; packet->data_head = data_head; packet->mac = macstate; packet->brigade = &session->packets; packet->next = NULL; if (session->packets.tail) { packet->prev = session->packets.tail; packet->prev->next = packet; session->packets.tail = packet; } else { session->packets.head = packet; session->packets.tail = packet; packet->prev = NULL; } if (data[0] == SSH_MSG_KEXINIT && !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) { /* Remote wants new keys * Well, it's already in the brigade, * let's just call back into ourselves */ #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Renegotiating Keys"); #endif libssh2_kex_exchange(session, 1); /* If there was a key reexchange failure, let's just hope we didn't send NEWKEYS yet, otherwise remote will drop us like a rock */ } return 0; }
/* * _libssh2_packet_add * * Create a new packet and attach it to the brigade. Called from the transport * layer when it has received a packet. * * The input pointer 'data' is pointing to allocated data that this function * is asked to deal with so on failure OR success, it must be freed fine. * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN. * * This function will always be called with 'datalen' greater than zero. */ int _libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, size_t datalen, int macstate) { int rc = 0; char *message=NULL; char *language=NULL; size_t message_len=0; size_t language_len=0; LIBSSH2_CHANNEL *channelp = NULL; size_t data_head = 0; unsigned char msg = data[0]; switch(session->packAdd_state) { case libssh2_NB_state_idle: _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Packet type %d received, length=%d", (int) msg, (int) datalen); if ((macstate == LIBSSH2_MAC_INVALID) && (!session->macerror || LIBSSH2_MACERROR(session, (char *) data, datalen))) { /* Bad MAC input, but no callback set or non-zero return from the callback */ LIBSSH2_FREE(session, data); return _libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid MAC received"); } session->packAdd_state = libssh2_NB_state_allocated; break; case libssh2_NB_state_jump1: goto libssh2_packet_add_jump_point1; case libssh2_NB_state_jump2: goto libssh2_packet_add_jump_point2; case libssh2_NB_state_jump3: goto libssh2_packet_add_jump_point3; case libssh2_NB_state_jump4: goto libssh2_packet_add_jump_point4; case libssh2_NB_state_jump5: goto libssh2_packet_add_jump_point5; default: /* nothing to do */ break; } if (session->packAdd_state == libssh2_NB_state_allocated) { /* A couple exceptions to the packet adding rule: */ switch (msg) { /* byte SSH_MSG_DISCONNECT uint32 reason code string description in ISO-10646 UTF-8 encoding [RFC3629] string language tag [RFC3066] */ case SSH_MSG_DISCONNECT: if(datalen >= 5) { size_t reason = _libssh2_ntohu32(data + 1); if(datalen >= 9) { message_len = _libssh2_ntohu32(data + 5); if(message_len < datalen-13) { /* 9 = packet_type(1) + reason(4) + message_len(4) */ message = (char *) data + 9; language_len = _libssh2_ntohu32(data + 9 + message_len); language = (char *) data + 9 + message_len + 4; if(language_len > (datalen-13-message_len)) { /* bad input, clear info */ language = message = NULL; language_len = message_len = 0; } } else /* bad size, clear it */ message_len=0; } if (session->ssh_msg_disconnect) { LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len); } _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Disconnect(%d): %s(%s)", reason, message, language); } LIBSSH2_FREE(session, data); session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; session->packAdd_state = libssh2_NB_state_idle; return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, "socket disconnect"); /* byte SSH_MSG_IGNORE string data */ case SSH_MSG_IGNORE: if (datalen >= 2) { if (session->ssh_msg_ignore) { LIBSSH2_IGNORE(session, (char *) data + 1, datalen - 1); } } else if (session->ssh_msg_ignore) { LIBSSH2_IGNORE(session, "", 0); } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; /* byte SSH_MSG_DEBUG boolean always_display string message in ISO-10646 UTF-8 encoding [RFC3629] string language tag [RFC3066] */ case SSH_MSG_DEBUG: if(datalen >= 2) { int always_display= data[1]; if(datalen >= 6) { message_len = _libssh2_ntohu32(data + 2); if(message_len <= (datalen - 10)) { /* 6 = packet_type(1) + display(1) + message_len(4) */ message = (char *) data + 6; language_len = _libssh2_ntohu32(data + 6 + message_len); if(language_len <= (datalen - 10 - message_len)) language = (char *) data + 10 + message_len; } } if (session->ssh_msg_debug) { LIBSSH2_DEBUG(session, always_display, message, message_len, language, language_len); } } /* * _libssh2_debug will actually truncate this for us so * that it's not an inordinate about of data */ _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Debug Packet: %s", message); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; /* byte SSH_MSG_GLOBAL_REQUEST string request name in US-ASCII only boolean want reply .... request-specific data follows */ case SSH_MSG_GLOBAL_REQUEST: if(datalen >= 5) { uint32_t len =0; unsigned char want_reply=0; len = _libssh2_ntohu32(data + 1); if(datalen >= (6 + len)) { want_reply = data[5 + len]; _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Received global request type %.*s (wr %X)", len, data + 5, want_reply); } if (want_reply) { static const unsigned char packet = SSH_MSG_REQUEST_FAILURE; libssh2_packet_add_jump_point5: session->packAdd_state = libssh2_NB_state_jump5; rc = _libssh2_transport_send(session, &packet, 1, NULL, 0); if (rc == LIBSSH2_ERROR_EAGAIN) return rc; } } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; /* byte SSH_MSG_CHANNEL_EXTENDED_DATA uint32 recipient channel uint32 data_type_code string data */ case SSH_MSG_CHANNEL_EXTENDED_DATA: /* streamid(4) */ data_head += 4; /* fall-through */ /* byte SSH_MSG_CHANNEL_DATA uint32 recipient channel string data */ case SSH_MSG_CHANNEL_DATA: /* packet_type(1) + channelno(4) + datalen(4) */ data_head += 9; if(datalen >= data_head) channelp = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!channelp) { _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel"); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } #ifdef LIBSSH2DEBUG { uint32_t stream_id = 0; if (msg == SSH_MSG_CHANNEL_EXTENDED_DATA) stream_id = _libssh2_ntohu32(data + 5); _libssh2_debug(session, LIBSSH2_TRACE_CONN, "%d bytes packet_add() for %lu/%lu/%lu", (int) (datalen - data_head), channelp->local.id, channelp->remote.id, stream_id); } #endif if ((channelp->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && (msg == SSH_MSG_CHANNEL_EXTENDED_DATA)) { /* Pretend we didn't receive this */ LIBSSH2_FREE(session, data); _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Ignoring extended data and refunding %d bytes", (int) (datalen - 13)); if (channelp->read_avail + datalen - data_head >= channelp->remote.window_size) datalen = channelp->remote.window_size - channelp->read_avail + data_head; channelp->remote.window_size -= datalen - data_head; _libssh2_debug(session, LIBSSH2_TRACE_CONN, "shrinking window size by %lu bytes to %lu, read_avail %lu", datalen - data_head, channelp->remote.window_size, channelp->read_avail); session->packAdd_channelp = channelp; /* Adjust the window based on the block we just freed */ libssh2_packet_add_jump_point1: session->packAdd_state = libssh2_NB_state_jump1; rc = _libssh2_channel_receive_window_adjust(session-> packAdd_channelp, datalen - 13, 1, NULL); if (rc == LIBSSH2_ERROR_EAGAIN) return rc; session->packAdd_state = libssh2_NB_state_idle; return 0; } /* * REMEMBER! remote means remote as source of data, * NOT remote window! */ if (channelp->remote.packet_size < (datalen - data_head)) { /* * Spec says we MAY ignore bytes sent beyond * packet_size */ _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "Packet contains more data than we offered" " to receive, truncating"); datalen = channelp->remote.packet_size + data_head; } if (channelp->remote.window_size <= channelp->read_avail) { /* * Spec says we MAY ignore bytes sent beyond * window_size */ _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "The current receive window is full," " data ignored"); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } /* Reset EOF status */ channelp->remote.eof = 0; if (channelp->read_avail + datalen - data_head > channelp->remote.window_size) { _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current " "window allows, truncating"); datalen = channelp->remote.window_size - channelp->read_avail + data_head; } /* Update the read_avail counter. The window size will be * updated once the data is actually read from the queue * from an upper layer */ channelp->read_avail += datalen - data_head; _libssh2_debug(session, LIBSSH2_TRACE_CONN, "increasing read_avail by %lu bytes to %lu/%lu", (long)(datalen - data_head), (long)channelp->read_avail, (long)channelp->remote.window_size); break; /* byte SSH_MSG_CHANNEL_EOF uint32 recipient channel */ case SSH_MSG_CHANNEL_EOF: if(datalen >= 5) channelp = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!channelp) /* We may have freed already, just quietly ignore this... */ ; else { _libssh2_debug(session, LIBSSH2_TRACE_CONN, "EOF received for channel %lu/%lu", channelp->local.id, channelp->remote.id); channelp->remote.eof = 1; } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; /* byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string request type in US-ASCII characters only boolean want reply .... type-specific data follows */ case SSH_MSG_CHANNEL_REQUEST: if(datalen >= 9) { uint32_t channel = _libssh2_ntohu32(data + 1); uint32_t len = _libssh2_ntohu32(data + 5); unsigned char want_reply = 1; if(len < (datalen - 10)) want_reply = data[9 + len]; _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Channel %d received request type %.*s (wr %X)", channel, len, data + 9, want_reply); if (len == sizeof("exit-status") - 1 && !memcmp("exit-status", data + 9, sizeof("exit-status") - 1)) { /* we've got "exit-status" packet. Set the session value */ if(datalen >= 20) channelp = _libssh2_channel_locate(session, channel); if (channelp) { channelp->exit_status = _libssh2_ntohu32(data + 9 + sizeof("exit-status")); _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Exit status %lu received for " "channel %lu/%lu", channelp->exit_status, channelp->local.id, channelp->remote.id); } } else if (len == sizeof("exit-signal") - 1 && !memcmp("exit-signal", data + 9, sizeof("exit-signal") - 1)) { /* command terminated due to signal */ if(datalen >= 20) channelp = _libssh2_channel_locate(session, channel); if (channelp) { /* set signal name (without SIG prefix) */ uint32_t namelen = _libssh2_ntohu32(data + 9 + sizeof("exit-signal")); channelp->exit_signal = LIBSSH2_ALLOC(session, namelen + 1); if (!channelp->exit_signal) rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, "memory for signal name"); else { memcpy(channelp->exit_signal, data + 13 + sizeof("exit_signal"), namelen); channelp->exit_signal[namelen] = '\0'; /* TODO: save error message and language tag */ _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Exit signal %s received for " "channel %lu/%lu", channelp->exit_signal, channelp->local.id, channelp->remote.id); } } } if (want_reply) { unsigned char packet[5]; libssh2_packet_add_jump_point4: session->packAdd_state = libssh2_NB_state_jump4; packet[0] = SSH_MSG_CHANNEL_FAILURE; memcpy(&packet[1], data+1, 4); rc = _libssh2_transport_send(session, packet, 5, NULL, 0); if (rc == LIBSSH2_ERROR_EAGAIN) return rc; } } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return rc; /* byte SSH_MSG_CHANNEL_CLOSE uint32 recipient channel */ case SSH_MSG_CHANNEL_CLOSE: if(datalen >= 5) channelp = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if (!channelp) { /* We may have freed already, just quietly ignore this... */ LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; } _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Close received for channel %lu/%lu", channelp->local.id, channelp->remote.id); channelp->remote.close = 1; channelp->remote.eof = 1; LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; /* byte SSH_MSG_CHANNEL_OPEN string "session" uint32 sender channel uint32 initial window size uint32 maximum packet size */ case SSH_MSG_CHANNEL_OPEN: if(datalen < 17) ; else if ((datalen >= (sizeof("forwarded-tcpip") + 4)) && ((sizeof("forwarded-tcpip") - 1) == _libssh2_ntohu32(data + 1)) && (memcmp(data + 5, "forwarded-tcpip", sizeof("forwarded-tcpip") - 1) == 0)) { /* init the state struct */ memset(&session->packAdd_Qlstn_state, 0, sizeof(session->packAdd_Qlstn_state)); libssh2_packet_add_jump_point2: session->packAdd_state = libssh2_NB_state_jump2; rc = packet_queue_listener(session, data, datalen, &session->packAdd_Qlstn_state); } else if ((datalen >= (sizeof("x11") + 4)) && ((sizeof("x11") - 1) == _libssh2_ntohu32(data + 1)) && (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { /* init the state struct */ memset(&session->packAdd_x11open_state, 0, sizeof(session->packAdd_x11open_state)); libssh2_packet_add_jump_point3: session->packAdd_state = libssh2_NB_state_jump3; rc = packet_x11_open(session, data, datalen, &session->packAdd_x11open_state); } if (rc == LIBSSH2_ERROR_EAGAIN) return rc; LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return rc; /* byte SSH_MSG_CHANNEL_WINDOW_ADJUST uint32 recipient channel uint32 bytes to add */ case SSH_MSG_CHANNEL_WINDOW_ADJUST: if(datalen < 9) ; else { uint32_t bytestoadd = _libssh2_ntohu32(data + 5); channelp = _libssh2_channel_locate(session, _libssh2_ntohu32(data + 1)); if(channelp) { channelp->local.window_size += bytestoadd; _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Window adjust for channel %lu/%lu, " "adding %lu bytes, new window_size=%lu", channelp->local.id, channelp->remote.id, bytestoadd, channelp->local.window_size); } } LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return 0; default: break; } session->packAdd_state = libssh2_NB_state_sent; } if (session->packAdd_state == libssh2_NB_state_sent) { LIBSSH2_PACKET *packetp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); if (!packetp) { _libssh2_debug(session, LIBSSH2_ERROR_ALLOC, "memory for packet"); LIBSSH2_FREE(session, data); session->packAdd_state = libssh2_NB_state_idle; return LIBSSH2_ERROR_ALLOC; } packetp->data = data; packetp->data_len = datalen; packetp->data_head = data_head; _libssh2_list_add(&session->packets, &packetp->node); session->packAdd_state = libssh2_NB_state_sent1; } if ((msg == SSH_MSG_KEXINIT && !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) || (session->packAdd_state == libssh2_NB_state_sent2)) { if (session->packAdd_state == libssh2_NB_state_sent1) { /* * Remote wants new keys * Well, it's already in the brigade, * let's just call back into ourselves */ _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Renegotiating Keys"); session->packAdd_state = libssh2_NB_state_sent2; } /* * The KEXINIT message has been added to the queue. The packAdd and * readPack states need to be reset because _libssh2_kex_exchange * (eventually) calls upon _libssh2_transport_read to read the rest of * the key exchange conversation. */ session->readPack_state = libssh2_NB_state_idle; session->packet.total_num = 0; session->packAdd_state = libssh2_NB_state_idle; session->fullpacket_state = libssh2_NB_state_idle; memset(&session->startup_key_state, 0, sizeof(key_exchange_state_t)); /* * If there was a key reexchange failure, let's just hope we didn't * send NEWKEYS yet, otherwise remote will drop us like a rock */ rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); if (rc == LIBSSH2_ERROR_EAGAIN) return rc; } session->packAdd_state = libssh2_NB_state_idle; return 0; }