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