4eaa53932dec2b47a781cda5bd79f724b985d794
[fwknop.git] / client / http_resolve_host.c
1 /*
2  *****************************************************************************
3  *
4  * File:    http_resolve_host.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Routine for using an http request to obtain a client's IP
9  *          address as seen from the outside world.
10  *
11  * Copyright 2009-2010 Damien Stuart (dstuart@dstuart.org)
12  *
13  *  License (GNU 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[6];
50     char    host[256];
51     char    path[1024];
52 };
53
54 static int
55 try_url(struct url *url, fko_cli_options_t *options)
56 {
57     int     sock, res, error, http_buf_len, i;
58     int     bytes_read = 0, position = 0;
59     int     o1, o2, o3, o4;
60     struct  addrinfo *result, *rp, hints;
61     char    http_buf[HTTP_MAX_REQUEST_LEN];
62     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         fprintf(stderr, "Winsock initialization error %d\n", 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         url->path,
85         options->http_user_agent,
86         url->host
87     );
88
89     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     hints.ai_socktype = SOCK_STREAM;
95     hints.ai_protocol = IPPROTO_TCP;
96
97     error = getaddrinfo(url->host, url->port, &hints, &result);
98     if (error != 0)
99     {
100         fprintf(stderr, "error in getaddrinfo: %s\n", gai_strerror(error));
101         return(-1);
102     }
103
104     for (rp = result; rp != NULL; rp = rp->ai_next) {
105         sock = socket(rp->ai_family, rp->ai_socktype,
106                 rp->ai_protocol);
107         if (sock < 0)
108             continue;
109
110         if ((error = (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1)))
111             break;  /* made it */
112
113 #ifdef WIN32
114         closesocket(sock);
115 #else
116         close(sock);
117 #endif
118     }
119
120     if (rp == NULL) {
121         perror("resolve_ip_http: Could not create socket: ");
122         return(-1);
123     }
124
125     freeaddrinfo(result);
126
127     if(options->verbose > 1)
128         printf("\nHTTP request: %s\n", http_buf);
129
130     res = send(sock, http_buf, http_buf_len, 0);
131
132     if(res < 0)
133     {
134         perror("resolve_ip_http: write error: ");
135     }
136     else if(res != http_buf_len)
137     {
138         fprintf(stderr,
139             "[#] Warning: bytes sent (%i) not spa data length (%i).\n",
140             res, http_buf_len
141         );
142     }
143
144     do
145     {
146         memset(http_buf, 0x0, sizeof(http_buf));
147         bytes_read = recv(sock, http_buf, sizeof(http_buf), 0);
148         if ( bytes_read > 0 ) {
149             if(position + bytes_read >= HTTP_MAX_RESPONSE_LEN)
150                 break;
151             memcpy(&http_response[position], http_buf, bytes_read);
152             position += bytes_read;
153         }
154     }
155     while ( bytes_read > 0 );
156
157     http_response[HTTP_MAX_RESPONSE_LEN-1] = '\0';
158
159 #ifdef WIN32
160     closesocket(sock);
161 #else
162     close(sock);
163 #endif
164
165     if(options->verbose > 1)
166         printf("\nHTTP response: %s\n", http_response);
167
168     /* Move to the end of the HTTP header and to the start of the content.
169     */
170     ndx = strstr(http_response, "\r\n\r\n");
171     if(ndx == NULL)
172     {
173         fprintf(stderr, "Did not find the end of HTTP header.\n");
174         return(-1);
175     }
176     ndx += 4;
177
178     /* Walk along the content to try to find the end of the IP address.
179      * Note: We are expecting the content to be just an IP address
180      *       (possibly followed by whitespace or other not-digit value).
181      */
182     for(i=0; i<MAX_IPV4_STR_LEN; i++) {
183         if(! isdigit(*(ndx+i)) && *(ndx+i) != '.')
184             break;
185     }
186
187     /* Terminate at the first non-digit and non-dot.
188     */
189     *(ndx+i) = '\0';
190
191     /* Now that we have what we think is an IP address string.  We make
192      * sure the format and values are sane.
193      */
194     if((sscanf(ndx, "%u.%u.%u.%u", &o1, &o2, &o3, &o4)) == 4
195             && o1 >= 0 && o1 <= 255
196             && o2 >= 0 && o2 <= 255
197             && o3 >= 0 && o3 <= 255
198             && o4 >= 0 && o4 <= 255)
199     {
200         strlcpy(options->allow_ip_str, ndx, MAX_IPV4_STR_LEN);
201
202         if(options->verbose)
203             printf("\n[+] Resolved external IP (via http://%s%s) as: %s\n",
204                     url->host,
205                     url->path,
206                     options->allow_ip_str);
207
208         return(1);
209     }
210     else
211     {
212         fprintf(stderr, "Invalid IP (%s) in HTTP response:\n\n%s\n",
213             ndx, http_response);
214         return(-1);
215     }
216 }
217
218 static int
219 parse_url(char *res_url, struct url* url)
220 {
221     char *s_ndx, *e_ndx;
222     int  tlen, tlen_offset, port;
223
224     /* https is not supported.
225     */
226     if(strncasecmp(res_url, "https", 5) == 0)
227     {
228         fprintf(stderr, "https is not yet supported for http-resolve-ip.\n");
229         return(-1);
230     }
231
232     /* Strip off http:// portion if necessary
233     */
234     if(strncasecmp(res_url, "http://", 7) == 0)
235         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     e_ndx = strchr(s_ndx, ':');
242     if(e_ndx != NULL)
243     {
244         port = atoi(e_ndx+1);
245         if(port < 1 || port > MAX_PORT)
246         {
247             fprintf(stderr, "resolve-url port value is invalid.\n");
248             return(-1);
249         }
250
251         sprintf(url->port, "%u", port);
252
253         /* Get the offset we need to skip the port portion when we
254          * extract the hostname part.
255         */
256         tlen_offset = strlen(url->port)+1;
257     }
258     else
259     {
260         strlcpy(url->port, "80", 3);
261         tlen_offset = 0;
262     }
263
264     /* Get rid of any trailing slash
265     */
266     if(res_url[strlen(res_url)-1] == '/')
267         res_url[strlen(res_url)-1] = '\0';
268
269     e_ndx = strchr(s_ndx, '/');
270     if(e_ndx == NULL)
271         tlen = strlen(s_ndx)+1;
272     else
273         tlen = (e_ndx-s_ndx)+1;
274
275     tlen -= tlen_offset;
276
277     if(tlen > MAX_URL_HOST_LEN)
278     {
279         fprintf(stderr, "resolve-url hostname portion is too large.\n");
280         return(-1);
281     }
282     strlcpy(url->host, s_ndx, tlen);
283
284     if(e_ndx != NULL)
285     {
286         if(strlen(e_ndx) > MAX_URL_PATH_LEN)
287         {
288             fprintf(stderr, "resolve-url path portion is too large.\n");
289             return(-1);
290         }
291
292         strlcpy(url->path, e_ndx, MAX_URL_PATH_LEN);
293     }
294     else
295     {
296         /* default to "GET /" if there isn't a more specific URL
297         */
298         strlcpy(url->path, "/", MAX_URL_PATH_LEN);
299     }
300
301     return(0);
302 }
303
304 int
305 resolve_ip_http(fko_cli_options_t *options)
306 {
307     int     res;
308     struct  url url;
309
310     if(options->resolve_url != NULL)
311     {
312         if(parse_url(options->resolve_url, &url) < 0)
313         {
314             fprintf(stderr, "Error parsing resolve-url\n");
315             return(-1);
316         }
317
318         res = try_url(&url, options);
319
320     } else {
321         strlcpy(url.port, "80", 3);
322         strlcpy(url.host, HTTP_RESOLVE_HOST, MAX_URL_HOST_LEN);
323         strlcpy(url.path, HTTP_RESOLVE_URL, MAX_URL_PATH_LEN);
324
325         res = try_url(&url, options);
326         if(res != 1)
327         {
328             /* try the backup url (just switches the host to cipherdyne.com)
329             */
330             strlcpy(url.host, HTTP_BACKUP_RESOLVE_HOST, MAX_URL_HOST_LEN);
331
332             sleep(2);
333             res = try_url(&url, options);
334         }
335     }
336     return(res);
337 }
338
339 /***EOF***/