+ reply = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, reply) == TRUE) {
+ connman_error("dbus error: %s", error.message);
+ dbus_error_free(&error);
+ goto done;
+ }
+
+ if (dbus_message_has_signature(reply, signature) == FALSE) {
+ connman_error("vpn configuration signature \"%s\" does not "
+ "match expected \"%s\"",
+ dbus_message_get_signature(reply), signature);
+ goto done;
+ }
+
+ if (dbus_message_iter_init(reply, &iter) == FALSE)
+ goto done;
+
+ dbus_message_iter_get_basic(&iter, &path);
+
+ /*
+ * Then try to connect the VPN as expected by ConnectProvider API
+ */
+ ident = get_ident(path);
+
+ data = g_hash_table_lookup(vpn_connections, ident);
+ if (data == NULL) {
+ /*
+ * Someone removed the data. We cannot really continue.
+ */
+ DBG("Pending data not found for %s, cannot continue!", ident);
+ } else {
+ data->call = NULL;
+ data->connect_pending = TRUE;
+
+ if (data->cb_data == NULL)
+ data->cb_data = cb_data;
+ else
+ DBG("Connection callback data already in use!");
+
+ /*
+ * Connection is created in add_connections() after
+ * we have received the ConnectionAdded signal.
+ */
+
+ DBG("cb %p msg %p", data->cb_data,
+ data->cb_data ? data->cb_data->message : NULL);
+ }
+
+done:
+ dbus_message_unref(reply);
+
+ dbus_pending_call_unref(call);
+}
+
+static void set_dbus_ident(char *ident)
+{
+ int i, len = strlen(ident);
+
+ for (i = 0; i < len; i++) {
+ if (ident[i] >= '0' && ident[i] <= '9')
+ continue;
+ if (ident[i] >= 'a' && ident[i] <= 'z')
+ continue;
+ if (ident[i] >= 'A' && ident[i] <= 'Z')
+ continue;
+ ident[i] = '_';
+ }
+}
+
+static struct vpn_route *parse_user_route(const char *user_route)
+{
+ char *network, *netmask;
+ struct vpn_route *route = NULL;
+ int family = PF_UNSPEC;
+ char **elems = g_strsplit(user_route, "/", 0);
+
+ if (elems == NULL)
+ return NULL;
+
+ network = elems[0];
+ if (network == NULL || *network == '\0') {
+ DBG("no network/netmask set");
+ goto out;
+ }
+
+ netmask = elems[1];
+ if (netmask != NULL && *netmask == '\0') {
+ DBG("no netmask set");
+ goto out;
+ }
+
+ if (g_strrstr(network, ":") != NULL)
+ family = AF_INET6;
+ else if (g_strrstr(network, ".") != NULL) {
+ family = AF_INET;
+
+ if (g_strrstr(netmask, ".") == NULL) {
+ /* We have netmask length */
+ in_addr_t addr;
+ struct in_addr netmask_in;
+ unsigned char prefix_len = 32;
+
+ if (netmask != NULL) {
+ char *ptr;
+ long int value = strtol(netmask, &ptr, 10);
+ if (ptr != netmask && *ptr == '\0' &&
+ value <= 32)
+ prefix_len = value;
+ }
+
+ addr = 0xffffffff << (32 - prefix_len);
+ netmask_in.s_addr = htonl(addr);
+ netmask = inet_ntoa(netmask_in);
+
+ DBG("network %s netmask %s", network, netmask);
+ }
+ }
+
+ route = g_try_new(struct vpn_route, 1);
+ if (route == NULL)
+ goto out;
+
+ route->network = g_strdup(network);
+ route->netmask = g_strdup(netmask);
+ route->gateway = NULL;
+ route->family = family;
+
+out:
+ g_strfreev(elems);
+ return route;
+}
+
+static GSList *get_user_networks(DBusMessageIter *array)
+{
+ DBusMessageIter entry;
+ GSList *list = NULL;
+
+ dbus_message_iter_recurse(array, &entry);
+
+ while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
+ const char *val;
+ struct vpn_route *route;
+
+ dbus_message_iter_get_basic(&entry, &val);
+
+ route = parse_user_route(val);
+ if (route != NULL)
+ list = g_slist_prepend(list, route);
+
+ dbus_message_iter_next(&entry);
+ }
+
+ return list;
+}
+
+static void append_route(DBusMessageIter *iter, void *user_data)
+{
+ struct vpn_route *route = user_data;
+ DBusMessageIter item;
+ int family = 0;
+
+ connman_dbus_dict_open(iter, &item);
+
+ if (route == NULL)
+ goto empty_dict;
+
+ if (route->family == AF_INET)
+ family = 4;
+ else if (route->family == AF_INET6)
+ family = 6;
+
+ if (family != 0)
+ connman_dbus_dict_append_basic(&item, "ProtocolFamily",
+ DBUS_TYPE_INT32, &family);
+
+ if (route->network != NULL)
+ connman_dbus_dict_append_basic(&item, "Network",
+ DBUS_TYPE_STRING, &route->network);
+
+ if (route->netmask != NULL)
+ connman_dbus_dict_append_basic(&item, "Netmask",
+ DBUS_TYPE_STRING, &route->netmask);
+
+ if (route->gateway != NULL)
+ connman_dbus_dict_append_basic(&item, "Gateway",
+ DBUS_TYPE_STRING, &route->gateway);
+
+empty_dict:
+ connman_dbus_dict_close(iter, &item);
+}
+
+static void append_routes(DBusMessageIter *iter, void *user_data)
+{
+ GSList *list, *routes = user_data;
+
+ DBG("routes %p", routes);
+
+ for (list = routes; list != NULL; list = g_slist_next(list)) {
+ DBusMessageIter dict;
+ struct vpn_route *route = list->data;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL,
+ &dict);
+ append_route(&dict, route);
+ dbus_message_iter_close_container(iter, &dict);
+ }
+}
+
+static int create_configuration(DBusMessage *msg, connection_ready_cb callback)
+{
+ DBusMessage *new_msg = NULL;
+ DBusPendingCall *call;
+ DBusMessageIter iter, array, new_iter, new_dict;
+ const char *type = NULL, *name = NULL;
+ const char *host = NULL, *domain = NULL;
+ char *ident, *me = NULL;
+ int err = 0;
+ dbus_bool_t result;
+ struct connection_data *data;
+ struct config_create_data *user_data = NULL;
+ GSList *networks = NULL;
+
+ /*
+ * We copy the old message data into new message. We cannot
+ * just use the old message as is because the user route
+ * information is not in the same format in vpnd.
+ */
+ new_msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+ dbus_message_iter_init_append(new_msg, &new_iter);
+ connman_dbus_dict_open(&new_iter, &new_dict);
+
+ dbus_message_iter_init(msg, &iter);
+ dbus_message_iter_recurse(&iter, &array);
+
+ while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry, value;
+ void *item_value;
+ const char *key;
+ int value_type;
+
+ dbus_message_iter_recurse(&array, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ value_type = dbus_message_iter_get_arg_type(&value);
+ item_value = NULL;
+
+ switch (value_type) {
+ case DBUS_TYPE_STRING:
+ dbus_message_iter_get_basic(&value, &item_value);
+
+ if (g_str_equal(key, "Type") == TRUE) {
+ type = (const char *)item_value;
+ } else if (g_str_equal(key, "Name") == TRUE) {
+ name = (const char *)item_value;
+ } else if (g_str_equal(key, "Host") == TRUE) {
+ host = (const char *)item_value;
+ } else if (g_str_equal(key, "VPN.Domain") == TRUE) {
+ domain = (const char *)item_value;
+ }
+
+ DBG("%s %s", key, (char *)item_value);
+
+ if (item_value != NULL)
+ connman_dbus_dict_append_basic(&new_dict, key,
+ value_type, &item_value);
+ break;
+ case DBUS_TYPE_ARRAY:
+ if (g_str_equal(key, "Networks") == TRUE) {
+ networks = get_user_networks(&value);
+ connman_dbus_dict_append_array(&new_dict,
+ "UserRoutes",
+ DBUS_TYPE_DICT_ENTRY,
+ append_routes,
+ networks);
+ }
+ break;
+ }
+
+ dbus_message_iter_next(&array);
+ }
+
+ connman_dbus_dict_close(&new_iter, &new_dict);
+
+ DBG("VPN type %s name %s host %s domain %s networks %p",
+ type, name, host, domain, networks);
+
+ if (host == NULL || domain == NULL) {
+ err = -EINVAL;
+ goto done;
+ }
+
+ if (type == NULL || name == NULL) {
+ err = -EOPNOTSUPP;
+ goto done;
+ }
+
+ ident = g_strdup_printf("%s_%s", host, domain);
+ set_dbus_ident(ident);
+
+ DBG("ident %s", ident);
+
+ data = g_hash_table_lookup(vpn_connections, ident);
+ if (data != NULL) {
+ if (data->call != NULL || data->cb_data != NULL) {
+ DBG("create configuration call already pending");
+ err = -EINPROGRESS;
+ goto done;
+ }
+ } else {
+ char *path = g_strdup_printf("%s/connection/%s", VPN_PATH,
+ ident);
+ data = create_connection_data(path);
+ g_free(path);
+
+ if (data == NULL) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ g_hash_table_insert(vpn_connections, g_strdup(ident), data);
+ }
+
+ /*
+ * User called net.connman.Manager.ConnectProvider if we are here.
+ * So use the data from original message in the new msg.
+ */
+ me = g_strdup(dbus_message_get_destination(msg));
+
+ dbus_message_set_interface(new_msg, VPN_MANAGER_INTERFACE);
+ dbus_message_set_path(new_msg, "/");
+ dbus_message_set_destination(new_msg, VPN_SERVICE);
+ dbus_message_set_sender(new_msg, me);
+ dbus_message_set_member(new_msg, "Create");
+
+ user_data = g_try_new0(struct config_create_data, 1);
+ if (user_data == NULL) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ user_data->callback = callback;
+ user_data->message = dbus_message_ref(msg);
+ user_data->path = NULL;
+
+ DBG("cb %p msg %p", user_data, msg);
+
+ result = dbus_connection_send_with_reply(connection, new_msg,
+ &call, DBUS_TIMEOUT);
+ if (result == FALSE || call == NULL) {
+ err = -EIO;
+ goto done;
+ }
+
+ dbus_pending_call_set_notify(call, configuration_create_reply,
+ user_data, NULL);
+ data->call = call;
+
+done:
+ if (new_msg != NULL)
+ dbus_message_unref(new_msg);
+
+ if (networks != NULL)
+ g_slist_free_full(networks, destroy_route);
+
+ g_free(me);
+ return err;
+}
+
+static connman_bool_t check_host(char **hosts, char *host)
+{
+ int i;
+
+ if (hosts == NULL)
+ return FALSE;
+
+ for (i = 0; hosts[i] != NULL; i++) {
+ if (g_strcmp0(hosts[i], host) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void set_route(struct connection_data *data, struct vpn_route *route)
+{
+ /*
+ * If the VPN administrator/user has given a route to
+ * VPN server, then we must discard that because the
+ * server cannot be contacted via VPN tunnel.
+ */
+ if (check_host(data->host_ip, route->network) == TRUE) {
+ DBG("Discarding VPN route to %s via %s at index %d",
+ route->network, route->gateway, data->index);
+ return;
+ }
+
+ if (route->family == AF_INET6) {
+ unsigned char prefix_len = atoi(route->netmask);
+
+ connman_inet_add_ipv6_network_route(data->index,
+ route->network,
+ route->gateway,
+ prefix_len);
+ } else {
+ connman_inet_add_network_route(data->index, route->network,
+ route->gateway,
+ route->netmask);
+ }