diff options
author | Tobias Brunner <tobias@strongswan.org> | 2015-07-28 13:29:03 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2015-07-28 13:55:23 +0200 |
commit | f14feed01421384b610f896916a28bd4f73f895a (patch) | |
tree | 598a92977c36e3af97755049259c8ac6cbece668 | |
parent | fbcac07043a6e98ebba8df8bc2c1eb282ae1a2a3 (diff) | |
parent | 1e323dc1b772a42470939ab53ad295b3bc786e30 (diff) | |
download | strongswan-f14feed01421384b610f896916a28bd4f73f895a.tar.bz2 strongswan-f14feed01421384b610f896916a28bd4f73f895a.tar.xz |
Merge branch 'android-updates'
Fixes the roaming behavior on Android 5+, a linker issue on Android M,
a few bugs, and adds several new advanced options for VPN profile (MTU,
server port, split tunneling).
Also adds methods and a constructor to parse settings_t from a string
instead of a file.
Fixes #782, #847, #865.
38 files changed, 1242 insertions, 399 deletions
diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 10365985b..5075440e2 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2014 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -20,7 +20,7 @@ android:versionCode="26" android:versionName="1.4.5" > - <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" /> + <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="22" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk index 670e83de1..1fb233b48 100644 --- a/src/frontends/android/jni/Android.mk +++ b/src/frontends/android/jni/Android.mk @@ -6,7 +6,7 @@ include $(CLEAR_VARS) strongswan_USE_BYOD := true strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ - pkcs1 pkcs8 pem xcbc hmac socket-default kernel-netlink \ + pkcs1 pkcs8 pem xcbc hmac socket-default \ eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls ifneq ($(strongswan_USE_BYOD),) diff --git a/src/frontends/android/jni/Application.mk b/src/frontends/android/jni/Application.mk index cdfb47400..9fa668354 100644 --- a/src/frontends/android/jni/Application.mk +++ b/src/frontends/android/jni/Application.mk @@ -1,2 +1,3 @@ # select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs). APP_ABI := armeabi x86 mips +APP_PLATFORM := android-19 diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c index 7ab9a24bd..a6412bdf7 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -15,6 +15,8 @@ * for more details. */ +#include <dlfcn.h> + #include "android_jni.h" #include <library.h> @@ -25,6 +27,21 @@ */ static JavaVM *android_jvm; +static struct { + char name[32]; + void *handle; +} libs[] = { + { "libstrongswan.so", NULL }, +#ifdef USE_BYOD + { "libtncif.so", NULL }, + { "libtnccs.so", NULL }, + { "libimcv.so", NULL }, +#endif + { "libhydra.so", NULL }, + { "libcharon.so", NULL }, + { "libipsec.so", NULL }, +}; + jclass *android_charonvpnservice_class; jclass *android_charonvpnservice_builder_class; android_sdk_version_t android_sdk_version; @@ -79,6 +96,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) JNIEnv *env; jclass jversion; jfieldID jsdk_int; + int i; android_jvm = vm; @@ -87,6 +105,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) return -1; } + for (i = 0; i < countof(libs); i++) + { + libs[i].handle = dlopen(libs[i].name, RTLD_GLOBAL); + if (!libs[i].handle) + { + return -1; + } + } + androidjni_threadlocal = thread_value_create(attached_thread_cleanup); android_charonvpnservice_class = @@ -109,6 +136,16 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) */ void JNI_OnUnload(JavaVM *vm, void *reserved) { + int i; + androidjni_threadlocal->destroy(androidjni_threadlocal); + + for (i = countof(libs) - 1; i >= 0; i--) + { + if (libs[i].handle) + { + dlclose(libs[i].handle); + } + } } diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 99c0bc2cd..b08670f7e 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -54,6 +54,8 @@ typedef enum { ANDROID_ICE_CREAM_SANDWICH = 14, ANDROID_ICE_CREAM_SANDWICH_MR1 = 15, ANDROID_JELLY_BEAN = 16, + ANDROID_JELLY_BEAN_MR1 = 17, + ANDROID_JELLY_BEAN_MR2 = 18, } android_sdk_version_t; /** diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 896bb0940..7ef3913f7 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 Tobias Brunner + * Copyright (C) 2010-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -32,8 +32,6 @@ typedef struct private_android_service_t private_android_service_t; -#define TUN_DEFAULT_MTU 1400 - /** * private data of Android service */ @@ -55,24 +53,9 @@ struct private_android_service_t { ike_sa_t *ike_sa; /** - * the type of VPN - */ - char *type; - - /** - * gateway + * configuration setttings */ - char *gateway; - - /** - * username - */ - char *username; - - /** - * password - */ - char *password; + settings_t *settings; /** * lock to safely access the TUN device fd @@ -85,6 +68,11 @@ struct private_android_service_t { int tunfd; /** + * MTU of TUN device + */ + int mtu; + + /** * DNS proxy */ android_dns_proxy_t *dns_proxy; @@ -191,7 +179,7 @@ static job_requeue_t handle_plain(private_android_service_t *this) return JOB_REQUEUE_DIRECT; } - raw = chunk_alloc(TUN_DEFAULT_MTU); + raw = chunk_alloc(this->mtu); len = read(tunfd, raw.ptr, raw.len); if (len < 0) { @@ -309,7 +297,7 @@ static bool setup_tun_device(private_android_service_t *this, return FALSE; } if (!add_routes(builder, child_sa) || - !builder->set_mtu(builder, TUN_DEFAULT_MTU)) + !builder->set_mtu(builder, this->mtu)) { return FALSE; } @@ -621,6 +609,7 @@ static void add_auth_cfg_pw(private_android_service_t *this, { identification_t *user; auth_cfg_t *auth; + char *username, *password; auth = auth_cfg_create(); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); @@ -629,12 +618,14 @@ static void add_auth_cfg_pw(private_android_service_t *this, auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TTLS); } - user = identification_create_from_string(this->username); + username = this->settings->get_str(this->settings, "connection.username", + NULL); + password = this->settings->get_str(this->settings, "connection.password", + NULL); + user = identification_create_from_string(username); auth->add(auth, AUTH_RULE_IDENTITY, user); - this->creds->add_username_password(this->creds, this->username, - this->password); - memwipe(this->password, strlen(this->password)); + this->creds->add_username_password(this->creds, username, password); peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); } @@ -644,6 +635,7 @@ static bool add_auth_cfg_cert(private_android_service_t *this, certificate_t *cert; identification_t *id; auth_cfg_t *auth; + char *type; cert = this->creds->load_user_certificate(this->creds); if (!cert) @@ -651,8 +643,9 @@ static bool add_auth_cfg_cert(private_android_service_t *this, return FALSE; } + type = this->settings->get_str(this->settings, "connection.type", NULL); auth = auth_cfg_create(); - if (strpfx("ikev2-eap-tls", this->type)) + if (strpfx("ikev2-eap-tls", type)) { auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS); @@ -687,11 +680,15 @@ static job_requeue_t initiate(private_android_service_t *this) .jitter = 300 /* 5min */ } }; + char *type, *server; + int port; + server = this->settings->get_str(this->settings, "connection.server", NULL); + port = this->settings->get_int(this->settings, "connection.port", + IKEV2_UDP_PORT); ike_cfg = ike_cfg_create(IKEV2, TRUE, TRUE, "0.0.0.0", charon->socket->get_port(charon->socket, FALSE), - this->gateway, IKEV2_UDP_PORT, - FRAGMENTATION_YES, 0); + server, port, FRAGMENTATION_YES, 0); ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE)); @@ -705,10 +702,11 @@ static job_requeue_t initiate(private_android_service_t *this) peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET)); peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6)); + type = this->settings->get_str(this->settings, "connection.type", NULL); /* local auth config */ - if (streq("ikev2-cert", this->type) || - streq("ikev2-cert-eap", this->type) || - streq("ikev2-eap-tls", this->type)) + if (streq("ikev2-cert", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-eap-tls", type)) { if (!add_auth_cfg_cert(this, peer_cfg)) { @@ -718,16 +716,16 @@ static job_requeue_t initiate(private_android_service_t *this) return JOB_REQUEUE_NONE; } } - if (streq("ikev2-eap", this->type) || - streq("ikev2-cert-eap", this->type) || - streq("ikev2-byod-eap", this->type)) + if (streq("ikev2-eap", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-byod-eap", type)) { - add_auth_cfg_pw(this, peer_cfg, strpfx(this->type, "ikev2-byod")); + add_auth_cfg_pw(this, peer_cfg, strpfx(type, "ikev2-byod")); } /* remote auth config */ auth = auth_cfg_create(); - gateway = identification_create_from_string(this->gateway); + gateway = identification_create_from_string(server); auth->add(auth, AUTH_RULE_IDENTITY, gateway); auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); @@ -806,23 +804,15 @@ METHOD(android_service_t, destroy, void, close_tun_device(this); this->dns_proxy->destroy(this->dns_proxy); this->lock->destroy(this->lock); - free(this->type); - free(this->gateway); - free(this->username); - if (this->password) - { - memwipe(this->password, strlen(this->password)); - free(this->password); - } + this->settings->destroy(this->settings); free(this); } /** * See header */ -android_service_t *android_service_create(android_creds_t *creds, char *type, - char *gateway, char *username, - char *password) +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings) { private_android_service_t *this; @@ -840,15 +830,14 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, }, .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .dns_proxy = android_dns_proxy_create(), - .username = username, - .password = password, - .gateway = gateway, + .settings = settings, .creds = creds, - .type = type, .tunfd = -1, + .mtu = settings->get_int(settings, "global.mtu", ANDROID_DEFAULT_MTU), ); /* only allow queries for the VPN gateway */ - this->dns_proxy->add_hostname(this->dns_proxy, gateway); + this->dns_proxy->add_hostname(this->dns_proxy, + this->settings->get_str(this->settings, "connection.server", NULL)); charon->bus->add_listener(charon->bus, &this->public.listener); diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h index 1bfdcf994..1a5175774 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2010-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -52,13 +52,9 @@ struct android_service_t { * new IKE SA. * * @param creds Android specific credential set - * @param type VPN type (see VpnType.java) - * @param gateway gateway address - * @param username user name (local identity) - * @param password password (if any) + * @param settings configuration settings (gets adopted) */ -android_service_t *android_service_create(android_creds_t *creds, char *type, - char *gateway, char *username, - char *password); +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings); #endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 524630024..2655f7361 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -44,7 +44,6 @@ #define ANDROID_RETRASNMIT_TRIES 3 #define ANDROID_RETRANSMIT_TIMEOUT 2.0 #define ANDROID_RETRANSMIT_BASE 1.4 -#define ANDROID_FRAGMENT_SIZE 1400 typedef struct private_charonservice_t private_charonservice_t; @@ -84,11 +83,6 @@ struct private_charonservice_t { network_manager_t *network_manager; /** - * Handle network events - */ - android_net_t *net_handler; - - /** * CharonVpnService reference */ jobject vpn_service; @@ -400,18 +394,27 @@ METHOD(charonservice_t, get_network_manager, network_manager_t*, /** * Initiate a new connection * - * @param gateway gateway address (gets owned) - * @param username username (gets owned) - * @param password password (gets owned) + * @param settings configuration settings (gets owned) */ -static void initiate(char *type, char *gateway, char *username, char *password) +static void initiate(settings_t *settings) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + lib->settings->set_str(lib->settings, + "charon.plugins.tnc-imc.preferred_language", + settings->get_str(settings, "global.language", "en")); + /* this is actually the size of the complete IKE/IP packet, so if the MTU + * for the TUN devices has to be reduced to pass traffic the IKE packets + * will be a bit smaller than necessary as there is no IPsec overhead like + * for the tunneled traffic (but compensating that seems like overkill) */ + lib->settings->set_int(lib->settings, + "charon.fragment_size", + settings->get_int(settings, "global.mtu", + ANDROID_DEFAULT_MTU)); + this->creds->clear(this->creds); DESTROY_IF(this->service); - this->service = android_service_create(this->creds, type, gateway, - username, password); + this->service = android_service_create(this->creds, settings); } /** @@ -423,14 +426,12 @@ static bool charonservice_register(plugin_t *plugin, plugin_feature_t *feature, private_charonservice_t *this = (private_charonservice_t*)charonservice; if (reg) { - this->net_handler = android_net_create(); lib->credmgr->add_set(lib->credmgr, &this->creds->set); charon->attributes->add_handler(charon->attributes, &this->attr->handler); } else { - this->net_handler->destroy(this->net_handler); lib->credmgr->remove_set(lib->credmgr, &this->creds->set); charon->attributes->remove_handler(charon->attributes, &this->attr->handler); @@ -466,8 +467,8 @@ static void set_options(char *logfile) "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); lib->settings->set_double(lib->settings, "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); - lib->settings->set_int(lib->settings, - "charon.fragment_size", ANDROID_FRAGMENT_SIZE); + lib->settings->set_bool(lib->settings, + "charon.initiator_only", TRUE); lib->settings->set_bool(lib->settings, "charon.close_ike_on_child_failure", TRUE); /* setting the source address breaks the VpnService.protect() function which @@ -483,19 +484,6 @@ static void set_options(char *logfile) * so lets disable IPv6 for now to avoid issues with dual-stack gateways */ lib->settings->set_bool(lib->settings, "charon.plugins.socket-default.use_ipv6", FALSE); - /* don't install virtual IPs via kernel-netlink */ - lib->settings->set_bool(lib->settings, - "charon.install_virtual_ip", FALSE); - /* kernel-netlink should not trigger roam events, we use Android's - * ConnectivityManager for that, much less noise */ - lib->settings->set_bool(lib->settings, - "charon.plugins.kernel-netlink.roam_events", FALSE); - /* ignore tun devices (it's mostly tun0 but it may already be taken, ignore - * some others too), also ignore lo as a default route points to it when - * no connectivity is available */ - lib->settings->set_str(lib->settings, - "charon.interfaces_ignore", "lo, tun0, tun1, tun2, tun3, " - "tun4"); #ifdef USE_BYOD lib->settings->set_str(lib->settings, @@ -519,6 +507,8 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, static plugin_feature_t features[] = { PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-net"), PLUGIN_CALLBACK(charonservice_register, NULL), PLUGIN_PROVIDE(CUSTOM, "android-backend"), PLUGIN_DEPENDS(CUSTOM, "libcharon"), @@ -705,14 +695,12 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) * Initiate SA */ JNI_METHOD(CharonVpnService, initiate, void, - jstring jtype, jstring jgateway, jstring jusername, jstring jpassword) + jstring jconfig) { - char *type, *gateway, *username, *password; - - type = androidjni_convert_jstring(env, jtype); - gateway = androidjni_convert_jstring(env, jgateway); - username = androidjni_convert_jstring(env, jusername); - password = androidjni_convert_jstring(env, jpassword); + settings_t *settings; + char *config; - initiate(type, gateway, username, password); + config = androidjni_convert_jstring(env, jconfig); + settings = settings_create_string(config); + initiate(settings); } diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 0c71d876d..8cb68e099 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -45,6 +45,11 @@ typedef enum android_imc_state_t android_imc_state_t; typedef struct charonservice_t charonservice_t; /** + * Default value for the MTU of TUN device and the size of IKE fragments + */ +#define ANDROID_DEFAULT_MTU 1400 + +/** * VPN status codes. As defined in CharonVpnService.java */ enum android_vpn_state_t { diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c index 653e2738b..2ce1bdfac 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -11,9 +11,14 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <errno.h> #include "android_net.h" +#include "../android_jni.h" #include "../charonservice.h" #include <hydra.h> #include <processing/jobs/callback_job.h> @@ -21,6 +26,7 @@ /** delay before firing roam events (ms) */ #define ROAM_DELAY 100 +#define ROAM_DELAY_RECHECK 1000 typedef struct private_android_net_t private_android_net_t; @@ -29,7 +35,7 @@ struct private_android_net_t { /** * Public kernel interface */ - android_net_t public; + kernel_net_t public; /** * Reference to NetworkManager object @@ -37,14 +43,29 @@ struct private_android_net_t { network_manager_t *network_manager; /** - * earliest time of the next roam event + * Earliest time of the next roam event */ timeval_t next_roam; /** - * mutex to check and update roam event time + * Mutex to check and update roam event time, and other private members */ mutex_t *mutex; + + /** + * List of virtual IPs + */ + linked_list_t *vips; + + /** + * Socket used to determine source address + */ + int socket_v4; + + /** + * Whether the device is currently connected + */ + bool connected; }; /** @@ -69,6 +90,7 @@ static void connectivity_cb(private_android_net_t *this, time_monotonic(&now); this->mutex->lock(this->mutex); + this->connected = !disconnected; if (!timercmp(&now, &this->next_roam, >)) { this->mutex->unlock(this->mutex); @@ -83,32 +105,210 @@ static void connectivity_cb(private_android_net_t *this, lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); } -METHOD(android_net_t, destroy, void, +METHOD(kernel_net_t, get_source_addr, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + union { + struct sockaddr sockaddr; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + socklen_t addrlen; + timeval_t now; + job_t *job; + + addrlen = *dest->get_sockaddr_len(dest); + addr.sockaddr.sa_family = AF_UNSPEC; + if (connect(this->socket_v4, &addr.sockaddr, addrlen) < 0) + { + DBG1(DBG_KNL, "failed to disconnect socket: %s", strerror(errno)); + return NULL; + } + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { /* this seems to help avoiding the VIP, unless there is no connectivity + * at all */ + charonservice->bypass_socket(charonservice, -1, 0); + } + if (connect(this->socket_v4, dest->get_sockaddr(dest), addrlen) < 0) + { + /* don't report an error if we are not connected (ENETUNREACH) */ + if (errno != ENETUNREACH) + { + DBG1(DBG_KNL, "failed to connect socket: %s", strerror(errno)); + } + else + { + time_monotonic(&now); + this->mutex->lock(this->mutex); + if (this->connected && timercmp(&now, &this->next_roam, >)) + { /* we were not able to find a source address but reportedly are + * connected, trigger a recheck in case an IP address appears + * delayed but the callback is not triggered again */ + timeval_add_ms(&now, ROAM_DELAY_RECHECK); + this->next_roam = now; + this->mutex->unlock(this->mutex); + job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, + NULL, NULL, NULL); + lib->scheduler->schedule_job_ms(lib->scheduler, job, + ROAM_DELAY_RECHECK); + } + else + { + this->mutex->unlock(this->mutex); + } + } + return NULL; + } + if (getsockname(this->socket_v4, &addr.sockaddr, &addrlen) < 0) + { + DBG1(DBG_KNL, "failed to determine src address: %s", strerror(errno)); + return NULL; + } + return host_create_from_sockaddr((sockaddr_t*)&addr); +} + +METHOD(kernel_net_t, get_source_addr_old, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + host_t *host; + + /* on older Android versions we might get the virtual IP back because + * the protect() implementation there and connect() don't properly work + * together, on newer releases (using fwmarks) that's not a problem */ + host = get_source_addr(this, dest, src); + if (host) + { + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)host->ip_equals, + NULL, host) == SUCCESS) + { + host->destroy(host); + host = NULL; + } + this->mutex->unlock(this->mutex); + } + return host; +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_android_net_t *this, host_t *dest, int prefix, host_t *src) +{ + return NULL; +} + +METHOD(kernel_net_t, get_interface, bool, + private_android_net_t *this, host_t *host, char **name) +{ + if (name) + { /* the actual name does not matter in our case */ + *name = strdup("strongswan"); + } + return TRUE; +} + +METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, + private_android_net_t *this, kernel_address_type_t which) +{ + /* return virtual IPs if requested, nothing otherwise */ + if (which & ADDR_TYPE_VIRTUAL) + { + this->mutex->lock(this->mutex); + return enumerator_create_cleaner( + this->vips->create_enumerator(this->vips), + (void*)this->mutex->unlock, this->mutex); + } + return enumerator_create_empty(); +} + +METHOD(kernel_net_t, add_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, char *iface) +{ + this->mutex->lock(this->mutex); + this->vips->insert_last(this->vips, virtual_ip->clone(virtual_ip)); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, del_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, bool wait) +{ + host_t *vip; + + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)virtual_ip->ip_equals, + (void**)&vip, virtual_ip) == SUCCESS) + { + this->vips->remove(this->vips, vip, NULL); + vip->destroy(vip); + } + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, add_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, del_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, destroy, void, private_android_net_t *this) { this->network_manager->remove_connectivity_cb(this->network_manager, (void*)connectivity_cb); this->mutex->destroy(this->mutex); + this->vips->destroy(this->vips); + close(this->socket_v4); free(this); } -/* - * Described in header. - */ -android_net_t *android_net_create() +kernel_net_t *kernel_android_net_create() { private_android_net_t *this; INIT(this, .public = { + .get_source_addr = _get_source_addr, + .get_nexthop = _get_nexthop, + .get_interface = _get_interface, + .create_address_enumerator = _create_address_enumerator, + .add_ip = _add_ip, + .del_ip = _del_ip, + .add_route = _add_route, + .del_route = _del_route, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .vips = linked_list_create(), .network_manager = charonservice->get_network_manager(charonservice), ); timerclear(&this->next_roam); - this->network_manager->add_connectivity_cb(this->network_manager, - (void*)connectivity_cb, this); + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { + this->public.get_source_addr = _get_source_addr_old; + } + + this->socket_v4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (this->socket_v4 < 0) + { + DBG1(DBG_KNL, "failed to create socket to lookup src addresses: %s", + strerror(errno)); + } + charonservice->bypass_socket(charonservice, this->socket_v4, AF_INET); + + this->mutex->lock(this->mutex); + this->network_manager->add_connectivity_cb( + this->network_manager, (void*)connectivity_cb, this); + this->connected = this->network_manager->is_connected(this->network_manager); + this->mutex->unlock(this->mutex); return &this->public; -}; +} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h index ade83f32a..761fa21bc 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -22,25 +22,14 @@ #define ANDROID_NET_H_ #include <library.h> - -typedef struct android_net_t android_net_t; - -/** - * Handle connectivity events from NetworkManager - */ -struct android_net_t { - - /** - * Destroy an android_net_t instance. - */ - void (*destroy)(android_net_t *this); -}; +#include <kernel/kernel_net.h> /** - * Create an android_net_t instance. + * Create an Android-specific kernel_net_t instance. * - * @return android_net_t instance + * @return kernel_net_t instance */ -android_net_t *android_net_create(); +kernel_net_t *kernel_android_net_create(); + #endif /** ANDROID_NET_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c index f8e560b56..372b25c55 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -90,8 +90,8 @@ METHOD(network_manager_t, add_connectivity_cb, void, this->connectivity_cb.cb = cb; this->connectivity_cb.data = data; } - androidjni_detach_thread(); } + androidjni_detach_thread(); } this->mutex->unlock(this->mutex); } @@ -132,6 +132,28 @@ METHOD(network_manager_t, remove_connectivity_cb, void, this->mutex->unlock(this->mutex); } +METHOD(network_manager_t, is_connected, bool, + private_network_manager_t *this) +{ + JNIEnv *env; + jmethodID method_id; + bool connected = FALSE; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "isConnected", "()Z"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + connected = (*env)->CallBooleanMethod(env, this->obj, method_id); + connected = !androidjni_exception_occurred(env) && connected; + } + androidjni_detach_thread(); + return connected; +} + METHOD(network_manager_t, destroy, void, private_network_manager_t *this) { @@ -174,6 +196,7 @@ network_manager_t *network_manager_create(jobject context) .public = { .add_connectivity_cb = _add_connectivity_cb, .remove_connectivity_cb = _remove_connectivity_cb, + .is_connected = _is_connected, .destroy = _destroy, }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h index abca239ea..9a6a715b7 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h +++ b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -66,6 +66,13 @@ struct network_manager_t { connectivity_cb_t cb); /** + * Check whether we currently have connectivity + * + * @return TRUE if currently connected + */ + bool (*is_connected)(network_manager_t *this); + + /** * Destroy a network_manager_t instance */ void (*destroy)(network_manager_t *this); diff --git a/src/frontends/android/project.properties b/src/frontends/android/project.properties index a5578ba09..bbe203c84 100644 --- a/src/frontends/android/project.properties +++ b/src/frontends/android/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-19 +target=android-21 diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index 91cd345ff..57d5606ff 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -139,6 +139,65 @@ android:id="@+id/select_certificate" layout="@layout/two_line_button" /> + <CheckBox + android:id="@+id/show_advanced" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_show_advanced_label" /> + + <LinearLayout + android:id="@+id/advanced_settings" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_mtu_label" /> + + <EditText + android:id="@+id/mtu" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="number|textNoSuggestions" + android:hint="@string/profile_use_default_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_port_label" /> + + <EditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="number|textNoSuggestions" + android:hint="@string/profile_use_default_hint" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_split_tunneling_label" /> + + <CheckBox + android:id="@+id/split_tunneling_v4" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_split_tunnelingv4_title" /> + + <CheckBox + android:id="@+id/split_tunneling_v6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/profile_split_tunnelingv6_title" /> + + </LinearLayout> </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 491fe8a8a..6cd5ba50a 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2013 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Automatisch wählen</string> <string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string> <string name="profile_ca_select_certificate">Wählen Sie ein bestimmtes CA-Zertifikat</string> + <string name="profile_show_advanced_label">Erweiterte Einstellungen anzeigen</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server Port:</string> + <string name="profile_use_default_hint">(Standardwert verwenden)</string> + <string name="profile_split_tunneling_label">Split-Tunneling:</string> + <string name="profile_split_tunnelingv4_title">Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist</string> + <string name="profile_split_tunnelingv6_title">Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Bitte geben Sie hier die Gateway-Adresse ein</string> <string name="alert_text_no_input_username">Bitte geben Sie hier Ihren Benutzernamen ein</string> <string name="alert_text_nocertfound_title">Kein CA-Zertifikat ausgewählt</string> <string name="alert_text_nocertfound">Bitte wählen Sie eines aus oder aktivieren Sie <i>Automatisch wählen</i></string> + <string name="alert_text_out_of_range">Bitte geben Sie eine Nummer von %1$d - %2$d ein</string> <string name="tnc_notice_title">EAP-TNC kann Ihre Privatsphäre beeinträchtigen</string> <string name="tnc_notice_subtitle">Gerätedaten werden an den Gateway-Betreiber gesendet</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) erlaubt Gateway-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.</p><p>Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.</p><b>Solche Daten werden nur übermittelt nachdem die Identität des Gateways geprüft wurde.</b></string> diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index d0cfa48f1..fb2aba003 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Wybierz automatycznie</string> <string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string> <string name="profile_ca_select_certificate">Wybierz określony certyfikat CA</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Wprowadź adres bramki</string> <string name="alert_text_no_input_username">Wprowadź swoją nazwę użytkownika</string> <string name="alert_text_nocertfound_title">Nie wybrano żadnego certyfikatu CA</string> <string name="alert_text_nocertfound">Wybierz lub uaktywnij jeden <i>Wybierz automatycznie</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index eb69183db..eabfc084b 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -60,11 +60,19 @@ <string name="profile_ca_auto_label">Выбрать автоматически</string> <string name="profile_ca_select_certificate_label">Выбрать сертификат CA</string> <string name="profile_ca_select_certificate">Выбрать CA сертификат</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Пожалуйста введите адрес шлюза</string> <string name="alert_text_no_input_username">Пожалуйста введите имя пользователя</string> <string name="alert_text_nocertfound_title">Не выбран сертификат CA</string> <string name="alert_text_nocertfound">Пожалуйста выберите один <i>Выбрать автоматически</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index e23b9b9b2..d7c238370 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -61,11 +61,19 @@ <string name="profile_ca_auto_label">Вибрати автоматично</string> <string name="profile_ca_select_certificate_label">Вибрати сертифікат CA</string> <string name="profile_ca_select_certificate">Вибрати спеціальний сертифікат CA</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Введіть адресу шлюза тут</string> <string name="alert_text_no_input_username">Введіть ім\'я користувача тут</string> <string name="alert_text_nocertfound_title">Не вибрано сертифікат CA</string> <string name="alert_text_nocertfound">Будь ласка виберіть один <i>Вибрати автоматично</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 933a80aff..5c8ebab57 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2012-2013 Tobias Brunner + Copyright (C) 2012-2015 Tobias Brunner Copyright (C) 2012 Giuliano Grassi Copyright (C) 2012 Ralf Sager Hochschule fuer Technik Rapperswil @@ -63,11 +63,19 @@ <string name="profile_ca_auto_label">Select automatically</string> <string name="profile_ca_select_certificate_label">Select CA certificate</string> <string name="profile_ca_select_certificate">Select a specific CA certificate</string> + <string name="profile_show_advanced_label">Show advanced settings</string> + <string name="profile_mtu_label">MTU:</string> + <string name="profile_port_label">Server port:</string> + <string name="profile_use_default_hint">(use default)</string> + <string name="profile_split_tunneling_label">Split tunneling:</string> + <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string> + <string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string> <!-- Warnings/Notifications in the details view --> <string name="alert_text_no_input_gateway">Please enter the gateway address here</string> <string name="alert_text_no_input_username">Please enter your username here</string> <string name="alert_text_nocertfound_title">No CA certificate selected</string> <string name="alert_text_nocertfound">Please select one or activate <i>Select automatically</i></string> + <string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string> <string name="tnc_notice_title">EAP-TNC may affect your privacy</string> <string name="tnc_notice_subtitle">Device data is sent to the gateway operator</string> <string name="tnc_notice_details"><p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b></string> diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index 8323826d2..5c64ad0e5 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -17,9 +17,15 @@ package org.strongswan.android.data; + public class VpnProfile implements Cloneable { + /* While storing this as EnumSet would be nicer this simplifies storing it in a database */ + public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1; + public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2; + private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; + private Integer mMTU, mPort, mSplitTunneling; private VpnType mVpnType; private long mId = -1; @@ -103,6 +109,36 @@ public class VpnProfile implements Cloneable this.mUserCertificate = alias; } + public Integer getMTU() + { + return mMTU; + } + + public void setMTU(Integer mtu) + { + this.mMTU = mtu; + } + + public Integer getPort() + { + return mPort; + } + + public void setPort(Integer port) + { + this.mPort = port; + } + + public Integer getSplitTunneling() + { + return mSplitTunneling; + } + + public void setSplitTunneling(Integer splitTunneling) + { + this.mSplitTunneling = splitTunneling; + } + @Override public String toString() { diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java index 6fd68d0c8..45e9b8650 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -40,6 +40,9 @@ public class VpnProfileDataSource public static final String KEY_PASSWORD = "password"; public static final String KEY_CERTIFICATE = "certificate"; public static final String KEY_USER_CERTIFICATE = "user_certificate"; + public static final String KEY_MTU = "mtu"; + public static final String KEY_PORT = "port"; + public static final String KEY_SPLIT_TUNNELING = "split_tunneling"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDatabase; @@ -48,7 +51,7 @@ public class VpnProfileDataSource private static final String DATABASE_NAME = "strongswan.db"; private static final String TABLE_VPNPROFILE = "vpnprofile"; - private static final int DATABASE_VERSION = 4; + private static final int DATABASE_VERSION = 7; public static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_VPNPROFILE + " (" + @@ -59,7 +62,10 @@ public class VpnProfileDataSource KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_CERTIFICATE + " TEXT," + - KEY_USER_CERTIFICATE + " TEXT" + + KEY_USER_CERTIFICATE + " TEXT," + + KEY_MTU + " INTEGER," + + KEY_PORT + " INTEGER," + + KEY_SPLIT_TUNNELING + " INTEGER" + ");"; private static final String[] ALL_COLUMNS = new String[] { KEY_ID, @@ -70,6 +76,9 @@ public class VpnProfileDataSource KEY_PASSWORD, KEY_CERTIFICATE, KEY_USER_CERTIFICATE, + KEY_MTU, + KEY_PORT, + KEY_SPLIT_TUNNELING, }; private static class DatabaseHelper extends SQLiteOpenHelper @@ -104,6 +113,21 @@ public class VpnProfileDataSource { /* remove NOT NULL constraint from username column */ updateColumns(db); } + if (oldVersion < 5) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + + " INTEGER;"); + } + if (oldVersion < 6) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + + " INTEGER;"); + } + if (oldVersion < 7) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + + " INTEGER;"); + } } private void updateColumns(SQLiteDatabase db) @@ -255,6 +279,9 @@ public class VpnProfileDataSource profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE))); + profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU))); + profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT))); + profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING))); return profile; } @@ -268,6 +295,14 @@ public class VpnProfileDataSource values.put(KEY_PASSWORD, profile.getPassword()); values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); + values.put(KEY_MTU, profile.getMTU()); + values.put(KEY_PORT, profile.getPort()); + values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); return values; } + + private Integer getInt(Cursor cursor, int columnIndex) + { + return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); + } } diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java index bffa8384c..bb7fd09f3 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnType.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnType.java @@ -24,7 +24,7 @@ public enum VpnType IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)), IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)), IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)), - IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE, VpnTypeFeature.BYOD)); + IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD)); /** * Features of a VPN type. diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index 7cdaee735..e5241d5a7 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -18,11 +18,16 @@ package org.strongswan.android.logic; import java.io.File; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; @@ -32,7 +37,9 @@ import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.logic.imc.ImcState; import org.strongswan.android.logic.imc.RemediationInstruction; import org.strongswan.android.ui.MainActivity; +import org.strongswan.android.utils.SettingsWriter; +import android.annotation.TargetApi; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; @@ -40,11 +47,13 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.net.VpnService; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.security.KeyChain; import android.security.KeyChainException; +import android.system.OsConstants; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -211,13 +220,19 @@ public class CharonVpnService extends VpnService implements Runnable startConnection(mCurrentProfile); mIsDisconnecting = false; - BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); + BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) { Log.i(TAG, "charon started"); - initiate(mCurrentProfile.getVpnType().getIdentifier(), - mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), - mCurrentProfile.getPassword()); + SettingsWriter writer = new SettingsWriter(); + writer.setValue("global.language", Locale.getDefault().getLanguage()); + writer.setValue("global.mtu", mCurrentProfile.getMTU()); + writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier()); + writer.setValue("connection.server", mCurrentProfile.getGateway()); + writer.setValue("connection.port", mCurrentProfile.getPort()); + writer.setValue("connection.username", mCurrentProfile.getUsername()); + writer.setValue("connection.password", mCurrentProfile.getPassword()); + initiate(writer.serialize()); } else { @@ -497,7 +512,6 @@ public class CharonVpnService extends VpnService implements Runnable private PrivateKey getUserKey() throws KeyChainException, InterruptedException { return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias); - } /** @@ -518,7 +532,7 @@ public class CharonVpnService extends VpnService implements Runnable /** * Initiate VPN, provided by libandroidbridge.so */ - public native void initiate(String type, String gateway, String username, String password); + public native void initiate(String config); /** * Adapter for VpnService.Builder which is used to access it safely via JNI. @@ -527,15 +541,17 @@ public class CharonVpnService extends VpnService implements Runnable public class BuilderAdapter { private final String mName; + private final Integer mSplitTunneling; private VpnService.Builder mBuilder; private BuilderCache mCache; private BuilderCache mEstablishedCache; - public BuilderAdapter(String name) + public BuilderAdapter(String name, Integer splitTunneling) { mName = name; + mSplitTunneling = splitTunneling; mBuilder = createBuilder(name); - mCache = new BuilderCache(); + mCache = new BuilderCache(mSplitTunneling); } private VpnService.Builder createBuilder(String name) @@ -557,7 +573,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.addAddress(address, prefixLength); mCache.addAddress(address, prefixLength); } catch (IllegalArgumentException ex) @@ -572,6 +587,7 @@ public class CharonVpnService extends VpnService implements Runnable try { mBuilder.addDnsServer(address); + mCache.recordAddressFamily(address); } catch (IllegalArgumentException ex) { @@ -584,7 +600,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.addRoute(address, prefixLength); mCache.addRoute(address, prefixLength); } catch (IllegalArgumentException ex) @@ -611,7 +626,6 @@ public class CharonVpnService extends VpnService implements Runnable { try { - mBuilder.setMtu(mtu); mCache.setMtu(mtu); } catch (IllegalArgumentException ex) @@ -626,6 +640,7 @@ public class CharonVpnService extends VpnService implements Runnable ParcelFileDescriptor fd; try { + mCache.applyData(mBuilder); fd = mBuilder.establish(); } catch (Exception ex) @@ -641,7 +656,7 @@ public class CharonVpnService extends VpnService implements Runnable * builder anymore, but we might need another when reestablishing */ mBuilder = createBuilder(mName); mEstablishedCache = mCache; - mCache = new BuilderCache(); + mCache = new BuilderCache(mSplitTunneling); return fd.detachFd(); } @@ -679,17 +694,40 @@ public class CharonVpnService extends VpnService implements Runnable public class BuilderCache { private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>(); - private final List<PrefixedAddress> mRoutes = new ArrayList<PrefixedAddress>(); + private final List<PrefixedAddress> mRoutesIPv4 = new ArrayList<PrefixedAddress>(); + private final List<PrefixedAddress> mRoutesIPv6 = new ArrayList<PrefixedAddress>(); + private final int mSplitTunneling; private int mMtu; + private boolean mIPv4Seen, mIPv6Seen; + + public BuilderCache(Integer splitTunneling) + { + mSplitTunneling = splitTunneling != null ? splitTunneling : 0; + } public void addAddress(String address, int prefixLength) { mAddresses.add(new PrefixedAddress(address, prefixLength)); + recordAddressFamily(address); } public void addRoute(String address, int prefixLength) { - mRoutes.add(new PrefixedAddress(address, prefixLength)); + try + { + if (isIPv6(address)) + { + mRoutesIPv6.add(new PrefixedAddress(address, prefixLength)); + } + else + { + mRoutesIPv4.add(new PrefixedAddress(address, prefixLength)); + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } } public void setMtu(int mtu) @@ -697,19 +735,89 @@ public class CharonVpnService extends VpnService implements Runnable mMtu = mtu; } + public void recordAddressFamily(String address) + { + try + { + if (isIPv6(address)) + { + mIPv6Seen = true; + } + else + { + mIPv4Seen = true; + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void applyData(VpnService.Builder builder) { for (PrefixedAddress address : mAddresses) { builder.addAddress(address.mAddress, address.mPrefix); } - for (PrefixedAddress route : mRoutes) + /* add routes depending on whether split tunneling is allowed or not, + * that is, whether we have to handle and block non-VPN traffic */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0) + { + if (mIPv4Seen) + { /* split tunneling is used depending on the routes */ + for (PrefixedAddress route : mRoutesIPv4) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { /* allow traffic that would otherwise be blocked to bypass the VPN */ + builder.allowFamily(OsConstants.AF_INET); + } + } + else if (mIPv4Seen) + { /* only needed if we've seen any addresses. otherwise, traffic + * is blocked by default (we also install no routes in that case) */ + builder.addRoute("0.0.0.0", 0); + } + /* same thing for IPv6 */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0) + { + if (mIPv6Seen) + { + for (PrefixedAddress route : mRoutesIPv6) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + builder.allowFamily(OsConstants.AF_INET6); + } + } + else if (mIPv6Seen) { - builder.addRoute(route.mAddress, route.mPrefix); + builder.addRoute("::", 0); } builder.setMtu(mMtu); } + private boolean isIPv6(String address) throws UnknownHostException + { + InetAddress addr = InetAddress.getByName(address); + if (addr instanceof Inet4Address) + { + return false; + } + else if (addr instanceof Inet6Address) + { + return true; + } + return false; + } + private class PrefixedAddress { public String mAddress; @@ -725,22 +833,25 @@ public class CharonVpnService extends VpnService implements Runnable /* * The libraries are extracted to /data/data/org.strongswan.android/... - * during installation. + * during installation. On newer releases most are loaded in JNI_OnLoad. */ static { - System.loadLibrary("strongswan"); - - if (MainActivity.USE_BYOD) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { - System.loadLibrary("tncif"); - System.loadLibrary("tnccs"); - System.loadLibrary("imcv"); - } + System.loadLibrary("strongswan"); - System.loadLibrary("hydra"); - System.loadLibrary("charon"); - System.loadLibrary("ipsec"); + if (MainActivity.USE_BYOD) + { + System.loadLibrary("tncif"); + System.loadLibrary("tnccs"); + System.loadLibrary("imcv"); + } + + System.loadLibrary("hydra"); + System.loadLibrary("charon"); + System.loadLibrary("ipsec"); + } System.loadLibrary("androidbridge"); } } diff --git a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java index 8ea07f4c0..ebe1d0080 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java +++ b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -42,12 +42,21 @@ public class NetworkManager extends BroadcastReceiver mContext.unregisterReceiver(this); } + public boolean isConnected() + { + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = null; + if (cm != null) + { + info = cm.getActiveNetworkInfo(); + } + return info != null && info.isConnected(); + } + @Override public void onReceive(Context context, Intent intent) { - ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - networkChanged(info == null || !info.isConnected()); + networkChanged(!isConnected()); } /** diff --git a/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java index 39f86b460..5b1799744 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java @@ -24,6 +24,7 @@ import org.strongswan.android.logic.imc.ImcState; import org.strongswan.android.logic.imc.RemediationInstruction; import android.app.Fragment; +import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.Service; import android.content.ComponentName; @@ -82,7 +83,7 @@ public class ImcStateFragment extends Fragment implements VpnStateListener public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.imc_state_fragment, null); + View view = inflater.inflate(R.layout.imc_state_fragment, container, false); mButton = (LinearLayout)view.findViewById(R.id.imc_state_button); mButton.setOnClickListener(new OnClickListener() { @@ -174,7 +175,12 @@ public class ImcStateFragment extends Fragment implements VpnStateListener public void updateView() { - FragmentTransaction ft = getFragmentManager().beginTransaction(); + FragmentManager fm = getFragmentManager(); + if (fm == null) + { + return; + } + FragmentTransaction ft = fm.beginTransaction(); switch (mService.getImcState()) { diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 41cd6e98c..a8b3daa06 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -60,6 +60,8 @@ import android.widget.TextView; public class VpnProfileDetailActivity extends Activity { private static final int SELECT_TRUSTED_CERTIFICATE = 0; + private static final int MTU_MIN = 1280; + private static final int MTU_MAX = 1500; private VpnProfileDataSource mDataSource; private Long mId; @@ -79,6 +81,12 @@ public class VpnProfileDetailActivity extends Activity private CheckBox mCheckAuto; private RelativeLayout mSelectCert; private RelativeLayout mTncNotice; + private CheckBox mShowAdvanced; + private ViewGroup mAdvancedSettings; + private EditText mMTU; + private EditText mPort; + private CheckBox mBlockIPv4; + private CheckBox mBlockIPv6; @Override public void onCreate(Bundle savedInstanceState) @@ -108,6 +116,14 @@ public class VpnProfileDetailActivity extends Activity mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); + mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); + mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); + + mMTU = (EditText)findViewById(R.id.mtu); + mPort = (EditText)findViewById(R.id.port); + mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); + mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) @@ -154,6 +170,14 @@ public class VpnProfileDetailActivity extends Activity } }); + mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateAdvancedSettings(); + } + }); + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); if (mId == null) { @@ -165,6 +189,7 @@ public class VpnProfileDetailActivity extends Activity updateCredentialView(); updateCertificateSelector(); + updateAdvancedSettings(); } @Override @@ -315,6 +340,22 @@ public class VpnProfileDetailActivity extends Activity } /** + * Update the advanced settings UI depending on whether any advanced + * settings have already been made. + */ + private void updateAdvancedSettings() + { + boolean show = mShowAdvanced.isChecked(); + if (!show && mProfile != null) + { + Integer st = mProfile.getSplitTunneling(); + show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0); + } + mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); + mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); + } + + /** * Save or update the profile depending on whether we actually have a * profile object or not (this was created in updateProfileData) */ @@ -368,6 +409,18 @@ public class VpnProfileDetailActivity extends Activity showCertificateAlert(); valid = false; } + Integer mtu = getInteger(mMTU); + if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX)) + { + mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX)); + valid = false; + } + Integer port = getInteger(mPort); + if (port != null && (port < 1 || port > 65535)) + { + mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535)); + valid = false; + } return valid; } @@ -395,6 +448,12 @@ public class VpnProfileDetailActivity extends Activity } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); + mProfile.setMTU(getInteger(mMTU)); + mProfile.setPort(getInteger(mPort)); + int st = 0; + st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; + st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; + mProfile.setSplitTunneling(st == 0 ? null : st); } /** @@ -417,6 +476,10 @@ public class VpnProfileDetailActivity extends Activity mVpnType = mProfile.getVpnType(); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); + mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null); + mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null); + mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); + mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false); useralias = mProfile.getUserCertificateAlias(); alias = mProfile.getCertificateAlias(); getActionBar().setTitle(mProfile.getName()); @@ -458,6 +521,17 @@ public class VpnProfileDetailActivity extends Activity } } + /** + * Get the integer value in the given text box or null if empty + * + * @param view text box (numeric entry assumed) + */ + private Integer getInteger(EditText view) + { + String value = view.getText().toString().trim(); + return value.isEmpty() ? null : Integer.valueOf(value); + } + private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback { @Override diff --git a/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java b/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java new file mode 100644 index 000000000..01c0ab8a8 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +package org.strongswan.android.utils; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.regex.Pattern; + + +/** + * Simple generator for data/files that may be parsed by libstrongswan's + * settings_t class. + */ +public class SettingsWriter +{ + /** + * Top-level section + */ + private final SettingsSection mTop = new SettingsSection(); + + /** + * Set a string value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, String value) + { + Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+"); + if (key == null || !pattern.matcher(key).matches()) + { + return this; + } + String[] keys = key.split("\\."); + SettingsSection section = mTop; + section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1)); + section.Settings.put(keys[keys.length-1], value); + return this; + } + + /** + * Set an integer value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Integer value) + { + return setValue(key, value == null ? null : value.toString()); + } + + /** + * Set a boolean value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Boolean value) + { + return setValue(key, value == null ? null : value ? "1" : "0"); + } + + /** + * Serializes the settings to a string in the format understood by + * libstrongswan's settings_t parser. + * @return serialized settings + */ + public String serialize() + { + StringBuilder builder = new StringBuilder(); + serializeSection(mTop, builder); + return builder.toString(); + } + + /** + * Serialize the settings in a section and recursively serialize sub-sections + * @param section + * @param builder + */ + private void serializeSection(SettingsSection section, StringBuilder builder) + { + for (Entry<String, String> setting : section.Settings.entrySet()) + { + builder.append(setting.getKey()).append('='); + if (setting.getValue() != null) + { + builder.append("\"").append(escapeValue(setting.getValue())).append("\""); + } + builder.append('\n'); + } + + for (Entry<String, SettingsSection> subsection : section.Sections.entrySet()) + { + builder.append(subsection.getKey()).append(" {\n"); + serializeSection(subsection.getValue(), builder); + builder.append("}\n"); + } + } + + /** + * Escape value so it may be wrapped in " + * @param value + * @return + */ + private String escapeValue(String value) + { + return value.replace("\"", "\\\""); + } + + /** + * Find or create the nested sections with the given names + * @param sections list of section names + * @return final section + */ + private SettingsSection findOrCreateSection(String[] sections) + { + SettingsSection section = mTop; + for (String name : sections) + { + SettingsSection subsection = section.Sections.get(name); + if (subsection == null) + { + subsection = new SettingsSection(); + section.Sections.put(name, subsection); + } + section = subsection; + } + return section; + } + + /** + * A section containing sub-sections and settings. + */ + private class SettingsSection + { + /** + * Assigned key/value pairs + */ + LinkedHashMap<String,String> Settings = new LinkedHashMap<String, String>(); + + /** + * Assigned sub-sections + */ + LinkedHashMap<String,SettingsSection> Sections = new LinkedHashMap<String, SettingsWriter.SettingsSection>(); + } +} diff --git a/src/include/Makefile.am b/src/include/Makefile.am index 5de713143..0284c094a 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -1,3 +1,2 @@ EXTRA_DIST = linux/if_alg.h linux/ipsec.h linux/netlink.h linux/rtnetlink.h \ - linux/pfkeyv2.h linux/udp.h linux/xfrm.h linux/types.h \ - sys/queue.h + linux/pfkeyv2.h linux/udp.h linux/xfrm.h sys/queue.h diff --git a/src/include/linux/types.h b/src/include/linux/types.h deleted file mode 100644 index 22cfdc05e..000000000 --- a/src/include/linux/types.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef _LINUX_TYPES_H -#define _LINUX_TYPES_H - - -#include <linux/posix_types.h> -#include <asm/types.h> - -#ifndef __KERNEL_STRICT_NAMES - -typedef __u32 __kernel_dev_t; - -typedef __kernel_fd_set fd_set; -typedef __kernel_dev_t dev_t; -typedef __kernel_ino_t ino_t; -typedef __kernel_mode_t mode_t; -typedef __kernel_nlink_t nlink_t; -typedef __kernel_off_t off_t; -typedef __kernel_pid_t pid_t; -typedef __kernel_daddr_t daddr_t; -typedef __kernel_key_t key_t; -typedef __kernel_suseconds_t suseconds_t; -typedef __kernel_timer_t timer_t; -typedef __kernel_clockid_t clockid_t; -typedef __kernel_mqd_t mqd_t; - -typedef __kernel_uid_t uid_t; -typedef __kernel_gid_t gid_t; - -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) -typedef __kernel_loff_t loff_t; -#endif - -/* - * The following typedefs are also protected by individual ifdefs for - * historical reasons: - */ -#ifndef _SIZE_T -#define _SIZE_T -typedef __kernel_size_t size_t; -#endif - -#ifndef _SSIZE_T -#define _SSIZE_T -typedef __kernel_ssize_t ssize_t; -#endif - -#ifndef _PTRDIFF_T -#define _PTRDIFF_T -typedef __kernel_ptrdiff_t ptrdiff_t; -#endif - -#ifndef _TIME_T -#define _TIME_T -typedef __kernel_time_t time_t; -#endif - -#ifndef _CLOCK_T -#define _CLOCK_T -typedef __kernel_clock_t clock_t; -#endif - -#ifndef _CADDR_T -#define _CADDR_T -typedef __kernel_caddr_t caddr_t; -#endif - -/* bsd */ -typedef unsigned char u_char; -typedef unsigned short u_short; -typedef unsigned int u_int; -typedef unsigned long u_long; - -/* sysv */ -typedef unsigned char unchar; -typedef unsigned short ushort; -typedef unsigned int uint; -typedef unsigned long ulong; - -#ifndef __BIT_TYPES_DEFINED__ -#define __BIT_TYPES_DEFINED__ - -typedef __u8 u_int8_t; -typedef __s8 int8_t; -typedef __u16 u_int16_t; -typedef __s16 int16_t; -typedef __u32 u_int32_t; -typedef __s32 int32_t; - -#endif /* !(__BIT_TYPES_DEFINED__) */ - -typedef __u8 uint8_t; -typedef __u16 uint16_t; -typedef __u32 uint32_t; - -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) -typedef __u64 uint64_t; -typedef __u64 u_int64_t; -typedef __s64 int64_t; -#endif - -/* this is a special 64bit data type that is 8-byte aligned */ -#define aligned_u64 unsigned long long __attribute__((aligned(8))) -#define aligned_be64 __be64 __attribute__((aligned(8))) -#define aligned_le64 __le64 __attribute__((aligned(8))) - -/** - * The type used for indexing onto a disc or disc partition. - * - * Linux always considers sectors to be 512 bytes long independently - * of the devices real block size. - */ -#ifdef CONFIG_LBD -typedef u64 sector_t; -#else -typedef unsigned long sector_t; -#endif - -/* - * The type of the inode's block count. - */ -#ifdef CONFIG_LSF -typedef u64 blkcnt_t; -#else -typedef unsigned long blkcnt_t; -#endif - -/* - * The type of an index into the pagecache. Use a #define so asm/types.h - * can override it. - */ -#ifndef pgoff_t -#define pgoff_t unsigned long -#endif - -#endif /* __KERNEL_STRICT_NAMES */ - -/* - * Below are truly Linux-specific types that should never collide with - * any application/library that wants linux/types.h. - */ - -#ifdef __CHECKER__ -#define __bitwise__ __attribute__((bitwise)) -#else -#define __bitwise__ -#endif -#ifdef __CHECK_ENDIAN__ -#define __bitwise __bitwise__ -#else -#define __bitwise -#endif - -typedef __u16 __bitwise __le16; -typedef __u16 __bitwise __be16; -typedef __u32 __bitwise __le32; -typedef __u32 __bitwise __be32; -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) -typedef __u64 __bitwise __le64; -typedef __u64 __bitwise __be64; -#endif -typedef __u16 __bitwise __sum16; -typedef __u32 __bitwise __wsum; - - -struct ustat { - __kernel_daddr_t f_tfree; - __kernel_ino_t f_tinode; - char f_fname[6]; - char f_fpack[6]; -}; - -#endif /* _LINUX_TYPES_H */ diff --git a/src/libstrongswan/settings/settings.c b/src/libstrongswan/settings/settings.c index acf9160d2..305ebe620 100644 --- a/src/libstrongswan/settings/settings.c +++ b/src/libstrongswan/settings/settings.c @@ -37,9 +37,10 @@ typedef struct private_settings_t private_settings_t; /** - * Parse function provided by the generated parser. + * Parse functions provided by the generated parser. */ bool settings_parser_parse_file(section_t *root, char *name); +bool settings_parser_parse_string(section_t *root, char *settings); /** * Private data of settings @@ -843,16 +844,17 @@ METHOD(settings_t, add_fallback, void, } /** - * Load settings from files matching the given file pattern. + * Load settings from files matching the given file pattern or from a string. * All sections and values are added relative to "parent". * All files (even included ones) have to be loaded successfully. * If merge is FALSE the contents of parent are replaced with the parsed * contents, otherwise they are merged together. */ -static bool load_files_internal(private_settings_t *this, section_t *parent, - char *pattern, bool merge) +static bool load_internal(private_settings_t *this, section_t *parent, + char *pattern, bool merge, bool string) { section_t *section; + bool loaded; if (pattern == NULL || !pattern[0]) { /* TODO: Clear parent if merge is FALSE? */ @@ -860,7 +862,9 @@ static bool load_files_internal(private_settings_t *this, section_t *parent, } section = settings_section_create(NULL); - if (!settings_parser_parse_file(section, pattern)) + loaded = string ? settings_parser_parse_string(section, pattern) : + settings_parser_parse_file(section, pattern); + if (!loaded) { settings_section_destroy(section, NULL); return FALSE; @@ -877,7 +881,7 @@ static bool load_files_internal(private_settings_t *this, section_t *parent, METHOD(settings_t, load_files, bool, private_settings_t *this, char *pattern, bool merge) { - return load_files_internal(this, this->top, pattern, merge); + return load_internal(this, this->top, pattern, merge, FALSE); } METHOD(settings_t, load_files_section, bool, @@ -894,7 +898,30 @@ METHOD(settings_t, load_files_section, bool, { return FALSE; } - return load_files_internal(this, section, pattern, merge); + return load_internal(this, section, pattern, merge, FALSE); +} + +METHOD(settings_t, load_string, bool, + private_settings_t *this, char *settings, bool merge) +{ + return load_internal(this, this->top, settings, merge, TRUE); +} + +METHOD(settings_t, load_string_section, bool, + private_settings_t *this, char *settings, bool merge, char *key, ...) +{ + section_t *section; + va_list args; + + va_start(args, key); + section = ensure_section(this, this->top, key, args); + va_end(args); + + if (!section) + { + return FALSE; + } + return load_internal(this, section, settings, merge, TRUE); } METHOD(settings_t, destroy, void, @@ -906,10 +933,7 @@ METHOD(settings_t, destroy, void, free(this); } -/* - * see header file - */ -settings_t *settings_create(char *file) +static private_settings_t *settings_create_base() { private_settings_t *this; @@ -931,14 +955,37 @@ settings_t *settings_create(char *file) .add_fallback = _add_fallback, .load_files = _load_files, .load_files_section = _load_files_section, + .load_string = _load_string, + .load_string_section = _load_string_section, .destroy = _destroy, }, .top = settings_section_create(NULL), .contents = array_create(0, 0), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); + return this; +} + +/* + * see header file + */ +settings_t *settings_create(char *file) +{ + private_settings_t *this = settings_create_base(); load_files(this, file, FALSE); return &this->public; } + +/* + * see header file + */ +settings_t *settings_create_string(char *settings) +{ + private_settings_t *this = settings_create_base(); + + load_string(this, settings, FALSE); + + return &this->public; +} diff --git a/src/libstrongswan/settings/settings.h b/src/libstrongswan/settings/settings.h index 3b87c8feb..4ef80d0f6 100644 --- a/src/libstrongswan/settings/settings.h +++ b/src/libstrongswan/settings/settings.h @@ -335,6 +335,50 @@ struct settings_t { char *section, ...); /** + * Load settings from the given string. + * + * If merge is TRUE, existing sections are extended, existing values + * replaced, by those found in the string. If it is FALSE, existing + * sections are purged before reading the new config. + * + * @note If the string contains _include_ statements they should be + * absolute paths. + * + * @note If any failures occur, no settings are added at all. So, it's all + * or nothing. + * + * @param settings string to parse + * @param merge TRUE to merge config with existing values + * @return TRUE, if settings were loaded successfully + */ + bool (*load_string)(settings_t *this, char *settings, bool merge); + + /** + * Load settings from the given string. + * + * If merge is TRUE, existing sections are extended, existing values + * replaced, by those found in the string. If it is FALSE, existing + * sections are purged before reading the new config. + * + * All settings are loaded relative to the given section. The section is + * created, if it does not yet exist. + * + * @note If the string contains _include_ statements they should be + * absolute paths. + * + * @note If any failures occur, no settings are added at all. So, it's all + * or nothing. + * + * @param settings string to parse + * @param merge TRUE to merge config with existing values + * @param section section name of parent section, printf style + * @param ... argument list for section + * @return TRUE, if settings were loaded successfully + */ + bool (*load_string_section)(settings_t *this, char *settings, bool merge, + char *section, ...); + + /** * Destroy a settings instance. */ void (*destroy)(settings_t *this); @@ -350,4 +394,14 @@ struct settings_t { */ settings_t *settings_create(char *file); +/** + * Load settings from a string. + * + * @note If parsing the file fails the object is still created. + * + * @param settings string to read settings from + * @return settings object, or NULL + */ +settings_t *settings_create_string(char *settings); + #endif /** SETTINGS_H_ @}*/ diff --git a/src/libstrongswan/settings/settings_lexer.l b/src/libstrongswan/settings/settings_lexer.l index 176387f1f..ce9d4eedc 100644 --- a/src/libstrongswan/settings/settings_lexer.l +++ b/src/libstrongswan/settings/settings_lexer.l @@ -119,16 +119,11 @@ static void include_files(parser_helper_t *ctx); <str>{ "\"" | <<EOF>> | - \n | \\ { if (!streq(yytext, "\"")) { - if (streq(yytext, "\n")) - { /* put the newline back to fix the line numbers */ - unput('\n'); - yy_set_bol(0); - } PARSER_DBG1(yyextra, "unterminated string detected"); + return STRING_ERROR; } if (yy_top_state(yyscanner) == inc) { /* string include */ @@ -146,11 +141,9 @@ static void include_files(parser_helper_t *ctx); \\n yyextra->string_add(yyextra, "\n"); \\r yyextra->string_add(yyextra, "\r"); \\t yyextra->string_add(yyextra, "\t"); - \\b yyextra->string_add(yyextra, "\b"); - \\f yyextra->string_add(yyextra, "\f"); \\\r?\n /* merge lines that end with EOL characters */ \\. yyextra->string_add(yyextra, yytext+1); - [^\\\n"]+ { + [^\\"]+ { yyextra->string_add(yyextra, yytext); } } @@ -198,3 +191,11 @@ static void include_files(parser_helper_t *ctx) settings_parser_open_next_file(ctx); } + +/** + * Load the given string to be parsed next + */ +void settings_parser_load_string(parser_helper_t *ctx, const char *content) +{ + settings_parser__scan_string(content, ctx->scanner); +} diff --git a/src/libstrongswan/settings/settings_parser.y b/src/libstrongswan/settings/settings_parser.y index d95a24b2a..96ab36faf 100644 --- a/src/libstrongswan/settings/settings_parser.y +++ b/src/libstrongswan/settings/settings_parser.y @@ -39,6 +39,7 @@ int settings_parser_get_leng(void *scanner); int settings_parser_get_lineno(void *scanner); /* Custom functions in lexer */ bool settings_parser_open_next_file(parser_helper_t *ctx); +bool settings_parser_load_string(parser_helper_t *ctx, const char *content); /** * Forward declarations @@ -79,7 +80,7 @@ static int yylex(YYSTYPE *lvalp, parser_helper_t *ctx) struct kv_t *kv; } %token <s> NAME STRING -%token NEWLINE +%token NEWLINE STRING_ERROR /* ...and other symbols */ %type <s> value valuepart @@ -286,3 +287,39 @@ bool settings_parser_parse_file(section_t *root, char *name) helper->destroy(helper); return success; } + +/** + * Parse the given string and add all sections and key/value pairs to the + * given section. + */ +bool settings_parser_parse_string(section_t *root, char *settings) +{ + parser_helper_t *helper; + array_t *sections = NULL; + bool success = FALSE; + + array_insert_create(§ions, ARRAY_TAIL, root); + helper = parser_helper_create(sections); + helper->get_lineno = settings_parser_get_lineno; + if (settings_parser_lex_init_extra(helper, &helper->scanner) != 0) + { + helper->destroy(helper); + array_destroy(sections); + return FALSE; + } + settings_parser_load_string(helper, settings); + if (getenv("DEBUG_SETTINGS_PARSER")) + { + yydebug = 1; + settings_parser_set_debug(1, helper->scanner); + } + success = yyparse(helper) == 0; + if (!success) + { + DBG1(DBG_CFG, "failed to parse settings '%s'", settings); + } + array_destroy(sections); + settings_parser_lex_destroy(helper->scanner); + helper->destroy(helper); + return success; +} diff --git a/src/libstrongswan/tests/suites/test_settings.c b/src/libstrongswan/tests/suites/test_settings.c index 9601a34a9..ec79a6288 100644 --- a/src/libstrongswan/tests/suites/test_settings.c +++ b/src/libstrongswan/tests/suites/test_settings.c @@ -58,6 +58,10 @@ START_SETUP(setup_base_config) " }\n" " key2 = with space\n" " key3 = \"string with\\nnewline\"\n" + " key4 = \"multi line\n" + "string\"\n" + " key5 = \"escaped \\\n" + "newline\"\n" "}\n" "out = side\n" "other {\n" @@ -88,6 +92,8 @@ START_TEST(test_get_str) verify_string("", "main.empty"); verify_string("with space", "main.key2"); verify_string("string with\nnewline", "main.key3"); + verify_string("multi line\nstring", "main.key4"); + verify_string("escaped newline", "main.key5"); verify_string("value", "main.sub1.key"); verify_string("value2", "main.sub1.key2"); verify_string("bar", "main.sub1.subsub.foo"); @@ -97,7 +103,7 @@ START_TEST(test_get_str) verify_string("other val", "other.key1"); verify_null("main.none"); - verify_null("main.key4"); + verify_null("main.key6"); verify_null("other.sub"); } END_TEST @@ -131,7 +137,7 @@ START_TEST(test_get_str_printf) * probably document it at least */ verify_null("main.%s%u.key%d", "sub", 1, 2); - verify_null("%s.%s%d", "main", "key", 4); + verify_null("%s.%s%d", "main", "key", 6); } END_TEST @@ -529,9 +535,7 @@ END_TEST # define include2 "/tmp/strongswan-settings-test-include2" #endif -START_SETUP(setup_include_config) -{ - chunk_t inc1 = chunk_from_str( +static char *include_content1 = "main {\n" " key1 = n1\n" " key2 = n2\n" @@ -544,14 +548,17 @@ START_SETUP(setup_include_config) " sub3 = val3\n" " }\n" " include " include2 "\n" - "}"); - chunk_t inc2 = chunk_from_str( + "}"; +static char *include_content2 = "key2 = v2\n" "sub1 {\n" " key = val\n" - "}"); - ck_assert(chunk_write(inc1, include1, 0022, TRUE)); - ck_assert(chunk_write(inc2, include2, 0022, TRUE)); + "}"; + +START_SETUP(setup_include_config) +{ + ck_assert(chunk_write(chunk_from_str(include_content1), include1, 0022, TRUE)); + ck_assert(chunk_write(chunk_from_str(include_content2), include2, 0022, TRUE)); } END_SETUP @@ -784,6 +791,104 @@ START_TEST(test_order_section) } END_TEST + +START_TEST(test_load_string) +{ + char *content = + "main {\n" + " key1 = val1\n" + " key2 = val2\n" + " key3 = val3\n" + " none = x\n" + " sub1 {\n" + " include = value\n" + " key2 = v2\n" + " sub1 {\n" + " key = val\n" + " }\n" + " }\n" + "}"; + char *val1, *val2, *val3; + + settings = settings_create_string(content); + + val1 = settings->get_str(settings, "main.key1", NULL); + val2 = settings->get_str(settings, "main.sub1.key2", NULL); + /* loading the same content twice should not change anything, with... */ + ck_assert(settings->load_string(settings, content, TRUE)); + ck_assert(val1 == settings->get_str(settings, "main.key1", NULL)); + ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL)); + /* ...or without merging */ + ck_assert(settings->load_string(settings, content, FALSE)); + ck_assert(val1 == settings->get_str(settings, "main.key1", NULL)); + ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL)); + + val1 = settings->get_str(settings, "main.key2", NULL); + val2 = settings->get_str(settings, "main.key3", NULL); + val3 = settings->get_str(settings, "main.none", NULL); + /* only pointers for modified settings should change, but still be valid */ + ck_assert(settings->load_string(settings, include_content1, FALSE)); + ck_assert(val1 != settings->get_str(settings, "main.key2", NULL)); + ck_assert_str_eq(val1, "val2"); + ck_assert(val2 == settings->get_str(settings, "main.key3", NULL)); + ck_assert(val3 != settings->get_str(settings, "main.none", NULL)); + ck_assert_str_eq(val3, "x"); + + settings->destroy(settings); + settings = settings_create_string(content); + ck_assert(settings); + + ck_assert(settings->load_string(settings, include_content1, TRUE)); + verify_include(); + + ck_assert(settings->load_string(settings, include_content2, FALSE)); + verify_null("main.key1"); + verify_string("v2", "key2"); + verify_string("val", "sub1.key"); + verify_null("main.sub1.key3"); +} +END_TEST + + +START_TEST(test_load_string_section) +{ + char *content = + "main {\n" + " key1 = val1\n" + " key2 = val2\n" + " none = x\n" + " sub1 {\n" + " include = value\n" + " key2 = value2\n" + " }\n" + "}"; + + settings = settings_create_string(content); + + ck_assert(settings->load_string_section(settings, include_content1, TRUE, "")); + ck_assert(settings->load_string_section(settings, include_content2, TRUE, "main.sub1")); + verify_include(); + + /* invalid strings are a failure */ + ck_assert(!settings->load_string_section(settings, "conf {", TRUE, "")); + /* NULL or empty strings are OK though */ + ck_assert(settings->load_string_section(settings, "", TRUE, "")); + ck_assert(settings->load_string_section(settings, NULL, TRUE, "")); + verify_include(); + + ck_assert(settings->load_string_section(settings, include_content2, FALSE, "main")); + verify_null("main.key1"); + verify_string("v2", "main.key2"); + verify_string("val", "main.sub1.key"); + verify_null("main.sub1.key3"); + verify_null("main.sub2.sub3"); + + ck_assert(settings->load_string_section(settings, include_content2, TRUE, "main.sub2")); + verify_string("v2", "main.sub2.key2"); + verify_string("val", "main.sub2.sub1.key"); +} +END_TEST + START_SETUP(setup_fallback_config) { create_settings(chunk_from_str( @@ -906,9 +1011,8 @@ START_SETUP(setup_string_config) create_settings(chunk_from_str( "string = \" with accurate\twhitespace\"\n" "special = \"all { special } characters # can be used.\"\n" - "unterminated = \"is fine\n" - "but = produces a warning\n" - "newlines = \"can either be encoded\\nor \\\n" + "newlines = \"can be encoded explicitly\\nor implicitly\n" + "or \\\n" "escaped\"\n" "quotes = \"\\\"and\\\" slashes \\\\ can \\\\ be\" # escaped too\n" "multiple = \"strings\" are \"combined\"\n" @@ -920,9 +1024,7 @@ START_TEST(test_strings) { verify_string(" with accurate\twhitespace", "string"); verify_string("all { special } characters # can be used.", "special"); - verify_string("is fine", "unterminated"); - verify_string("produces a warning", "but"); - verify_string("can either be encoded\nor escaped", "newlines"); + verify_string("can be encoded explicitly\nor implicitly\nor escaped", "newlines"); verify_string("\"and\" slashes \\ can \\ be", "quotes"); verify_string("strings are combined", "multiple"); } @@ -990,6 +1092,12 @@ START_TEST(test_invalid) ck_assert(!settings->load_files(settings, path, FALSE)); contents = chunk_from_str( + "unterminated {\n" + " strings = \"are invalid\n"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(!settings->load_files(settings, path, FALSE)); + + contents = chunk_from_str( "spaces in name {}"); ck_assert(chunk_write(contents, path, 0022, TRUE)); ck_assert(!settings->load_files(settings, path, FALSE)); @@ -1060,6 +1168,12 @@ Suite *settings_suite_create() tcase_add_test(tc, test_order_section); suite_add_tcase(s, tc); + tc = tcase_create("load_string[_section]"); + tcase_add_checked_fixture(tc, setup_include_config, teardown_config); + tcase_add_test(tc, test_load_string); + tcase_add_test(tc, test_load_string_section); + suite_add_tcase(s, tc); + tc = tcase_create("fallback"); tcase_add_checked_fixture(tc, setup_fallback_config, teardown_config); tcase_add_test(tc, test_add_fallback); diff --git a/src/starter/parser/lexer.l b/src/starter/parser/lexer.l index d967e745b..f70658e68 100644 --- a/src/starter/parser/lexer.l +++ b/src/starter/parser/lexer.l @@ -123,16 +123,11 @@ static void include_files(parser_helper_t *ctx); <str>{ "\"" | <<EOF>> | - \n | \\ { if (!streq(yytext, "\"")) { - if (streq(yytext, "\n")) - { /* put the newline back to fix the line numbers */ - unput('\n'); - yy_set_bol(0); - } PARSER_DBG1(yyextra, "unterminated string detected"); + return STRING_ERROR; } if (yy_top_state(yyscanner) == inc) { /* string include */ @@ -150,11 +145,9 @@ static void include_files(parser_helper_t *ctx); \\n yyextra->string_add(yyextra, "\n"); \\r yyextra->string_add(yyextra, "\r"); \\t yyextra->string_add(yyextra, "\t"); - \\b yyextra->string_add(yyextra, "\b"); - \\f yyextra->string_add(yyextra, "\f"); \\\r?\n /* merge lines that end with EOL characters */ \\. yyextra->string_add(yyextra, yytext+1); - [^\\\n"]+ { + [^\\"]+ { yyextra->string_add(yyextra, yytext); } } diff --git a/src/starter/parser/parser.y b/src/starter/parser/parser.y index 54dedc12b..0b2b3b09f 100644 --- a/src/starter/parser/parser.y +++ b/src/starter/parser/parser.y @@ -73,7 +73,7 @@ static int yylex(YYSTYPE *lvalp, parser_helper_t *ctx) conf_parser_section_t t; } %token <s> STRING -%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA +%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA STRING_ERROR /* ...and other symbols */ %type <t> section_type diff --git a/src/starter/tests/suites/test_parser.c b/src/starter/tests/suites/test_parser.c index 26a41ba55..4ae7b22fa 100644 --- a/src/starter/tests/suites/test_parser.c +++ b/src/starter/tests/suites/test_parser.c @@ -328,6 +328,9 @@ static struct { { TRUE, "conn foo\n\tkey=val ue", "foo", "val ue" }, { TRUE, "conn foo\n\tkey=\"val ue\"", "foo", "val ue" }, { TRUE, "conn foo\n\tkey=\"val\\nue\"", "foo", "val\nue" }, + { TRUE, "conn foo\n\tkey=\"val\nue\"", "foo", "val\nue" }, + { TRUE, "conn foo\n\tkey=\"val\\\nue\"", "foo", "value" }, + { FALSE, "conn foo\n\tkey=\"unterminated", "foo", NULL }, }; START_TEST(test_strings) |