Add some more portal plugin debug strings
[framework/connectivity/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                 connman_error("TCP event error %d", condition);
171                 remove_timeout(data);
172                 data->watch = 0;
173                 if (data->get_page)
174                         data->get_page(location, NULL, 0, GET_PAGE_FAILED);
175
176                 return FALSE;
177         }
178
179         sk = g_io_channel_unix_get_fd(channel);
180         len = recv(sk, buf, BUFSIZ, 0);
181
182         if (len > 0) {
183                 remove_timeout(data);
184                 if (data->get_page)
185                         data->get_page(location, buf, len, GET_PAGE_SUCCESS);
186         }
187
188         return TRUE;
189 }
190
191 static gboolean socket_event(GIOChannel *channel, GIOCondition condition,
192                                 gpointer user_data)
193 {
194         struct connman_location *location = user_data;
195         struct server_data *data = connman_location_get_data(location);
196         char *query;
197         int sk;
198         unsigned int send_counter = 0;
199         int ret;
200
201         if (data == NULL)
202                 return FALSE;
203
204         if (condition & G_IO_OUT && data->connection_ready == 0) {
205                 data->connection_ready = 1;
206                 sk = g_io_channel_unix_get_fd(channel);
207
208                 query = build_get_query(data->host, data->page);
209                 DBG("query is:\n%s\n", query);
210
211                 while (send_counter < strlen(query)) {
212                         ret = send(sk, query+send_counter,
213                                         strlen(query) - send_counter, 0);
214                         if (ret == -1) {
215                                 DBG("Error sending query");
216                                 remove_timeout(data);
217                                 if (data->get_page)
218                                         data->get_page(location, NULL, 0,
219                                                         GET_PAGE_FAILED);
220                                 g_free(query);
221                                 return FALSE;
222                         }
223                         send_counter += ret;
224                 }
225                 g_free(query);
226         } else if (condition & G_IO_IN)
227                 tcp_event(channel, condition, user_data);
228
229         return TRUE;
230 }
231
232 static void remove_connection(struct connman_location *location)
233 {
234         struct server_data *data = connman_location_get_data(location);
235
236         data = connman_location_get_data(location);
237         if (data == NULL)
238                 return;
239
240         remove_timeout(data);
241         if (data->watch)
242                 g_source_remove(data->watch);
243
244         if (data->channel != NULL)
245                 g_io_channel_shutdown(data->channel, TRUE, NULL);
246
247         if (data->sock >= 0)
248                 close(data->sock);
249
250         g_free(data);
251         connman_location_set_data(location, NULL);
252 }
253
254 static int get_html(struct connman_location *location, int ms_time)
255 {
256         struct server_data *data;
257         struct sockaddr_in *remote_host = NULL;
258         int ret;
259         char *ip = NULL;
260
261         DBG("");
262
263         data = connman_location_get_data(location);
264         data->connection_ready = 0;
265         data->sock = create_socket();
266         if (data->sock < 0)
267                 goto error;
268
269         DBG("proxy %s port %d", data->proxy, data->proxy_port);
270
271         if (strlen(data->proxy) > 0)
272                 ip = get_ip_from_host(data->proxy);
273         else {
274                 ip = g_try_malloc0(16);
275                 if (ip != NULL)
276                         strcpy(ip, CONNMAN_NET_IP);
277         }
278
279         if (ip == NULL)
280                 goto error;
281
282         DBG("IP from host %s is %s", data->host, ip);
283
284         remote_host = g_try_new0(struct sockaddr_in, 1);
285         remote_host->sin_family = AF_INET;
286         ret = inet_pton(AF_INET, ip,
287                         (void *) (&(remote_host->sin_addr.s_addr)));
288         if (ret < 0) {
289                 connman_error("Error Calling inet_pton");
290                 goto error;
291         } else if (ret == 0) {
292                 connman_error("Wrong IP address %s", ip);
293                 goto error;
294         }
295         if (strlen(data->proxy) > 0)
296                 remote_host->sin_port = htons(data->proxy_port);
297         else
298                 remote_host->sin_port = htons(PORT);
299
300         data->channel = g_io_channel_unix_new(data->sock);
301         g_io_channel_set_flags(data->channel, G_IO_FLAG_NONBLOCK, NULL);
302         g_io_channel_set_close_on_unref(data->channel, TRUE);
303         data->watch = g_io_add_watch(data->channel, G_IO_OUT | G_IO_IN,
304                                                         socket_event, location);
305         data->timeout = g_timeout_add_seconds(ms_time, connect_timeout,
306                                                                 location);
307
308         ret = connect(data->sock, (struct sockaddr *)remote_host,
309                                                 sizeof(struct sockaddr));
310         if (ret < 0 && errno != EINPROGRESS) {
311                 connman_error("Could not connect");
312                 remove_timeout(data);
313                 goto error;
314         }
315
316         g_free(remote_host);
317         g_free(ip);
318         return 0;
319
320 error:
321         g_free(remote_host);
322         g_free(ip);
323
324         if (data->get_page)
325                 data->get_page(location, NULL, 0, GET_PAGE_FAILED);
326
327         return ret;
328 }
329
330 static int get_status(struct server_data *data, char *page, int len)
331 {
332         gchar **lines;
333         gchar *str;
334         int i;
335
336         /*
337          * Right now we are only looking at HTTP response header to figure
338          * out if AP redirected our HTTP request. In the future we are going
339          * to parse the HTTP body and look for certain fixed context.
340          * To figure out if we are redirected we look for some HTTP header line,
341          * if these header was found then we have our page otherwise we
342          * have a redirection page.
343          */
344         lines = g_strsplit(page, "\n", MAX_HEADER_LINES);
345
346         str = g_strrstr(lines[0], "200 OK");
347         if (str != NULL) {
348                 for (i = 0; lines[i] != NULL && i < 12; i++) {
349                         DBG("%s", lines[i]);
350                         str = g_strstr_len(lines[i], 12, "Set-Cookie");
351                         if (str != NULL) {
352                                 g_strfreev(lines);
353                                 DBG("success");
354                                 return GET_PAGE_SUCCESS;
355                         }
356                 }
357         }
358         g_strfreev(lines);
359
360         DBG("redirection");
361
362         return GET_PAGE_REDIRECTED;
363 }
364
365 static int get_page_cb(struct connman_location *location, char *page, int len,
366                 enum get_page_status status)
367 {
368         int ret;
369         struct server_data *data = connman_location_get_data(location);
370
371         remove_connection(location);
372
373         if (page)
374                 ret = get_status(data, page, len);
375         else
376                 ret = status;
377
378         DBG("status %d", status);
379
380         switch (ret) {
381         case GET_PAGE_SUCCESS:
382                 connman_location_report_result(location,
383                                         CONNMAN_LOCATION_RESULT_ONLINE);
384                 DBG("Page fetched");
385                 break;
386         case GET_PAGE_REDIRECTED:
387                 connman_location_report_result(location,
388                                         CONNMAN_LOCATION_RESULT_PORTAL);
389                 DBG("Page redirected");
390                 break;
391         case GET_PAGE_FAILED:
392                 connman_location_report_result(location,
393                                         CONNMAN_LOCATION_RESULT_UNKNOWN);
394                 DBG("Could not get the page");
395                 break;
396         case GET_PAGE_TIMEOUT:
397                 connman_location_report_result(location,
398                                         CONNMAN_LOCATION_RESULT_UNKNOWN);
399                 DBG("Page timeout");
400                 break;
401         }
402
403         return ret;
404 }
405
406 static int location_detect(struct connman_location *location)
407 {
408         char *proxy;
409         struct server_data *data;
410         enum connman_service_type service_type;
411
412         service_type = connman_location_get_type(location);
413
414         DBG("service type %d", service_type);
415
416         switch (service_type) {
417         case CONNMAN_SERVICE_TYPE_ETHERNET:
418         case CONNMAN_SERVICE_TYPE_WIFI:
419         case CONNMAN_SERVICE_TYPE_WIMAX:
420         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
421         case CONNMAN_SERVICE_TYPE_CELLULAR:
422                 break;
423         case CONNMAN_SERVICE_TYPE_UNKNOWN:
424         case CONNMAN_SERVICE_TYPE_SYSTEM:
425         case CONNMAN_SERVICE_TYPE_GPS:
426         case CONNMAN_SERVICE_TYPE_VPN:
427                 return -EOPNOTSUPP;
428         }
429
430         data = g_try_new0(struct server_data, 1);
431         if (data == NULL)
432                 return -ENOMEM;
433
434         strcpy(data->host, HOST);
435         strcpy(data->page, PAGE);
436         data->get_page = get_page_cb;
437         data->timeout = 0;
438
439         proxy = getenv("http_proxy");
440         if (proxy) {
441                 char *delim;
442
443                 if (strncmp(proxy, "http://", PROXY_HEADER_LENGTH) == 0)
444                         strcpy(data->proxy, proxy + PROXY_HEADER_LENGTH);
445                 else
446                         strcpy(data->proxy, proxy);
447
448                 delim = strchr(data->proxy, ':');
449                 if (delim) {
450                         int len;
451
452                         len = delim - data->proxy;
453                         data->proxy[len] = '\0';
454
455                         data->proxy_port = atoi(delim + 1);
456                 } else
457                         data->proxy_port = PROXY_PORT;
458         }
459
460         connman_location_set_data(location, data);
461
462         return get_html(location, CONNECT_TIMEOUT);
463 }
464
465 static int location_finish(struct connman_location *location)
466 {
467
468         remove_connection(location);
469         return 0;
470 }
471
472 static struct connman_location_driver location = {
473         .name           = "wifi and ethernet location",
474         .type           = CONNMAN_SERVICE_TYPE_WIFI,
475         .priority       = CONNMAN_LOCATION_PRIORITY_HIGH,
476         .detect         = location_detect,
477         .finish         = location_finish,
478 };
479
480 static int portal_init(void)
481 {
482         return connman_location_driver_register(&location);
483 }
484
485 static void portal_exit(void)
486 {
487         connman_location_driver_unregister(&location);
488 }
489
490 CONNMAN_PLUGIN_DEFINE(portal, "Portal detection plugin", VERSION,
491                 CONNMAN_PLUGIN_PRIORITY_DEFAULT, portal_init, portal_exit)