Imported Upstream version 1.40
[platform/upstream/connman.git] / src / wispr.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2013  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_IPV4  "http://ipv4.connman.net/online/status.html"
34 #define STATUS_URL_IPV6  "http://ipv6.connman.net/online/status.html"
35
36 struct connman_wispr_message {
37         bool has_error;
38         const char *current_element;
39         int message_type;
40         int response_code;
41         char *login_url;
42         char *abort_login_url;
43         char *logoff_url;
44         char *access_procedure;
45         char *access_location;
46         char *location_name;
47 };
48
49 enum connman_wispr_result {
50         CONNMAN_WISPR_RESULT_UNKNOWN = 0,
51         CONNMAN_WISPR_RESULT_LOGIN   = 1,
52         CONNMAN_WISPR_RESULT_ONLINE  = 2,
53         CONNMAN_WISPR_RESULT_FAILED  = 3,
54 };
55
56 struct wispr_route {
57         char *address;
58         int if_index;
59 };
60
61 struct connman_wispr_portal_context {
62         struct connman_service *service;
63         enum connman_ipconfig_type type;
64         struct connman_wispr_portal *wispr_portal;
65
66         /* Portal/WISPr common */
67         GWeb *web;
68         unsigned int token;
69         guint request_id;
70
71         const char *status_url;
72
73         char *redirect_url;
74
75         /* WISPr specific */
76         GWebParser *wispr_parser;
77         struct connman_wispr_message wispr_msg;
78
79         char *wispr_username;
80         char *wispr_password;
81         char *wispr_formdata;
82
83         enum connman_wispr_result wispr_result;
84
85         GSList *route_list;
86
87         guint timeout;
88 };
89
90 struct connman_wispr_portal {
91         struct connman_wispr_portal_context *ipv4_context;
92         struct connman_wispr_portal_context *ipv6_context;
93 };
94
95 static bool wispr_portal_web_result(GWebResult *result, gpointer user_data);
96
97 static GHashTable *wispr_portal_list = NULL;
98
99 static bool enable_online_to_ready_transition = false;
100
101 static void connman_wispr_message_init(struct connman_wispr_message *msg)
102 {
103         DBG("");
104
105         msg->has_error = false;
106         msg->current_element = NULL;
107
108         msg->message_type = -1;
109         msg->response_code = -1;
110
111         g_free(msg->login_url);
112         msg->login_url = NULL;
113
114         g_free(msg->abort_login_url);
115         msg->abort_login_url = NULL;
116
117         g_free(msg->logoff_url);
118         msg->logoff_url = NULL;
119
120         g_free(msg->access_procedure);
121         msg->access_procedure = NULL;
122
123         g_free(msg->access_location);
124         msg->access_location = NULL;
125
126         g_free(msg->location_name);
127         msg->location_name = NULL;
128 }
129
130 static void free_wispr_routes(struct connman_wispr_portal_context *wp_context)
131 {
132         while (wp_context->route_list) {
133                 struct wispr_route *route = wp_context->route_list->data;
134
135                 DBG("free route to %s if %d type %d", route->address,
136                                 route->if_index, wp_context->type);
137
138                 switch (wp_context->type) {
139                 case CONNMAN_IPCONFIG_TYPE_IPV4:
140                         connman_inet_del_host_route(route->if_index,
141                                         route->address);
142                         break;
143                 case CONNMAN_IPCONFIG_TYPE_IPV6:
144                         connman_inet_del_ipv6_host_route(route->if_index,
145                                         route->address);
146                         break;
147                 case CONNMAN_IPCONFIG_TYPE_UNKNOWN:
148                 case CONNMAN_IPCONFIG_TYPE_ALL:
149                         break;
150                 }
151
152                 g_free(route->address);
153                 g_free(route);
154
155                 wp_context->route_list =
156                         g_slist_delete_link(wp_context->route_list,
157                                         wp_context->route_list);
158         }
159 }
160
161 static void free_connman_wispr_portal_context(
162                 struct connman_wispr_portal_context *wp_context)
163 {
164         DBG("context %p", wp_context);
165
166         if (!wp_context)
167                 return;
168
169         if (wp_context->wispr_portal) {
170                 if (wp_context->wispr_portal->ipv4_context == wp_context)
171                         wp_context->wispr_portal->ipv4_context = NULL;
172
173                 if (wp_context->wispr_portal->ipv6_context == wp_context)
174                         wp_context->wispr_portal->ipv6_context = NULL;
175         }
176
177         if (wp_context->token > 0)
178                 connman_proxy_lookup_cancel(wp_context->token);
179
180         if (wp_context->request_id > 0)
181                 g_web_cancel_request(wp_context->web, wp_context->request_id);
182
183         if (wp_context->timeout > 0)
184                 g_source_remove(wp_context->timeout);
185
186         if (wp_context->web)
187                 g_web_unref(wp_context->web);
188
189         g_free(wp_context->redirect_url);
190
191         if (wp_context->wispr_parser)
192                 g_web_parser_unref(wp_context->wispr_parser);
193
194         connman_wispr_message_init(&wp_context->wispr_msg);
195
196         g_free(wp_context->wispr_username);
197         g_free(wp_context->wispr_password);
198         g_free(wp_context->wispr_formdata);
199
200         free_wispr_routes(wp_context);
201
202         g_free(wp_context);
203 }
204
205 static struct connman_wispr_portal_context *create_wispr_portal_context(void)
206 {
207         return g_try_new0(struct connman_wispr_portal_context, 1);
208 }
209
210 static void free_connman_wispr_portal(gpointer data)
211 {
212         struct connman_wispr_portal *wispr_portal = data;
213
214         DBG("");
215
216         if (!wispr_portal)
217                 return;
218
219         free_connman_wispr_portal_context(wispr_portal->ipv4_context);
220         free_connman_wispr_portal_context(wispr_portal->ipv6_context);
221
222         g_free(wispr_portal);
223 }
224
225 static const char *message_type_to_string(int message_type)
226 {
227         switch (message_type) {
228         case 100:
229                 return "Initial redirect message";
230         case 110:
231                 return "Proxy notification";
232         case 120:
233                 return "Authentication notification";
234         case 130:
235                 return "Logoff notification";
236         case 140:
237                 return "Response to Authentication Poll";
238         case 150:
239                 return "Response to Abort Login";
240         }
241
242         return NULL;
243 }
244
245 static const char *response_code_to_string(int response_code)
246 {
247         switch (response_code) {
248         case 0:
249                 return "No error";
250         case 50:
251                 return "Login succeeded";
252         case 100:
253                 return "Login failed";
254         case 102:
255                 return "RADIUS server error/timeout";
256         case 105:
257                 return "RADIUS server not enabled";
258         case 150:
259                 return "Logoff succeeded";
260         case 151:
261                 return "Login aborted";
262         case 200:
263                 return "Proxy detection/repeat operation";
264         case 201:
265                 return "Authentication pending";
266         case 255:
267                 return "Access gateway internal error";
268         }
269
270         return NULL;
271 }
272
273 static struct {
274         const char *str;
275         enum {
276                 WISPR_ELEMENT_NONE              = 0,
277                 WISPR_ELEMENT_ACCESS_PROCEDURE  = 1,
278                 WISPR_ELEMENT_ACCESS_LOCATION   = 2,
279                 WISPR_ELEMENT_LOCATION_NAME     = 3,
280                 WISPR_ELEMENT_LOGIN_URL         = 4,
281                 WISPR_ELEMENT_ABORT_LOGIN_URL   = 5,
282                 WISPR_ELEMENT_MESSAGE_TYPE      = 6,
283                 WISPR_ELEMENT_RESPONSE_CODE     = 7,
284                 WISPR_ELEMENT_NEXT_URL          = 8,
285                 WISPR_ELEMENT_DELAY             = 9,
286                 WISPR_ELEMENT_REPLY_MESSAGE     = 10,
287                 WISPR_ELEMENT_LOGIN_RESULTS_URL = 11,
288                 WISPR_ELEMENT_LOGOFF_URL        = 12,
289         } element;
290 } wispr_element_map[] = {
291         { "AccessProcedure",    WISPR_ELEMENT_ACCESS_PROCEDURE  },
292         { "AccessLocation",     WISPR_ELEMENT_ACCESS_LOCATION   },
293         { "LocationName",       WISPR_ELEMENT_LOCATION_NAME     },
294         { "LoginURL",           WISPR_ELEMENT_LOGIN_URL         },
295         { "AbortLoginURL",      WISPR_ELEMENT_ABORT_LOGIN_URL   },
296         { "MessageType",        WISPR_ELEMENT_MESSAGE_TYPE      },
297         { "ResponseCode",       WISPR_ELEMENT_RESPONSE_CODE     },
298         { "NextURL",            WISPR_ELEMENT_NEXT_URL          },
299         { "Delay",              WISPR_ELEMENT_DELAY             },
300         { "ReplyMessage",       WISPR_ELEMENT_REPLY_MESSAGE     },
301         { "LoginResultsURL",    WISPR_ELEMENT_LOGIN_RESULTS_URL },
302         { "LogoffURL",          WISPR_ELEMENT_LOGOFF_URL        },
303         { NULL,                 WISPR_ELEMENT_NONE              },
304 };
305
306 static void xml_wispr_start_element_handler(GMarkupParseContext *context,
307                                         const gchar *element_name,
308                                         const gchar **attribute_names,
309                                         const gchar **attribute_values,
310                                         gpointer user_data, GError **error)
311 {
312         struct connman_wispr_message *msg = user_data;
313
314         msg->current_element = element_name;
315 }
316
317 static void xml_wispr_end_element_handler(GMarkupParseContext *context,
318                                         const gchar *element_name,
319                                         gpointer user_data, GError **error)
320 {
321         struct connman_wispr_message *msg = user_data;
322
323         msg->current_element = NULL;
324 }
325
326 static void xml_wispr_text_handler(GMarkupParseContext *context,
327                                         const gchar *text, gsize text_len,
328                                         gpointer user_data, GError **error)
329 {
330         struct connman_wispr_message *msg = user_data;
331         int i;
332
333         if (!msg->current_element)
334                 return;
335
336         for (i = 0; wispr_element_map[i].str; i++) {
337                 if (!g_str_equal(wispr_element_map[i].str, msg->current_element))
338                         continue;
339
340                 switch (wispr_element_map[i].element) {
341                 case WISPR_ELEMENT_NONE:
342                 case WISPR_ELEMENT_ACCESS_PROCEDURE:
343                         g_free(msg->access_procedure);
344                         msg->access_procedure = g_strdup(text);
345                         break;
346                 case WISPR_ELEMENT_ACCESS_LOCATION:
347                         g_free(msg->access_location);
348                         msg->access_location = g_strdup(text);
349                         break;
350                 case WISPR_ELEMENT_LOCATION_NAME:
351                         g_free(msg->location_name);
352                         msg->location_name = g_strdup(text);
353                         break;
354                 case WISPR_ELEMENT_LOGIN_URL:
355                         g_free(msg->login_url);
356                         msg->login_url = g_strdup(text);
357                         break;
358                 case WISPR_ELEMENT_ABORT_LOGIN_URL:
359                         g_free(msg->abort_login_url);
360                         msg->abort_login_url = g_strdup(text);
361                         break;
362                 case WISPR_ELEMENT_MESSAGE_TYPE:
363                         msg->message_type = atoi(text);
364                         break;
365                 case WISPR_ELEMENT_RESPONSE_CODE:
366                         msg->response_code = atoi(text);
367                         break;
368                 case WISPR_ELEMENT_NEXT_URL:
369                 case WISPR_ELEMENT_DELAY:
370                 case WISPR_ELEMENT_REPLY_MESSAGE:
371                 case WISPR_ELEMENT_LOGIN_RESULTS_URL:
372                         break;
373                 case WISPR_ELEMENT_LOGOFF_URL:
374                         g_free(msg->logoff_url);
375                         msg->logoff_url = g_strdup(text);
376                         break;
377                 }
378         }
379 }
380
381 static void xml_wispr_error_handler(GMarkupParseContext *context,
382                                         GError *error, gpointer user_data)
383 {
384         struct connman_wispr_message *msg = user_data;
385
386         msg->has_error = true;
387 }
388
389 static const GMarkupParser xml_wispr_parser_handlers = {
390         xml_wispr_start_element_handler,
391         xml_wispr_end_element_handler,
392         xml_wispr_text_handler,
393         NULL,
394         xml_wispr_error_handler,
395 };
396
397 static void xml_wispr_parser_callback(const char *str, gpointer user_data)
398 {
399         struct connman_wispr_portal_context *wp_context = user_data;
400         GMarkupParseContext *parser_context = NULL;
401         bool result;
402
403         DBG("");
404
405         parser_context = g_markup_parse_context_new(&xml_wispr_parser_handlers,
406                                         G_MARKUP_TREAT_CDATA_AS_TEXT,
407                                         &(wp_context->wispr_msg), NULL);
408
409         result = g_markup_parse_context_parse(parser_context,
410                                         str, strlen(str), NULL);
411         if (result)
412                 g_markup_parse_context_end_parse(parser_context, NULL);
413
414         g_markup_parse_context_free(parser_context);
415 }
416
417 static void web_debug(const char *str, void *data)
418 {
419         connman_info("%s: %s\n", (const char *) data, str);
420 }
421
422 static void wispr_portal_error(struct connman_wispr_portal_context *wp_context)
423 {
424         DBG("Failed to proceed wispr/portal web request");
425
426         wp_context->wispr_result = CONNMAN_WISPR_RESULT_FAILED;
427 }
428
429 static void portal_manage_status(GWebResult *result,
430                         struct connman_wispr_portal_context *wp_context)
431 {
432         struct connman_service *service = wp_context->service;
433         enum connman_ipconfig_type type = wp_context->type;
434         const char *str = NULL;
435
436         DBG("");
437
438         /* We currently don't do anything with this info */
439         if (g_web_result_get_header(result, "X-ConnMan-Client-IP",
440                                 &str))
441                 connman_info("Client-IP: %s", str);
442
443         if (g_web_result_get_header(result, "X-ConnMan-Client-Country",
444                                 &str))
445                 connman_info("Client-Country: %s", str);
446
447         if (g_web_result_get_header(result, "X-ConnMan-Client-Region",
448                                 &str))
449                 connman_info("Client-Region: %s", str);
450
451         if (g_web_result_get_header(result, "X-ConnMan-Client-Timezone",
452                                 &str))
453                 connman_info("Client-Timezone: %s", str);
454
455         if (!enable_online_to_ready_transition)
456                 free_connman_wispr_portal_context(wp_context);
457
458         __connman_service_ipconfig_indicate_state(service,
459                                         CONNMAN_SERVICE_STATE_ONLINE, type);
460
461         if (enable_online_to_ready_transition)
462                 __connman_service_online_check(service, type, true);
463 }
464
465 static bool wispr_route_request(const char *address, int ai_family,
466                 int if_index, gpointer user_data)
467 {
468         int result = -1;
469         struct connman_wispr_portal_context *wp_context = user_data;
470         const char *gateway;
471         struct wispr_route *route;
472
473         gateway = __connman_ipconfig_get_gateway_from_index(if_index,
474                 wp_context->type);
475
476         DBG("address %s if %d gw %s", address, if_index, gateway);
477
478         if (!gateway)
479                 return false;
480
481         route = g_try_new0(struct wispr_route, 1);
482         if (route == 0) {
483                 DBG("could not create struct");
484                 return false;
485         }
486
487         switch (wp_context->type) {
488         case CONNMAN_IPCONFIG_TYPE_IPV4:
489                 result = connman_inet_add_host_route(if_index, address,
490                                 gateway);
491                 break;
492         case CONNMAN_IPCONFIG_TYPE_IPV6:
493                 result = connman_inet_add_ipv6_host_route(if_index, address,
494                                 gateway);
495                 break;
496         case CONNMAN_IPCONFIG_TYPE_UNKNOWN:
497         case CONNMAN_IPCONFIG_TYPE_ALL:
498                 break;
499         }
500
501         if (result < 0) {
502                 g_free(route);
503                 return false;
504         }
505
506         route->address = g_strdup(address);
507         route->if_index = if_index;
508         wp_context->route_list = g_slist_prepend(wp_context->route_list, route);
509
510         return true;
511 }
512
513 static void wispr_portal_request_portal(
514                 struct connman_wispr_portal_context *wp_context)
515 {
516         DBG("");
517
518         wp_context->request_id = g_web_request_get(wp_context->web,
519                                         wp_context->status_url,
520                                         wispr_portal_web_result,
521                                         wispr_route_request,
522                                         wp_context);
523
524         if (wp_context->request_id == 0)
525                 wispr_portal_error(wp_context);
526 }
527
528 static bool wispr_input(const guint8 **data, gsize *length,
529                                                 gpointer user_data)
530 {
531         struct connman_wispr_portal_context *wp_context = user_data;
532         GString *buf;
533         gsize count;
534
535         DBG("");
536
537         buf = g_string_sized_new(100);
538
539         g_string_append(buf, "button=Login&UserName=");
540         g_string_append_uri_escaped(buf, wp_context->wispr_username,
541                                                                 NULL, FALSE);
542         g_string_append(buf, "&Password=");
543         g_string_append_uri_escaped(buf, wp_context->wispr_password,
544                                                                 NULL, FALSE);
545         g_string_append(buf, "&FNAME=0&OriginatingServer=");
546         g_string_append_uri_escaped(buf, wp_context->status_url, NULL, FALSE);
547
548         count = buf->len;
549
550         g_free(wp_context->wispr_formdata);
551         wp_context->wispr_formdata = g_string_free(buf, FALSE);
552
553         *data = (guint8 *) wp_context->wispr_formdata;
554         *length = count;
555
556         return false;
557 }
558
559 static void wispr_portal_browser_reply_cb(struct connman_service *service,
560                                         bool authentication_done,
561                                         const char *error, void *user_data)
562 {
563         struct connman_wispr_portal_context *wp_context = user_data;
564         struct connman_wispr_portal *wispr_portal;
565         int index;
566
567         DBG("");
568
569         if (!service || !wp_context)
570                 return;
571
572         /*
573          * No way to cancel this if wp_context has been freed, so we lookup
574          * from the service and check that this is still the right context.
575          */
576         index = __connman_service_get_index(service);
577         if (index < 0)
578                 return;
579
580         wispr_portal = g_hash_table_lookup(wispr_portal_list,
581                                         GINT_TO_POINTER(index));
582         if (!wispr_portal)
583                 return;
584
585         if (wp_context != wispr_portal->ipv4_context &&
586                 wp_context != wispr_portal->ipv6_context)
587                 return;
588
589         if (!authentication_done) {
590                 wispr_portal_error(wp_context);
591                 free_wispr_routes(wp_context);
592                 return;
593         }
594
595         /* Restarting the test */
596         __connman_service_wispr_start(service, wp_context->type);
597 }
598
599 static void wispr_portal_request_wispr_login(struct connman_service *service,
600                                 bool success,
601                                 const char *ssid, int ssid_len,
602                                 const char *username, const char *password,
603                                 bool wps, const char *wpspin,
604                                 const char *error, void *user_data)
605 {
606         struct connman_wispr_portal_context *wp_context = user_data;
607
608         DBG("");
609
610         if (error) {
611                 if (g_strcmp0(error,
612                         "net.connman.Agent.Error.LaunchBrowser") == 0) {
613                         if (__connman_agent_request_browser(service,
614                                         wispr_portal_browser_reply_cb,
615                                         wp_context->redirect_url,
616                                         wp_context) == -EINPROGRESS)
617                                 return;
618                 }
619
620                 free_connman_wispr_portal_context(wp_context);
621                 return;
622         }
623
624         g_free(wp_context->wispr_username);
625         wp_context->wispr_username = g_strdup(username);
626
627         g_free(wp_context->wispr_password);
628         wp_context->wispr_password = g_strdup(password);
629
630         wp_context->request_id = g_web_request_post(wp_context->web,
631                                         wp_context->wispr_msg.login_url,
632                                         "application/x-www-form-urlencoded",
633                                         wispr_input, wispr_portal_web_result,
634                                         wp_context);
635
636         connman_wispr_message_init(&wp_context->wispr_msg);
637 }
638
639 static bool wispr_manage_message(GWebResult *result,
640                         struct connman_wispr_portal_context *wp_context)
641 {
642         DBG("Message type: %s (%d)",
643                 message_type_to_string(wp_context->wispr_msg.message_type),
644                                         wp_context->wispr_msg.message_type);
645         DBG("Response code: %s (%d)",
646                 response_code_to_string(wp_context->wispr_msg.response_code),
647                                         wp_context->wispr_msg.response_code);
648
649         if (wp_context->wispr_msg.access_procedure)
650                 DBG("Access procedure: %s",
651                         wp_context->wispr_msg.access_procedure);
652         if (wp_context->wispr_msg.access_location)
653                 DBG("Access location: %s",
654                         wp_context->wispr_msg.access_location);
655         if (wp_context->wispr_msg.location_name)
656                 DBG("Location name: %s",
657                         wp_context->wispr_msg.location_name);
658         if (wp_context->wispr_msg.login_url)
659                 DBG("Login URL: %s", wp_context->wispr_msg.login_url);
660         if (wp_context->wispr_msg.abort_login_url)
661                 DBG("Abort login URL: %s",
662                         wp_context->wispr_msg.abort_login_url);
663         if (wp_context->wispr_msg.logoff_url)
664                 DBG("Logoff URL: %s", wp_context->wispr_msg.logoff_url);
665
666         switch (wp_context->wispr_msg.message_type) {
667         case 100:
668                 DBG("Login required");
669
670                 wp_context->wispr_result = CONNMAN_WISPR_RESULT_LOGIN;
671
672                 if (__connman_agent_request_login_input(wp_context->service,
673                                         wispr_portal_request_wispr_login,
674                                         wp_context) != -EINPROGRESS)
675                         wispr_portal_error(wp_context);
676                 else
677                         return true;
678
679                 break;
680         case 120: /* Falling down */
681         case 140:
682                 if (wp_context->wispr_msg.response_code == 50) {
683                         wp_context->wispr_result = CONNMAN_WISPR_RESULT_ONLINE;
684
685                         g_free(wp_context->wispr_username);
686                         wp_context->wispr_username = NULL;
687
688                         g_free(wp_context->wispr_password);
689                         wp_context->wispr_password = NULL;
690
691                         g_free(wp_context->wispr_formdata);
692                         wp_context->wispr_formdata = NULL;
693
694                         wispr_portal_request_portal(wp_context);
695
696                         return true;
697                 } else
698                         wispr_portal_error(wp_context);
699
700                 break;
701         default:
702                 break;
703         }
704
705         return false;
706 }
707
708 static bool wispr_portal_web_result(GWebResult *result, gpointer user_data)
709 {
710         struct connman_wispr_portal_context *wp_context = user_data;
711         const char *redirect = NULL;
712         const guint8 *chunk = NULL;
713         const char *str = NULL;
714         guint16 status;
715         gsize length;
716
717         DBG("");
718
719         if (wp_context->wispr_result != CONNMAN_WISPR_RESULT_ONLINE) {
720                 g_web_result_get_chunk(result, &chunk, &length);
721
722                 if (length > 0) {
723                         g_web_parser_feed_data(wp_context->wispr_parser,
724                                                                 chunk, length);
725                         return true;
726                 }
727
728                 g_web_parser_end_data(wp_context->wispr_parser);
729
730                 if (wp_context->wispr_msg.message_type >= 0) {
731                         if (wispr_manage_message(result, wp_context))
732                                 goto done;
733                 }
734         }
735
736         status = g_web_result_get_status(result);
737
738         DBG("status: %03u", status);
739
740         switch (status) {
741         case 000:
742                 __connman_agent_request_browser(wp_context->service,
743                                 wispr_portal_browser_reply_cb,
744                                 wp_context->status_url, wp_context);
745                 break;
746         case 200:
747                 if (wp_context->wispr_msg.message_type >= 0)
748                         break;
749
750                 if (g_web_result_get_header(result, "X-ConnMan-Status",
751                                                 &str)) {
752                         portal_manage_status(result, wp_context);
753                         return false;
754                 } else
755                         __connman_agent_request_browser(wp_context->service,
756                                         wispr_portal_browser_reply_cb,
757                                         wp_context->redirect_url, wp_context);
758
759                 break;
760         case 300:
761         case 301:
762         case 302:
763         case 303:
764         case 307:
765         case 308:
766                 if (!g_web_supports_tls() ||
767                         !g_web_result_get_header(result, "Location",
768                                                         &redirect)) {
769
770                         __connman_agent_request_browser(wp_context->service,
771                                         wispr_portal_browser_reply_cb,
772                                         wp_context->status_url, wp_context);
773                         break;
774                 }
775
776                 DBG("Redirect URL: %s", redirect);
777
778                 wp_context->redirect_url = g_strdup(redirect);
779
780                 wp_context->request_id = g_web_request_get(wp_context->web,
781                                 redirect, wispr_portal_web_result,
782                                 wispr_route_request, wp_context);
783
784                 goto done;
785         case 400:
786         case 404:
787                 __connman_service_online_check(wp_context->service,
788                                                 wp_context->type, false);
789
790                 break;
791         case 505:
792                 __connman_agent_request_browser(wp_context->service,
793                                 wispr_portal_browser_reply_cb,
794                                 wp_context->status_url, wp_context);
795                 break;
796         default:
797                 break;
798         }
799
800         free_wispr_routes(wp_context);
801         wp_context->request_id = 0;
802 done:
803         wp_context->wispr_msg.message_type = -1;
804         return false;
805 }
806
807 static void proxy_callback(const char *proxy, void *user_data)
808 {
809         struct connman_wispr_portal_context *wp_context = user_data;
810
811         DBG("proxy %s", proxy);
812
813         if (!wp_context)
814                 return;
815
816         wp_context->token = 0;
817
818         if (proxy && g_strcmp0(proxy, "DIRECT") != 0) {
819                 if (g_str_has_prefix(proxy, "PROXY")) {
820                         proxy += 5;
821                         for (; *proxy == ' ' && *proxy != '\0'; proxy++);
822                 }
823                 g_web_set_proxy(wp_context->web, proxy);
824         }
825
826         g_web_set_accept(wp_context->web, NULL);
827         g_web_set_user_agent(wp_context->web, "ConnMan/%s wispr", VERSION);
828         g_web_set_close_connection(wp_context->web, TRUE);
829
830         connman_wispr_message_init(&wp_context->wispr_msg);
831
832         wp_context->wispr_parser = g_web_parser_new(
833                                         "<WISPAccessGatewayParam",
834                                         "WISPAccessGatewayParam>",
835                                         xml_wispr_parser_callback, wp_context);
836
837         wispr_portal_request_portal(wp_context);
838 }
839
840 static gboolean no_proxy_callback(gpointer user_data)
841 {
842         struct connman_wispr_portal_context *wp_context = user_data;
843
844         wp_context->timeout = 0;
845
846         proxy_callback("DIRECT", wp_context);
847
848         return FALSE;
849 }
850
851 static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context)
852 {
853         enum connman_service_proxy_method proxy_method;
854         enum connman_service_type service_type;
855         char *interface = NULL;
856         char **nameservers = NULL;
857         int if_index;
858         int err = 0;
859         int i;
860
861         DBG("wispr/portal context %p service %p", wp_context,
862                 wp_context->service);
863
864         service_type = connman_service_get_type(wp_context->service);
865
866         switch (service_type) {
867         case CONNMAN_SERVICE_TYPE_ETHERNET:
868         case CONNMAN_SERVICE_TYPE_WIFI:
869         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
870         case CONNMAN_SERVICE_TYPE_CELLULAR:
871         case CONNMAN_SERVICE_TYPE_GADGET:
872                 break;
873         case CONNMAN_SERVICE_TYPE_UNKNOWN:
874         case CONNMAN_SERVICE_TYPE_SYSTEM:
875         case CONNMAN_SERVICE_TYPE_GPS:
876         case CONNMAN_SERVICE_TYPE_VPN:
877         case CONNMAN_SERVICE_TYPE_P2P:
878                 return -EOPNOTSUPP;
879         }
880
881         interface = connman_service_get_interface(wp_context->service);
882         if (!interface)
883                 return -EINVAL;
884
885         DBG("interface %s", interface);
886
887         if_index = connman_inet_ifindex(interface);
888         if (if_index < 0) {
889                 DBG("Could not get ifindex");
890                 err = -EINVAL;
891                 goto done;
892         }
893
894         nameservers = connman_service_get_nameservers(wp_context->service);
895         if (!nameservers) {
896                 DBG("Could not get nameservers");
897                 err = -EINVAL;
898                 goto done;
899         }
900
901         wp_context->web = g_web_new(if_index);
902         if (!wp_context->web) {
903                 DBG("Could not set up GWeb");
904                 err = -ENOMEM;
905                 goto done;
906         }
907
908         if (getenv("CONNMAN_WEB_DEBUG"))
909                 g_web_set_debug(wp_context->web, web_debug, "WEB");
910
911         if (wp_context->type == CONNMAN_IPCONFIG_TYPE_IPV4) {
912                 g_web_set_address_family(wp_context->web, AF_INET);
913                 wp_context->status_url = STATUS_URL_IPV4;
914         } else {
915                 g_web_set_address_family(wp_context->web, AF_INET6);
916                 wp_context->status_url = STATUS_URL_IPV6;
917         }
918
919         for (i = 0; nameservers[i]; i++)
920                 g_web_add_nameserver(wp_context->web, nameservers[i]);
921
922         proxy_method = connman_service_get_proxy_method(wp_context->service);
923
924         if (proxy_method != CONNMAN_SERVICE_PROXY_METHOD_DIRECT) {
925                 wp_context->token = connman_proxy_lookup(interface,
926                                                 wp_context->status_url,
927                                                 wp_context->service,
928                                                 proxy_callback, wp_context);
929
930                 if (wp_context->token == 0) {
931                         err = -EINVAL;
932                         free_connman_wispr_portal_context(wp_context);
933                 }
934         } else if (wp_context->timeout == 0) {
935                 wp_context->timeout = g_idle_add(no_proxy_callback, wp_context);
936         }
937
938 done:
939         g_strfreev(nameservers);
940
941         g_free(interface);
942         return err;
943 }
944
945 int __connman_wispr_start(struct connman_service *service,
946                                         enum connman_ipconfig_type type)
947 {
948         struct connman_wispr_portal_context *wp_context = NULL;
949         struct connman_wispr_portal *wispr_portal = NULL;
950         int index;
951
952         DBG("service %p", service);
953
954         if (!wispr_portal_list)
955                 return -EINVAL;
956
957         index = __connman_service_get_index(service);
958         if (index < 0)
959                 return -EINVAL;
960
961         wispr_portal = g_hash_table_lookup(wispr_portal_list,
962                                         GINT_TO_POINTER(index));
963         if (!wispr_portal) {
964                 wispr_portal = g_try_new0(struct connman_wispr_portal, 1);
965                 if (!wispr_portal)
966                         return -ENOMEM;
967
968                 g_hash_table_replace(wispr_portal_list,
969                                         GINT_TO_POINTER(index), wispr_portal);
970         }
971
972         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
973                 wp_context = wispr_portal->ipv4_context;
974         else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
975                 wp_context = wispr_portal->ipv6_context;
976         else
977                 return -EINVAL;
978
979         /* If there is already an existing context, we wipe it */
980         if (wp_context)
981                 free_connman_wispr_portal_context(wp_context);
982
983         wp_context = create_wispr_portal_context();
984         if (!wp_context)
985                 return -ENOMEM;
986
987         wp_context->service = service;
988         wp_context->type = type;
989         wp_context->wispr_portal = wispr_portal;
990
991         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
992                 wispr_portal->ipv4_context = wp_context;
993         else
994                 wispr_portal->ipv6_context = wp_context;
995
996         return wispr_portal_detect(wp_context);
997 }
998
999 void __connman_wispr_stop(struct connman_service *service)
1000 {
1001         struct connman_wispr_portal *wispr_portal;
1002         int index;
1003
1004         DBG("service %p", service);
1005
1006         if (!wispr_portal_list)
1007                 return;
1008
1009         index = __connman_service_get_index(service);
1010         if (index < 0)
1011                 return;
1012
1013         wispr_portal = g_hash_table_lookup(wispr_portal_list,
1014                                         GINT_TO_POINTER(index));
1015         if (!wispr_portal)
1016                 return;
1017
1018         if (wispr_portal->ipv4_context) {
1019                 if (service == wispr_portal->ipv4_context->service)
1020                         g_hash_table_remove(wispr_portal_list,
1021                                         GINT_TO_POINTER(index));
1022         }
1023
1024         if (wispr_portal->ipv6_context) {
1025                 if (service == wispr_portal->ipv6_context->service)
1026                         g_hash_table_remove(wispr_portal_list,
1027                                         GINT_TO_POINTER(index));
1028         }
1029 }
1030
1031 int __connman_wispr_init(void)
1032 {
1033         DBG("");
1034
1035         wispr_portal_list = g_hash_table_new_full(g_direct_hash,
1036                                                 g_direct_equal, NULL,
1037                                                 free_connman_wispr_portal);
1038
1039         enable_online_to_ready_transition =
1040                 connman_setting_get_bool("EnableOnlineToReadyTransition");
1041
1042         return 0;
1043 }
1044
1045 void __connman_wispr_cleanup(void)
1046 {
1047         DBG("");
1048
1049         g_hash_table_destroy(wispr_portal_list);
1050         wispr_portal_list = NULL;
1051 }