/* * pkt.c * ptunnel is licensed under the BSD license: * * Copyright (c) 2004-2011, Daniel Stoedle , * Yellow Lemon Software. All rights reserved. * * Copyright (c) 2017-2019, Toni Uhlig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of the Yellow Lemon Software nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Contacting the author: * You can get in touch with me, Daniel Stødle (that's the Norwegian letter oe, * in case your text editor didn't realize), here: * * The official ptunnel website is here: * * * Note that the source code is best viewed with tabs set to 4 spaces. */ #ifndef WIN32 #include #include #include #endif #include #include "ptunnel.h" #include "pkt.h" #include "pdesc.h" #include "options.h" #include "utils.h" static proxy_desc_t * handle_incoming_tunnel_request(unsigned bytes, struct sockaddr_in * addr, int icmp_sock, icmp_echo_packet_t * const pkt, ping_tunnel_pkt_t * const pt_pkt) { struct timeval tt; struct in_addr in_addr; uint32_t init_state; proxy_desc_t * cur; pt_log(kLog_info, "Incoming tunnel request from %s.\n", inet_ntoa(*(struct in_addr *)&addr->sin_addr)); gettimeofday(&tt, 0); if (tt.tv_sec < seq_expiry_tbl[pt_pkt->id_no]) { pt_log(kLog_verbose, "Dropping request: ID was recently in use.\n"); return NULL; } in_addr.s_addr = pt_pkt->dst_ip; pt_log(kLog_info, "Starting new session to %s:%d with ID %d\n", inet_ntoa(in_addr), ntohl(pt_pkt->dst_port), pt_pkt->id_no); if ((opts.restrict_dst_ip && opts.given_dst_ip && opts.given_dst_ip != pt_pkt->dst_ip) || (opts.restrict_dst_port && (uint32_t)-1 != opts.given_dst_port && opts.given_dst_port != ntohl(pt_pkt->dst_port))) { pt_log(kLog_info, "Destination administratively prohibited!\n"); return NULL; } if (opts.password) { init_state = kProto_authenticate; } else { init_state = kProto_data; } cur = (proxy_desc_t *)create_and_insert_proxy_desc( pt_pkt->id_no, pkt->identifier, 0, addr, pt_pkt->dst_ip, ntohl(pt_pkt->dst_port), init_state, kProxy_flag); if (!cur) { /* if failed, abort. Logging is done in create_insert_proxy_desc */ pt_log(kLog_error, "Failed to create proxy descriptor!\n"); return NULL; } if (pt_pkt->data_len > 0) { handle_data(pkt, bytes, cur); } if (init_state == kProto_authenticate) { pt_log(kLog_debug, "Sending authentication challenge..\n"); /* Send challenge */ cur->challenge = generate_challenge(); memcpy(cur->buf, cur->challenge, sizeof(challenge_t)); queue_packet(icmp_sock, cur, cur->buf, sizeof(challenge_t), 0, 0, kProto_authenticate | cur->type_flag); } return cur; } static void handle_auth_request(unsigned bytes, int icmp_sock, icmp_echo_packet_t * const pkt, proxy_desc_t * const cur, challenge_t * const challenge) { if (!opts.password) { pt_log(kLog_error, "This proxy requires a password! " "Please supply one usin g the -x switch.\n"); send_termination_msg(cur, icmp_sock); cur->should_remove = 1; return; } #ifdef ENABLE_SHA512 if (opts.force_sha512) { pt_log(kLog_debug, "Got authentication challenge - sending SHA512 response\n"); generate_response_sha512(&challenge->plain, &challenge->digest); } else #endif { pt_log(kLog_debug, "Got authentication challenge - sending MD5 response\n"); generate_response_md5(&challenge->plain, &challenge->digest); } memcpy(cur->buf, challenge, sizeof(challenge_t)); queue_packet(icmp_sock, cur, cur->buf, sizeof(challenge_t), 0, 0, kProto_authenticate | cur->type_flag); /* We have authenticated locally. * It's up to the proxy now if it accepts our response or not.. */ cur->authenticated = 1; handle_data(pkt, bytes, cur); } static void handle_auth_response(unsigned bytes, int icmp_sock, icmp_echo_packet_t * const pkt, proxy_desc_t * const cur, challenge_t * const challenge) { pt_log(kLog_debug, "Received remote %s challenge response.\n", (challenge->digest.hash_type == HT_SHA512 ? "SHA512" : "MD5")); if ((!opts.force_sha512 && challenge->digest.hash_type == HT_MD5 && validate_challenge_md5(cur->challenge, &challenge->digest)) || #ifdef ENABLE_SHA512 (challenge->digest.hash_type == HT_SHA512 && validate_challenge_sha512(cur->challenge, &challenge->digest)) || #endif cur->authenticated) { pt_log(kLog_verbose, "Remote end authenticated successfully.\n"); /* Authentication has succeeded, so now we can proceed * to handle incoming TCP data. */ cur->authenticated = 1; cur->state = kProto_data; /* Insert the packet into the receive ring, to avoid * confusing the reliab ility mechanism. */ handle_data(pkt, bytes, cur); } else { pt_log(kLog_info, "Remote end failed authentication.\n"); send_termination_msg(cur, icmp_sock); cur->should_remove = 1; } } static void header_byteorder_ntoh(icmp_echo_packet_t * const icmp_pkt, ping_tunnel_pkt_t * const pt_pkt) { pt_pkt->state = ntohl(pt_pkt->state); icmp_pkt->identifier = ntohs(icmp_pkt->identifier); icmp_pkt->seq = ntohs(icmp_pkt->seq); pt_pkt->id_no = ntohs(pt_pkt->id_no); pt_pkt->seq_no = ntohs(pt_pkt->seq_no); } static proxy_desc_t * get_proxy_descriptor(uint16_t id_no) { proxy_desc_t * cur; /* Find the relevant connection, if it exists */ pthread_mutex_lock(&chain_lock); for (cur = chain; cur; cur = cur->next) { if (cur->id_no == id_no) { break; } } pthread_mutex_unlock(&chain_lock); return cur; } /* handle_proxy_packet: * Processes incoming ICMP packets for the proxy. The packet can come either from the * packet capture lib, or from the actual socket or both. * Input: A buffer pointing at the start of an IP header, the buffer length and the proxy * descriptor chain. */ void handle_packet(char * buf, unsigned bytes, int is_pcap, struct sockaddr_in * addr, int icmp_sock) { ip_packet_t * ip_pkt; icmp_echo_packet_t * pkt; ping_tunnel_pkt_t * pt_pkt; proxy_desc_t * cur; enum pkt_flag type_flag, pkt_flag, proxy_flag; challenge_t * challenge; proxy_flag = kProxy_flag; if (bytes < sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t)) { pt_log(kLog_verbose, "Skipping this packet - too short. " "Expect: %lu+%lu = %lu ; Got: %u\n", sizeof(icmp_echo_packet_t), sizeof(ping_tunnel_pkt_t), sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t), bytes); return; } if (opts.udp || opts.unprivileged) { ip_pkt = 0; pkt = (icmp_echo_packet_t *)buf; pt_pkt = (ping_tunnel_pkt_t *)pkt->data; } else { ip_pkt = (ip_packet_t *)buf; pkt = (icmp_echo_packet_t *)ip_pkt->data; pt_pkt = (ping_tunnel_pkt_t *)pkt->data; } if (ntohl(pt_pkt->magic) != opts.magic) { pt_log(kLog_verbose, "Ignored incoming packet. Magic value 0x%X mismatch.\n", ntohl(pt_pkt->magic)); return; } header_byteorder_ntoh(pkt, pt_pkt); cur = get_proxy_descriptor(pt_pkt->id_no); /* Handle the packet if it comes from "the other end." This is a bit tricky * to get right, since we receive both our own and the other end's packets. * Basically, a proxy will accept any packet from a user, regardless if it * has a valid connection or not. A user will only accept the packet if there * exists a connection to handle it. */ if (cur) { type_flag = cur->type_flag; if (type_flag == kProxy_flag) { cur->icmp_id = pkt->identifier; cur->ping_seq = pkt->seq; } if (!is_pcap) cur->xfer.icmp_in++; } else { type_flag = kProxy_flag; } pkt_flag = pt_pkt->state & kFlag_mask; pt_pkt->state &= ~kFlag_mask; if (pt_pkt->state > (kNum_proto_types - 1)) { pt_log(kLog_error, "Dropping packet with invalid state.\n"); return; } pt_log(kLog_sendrecv, "Recv: %4d [%4d] bytes " "[id = 0x%04X] [seq = %d] " "[seq_no = %d] [type = %s] " "[ack = %d] [icmp = %d] " "[user = %s] [pcap = %d]\n", bytes, ntohl(pt_pkt->data_len), pkt->identifier, ntohs(pkt->seq), pt_pkt->seq_no, state_name[pt_pkt->state & (~kFlag_mask)], ntohl(pt_pkt->ack), pkt->type, (pkt_flag == kUser_flag ? "yes" : "no"), is_pcap); /* This test essentially verifies that the packet comes from someone who isn't us. */ if ((pkt_flag == kUser_flag && type_flag == proxy_flag) || (pkt_flag == proxy_flag && type_flag == kUser_flag)) { pt_pkt->data_len = ntohl(pt_pkt->data_len); pt_pkt->ack = ntohl(pt_pkt->ack); if (pt_pkt->state == kProxy_start) { if (!cur && type_flag == proxy_flag) { cur = handle_incoming_tunnel_request(bytes, addr, icmp_sock, pkt, pt_pkt); if (!cur) { return; } } else if (type_flag == kUser_flag) { pt_log(kLog_error, "Dropping proxy session request - we are not a proxy!\n"); return; } else { pt_log(kLog_error, "Dropping duplicate proxy session request " "with ID %d and seq %d.\n", pt_pkt->id_no, pt_pkt->seq_no); } } else if (cur && pt_pkt->state == kProto_authenticate) { /* Sanity check packet length, and make sure it matches what we expect */ if (pt_pkt->data_len != sizeof(challenge_t)) { pt_log(kLog_error, "Received challenge packet, but data length " "is not as expected.\n"); pt_log(kLog_debug, "Data length: %u Expected: %lu\n", pt_pkt->data_len, sizeof(challenge_t)); cur->should_remove = 1; return; } /* Prevent packet data from being forwarded over TCP! */ pt_pkt->data_len = 0; challenge = (challenge_t *)pt_pkt->data; /* If client: Compute response to challenge */ if (type_flag == kUser_flag) { /* Required for integration tests w/ passwd set. */ pt_log(kLog_debug, "AUTH-REQUEST: Received ack-series starting at seq %d\n", pt_pkt->seq_no); handle_auth_request(bytes, icmp_sock, pkt, cur, challenge); return; } /* If proxy: Handle client's response to challenge */ else if (type_flag == proxy_flag) { cur->next_remote_seq++; handle_auth_response(bytes, icmp_sock, pkt, cur, challenge); return; } } /* Handle close-messages for connections we know about */ if (cur && pt_pkt->state == kProto_close) { pt_log(kLog_info, "Received session close from remote peer.\n"); cur->should_remove = 1; return; } /* The proxy will ignore any other packets from the client * until it has been authenticated. The packet resend mechanism * insures that this isn't problematic. */ if (type_flag == proxy_flag && opts.password && cur && !cur->authenticated) { pt_log(kLog_debug, "Ignoring packet with seq-no %d " "- not authenticated yet.\n", pt_pkt->seq_no); return; } if (cur && cur->sock) { double now = time_as_double(); if (pt_pkt->state != kProto_ack) { cur->last_data_activity = now; } if (pt_pkt->state == kProto_data || pt_pkt->state == kProxy_start || pt_pkt->state == kProto_ack) { if (pt_pkt->state == kProxy_start) { pt_pkt->data_len = 0; } handle_data(pkt, bytes, cur); } handle_ack(pt_pkt->ack, cur); cur->last_activity = now; } } } static void queue_payload_data(ping_tunnel_pkt_t * const pt_pkt, proxy_desc_t * const cur) { /* Check if we should add payload data to the queue. */ if (!cur->recv_ring[cur->recv_idx] && pt_pkt->state == kProto_data) { pt_log(kLog_debug, "Queing data packet: %d\n", pt_pkt->seq_no); cur->recv_ring[cur->recv_idx] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); cur->recv_wait_send++; cur->recv_idx++; } else { pt_log(kLog_debug, "Dup packet for %d ?\n", pt_pkt->seq_no); } cur->next_remote_seq++; if (cur->recv_idx >= cur->window_size) { cur->recv_idx = 0; } /* Check if we have already received some of the next packets. */ while (cur->recv_ring[cur->recv_idx]) { if (cur->recv_ring[cur->recv_idx]->seq_no == cur->next_remote_seq) { cur->next_remote_seq++; cur->recv_idx++; if (cur->recv_idx >= cur->window_size) { cur->recv_idx = 0; } } else { break; } } } static void queue_payload_data_out_of_order(ping_tunnel_pkt_t * const pt_pkt, proxy_desc_t * const cur) { int r, s, d, pos; pos = -1; /* If pos ends up staying -1, packet is discarded. */ r = cur->next_remote_seq; s = pt_pkt->seq_no; d = s - r; if (d < 0) { /* This packet _may_ be old, or seq_no may have wrapped around */ d = (s + 0xFFFF) - r; if (cur->window_size && d < cur->window_size) { /* Counter has wrapped, so we should add this packet to the recv ring */ pos = (cur->recv_idx + d) % cur->window_size; } } else if (cur->window_size && d < cur->window_size) { pos = (cur->recv_idx + d) % cur->window_size; } if (pos != -1) { if (!cur->recv_ring[pos]) { pt_log(kLog_verbose, "Out of order. Expected: %d Got: %d Inserted: %d " "(cur = %d)\n", cur->next_remote_seq, pt_pkt->seq_no, pos, cur->recv_idx); cur->recv_ring[pos] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data); cur->recv_wait_send++; } } else { pt_log(kLog_info, "Packet discarded - outside receive window.\n"); } } /* handle_data: * Utility function for handling kProto_data packets, and place the data it contains * onto the passed-in receive ring. */ void handle_data(icmp_echo_packet_t * pkt, int total_len, proxy_desc_t * cur) { ping_tunnel_pkt_t * pt_pkt = (ping_tunnel_pkt_t *)pkt->data; int expected_len = sizeof(ip_packet_t) + sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t); /* 20+8+28 */ /* Place packet in the receive ring, in its proper place. * This works as follows: * -1. Packet == ack packet? Perform ack, and continue. * 0. seq_no < next_remote_seq, and absolute difference is bigger than w size => discard * 1. If seq_no == next_remote_seq, we have no problems; just put it in the ring. * 2. If seq_no > next_remote_seq + remaining window size, discard packet. * Send resend request for missing packets. * 3. Else, put packet in the proper place in the ring * (don't overwrite if one is already there), but don't increment next_remote_seq_no * 4. If packed was not discarded, process ack info in packet. */ expected_len += pt_pkt->data_len; expected_len += expected_len % 2; if (opts.udp || opts.unprivileged) { expected_len -= sizeof(ip_packet_t); } if (total_len < expected_len) { pt_log(kLog_error, "Packet not completely received: %d Should be: %d.\n", total_len, expected_len); pt_log(kLog_debug, "Data length: %d Total length: %d\n", pt_pkt->data_len, total_len); /* just ignore that packet */ return; } if (pt_pkt->seq_no == cur->next_remote_seq) { queue_payload_data(pt_pkt, cur); } else { queue_payload_data_out_of_order(pt_pkt, cur); } } void handle_ack(uint32_t seq_no, proxy_desc_t * cur) { if (cur->send_wait_ack > 0) { int i, can_ack = 0, count = 0; i = cur->send_idx - 1; if (i < 0) { i = cur->window_size - 1; } pt_log(kLog_debug, "Received ack-series starting at seq %d\n", seq_no); while (count < cur->window_size) { if (!cur->send_ring[i].pkt) { break; } if (cur->send_ring[i].seq_no == seq_no) { can_ack = 1; } else if (!can_ack) { cur->send_first_ack = i; } if (can_ack) { free(cur->send_ring[i].pkt); cur->send_ring[i].pkt = 0; cur->send_ring[i].pkt_len = 0; cur->send_wait_ack--; } i--; if (i < 0) { i = cur->window_size - 1; } count++; } } else { pt_log(kLog_verbose, "Dropping superfluous acknowledgement for seq %d " "(no outstanding packets needing ack.)\n", seq_no); } }