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