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