Portal check for Bluetooth, WiMAX and cellular networks
[platform/upstream/connman.git] / plugins / portal.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <netdb.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34
35 #include <glib.h>
36
37 #define CONNMAN_API_SUBJECT_TO_CHANGE
38 #include <connman/plugin.h>
39 #include <connman/location.h>
40 #include <connman/log.h>
41
42 #define PORT 80
43 #define PROXY_PORT 911
44 #define PAGE "/"
45 #define HOST "connman.net"
46 #define USER_APP "connman"
47 #define CONNMAN_NET_IP "174.36.13.145"
48 #define CONNMAN_MAX_IP_LENGTH   15
49 #define CONNECT_TIMEOUT         120
50 #define MAX_COUNTER             80
51
52 #define MAX_HEADER_LINES        13
53 #define PROXY_HEADER_LENGTH     7
54
55 enum get_page_status {
56         GET_PAGE_SUCCESS        = 0,
57         GET_PAGE_TIMEOUT        = 1,
58         GET_PAGE_FAILED         = 2,
59         GET_PAGE_REDIRECTED     = 3,
60 };
61
62 struct server_data {
63         char host[MAX_COUNTER];
64         char page[MAX_COUNTER];
65         char proxy[MAX_COUNTER];
66         GIOChannel *channel;
67         guint watch;
68         guint timeout;
69         int connection_ready;
70         int sock;
71         int proxy_port;
72         int (*get_page) (struct connman_location *location, char *page, int len,
73                                                 enum get_page_status status);
74 };
75
76 static int create_socket()
77 {
78         int sk;
79
80         sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
81         if (sk < 0)
82                 connman_error("Can not create TCP socket");
83
84         return sk;
85 }
86
87 static char *get_ip_from_host(char *host)
88 {
89         int ip_len = CONNMAN_MAX_IP_LENGTH;
90         char *ip;
91         struct hostent *host_ent;
92
93         DBG("Get ip for %s", host);
94         ip = g_try_malloc0(ip_len + 1);
95         if (ip == NULL)
96                 return NULL;
97
98         host_ent = gethostbyname(host);
99         if (host_ent == NULL) {
100                 connman_error("Can not get IP");
101                 goto failed;
102         }
103
104         if (inet_ntop(AF_INET, (void *) host_ent->h_addr_list[0],
105                                                         ip, ip_len) == NULL) {
106                 connman_error("Can not resolve host");
107                 goto failed;
108         }
109
110         return ip;
111 failed:
112         g_free(ip);
113
114         return NULL;
115 }
116
117 static char *build_get_query(char *host, char *page)
118 {
119         char *query;
120         char *host_page = page;
121         char *tpl = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";
122
123         if (host_page[0] == '/')
124                 host_page = host_page + 1;
125
126         query = g_try_malloc0(strlen(host) + strlen(host_page) +
127                                         strlen(USER_APP) + strlen(tpl) - 5);
128         sprintf(query, tpl, host_page, host, USER_APP);
129
130         return query;
131 }
132
133 static gboolean connect_timeout(gpointer user_data)
134 {
135         struct connman_location *location = user_data;
136         struct server_data *data = connman_location_get_data(location);
137
138         if (data == NULL)
139                 return FALSE;
140
141         data->timeout = 0;
142
143         if (data->get_page)
144                 data->get_page(location, NULL, 0, GET_PAGE_TIMEOUT);
145
146         return FALSE;
147 }
148
149 static void remove_timeout(struct server_data *data)
150 {
151         if (data && data->timeout > 0) {
152                 g_source_remove(data->timeout);
153                 data->timeout = 0;
154         }
155 }
156
157 static gboolean tcp_event(GIOChannel *channel, GIOCondition condition,
158                                                         gpointer user_data)
159 {
160         struct connman_location *location = user_data;
161         struct server_data *data = connman_location_get_data(location);
162         char buf[BUFSIZ+1];
163         int len;
164         int sk;
165
166         if (data == NULL)
167                 return FALSE;
168
169         if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
170                 remove_timeout(data);
171                 data->watch = 0;
172                 if (data->get_page)
173                         data->get_page(location, NULL, 0, GET_PAGE_FAILED);
174
175                 return FALSE;
176         }
177
178         sk = g_io_channel_unix_get_fd(channel);
179         len = recv(sk, buf, BUFSIZ, 0);
180
181         if (len > 0) {
182                 remove_timeout(data);
183                 if (data->get_page)
184                         data->get_page(location, buf, len, GET_PAGE_SUCCESS);
185         }
186
187         return TRUE;
188 }
189
190 static gboolean socket_event(GIOChannel *channel, GIOCondition condition,
191                                 gpointer user_data)
192 {
193         struct connman_location *location = user_data;
194         struct server_data *data = connman_location_get_data(location);
195         char *query;
196         int sk;
197         unsigned int send_counter = 0;
198         int ret;
199
200         if (data == NULL)
201                 return FALSE;
202
203         if (condition & G_IO_OUT && data->connection_ready == 0) {
204                 data->connection_ready = 1;
205                 sk = g_io_channel_unix_get_fd(channel);
206
207                 query = build_get_query(data->host, data->page);
208                 DBG("query is:\n%s\n", query);
209
210                 while (send_counter < strlen(query)) {
211                         ret = send(sk, query+send_counter,
212                                         strlen(query) - send_counter, 0);
213                         if (ret == -1) {
214                                 DBG("Error sending query");
215                                 remove_timeout(data);
216                                 if (data->get_page)
217                                         data->get_page(location, NULL, 0,
218                                                         GET_PAGE_FAILED);
219                                 g_free(query);
220                                 return FALSE;
221                         }
222                         send_counter += ret;
223                 }
224                 g_free(query);
225         } else if (condition & G_IO_IN)
226                 tcp_event(channel, condition, user_data);
227
228         return TRUE;
229 }
230
231 static void remove_connection(struct connman_location *location)
232 {
233         struct server_data *data = connman_location_get_data(location);
234
235         data = connman_location_get_data(location);
236         if (data == NULL)
237                 return;
238
239         remove_timeout(data);
240         if (data->watch)
241                 g_source_remove(data->watch);
242
243         if (data->channel != NULL)
244                 g_io_channel_shutdown(data->channel, TRUE, NULL);
245
246         if (data->sock >= 0)
247                 close(data->sock);
248
249         g_free(data);
250         connman_location_set_data(location, NULL);
251 }
252
253 static int get_html(struct connman_location *location, int ms_time)
254 {
255         struct server_data *data;
256         struct sockaddr_in *remote_host = NULL;
257         int ret;
258         char *ip = NULL;
259
260         data = connman_location_get_data(location);
261         data->connection_ready = 0;
262         data->sock = create_socket();
263         if (data->sock < 0)
264                 goto error;
265
266         if (strlen(data->proxy) > 0)
267                 ip = get_ip_from_host(data->proxy);
268         else {
269                 ip = g_try_malloc0(16);
270                 if (ip != NULL)
271                         strcpy(ip, CONNMAN_NET_IP);
272         }
273
274         if (ip == NULL)
275                 goto error;
276
277         DBG("IP from host %s is %s", data->host, ip);
278
279         remote_host = g_try_new0(struct sockaddr_in, 1);
280         remote_host->sin_family = AF_INET;
281         ret = inet_pton(AF_INET, ip,
282                         (void *) (&(remote_host->sin_addr.s_addr)));
283         if (ret < 0) {
284                 connman_error("Error Calling inet_pton");
285                 goto error;
286         } else if (ret == 0) {
287                 connman_error("Wrong IP address %s", ip);
288                 goto error;
289         }
290         if (strlen(data->proxy) > 0)
291                 remote_host->sin_port = htons(data->proxy_port);
292         else
293                 remote_host->sin_port = htons(PORT);
294
295         data->channel = g_io_channel_unix_new(data->sock);
296         g_io_channel_set_flags(data->channel, G_IO_FLAG_NONBLOCK, NULL);
297         g_io_channel_set_close_on_unref(data->channel, TRUE);
298         data->watch = g_io_add_watch(data->channel, G_IO_OUT | G_IO_IN,
299                                                         socket_event, location);
300         data->timeout = g_timeout_add_seconds(ms_time, connect_timeout,
301                                                                 location);
302
303         ret = connect(data->sock, (struct sockaddr *)remote_host,
304                                                 sizeof(struct sockaddr));
305         if (ret < 0 && errno != EINPROGRESS) {
306                 connman_error("Could not connect");
307                 remove_timeout(data);
308                 goto error;
309         }
310
311         g_free(remote_host);
312         g_free(ip);
313         return 0;
314
315 error:
316         g_free(remote_host);
317         g_free(ip);
318
319         if (data->get_page)
320                 data->get_page(location, NULL, 0, GET_PAGE_FAILED);
321
322         return ret;
323 }
324
325 static int get_status(struct server_data *data, char *page, int len)
326 {
327         gchar **lines;
328         gchar *str;
329         int i;
330
331         /*
332          * Right now we are only looking at HTTP response header to figure
333          * out if AP redirected our HTTP request. In the future we are going
334          * to parse the HTTP body and look for certain fixed context.
335          * To figure out if we are redirected we look for some HTTP header line,
336          * if these header was found then we have our page otherwise we
337          * have a redirection page.
338          */
339         lines = g_strsplit(page, "\n", MAX_HEADER_LINES);
340
341         str = g_strrstr(lines[0], "200 OK");
342         if (str != NULL) {
343                 for (i = 0; lines[i] != NULL && i < 12; i++) {
344                         str = g_strstr_len(lines[i], 12, "Set-Cookie");
345                         if (str != NULL) {
346                                 g_strfreev(lines);
347                                 return GET_PAGE_SUCCESS;
348                         }
349                 }
350         }
351         g_strfreev(lines);
352
353         return GET_PAGE_REDIRECTED;
354 }
355
356 static int get_page_cb(struct connman_location *location, char *page, int len,
357                 enum get_page_status status)
358 {
359         int ret;
360         struct server_data *data = connman_location_get_data(location);
361
362         remove_connection(location);
363
364         if (page)
365                 ret = get_status(data, page, len);
366         else
367                 ret = status;
368
369         switch (ret) {
370         case GET_PAGE_SUCCESS:
371                 connman_location_report_result(location,
372                                         CONNMAN_LOCATION_RESULT_ONLINE);
373                 DBG("Page fetched");
374                 break;
375         case GET_PAGE_REDIRECTED:
376                 connman_location_report_result(location,
377                                         CONNMAN_LOCATION_RESULT_PORTAL);
378                 DBG("Page redirected");
379                 break;
380         case GET_PAGE_FAILED:
381                 connman_location_report_result(location,
382                                         CONNMAN_LOCATION_RESULT_UNKNOWN);
383                 DBG("Could not get the page");
384                 break;
385         case GET_PAGE_TIMEOUT:
386                 connman_location_report_result(location,
387                                         CONNMAN_LOCATION_RESULT_UNKNOWN);
388                 DBG("Page timeout");
389                 break;
390         }
391
392         return ret;
393 }
394
395 static int location_detect(struct connman_location *location)
396 {
397         char *proxy;
398         struct server_data *data;
399         enum connman_service_type service_type;
400
401         service_type = connman_location_get_type(location);
402         switch (service_type) {
403         case CONNMAN_SERVICE_TYPE_ETHERNET:
404         case CONNMAN_SERVICE_TYPE_WIFI:
405         case CONNMAN_SERVICE_TYPE_WIMAX:
406         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
407         case CONNMAN_SERVICE_TYPE_CELLULAR:
408                 break;
409         case CONNMAN_SERVICE_TYPE_UNKNOWN:
410         case CONNMAN_SERVICE_TYPE_SYSTEM:
411         case CONNMAN_SERVICE_TYPE_GPS:
412         case CONNMAN_SERVICE_TYPE_VPN:
413                 return -EOPNOTSUPP;
414         }
415
416         data = g_try_new0(struct server_data, 1);
417         if (data == NULL)
418                 return -ENOMEM;
419
420         strcpy(data->host, HOST);
421         strcpy(data->page, PAGE);
422         data->get_page = get_page_cb;
423         data->timeout = 0;
424
425         proxy = getenv("http_proxy");
426         if (proxy) {
427                 char *delim;
428
429                 if (strncmp(proxy, "http://", PROXY_HEADER_LENGTH) == 0)
430                         strcpy(data->proxy, proxy + PROXY_HEADER_LENGTH);
431                 else
432                         strcpy(data->proxy, proxy);
433
434                 delim = strchr(data->proxy, ':');
435                 if (delim) {
436                         int len;
437
438                         len = delim - data->proxy;
439                         data->proxy[len] = '\0';
440
441                         data->proxy_port = atoi(delim + 1);
442                 } else
443                         data->proxy_port = PROXY_PORT;
444         }
445
446         connman_location_set_data(location, data);
447
448         return get_html(location, CONNECT_TIMEOUT);
449 }
450
451 static int location_finish(struct connman_location *location)
452 {
453
454         remove_connection(location);
455         return 0;
456 }
457
458 static struct connman_location_driver location = {
459         .name           = "wifi and ethernet location",
460         .type           = CONNMAN_SERVICE_TYPE_WIFI,
461         .priority       = CONNMAN_LOCATION_PRIORITY_HIGH,
462         .detect         = location_detect,
463         .finish         = location_finish,
464 };
465
466 static int portal_init(void)
467 {
468         return connman_location_driver_register(&location);
469 }
470
471 static void portal_exit(void)
472 {
473         connman_location_driver_unregister(&location);
474 }
475
476 CONNMAN_PLUGIN_DEFINE(portal, "Portal detection plugin", VERSION,
477                 CONNMAN_PLUGIN_PRIORITY_DEFAULT, portal_init, portal_exit)