/** * \file client/spa_comm.c * * \brief Network-related functions for the fwknop client */ /* Fwknop is developed primarily by the people listed in the file 'AUTHORS'. * Copyright (C) 2009-2015 fwknop developers and contributors. For a full * list of contributors, see the file 'CREDITS'. * * License (GNU General Public License): * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * ***************************************************************************** */ #include "spa_comm.h" #include "utils.h" static void dump_transmit_options(const fko_cli_options_t *options) { char proto_str[PROTOCOL_BUFSIZE] = {0}; /* Protocol string */ proto_inttostr(options->spa_proto, proto_str, sizeof(proto_str)); log_msg(LOG_VERBOSITY_INFO, "Generating SPA packet:"); log_msg(LOG_VERBOSITY_INFO, " protocol: %s", proto_str); if (options->spa_src_port) log_msg(LOG_VERBOSITY_INFO, " source port: %d", options->spa_src_port); else log_msg(LOG_VERBOSITY_INFO, " source port: "); log_msg(LOG_VERBOSITY_INFO, " destination port: %d", options->spa_dst_port); log_msg(LOG_VERBOSITY_INFO, " IP/host: %s", options->spa_server_str); return; } /* Function to generate a header checksum. */ static unsigned short chksum(unsigned short *buf, int nbytes) { unsigned int sum; unsigned short oddbyte; sum = 0; while (nbytes > 1) { sum += *buf++; nbytes -= 2; } if (nbytes == 1) { oddbyte = 0; *((unsigned short *) &oddbyte) = *(unsigned short *) buf; sum += oddbyte; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short) ~sum; } /* Send the SPA data via UDP packet. */ static int send_spa_packet_tcp_or_udp(const char *spa_data, const int sd_len, const fko_cli_options_t *options) { int sock=-1, sock_success=0, res=0, error; struct addrinfo *result=NULL, *rp, hints; char port_str[MAX_PORT_STR_LEN+1] = {0}; if (options->test) { log_msg(LOG_VERBOSITY_NORMAL, "test mode enabled, SPA packet not actually sent."); return res; } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ if (options->spa_proto == FKO_PROTO_UDP) { /* Send the SPA data packet via an single UDP packet - this is the * most common usage. */ hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; } else { /* Send the SPA data packet via an established TCP connection. */ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; } snprintf(port_str, MAX_PORT_STR_LEN+1, "%d", options->spa_dst_port); #if AFL_FUZZING /* Make sure to never send SPA packets under AFL fuzzing cycles */ log_msg(LOG_VERBOSITY_NORMAL, "AFL fuzzing enabled, SPA packet not actually sent."); return res; #endif error = getaddrinfo(options->spa_server_str, port_str, &hints, &result); if (error != 0) { log_msg(LOG_VERBOSITY_ERROR, "error in getaddrinfo: %s", gai_strerror(error)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { /* Apply --server-resolve-ipv4 criteria */ if(options->spa_server_resolve_ipv4) { if(rp->ai_family != AF_INET) { log_msg(LOG_VERBOSITY_DEBUG, "Non-IPv4 resolution"); continue; } } sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock < 0) continue; if ((error = (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1))) { sock_success = 1; break; /* made it */ } else /* close the open socket if there was a connect error */ { #ifdef WIN32 closesocket(sock); #else close(sock); #endif } } if(result != NULL) freeaddrinfo(result); if (! sock_success) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_or_udp: Could not create socket: ", strerror(errno)); return -1; } res = send(sock, spa_data, sd_len, 0); if(res < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_or_udp: write error: ", strerror(errno)); } else if(res != sd_len) { log_msg(LOG_VERBOSITY_WARNING, "[#] Warning: bytes sent (%i) not spa data length (%i).", res, sd_len ); } #ifdef WIN32 closesocket(sock); #else close(sock); #endif return(res); } /* Send the SPA data via raw TCP packet. */ static int send_spa_packet_tcp_raw(const char *spa_data, const int sd_len, const struct sockaddr_in *saddr, const struct sockaddr_in *daddr, const fko_cli_options_t *options) { #ifdef WIN32 log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_raw: raw packets are not yet supported."); return(-1); #else int sock, res = 0; char pkt_data[2048] = {0}; /* Should be enough for our purposes */ struct iphdr *iph = (struct iphdr *) pkt_data; struct tcphdr *tcph = (struct tcphdr *) (pkt_data + sizeof (struct iphdr)); int hdrlen = sizeof(struct iphdr) + sizeof(struct tcphdr); /* Values for setsockopt. */ int one = 1; const int *so_val = &one; if (options->test) { log_msg(LOG_VERBOSITY_NORMAL, "test mode enabled, SPA packet not actually sent."); return res; } sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_raw: create socket: ", strerror(errno)); return(sock); } /* Put the spa data in place. */ memcpy((pkt_data + hdrlen), spa_data, sd_len); /* Construct our own header by filling in the ip/tcp header values, * starting with the IP header values. */ iph->ihl = 5; iph->version = 4; iph->tos = 0; /* Total size is header plus payload */ iph->tot_len = hdrlen + sd_len; /* The value here does not matter */ iph->id = random() & 0xffff; iph->frag_off = 0; iph->ttl = RAW_SPA_TTL; iph->protocol = IPPROTO_TCP; iph->check = 0; iph->saddr = saddr->sin_addr.s_addr; iph->daddr = daddr->sin_addr.s_addr; /* Now the TCP header values. */ tcph->source = saddr->sin_port; tcph->dest = daddr->sin_port; tcph->seq = htonl(1); tcph->ack_seq = 0; tcph->doff = 5; tcph->res1 = 0; /* TCP flags */ tcph->fin = 0; tcph->syn = 1; tcph->rst = 0; tcph->psh = 0; tcph->ack = 0; tcph->urg = 0; tcph->res2 = 0; tcph->window = htons(32767); tcph->check = 0; tcph->urg_ptr = 0; /* Now we can compute our checksum. */ iph->check = chksum((unsigned short *)pkt_data, iph->tot_len); /* Make sure the kernel knows the header is included in the data so it * doesn't try to insert its own header into the packet. */ if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, so_val, sizeof(one)) < 0) log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_raw: setsockopt HDRINCL: ", strerror(errno)); res = sendto (sock, pkt_data, iph->tot_len, 0, (struct sockaddr *)daddr, sizeof(*daddr)); if(res < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_tcp_raw: sendto error: ", strerror(errno)); } else if(res != sd_len + hdrlen) /* account for the header ?*/ { log_msg(LOG_VERBOSITY_WARNING, "[#] Warning: bytes sent (%i) not spa data length (%i).", res, sd_len ); } close(sock); return(res); #endif /* !WIN32 */ } /* Send the SPA data via raw UDP packet. */ static int send_spa_packet_udp_raw(const char *spa_data, const int sd_len, const struct sockaddr_in *saddr, const struct sockaddr_in *daddr, const fko_cli_options_t *options) { #ifdef WIN32 log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_udp_raw: raw packets are not yet supported."); return(-1); #else int sock, res = 0; char pkt_data[2048] = {0}; /* Should be enough for our purposes */ struct iphdr *iph = (struct iphdr *) pkt_data; struct udphdr *udph = (struct udphdr *) (pkt_data + sizeof (struct iphdr)); int hdrlen = sizeof(struct iphdr) + sizeof(struct udphdr); /* Values for setsockopt. */ int one = 1; const int *so_val = &one; if (options->test) { log_msg(LOG_VERBOSITY_NORMAL, "test mode enabled, SPA packet not actually sent."); return res; } sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_udp_raw: create socket: ", strerror(errno)); return(sock); } /* Put the spa data in place. */ memcpy((pkt_data + hdrlen), spa_data, sd_len); /* Construct our own header by filling in the ip/udp header values, * starting with the IP header values. */ iph->ihl = 5; iph->version = 4; iph->tos = 0; /* Total size is header plus payload */ iph->tot_len = hdrlen + sd_len; /* The value here does not matter */ iph->id = random() & 0xffff; iph->frag_off = 0; iph->ttl = RAW_SPA_TTL; iph->protocol = IPPROTO_UDP; iph->check = 0; iph->saddr = saddr->sin_addr.s_addr; iph->daddr = daddr->sin_addr.s_addr; /* Now the UDP header values. */ udph->source = saddr->sin_port; udph->dest = daddr->sin_port; udph->check = 0; udph->len = htons(sd_len + sizeof(struct udphdr)); /* Now we can compute our checksum. */ iph->check = chksum((unsigned short *)pkt_data, iph->tot_len); /* Make sure the kernel knows the header is included in the data so it * doesn't try to insert its own header into the packet. */ if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, so_val, sizeof(one)) < 0) log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_udp_raw: setsockopt HDRINCL: ", strerror(errno)); res = sendto (sock, pkt_data, iph->tot_len, 0, (struct sockaddr *)daddr, sizeof(*daddr)); if(res < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_udp_raw: sendto error: ", strerror(errno)); } else if(res != sd_len + hdrlen) /* account for the header ?*/ { log_msg(LOG_VERBOSITY_WARNING, "[#] Warning: bytes sent (%i) not spa data length (%i).", res, sd_len ); } close(sock); return(res); #endif /* !WIN32 */ } /* Send the SPA data via ICMP packet. */ static int send_spa_packet_icmp(const char *spa_data, const int sd_len, const struct sockaddr_in *saddr, const struct sockaddr_in *daddr, const fko_cli_options_t *options) { #ifdef WIN32 log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_icmp: raw packets are not yet supported."); return(-1); #else int res = 0, sock; char pkt_data[2048] = {0}; struct iphdr *iph = (struct iphdr *) pkt_data; struct icmphdr *icmph = (struct icmphdr *) (pkt_data + sizeof (struct iphdr)); int hdrlen = sizeof(struct iphdr) + sizeof(struct icmphdr); /* Values for setsockopt. */ int one = 1; const int *so_val = &one; if (options->test) { log_msg(LOG_VERBOSITY_NORMAL, "test mode enabled, SPA packet not actually sent."); return res; } sock = socket (PF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_icmp: create socket: ", strerror(errno)); return(sock); } /* Put the spa data in place. */ memcpy((pkt_data + hdrlen), spa_data, sd_len); /* Construct our own header by filling in the ip/icmp header values, * starting with the IP header values. */ iph->ihl = 5; iph->version = 4; iph->tos = 0; /* Total size is header plus payload */ iph->tot_len = hdrlen + sd_len; /* The value here does not matter */ iph->id = random() & 0xffff; iph->frag_off = 0; iph->ttl = RAW_SPA_TTL; iph->protocol = IPPROTO_ICMP; iph->check = 0; iph->saddr = saddr->sin_addr.s_addr; iph->daddr = daddr->sin_addr.s_addr; /* Now the ICMP header values. */ icmph->type = options->spa_icmp_type; icmph->code = options->spa_icmp_code; icmph->checksum = 0; if(icmph->type == ICMP_ECHO && icmph->code == 0) { icmph->un.echo.id = htons(random() & 0xffff); icmph->un.echo.sequence = htons(1); } /* Now we can compute our checksum. */ iph->check = chksum((unsigned short *)pkt_data, iph->tot_len); icmph->checksum = chksum((unsigned short *)icmph, sizeof(struct icmphdr) + sd_len); /* Make sure the kernel knows the header is included in the data so it * doesn't try to insert its own header into the packet. */ if (setsockopt (sock, IPPROTO_IP, IP_HDRINCL, so_val, sizeof(one)) < 0) log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_icmp: setsockopt HDRINCL: ", strerror(errno)); res = sendto (sock, pkt_data, iph->tot_len, 0, (struct sockaddr *)daddr, sizeof(*daddr)); if(res < 0) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet_icmp: sendto error: ", strerror(errno)); } else if(res != sd_len + hdrlen) /* account for icmp header */ { log_msg(LOG_VERBOSITY_WARNING, "[#] Warning: bytes sent (%i) not spa data length (%i).", res, sd_len); } close(sock); return(res); #endif /* !WIN32 */ } /* Send the SPA data packet via an HTTP request */ static int send_spa_packet_http(const char *spa_data, const int sd_len, fko_cli_options_t *options) { char http_buf[HTTP_MAX_REQUEST_LEN] = {0}, *spa_data_copy = NULL; char *ndx = options->http_proxy; int i, proxy_port = 0, is_err; spa_data_copy = malloc(sd_len+1); if (spa_data_copy == NULL) { log_msg(LOG_VERBOSITY_ERROR, "[*] Fatal, could not allocate memory."); return -1; } memcpy(spa_data_copy, spa_data, sd_len+1); /* Change "+" to "-", and "/" to "_" for HTTP requests (the server * side will translate these back before decrypting) */ for (i=0; i < sd_len; i++) { if (spa_data_copy[i] == '+') { spa_data_copy[i] = '-'; } else if (spa_data_copy[i] == '/') { spa_data_copy[i] = '_'; } } if(options->http_proxy[0] == 0x0) { snprintf(http_buf, HTTP_MAX_REQUEST_LEN, "GET /%s HTTP/1.0\r\nUser-Agent: %s\r\nAccept: */*\r\n" "Host: %s\r\nConnection: close\r\n\r\n", spa_data_copy, options->http_user_agent, options->spa_server_str /* hostname or IP */ ); } else /* we are sending the SPA packet through an HTTP proxy */ { /* Extract the hostname if it was specified as a URL. Actually, * we just move the start of the hostname to the begining of the * original string. */ if(strncasecmp(ndx, "http://", 7) == 0) memmove(ndx, ndx+7, strlen(ndx)+1); /* If there is a colon assume the proxy hostame or IP is on the left * and the proxy port is on the right. So we make the : a \0 and * extract the port value. */ ndx = strchr(options->http_proxy, ':'); if(ndx) { *ndx = '\0'; proxy_port = strtol_wrapper(ndx+1, 1, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "[-] proxy port value is invalid, must be in [%d-%d]", 1, MAX_PORT); free(spa_data_copy); return -1; } } /* If we have a valid port value, use it. */ if(proxy_port) options->spa_dst_port = proxy_port; snprintf(http_buf, HTTP_MAX_REQUEST_LEN, "GET http://%s/%s HTTP/1.0\r\nUser-Agent: %s\r\nAccept: */*\r\n" "Host: %s\r\nConnection: close\r\n\r\n", options->spa_server_str, spa_data_copy, options->http_user_agent, options->http_proxy /* hostname or IP */ ); strlcpy(options->spa_server_str, options->http_proxy, sizeof(options->spa_server_str)); } free(spa_data_copy); if (options->test) { log_msg(LOG_VERBOSITY_INFO, "%s", http_buf); log_msg(LOG_VERBOSITY_NORMAL, "Test mode enabled, SPA packet not actually sent."); return 0; } /* In AFL fuzzing mode, the following function will not send * the SPA packet. */ return send_spa_packet_tcp_or_udp(http_buf, strlen(http_buf), options); } /* Function used to send the SPA data. */ int send_spa_packet(fko_ctx_t ctx, fko_cli_options_t *options) { int res, sd_len; char *spa_data; struct sockaddr_in saddr, daddr; char ip_str[INET_ADDRSTRLEN] = {0}; /* String used to contain the ip addres of an hostname */ struct addrinfo hints; /* Structure used to set hints to resolve hostname */ #ifdef WIN32 WSADATA wsa_data; #endif /* Initialize the hint buffer */ memset(&hints, 0 , sizeof(hints)); /* Get our spa data here. */ res = fko_get_spa_data(ctx, &spa_data); if(res != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "send_spa_packet: Error #%i from fko_get_spa_data: %s", res, fko_errstr(res) ); return(-1); } sd_len = strlen(spa_data); #ifdef WIN32 /* Winsock needs to be initialized... */ res = WSAStartup( MAKEWORD(1,1), &wsa_data ); if( res != 0 ) { log_msg(LOG_VERBOSITY_ERROR, "Winsock initialization error %d", res ); return(-1); } #endif errno = 0; dump_transmit_options(options); if (options->spa_proto == FKO_PROTO_TCP || options->spa_proto == FKO_PROTO_UDP) { res = send_spa_packet_tcp_or_udp(spa_data, sd_len, options); } else if (options->spa_proto == FKO_PROTO_HTTP) { res = send_spa_packet_http(spa_data, sd_len, options); } else if (options->spa_proto == FKO_PROTO_TCP_RAW || options->spa_proto == FKO_PROTO_UDP_RAW || options->spa_proto == FKO_PROTO_ICMP) { memset(&saddr, 0, sizeof(saddr)); memset(&daddr, 0, sizeof(daddr)); saddr.sin_family = AF_INET; daddr.sin_family = AF_INET; /* Set source address and port */ if (options->spa_src_port) saddr.sin_port = htons(options->spa_src_port); else saddr.sin_port = INADDR_ANY; if (options->spoof_ip_src_str[0] != 0x00) { saddr.sin_addr.s_addr = inet_addr(options->spoof_ip_src_str); } else saddr.sin_addr.s_addr = INADDR_ANY; /* default */ if (saddr.sin_addr.s_addr == -1) { log_msg(LOG_VERBOSITY_ERROR, "Could not set source IP."); return -1; } /* Set destination port */ daddr.sin_port = htons(options->spa_dst_port); /* Set destination address. We use the default protocol to resolve * the ip address */ hints.ai_family = AF_INET; #if AFL_FUZZING /* Make sure to never send SPA packets under AFL fuzzing cycles */ log_msg(LOG_VERBOSITY_NORMAL, "AFL fuzzing enabled, SPA packet not actually sent."); return res; #endif if (resolve_dst_addr(options->spa_server_str, &hints, ip_str, sizeof(ip_str), options) != 0) { log_msg(LOG_VERBOSITY_ERROR, "[*] Unable to resolve %s as an ip address", options->spa_server_str); return -1; } else; daddr.sin_addr.s_addr = inet_addr(ip_str); if (options->spa_proto == FKO_PROTO_TCP_RAW) { res = send_spa_packet_tcp_raw(spa_data, sd_len, &saddr, &daddr, options); } else if (options->spa_proto == FKO_PROTO_UDP_RAW) { res = send_spa_packet_udp_raw(spa_data, sd_len, &saddr, &daddr, options); } else { res = send_spa_packet_icmp(spa_data, sd_len, &saddr, &daddr, options); } } else { /* --DSS XXX: What to we really want to do here? */ log_msg(LOG_VERBOSITY_ERROR, "%i is not a valid or supported protocol.", options->spa_proto); res = -1; } return res; } /* Function to write SPA packet data to the filesystem */ int write_spa_packet_data(fko_ctx_t ctx, const fko_cli_options_t *options) { FILE *fp; char *spa_data; int res; res = fko_get_spa_data(ctx, &spa_data); if(res != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "write_spa_packet_data: Error #%i from fko_get_spa_data: %s", res, fko_errstr(res) ); return(-1); } if (options->save_packet_file_append) { fp = fopen(options->save_packet_file, "a"); } else { unlink(options->save_packet_file); fp = fopen(options->save_packet_file, "w"); } if(fp == NULL) { log_msg(LOG_VERBOSITY_ERROR, "write_spa_packet_data: ", strerror(errno)); return(-1); } fprintf(fp, "%s\n", (spa_data == NULL) ? "" : spa_data); fclose(fp); return(0); } /***EOF***/