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