/* * * Connection Manager * * Copyright (C) 2007-2013 Intel Corporation. All rights reserved. * * 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 #endif #include #include #include #include #include #include #include "connman.h" #define TS_RECHECK_INTERVAL 7200 static struct connman_service *ts_service; static GSList *timeservers_list = NULL; static GSList *ts_list = NULL; static char *ts_current = NULL; static int ts_recheck_id = 0; static int ts_backoff_id = 0; static bool ts_is_synced = false; static GResolv *resolv = NULL; static int resolv_id = 0; static void sync_next(void); static void resolv_debug(const char *str, void *data) { connman_info("%s: %s\n", (const char *) data, str); } static void ntp_callback(bool success, void *user_data) { dbus_uint64_t timestamp; struct timeval tv; DBG("success %d", success); __connman_timeserver_set_synced(success); if (!success) { sync_next(); return; } if (gettimeofday(&tv, NULL) < 0) { connman_warn("Failed to get current time"); } timestamp = tv.tv_sec; connman_dbus_property_changed_basic( CONNMAN_MANAGER_PATH, CONNMAN_CLOCK_INTERFACE, "Time", DBUS_TYPE_UINT64, ×tamp); } static void save_timeservers(char **servers) { GKeyFile *keyfile; int cnt; keyfile = __connman_storage_load_global(); if (!keyfile) keyfile = g_key_file_new(); for (cnt = 0; servers && servers[cnt]; cnt++); g_key_file_set_string_list(keyfile, "global", "Timeservers", (const gchar **)servers, cnt); __connman_storage_save_global(keyfile); g_key_file_free(keyfile); } static char **load_timeservers(void) { GKeyFile *keyfile; char **servers = NULL; keyfile = __connman_storage_load_global(); if (!keyfile) return NULL; servers = g_key_file_get_string_list(keyfile, "global", "Timeservers", NULL, NULL); g_key_file_free(keyfile); return servers; } static void resolv_result(GResolvResultStatus status, char **results, gpointer user_data) { int i; #if defined TIZEN_EXT gchar *server = NULL; server = g_strdup((gchar *)user_data); ts_list = g_slist_append(ts_list, server); if (!simplified_log) DBG("added server %s", server); if (!simplified_log) #endif DBG("status %d", status); if (status == G_RESOLV_RESULT_STATUS_SUCCESS) { if (results) { /* prepend the results in reverse order */ for (i = 0; results[i]; i++) /* count */; i--; for (; i >= 0; i--) { DBG("result[%d]: %s", i, results[i]); ts_list = __connman_timeserver_add_list( ts_list, results[i]); } } } sync_next(); } /* * Once the timeserver list (timeserver_list) is created, we start * querying the servers one by one. If resolving fails on one of them, * we move to the next one. The user can enter either an IP address or * a URL for the timeserver. We only resolve the URLs. Once we have an * IP for the NTP server, we start querying it for time corrections. */ static void timeserver_sync_start(void) { GSList *list; for (list = timeservers_list; list; list = list->next) { char *timeserver = list->data; ts_list = g_slist_prepend(ts_list, g_strdup(timeserver)); } ts_list = g_slist_reverse(ts_list); sync_next(); } static gboolean timeserver_sync_restart(gpointer user_data) { timeserver_sync_start(); ts_backoff_id = 0; return FALSE; } /* * Select the next time server from the working list (ts_list) because * for some reason the first time server in the list didn't work. If * none of the server did work we start over with the first server * with a backoff. */ static void sync_next(void) { if (ts_current) { g_free(ts_current); ts_current = NULL; } __connman_ntp_stop(); while (ts_list) { ts_current = ts_list->data; ts_list = g_slist_delete_link(ts_list, ts_list); /* if it's an IP, directly query it. */ if (connman_inet_check_ipaddress(ts_current) > 0) { DBG("Using timeserver %s", ts_current); __connman_ntp_start(ts_current, ntp_callback, NULL); return; } #if defined TIZEN_EXT if (!simplified_log) #endif DBG("Resolving timeserver %s", ts_current); #if defined TIZEN_EXT resolv_id = g_resolv_lookup_hostname(resolv, ts_current, resolv_result, ts_current); #else resolv_id = g_resolv_lookup_hostname(resolv, ts_current, resolv_result, NULL); #endif return; } DBG("No timeserver could be used, restart probing in 5 seconds"); ts_backoff_id = g_timeout_add_seconds(5, timeserver_sync_restart, NULL); } GSList *__connman_timeserver_add_list(GSList *server_list, const char *timeserver) { GSList *list = server_list; if (!timeserver) return server_list; while (list) { char *existing_server = list->data; if (strcmp(timeserver, existing_server) == 0) return server_list; list = g_slist_next(list); } return g_slist_prepend(server_list, g_strdup(timeserver)); } /* * __connman_timeserver_get_all function creates the timeserver * list which will be used to determine NTP server for time corrections. * The service settings take priority over the global timeservers. */ GSList *__connman_timeserver_get_all(struct connman_service *service) { GSList *list = NULL; struct connman_network *network; char **timeservers; char **service_ts; char **service_ts_config; const char *service_gw; char **fallback_ts; int index, i; if (__connman_clock_timeupdates() == TIME_UPDATES_MANUAL) return NULL; service_ts_config = connman_service_get_timeservers_config(service); /* First add Service Timeservers.Configuration to the list */ for (i = 0; service_ts_config && service_ts_config[i]; i++) list = __connman_timeserver_add_list(list, service_ts_config[i]); service_ts = connman_service_get_timeservers(service); /* Then add Service Timeservers via DHCP to the list */ for (i = 0; service_ts && service_ts[i]; i++) list = __connman_timeserver_add_list(list, service_ts[i]); /* * Then add Service Gateway to the list, if UseGatewaysAsTimeservers * configuration option is set to true. */ if (connman_setting_get_bool("UseGatewaysAsTimeservers")) { network = __connman_service_get_network(service); if (network) { index = connman_network_get_index(network); service_gw = __connman_ipconfig_get_gateway_from_index(index, CONNMAN_IPCONFIG_TYPE_ALL); if (service_gw) list = __connman_timeserver_add_list(list, service_gw); } } /* Then add Global Timeservers to the list */ timeservers = load_timeservers(); for (i = 0; timeservers && timeservers[i]; i++) list = __connman_timeserver_add_list(list, timeservers[i]); g_strfreev(timeservers); fallback_ts = connman_setting_get_string_list("FallbackTimeservers"); /* Lastly add the fallback servers */ for (i = 0; fallback_ts && fallback_ts[i]; i++) list = __connman_timeserver_add_list(list, fallback_ts[i]); return g_slist_reverse(list); } static gboolean ts_recheck(gpointer user_data) { struct connman_service *service; GSList *ts; ts = __connman_timeserver_get_all(connman_service_get_default()); if (!ts) { DBG("timeservers disabled"); return TRUE; } if (g_strcmp0(ts_current, ts->data) != 0) { DBG("current %s preferred %s", ts_current, (char *)ts->data); g_slist_free_full(ts, g_free); service = connman_service_get_default(); __connman_timeserver_sync(service); return FALSE; } DBG(""); g_slist_free_full(ts, g_free); return TRUE; } static void ts_recheck_disable(void) { if (ts_recheck_id == 0) return; g_source_remove(ts_recheck_id); ts_recheck_id = 0; if (ts_backoff_id) { g_source_remove(ts_backoff_id); ts_backoff_id = 0; } if (ts_current) { g_free(ts_current); ts_current = NULL; } } static void ts_recheck_enable(void) { if (ts_recheck_id > 0) return; ts_recheck_id = g_timeout_add_seconds(TS_RECHECK_INTERVAL, ts_recheck, NULL); } static void ts_reset(struct connman_service *service) { char **nameservers; int i; if (!resolv) return; __connman_timeserver_set_synced(false); /* * Before we start creating the new timeserver list we must stop * any ongoing ntp query and server resolution. */ __connman_ntp_stop(); ts_recheck_disable(); if (resolv_id > 0) g_resolv_cancel_lookup(resolv, resolv_id); g_resolv_flush_nameservers(resolv); nameservers = connman_service_get_nameservers(service); if (nameservers) { for (i = 0; nameservers[i]; i++) g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); g_strfreev(nameservers); } g_slist_free_full(timeservers_list, g_free); timeservers_list = __connman_timeserver_get_all(service); __connman_service_timeserver_changed(service, timeservers_list); if (!timeservers_list) { DBG("No timeservers set."); return; } ts_recheck_enable(); ts_service = service; timeserver_sync_start(); } void __connman_timeserver_sync(struct connman_service *service) { if (!service || ts_service == service) return; ts_reset(service); } void __connman_timeserver_conf_update(struct connman_service *service) { if (!service || (ts_service && ts_service != service)) return; ts_reset(service); } bool __connman_timeserver_is_synced(void) { return ts_is_synced; } void __connman_timeserver_set_synced(bool status) { dbus_bool_t is_synced; if (ts_is_synced == status) return; ts_is_synced = status; is_synced = status; connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH, CONNMAN_CLOCK_INTERFACE, "TimeserverSynced", DBUS_TYPE_BOOLEAN, &is_synced); } static int timeserver_start(struct connman_service *service) { char **nameservers; int i; DBG("service %p", service); i = __connman_service_get_index(service); if (i < 0) return -EINVAL; nameservers = connman_service_get_nameservers(service); /* Stop an already ongoing resolution, if there is one */ if (resolv && resolv_id > 0) g_resolv_cancel_lookup(resolv, resolv_id); /* get rid of the old resolver */ if (resolv) { g_resolv_unref(resolv); resolv = NULL; } resolv = g_resolv_new(i); if (!resolv) { g_strfreev(nameservers); return -ENOMEM; } if (getenv("CONNMAN_RESOLV_DEBUG")) g_resolv_set_debug(resolv, resolv_debug, "RESOLV"); if (nameservers) { for (i = 0; nameservers[i]; i++) g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); g_strfreev(nameservers); } __connman_timeserver_sync(service); return 0; } static void timeserver_stop(void) { DBG(" "); ts_service = NULL; if (resolv) { g_resolv_unref(resolv); resolv = NULL; } g_slist_free_full(timeservers_list, g_free); timeservers_list = NULL; g_slist_free_full(ts_list, g_free); ts_list = NULL; __connman_ntp_stop(); ts_recheck_disable(); } int __connman_timeserver_system_set(char **servers) { struct connman_service *service; save_timeservers(servers); service = connman_service_get_default(); if (service) ts_reset(service); return 0; } char **__connman_timeserver_system_get() { char **servers; servers = load_timeservers(); return servers; } static void default_changed(struct connman_service *default_service) { if (default_service) timeserver_start(default_service); else timeserver_stop(); } static const struct connman_notifier timeserver_notifier = { .name = "timeserver", .default_changed = default_changed, }; int __connman_timeserver_init(void) { DBG(""); connman_notifier_register(×erver_notifier); return 0; } void __connman_timeserver_cleanup(void) { DBG(""); connman_notifier_unregister(×erver_notifier); }