Branch data Line data Source code
1 : : /**
2 : : * \file client/http_resolve_host.c
3 : : *
4 : : * \brief Routine for using an http request to obtain a client's IP
5 : : * address as seen from the outside world.
6 : : */
7 : :
8 : : /* Fwknop is developed primarily by the people listed in the file 'AUTHORS'.
9 : : * Copyright (C) 2009-2015 fwknop developers and contributors. For a full
10 : : * list of contributors, see the file 'CREDITS'.
11 : : *
12 : : * License (GNU General Public License):
13 : : *
14 : : * This program is free software; you can redistribute it and/or
15 : : * modify it under the terms of the GNU General Public License
16 : : * as published by the Free Software Foundation; either version 2
17 : : * of the License, or (at your option) any later version.
18 : : *
19 : : * This program is distributed in the hope that it will be useful,
20 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 : : * GNU General Public License for more details.
23 : : *
24 : : * You should have received a copy of the GNU General Public License
25 : : * along with this program; if not, write to the Free Software
26 : : * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27 : : * USA
28 : : *
29 : : *****************************************************************************
30 : : */
31 : : #include "fwknop_common.h"
32 : : #include "utils.h"
33 : :
34 : : #include <errno.h>
35 : :
36 : : #ifdef WIN32
37 : : #include <winsock2.h>
38 : : #include <ws2tcpip.h>
39 : : #else
40 : : #if HAVE_SYS_SOCKET_H
41 : : #include <sys/socket.h>
42 : : #endif
43 : : #include <netdb.h>
44 : : #include <sys/wait.h>
45 : : #endif
46 : :
47 : : #if AFL_FUZZING
48 : : #define AFL_SET_RESOLVE_HOST "192.168.12.123" /* force to non-routable IP */
49 : : #endif
50 : :
51 : : struct url
52 : : {
53 : : char port[MAX_PORT_STR_LEN+1];
54 : : char host[MAX_URL_HOST_LEN+1];
55 : : char path[MAX_URL_PATH_LEN+1];
56 : : };
57 : :
58 : : static int
59 : 4 : try_url(struct url *url, fko_cli_options_t *options)
60 : : {
61 : 4 : int sock=-1, sock_success=0, res, error, http_buf_len, i;
62 : 4 : int bytes_read = 0, position = 0;
63 : : int o1, o2, o3, o4;
64 : 4 : struct addrinfo *result=NULL, *rp, hints;
65 : 4 : char http_buf[HTTP_MAX_REQUEST_LEN] = {0};
66 : 4 : char http_response[HTTP_MAX_RESPONSE_LEN] = {0};
67 : : char *ndx;
68 : :
69 : : #ifdef WIN32
70 : : WSADATA wsa_data;
71 : :
72 : : /* Winsock needs to be initialized...
73 : : */
74 : : res = WSAStartup( MAKEWORD(1,1), &wsa_data );
75 : : if( res != 0 )
76 : : {
77 : : log_msg(LOG_VERBOSITY_ERROR, "Winsock initialization error %d", res );
78 : : return(-1);
79 : : }
80 : : #endif
81 : :
82 : : /* Build our HTTP request to resolve the external IP (this is similar to
83 : : * to contacting whatismyip.org, but using a different URL).
84 : : */
85 : : snprintf(http_buf, HTTP_MAX_REQUEST_LEN,
86 : : "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nAccept: */*\r\n"
87 : : "Host: %s\r\nConnection: close\r\n\r\n",
88 : 4 : url->path,
89 : 4 : options->http_user_agent,
90 : 4 : url->host
91 : : );
92 : :
93 : 4 : http_buf_len = strlen(http_buf);
94 : :
95 : : memset(&hints, 0, sizeof(struct addrinfo));
96 : :
97 : : hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
98 : 4 : hints.ai_socktype = SOCK_STREAM;
99 : 4 : hints.ai_protocol = IPPROTO_TCP;
100 : :
101 : : #if AFL_FUZZING
102 : : /* Make sure to not generate any resolution requests when compiled
103 : : * for AFL fuzzing cycles
104 : : */
105 : : strlcpy(options->allow_ip_str, AFL_SET_RESOLVE_HOST,
106 : : sizeof(options->allow_ip_str));
107 : : log_msg(LOG_VERBOSITY_INFO,
108 : : "\n[+] AFL fuzzing cycle, force IP resolution to: %s",
109 : : options->allow_ip_str);
110 : :
111 : : return(1);
112 : : #endif
113 : :
114 : 4 : error = getaddrinfo(url->host, url->port, &hints, &result);
115 [ - + ]: 4 : if (error != 0)
116 : : {
117 : 0 : log_msg(LOG_VERBOSITY_ERROR, "error in getaddrinfo: %s", gai_strerror(error));
118 : 0 : return(-1);
119 : : }
120 : :
121 [ + - ]: 4 : for (rp = result; rp != NULL; rp = rp->ai_next) {
122 : 4 : sock = socket(rp->ai_family, rp->ai_socktype,
123 : : rp->ai_protocol);
124 [ - + ]: 4 : if (sock < 0)
125 : 0 : continue;
126 : :
127 [ - + ]: 4 : if ((error = (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1)))
128 : : {
129 : : sock_success = 1;
130 : : break; /* made it */
131 : : }
132 : : else /* close the open socket if there was a connect error */
133 : : {
134 : : #ifdef WIN32
135 : : closesocket(sock);
136 : : #else
137 : 0 : close(sock);
138 : : #endif
139 : : }
140 : :
141 : : }
142 [ + - ]: 4 : if(result != NULL)
143 : 4 : freeaddrinfo(result);
144 : :
145 [ - + ]: 4 : if (! sock_success)
146 : : {
147 : 0 : log_msg(LOG_VERBOSITY_ERROR, "resolve_ip_http: Could not create socket: ", strerror(errno));
148 : 0 : return(-1);
149 : : }
150 : :
151 : 4 : log_msg(LOG_VERBOSITY_DEBUG, "\nHTTP request: %s", http_buf);
152 : :
153 : 4 : res = send(sock, http_buf, http_buf_len, 0);
154 : :
155 [ - + ]: 4 : if(res < 0)
156 : : {
157 : 0 : log_msg(LOG_VERBOSITY_ERROR, "resolve_ip_http: write error: ", strerror(errno));
158 : : }
159 [ - + ]: 4 : else if(res != http_buf_len)
160 : : {
161 : 4 : log_msg(LOG_VERBOSITY_WARNING,
162 : : "[#] Warning: bytes sent (%i) not spa data length (%i).",
163 : : res, http_buf_len
164 : : );
165 : : }
166 : :
167 : : do
168 : : {
169 : : memset(http_buf, 0x0, sizeof(http_buf));
170 : 8 : bytes_read = recv(sock, http_buf, sizeof(http_buf), 0);
171 [ + + ]: 8 : if ( bytes_read > 0 ) {
172 [ + - ]: 4 : if(position + bytes_read >= HTTP_MAX_RESPONSE_LEN)
173 : : break;
174 : 4 : memcpy(&http_response[position], http_buf, bytes_read);
175 : 4 : position += bytes_read;
176 : : }
177 : : }
178 [ + + ]: 8 : while ( bytes_read > 0 );
179 : :
180 : 4 : http_response[HTTP_MAX_RESPONSE_LEN-1] = '\0';
181 : :
182 : : #ifdef WIN32
183 : : closesocket(sock);
184 : : #else
185 : 4 : close(sock);
186 : : #endif
187 : :
188 : 4 : log_msg(LOG_VERBOSITY_DEBUG, "\nHTTP response: %s", http_response);
189 : :
190 : : /* Move to the end of the HTTP header and to the start of the content.
191 : : */
192 : 4 : ndx = strstr(http_response, "\r\n\r\n");
193 [ - + ]: 4 : if(ndx == NULL)
194 : : {
195 : 0 : log_msg(LOG_VERBOSITY_ERROR, "Did not find the end of HTTP header.");
196 : 0 : return(-1);
197 : : }
198 : 4 : ndx += 4;
199 : :
200 : : /* Walk along the content to try to find the end of the IP address.
201 : : * Note: We are expecting the content to be just an IP address
202 : : * (possibly followed by whitespace or other not-digit value).
203 : : */
204 [ + - ]: 60 : for(i=0; i<MAX_IPV4_STR_LEN; i++) {
205 [ + + ][ + + ]: 60 : if(! isdigit(*(ndx+i)) && *(ndx+i) != '.')
206 : : break;
207 : : }
208 : :
209 : : /* Terminate at the first non-digit and non-dot.
210 : : */
211 : 4 : *(ndx+i) = '\0';
212 : :
213 : : /* Now that we have what we think is an IP address string. We make
214 : : * sure the format and values are sane.
215 : : */
216 [ + - ]: 4 : if((sscanf(ndx, "%u.%u.%u.%u", &o1, &o2, &o3, &o4)) == 4
217 [ + - ][ + - ]: 4 : && o1 >= 0 && o1 <= 255
218 [ + - ][ + - ]: 4 : && o2 >= 0 && o2 <= 255
219 [ + - ][ + - ]: 4 : && o3 >= 0 && o3 <= 255
220 [ + - ][ + - ]: 4 : && o4 >= 0 && o4 <= 255)
221 : : {
222 : 4 : strlcpy(options->allow_ip_str, ndx, sizeof(options->allow_ip_str));
223 : :
224 : 4 : log_msg(LOG_VERBOSITY_INFO,
225 : : "\n[+] Resolved external IP (via http://%s%s) as: %s",
226 : : url->host,
227 : : url->path,
228 : : options->allow_ip_str);
229 : :
230 : 4 : return(1);
231 : : }
232 : : else
233 : : {
234 : 0 : log_msg(LOG_VERBOSITY_ERROR,
235 : : "[-] From http://%s%s\n Invalid IP (%s) in HTTP response:\n\n%s",
236 : : url->host, url->path, ndx, http_response);
237 : 0 : return(-1);
238 : : }
239 : : }
240 : :
241 : : static int
242 : 20 : parse_url(char *res_url, struct url* url)
243 : : {
244 : : char *s_ndx, *e_ndx;
245 : : int tlen, tlen_offset, port, is_err;
246 : :
247 : : /* Strip off https:// or http:// portion if necessary
248 : : */
249 [ + + ]: 20 : if(strncasecmp(res_url, "https://", 8) == 0)
250 : 4 : s_ndx = res_url + 8;
251 [ + + ]: 16 : else if(strncasecmp(res_url, "http://", 7) == 0)
252 : 15 : s_ndx = res_url + 7;
253 : : else
254 : : s_ndx = res_url;
255 : :
256 : : /* Look for a colon in case an alternate port was specified.
257 : : */
258 : 20 : e_ndx = strchr(s_ndx, ':');
259 [ + + ]: 20 : if(e_ndx != NULL)
260 : : {
261 : 5 : port = strtol_wrapper(e_ndx+1, 1, MAX_PORT, NO_EXIT_UPON_ERR, &is_err);
262 [ + + ]: 5 : if(is_err != FKO_SUCCESS)
263 : : {
264 : 2 : log_msg(LOG_VERBOSITY_ERROR,
265 : : "[*] resolve-url port value is invalid, must be in [%d-%d]",
266 : : 1, MAX_PORT);
267 : 2 : return(-1);
268 : : }
269 : :
270 : 3 : snprintf(url->port, sizeof(url->port)-1, "%u", port);
271 : :
272 : : /* Get the offset we need to skip the port portion when we
273 : : * extract the hostname part.
274 : : */
275 : 3 : tlen_offset = strlen(url->port)+1;
276 : : }
277 : : else
278 : : {
279 : 15 : strlcpy(url->port, "80", sizeof(url->port));
280 : 15 : tlen_offset = 0;
281 : : }
282 : :
283 : : /* Get rid of any trailing slash
284 : : */
285 [ + + ]: 18 : if(res_url[strlen(res_url)-1] == '/')
286 : 3 : res_url[strlen(res_url)-1] = '\0';
287 : :
288 : 18 : e_ndx = strchr(s_ndx, '/');
289 [ + + ]: 18 : if(e_ndx == NULL)
290 : 2 : tlen = strlen(s_ndx)+1;
291 : : else
292 : 16 : tlen = (e_ndx-s_ndx)+1;
293 : :
294 : 18 : tlen -= tlen_offset;
295 : :
296 [ + + ]: 18 : if(tlen > MAX_URL_HOST_LEN)
297 : : {
298 : 1 : log_msg(LOG_VERBOSITY_ERROR, "resolve-url hostname portion is too large.");
299 : 1 : return(-1);
300 : : }
301 : 17 : strlcpy(url->host, s_ndx, tlen);
302 : :
303 [ + + ]: 17 : if(e_ndx != NULL)
304 : : {
305 [ + + ]: 15 : if(strlen(e_ndx) > MAX_URL_PATH_LEN)
306 : : {
307 : 1 : log_msg(LOG_VERBOSITY_ERROR, "resolve-url path portion is too large.");
308 : 1 : return(-1);
309 : : }
310 : :
311 : 14 : strlcpy(url->path, e_ndx, sizeof(url->path));
312 : : }
313 : : else
314 : : {
315 : : /* default to "GET /" if there isn't a more specific URL
316 : : */
317 : 2 : strlcpy(url->path, "/", sizeof(url->path));
318 : : }
319 : :
320 : : return(0);
321 : : }
322 : :
323 : : int
324 : 26 : resolve_ip_https(fko_cli_options_t *options)
325 : : {
326 : 26 : int o1, o2, o3, o4, got_resp=0, i=0;
327 : 26 : char *ndx, resp[MAX_IPV4_STR_LEN+1] = {0};
328 : : struct url url; /* for validation only */
329 : 26 : char wget_ssl_cmd[MAX_URL_PATH_LEN] = {0}; /* for verbose logging only */
330 : :
331 : : #if HAVE_EXECVPE
332 : : char *wget_argv[MAX_CMDLINE_ARGS]; /* for execvpe() */
333 : 26 : int wget_argc=0;
334 : : int pipe_fd[2];
335 : 26 : pid_t pid=0;
336 : : FILE *output;
337 : : int status;
338 : : #else
339 : : FILE *wget;
340 : : #endif
341 : :
342 : : #if HAVE_EXECVPE
343 : : memset(wget_argv, 0x0, sizeof(wget_argv));
344 : : #endif
345 : : memset(&url, 0x0, sizeof(url));
346 : :
347 [ + + ]: 26 : if(options->wget_bin != NULL)
348 : : {
349 : 3 : strlcpy(wget_ssl_cmd, options->wget_bin, sizeof(wget_ssl_cmd));
350 : : }
351 : : else
352 : : {
353 : : #ifdef WGET_EXE
354 : 23 : strlcpy(wget_ssl_cmd, WGET_EXE, sizeof(wget_ssl_cmd));
355 : : #else
356 : : log_msg(LOG_VERBOSITY_ERROR,
357 : : "[*] Use --wget-cmd <path> to specify path to the wget command.");
358 : : return(-1);
359 : : #endif
360 : : }
361 : :
362 : : /* See whether we're supposed to change the default wget user agent
363 : : */
364 [ + + ]: 26 : if(! options->use_wget_user_agent)
365 : : {
366 : 23 : strlcat(wget_ssl_cmd, " -U ", sizeof(wget_ssl_cmd));
367 : 23 : strlcat(wget_ssl_cmd, options->http_user_agent, sizeof(wget_ssl_cmd));
368 : : }
369 : :
370 : : /* We collect the IP from wget's stdout
371 : : */
372 : 26 : strlcat(wget_ssl_cmd,
373 : : " --secure-protocol=auto --quiet -O - ", sizeof(wget_ssl_cmd));
374 : :
375 [ + + ]: 26 : if(options->resolve_url != NULL)
376 : : {
377 [ + + ]: 19 : if(strncasecmp(options->resolve_url, "https", 5) != 0)
378 : : {
379 : 15 : log_msg(LOG_VERBOSITY_ERROR,
380 : : "[-] Warning: IP resolution URL '%s' should begin with 'https://' in -R mode.",
381 : : options->resolve_url);
382 : : }
383 : :
384 [ + + ]: 19 : if(parse_url(options->resolve_url, &url) < 0)
385 : : {
386 : 4 : log_msg(LOG_VERBOSITY_ERROR, "Error parsing resolve-url");
387 : 4 : return(-1);
388 : : }
389 : : /* tack on the original URL to the wget command
390 : : */
391 : 15 : strlcat(wget_ssl_cmd, options->resolve_url, sizeof(wget_ssl_cmd));
392 : : }
393 : : else
394 : : {
395 : : /* tack on the default URL to the wget command
396 : : */
397 : 7 : strlcat(wget_ssl_cmd, WGET_RESOLVE_URL_SSL, sizeof(wget_ssl_cmd));
398 : : }
399 : :
400 : : #if AFL_FUZZING
401 : : /* Make sure to not generate any resolution requests when compiled
402 : : * for AFL fuzzing cycles
403 : : */
404 : : strlcpy(options->allow_ip_str, AFL_SET_RESOLVE_HOST,
405 : : sizeof(options->allow_ip_str));
406 : : log_msg(LOG_VERBOSITY_INFO,
407 : : "\n[+] AFL fuzzing cycle, force IP resolution to: %s",
408 : : options->allow_ip_str);
409 : :
410 : : return(1);
411 : : #endif
412 : :
413 : : #if HAVE_EXECVPE
414 [ - + ]: 22 : if(strtoargv(wget_ssl_cmd, wget_argv, &wget_argc) != 1)
415 : : {
416 : 0 : log_msg(LOG_VERBOSITY_ERROR, "Error converting wget cmd str to argv");
417 : 0 : return(-1);
418 : : }
419 : :
420 : : /* We drive wget to resolve the external IP via SSL. This may not
421 : : * work on all platforms, but is a better strategy for now than
422 : : * requiring that fwknop link against an SSL library.
423 : : */
424 [ - + ]: 22 : if(pipe(pipe_fd) < 0)
425 : : {
426 : 0 : log_msg(LOG_VERBOSITY_ERROR, "[*] pipe() error");
427 : 0 : free_argv(wget_argv, &wget_argc);
428 : 0 : return -1;
429 : : }
430 : :
431 : 22 : pid = fork();
432 [ + + ]: 23 : if (pid == 0)
433 : : {
434 : 1 : close(pipe_fd[0]);
435 : 1 : dup2(pipe_fd[1], STDOUT_FILENO);
436 : 1 : dup2(pipe_fd[1], STDERR_FILENO);
437 : 1 : execvpe(wget_argv[0], wget_argv, (char * const *)NULL); /* don't use env */
438 : : }
439 [ - + ]: 22 : else if(pid == -1)
440 : : {
441 : 0 : log_msg(LOG_VERBOSITY_INFO, "[*] Could not fork() for wget.");
442 : 0 : free_argv(wget_argv, &wget_argc);
443 : 0 : return -1;
444 : : }
445 : :
446 : : /* Only the parent process makes it here
447 : : */
448 : 23 : close(pipe_fd[1]);
449 [ + + ]: 23 : if ((output = fdopen(pipe_fd[0], "r")) != NULL)
450 : : {
451 [ + + ]: 22 : if(fgets(resp, sizeof(resp), output) != NULL)
452 : : {
453 : 20 : got_resp = 1;
454 : : }
455 : 22 : fclose(output);
456 : : }
457 : : else
458 : : {
459 : 1 : log_msg(LOG_VERBOSITY_INFO,
460 : : "[*] Could not fdopen() pipe output file descriptor.");
461 : 1 : free_argv(wget_argv, &wget_argc);
462 : 1 : return -1;
463 : : }
464 : :
465 : 22 : waitpid(pid, &status, 0);
466 : :
467 : 22 : free_argv(wget_argv, &wget_argc);
468 : :
469 : : #else /* fall back to popen() */
470 : : wget = popen(wget_ssl_cmd, "r");
471 : : if(wget == NULL)
472 : : {
473 : : log_msg(LOG_VERBOSITY_ERROR, "[*] Could not run cmd: %s",
474 : : wget_ssl_cmd);
475 : : return -1;
476 : : }
477 : : /* Expecting one line of wget output that contains the resolved IP.
478 : : * */
479 : : if ((fgets(resp, sizeof(resp), wget)) != NULL)
480 : : {
481 : : got_resp = 1;
482 : : }
483 : : pclose(wget);
484 : : #endif
485 : :
486 [ + + ]: 22 : if(got_resp)
487 : : {
488 : : ndx = resp;
489 [ + - ]: 286 : for(i=0; i<MAX_IPV4_STR_LEN; i++) {
490 [ + + ][ + + ]: 286 : if(! isdigit(*(ndx+i)) && *(ndx+i) != '.')
491 : : break;
492 : : }
493 : 20 : *(ndx+i) = '\0';
494 : :
495 [ + + ]: 20 : if((sscanf(ndx, "%u.%u.%u.%u", &o1, &o2, &o3, &o4)) == 4
496 [ + - ][ + - ]: 19 : && o1 >= 0 && o1 <= 255
497 [ + - ][ + - ]: 19 : && o2 >= 0 && o2 <= 255
498 [ + - ][ + - ]: 19 : && o3 >= 0 && o3 <= 255
499 [ + - ][ + - ]: 19 : && o4 >= 0 && o4 <= 255)
500 : : {
501 : 19 : strlcpy(options->allow_ip_str, ndx, sizeof(options->allow_ip_str));
502 : :
503 : 19 : log_msg(LOG_VERBOSITY_INFO,
504 : : "\n[+] Resolved external IP (via '%s') as: %s",
505 : : wget_ssl_cmd, options->allow_ip_str);
506 : 19 : return 1;
507 : : }
508 : : }
509 : 3 : log_msg(LOG_VERBOSITY_ERROR,
510 : : "[-] Could not resolve IP via: '%s'", wget_ssl_cmd);
511 : 3 : return -1;
512 : : }
513 : :
514 : : int
515 : 6 : resolve_ip_http(fko_cli_options_t *options)
516 : : {
517 : : int res;
518 : : struct url url;
519 : :
520 : : memset(&url, 0, sizeof(url));
521 : :
522 [ + + ]: 6 : if(options->resolve_url != NULL)
523 : : {
524 : : /* we only enter this function when the user forces non-HTTPS
525 : : * IP resolution
526 : : */
527 [ + + ]: 3 : if(strncasecmp(options->resolve_url, "https", 5) == 0)
528 : : {
529 : 2 : log_msg(LOG_VERBOSITY_ERROR,
530 : : "[*] https is not supported for --resolve-http-only.");
531 : 2 : return(-1);
532 : : }
533 : :
534 [ - + ]: 1 : if(parse_url(options->resolve_url, &url) < 0)
535 : : {
536 : 0 : log_msg(LOG_VERBOSITY_ERROR, "Error parsing resolve-url");
537 : 0 : return(-1);
538 : : }
539 : :
540 : 1 : res = try_url(&url, options);
541 : :
542 : : } else {
543 : 3 : strlcpy(url.port, "80", sizeof(url.port));
544 : 3 : strlcpy(url.host, HTTP_RESOLVE_HOST, sizeof(url.host));
545 : 3 : strlcpy(url.path, HTTP_RESOLVE_URL, sizeof(url.path));
546 : :
547 : 3 : res = try_url(&url, options);
548 [ - + ]: 3 : if(res != 1)
549 : : {
550 : : /* try the backup url (just switches the host to cipherdyne.com)
551 : : */
552 : 0 : strlcpy(url.host, HTTP_BACKUP_RESOLVE_HOST, sizeof(url.host));
553 : :
554 : : #ifndef WIN32
555 : 0 : sleep(2);
556 : : #endif
557 : 0 : res = try_url(&url, options);
558 : : }
559 : : }
560 : 4 : return(res);
561 : : }
562 : :
563 : : /***EOF***/
|