| /* |
| * Portal Check - Determines if a user is in an IP restrict pool or behind a |
| * captive portal. |
| * |
| * This file initially created by Google, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdlib.h> |
| #include <time.h> |
| |
| #include <curl/curl.h> |
| |
| #include "connman.h" |
| |
| #define lengthof(array) (sizeof (array) / sizeof ((array)[0])) |
| #define _DBG_PORTAL(fmt, arg...) DBG(DBG_PORTAL, fmt, ## arg) |
| |
| #define MSEC_PER_SEC 1000LL |
| #define NSEC_PER_SEC 1000000000LL |
| |
| #define REQUEST_TIMEOUT_S 10 |
| |
| static CURLM *curl_multi_handle_; |
| |
| struct curl_source { |
| GSource source; /* base class */ |
| guint fd_bmap; |
| GPollFD poll_fds[sizeof(guint) * 8]; |
| struct timespec expiration; |
| }; |
| |
| static struct curl_source *curl_source; |
| |
| struct portal_request { |
| struct connman_service *service; |
| struct connman_device *device; |
| char *interface; |
| CURL *easy_handle; |
| connman_bool_t handle_added; |
| }; |
| |
| static GSList *request_list = NULL; |
| |
| /** |
| * Allocate a new portal request and make it the latest request |
| */ |
| static struct portal_request *new_request(struct connman_service *service, |
| struct connman_device *device, |
| const char *interface, |
| CURL *easy_handle) |
| { |
| struct portal_request *request = g_try_new0(struct portal_request, 1); |
| if (request == NULL) |
| return NULL; |
| request->service = connman_service_ref(service); |
| request->device = connman_device_ref(device); |
| request->interface = g_strdup(interface); |
| request->easy_handle = easy_handle; |
| request->handle_added = FALSE; |
| |
| connman_device_rp_filter_disable(device); |
| |
| request_list = g_slist_append(request_list, request); |
| |
| return request; |
| } |
| |
| /** |
| * Free a portal request |
| */ |
| static void free_request(struct portal_request *request) |
| { |
| request_list = g_slist_remove(request_list, request); |
| |
| if (request->handle_added) |
| curl_multi_remove_handle(curl_multi_handle_, |
| request->easy_handle); |
| curl_easy_cleanup(request->easy_handle); |
| connman_device_rp_filter_enable(request->device); |
| g_free(request->interface); |
| connman_device_unref(request->device); |
| connman_service_unref(request->service); |
| g_free(request); |
| } |
| |
| /** |
| * Cancels a request |
| */ |
| static void cancel_request(struct portal_request *request) |
| { |
| _DBG_PORTAL("%s: cancelling old request", request->interface); |
| free_request(request); |
| } |
| |
| /** |
| * Cancels old requests on this request's interface |
| */ |
| static void cancel_old_requests(struct portal_request *request) |
| { |
| GSList *list; |
| |
| list = request_list; |
| while(list != NULL) { |
| struct portal_request *old_request = list->data; |
| |
| list = list->next; |
| if (old_request == request) |
| continue; |
| |
| if (request->service == old_request->service) |
| cancel_request(old_request); |
| } |
| } |
| |
| /** |
| * Cancels old requests on this service |
| */ |
| static void cancel_service_requests(struct connman_service *service) |
| { |
| GSList *list; |
| |
| for (list = request_list; list != NULL; list = list->next) { |
| struct portal_request *request = list->data; |
| |
| if (request->service == service) |
| cancel_request(request); |
| } |
| } |
| |
| /** |
| * Return TRUE if there is a request in progress for this service. |
| */ |
| static connman_bool_t service_has_request(struct connman_service *service) |
| { |
| GSList *list; |
| |
| for (list = request_list; list != NULL; list = list->next) { |
| struct portal_request *request = list->data; |
| |
| if (request->service == service) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /** |
| * Returns the HTTP response code from a completed curl request. |
| */ |
| static long get_http_code(CURL *curl_handle) |
| { |
| long http_code = 0; |
| curl_easy_getinfo (curl_handle, CURLINFO_RESPONSE_CODE, &http_code); |
| _DBG_PORTAL("http response code is %d", (int) http_code); |
| return http_code; |
| } |
| |
| /** |
| * Returns a connectivity state based on the status of an HTTP request |
| * to portal check URL |
| */ |
| static enum connman_service_connectivity_state check_connectivity_state( |
| CURLMsg *request_status, |
| const char *interface) |
| { |
| CURLcode code = (CURLcode)request_status->data.result; |
| |
| /* Check the request state */ |
| if (CURLE_COULDNT_RESOLVE_HOST == code) { |
| /* |
| * TODO(jglasgow): test resolving the address of the |
| * captive portal web server instead |
| */ |
| connman_info("%s: cannot resolve %s, marking portal", |
| interface, |
| __connman_profile_get_portal_url()); |
| return CONNMAN_SERVICE_CONNECTIVITY_STATE_NONE; |
| } |
| |
| if (CURLE_OK == code) { |
| int http_status_code = get_http_code(request_status->easy_handle); |
| if (204 == http_status_code) { |
| connman_info("%s: interface can route traffic, marking online", |
| interface); |
| return CONNMAN_SERVICE_CONNECTIVITY_STATE_UNRESTRICTED; |
| } |
| connman_info("%s: http return code %d, marking portal", |
| interface, http_status_code); |
| return CONNMAN_SERVICE_CONNECTIVITY_STATE_RESTRICTED; |
| } |
| connman_info("%s: http_get_failed, curl code %d, marking portal", |
| interface, code); |
| return CONNMAN_SERVICE_CONNECTIVITY_STATE_RESTRICTED; |
| } |
| |
| /** |
| * Check the result of previous HTTP requests. |
| */ |
| static void check_request_status(void) |
| { |
| int remaining_updates; |
| CURLMsg* request_status; |
| struct portal_request *portal_request = NULL; |
| struct connman_service *service; |
| |
| while ((request_status = |
| curl_multi_info_read(curl_multi_handle_, &remaining_updates))) { |
| |
| if (CURLMSG_DONE != request_status->msg) |
| continue; |
| |
| curl_easy_getinfo(request_status->easy_handle, CURLINFO_PRIVATE, |
| (char **)&portal_request); |
| service = portal_request->service; |
| |
| if (service != NULL) { |
| enum connman_service_connectivity_state state = |
| check_connectivity_state(request_status, |
| portal_request->interface); |
| |
| connman_service_set_connectivity_state(service, state); |
| |
| } else { |
| _DBG_PORTAL("%s: Ignoring stale results", |
| portal_request->interface); |
| } |
| free_request(portal_request); |
| } |
| } |
| |
| /** |
| * Check the timeout set by libcurl has expired. |
| * |
| * Returns TRUE if it has expired. |
| */ |
| static inline gboolean source_check_expiration(GSource* source, gint *timeout) |
| { |
| struct curl_source *src = (struct curl_source *)source; |
| struct timespec now; |
| gint delay_ms; |
| |
| if (src->expiration.tv_sec == LONG_MAX) { |
| if (timeout) |
| *timeout = -1; |
| return FALSE; |
| } |
| |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| |
| delay_ms = (src->expiration.tv_sec - now.tv_sec) * MSEC_PER_SEC |
| + src->expiration.tv_nsec / (NSEC_PER_SEC / MSEC_PER_SEC) |
| - now.tv_nsec / (NSEC_PER_SEC / MSEC_PER_SEC); |
| |
| if (delay_ms <= 0) { |
| _DBG_PORTAL("request has timeout'ed"); |
| return TRUE; |
| } else { |
| if (timeout) |
| *timeout = delay_ms; |
| return FALSE; |
| } |
| } |
| |
| /** |
| * Callback triggered before the main loop select operation. |
| */ |
| static gboolean source_prepare(GSource* source, gint* timeout) |
| { |
| return source_check_expiration(source, timeout); |
| } |
| |
| /** |
| * Callback triggered when exiting the main loop select. |
| */ |
| static gboolean source_check(GSource* source) |
| { |
| int i, bmap; |
| struct curl_source *src = (struct curl_source *)source; |
| |
| if (source_check_expiration(source, NULL)) |
| return TRUE; |
| |
| bmap = src->fd_bmap; |
| while ((i = __builtin_ffs(bmap) - 1) >= 0) { |
| if (src->poll_fds[i].revents) |
| return TRUE; |
| |
| bmap &= ~(1 << i); |
| } |
| |
| return FALSE; |
| } |
| |
| /** |
| * Conversion table between glib poll events and libcurl select events. |
| */ |
| static struct { |
| int curl_event; |
| int poll_mask; |
| } event_flags[] = { |
| { CURL_CSELECT_IN, G_IO_IN | G_IO_PRI }, |
| { CURL_CSELECT_OUT, G_IO_OUT }, |
| { CURL_CSELECT_ERR, G_IO_ERR | G_IO_HUP | G_IO_NVAL }, |
| }; |
| |
| /** |
| * Process the request if there is an event of the file descriptors. |
| */ |
| static gboolean source_dispatch(GSource *source, GSourceFunc callback, |
| gpointer user_data) |
| { |
| int i, e, bmap; |
| int remaining = -1; |
| struct curl_source *src = (struct curl_source *)source; |
| CURLMcode mcode; |
| |
| if (source_check_expiration(source, NULL)) { |
| mcode = curl_multi_perform(curl_multi_handle_, &remaining); |
| _DBG_PORTAL("mcode = %d, remaining = %d", mcode, remaining); |
| } |
| |
| bmap = src->fd_bmap; |
| while ((i = __builtin_ffs(bmap) - 1) >= 0) { |
| if (src->poll_fds[i].revents) { |
| int ev_bitmask = 0; |
| |
| for (e = 0; e < G_N_ELEMENTS(event_flags); e++) |
| if (src->poll_fds[i].revents & |
| event_flags[e].poll_mask) |
| ev_bitmask |= event_flags[e].curl_event; |
| |
| mcode = curl_multi_socket_action(curl_multi_handle_, |
| src->poll_fds[i].fd, |
| ev_bitmask, |
| &remaining); |
| |
| _DBG_PORTAL("mcode = %d, remaining = %d", mcode, remaining); |
| } |
| bmap &= ~(1 << i); |
| } |
| |
| if (!remaining) { |
| /* no more pending transfer, set infinite timeout */ |
| curl_source->expiration.tv_sec = LONG_MAX; |
| } |
| check_request_status(); |
| |
| return TRUE; |
| } |
| |
| /** |
| * Define the source used to poll on the request descriptors. |
| */ |
| static GSourceFuncs source_callbacks = { |
| source_prepare, source_check, source_dispatch, 0, 0, 0 |
| }; |
| |
| /** |
| * Update the file descriptors used to monitor in the main loop. |
| */ |
| static int socket_callback(CURL *easy, curl_socket_t s, int action, |
| void *userp, void *socketp) |
| { |
| gushort events = G_IO_ERR | G_IO_HUP; |
| struct curl_source *src = userp; |
| GPollFD *poll_fd = socketp; |
| int idx; |
| |
| switch (action) { |
| case CURL_POLL_IN: |
| case CURL_POLL_OUT: |
| case CURL_POLL_INOUT: |
| if (socketp) { |
| g_source_remove_poll(&src->source, poll_fd); |
| } else { |
| if ((idx = (__builtin_ffs(~src->fd_bmap) - 1)) < 0) { |
| _DBG_PORTAL("Cannot set FD, no empty slot.\n"); |
| break; |
| } |
| src->fd_bmap |= (1 << idx); |
| _DBG_PORTAL("FD#%d set (using slot %d)", s, idx); |
| poll_fd = &src->poll_fds[idx]; |
| poll_fd->fd = s; |
| curl_multi_assign(curl_multi_handle_, s, poll_fd); |
| } |
| if ((action == CURL_POLL_IN) || (action == CURL_POLL_INOUT)) |
| events |= G_IO_IN | G_IO_PRI; |
| if ((action == CURL_POLL_OUT) || (action == CURL_POLL_INOUT)) |
| events |= G_IO_OUT; |
| poll_fd->events = events; |
| poll_fd->revents = 0; |
| g_source_add_poll(&src->source, poll_fd); |
| break; |
| case CURL_POLL_REMOVE: |
| if (poll_fd) { |
| poll_fd->revents = 0; |
| g_source_remove_poll(&src->source, poll_fd); |
| _DBG_PORTAL("FD#%d unset (using slot %d)", |
| poll_fd->fd, |
| (int) (poll_fd - src->poll_fds)); |
| src->fd_bmap &= ~(1 << (poll_fd - src->poll_fds)); |
| curl_multi_assign(curl_multi_handle_, s, NULL); |
| } |
| break; |
| } |
| return 0; |
| } |
| |
| /** |
| * Compute and record the timeout requested by libcurl. |
| */ |
| static int timer_callback(CURLM *multi, long timeout_ms, void *userp) |
| { |
| struct curl_source *src = userp; |
| |
| clock_gettime(CLOCK_MONOTONIC, &src->expiration); |
| |
| if (timeout_ms >= 0) { |
| src->expiration.tv_sec += |
| ((long long)timeout_ms * 1000000LL) / NSEC_PER_SEC; |
| src->expiration.tv_nsec += |
| ((long long)timeout_ms * 1000000LL) % NSEC_PER_SEC; |
| if (src->expiration.tv_nsec >= NSEC_PER_SEC) |
| { |
| src->expiration.tv_sec++; |
| src->expiration.tv_nsec -= NSEC_PER_SEC; |
| } |
| |
| } else { |
| src->expiration.tv_sec = LONG_MAX; |
| } |
| _DBG_PORTAL("set timeout %ld ms (expires %ld.%09ld)", timeout_ms, |
| src->expiration.tv_sec, src->expiration.tv_nsec); |
| return 0; |
| } |
| |
| /** |
| * Returns TRUE if a service has entered a state where it may go away soon |
| */ |
| static gboolean should_cancel_request(struct connman_service *service) |
| { |
| const char *service_state; |
| |
| if (service == NULL) { |
| _DBG_PORTAL("SKIP, NULL service"); |
| return FALSE; |
| } |
| service_state = connman_service_get_state(service); |
| if ((g_strcmp0(service_state, "idle") == 0) || |
| (g_strcmp0(service_state, "disconnect") == 0) || |
| (g_strcmp0(service_state, "failure") == 0) || |
| (g_strcmp0(service_state, "activation-failure") == 0)) |
| { |
| _DBG_PORTAL("CANCEL, service %s state %s", |
| connman_service_get_identifier(service), service_state); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /** |
| * Returns TRUE if the new default route is "ready" |
| */ |
| static gboolean should_process_request(struct connman_service *service) |
| { |
| const char *service_state; |
| |
| if (service == NULL) { |
| _DBG_PORTAL("SKIP, NULL service"); |
| return FALSE; |
| } |
| service_state = connman_service_get_state(service); |
| if (g_strcmp0(service_state, "ready") != 0) { |
| _DBG_PORTAL("SKIP, service %s state %s != ready", |
| connman_service_get_identifier(service), service_state); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * Unref a service on the idle thread. |
| */ |
| static gboolean __unref_service(gpointer data) |
| { |
| struct connman_service *service = data; |
| const char *interface = connman_service_get_interface(service); |
| |
| _DBG_PORTAL("%s: unref service", |
| interface != NULL ? interface : "unknown"); |
| |
| connman_service_unref(service); |
| return FALSE; |
| } |
| |
| /** |
| * Cycle a service to the ONLINE state. Used when portal check |
| * is disabled for the service (called from the idle loop). |
| */ |
| static gboolean __mark_unrestricted(gpointer data) |
| { |
| struct connman_service *service = data; |
| const char *interface = connman_service_get_interface(service); |
| |
| _DBG_PORTAL("%s: BYPASS, mark service ONLINE", |
| interface != NULL ? interface : "unknown"); |
| |
| connman_service_set_connectivity_state(service, |
| CONNMAN_SERVICE_CONNECTIVITY_STATE_UNRESTRICTED); |
| connman_service_unref(service); |
| return FALSE; |
| } |
| |
| static int debug_function(CURL *handle, curl_infotype type, |
| char *data, size_t size, void *userptr) |
| { |
| if (type == CURLINFO_TEXT) |
| _DBG_PORTAL("curl: %s", data); |
| return 0; |
| } |
| |
| static void __portal_test(struct connman_service *service) |
| { |
| struct connman_device *device; |
| const char *interface; |
| const struct connman_resolver_state *resolver_state; |
| CURLcode ecode; |
| CURLMcode mcode; |
| CURL * request_handle; |
| struct portal_request *portal_request; |
| connman_bool_t first = TRUE; |
| GString *servers = NULL; |
| char **p; |
| static const struct { |
| CURLoption option; |
| unsigned int parameter; |
| const char *option_name; |
| } options[] = { |
| /* Do not let curl cache DNS entries */ |
| {CURLOPT_DNS_CACHE_TIMEOUT, 0, "DNS_CACHE_TIMEOUT"}, |
| |
| /* Do not allow curl to reuse the connection */ |
| {CURLOPT_FORBID_REUSE, 1, "FORBID_REUSE"}, |
| |
| /* Set a timeout for the request in seconds */ |
| {CURLOPT_TIMEOUT, REQUEST_TIMEOUT_S, "TIMEOUT"}, |
| |
| /* Set a timeout for the TCP/IP connect in seconds */ |
| {CURLOPT_CONNECTTIMEOUT, REQUEST_TIMEOUT_S, "CONNECTTIMEOUT"}, |
| |
| /* Ensure CURL does not send signals or install handlers */ |
| {CURLOPT_NOSIGNAL, 1, "NOSIGNAL"}, |
| |
| /* Instruct curl to generate debugging information */ |
| {CURLOPT_VERBOSE, 1, "VERBOSE"}, |
| }; |
| int i; |
| |
| if (__connman_service_check_portal(service) == FALSE) { |
| /* |
| * The service or device is not meant to do a portal |
| * check; mark it online immediately. |
| */ |
| g_idle_add(__mark_unrestricted, connman_service_ref(service)); |
| return; |
| } |
| |
| device = connman_service_get_device(service); |
| if (device == NULL) { |
| connman_error("%s: no device for service %p", |
| __func__, service); |
| return; |
| } |
| interface = connman_device_get_interface(device); |
| if (interface == NULL) { |
| connman_error("%s: no interface for device %p (service %p)", |
| __func__, device, service); |
| return; |
| } |
| resolver_state = connman_resolver_lookup(interface); |
| if (resolver_state == NULL) { |
| connman_error("%s: no resolver state for service %p", |
| __func__, service); |
| return; |
| } |
| |
| _DBG_PORTAL("%s: Checking %s on %s", |
| interface, |
| __connman_profile_get_portal_url(), |
| connman_service_get_identifier(service)); |
| |
| request_handle = curl_easy_init(); |
| if (request_handle == NULL) { |
| connman_error("%s: easy request is NULL", __func__); |
| return; |
| } |
| |
| /* The portal request will take ownership of the request_handle */ |
| portal_request = new_request(service, device, interface, request_handle); |
| if (portal_request == NULL) { |
| connman_error("%s: Cannot allocate portal request.", __func__); |
| curl_easy_cleanup(request_handle); |
| return; |
| } |
| |
| /* Set standard options */ |
| for (i = 0; i < lengthof(options); i++) { |
| ecode = curl_easy_setopt(request_handle, |
| options[i].option, |
| options[i].parameter); |
| if (ecode != CURLE_OK) { |
| connman_error("%s :%s = %s", __func__, |
| options[i].option_name, |
| curl_easy_strerror(ecode)); |
| goto error; |
| } |
| } |
| |
| /* Set pointer options */ |
| ecode = curl_easy_setopt(request_handle, |
| CURLOPT_PRIVATE, portal_request); |
| if (ecode != CURLE_OK) { |
| connman_error("%s: Unable to set PRIVATE: %s", __func__, |
| curl_easy_strerror(ecode)); |
| goto error; |
| } |
| ecode = curl_easy_setopt(request_handle, CURLOPT_URL, |
| __connman_profile_get_portal_url()); |
| if (ecode != CURLE_OK) { |
| connman_error("%s: Unable to set URL: %s", __func__, |
| curl_easy_strerror(ecode)); |
| goto error; |
| } |
| ecode = curl_easy_setopt(request_handle, CURLOPT_INTERFACE, interface); |
| if (ecode != CURLE_OK) { |
| connman_error("%s: Unable to set INTERFACE: %s", __func__, |
| curl_easy_strerror(ecode)); |
| goto error; |
| } |
| |
| /* name servers */ |
| servers = g_string_sized_new(128); |
| for(p = resolver_state->servers; *p != NULL; p++) { |
| if (!first) |
| g_string_append_c(servers, ','); |
| g_string_append(servers, *p); |
| first = FALSE; |
| } |
| _DBG_PORTAL("%s: Using name servers %s", interface, servers->str); |
| |
| ecode = curl_easy_setopt(request_handle, |
| CURLOPT_DNS_SERVERS, servers->str); |
| if (ecode != CURLE_OK) { |
| connman_error("%s: Unable to set DNS_SERVERS: %s", |
| __func__, curl_easy_strerror(ecode)); |
| goto error; |
| } |
| |
| |
| ecode = curl_easy_setopt(request_handle, |
| CURLOPT_DEBUGFUNCTION, debug_function); |
| if (ecode != CURLE_OK) { |
| connman_error("%s: Unable to set DEBUGFUNCTION: %s", |
| __func__, curl_easy_strerror(ecode)); |
| goto error; |
| } |
| |
| mcode = curl_multi_add_handle(curl_multi_handle_, request_handle); |
| if (mcode != CURLM_OK) { |
| connman_error("%s: curl_multi_add_handle() = %s", __func__, |
| curl_multi_strerror(mcode)); |
| goto error; |
| } |
| portal_request->handle_added = TRUE; |
| |
| cancel_old_requests(portal_request); |
| |
| curl_multi_socket_action(curl_multi_handle_, CURL_SOCKET_BAD, 0, &i); |
| g_string_free(servers, TRUE); |
| return; |
| |
| error: |
| g_string_free(servers, TRUE); |
| free_request(portal_request); |
| } |
| |
| /** |
| * Check to see if we're in an IP restrict pool everytime a service |
| * becomes ready. |
| */ |
| static void portal_service_state_changed(struct connman_service *service) |
| { |
| if (should_cancel_request(service)) { |
| /* |
| * cancel_service_requests might unref the last |
| * reference to the service. That would free the |
| * service object and mean that callers higher up the |
| * stack might dereference an invalid service object. |
| * Prevent that by adding a reference here, and then |
| * releasing that reference on the idle thread. |
| */ |
| connman_service_ref(service); |
| cancel_service_requests(service); |
| g_idle_add(__unref_service, service); |
| |
| return; |
| } |
| |
| if (!should_process_request(service)) |
| return; |
| |
| __portal_test(service); |
| } |
| |
| /** |
| * recheck a service to see if it is in the portal state or online |
| */ |
| void __connman_portal_service_recheck_state(struct connman_service *service) |
| { |
| if (service_has_request(service)) |
| return; |
| |
| __portal_test(service); |
| } |
| |
| static struct connman_notifier portal_notifier = { |
| .name = "portal", |
| .priority = CONNMAN_NOTIFIER_PRIORITY_LOW, |
| .service_state_changed = portal_service_state_changed, |
| }; |
| |
| int __connman_portal_init(void) |
| { |
| CURLMcode mcode; |
| |
| if (connman_notifier_register(&portal_notifier) < 0) { |
| connman_error("Failed to register the portal notifier"); |
| return -1; |
| } |
| |
| curl_multi_handle_ = curl_multi_init(); |
| if (!curl_multi_handle_) { |
| _DBG_PORTAL("Unable to initialize libcurl"); |
| connman_notifier_unregister(&portal_notifier); |
| return -1; |
| } |
| |
| curl_source = (struct curl_source *) |
| g_source_new(&source_callbacks, sizeof(struct curl_source)); |
| curl_source->fd_bmap = 0; |
| curl_source->expiration.tv_sec = LONG_MAX; |
| mcode = curl_multi_setopt(curl_multi_handle_, CURLMOPT_SOCKETFUNCTION, |
| socket_callback); |
| if (mcode != CURLM_OK) { |
| connman_error("%s: curl_multi_setopt(SOCKETFUNCTION) = %s", |
| __func__, curl_multi_strerror(mcode)); |
| goto err_source; |
| } |
| mcode = curl_multi_setopt(curl_multi_handle_, CURLMOPT_SOCKETDATA, |
| curl_source); |
| if (mcode != CURLM_OK) { |
| connman_error("%s: curl_multi_setopt(SOCKETDATA) = %s", |
| __func__, curl_multi_strerror(mcode)); |
| goto err_source; |
| } |
| mcode = curl_multi_setopt(curl_multi_handle_, CURLMOPT_TIMERFUNCTION, |
| timer_callback); |
| if (mcode != CURLM_OK) { |
| connman_error("%s: curl_multi_setopt(TIMERFUNCTION) = %s", |
| __func__, curl_multi_strerror(mcode)); |
| goto err_source; |
| } |
| mcode = curl_multi_setopt(curl_multi_handle_, CURLMOPT_TIMERDATA, |
| curl_source); |
| if (mcode != CURLM_OK) { |
| connman_error("%s: curl_multi_setopt(TIMERDATA) = %s", |
| __func__, curl_multi_strerror(mcode)); |
| goto err_source; |
| } |
| g_source_attach(&curl_source->source, NULL); |
| |
| return 0; |
| |
| err_source: |
| g_source_unref(&curl_source->source); |
| curl_multi_cleanup(curl_multi_handle_); |
| connman_notifier_unregister(&portal_notifier); |
| return -1; |
| } |
| |
| void __connman_portal_cleanup(void) |
| { |
| connman_notifier_unregister(&portal_notifier); |
| g_source_destroy(&curl_source->source); |
| g_source_unref(&curl_source->source); |
| curl_multi_cleanup(curl_multi_handle_); |
| } |