wispr: Handle WISPr messages and apply relevant states
[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 enum connman_wispr_result {
49         CONNMAN_WISPR_RESULT_UNKNOWN = 0,
50         CONNMAN_WISPR_RESULT_LOGIN   = 1,
51         CONNMAN_WISPR_RESULT_ONLINE  = 2,
52         CONNMAN_WISPR_RESULT_FAILED  = 3,
53 };
54
55 struct connman_wispr_portal_context {
56         struct connman_service *service;
57         enum connman_ipconfig_type type;
58
59         /* Portal/WISPr common */
60         GWeb *web;
61         unsigned int token;
62         guint request_id;
63
64         /* WISPr specific */
65         GWebParser *wispr_parser;
66         struct connman_wispr_message wispr_msg;
67
68         enum connman_wispr_result wispr_result;
69 };
70
71 struct connman_wispr_portal {
72         struct connman_wispr_portal_context *ipv4_context;
73         struct connman_wispr_portal_context *ipv6_context;
74 };
75
76 static gboolean wispr_portal_web_result(GWebResult *result, gpointer user_data);
77
78 static GHashTable *wispr_portal_list = NULL;
79
80 static void connman_wispr_message_init(struct connman_wispr_message *msg)
81 {
82         DBG("");
83
84         msg->has_error = FALSE;
85         msg->current_element = NULL;
86
87         msg->message_type = -1;
88         msg->response_code = -1;
89
90         g_free(msg->login_url);
91         msg->login_url = NULL;
92
93         g_free(msg->abort_login_url);
94         msg->abort_login_url = NULL;
95
96         g_free(msg->logoff_url);
97         msg->logoff_url = NULL;
98
99         g_free(msg->access_procedure);
100         msg->access_procedure = NULL;
101
102         g_free(msg->access_location);
103         msg->access_location = NULL;
104
105         g_free(msg->location_name);
106         msg->location_name = NULL;
107 }
108
109 static void free_connman_wispr_portal_context(struct connman_wispr_portal_context *wp_context)
110 {
111         DBG("");
112
113         if (wp_context == NULL)
114                 return;
115
116         if (wp_context->token > 0)
117                 connman_proxy_lookup_cancel(wp_context->token);
118
119         if (wp_context->request_id > 0)
120                 g_web_cancel_request(wp_context->web, wp_context->request_id);
121
122         g_web_unref(wp_context->web);
123
124         g_web_parser_unref(wp_context->wispr_parser);
125         connman_wispr_message_init(&wp_context->wispr_msg);
126
127         g_free(wp_context);
128 }
129
130 static void free_connman_wispr_portal(gpointer data)
131 {
132         struct connman_wispr_portal *wispr_portal = data;
133
134         DBG("");
135
136         if (wispr_portal == NULL)
137                 return;
138
139         free_connman_wispr_portal_context(wispr_portal->ipv4_context);
140         free_connman_wispr_portal_context(wispr_portal->ipv6_context);
141
142         g_free(wispr_portal);
143 }
144
145 static const char *message_type_to_string(int message_type)
146 {
147         switch (message_type) {
148         case 100:
149                 return "Initial redirect message";
150         case 110:
151                 return "Proxy notification";
152         case 120:
153                 return "Authentication notification";
154         case 130:
155                 return "Logoff notification";
156         case 140:
157                 return "Response to Authentication Poll";
158         case 150:
159                 return "Response to Abort Login";
160         }
161
162         return NULL;
163 }
164
165 static const char *response_code_to_string(int response_code)
166 {
167         switch (response_code) {
168         case 0:
169                 return "No error";
170         case 50:
171                 return "Login succeeded";
172         case 100:
173                 return "Login failed";
174         case 102:
175                 return "RADIUS server error/timeout";
176         case 105:
177                 return "RADIUS server not enabled";
178         case 150:
179                 return "Logoff succeeded";
180         case 151:
181                 return "Login aborted";
182         case 200:
183                 return "Proxy detection/repeat operation";
184         case 201:
185                 return "Authentication pending";
186         case 255:
187                 return "Access gateway internal error";
188         }
189
190         return NULL;
191 }
192
193 static struct {
194         const char *str;
195         enum {
196                 WISPR_ELEMENT_NONE              = 0,
197                 WISPR_ELEMENT_ACCESS_PROCEDURE  = 1,
198                 WISPR_ELEMENT_ACCESS_LOCATION   = 2,
199                 WISPR_ELEMENT_LOCATION_NAME     = 3,
200                 WISPR_ELEMENT_LOGIN_URL         = 4,
201                 WISPR_ELEMENT_ABORT_LOGIN_URL   = 5,
202                 WISPR_ELEMENT_MESSAGE_TYPE      = 6,
203                 WISPR_ELEMENT_RESPONSE_CODE     = 7,
204                 WISPR_ELEMENT_NEXT_URL          = 8,
205                 WISPR_ELEMENT_DELAY             = 9,
206                 WISPR_ELEMENT_REPLY_MESSAGE     = 10,
207                 WISPR_ELEMENT_LOGIN_RESULTS_URL = 11,
208                 WISPR_ELEMENT_LOGOFF_URL        = 12,
209         } element;
210 } wispr_element_map[] = {
211         { "AccessProcedure",    WISPR_ELEMENT_ACCESS_PROCEDURE  },
212         { "AccessLocation",     WISPR_ELEMENT_ACCESS_LOCATION   },
213         { "LocationName",       WISPR_ELEMENT_LOCATION_NAME     },
214         { "LoginURL",           WISPR_ELEMENT_LOGIN_URL         },
215         { "AbortLoginURL",      WISPR_ELEMENT_ABORT_LOGIN_URL   },
216         { "MessageType",        WISPR_ELEMENT_MESSAGE_TYPE      },
217         { "ResponseCode",       WISPR_ELEMENT_RESPONSE_CODE     },
218         { "NextURL",            WISPR_ELEMENT_NEXT_URL          },
219         { "Delay",              WISPR_ELEMENT_DELAY             },
220         { "ReplyMessage",       WISPR_ELEMENT_REPLY_MESSAGE     },
221         { "LoginResultsURL",    WISPR_ELEMENT_LOGIN_RESULTS_URL },
222         { "LogoffURL",          WISPR_ELEMENT_LOGOFF_URL        },
223         { NULL,                 WISPR_ELEMENT_NONE              },
224 };
225
226 static void xml_wispr_start_element_handler(GMarkupParseContext *context,
227                                         const gchar *element_name,
228                                         const gchar **attribute_names,
229                                         const gchar **attribute_values,
230                                         gpointer user_data, GError **error)
231 {
232         struct connman_wispr_message *msg = user_data;
233
234         msg->current_element = element_name;
235 }
236
237 static void xml_wispr_end_element_handler(GMarkupParseContext *context,
238                                         const gchar *element_name,
239                                         gpointer user_data, GError **error)
240 {
241         struct connman_wispr_message *msg = user_data;
242
243         msg->current_element = NULL;
244 }
245
246 static void xml_wispr_text_handler(GMarkupParseContext *context,
247                                         const gchar *text, gsize text_len,
248                                         gpointer user_data, GError **error)
249 {
250         struct connman_wispr_message *msg = user_data;
251         int i;
252
253         if (msg->current_element == NULL)
254                 return;
255
256         for (i = 0; wispr_element_map[i].str; i++) {
257                 if (g_str_equal(wispr_element_map[i].str,
258                                         msg->current_element) == FALSE)
259                         continue;
260
261                 switch (wispr_element_map[i].element) {
262                 case WISPR_ELEMENT_NONE:
263                 case WISPR_ELEMENT_ACCESS_PROCEDURE:
264                         g_free(msg->access_procedure);
265                         msg->access_procedure = g_strdup(text);
266                         break;
267                 case WISPR_ELEMENT_ACCESS_LOCATION:
268                         g_free(msg->access_location);
269                         msg->access_location = g_strdup(text);
270                         break;
271                 case WISPR_ELEMENT_LOCATION_NAME:
272                         g_free(msg->location_name);
273                         msg->location_name = g_strdup(text);
274                         break;
275                 case WISPR_ELEMENT_LOGIN_URL:
276                         g_free(msg->login_url);
277                         msg->login_url = g_strdup(text);
278                         break;
279                 case WISPR_ELEMENT_ABORT_LOGIN_URL:
280                         g_free(msg->abort_login_url);
281                         msg->abort_login_url = g_strdup(text);
282                         break;
283                 case WISPR_ELEMENT_MESSAGE_TYPE:
284                         msg->message_type = atoi(text);
285                         break;
286                 case WISPR_ELEMENT_RESPONSE_CODE:
287                         msg->response_code = atoi(text);
288                         break;
289                 case WISPR_ELEMENT_NEXT_URL:
290                 case WISPR_ELEMENT_DELAY:
291                 case WISPR_ELEMENT_REPLY_MESSAGE:
292                 case WISPR_ELEMENT_LOGIN_RESULTS_URL:
293                         break;
294                 case WISPR_ELEMENT_LOGOFF_URL:
295                         g_free(msg->logoff_url);
296                         msg->logoff_url = g_strdup(text);
297                         break;
298                 }
299         }
300 }
301
302 static void xml_wispr_error_handler(GMarkupParseContext *context,
303                                         GError *error, gpointer user_data)
304 {
305         struct connman_wispr_message *msg = user_data;
306
307         msg->has_error = TRUE;
308 }
309
310 static const GMarkupParser xml_wispr_parser_handlers = {
311         xml_wispr_start_element_handler,
312         xml_wispr_end_element_handler,
313         xml_wispr_text_handler,
314         NULL,
315         xml_wispr_error_handler,
316 };
317
318 static void xml_wispr_parser_callback(const char *str, gpointer user_data)
319 {
320         struct connman_wispr_portal_context *wp_context = user_data;
321         GMarkupParseContext *parser_context = NULL;
322         gboolean result;
323
324         DBG("");
325
326         parser_context = g_markup_parse_context_new(&xml_wispr_parser_handlers,
327                                         G_MARKUP_TREAT_CDATA_AS_TEXT,
328                                         &(wp_context->wispr_msg), NULL);
329
330         result = g_markup_parse_context_parse(parser_context,
331                                         str, strlen(str), NULL);
332         if (result == TRUE)
333                 result = g_markup_parse_context_end_parse(parser_context, NULL);
334
335         g_markup_parse_context_free(parser_context);
336 }
337
338 static void web_debug(const char *str, void *data)
339 {
340         connman_info("%s: %s\n", (const char *) data, str);
341 }
342
343 static void wispr_portal_error(struct connman_wispr_portal_context *wp_context)
344 {
345         DBG("Failed to proceed wispr/portal web request");
346
347         wp_context->wispr_result = CONNMAN_WISPR_RESULT_FAILED;
348 }
349
350 static void portal_manage_status(GWebResult *result,
351                         struct connman_wispr_portal_context *wp_context)
352 {
353         const char *str = NULL;
354
355         DBG("");
356
357         /* We currently don't do anything with this info */
358         if (g_web_result_get_header(result, "X-ConnMan-Client-IP",
359                                 &str) == TRUE)
360                 connman_info("Client-IP: %s", str);
361
362         if (g_web_result_get_header(result, "X-ConnMan-Client-Country",
363                                 &str) == TRUE)
364                 connman_info("Client-Country: %s", str);
365
366         if (g_web_result_get_header(result, "X-ConnMan-Client-Region",
367                                 &str) == TRUE)
368                 connman_info("Client-Region: %s", str);
369
370         __connman_service_ipconfig_indicate_state(wp_context->service,
371                                                 CONNMAN_SERVICE_STATE_ONLINE,
372                                                 wp_context->type);
373 }
374
375 static void wispr_portal_request_portal(struct connman_wispr_portal_context *wp_context)
376 {
377         DBG("");
378
379         wp_context->request_id = g_web_request_get(wp_context->web,
380                         STATUS_URL, wispr_portal_web_result, wp_context);
381
382         if (wp_context->request_id == 0)
383                 wispr_portal_error(wp_context);
384 }
385
386 static gboolean wispr_manage_message(GWebResult *result,
387                         struct connman_wispr_portal_context *wp_context)
388 {
389         DBG("Message type: %s (%d)",
390                 message_type_to_string(wp_context->wispr_msg.message_type),
391                                         wp_context->wispr_msg.message_type);
392         DBG("Response code: %s (%d)",
393                 response_code_to_string(wp_context->wispr_msg.response_code),
394                                         wp_context->wispr_msg.response_code);
395
396         if (wp_context->wispr_msg.access_procedure != NULL)
397                 DBG("Access procedure: %s",
398                         wp_context->wispr_msg.access_procedure);
399         if (wp_context->wispr_msg.access_location != NULL)
400                 DBG("Access location: %s",
401                         wp_context->wispr_msg.access_location);
402         if (wp_context->wispr_msg.location_name != NULL)
403                 DBG("Location name: %s",
404                         wp_context->wispr_msg.location_name);
405         if (wp_context->wispr_msg.login_url != NULL)
406                 DBG("Login URL: %s", wp_context->wispr_msg.login_url);
407         if (wp_context->wispr_msg.abort_login_url != NULL)
408                 DBG("Abort login URL: %s",
409                         wp_context->wispr_msg.abort_login_url);
410         if (wp_context->wispr_msg.logoff_url != NULL)
411                 DBG("Logoff URL: %s", wp_context->wispr_msg.logoff_url);
412
413         switch (wp_context->wispr_msg.message_type) {
414         case 100:
415                 DBG("Login required");
416
417                 wp_context->wispr_result = CONNMAN_WISPR_RESULT_LOGIN;
418
419                 break;
420         case 120: /* Falling down */
421         case 140:
422                 if (wp_context->wispr_msg.response_code == 50) {
423                         wp_context->wispr_result = CONNMAN_WISPR_RESULT_ONLINE;
424
425                         wispr_portal_request_portal(wp_context);
426
427                         return TRUE;
428                 } else
429                         wispr_portal_error(wp_context);
430
431                 break;
432         default:
433                 break;
434         }
435
436         return FALSE;
437 }
438
439 static gboolean wispr_portal_web_result(GWebResult *result, gpointer user_data)
440 {
441         struct connman_wispr_portal_context *wp_context = user_data;
442         const char *redirect = NULL;
443         const guint8 *chunk = NULL;
444         const char *str = NULL;
445         guint16 status;
446         gsize length;
447
448         DBG("");
449
450         if (wp_context->request_id == 0)
451                 return FALSE;
452
453         if (wp_context->wispr_result != CONNMAN_WISPR_RESULT_ONLINE) {
454                 g_web_result_get_chunk(result, &chunk, &length);
455
456                 if (length > 0) {
457                         g_web_parser_feed_data(wp_context->wispr_parser,
458                                                                 chunk, length);
459                         return TRUE;
460                 }
461
462                 g_web_parser_end_data(wp_context->wispr_parser);
463
464                 if (wp_context->wispr_msg.message_type >= 0) {
465                         if (wispr_manage_message(result, wp_context) == TRUE)
466                                 goto done;
467                 }
468         }
469
470         status = g_web_result_get_status(result);
471
472         DBG("status: %03u", status);
473
474         switch (status) {
475         case 200:
476                 if (wp_context->wispr_msg.message_type >= 0)
477                         break;
478
479                 if (g_web_result_get_header(result, "X-ConnMan-Status",
480                                                                 &str) == TRUE)
481                         portal_manage_status(result, wp_context);
482
483                 break;
484         case 302:
485                 if (g_web_result_get_header(result, "Location",
486                                                 &redirect) == FALSE)
487                         break;
488
489                 DBG("Redirect URL: %s", redirect);
490
491                 wp_context->request_id = g_web_request_get(wp_context->web,
492                                 redirect, wispr_portal_web_result, wp_context);
493
494                 goto done;
495         case 404:
496                 wispr_portal_error(wp_context);
497
498                 break;
499         default:
500                 break;
501         }
502
503         wp_context->request_id = 0;
504 done:
505         wp_context->wispr_msg.message_type = -1;
506         return FALSE;
507 }
508
509 static void proxy_callback(const char *proxy, void *user_data)
510 {
511         struct connman_wispr_portal_context *wp_context = user_data;
512
513         DBG("proxy %s", proxy);
514
515         wp_context->token = 0;
516
517         if (proxy == NULL)
518                 proxy = getenv("http_proxy");
519
520         if (getenv("CONNMAN_WEB_DEBUG"))
521                 g_web_set_debug(wp_context->web, web_debug, "WEB");
522
523         if (proxy != NULL && g_strcmp0(proxy, "DIRECT") != 0)
524                 g_web_set_proxy(wp_context->web, proxy);
525
526         g_web_set_accept(wp_context->web, NULL);
527         g_web_set_user_agent(wp_context->web, "ConnMan/%s", VERSION);
528         g_web_set_close_connection(wp_context->web, TRUE);
529
530         connman_wispr_message_init(&wp_context->wispr_msg);
531
532         wp_context->wispr_parser = g_web_parser_new(
533                                         "<WISPAccessGatewayParam",
534                                         "WISPAccessGatewayParam>",
535                                         xml_wispr_parser_callback, wp_context);
536
537         wispr_portal_request_portal(wp_context);
538 }
539
540 static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context)
541 {
542         enum connman_service_type service_type;
543         char *interface = NULL;
544         int err = 0;
545
546         DBG("wispr/portal context %p", wp_context);
547         DBG("service %p", wp_context->service);
548
549         service_type = connman_service_get_type(wp_context->service);
550
551         switch (service_type) {
552         case CONNMAN_SERVICE_TYPE_ETHERNET:
553         case CONNMAN_SERVICE_TYPE_WIFI:
554         case CONNMAN_SERVICE_TYPE_WIMAX:
555         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
556         case CONNMAN_SERVICE_TYPE_CELLULAR:
557                 break;
558         case CONNMAN_SERVICE_TYPE_UNKNOWN:
559         case CONNMAN_SERVICE_TYPE_SYSTEM:
560         case CONNMAN_SERVICE_TYPE_GPS:
561         case CONNMAN_SERVICE_TYPE_VPN:
562         case CONNMAN_SERVICE_TYPE_GADGET:
563                 return -EOPNOTSUPP;
564         }
565
566         interface = connman_service_get_interface(wp_context->service);
567         if (interface == NULL)
568                 return -EINVAL;
569
570         DBG("interface %s", interface);
571
572         wp_context->web = g_web_new(0);
573         if (wp_context->web == NULL) {
574                 err = -ENOMEM;
575                 goto done;
576         }
577
578         if (wp_context->type == CONNMAN_IPCONFIG_TYPE_IPV4)
579                 g_web_set_address_family(wp_context->web, AF_INET);
580         else
581                 g_web_set_address_family(wp_context->web, AF_INET6);
582
583         wp_context->token = connman_proxy_lookup(interface,
584                                         STATUS_URL, wp_context->service,
585                                         proxy_callback, wp_context);
586         if (wp_context->token == 0)
587                 err = -EINVAL;
588
589 done:
590         g_free(interface);
591         return err;
592 }
593
594 int __connman_wispr_start(struct connman_service *service,
595                                         enum connman_ipconfig_type type)
596 {
597         struct connman_wispr_portal_context *wp_context = NULL;
598         struct connman_wispr_portal *wispr_portal = NULL;
599         int index;
600
601         DBG("service %p", service);
602
603         if (wispr_portal_list == NULL)
604                 return -EINVAL;
605
606         index = __connman_service_get_index(service);
607         if (index < 0)
608                 return -EINVAL;
609
610         wispr_portal = g_hash_table_lookup(wispr_portal_list,
611                                         GINT_TO_POINTER(index));
612         if (wispr_portal == NULL) {
613                 wispr_portal = g_try_new0(struct connman_wispr_portal, 1);
614                 if (wispr_portal == NULL)
615                         return -ENOMEM;
616
617                 g_hash_table_replace(wispr_portal_list,
618                                         GINT_TO_POINTER(index), wispr_portal);
619         }
620
621         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
622                 wp_context = wispr_portal->ipv4_context;
623         else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
624                 wp_context = wispr_portal->ipv6_context;
625         else
626                 return -EINVAL;
627
628         if (wp_context == NULL) {
629                 wp_context = g_try_new0(struct connman_wispr_portal_context, 1);
630                 if (wp_context == NULL)
631                         return -ENOMEM;
632
633                 wp_context->service = service;
634                 wp_context->type = type;
635
636                 if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
637                         wispr_portal->ipv4_context = wp_context;
638                 else
639                         wispr_portal->ipv6_context = wp_context;
640
641                 return wispr_portal_detect(wp_context);
642         }
643
644         return 0;
645 }
646
647 void __connman_wispr_stop(struct connman_service *service)
648 {
649         int index;
650
651         DBG("service %p", service);
652
653         if (wispr_portal_list == NULL)
654                 return;
655
656         index = __connman_service_get_index(service);
657         if (index < 0)
658                 return;
659
660         g_hash_table_remove(wispr_portal_list, GINT_TO_POINTER(index));
661 }
662
663 int __connman_wispr_init(void)
664 {
665         DBG("");
666
667         wispr_portal_list = g_hash_table_new_full(g_direct_hash,
668                                                 g_direct_equal, NULL,
669                                                 free_connman_wispr_portal);
670
671         return 0;
672 }
673
674 void __connman_wispr_cleanup(void)
675 {
676         DBG("");
677
678         g_hash_table_destroy(wispr_portal_list);
679         wispr_portal_list = NULL;
680 }