packet_t *packet_parse(uint8_t *data, size_t length, options_t options) { packet_t *packet = (packet_t*) safe_malloc(sizeof(packet_t)); buffer_t *buffer = buffer_create_with_data(BO_BIG_ENDIAN, data, length); /* Validate the size */ if(buffer_get_length(buffer) > MAX_PACKET_SIZE) { LOG_FATAL("Packet is too long: %zu bytes\n", buffer_get_length(buffer)); exit(1); } packet->packet_id = buffer_read_next_int16(buffer); packet->packet_type = (packet_type_t) buffer_read_next_int8(buffer); packet->session_id = buffer_read_next_int16(buffer); switch(packet->packet_type) { case PACKET_TYPE_SYN: packet->body.syn.seq = buffer_read_next_int16(buffer); packet->body.syn.options = buffer_read_next_int16(buffer); break; case PACKET_TYPE_MSG: if(options & OPT_CHUNKED_DOWNLOAD) { packet->body.msg.options.chunked.chunk = buffer_read_next_int32(buffer); } else { packet->body.msg.options.normal.seq = buffer_read_next_int16(buffer); packet->body.msg.options.normal.ack = buffer_read_next_int16(buffer); } packet->body.msg.data = buffer_read_remaining_bytes(buffer, &packet->body.msg.data_length, -1, FALSE); break; case PACKET_TYPE_FIN: packet->body.fin.reason = buffer_alloc_next_ntstring(buffer); break; case PACKET_TYPE_PING: packet->body.ping.data = buffer_alloc_next_ntstring(buffer); break; default: LOG_FATAL("Error: unknown message type (0x%02x)\n", packet->packet_type); exit(0); } buffer_destroy(buffer); return packet; }
void sessions_print(sessions_t *sessions) { session_t *session = sessions->first_session; while(session) { fprintf(stderr, "%s: %d bytes waiting\n", session->name, buffer_get_length(session->buffer)); session = (session_t*)session->next_session; } if(buffer_can_read_int8(sessions->buffer_data)) fprintf(stderr, "%d bytes buffered\n", buffer_get_length(sessions->buffer_data)); }
// Parses body. Returns true iff progress was made. bool frameparser_parse_body(frameparser *fp, buffer *b) { log_printf(LOG_LEVEL_DEBUG, "frameparser_parse_body\n"); if (fp->length_left == FP_LENGTH_UNKNOWN) { // Look for NUL terminator int nulpos = buffer_find_byte(b, '\x00'); if (nulpos < 0) { // No NUL, so grab everything int count = buffer_get_length(b); bytestring *body = frame_ensure_body(fp->cur_frame); buffer_append_bytestring(b, body, 0, count); buffer_consume(b, count); return true; } else if (nulpos == 0) { // NUL as next character, so the frame is complete fp->state = FP_STATE_END; return true; } else { // NUL after some other bytes, so grab them and the frame is complete bytestring *body = frame_ensure_body(fp->cur_frame); buffer_append_bytestring(b, body, 0, nulpos); buffer_consume(b, nulpos); fp->state = FP_STATE_END; return true; } } else { // Remaining length is known. Figure out how many bytes to grab. int count = buffer_get_length(b); if (fp->length_left < count) count = fp->length_left; // Grab the bytes bytestring *body = frame_ensure_body(fp->cur_frame); buffer_append_bytestring(b, body, 0, count); buffer_consume(b, count); fp->length_left -= count; // If we satisfied the expected length then the frame is complete if (fp->length_left == 0) fp->state = FP_STATE_END; return true; } }
// Parses a frame's command. Returns true iff progress was made. bool frameparser_parse_command(frameparser *fp, buffer *b) { log_printf(LOG_LEVEL_DEBUG, "frameparser_parse_command\n"); // Valid input in this state is a string followed by CR/LF or LF, which matches a known frame command // Try to find LF line terminator int lfpos = buffer_find_byte(b, '\x0A'); if (lfpos < 0) // No LF yet? { if (buffer_get_length(b) > LIMIT_FRAME_CMD_LINE_LEN) frameparser_set_error(fp, "Line length limit exceeded waiting for command"); return false; // No progress } else if (lfpos == 0) abort(); // Should never happen in this state // Figure out number of bytes in command string int len = lfpos; if (buffer_get_byte(b, len - 1) == '\x0D') len--; // Extract the command into a new bytestring bytestring *bs = bytestring_new(len); buffer_append_bytestring(b, bs, 0, len); bytestring_dump(bs); // Consume the current line buffer_consume(b, lfpos + 1); // Find the command code for this command frame_command cmd = frame_command_code(bytestring_get_bytes(bs), bytestring_get_length(bs)); // Clean up the bytestring bytestring_free(bs); // Make sure command is valid if (cmd == CMD_NONE) { frameparser_set_error(fp, "Unknown command"); return false; } // Set up a new frame structure to hold parsed data if (!fp->cur_frame) fp->cur_frame = frame_new(); // Store parsed data frame_set_command(fp->cur_frame, cmd); // Got a valid command, so headers should be next fp->state = FP_STATE_HEADER; return true; }
frameparser_outcome frameparser_parse(frameparser *fp, buffer *b) { // Parse as much as we can from the buffer bool done = false; while ((!done) && (buffer_get_length(b) > 0)) { if (!frameparser_parse_internal(fp, b)) done = true; } // Can't make any more parsing progress for now. Might as well compact the buffer. buffer_compact(b); if (fp->state == FP_STATE_ERROR) return FP_OUTCOME_ERROR; else if (fp->fin_frame) return FP_OUTCOME_FRAME; return FP_OUTCOME_WAITING; }
// Push waiting output out to the socket. void connection_pump_output(connection *c) { int writecount = 0; // If we have data waiting to go out, try writing it size_t outbuflen = buffer_get_length(c->outbuffer); if (outbuflen > 0) { writecount = buffer_output_fd(c->outbuffer, c->fd, outbuflen); if (writecount < 0) { int error = errno; if (error == EPIPE) connection_close(c); else if ((error != EAGAIN) && (error != EWOULDBLOCK)) connection_abort(c, error); // Unexpected error } } // Update write timestamp, if needed if (writecount > 0) gettimeofday(&c->writetime, NULL); }
// Parses keepalive linefeeds. Returns true iff progress was made. bool frameparser_parse_idle(frameparser *fp, buffer *b) { log_printf(LOG_LEVEL_DEBUG, "frameparser_parse_idle\n"); // Valid input in this state is a CR/LF pair, or bare LF uint8_t byte = buffer_get_byte(b, 0); if (byte == '\x0D') { // Should be CR/LF pair. Make sure we have at least two bytes size_t len = buffer_get_length(b); if (len < 2) return false; // No progress // Make sure second byte is LF byte = buffer_get_byte(b, 1); if (byte != '\x0A') { frameparser_set_error(fp, "Expected 0x0A after 0x0D, got 0x%02X", byte); return false; // No progress } // Eat the CR/LF pair buffer_consume(b, 2); return true; } else if (byte == '\x0A') { // Bare LF buffer_consume(b, 1); return true; } // Something else? Must be the start of a frame fp->state = FP_STATE_COMMAND; return true; }
/* Needs to be freed with safe_free() */ uint8_t *command_packet_to_bytes(command_packet_t *packet, size_t *length) { buffer_t *buffer = buffer_create(BO_BIG_ENDIAN); buffer_t *buffer_with_size = buffer_create(BO_BIG_ENDIAN); uint16_t packed_id; packed_id = (packet->is_request ? 0x0000 : 0x8000); packed_id |= (packet->request_id & 0x7FFF); buffer_add_int16(buffer, packed_id); buffer_add_int16(buffer, packet->command_id); switch(packet->command_id) { case COMMAND_PING: if(packet->is_request) buffer_add_ntstring(buffer, packet->r.request.body.ping.data); else buffer_add_ntstring(buffer, packet->r.response.body.ping.data); break; case COMMAND_SHELL: if(packet->is_request) buffer_add_ntstring(buffer, packet->r.request.body.shell.name); else buffer_add_int16(buffer, packet->r.response.body.shell.session_id); break; case COMMAND_EXEC: if(packet->is_request) { buffer_add_ntstring(buffer, packet->r.request.body.exec.name); buffer_add_ntstring(buffer, packet->r.request.body.exec.command); } else { buffer_add_int16(buffer, packet->r.response.body.exec.session_id); } break; case COMMAND_DOWNLOAD: if(packet->is_request) { buffer_add_ntstring(buffer, packet->r.request.body.download.filename); } else { buffer_add_bytes(buffer, packet->r.response.body.download.data, packet->r.response.body.download.length); } break; case COMMAND_UPLOAD: if(packet->is_request) { buffer_add_ntstring(buffer, packet->r.request.body.upload.filename); buffer_add_bytes(buffer, packet->r.request.body.upload.data, packet->r.request.body.upload.length); } else { } break; case COMMAND_SHUTDOWN: break; case TUNNEL_CONNECT: if(packet->is_request) { buffer_add_int32(buffer, packet->r.request.body.tunnel_connect.options); buffer_add_ntstring(buffer, packet->r.request.body.tunnel_connect.host); buffer_add_int16(buffer, packet->r.request.body.tunnel_connect.port); } else { buffer_add_int32(buffer, packet->r.response.body.tunnel_connect.tunnel_id); } break; case TUNNEL_DATA: if(packet->is_request) { buffer_add_int32(buffer, packet->r.request.body.tunnel_data.tunnel_id); buffer_add_bytes(buffer, packet->r.request.body.tunnel_data.data, packet->r.request.body.tunnel_data.length); } else { } break; case TUNNEL_CLOSE: if(packet->is_request) { buffer_add_int32(buffer, packet->r.request.body.tunnel_close.tunnel_id); buffer_add_ntstring(buffer, packet->r.request.body.tunnel_close.reason); } else { } break; case COMMAND_ERROR: if(packet->is_request) { buffer_add_int16(buffer, packet->r.request.body.error.status); buffer_add_ntstring(buffer, packet->r.request.body.error.reason); } else { buffer_add_int16(buffer, packet->r.response.body.error.status); buffer_add_ntstring(buffer, packet->r.response.body.error.reason); } break; default: LOG_FATAL("Unknown command_id: 0x%04x", packet->command_id); exit(1); } buffer_add_int32(buffer_with_size, buffer_get_length(buffer)); buffer_add_buffer(buffer_with_size, buffer); buffer_destroy(buffer); return buffer_create_string_and_destroy(buffer_with_size, length); }
// Parses a header line. Returns true iff progress was made. bool frameparser_parse_header(frameparser *fp, buffer *b) { log_printf(LOG_LEVEL_DEBUG, "frameparser_parse_header\n"); // Valid input in this state is either: // 1. CR/LF or LF alone, denoting the end of headers // 2. A key name, followed by a colon, followed by a value name, terminated with CR/LF or LF // Try to find LF line terminator int lfpos = buffer_find_byte(b, '\x0A'); if (lfpos < 0) // No LF yet? { if (buffer_get_length(b) > LIMIT_FRAME_HEADER_LINE_LEN) frameparser_set_error(fp, "Line length limit exceeded waiting for header"); return false; // No progress } else if (lfpos == 0) // LF alone { buffer_consume(b, 1); frameparser_parse_headers_complete(fp); return true; } else if ((lfpos == 1) && (buffer_get_byte(b, 0) == '\x0D')) // CR/LF alone { buffer_consume(b, 2); frameparser_parse_headers_complete(fp); return true; } // Figure out number of bytes in the line int len = lfpos; if (buffer_get_byte(b, len - 1) == '\x0D') len--; // Find the colon delimiter int colonpos = buffer_find_byte_within(b, ':', 0, len); if (colonpos < 0) // No colon? { frameparser_set_error(fp, "Expected colon delimiter on header line"); return false; } else if (colonpos == 0) // Starts with colon? { frameparser_set_error(fp, "Header name has zero length"); return false; } // Extract the key bytestring *key = bytestring_new(colonpos); buffer_append_bytestring(b, key, 0, colonpos); // Extract the value bytestring *val = bytestring_new(len - colonpos - 1); buffer_append_bytestring(b, val, colonpos + 1, len - colonpos - 1); // Consume the current line buffer_consume(b, lfpos + 1); // Unescape the key and value if needed frame_command cmd = frame_get_command(fp->cur_frame); if ((cmd != CMD_CONNECT) && (cmd != CMD_CONNECTED)) { key = unescape_header_bytestring(key); val = unescape_header_bytestring(val); } bytestring_dump(key); bytestring_dump(val); headerbundle *hb = frame_get_headerbundle(fp->cur_frame); headerbundle_append_header(hb, key, val); // The bundle takes ownership of key and val return true; }