fwknop/client/http_resolve_host.c
Pierre Pronchery 0b475ec7b3 Use execvp() instead of execvpe()
execvp() is (usually) equivalent to execvpe(), without enforcing any
change to the environment. However, unlike execvp(), execvpe() is not
standardized by POSIX, and may therefore not be available nor detected
when configuring the project (like on NetBSD).

No place could be found in fwknop to be using execvpe() and changing the
environment. Therefore it seems only logical (and safer) to use execvp()
instead.

This also updates the tests to reflect this change.
2018-08-06 12:04:45 +02:00

574 lines
16 KiB
C

/**
* \file client/http_resolve_host.c
*
* \brief Routine for using an http request to obtain a client's IP
* address as seen from the outside world.
*/
/* 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 "fwknop_common.h"
#include "utils.h"
#include <errno.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <netdb.h>
#include <sys/wait.h>
#endif
#if AFL_FUZZING
#define AFL_SET_RESOLVE_HOST "192.168.12.123" /* force to non-routable IP */
#endif
struct url
{
char port[MAX_PORT_STR_LEN+1];
char host[MAX_URL_HOST_LEN+1];
char path[MAX_URL_PATH_LEN+1];
};
static int
try_url(struct url *url, fko_cli_options_t *options)
{
int sock=-1, sock_success=0, res, error, http_buf_len, i;
int bytes_read = 0, position = 0;
int o1, o2, o3, o4;
struct addrinfo *result=NULL, *rp, hints;
char http_buf[HTTP_MAX_REQUEST_LEN] = {0};
char http_response[HTTP_MAX_RESPONSE_LEN] = {0};
char *ndx;
#ifdef WIN32
WSADATA wsa_data;
/* 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
/* Build our HTTP request to resolve the external IP (this is similar to
* to contacting whatismyip.org, but using a different URL).
*/
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",
url->path,
options->http_user_agent,
url->host
);
http_buf_len = strlen(http_buf);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
#if AFL_FUZZING
/* Make sure to not generate any resolution requests when compiled
* for AFL fuzzing cycles
*/
strlcpy(options->allow_ip_str, AFL_SET_RESOLVE_HOST,
sizeof(options->allow_ip_str));
log_msg(LOG_VERBOSITY_INFO,
"\n[+] AFL fuzzing cycle, force IP resolution to: %s",
options->allow_ip_str);
return(1);
#endif
error = getaddrinfo(url->host, url->port, &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) {
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, "resolve_ip_http: Could not create socket: ", strerror(errno));
return(-1);
}
log_msg(LOG_VERBOSITY_DEBUG, "\nHTTP request: %s", http_buf);
res = send(sock, http_buf, http_buf_len, 0);
if(res < 0)
{
log_msg(LOG_VERBOSITY_ERROR, "resolve_ip_http: write error: ", strerror(errno));
}
else if(res != http_buf_len)
{
log_msg(LOG_VERBOSITY_WARNING,
"[#] Warning: bytes sent (%i) not spa data length (%i).",
res, http_buf_len
);
}
do
{
memset(http_buf, 0x0, sizeof(http_buf));
bytes_read = recv(sock, http_buf, sizeof(http_buf), 0);
if ( bytes_read > 0 ) {
if(position + bytes_read >= HTTP_MAX_RESPONSE_LEN)
break;
memcpy(&http_response[position], http_buf, bytes_read);
position += bytes_read;
}
}
while ( bytes_read > 0 );
http_response[HTTP_MAX_RESPONSE_LEN-1] = '\0';
#ifdef WIN32
closesocket(sock);
#else
close(sock);
#endif
log_msg(LOG_VERBOSITY_DEBUG, "\nHTTP response: %s", http_response);
/* Move to the end of the HTTP header and to the start of the content.
*/
ndx = strstr(http_response, "\r\n\r\n");
if(ndx == NULL)
{
log_msg(LOG_VERBOSITY_ERROR, "Did not find the end of HTTP header.");
return(-1);
}
ndx += 4;
/* Walk along the content to try to find the end of the IP address.
* Note: We are expecting the content to be just an IP address
* (possibly followed by whitespace or other not-digit value).
*/
for(i=0; i<MAX_IPV4_STR_LEN; i++) {
if(! isdigit((int)(unsigned char)*(ndx+i)) && *(ndx+i) != '.')
break;
}
/* Terminate at the first non-digit and non-dot.
*/
*(ndx+i) = '\0';
/* Now that we have what we think is an IP address string. We make
* sure the format and values are sane.
*/
if((sscanf(ndx, "%u.%u.%u.%u", &o1, &o2, &o3, &o4)) == 4
&& o1 >= 0 && o1 <= 255
&& o2 >= 0 && o2 <= 255
&& o3 >= 0 && o3 <= 255
&& o4 >= 0 && o4 <= 255)
{
strlcpy(options->allow_ip_str, ndx, sizeof(options->allow_ip_str));
log_msg(LOG_VERBOSITY_INFO,
"\n[+] Resolved external IP (via http://%s%s) as: %s",
url->host,
url->path,
options->allow_ip_str);
return(1);
}
else
{
log_msg(LOG_VERBOSITY_ERROR,
"[-] From http://%s%s\n Invalid IP (%s) in HTTP response:\n\n%s",
url->host, url->path, ndx, http_response);
return(-1);
}
}
static int
parse_url(char *res_url, struct url* url)
{
char *s_ndx, *e_ndx;
int tlen, tlen_offset, port, is_err;
/* Strip off https:// or http:// portion if necessary
*/
if(strncasecmp(res_url, "https://", 8) == 0)
s_ndx = res_url + 8;
else if(strncasecmp(res_url, "http://", 7) == 0)
s_ndx = res_url + 7;
else
s_ndx = res_url;
/* Look for a colon in case an alternate port was specified.
*/
e_ndx = strchr(s_ndx, ':');
if(e_ndx != NULL)
{
port = strtol_wrapper(e_ndx+1, 1, MAX_PORT, NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
{
log_msg(LOG_VERBOSITY_ERROR,
"[*] resolve-url port value is invalid, must be in [%d-%d]",
1, MAX_PORT);
return(-1);
}
snprintf(url->port, sizeof(url->port)-1, "%u", port);
/* Get the offset we need to skip the port portion when we
* extract the hostname part.
*/
tlen_offset = strlen(url->port)+1;
}
else
{
strlcpy(url->port, "80", sizeof(url->port));
tlen_offset = 0;
}
/* Get rid of any trailing slash
*/
if(res_url[strlen(res_url)-1] == '/')
res_url[strlen(res_url)-1] = '\0';
e_ndx = strchr(s_ndx, '/');
if(e_ndx == NULL)
tlen = strlen(s_ndx)+1;
else
tlen = (e_ndx-s_ndx)+1;
tlen -= tlen_offset;
if(tlen > MAX_URL_HOST_LEN)
{
log_msg(LOG_VERBOSITY_ERROR, "resolve-url hostname portion is too large.");
return(-1);
}
strlcpy(url->host, s_ndx, tlen);
if(e_ndx != NULL)
{
if(strlen(e_ndx) > MAX_URL_PATH_LEN)
{
log_msg(LOG_VERBOSITY_ERROR, "resolve-url path portion is too large.");
return(-1);
}
strlcpy(url->path, e_ndx, sizeof(url->path));
}
else
{
/* default to "GET /" if there isn't a more specific URL
*/
strlcpy(url->path, "/", sizeof(url->path));
}
return(0);
}
int
resolve_ip_https(fko_cli_options_t *options)
{
int o1, o2, o3, o4, got_resp=0, i=0;
char *ndx, resp[MAX_IPV4_STR_LEN+1] = {0};
struct url url; /* for validation only */
char wget_ssl_cmd[MAX_URL_PATH_LEN] = {0}; /* for verbose logging only */
#if HAVE_EXECVP
char *wget_argv[MAX_CMDLINE_ARGS]; /* for execvp() */
int wget_argc=0;
int pipe_fd[2];
pid_t pid=0;
FILE *output;
int status, es = 0;
#else
FILE *wget;
#endif
#if HAVE_EXECVP
memset(wget_argv, 0x0, sizeof(wget_argv));
#endif
memset(&url, 0x0, sizeof(url));
if(options->wget_bin != NULL)
{
strlcpy(wget_ssl_cmd, options->wget_bin, sizeof(wget_ssl_cmd));
}
else
{
#ifdef WGET_EXE
strlcpy(wget_ssl_cmd, WGET_EXE, sizeof(wget_ssl_cmd));
#else
log_msg(LOG_VERBOSITY_ERROR,
"[*] Use --wget-cmd <path> to specify path to the wget command.");
return(-1);
#endif
}
/* See whether we're supposed to change the default wget user agent
*/
if(! options->use_wget_user_agent)
{
strlcat(wget_ssl_cmd, " -U ", sizeof(wget_ssl_cmd));
strlcat(wget_ssl_cmd, options->http_user_agent, sizeof(wget_ssl_cmd));
}
/* We collect the IP from wget's stdout
*/
strlcat(wget_ssl_cmd,
" --secure-protocol=auto --quiet -O - ", sizeof(wget_ssl_cmd));
if(options->resolve_url != NULL)
{
if(strncasecmp(options->resolve_url, "https", 5) != 0)
{
log_msg(LOG_VERBOSITY_ERROR,
"[-] Warning: IP resolution URL '%s' should begin with 'https://' in -R mode.",
options->resolve_url);
}
if(parse_url(options->resolve_url, &url) < 0)
{
log_msg(LOG_VERBOSITY_ERROR, "Error parsing resolve-url");
return(-1);
}
/* tack on the original URL to the wget command
*/
strlcat(wget_ssl_cmd, options->resolve_url, sizeof(wget_ssl_cmd));
}
else
{
/* tack on the default URL to the wget command
*/
strlcat(wget_ssl_cmd, WGET_RESOLVE_URL_SSL, sizeof(wget_ssl_cmd));
}
#if AFL_FUZZING
/* Make sure to not generate any resolution requests when compiled
* for AFL fuzzing cycles
*/
strlcpy(options->allow_ip_str, AFL_SET_RESOLVE_HOST,
sizeof(options->allow_ip_str));
log_msg(LOG_VERBOSITY_INFO,
"\n[+] AFL fuzzing cycle, force IP resolution to: %s",
options->allow_ip_str);
return(1);
#endif
#if HAVE_EXECVP
if(strtoargv(wget_ssl_cmd, wget_argv, &wget_argc) != 1)
{
log_msg(LOG_VERBOSITY_ERROR, "Error converting wget cmd str to argv");
return(-1);
}
/* We drive wget to resolve the external IP via SSL. This may not
* work on all platforms, but is a better strategy for now than
* requiring that fwknop link against an SSL library.
*/
if(pipe(pipe_fd) < 0)
{
log_msg(LOG_VERBOSITY_ERROR, "[*] pipe() error");
free_argv(wget_argv, &wget_argc);
return -1;
}
pid = fork();
if (pid == 0)
{
close(pipe_fd[0]);
dup2(pipe_fd[1], STDOUT_FILENO);
dup2(pipe_fd[1], STDERR_FILENO);
es = execvp(wget_argv[0], wget_argv);
if(es == -1)
log_msg(LOG_VERBOSITY_ERROR,
"[*] resolve_ip_https(): execvp() failed: %s",
strerror(errno));
/* We only make it here if there was a problem with execvp(),
* so exit() here either way
*/
exit(es);
}
else if(pid == -1)
{
log_msg(LOG_VERBOSITY_INFO, "[*] Could not fork() for wget.");
free_argv(wget_argv, &wget_argc);
return -1;
}
/* Only the parent process makes it here
*/
close(pipe_fd[1]);
if ((output = fdopen(pipe_fd[0], "r")) != NULL)
{
if(fgets(resp, sizeof(resp), output) != NULL)
{
got_resp = 1;
}
fclose(output);
}
else
{
log_msg(LOG_VERBOSITY_INFO,
"[*] Could not fdopen() pipe output file descriptor.");
free_argv(wget_argv, &wget_argc);
return -1;
}
waitpid(pid, &status, 0);
free_argv(wget_argv, &wget_argc);
#else /* fall back to popen() */
wget = popen(wget_ssl_cmd, "r");
if(wget == NULL)
{
log_msg(LOG_VERBOSITY_ERROR, "[*] Could not run cmd: %s",
wget_ssl_cmd);
return -1;
}
/* Expecting one line of wget output that contains the resolved IP.
* */
if ((fgets(resp, sizeof(resp), wget)) != NULL)
{
got_resp = 1;
}
pclose(wget);
#endif
if(got_resp)
{
ndx = resp;
for(i=0; i<MAX_IPV4_STR_LEN; i++) {
if(! isdigit((int)(unsigned char)*(ndx+i)) && *(ndx+i) != '.')
break;
}
*(ndx+i) = '\0';
if((sscanf(ndx, "%u.%u.%u.%u", &o1, &o2, &o3, &o4)) == 4
&& o1 >= 0 && o1 <= 255
&& o2 >= 0 && o2 <= 255
&& o3 >= 0 && o3 <= 255
&& o4 >= 0 && o4 <= 255)
{
strlcpy(options->allow_ip_str, ndx, sizeof(options->allow_ip_str));
log_msg(LOG_VERBOSITY_INFO,
"\n[+] Resolved external IP (via '%s') as: %s",
wget_ssl_cmd, options->allow_ip_str);
return 1;
}
}
log_msg(LOG_VERBOSITY_ERROR,
"[-] Could not resolve IP via: '%s'", wget_ssl_cmd);
return -1;
}
int
resolve_ip_http(fko_cli_options_t *options)
{
int res;
struct url url;
memset(&url, 0, sizeof(url));
if(options->resolve_url != NULL)
{
/* we only enter this function when the user forces non-HTTPS
* IP resolution
*/
if(strncasecmp(options->resolve_url, "https", 5) == 0)
{
log_msg(LOG_VERBOSITY_ERROR,
"[*] https is not supported for --resolve-http-only.");
return(-1);
}
if(parse_url(options->resolve_url, &url) < 0)
{
log_msg(LOG_VERBOSITY_ERROR, "Error parsing resolve-url");
return(-1);
}
res = try_url(&url, options);
} else {
strlcpy(url.port, "80", sizeof(url.port));
strlcpy(url.host, HTTP_RESOLVE_HOST, sizeof(url.host));
strlcpy(url.path, HTTP_RESOLVE_URL, sizeof(url.path));
res = try_url(&url, options);
if(res != 1)
{
/* try the backup url (just switches the host to cipherdyne.com)
*/
strlcpy(url.host, HTTP_BACKUP_RESOLVE_HOST, sizeof(url.host));
#ifndef WIN32
sleep(2);
#endif
res = try_url(&url, options);
}
}
return(res);
}
/***EOF***/