wispr: Added WISPr XML content parser
[platform/upstream/connman.git] / src / wispr.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 <stdlib.h>
28
29 #include <gweb/gweb.h>
30
31 #include "connman.h"
32
33 #define STATUS_URL  "http://www.connman.net/online/status.html"
34
35 struct connman_wispr_message {
36         gboolean has_error;
37         const char *current_element;
38         int message_type;
39         int response_code;
40         char *login_url;
41         char *abort_login_url;
42         char *logoff_url;
43         char *access_procedure;
44         char *access_location;
45         char *location_name;
46 };
47
48 struct connman_wispr_portal_context {
49         struct connman_service *service;
50         enum connman_ipconfig_type type;
51
52         /* Portal/WISPr common */
53         GWeb *web;
54         unsigned int token;
55         guint request_id;
56
57         /* WISPr specific */
58         GWebParser *wispr_parser;
59         struct connman_wispr_message wispr_msg;
60 };
61
62 struct connman_wispr_portal {
63         struct connman_wispr_portal_context *ipv4_context;
64         struct connman_wispr_portal_context *ipv6_context;
65 };
66
67 static GHashTable *wispr_portal_list = NULL;
68
69 static void connman_wispr_message_init(struct connman_wispr_message *msg)
70 {
71         DBG("");
72
73         msg->has_error = FALSE;
74         msg->current_element = NULL;
75
76         msg->message_type = -1;
77         msg->response_code = -1;
78
79         g_free(msg->login_url);
80         msg->login_url = NULL;
81
82         g_free(msg->abort_login_url);
83         msg->abort_login_url = NULL;
84
85         g_free(msg->logoff_url);
86         msg->logoff_url = NULL;
87
88         g_free(msg->access_procedure);
89         msg->access_procedure = NULL;
90
91         g_free(msg->access_location);
92         msg->access_location = NULL;
93
94         g_free(msg->location_name);
95         msg->location_name = NULL;
96 }
97
98 static void free_connman_wispr_portal_context(struct connman_wispr_portal_context *wp_context)
99 {
100         DBG("");
101
102         if (wp_context == NULL)
103                 return;
104
105         if (wp_context->token > 0)
106                 connman_proxy_lookup_cancel(wp_context->token);
107
108         if (wp_context->request_id > 0)
109                 g_web_cancel_request(wp_context->web, wp_context->request_id);
110
111         g_web_unref(wp_context->web);
112
113         g_web_parser_unref(wp_context->wispr_parser);
114         connman_wispr_message_init(&wp_context->wispr_msg);
115
116         g_free(wp_context);
117 }
118
119 static void free_connman_wispr_portal(gpointer data)
120 {
121         struct connman_wispr_portal *wispr_portal = data;
122
123         DBG("");
124
125         if (wispr_portal == NULL)
126                 return;
127
128         free_connman_wispr_portal_context(wispr_portal->ipv4_context);
129         free_connman_wispr_portal_context(wispr_portal->ipv6_context);
130
131         g_free(wispr_portal);
132 }
133
134 static struct {
135         const char *str;
136         enum {
137                 WISPR_ELEMENT_NONE              = 0,
138                 WISPR_ELEMENT_ACCESS_PROCEDURE  = 1,
139                 WISPR_ELEMENT_ACCESS_LOCATION   = 2,
140                 WISPR_ELEMENT_LOCATION_NAME     = 3,
141                 WISPR_ELEMENT_LOGIN_URL         = 4,
142                 WISPR_ELEMENT_ABORT_LOGIN_URL   = 5,
143                 WISPR_ELEMENT_MESSAGE_TYPE      = 6,
144                 WISPR_ELEMENT_RESPONSE_CODE     = 7,
145                 WISPR_ELEMENT_NEXT_URL          = 8,
146                 WISPR_ELEMENT_DELAY             = 9,
147                 WISPR_ELEMENT_REPLY_MESSAGE     = 10,
148                 WISPR_ELEMENT_LOGIN_RESULTS_URL = 11,
149                 WISPR_ELEMENT_LOGOFF_URL        = 12,
150         } element;
151 } wispr_element_map[] = {
152         { "AccessProcedure",    WISPR_ELEMENT_ACCESS_PROCEDURE  },
153         { "AccessLocation",     WISPR_ELEMENT_ACCESS_LOCATION   },
154         { "LocationName",       WISPR_ELEMENT_LOCATION_NAME     },
155         { "LoginURL",           WISPR_ELEMENT_LOGIN_URL         },
156         { "AbortLoginURL",      WISPR_ELEMENT_ABORT_LOGIN_URL   },
157         { "MessageType",        WISPR_ELEMENT_MESSAGE_TYPE      },
158         { "ResponseCode",       WISPR_ELEMENT_RESPONSE_CODE     },
159         { "NextURL",            WISPR_ELEMENT_NEXT_URL          },
160         { "Delay",              WISPR_ELEMENT_DELAY             },
161         { "ReplyMessage",       WISPR_ELEMENT_REPLY_MESSAGE     },
162         { "LoginResultsURL",    WISPR_ELEMENT_LOGIN_RESULTS_URL },
163         { "LogoffURL",          WISPR_ELEMENT_LOGOFF_URL        },
164         { NULL,                 WISPR_ELEMENT_NONE              },
165 };
166
167 static void xml_wispr_start_element_handler(GMarkupParseContext *context,
168                                         const gchar *element_name,
169                                         const gchar **attribute_names,
170                                         const gchar **attribute_values,
171                                         gpointer user_data, GError **error)
172 {
173         struct connman_wispr_message *msg = user_data;
174
175         msg->current_element = element_name;
176 }
177
178 static void xml_wispr_end_element_handler(GMarkupParseContext *context,
179                                         const gchar *element_name,
180                                         gpointer user_data, GError **error)
181 {
182         struct connman_wispr_message *msg = user_data;
183
184         msg->current_element = NULL;
185 }
186
187 static void xml_wispr_text_handler(GMarkupParseContext *context,
188                                         const gchar *text, gsize text_len,
189                                         gpointer user_data, GError **error)
190 {
191         struct connman_wispr_message *msg = user_data;
192         int i;
193
194         if (msg->current_element == NULL)
195                 return;
196
197         for (i = 0; wispr_element_map[i].str; i++) {
198                 if (g_str_equal(wispr_element_map[i].str,
199                                         msg->current_element) == FALSE)
200                         continue;
201
202                 switch (wispr_element_map[i].element) {
203                 case WISPR_ELEMENT_NONE:
204                 case WISPR_ELEMENT_ACCESS_PROCEDURE:
205                         g_free(msg->access_procedure);
206                         msg->access_procedure = g_strdup(text);
207                         break;
208                 case WISPR_ELEMENT_ACCESS_LOCATION:
209                         g_free(msg->access_location);
210                         msg->access_location = g_strdup(text);
211                         break;
212                 case WISPR_ELEMENT_LOCATION_NAME:
213                         g_free(msg->location_name);
214                         msg->location_name = g_strdup(text);
215                         break;
216                 case WISPR_ELEMENT_LOGIN_URL:
217                         g_free(msg->login_url);
218                         msg->login_url = g_strdup(text);
219                         break;
220                 case WISPR_ELEMENT_ABORT_LOGIN_URL:
221                         g_free(msg->abort_login_url);
222                         msg->abort_login_url = g_strdup(text);
223                         break;
224                 case WISPR_ELEMENT_MESSAGE_TYPE:
225                         msg->message_type = atoi(text);
226                         break;
227                 case WISPR_ELEMENT_RESPONSE_CODE:
228                         msg->response_code = atoi(text);
229                         break;
230                 case WISPR_ELEMENT_NEXT_URL:
231                 case WISPR_ELEMENT_DELAY:
232                 case WISPR_ELEMENT_REPLY_MESSAGE:
233                 case WISPR_ELEMENT_LOGIN_RESULTS_URL:
234                         break;
235                 case WISPR_ELEMENT_LOGOFF_URL:
236                         g_free(msg->logoff_url);
237                         msg->logoff_url = g_strdup(text);
238                         break;
239                 }
240         }
241 }
242
243 static void xml_wispr_error_handler(GMarkupParseContext *context,
244                                         GError *error, gpointer user_data)
245 {
246         struct connman_wispr_message *msg = user_data;
247
248         msg->has_error = TRUE;
249 }
250
251 static const GMarkupParser xml_wispr_parser_handlers = {
252         xml_wispr_start_element_handler,
253         xml_wispr_end_element_handler,
254         xml_wispr_text_handler,
255         NULL,
256         xml_wispr_error_handler,
257 };
258
259 static void xml_wispr_parser_callback(const char *str, gpointer user_data)
260 {
261         struct connman_wispr_portal_context *wp_context = user_data;
262         GMarkupParseContext *parser_context = NULL;
263         gboolean result;
264
265         DBG("");
266
267         parser_context = g_markup_parse_context_new(&xml_wispr_parser_handlers,
268                                         G_MARKUP_TREAT_CDATA_AS_TEXT,
269                                         &(wp_context->wispr_msg), NULL);
270
271         result = g_markup_parse_context_parse(parser_context,
272                                         str, strlen(str), NULL);
273         if (result == TRUE)
274                 result = g_markup_parse_context_end_parse(parser_context, NULL);
275
276         g_markup_parse_context_free(parser_context);
277 }
278
279 static void web_debug(const char *str, void *data)
280 {
281         connman_info("%s: %s\n", (const char *) data, str);
282 }
283
284 static void wispr_portal_error(struct connman_wispr_portal_context *wp_context)
285 {
286         DBG("Failed to proceed wispr/portal web request");
287 }
288
289 static void portal_manage_status(GWebResult *result,
290                         struct connman_wispr_portal_context *wp_context)
291 {
292         const char *str = NULL;
293
294         DBG("");
295
296         /* We currently don't do anything with this info */
297         if (g_web_result_get_header(result, "X-ConnMan-Client-IP",
298                                 &str) == TRUE)
299                 connman_info("Client-IP: %s", str);
300
301         if (g_web_result_get_header(result, "X-ConnMan-Client-Country",
302                                 &str) == TRUE)
303                 connman_info("Client-Country: %s", str);
304
305         if (g_web_result_get_header(result, "X-ConnMan-Client-Region",
306                                 &str) == TRUE)
307                 connman_info("Client-Region: %s", str);
308
309         __connman_service_ipconfig_indicate_state(wp_context->service,
310                                                 CONNMAN_SERVICE_STATE_ONLINE,
311                                                 wp_context->type);
312 }
313
314 static gboolean wispr_portal_web_result(GWebResult *result, gpointer user_data)
315 {
316         struct connman_wispr_portal_context *wp_context = user_data;
317         const char *redirect = NULL;
318         const guint8 *chunk = NULL;
319         const char *str = NULL;
320         guint16 status;
321         gsize length;
322
323         DBG("");
324
325         if (wp_context->request_id == 0)
326                 return FALSE;
327
328         g_web_result_get_chunk(result, &chunk, &length);
329
330         if (length > 0) {
331                 g_web_parser_feed_data(wp_context->wispr_parser,
332                                                         chunk, length);
333                 return TRUE;
334         }
335
336         g_web_parser_end_data(wp_context->wispr_parser);
337
338         status = g_web_result_get_status(result);
339
340         DBG("status: %03u", status);
341
342         switch (status) {
343         case 200:
344                 if (wp_context->wispr_msg.message_type >= 0)
345                         break;
346
347                 if (g_web_result_get_header(result, "X-ConnMan-Status",
348                                                                 &str) == TRUE)
349                         portal_manage_status(result, wp_context);
350
351                 break;
352         case 302:
353                 if (g_web_result_get_header(result, "Location",
354                                                 &redirect) == FALSE)
355                         break;
356
357                 DBG("Redirect URL: %s", redirect);
358
359                 wp_context->request_id = g_web_request_get(wp_context->web,
360                                 redirect, wispr_portal_web_result, wp_context);
361
362                 goto done;
363         case 404:
364                 wispr_portal_error(wp_context);
365
366                 break;
367         default:
368                 break;
369         }
370
371         wp_context->request_id = 0;
372 done:
373         wp_context->wispr_msg.message_type = -1;
374         return FALSE;
375 }
376
377 static void wispr_portal_request_portal(struct connman_wispr_portal_context *wp_context)
378 {
379         DBG("");
380
381         wp_context->request_id = g_web_request_get(wp_context->web,
382                         STATUS_URL, wispr_portal_web_result, wp_context);
383
384         if (wp_context->request_id == 0)
385                 wispr_portal_error(wp_context);
386 }
387
388 static void proxy_callback(const char *proxy, void *user_data)
389 {
390         struct connman_wispr_portal_context *wp_context = user_data;
391
392         DBG("proxy %s", proxy);
393
394         wp_context->token = 0;
395
396         if (proxy == NULL)
397                 proxy = getenv("http_proxy");
398
399         if (getenv("CONNMAN_WEB_DEBUG"))
400                 g_web_set_debug(wp_context->web, web_debug, "WEB");
401
402         if (proxy != NULL && g_strcmp0(proxy, "DIRECT") != 0)
403                 g_web_set_proxy(wp_context->web, proxy);
404
405         g_web_set_accept(wp_context->web, NULL);
406         g_web_set_user_agent(wp_context->web, "ConnMan/%s", VERSION);
407         g_web_set_close_connection(wp_context->web, TRUE);
408
409         connman_wispr_message_init(&wp_context->wispr_msg);
410
411         wp_context->wispr_parser = g_web_parser_new(
412                                         "<WISPAccessGatewayParam",
413                                         "WISPAccessGatewayParam>",
414                                         xml_wispr_parser_callback, wp_context);
415
416         wispr_portal_request_portal(wp_context);
417 }
418
419 static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context)
420 {
421         enum connman_service_type service_type;
422         char *interface = NULL;
423         int err = 0;
424
425         DBG("wispr/portal context %p", wp_context);
426         DBG("service %p", wp_context->service);
427
428         service_type = connman_service_get_type(wp_context->service);
429
430         switch (service_type) {
431         case CONNMAN_SERVICE_TYPE_ETHERNET:
432         case CONNMAN_SERVICE_TYPE_WIFI:
433         case CONNMAN_SERVICE_TYPE_WIMAX:
434         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
435         case CONNMAN_SERVICE_TYPE_CELLULAR:
436                 break;
437         case CONNMAN_SERVICE_TYPE_UNKNOWN:
438         case CONNMAN_SERVICE_TYPE_SYSTEM:
439         case CONNMAN_SERVICE_TYPE_GPS:
440         case CONNMAN_SERVICE_TYPE_VPN:
441         case CONNMAN_SERVICE_TYPE_GADGET:
442                 return -EOPNOTSUPP;
443         }
444
445         interface = connman_service_get_interface(wp_context->service);
446         if (interface == NULL)
447                 return -EINVAL;
448
449         DBG("interface %s", interface);
450
451         wp_context->web = g_web_new(0);
452         if (wp_context->web == NULL) {
453                 err = -ENOMEM;
454                 goto done;
455         }
456
457         if (wp_context->type == CONNMAN_IPCONFIG_TYPE_IPV4)
458                 g_web_set_address_family(wp_context->web, AF_INET);
459         else
460                 g_web_set_address_family(wp_context->web, AF_INET6);
461
462         wp_context->token = connman_proxy_lookup(interface,
463                                         STATUS_URL, wp_context->service,
464                                         proxy_callback, wp_context);
465         if (wp_context->token == 0)
466                 err = -EINVAL;
467
468 done:
469         g_free(interface);
470         return err;
471 }
472
473 int __connman_wispr_start(struct connman_service *service,
474                                         enum connman_ipconfig_type type)
475 {
476         struct connman_wispr_portal_context *wp_context = NULL;
477         struct connman_wispr_portal *wispr_portal = NULL;
478         int index;
479
480         DBG("service %p", service);
481
482         if (wispr_portal_list == NULL)
483                 return -EINVAL;
484
485         index = __connman_service_get_index(service);
486         if (index < 0)
487                 return -EINVAL;
488
489         wispr_portal = g_hash_table_lookup(wispr_portal_list,
490                                         GINT_TO_POINTER(index));
491         if (wispr_portal == NULL) {
492                 wispr_portal = g_try_new0(struct connman_wispr_portal, 1);
493                 if (wispr_portal == NULL)
494                         return -ENOMEM;
495
496                 g_hash_table_replace(wispr_portal_list,
497                                         GINT_TO_POINTER(index), wispr_portal);
498         }
499
500         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
501                 wp_context = wispr_portal->ipv4_context;
502         else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
503                 wp_context = wispr_portal->ipv6_context;
504         else
505                 return -EINVAL;
506
507         if (wp_context == NULL) {
508                 wp_context = g_try_new0(struct connman_wispr_portal_context, 1);
509                 if (wp_context == NULL)
510                         return -ENOMEM;
511
512                 wp_context->service = service;
513                 wp_context->type = type;
514
515                 if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
516                         wispr_portal->ipv4_context = wp_context;
517                 else
518                         wispr_portal->ipv6_context = wp_context;
519
520                 return wispr_portal_detect(wp_context);
521         }
522
523         return 0;
524 }
525
526 void __connman_wispr_stop(struct connman_service *service)
527 {
528         int index;
529
530         DBG("service %p", service);
531
532         if (wispr_portal_list == NULL)
533                 return;
534
535         index = __connman_service_get_index(service);
536         if (index < 0)
537                 return;
538
539         g_hash_table_remove(wispr_portal_list, GINT_TO_POINTER(index));
540 }
541
542 int __connman_wispr_init(void)
543 {
544         DBG("");
545
546         wispr_portal_list = g_hash_table_new_full(g_direct_hash,
547                                                 g_direct_equal, NULL,
548                                                 free_connman_wispr_portal);
549
550         return 0;
551 }
552
553 void __connman_wispr_cleanup(void)
554 {
555         DBG("");
556
557         g_hash_table_destroy(wispr_portal_list);
558         wispr_portal_list = NULL;
559 }