| /* |
| * L2TP/IPsec VPN plugin. |
| * |
| * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <net/if.h> |
| #include <netdb.h> |
| |
| #include <glib.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/plugin.h> |
| #include <connman/dbus.h> |
| #include <connman/provider.h> |
| #include <connman/log.h> |
| #include <connman/task.h> |
| #include <connman/inet.h> |
| |
| #ifdef ENABLE_NSS |
| #include "nss.h" |
| #endif |
| |
| #include "vpn.h" |
| |
| #define _DBG_VPN(fmt, arg...) DBG(DBG_VPN, fmt, ## arg) |
| |
| /* Exit status of l2tpipsec_vpn defined in vpn-manager/service_error.h */ |
| enum l2tpipsec_exit_status { |
| L2TPIPSEC_ERROR_NO_ERROR = 0, |
| |
| /* Common errors */ |
| L2TPIPSEC_ERROR_INTERNAL = 1, |
| L2TPIPSEC_ERROR_INVALID_ARGUMENT = 2, |
| L2TPIPSEC_ERROR_RESOLVE_HOSTNAME_FAILED = 3, |
| |
| /* IPsec specific errors */ |
| L2TPIPSEC_ERROR_IPSEC_CONNECTION_FAILED = 32, |
| L2TPIPSEC_ERROR_IPSEC_PSK_AUTH_FAILED = 33, |
| L2TPIPSEC_ERROR_IPSEC_CERT_AUTH_FAILED = 34, |
| |
| /* LT2P specific errors */ |
| L2TPIPSEC_ERROR_L2TP_CONNECTION_FAILED = 64, |
| |
| /* PPP specific errors */ |
| L2TPIPSEC_ERROR_PPP_CONNECTION_FAILED = 128, |
| L2TPIPSEC_ERROR_PPP_AUTH_FAILED = 129 |
| }; |
| |
| /* Structure used to store any L2TP/IPsec VPN-specific data. */ |
| struct l2tpipsec_data { |
| /* Path to file containing PSK or NULL if PSK not needed. */ |
| gchar *psk_file; |
| connman_bool_t added_host_route; |
| in_addr_t vpn_addr; |
| in_addr_t gw_addr; |
| uint32_t gw_index; |
| }; |
| |
| static DBusConnection *connection; |
| |
| static const char *kPSKProperty = "L2TPIPsec.PSK"; |
| static const char *kPasswordProperty = "L2TPIPsec.Password"; |
| static const char *kUserProperty = "L2TPIPsec.User"; |
| |
| static DBusMessage *li_get_sec(struct connman_task *task, |
| DBusMessage *msg, void *user_data) |
| { |
| const char *user, *passwd; |
| struct connman_provider *provider = user_data; |
| |
| if (dbus_message_get_no_reply(msg) == FALSE) { |
| DBusMessage *reply; |
| |
| user = connman_provider_get_string(provider, kUserProperty); |
| passwd = connman_provider_get_string(provider, |
| kPasswordProperty); |
| |
| if (user == NULL || strlen(user) == 0) { |
| _DBG_VPN("User not set"); |
| return NULL; |
| } |
| if (passwd == NULL || strlen(passwd) == 0) { |
| _DBG_VPN("Password not set"); |
| return NULL; |
| } |
| |
| reply = dbus_message_new_method_return(msg); |
| if (reply == NULL) |
| return NULL; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_STRING, &user, |
| DBUS_TYPE_STRING, &passwd, |
| DBUS_TYPE_INVALID); |
| |
| return reply; |
| } |
| |
| return NULL; |
| } |
| |
| static void remove_psk_file(struct connman_provider *provider) |
| { |
| struct l2tpipsec_data *specific_data; |
| |
| specific_data = vpn_get_specific_data(provider); |
| if (specific_data == NULL) |
| return; |
| if (specific_data->psk_file == NULL) |
| return; |
| unlink(specific_data->psk_file); |
| g_free(specific_data->psk_file); |
| specific_data->psk_file = NULL; |
| } |
| |
| static int li_notify(DBusMessage *msg, struct connman_provider *provider) |
| { |
| DBusMessageIter iter, dict; |
| const char *reason, *key; |
| char *value; |
| char *ifname = NULL; |
| char *dns_servers[3]; |
| struct connman_ipaddress ipaddr; |
| char *remote_server_address = NULL; |
| |
| dbus_message_iter_init(msg, &iter); |
| |
| dbus_message_iter_get_basic(&iter, &reason); |
| dbus_message_iter_next(&iter); |
| |
| _DBG_VPN("Reason %s", reason); |
| |
| if (provider == NULL) { |
| connman_error("%s: No provider found", __func__); |
| return VPN_STATE_FAILURE; |
| } |
| |
| if (strcmp(reason, "connect")) |
| return VPN_STATE_DISCONNECT; |
| |
| memset(&ipaddr, 0, sizeof(ipaddr)); |
| ipaddr.af = AF_INET; |
| ipaddr.mask |= CONNMAN_IPCONFIG_AF; |
| memset(dns_servers, 0, sizeof(dns_servers)); |
| ipaddr.dns_servers = dns_servers; |
| |
| dbus_message_iter_recurse(&iter, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_get_basic(&entry, &value); |
| |
| _DBG_VPN("%s = %s", key, value); |
| |
| if (!strcmp(key, "INTERNAL_IP4_ADDRESS")) { |
| ipaddr.local = value; |
| ipaddr.mask |= CONNMAN_IPCONFIG_LOCAL; |
| } else if (!strcmp(key, "EXTERNAL_IP4_ADDRESS")) { |
| ipaddr.peer = value; |
| ipaddr.mask |= CONNMAN_IPCONFIG_PEER; |
| } else if (!strcmp(key, "GATEWAY_ADDRESS")) { |
| ipaddr.gateway = value; |
| ipaddr.mask |= CONNMAN_IPCONFIG_GW; |
| /* |
| * Set the prefixlen so that dns configuration occurs |
| * and host routes are enabled. The kernel expects |
| * PPP device netmasks to be 32b, otherwise it removes |
| * host routes that it configures. |
| */ |
| ipaddr.prefixlen = 32; |
| ipaddr.mask |= CONNMAN_IPCONFIG_PREFIX; |
| } else if (!strcmp(key, "DNS1")) { |
| ipaddr.dns_servers[0] = value; |
| ipaddr.mask |= CONNMAN_IPCONFIG_DNS; |
| } else if (!strcmp(key, "DNS2")) { |
| ipaddr.dns_servers[1] = value; |
| } else if (!strcmp(key, "INTERNAL_IFNAME")) { |
| ifname = value; |
| } else if (!strcmp(key, "LNS_ADDRESS")) { |
| remote_server_address = value; |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| |
| if (vpn_set_ifname(provider, ifname) < 0) { |
| return VPN_STATE_FAILURE; |
| } |
| |
| if (ipaddr.mask & CONNMAN_IPCONFIG_GW) { |
| /* |
| * If we are creating a new default gateway, we need |
| * to first pin a host route to the VPN server so that |
| * encapsulated packets continue to be sent to the VPN |
| * server endpoint. Note that we do not need to |
| * observe default gateway changes since IPsec v1 |
| * which is used in L2TP over IPsec itself is not able |
| * to handle switching of underlying networks. |
| */ |
| struct l2tpipsec_data *specific_data; |
| if (remote_server_address == NULL) { |
| connman_error("%s: request to set gw without " |
| "LNS_ADDRESS", __func__); |
| return VPN_STATE_FAILURE; |
| } |
| _DBG_VPN("Adding host VPN server route"); |
| specific_data = vpn_get_specific_data(provider); |
| if (!specific_data) { |
| connman_error("%s: no specific data", __func__); |
| return VPN_STATE_FAILURE; |
| } |
| specific_data->vpn_addr = inet_addr(remote_server_address); |
| |
| if (!connman_inet_get_route(specific_data->vpn_addr, |
| &specific_data->gw_index, |
| &specific_data->gw_addr, |
| NULL)) { |
| connman_error("%s: unable to get route", __func__); |
| return VPN_STATE_FAILURE; |
| } |
| connman_inet_add_hostroute( |
| specific_data->gw_index, specific_data->vpn_addr, |
| specific_data->gw_addr); |
| specific_data->added_host_route = TRUE; |
| } |
| connman_provider_ipconfig_set(provider, &ipaddr); |
| |
| remove_psk_file(provider); |
| |
| return VPN_STATE_CONNECT; |
| } |
| |
| static enum connman_provider_error exit_status_to_error(gint status) |
| { |
| if (WIFEXITED(status)) { |
| switch (WEXITSTATUS(status)) { |
| case L2TPIPSEC_ERROR_NO_ERROR: |
| return CONNMAN_PROVIDER_ERROR_NO_ERROR; |
| |
| case L2TPIPSEC_ERROR_RESOLVE_HOSTNAME_FAILED: |
| return CONNMAN_PROVIDER_ERROR_RESOLVE_HOSTNAME_FAILED; |
| |
| case L2TPIPSEC_ERROR_IPSEC_CONNECTION_FAILED: |
| case L2TPIPSEC_ERROR_L2TP_CONNECTION_FAILED: |
| case L2TPIPSEC_ERROR_PPP_CONNECTION_FAILED: |
| return CONNMAN_PROVIDER_ERROR_CONNECT_FAILED; |
| |
| case L2TPIPSEC_ERROR_IPSEC_PSK_AUTH_FAILED: |
| return CONNMAN_PROVIDER_ERROR_IPSEC_PSK_AUTH_FAILED; |
| case L2TPIPSEC_ERROR_IPSEC_CERT_AUTH_FAILED: |
| return CONNMAN_PROVIDER_ERROR_IPSEC_CERT_AUTH_FAILED; |
| case L2TPIPSEC_ERROR_PPP_AUTH_FAILED: |
| return CONNMAN_PROVIDER_ERROR_PPP_AUTH_FAILED; |
| |
| default: |
| return CONNMAN_PROVIDER_ERROR_INTERNAL; |
| } |
| } |
| return CONNMAN_PROVIDER_ERROR_INTERNAL; |
| } |
| |
| static void li_vpn_died(struct connman_task *task, void *user_data) |
| { |
| struct connman_provider *provider = user_data; |
| struct l2tpipsec_data *specific_data; |
| gint status = connman_task_get_exit_status(task); |
| enum connman_provider_error error = exit_status_to_error(status); |
| |
| remove_psk_file(provider); |
| specific_data = vpn_get_specific_data(provider); |
| if (specific_data && specific_data->added_host_route) { |
| _DBG_VPN("Tearing down host VPN server route"); |
| connman_inet_del_hostroute(specific_data->gw_index, |
| specific_data->vpn_addr, |
| specific_data->gw_addr); |
| specific_data->added_host_route = FALSE; |
| } |
| vpn_set_specific_data(provider, NULL); |
| g_free(specific_data); |
| vpn_died(task, user_data, error); |
| } |
| |
| static int init_specific_data(struct connman_provider *provider) |
| { |
| struct l2tpipsec_data *specific_data; |
| |
| specific_data = g_try_new0(struct l2tpipsec_data, 1); |
| if (specific_data == NULL) { |
| connman_error("%s: out of memory", __func__); |
| return -ENOMEM; |
| } |
| vpn_set_specific_data(provider, specific_data); |
| return 0; |
| } |
| |
| static int create_psk_file(struct connman_provider *provider, |
| const char *psk, |
| const char **psk_file) |
| { |
| struct l2tpipsec_data *specific_data = NULL; |
| char psk_file_template[] = "/var/run/flimflam/l2tpipsec_XXXXXX"; |
| int psk_file_fd; |
| |
| /* mkstemp assures 0600 file permissions. */ |
| psk_file_fd = mkstemp(psk_file_template); |
| if (psk_file_fd < 0) { |
| connman_error("%s: mkstemp failed", __func__); |
| return -EIO; |
| } |
| |
| if (write(psk_file_fd, psk, strlen(psk)) != strlen(psk)) { |
| connman_error("%s: bad write", __func__); |
| return -EIO; |
| } |
| |
| close(psk_file_fd); |
| |
| specific_data = vpn_get_specific_data(provider); |
| |
| specific_data->psk_file = g_strdup(psk_file_template); |
| |
| *psk_file = specific_data->psk_file; |
| return 0; |
| } |
| |
| static int li_connect(struct connman_provider *provider, |
| struct connman_task *task, const char *if_name) |
| { |
| #define OPT_STR(property, option) do { \ |
| const char *s = connman_provider_get_string(provider, property); \ |
| if (s != NULL) \ |
| connman_task_add_argument(task, option, "%s", s); \ |
| } while (0) |
| #define OPT_BOOL(property, true_option, false_option) do { \ |
| const char *s = connman_provider_get_string(provider, property); \ |
| if (s != NULL) { \ |
| connman_task_add_argument(task, strcmp("true", s) == 0 ? \ |
| true_option : false_option, \ |
| NULL); \ |
| } \ |
| } while (0) |
| const char *vpnhost; |
| const char *value; |
| const char *psk_file; |
| int err, fd; |
| |
| if (init_specific_data(provider) < 0) |
| return -ENOMEM; |
| |
| if (connman_task_set_notify(task, "getsec", |
| li_get_sec, provider)) |
| return -ENOMEM; |
| |
| vpnhost = connman_provider_get_string(provider, CONNMAN_PROVIDER_HOST); |
| if (!vpnhost) { |
| connman_error("%s: host not set; cannot enable VPN", __func__); |
| return -EINVAL; |
| } |
| |
| value = connman_provider_get_string(provider, kPSKProperty); |
| if (value != NULL && value[0] != '\0') { |
| if (create_psk_file(provider, value, &psk_file) < 0) { |
| connman_error("%s: unable to write psk file", __func__); |
| } else { |
| connman_task_add_argument(task, "--psk_file", |
| "%s", psk_file); |
| } |
| } |
| |
| connman_task_add_argument(task, "--remote_host", |
| "%s", vpnhost); |
| connman_task_add_argument(task, "--pppd_plugin", |
| "%s", SCRIPTDIR "/libppp-plugin.so"); |
| /* Disable pppd from configuring IP addresses, routes, dns. */ |
| connman_task_add_argument(task, "--nosystemconfig", NULL); |
| |
| #ifdef ENABLE_NSS |
| value = connman_provider_get_string(provider, "L2TPIPsec.CACertNSS"); |
| if (value != NULL) { |
| char *filename; |
| filename = nss_get_der_certfile(value, (uint8_t *)vpnhost, |
| strlen(vpnhost)); |
| if (filename != NULL) { |
| connman_task_add_argument(task, "--server_ca_file", |
| "%s", filename); |
| } |
| g_free(filename); |
| } |
| #endif |
| OPT_STR("L2TPIPsec.ClientCertID", "--client_cert_id"); |
| OPT_STR("L2TPIPsec.ClientCertSlot", "--client_cert_slot"); |
| OPT_STR("L2TPIPsec.PIN", "--user_pin"); |
| OPT_STR("L2TPIPsec.User", "--user"); |
| OPT_STR("L2TPIPsec.IPsecTimeout", "--ipsec_timeout"); |
| OPT_STR("L2TPIPsec.LeftProtoPort", "--leftprotoport"); |
| OPT_BOOL("L2TPIPsec.PFS", "--pfs", "--nopfs"); |
| OPT_BOOL("L2TPIPsec.Rekey", "--rekey", "--norekey"); |
| OPT_STR("L2TPIPsec.RightProtoPort", "--leftprotoport"); |
| |
| OPT_BOOL("L2TPIPsec.RequireChap", "--require_chap", |
| "--norequire_chap"); |
| OPT_BOOL("L2TPIPsec.RefusePap", "--refuse_pap", "--norefuse_pap"); |
| OPT_BOOL("L2TPIPsec.RequireAuth", "--require_authentication", |
| "--norequire_authentication"); |
| OPT_BOOL("L2TPIPsec.LengthBit", "--length_bit", "--nolength_bit"); |
| if (connman_debug_enabled(DBG_VPN) == TRUE) |
| connman_task_add_argument(task, "--debug", NULL); |
| |
| fd = fileno(stderr); |
| err = connman_task_run(task, li_vpn_died, provider, |
| NULL, &fd, &fd); |
| if (err < 0) { |
| connman_error("l2tpipsec failed to start"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static const char *li_public_props[] = { |
| "L2TPIPsec.CACertNSS", |
| "L2TPIPsec.ClientCertID", |
| "L2TPIPsec.ClientCertSlot", |
| "L2TPIPsec.IPsecTimeout", |
| "L2TPIPsec.LeftProtoPort", |
| "L2TPIPsec.LengthBit", |
| "L2TPIPsec.PFS", |
| "L2TPIPsec.RefusePap", |
| "L2TPIPsec.Rekey", |
| "L2TPIPsec.RequireAuth", |
| "L2TPIPsec.RequireChap", |
| "L2TPIPsec.RightProtoPort", |
| "L2TPIPsec.User", |
| NULL |
| }; |
| |
| /* |
| * Append plugin-specific properties to the D-Bus dictionary. |
| */ |
| void li_append_props(struct connman_provider *provider, DBusMessageIter *iter, |
| connman_bool_t isprivileged) |
| { |
| static const char *li_priv_props[] = { |
| "L2TPIPsec.Password", |
| "L2TPIPsec.PIN", |
| "L2TPIPsec.PSK", |
| NULL |
| }; |
| dbus_bool_t required; |
| |
| connman_provider_append_properties(provider, li_public_props, iter); |
| if (isprivileged) |
| connman_provider_append_properties(provider, li_priv_props, |
| iter); |
| |
| required = connman_provider_property_is_empty(provider, |
| kPasswordProperty); |
| connman_dbus_dict_append_basic(iter, "PassphraseRequired", |
| DBUS_TYPE_BOOLEAN, &required); |
| required = connman_provider_property_is_empty(provider, |
| kPSKProperty); |
| connman_dbus_dict_append_basic(iter, "L2TPIPsec.PSKRequired", |
| DBUS_TYPE_BOOLEAN, &required); |
| } |
| |
| /* |
| * Load plugin-specific properties from the profile. |
| */ |
| static void li_load_props(struct connman_provider *provider, GKeyFile *keyfile) |
| { |
| connman_provider_load_save_properties( |
| provider, li_public_props, connman_provider_load_property, keyfile); |
| connman_provider_load_encrypted_property(provider, kPSKProperty, |
| keyfile); |
| connman_provider_load_encrypted_property(provider, kPasswordProperty, |
| keyfile); |
| } |
| |
| /* |
| * Save plugin-specific properties to the profile. |
| */ |
| static void li_save_props(struct connman_provider *provider, GKeyFile *keyfile) |
| { |
| connman_provider_load_save_properties( |
| provider, li_public_props, connman_provider_save_property, keyfile); |
| connman_provider_save_encrypted_property(provider, kPSKProperty, |
| keyfile); |
| connman_provider_save_encrypted_property(provider, kPasswordProperty, |
| keyfile); |
| } |
| |
| static struct vpn_driver vpn_driver = { |
| .flags = VPN_FLAG_NO_TUN, |
| .notify = li_notify, |
| .connect = li_connect, |
| .append_props = li_append_props, |
| .load_props = li_load_props, |
| .save_props = li_save_props, |
| }; |
| |
| static void __mask_value_of_keys(void) |
| { |
| connman_log_mask_value_of_key(kPSKProperty); |
| connman_log_mask_value_of_key(kPasswordProperty); |
| connman_log_mask_value_of_key(kUserProperty); |
| } |
| |
| static int li_init(void) |
| { |
| connection = connman_dbus_get_connection(); |
| |
| __mask_value_of_keys(); |
| |
| return vpn_register("l2tpipsec", &vpn_driver, L2TPIPSEC); |
| } |
| |
| static void li_exit(void) |
| { |
| vpn_unregister("l2tpipsec"); |
| |
| dbus_connection_unref(connection); |
| } |
| |
| CONNMAN_PLUGIN_DEFINE(l2tpipsec, "l2tpipsec plugin", VERSION, |
| CONNMAN_PLUGIN_PRIORITY_DEFAULT, li_init, li_exit) |