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 @@
Bitte geben Sie hier die Gateway-Adresse ein
Bitte geben Sie hier Ihren Benutzernamen ein
Kein CA-Zertifikat ausgewählt
Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen
+ Bitte geben Sie eine Nummer von %1$d - %2$d ein
EAP-TNC kann Ihre Privatsphäre beeinträchtigen
Gerätedaten werden an den Gateway-Betreiber gesendet
<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>
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 @@
Wybierz automatycznie
Wybierz certyfikat CA
Wybierz określony certyfikat CA
+ Show advanced settings
+ MTU:
+ Server port:
+ (use default)
+ Split tunneling:
+ Block IPv4 traffic not destined for the VPN
+ Block IPv6 traffic not destined for the VPN
Wprowadź adres bramki
Wprowadź swoją nazwę użytkownika
Nie wybrano żadnego certyfikatu CA
Wybierz lub uaktywnij jeden Wybierz automatycznie
+ Please enter a number in the range from %1$d - %2$d
EAP-TNC may affect your privacy
Device data is sent to the gateway operator
<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>
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 @@
Выбрать автоматически
Выбрать сертификат CA
Выбрать CA сертификат
+ Show advanced settings
+ MTU:
+ Server port:
+ (use default)
+ Split tunneling:
+ Block IPv4 traffic not destined for the VPN
+ Block IPv6 traffic not destined for the VPN
Пожалуйста введите адрес шлюза
Пожалуйста введите имя пользователя
Не выбран сертификат CA
Пожалуйста выберите один Выбрать автоматически
+ Please enter a number in the range from %1$d - %2$d
EAP-TNC may affect your privacy
Device data is sent to the gateway operator
<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>
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 @@
Вибрати автоматично
Вибрати сертифікат CA
Вибрати спеціальний сертифікат CA
+ Show advanced settings
+ MTU:
+ Server port:
+ (use default)
+ Split tunneling:
+ Block IPv4 traffic not destined for the VPN
+ Block IPv6 traffic not destined for the VPN
Введіть адресу шлюза тут
Введіть ім\'я користувача тут
Не вибрано сертифікат CA
Будь ласка виберіть один Вибрати автоматично
+ Please enter a number in the range from %1$d - %2$d
EAP-TNC may affect your privacy
Device data is sent to the gateway operator
<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>
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 @@
Please enter the gateway address here
Please enter your username here
No CA certificate selected
Please select one or activate Select automatically
+ Please enter a number in the range from %1$d - %2$d
EAP-TNC may affect your privacy
Device data is sent to the gateway operator
<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>
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 mAddresses = new ArrayList();
- private final List mRoutes = new ArrayList();
+ private final List mRoutesIPv4 = new ArrayList();
+ private final List mRoutesIPv6 = new ArrayList();
+ 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)
{
- builder.addRoute(route.mAddress, route.mPrefix);
+ 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("::", 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
@@ -314,6 +339,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 .
+ *
+ * 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 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 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 Settings = new LinkedHashMap();
+
+ /**
+ * Assigned sub-sections
+ */
+ LinkedHashMap Sections = new LinkedHashMap();
+ }
+}
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
-#include
-
-#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
@@ -334,6 +334,50 @@ struct settings_t {
bool (*load_files_section)(settings_t *this, char *pattern, bool merge,
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.
*/
@@ -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);
{
"\"" |
<> |
- \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 NAME STRING
-%token NEWLINE
+%token NEWLINE STRING_ERROR
/* ...and other symbols */
%type 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");
}
@@ -989,6 +1091,12 @@ START_TEST(test_invalid)
ck_assert(chunk_write(contents, path, 0022, TRUE));
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));
@@ -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);
{
"\"" |
<> |
- \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 STRING
-%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA
+%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA STRING_ERROR
/* ...and other symbols */
%type 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)