diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index b708be4cb..6767dab1e 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 23
- buildToolsVersion '25.0.0'
+ compileSdkVersion 26
+ buildToolsVersion '27.0.3'
defaultConfig {
applicationId "org.strongswan.android"
minSdkVersion 15
- targetSdkVersion 22
- versionCode 50
- versionName "1.9.6"
+ targetSdkVersion 26
+ versionCode 55
+ versionName "2.0.0"
}
sourceSets.main {
@@ -40,10 +40,16 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
+ compileOptions {
+ targetCompatibility 1.8
+ sourceCompatibility 1.8
+ }
}
dependencies {
- compile 'com.android.support:appcompat-v7:23.3.0'
- compile 'com.android.support:design:23.3.0'
- testCompile 'junit:junit:4.12'
+ implementation 'com.android.support:appcompat-v7:26.0.2'
+ implementation 'com.android.support:design:26.0.2'
+ implementation 'com.android.support:preference-v7:26.0.2'
+ implementation 'com.android.support:support-v4:26.0.2'
+ testImplementation 'junit:junit:4.12'
}
diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml
index 287208238..2a75d05ab 100644
--- a/src/frontends/android/app/src/main/AndroidManifest.xml
+++ b/src/frontends/android/app/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
@@ -97,11 +117,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:theme="@style/TransparentActivity" >
@@ -124,6 +160,15 @@
+
+
+
+
+
= Build.VERSION_CODES.LOLLIPOP)
+ {
+ mBuilderAdapter.setProfile(mNextProfile);
+ mBuilderAdapter.establishBlocking();
+ }
+
if (mCurrentProfile != null)
{
setState(State.DISCONNECTING);
mIsDisconnecting = true;
+ SimpleFetcher.disable();
deinitializeCharon();
Log.i(TAG, "charon stopped");
mCurrentProfile = null;
- removeNotification();
+ if (mNextProfile == null)
+ { /* only do this if we are not connecting to another profile */
+ removeNotification();
+ mBuilderAdapter.closeBlocking();
+ }
}
}
}
@@ -308,8 +371,15 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
*/
private void addNotification()
{
- mShowNotification = true;
- startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
+ mHandler.post(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mShowNotification = true;
+ startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
+ }
+ });
}
/**
@@ -317,10 +387,36 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
*/
private void removeNotification()
{
- mShowNotification = false;
- stopForeground(true);
+ mHandler.post(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mShowNotification = false;
+ stopForeground(true);
+ }
+ });
}
+ /**
+ * Create a notification channel for Android 8+
+ */
+ private void createNotificationChannel()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ {
+ NotificationChannel channel;
+ channel = new NotificationChannel(NOTIFICATION_CHANNEL, getString(R.string.permanent_notification_name),
+ NotificationManager.IMPORTANCE_LOW);
+ channel.setDescription(getString(R.string.permanent_notification_description));
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(false);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+
+
/**
* Build a notification matching the current state
*/
@@ -336,7 +432,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
name = profile.getName();
}
- android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_notification)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
@@ -344,17 +440,38 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
int s = R.string.state_disabled;
if (error != ErrorState.NO_ERROR)
{
- s = R.string.state_error;
+ s = mService.getErrorText();
builder.setSmallIcon(R.drawable.ic_notification_warning);
builder.setColor(ContextCompat.getColor(this, R.color.error_text));
+
+ if (!publicVersion && profile != null)
+ {
+ int retry = mService.getRetryIn();
+ if (retry > 0)
+ {
+ builder.setContentText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
+ builder.setProgress(mService.getRetryTimeout(), retry, false);
+ }
+
+ Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setAction(VpnProfileControlActivity.START_PROFILE);
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.addAction(R.drawable.ic_notification_connecting, getString(R.string.retry), pending);
+ add_action = true;
+ }
}
else
{
+ builder.setProgress(0, 0, false);
+
switch (state)
{
case CONNECTING:
s = R.string.state_connecting;
- builder.setSmallIcon(R.drawable.ic_notification_warning);
+ builder.setSmallIcon(R.drawable.ic_notification_connecting);
builder.setColor(ContextCompat.getColor(this, R.color.warning_text));
add_action = true;
break;
@@ -374,13 +491,16 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
if (add_action)
{
- Intent intent = new Intent(getApplicationContext(), MainActivity.class);
- intent.setAction(MainActivity.DISCONNECT);
+ Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
+ intent.setAction(VpnProfileControlActivity.DISCONNECT);
PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(R.drawable.ic_notification_disconnect, getString(R.string.disconnect), pending);
}
- builder.setContentText(name);
+ if (error == ErrorState.NO_ERROR)
+ {
+ builder.setContentText(name);
+ }
builder.setPublicVersion(buildNotification(true));
}
@@ -519,6 +639,9 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
case STATE_UNREACHABLE_ERROR:
setErrorDisconnect(ErrorState.UNREACHABLE);
break;
+ case STATE_CERTIFICATE_UNAVAILABLE:
+ setErrorDisconnect(ErrorState.CERTIFICATE_UNAVAILABLE);
+ break;
case STATE_GENERIC_ERROR:
setErrorDisconnect(ErrorState.GENERIC_ERROR);
break;
@@ -637,7 +760,6 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
* @return the private key
* @throws InterruptedException
* @throws KeyChainException
- * @throws CertificateEncodingException
*/
private PrivateKey getUserKey() throws KeyChainException, InterruptedException
{
@@ -671,12 +793,13 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
*/
public class BuilderAdapter
{
- private final VpnProfile mProfile;
+ private VpnProfile mProfile;
private VpnService.Builder mBuilder;
private BuilderCache mCache;
private BuilderCache mEstablishedCache;
+ private PacketDropper mDropper = new PacketDropper();
- public BuilderAdapter(VpnProfile profile)
+ public synchronized void setProfile(VpnProfile profile)
{
mProfile = profile;
mBuilder = createBuilder(mProfile.getName());
@@ -764,29 +887,61 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
return true;
}
- public synchronized int establish()
+ private synchronized ParcelFileDescriptor establishIntern()
{
ParcelFileDescriptor fd;
try
{
mCache.applyData(mBuilder);
fd = mBuilder.establish();
+ if (fd != null)
+ {
+ closeBlocking();
+ }
}
catch (Exception ex)
{
ex.printStackTrace();
- return -1;
+ return null;
}
if (fd == null)
{
- return -1;
+ return null;
}
/* now that the TUN device is created we don't need the current
* builder anymore, but we might need another when reestablishing */
mBuilder = createBuilder(mProfile.getName());
mEstablishedCache = mCache;
mCache = new BuilderCache(mProfile);
- return fd.detachFd();
+ return fd;
+ }
+
+ public synchronized int establish()
+ {
+ ParcelFileDescriptor fd = establishIntern();
+ return fd != null ? fd.detachFd() : -1;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public synchronized void establishBlocking()
+ {
+ /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
+ mCache.addAddress("172.16.252.1", 32);
+ mCache.addAddress("fd00::fd02:1", 128);
+ mCache.addRoute("0.0.0.0", 0);
+ mCache.addRoute("::", 0);
+ /* use blocking mode to simplify packet dropping */
+ mBuilder.setBlocking(true);
+ ParcelFileDescriptor fd = establishIntern();
+ if (fd != null)
+ {
+ mDropper.start(fd);
+ }
+ }
+
+ public synchronized void closeBlocking()
+ {
+ mDropper.stop();
}
public synchronized int establishNoDns()
@@ -814,6 +969,68 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
return fd.detachFd();
}
+
+ private class PacketDropper implements Runnable
+ {
+ private ParcelFileDescriptor mFd;
+ private Thread mThread;
+
+ public void start(ParcelFileDescriptor fd)
+ {
+ mFd = fd;
+ mThread = new Thread(this);
+ mThread.start();
+ }
+
+ public void stop()
+ {
+ if (mFd != null)
+ {
+ try
+ {
+ mThread.interrupt();
+ mThread.join();
+ mFd.close();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ mFd = null;
+ }
+ }
+
+ @Override
+ public synchronized void run()
+ {
+ try
+ {
+ FileInputStream plain = new FileInputStream(mFd.getFileDescriptor());
+ ByteBuffer packet = ByteBuffer.allocate(mCache.mMtu);
+ while (true)
+ { /* just read and ignore all data, regular read() is not properly interruptible */
+ int len = plain.getChannel().read(packet);
+ packet.clear();
+ if (len < 0)
+ {
+ break;
+ }
+ }
+ }
+ catch (ClosedByInterruptException e)
+ {
+ /* regular interruption */
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
}
/**
@@ -851,8 +1068,27 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
mExcludedSubnets = IPRangeSet.fromString(profile.getExcludedSubnets());
Integer splitTunneling = profile.getSplitTunneling();
mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
- mAppHandling = profile.getSelectedAppsHandling();
+ SelectedAppsHandling appHandling = profile.getSelectedAppsHandling();
mSelectedApps = profile.getSelectedAppsSet();
+ /* exclude our own app, otherwise the fetcher is blocked */
+ switch (appHandling)
+ {
+ case SELECTED_APPS_DISABLE:
+ appHandling = SelectedAppsHandling.SELECTED_APPS_EXCLUDE;
+ mSelectedApps.clear();
+ /* fall-through */
+ case SELECTED_APPS_EXCLUDE:
+ mSelectedApps.add(getPackageName());
+ break;
+ case SELECTED_APPS_ONLY:
+ mSelectedApps.remove(getPackageName());
+ break;
+ }
+ mAppHandling = appHandling;
+
+ /* set a default MTU, will be set by the daemon for regular interfaces */
+ Integer mtu = profile.getMTU();
+ mMtu = mtu == null ? Constants.MTU_MAX : mtu;
}
public void addAddress(String address, int prefixLength)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java
index b7334cee1..6eaccc4d8 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 Tobias Brunner
+ * Copyright (C) 2017-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -23,36 +23,118 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
@Keep
public class SimpleFetcher
{
- public static byte[] fetch(String uri, byte[] data, String contentType) throws IOException
+ private static ExecutorService mExecutor = Executors.newCachedThreadPool();
+ private static Object mLock = new Object();
+ private static ArrayList mFutures = new ArrayList<>();
+ private static boolean mDisabled;
+
+ public static byte[] fetch(String uri, byte[] data, String contentType)
{
- URL url = new URL(uri);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setConnectTimeout(10000);
- conn.setReadTimeout(10000);
+ Future future;
+
+ synchronized (mLock)
+ {
+ if (mDisabled)
+ {
+ return null;
+ }
+ future = mExecutor.submit(() -> {
+ URL url = new URL(uri);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setConnectTimeout(10000);
+ conn.setReadTimeout(10000);
+ try
+ {
+ if (contentType != null)
+ {
+ conn.setRequestProperty("Content-Type", contentType);
+ }
+ if (data != null)
+ {
+ conn.setDoOutput(true);
+ conn.setFixedLengthStreamingMode(data.length);
+ OutputStream out = new BufferedOutputStream(conn.getOutputStream());
+ out.write(data);
+ out.close();
+ }
+ return streamToArray(conn.getInputStream());
+ }
+ catch (SocketTimeoutException e)
+ {
+ return null;
+ }
+ finally
+ {
+ conn.disconnect();
+ }
+ });
+
+ mFutures.add(future);
+ }
+
try
{
- if (contentType != null)
- {
- conn.setRequestProperty("Content-Type", contentType);
- }
- if (data != null)
- {
- conn.setDoOutput(true);
- conn.setFixedLengthStreamingMode(data.length);
- OutputStream out = new BufferedOutputStream(conn.getOutputStream());
- out.write(data);
- out.close();
- }
- return streamToArray(conn.getInputStream());
+ /* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */
+ return future.get(10000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e)
+ {
+ return null;
}
finally
{
- conn.disconnect();
+ synchronized (mLock)
+ {
+ mFutures.remove(future);
+ }
+ }
+ }
+
+ /**
+ * Enable fetching after it has been disabled.
+ */
+ public static void enable()
+ {
+ synchronized (mLock)
+ {
+ mDisabled = false;
+ }
+ }
+
+ /**
+ * Disable the fetcher and abort any future requests.
+ *
+ * The native thread is not cancelable as it is working on an IKE_SA (cancelling the methods of
+ * HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the
+ * Future (causing a return from fetch() immediately) and let the executor thread continue its
+ * thing in the background.
+ *
+ * Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then
+ * block in the subsequent fetch for a CRL).
+ */
+ public static void disable()
+ {
+ synchronized (mLock)
+ {
+ mDisabled = true;
+ for (Future future : mFutures)
+ {
+ future.cancel(true);
+ }
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
index cd30049f7..588929361 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
@@ -19,13 +19,22 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v4.content.ContextCompat;
+import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType;
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
+import org.strongswan.android.ui.VpnProfileControlActivity;
+import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
@@ -43,6 +52,13 @@ public class VpnStateService extends Service
private ErrorState mError = ErrorState.NO_ERROR;
private ImcState mImcState = ImcState.UNKNOWN;
private final LinkedList mRemediationInstructions = new LinkedList();
+ private static long RETRY_INTERVAL = 1000;
+ /* cap the retry interval at 2 minutes */
+ private static long MAX_RETRY_INTERVAL = 120000;
+ private static int RETRY_MSG = 1;
+ private RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider();
+ private long mRetryTimeout;
+ private long mRetryIn;
public enum State
{
@@ -60,6 +76,8 @@ public class VpnStateService extends Service
LOOKUP_FAILED,
UNREACHABLE,
GENERIC_ERROR,
+ PASSWORD_MISSING,
+ CERTIFICATE_UNAVAILABLE,
}
/**
@@ -88,7 +106,7 @@ public class VpnStateService extends Service
{
/* this handler allows us to notify listeners from the UI thread and
* not from the threads that actually report any state changes */
- mHandler = new Handler();
+ mHandler = new RetryHandler(this);
}
@Override
@@ -146,6 +164,24 @@ public class VpnStateService extends Service
return mConnectionID;
}
+ /**
+ * Get the total number of seconds until there is an automatic retry to reconnect.
+ * @return total number of seconds until the retry
+ */
+ public int getRetryTimeout()
+ {
+ return (int)(mRetryTimeout / 1000);
+ }
+
+ /**
+ * Get the number of seconds until there is an automatic retry to reconnect.
+ * @return number of seconds until the retry
+ */
+ public int getRetryIn()
+ {
+ return (int)(mRetryIn / 1000);
+ }
+
/**
* Get the current state.
*
@@ -166,6 +202,39 @@ public class VpnStateService extends Service
return mError;
}
+ /**
+ * Get a description of the current error, if any.
+ *
+ * @return error description text id
+ */
+ public int getErrorText()
+ {
+ switch (mError)
+ {
+ case AUTH_FAILED:
+ if (mImcState == ImcState.BLOCK)
+ {
+ return R.string.error_assessment_failed;
+ }
+ else
+ {
+ return R.string.error_auth_failed;
+ }
+ case PEER_AUTH_FAILED:
+ return R.string.error_peer_auth_failed;
+ case LOOKUP_FAILED:
+ return R.string.error_lookup_failed;
+ case UNREACHABLE:
+ return R.string.error_unreachable;
+ case PASSWORD_MISSING:
+ return R.string.error_password_missing;
+ case CERTIFICATE_UNAVAILABLE:
+ return R.string.error_certificate_unavailable;
+ default:
+ return R.string.error_generic;
+ }
+ }
+
/**
* Get the current IMC state, if any.
*
@@ -193,6 +262,10 @@ public class VpnStateService extends Service
*/
public void disconnect()
{
+ /* reset any potential retry timer and error state */
+ resetRetryTimer();
+ setError(ErrorState.NO_ERROR);
+
/* as soon as the TUN device is created by calling establish() on the
* VpnService.Builder object the system binds to the service and keeps
* bound until the file descriptor of the TUN device is closed. thus
@@ -205,6 +278,68 @@ public class VpnStateService extends Service
context.startService(intent);
}
+ /**
+ * Connect (or reconnect) a profile
+ * @param profileInfo optional profile info (basically the UUID and password), taken from the
+ * previous profile if null
+ * @param fromScratch true if this is a manual retry/reconnect or a completely new connection
+ */
+ public void connect(Bundle profileInfo, boolean fromScratch)
+ {
+ /* we assume we have the necessary permission */
+ Context context = getApplicationContext();
+ Intent intent = new Intent(context, CharonVpnService.class);
+ if (profileInfo == null)
+ {
+ profileInfo = new Bundle();
+ profileInfo.putString(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString());
+ /* pass the previous password along */
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword());
+ }
+ if (fromScratch)
+ {
+ /* reset if this is a manual retry or a new connection */
+ mTimeoutProvider.reset();
+ }
+ else
+ { /* mark this as an automatic retry */
+ profileInfo.putBoolean(CharonVpnService.KEY_IS_RETRY, true);
+ }
+ intent.putExtras(profileInfo);
+ ContextCompat.startForegroundService(context, intent);
+ }
+
+ /**
+ * Reconnect to the previous profile.
+ */
+ public void reconnect()
+ {
+ if (mProfile == null)
+ {
+ return;
+ }
+ if (mProfile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS))
+ {
+ if (mProfile.getPassword() == null ||
+ mError == ErrorState.AUTH_FAILED)
+ { /* show a dialog if we either don't have the password or if it might be the wrong
+ * one (which is or isn't stored with the profile, let the activity decide) */
+ Intent intent = new Intent(this, VpnProfileControlActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setAction(VpnProfileControlActivity.START_PROFILE);
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, mProfile.getUUID().toString());
+ startActivity(intent);
+ /* reset the retry timer immediately in case the user needs more time to enter the password */
+ notifyListeners(() -> {
+ resetRetryTimer();
+ return true;
+ });
+ return;
+ }
+ }
+ connect(null, true);
+ }
+
/**
* Update state and notify all listeners about the change. By using a Handler
* this is done from the main UI thread and not the initial reporter thread.
@@ -252,6 +387,7 @@ public class VpnStateService extends Service
@Override
public Boolean call() throws Exception
{
+ resetRetryTimer();
VpnStateService.this.mConnectionID++;
VpnStateService.this.mProfile = profile;
VpnStateService.this.mState = State.CONNECTING;
@@ -276,6 +412,10 @@ public class VpnStateService extends Service
@Override
public Boolean call() throws Exception
{
+ if (state == State.CONNECTED)
+ { /* reset counter in case there is an error later on */
+ mTimeoutProvider.reset();
+ }
if (VpnStateService.this.mState != state)
{
VpnStateService.this.mState = state;
@@ -301,6 +441,14 @@ public class VpnStateService extends Service
{
if (VpnStateService.this.mError != error)
{
+ if (VpnStateService.this.mError == ErrorState.NO_ERROR)
+ {
+ setRetryTimer(error);
+ }
+ else if (error == ErrorState.NO_ERROR)
+ {
+ resetRetryTimer();
+ }
VpnStateService.this.mError = error;
return true;
}
@@ -358,4 +506,115 @@ public class VpnStateService extends Service
}
});
}
+
+ /**
+ * Sets the retry timer
+ */
+ private void setRetryTimer(ErrorState error)
+ {
+ mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
+ if (mRetryTimeout <= 0)
+ {
+ return;
+ }
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_INTERVAL);
+ }
+
+ /**
+ * Reset the retry timer
+ */
+ private void resetRetryTimer()
+ {
+ mRetryTimeout = 0;
+ mRetryIn = 0;
+ }
+
+ /**
+ * Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer)
+ */
+ private static class RetryHandler extends Handler {
+ WeakReference mService;
+
+ public RetryHandler(VpnStateService service)
+ {
+ mService = new WeakReference<>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg)
+ {
+ /* handle retry countdown */
+ if (mService.get().mRetryTimeout <= 0)
+ {
+ return;
+ }
+ mService.get().mRetryIn -= RETRY_INTERVAL;
+ if (mService.get().mRetryIn > 0)
+ {
+ /* calculate next interval before notifying listeners */
+ long next = SystemClock.uptimeMillis() + RETRY_INTERVAL;
+
+ for (VpnStateListener listener : mService.get().mListeners)
+ {
+ listener.stateChanged();
+ }
+ sendMessageAtTime(obtainMessage(RETRY_MSG), next);
+ }
+ else
+ {
+ mService.get().connect(null, false);
+ }
+ }
+ }
+
+ /**
+ * Class that handles an exponential backoff for retry timeouts
+ */
+ private static class RetryTimeoutProvider
+ {
+ private long mRetry;
+
+ private long getBaseTimeout(ErrorState error)
+ {
+ switch (error)
+ {
+ case AUTH_FAILED:
+ return 10000;
+ case PEER_AUTH_FAILED:
+ return 5000;
+ case LOOKUP_FAILED:
+ return 5000;
+ case UNREACHABLE:
+ return 5000;
+ case PASSWORD_MISSING:
+ /* this needs user intervention (entering the password) */
+ return 0;
+ case CERTIFICATE_UNAVAILABLE:
+ /* if this is because the device has to be unlocked we might be able to reconnect */
+ return 5000;
+ default:
+ return 10000;
+ }
+ }
+
+ /**
+ * Called each time a new retry timeout is started. The timeout increases until reset() is
+ * called and the base timeout is returned again.
+ * @param error Error state
+ */
+ public long getTimeout(ErrorState error)
+ {
+ long timeout = (long)(getBaseTimeout(error) * Math.pow(2, mRetry++));
+ /* return the result rounded to seconds */
+ return Math.min((timeout / 1000) * 1000, MAX_RETRY_INTERVAL);
+ }
+
+ /**
+ * Reset the retry counter.
+ */
+ public void reset()
+ {
+ mRetry = 0;
+ }
+ }
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
index f3123f681..5e36b11ff 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
@@ -15,8 +15,8 @@
package org.strongswan.android.ui;
-import android.app.Activity;
import android.app.Dialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
@@ -43,12 +43,12 @@ public class CertificateDeleteConfirmationDialog extends AppCompatDialogFragment
}
@Override
- public void onAttach(Activity activity)
+ public void onAttach(Context context)
{
- super.onAttach(activity);
- if (activity instanceof OnCertificateDeleteListener)
+ super.onAttach(context);
+ if (context instanceof OnCertificateDeleteListener)
{
- mListener = (OnCertificateDeleteListener)activity;
+ mListener = (OnCertificateDeleteListener)context;
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
index c6a80a100..a3cb245e8 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
@@ -49,6 +49,7 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
{
private int mColorIsolate;
private int mColorBlock;
+ private boolean mVisible;
private TextView mStateView;
private TextView mAction;
private LinearLayout mButton;
@@ -65,7 +66,11 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = ((VpnStateService.LocalBinder)service).getService();
- mService.registerListener(ImcStateFragment.this);
+ if (mVisible)
+ {
+ mService.registerListener(ImcStateFragment.this);
+ updateView();
+ }
}
};
@@ -149,6 +154,7 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
public void onResume()
{
super.onResume();
+ mVisible = true;
if (mService != null)
{
mService.registerListener(this);
@@ -160,6 +166,7 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
public void onPause()
{
super.onPause();
+ mVisible = false;
if (mService != null)
{
mService.unregisterListener(this);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
index f68d0c617..625940fd4 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2017 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -15,14 +15,17 @@
package org.strongswan.android.ui;
+import android.content.Context;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
import org.strongswan.android.R;
import org.strongswan.android.logic.CharonVpnService;
@@ -34,16 +37,15 @@ import java.io.FileReader;
import java.io.StringReader;
import java.util.ArrayList;
-public class LogFragment extends Fragment implements Runnable
+public class LogFragment extends Fragment
{
+ private static String SCROLL_POSITION = "SCROLL_POSITION";
private String mLogFilePath;
private Handler mLogHandler;
- private TextView mLogView;
- private LogScrollView mScrollView;
- private BufferedReader mReader;
- private Thread mThread;
- private volatile boolean mRunning;
+ private ListView mLog;
+ private LogAdapter mLogAdapter;
private FileObserver mDirectoryObserver;
+ private int mScrollPosition;
@Override
public void onCreate(Bundle savedInstanceState)
@@ -51,7 +53,7 @@ public class LogFragment extends Fragment implements Runnable
super.onCreate(savedInstanceState);
mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE;
- /* use a handler to update the log view */
+
mLogHandler = new Handler();
mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath());
@@ -61,16 +63,39 @@ public class LogFragment extends Fragment implements Runnable
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.log_fragment, null);
- mLogView = (TextView)view.findViewById(R.id.log_view);
- mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view);
+
+ mLogAdapter = new LogAdapter(getActivity());
+ mLog = view.findViewById(R.id.log);
+ mLog.setAdapter(mLogAdapter);
+
+ mScrollPosition = -1;
+ if (savedInstanceState != null)
+ {
+ mScrollPosition = savedInstanceState.getInt(SCROLL_POSITION, mScrollPosition);
+ }
return view;
}
+ @Override
+ public void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+
+ if (mLog.getLastVisiblePosition() == (mLogAdapter.getCount() - 1))
+ {
+ outState.putInt(SCROLL_POSITION, -1);
+ }
+ else
+ {
+ outState.putInt(SCROLL_POSITION, mLog.getFirstVisiblePosition());
+ }
+ }
+
@Override
public void onStart()
{
super.onStart();
- startLogReader();
+ mLogAdapter.restart();
mDirectoryObserver.startWatching();
}
@@ -79,113 +104,114 @@ public class LogFragment extends Fragment implements Runnable
{
super.onStop();
mDirectoryObserver.stopWatching();
- stopLogReader();
+ mLogAdapter.stop();
}
- /**
- * Start reading from the log file
- */
- private void startLogReader()
+ private class LogAdapter extends ArrayAdapter implements Runnable
{
- try
+ private BufferedReader mReader;
+ private Thread mThread;
+ private volatile boolean mRunning;
+
+ public LogAdapter(@NonNull Context context)
{
- mReader = new BufferedReader(new FileReader(mLogFilePath));
- }
- catch (FileNotFoundException e)
- {
- mReader = new BufferedReader(new StringReader(""));
+ super(context, R.layout.log_list_item, R.id.log_line);
}
- mLogView.setText("");
- mRunning = true;
- mThread = new Thread(this);
- mThread.start();
- }
-
- /**
- * Stop reading from the log file
- */
- private void stopLogReader()
- {
- try
+ public void restart()
{
- mRunning = false;
- mThread.interrupt();
- mThread.join();
- }
- catch (InterruptedException e)
- {
- }
- }
-
- /**
- * Write the given log line to the TextView. We strip the prefix off to save
- * some space (it is not that helpful for regular users anyway).
- *
- * @param lines log lines to log
- */
- public void logLines(final ArrayList lines)
- {
- mLogHandler.post(new Runnable() {
- @Override
- public void run()
+ if (mRunning)
{
- mLogView.beginBatchEdit();
- for (String line : lines)
- { /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */
- mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n');
- }
- mLogView.endBatchEdit();
- /* calling autoScroll() directly does not work, probably because content
- * is not yet updated, so we post this to be done later */
- mScrollView.post(new Runnable() {
- @Override
- public void run()
- {
- mScrollView.autoScroll();
- }
- });
+ stop();
}
- });
- }
- @Override
- public void run()
- {
- ArrayList lines = null;
+ clear();
- while (mRunning)
+ try
+ {
+ mReader = new BufferedReader(new FileReader(mLogFilePath));
+ }
+ catch (FileNotFoundException e)
+ {
+ mReader = new BufferedReader(new StringReader(""));
+ }
+ mRunning = true;
+ mThread = new Thread(this);
+ mThread.start();
+ }
+
+ public void stop()
{
try
- { /* this works as long as the file is not truncated */
- String line = mReader.readLine();
- if (line == null)
- {
- if (lines != null)
- {
- logLines(lines);
- lines = null;
- }
- /* wait until there is more to log */
- Thread.sleep(1000);
- }
- else
- {
- if (lines == null)
- {
- lines = new ArrayList<>();
- }
- lines.add(line);
- }
- }
- catch (Exception e)
{
- break;
+ mRunning = false;
+ mThread.interrupt();
+ mThread.join();
+ }
+ catch (InterruptedException e)
+ {
}
}
- if (lines != null)
+
+ private void logLines(final ArrayList lines)
{
- logLines(lines);
+ mLogHandler.post(() -> {
+ boolean scroll = getCount() == 0;
+ setNotifyOnChange(false);
+ for (String line : lines)
+ {
+ if (getResources().getConfiguration().screenWidthDp < 600)
+ { /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */
+ line = line.length() > 18 ? line.substring(18) : line;
+ }
+ add(line);
+ }
+ notifyDataSetChanged();
+ if (scroll)
+ { /* scroll to the bottom or saved position after adding the first batch */
+ mLogHandler.post(() -> mLog.setSelection(mScrollPosition == -1 ? getCount() - 1 : mScrollPosition));
+ }
+ });
+ }
+
+ @Override
+ public void run()
+ {
+ ArrayList lines = null;
+
+ while (mRunning)
+ {
+ try
+ { /* this works as long as the file is not truncated */
+ String line = mReader.readLine();
+ if (line == null)
+ {
+ if (lines != null)
+ {
+ logLines(lines);
+ lines = null;
+ }
+ /* wait until there is more to log */
+ Thread.sleep(1000);
+ }
+ else
+ {
+ if (lines == null)
+ {
+ lines = new ArrayList<>();
+ }
+ lines.add(line);
+ }
+ }
+ catch (Exception e)
+ {
+ break;
+ }
+ }
+ if (lines != null)
+ {
+ logLines(lines);
+ }
}
}
@@ -239,8 +265,7 @@ public class LogFragment extends Fragment implements Runnable
@Override
public void run()
{
- stopLogReader();
- startLogReader();
+ mLogAdapter.restart();
}
});
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java
deleted file mode 100644
index cf2502271..000000000
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2012 Tobias Brunner
- * Copyright (C) 2012 Giuliano Grassi
- * Copyright (C) 2012 Ralf Sager
- * HSR 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.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ScrollView;
-
-public class LogScrollView extends ScrollView
-{
- private boolean mAutoScroll = true;
-
- public LogScrollView(Context context)
- {
- super(context);
- }
-
- public LogScrollView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
- }
-
- public LogScrollView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev)
- {
- /* disable auto-scrolling when the user starts scrolling around */
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN)
- {
- mAutoScroll = false;
- }
- return super.onTouchEvent(ev);
- }
-
- /**
- * Call this to move newly added content into view by scrolling to the bottom.
- * Nothing happens if auto-scrolling is disabled.
- */
- public void autoScroll()
- {
- if (mAutoScroll)
- {
- fullScroll(View.FOCUS_DOWN);
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt)
- {
- super.onScrollChanged(l, t, oldl, oldt);
- /* if the user scrolls to the bottom we enable auto-scrolling again */
- if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight())
- {
- mAutoScroll = true;
- }
- }
-}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index c0e375da4..bfef8e98c 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2017 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@@ -18,17 +18,11 @@
package org.strongswan.android.ui;
import android.app.Dialog;
-import android.app.Service;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.VpnService;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@@ -37,22 +31,13 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDialogFragment;
import android.text.format.Formatter;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.EditText;
import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
-import org.strongswan.android.data.VpnProfileDataSource;
-import org.strongswan.android.data.VpnType.VpnTypeFeature;
-import org.strongswan.android.logic.CharonVpnService;
import org.strongswan.android.logic.TrustedCertificateManager;
-import org.strongswan.android.logic.VpnStateService;
-import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
import java.io.File;
@@ -62,53 +47,18 @@ import java.util.List;
public class MainActivity extends AppCompatActivity implements OnVpnProfileSelectedListener
{
public static final String CONTACT_EMAIL = "android@strongswan.org";
- public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
- public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
- public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
public static final String EXTRA_CRL_LIST = "org.strongswan.android.CRL_LIST";
+
/**
* Use "bring your own device" (BYOD) features
*/
public static final boolean USE_BYOD = true;
- private static final int PREPARE_VPN_SERVICE = 0;
- private static final String PROFILE_NAME = "org.strongswan.android.MainActivity.PROFILE_NAME";
- private static final String PROFILE_REQUIRES_PASSWORD = "org.strongswan.android.MainActivity.REQUIRES_PASSWORD";
- private static final String PROFILE_RECONNECT = "org.strongswan.android.MainActivity.RECONNECT";
- private static final String PROFILE_DISCONNECT = "org.strongswan.android.MainActivity.DISCONNECT";
- private static final String PROFILE_FOREGROUND = "org.strongswan.android.MainActivity.PROFILE_FOREGROUND";
+
private static final String DIALOG_TAG = "Dialog";
- private boolean mIsVisible;
- private Bundle mProfileInfo;
- private VpnStateService mService;
- private final ServiceConnection mServiceConnection = new ServiceConnection()
- {
- @Override
- public void onServiceDisconnected(ComponentName name)
- {
- mService = null;
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service)
- {
- mService = ((VpnStateService.LocalBinder)service).getService();
-
- if (START_PROFILE.equals(getIntent().getAction()))
- {
- startVpnProfile(getIntent(), false);
- }
- else if (DISCONNECT.equals(getIntent().getAction()))
- {
- disconnect(false);
- }
- }
- };
-
@Override
public void onCreate(Bundle savedInstanceState)
{
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
@@ -117,55 +67,10 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
bar.setDisplayShowTitleEnabled(false);
bar.setIcon(R.drawable.ic_launcher);
- this.bindService(new Intent(this, VpnStateService.class),
- mServiceConnection, Service.BIND_AUTO_CREATE);
-
/* load CA certificates in a background task */
new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- @Override
- protected void onDestroy()
- {
- super.onDestroy();
- if (mService != null)
- {
- this.unbindService(mServiceConnection);
- }
- }
-
- @Override
- protected void onStart()
- {
- super.onStart();
- mIsVisible = true;
- }
-
- @Override
- protected void onStop()
- {
- super.onStop();
- mIsVisible = false;
- }
-
- /**
- * Due to launchMode=singleTop this is called if the Activity already exists
- */
- @Override
- protected void onNewIntent(Intent intent)
- {
- super.onNewIntent(intent);
-
- if (START_PROFILE.equals(intent.getAction()))
- {
- startVpnProfile(intent, mIsVisible);
- }
- else if (DISCONNECT.equals(intent.getAction()))
- {
- disconnect(mIsVisible);
- }
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
@@ -203,177 +108,22 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
Intent logIntent = new Intent(this, LogActivity.class);
startActivity(logIntent);
return true;
+ case R.id.menu_settings:
+ Intent settingsIntent = new Intent(this, SettingsActivity.class);
+ startActivity(settingsIntent);
+ return true;
default:
return super.onOptionsItemSelected(item);
}
}
- /**
- * Prepare the VpnService. If this succeeds the current VPN profile is
- * started.
- *
- * @param profileInfo a bundle containing the information about the profile to be started
- */
- protected void prepareVpnService(Bundle profileInfo)
- {
- Intent intent;
- try
- {
- intent = VpnService.prepare(this);
- }
- catch (IllegalStateException ex)
- {
- /* this happens if the always-on VPN feature (Android 4.2+) is activated */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
- return;
- }
- catch (NullPointerException ex)
- {
- /* not sure when this happens exactly, but apparently it does */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
- return;
- }
- /* store profile info until the user grants us permission */
- mProfileInfo = profileInfo;
- if (intent != null)
- {
- try
- {
- startActivityForResult(intent, PREPARE_VPN_SERVICE);
- }
- catch (ActivityNotFoundException ex)
- {
- /* it seems some devices, even though they come with Android 4,
- * don't have the VPN components built into the system image.
- * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
- * will not be found then */
- VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
- }
- }
- else
- { /* user already granted permission to use VpnService */
- onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data)
- {
- switch (requestCode)
- {
- case PREPARE_VPN_SERVICE:
- if (resultCode == RESULT_OK && mProfileInfo != null)
- {
- Intent intent = new Intent(this, CharonVpnService.class);
- intent.putExtras(mProfileInfo);
- this.startService(intent);
- }
- break;
- default:
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
@Override
public void onVpnProfileSelected(VpnProfile profile)
{
- startVpnProfile(profile, true);
- }
-
- /**
- * Start the given VPN profile
- *
- * @param profile VPN profile
- * @param foreground whether this was initiated when the activity was visible
- */
- public void startVpnProfile(VpnProfile profile, boolean foreground)
- {
- Bundle profileInfo = new Bundle();
- profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
- profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
- profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
- profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
- profileInfo.putString(PROFILE_NAME, profile.getName());
-
- removeFragmentByTag(DIALOG_TAG);
-
- if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
- {
- profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId());
- profileInfo.putBoolean(PROFILE_FOREGROUND, foreground);
-
- ConfirmationDialog dialog = new ConfirmationDialog();
- dialog.setArguments(profileInfo);
- dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
- return;
- }
- startVpnProfile(profileInfo);
- }
-
- /**
- * Start the given VPN profile asking the user for a password if required.
- *
- * @param profileInfo data about the profile
- */
- private void startVpnProfile(Bundle profileInfo)
- {
- if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
- profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
- {
- LoginDialog login = new LoginDialog();
- login.setArguments(profileInfo);
- login.show(getSupportFragmentManager(), DIALOG_TAG);
- return;
- }
- prepareVpnService(profileInfo);
- }
-
- /**
- * Start the VPN profile referred to by the given intent. Displays an error
- * if the profile doesn't exist.
- *
- * @param intent Intent that caused us to start this
- * @param foreground whether this was initiated when the activity was visible
- */
- private void startVpnProfile(Intent intent, boolean foreground)
- {
- long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
- if (profileId <= 0)
- { /* invalid invocation */
- return;
- }
- VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
- dataSource.open();
- VpnProfile profile = dataSource.getVpnProfile(profileId);
- dataSource.close();
-
- if (profile != null)
- {
- startVpnProfile(profile, foreground);
- }
- else
- {
- Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Disconnect the current connection, if any (silently ignored if there is no connection).
- */
- private void disconnect(boolean foreground)
- {
- removeFragmentByTag(DIALOG_TAG);
-
- if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
- {
- Bundle args = new Bundle();
- args.putBoolean(PROFILE_DISCONNECT, true);
- args.putBoolean(PROFILE_FOREGROUND, foreground);
-
- ConfirmationDialog dialog = new ConfirmationDialog();
- dialog.setArguments(args);
- dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
- }
+ Intent intent = new Intent(this, VpnProfileControlActivity.class);
+ intent.setAction(VpnProfileControlActivity.START_PROFILE);
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ startActivity(intent);
}
/**
@@ -411,23 +161,11 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
*/
private class LoadCertificatesTask extends AsyncTask
{
- @Override
- protected void onPreExecute()
- {
- setProgressBarIndeterminateVisibility(true);
- }
-
@Override
protected TrustedCertificateManager doInBackground(Void... params)
{
return TrustedCertificateManager.getInstance().load();
}
-
- @Override
- protected void onPostExecute(TrustedCertificateManager result)
- {
- setProgressBarIndeterminateVisibility(false);
- }
}
/**
@@ -445,175 +183,6 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
}
}
- /**
- * Class that displays a confirmation dialog if a VPN profile is already connected
- * and then initiates the selected VPN profile if the user confirms the dialog.
- */
- public static class ConfirmationDialog extends AppCompatDialogFragment
- {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle profileInfo = getArguments();
- int icon = android.R.drawable.ic_dialog_alert;
- int title = R.string.connect_profile_question;
- int message = R.string.replaces_active_connection;
- int button = R.string.connect;
-
- if (profileInfo.getBoolean(PROFILE_RECONNECT))
- {
- icon = android.R.drawable.ic_dialog_info;
- title = R.string.vpn_connected;
- message = R.string.vpn_profile_connected;
- button = R.string.reconnect;
- }
- else if (profileInfo.getBoolean(PROFILE_DISCONNECT))
- {
- title = R.string.disconnect_question;
- message = R.string.disconnect_active_connection;
- button = R.string.disconnect;
- }
-
- DialogInterface.OnClickListener connectListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- MainActivity activity = (MainActivity)getActivity();
- activity.startVpnProfile(profileInfo);
- }
- };
- DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- MainActivity activity = (MainActivity)getActivity();
- if (activity.mService != null)
- {
- activity.mService.disconnect();
- }
- }
- };
- DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dismiss();
- if (!profileInfo.getBoolean(PROFILE_FOREGROUND))
- { /* if the app was not in the foreground before this action was triggered
- * externally, we just close the activity if canceled */
- getActivity().finish();
- }
- }
- };
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setIcon(icon)
- .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME)))
- .setMessage(message);
-
- if (profileInfo.getBoolean(PROFILE_DISCONNECT))
- {
- builder.setPositiveButton(button, disconnectListener);
- }
- else
- {
- builder.setPositiveButton(button, connectListener);
- }
-
- if (profileInfo.getBoolean(PROFILE_RECONNECT))
- {
- builder.setNegativeButton(R.string.disconnect, disconnectListener);
- builder.setNeutralButton(android.R.string.cancel, cancelListener);
- }
- else
- {
- builder.setNegativeButton(android.R.string.cancel, cancelListener);
- }
- return builder.create();
- }
- }
-
- /**
- * Class that displays a login dialog and initiates the selected VPN
- * profile if the user confirms the dialog.
- */
- public static class LoginDialog extends AppCompatDialogFragment
- {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle profileInfo = getArguments();
- LayoutInflater inflater = getActivity().getLayoutInflater();
- View view = inflater.inflate(R.layout.login_dialog, null);
- EditText username = (EditText)view.findViewById(R.id.username);
- username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
- final EditText password = (EditText)view.findViewById(R.id.password);
-
- AlertDialog.Builder adb = new AlertDialog.Builder(getActivity());
- adb.setView(view);
- adb.setTitle(getString(R.string.login_title));
- adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int whichButton)
- {
- MainActivity activity = (MainActivity)getActivity();
- profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
- activity.prepareVpnService(profileInfo);
- }
- });
- adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dismiss();
- }
- });
- return adb.create();
- }
- }
-
- /**
- * Class representing an error message which is displayed if VpnService is
- * not supported on the current device.
- */
- public static class VpnNotSupportedError extends AppCompatDialogFragment
- {
- static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
-
- public static void showWithMessage(AppCompatActivity activity, int messageId)
- {
- Bundle bundle = new Bundle();
- bundle.putInt(ERROR_MESSAGE_ID, messageId);
- VpnNotSupportedError dialog = new VpnNotSupportedError();
- dialog.setArguments(bundle);
- dialog.show(activity.getSupportFragmentManager(), DIALOG_TAG);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState)
- {
- final Bundle arguments = getArguments();
- final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
- return new AlertDialog.Builder(getActivity())
- .setTitle(R.string.vpn_not_supported_title)
- .setMessage(messageId)
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int id)
- {
- dialog.dismiss();
- }
- }).create();
- }
- }
-
/**
* Confirmation dialog to clear CRL cache
*/
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java
new file mode 100644
index 000000000..ad28529f8
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 Tobias Brunner
+ * HSR 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.ui;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+
+public class SettingsActivity extends AppCompatActivity
+{
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, new SettingsFragment())
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ switch (item.getItemId())
+ {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
new file mode 100644
index 000000000..b710c828c
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 Tobias Brunner
+ * HSR 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.ui;
+
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceFragmentCompat;
+import android.support.v7.preference.PreferenceManager;
+
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE;
+import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU;
+
+public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener
+{
+ private ListPreference mDefaultVPNProfile;
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String s)
+ {
+ setPreferencesFromResource(R.xml.settings, s);
+
+ mDefaultVPNProfile = (ListPreference)findPreference(PREF_DEFAULT_VPN_PROFILE);
+ mDefaultVPNProfile.setOnPreferenceChangeListener(this);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ {
+ mDefaultVPNProfile.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+
+ VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity());
+ profiles.open();
+
+ List all = profiles.getAllVpnProfiles();
+ Collections.sort(all, new Comparator() {
+ @Override
+ public int compare(VpnProfile lhs, VpnProfile rhs)
+ {
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
+ }
+ });
+
+ ArrayList entries = new ArrayList<>();
+ ArrayList entryvalues = new ArrayList<>();
+
+ entries.add(getString(R.string.pref_default_vpn_profile_mru));
+ entryvalues.add(PREF_DEFAULT_VPN_PROFILE_MRU);
+
+ for (VpnProfile profile : all)
+ {
+ entries.add(profile.getName());
+ entryvalues.add(profile.getUUID().toString());
+ }
+ profiles.close();
+
+ if (entries.size() <= 1)
+ {
+ mDefaultVPNProfile.setEnabled(false);
+ }
+ else
+ {
+ mDefaultVPNProfile.setEnabled(true);
+ mDefaultVPNProfile.setEntries(entries.toArray(new CharSequence[0]));
+ mDefaultVPNProfile.setEntryValues(entryvalues.toArray(new CharSequence[0]));
+ }
+
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ setCurrentProfileName(pref.getString(PREF_DEFAULT_VPN_PROFILE, PREF_DEFAULT_VPN_PROFILE_MRU));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ if (preference == mDefaultVPNProfile)
+ {
+ setCurrentProfileName((String)newValue);
+ }
+ return true;
+ }
+
+ private void setCurrentProfileName(String uuid)
+ {
+ VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity());
+ profiles.open();
+
+ if (!uuid.equals(PREF_DEFAULT_VPN_PROFILE_MRU))
+ {
+ VpnProfile current = profiles.getVpnProfile(uuid);
+ if (current != null)
+ {
+ mDefaultVPNProfile.setSummary(current.getName());
+ }
+ else
+ {
+ mDefaultVPNProfile.setSummary(R.string.profile_not_found);
+ }
+ }
+ else
+ {
+ mDefaultVPNProfile.setSummary(R.string.pref_default_vpn_profile_mru);
+ }
+ profiles.close();
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
index ed950cd11..9cfecf348 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
@@ -18,6 +18,7 @@ package org.strongswan.android.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
+import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
@@ -44,14 +45,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
{
private static final int OPEN_DOCUMENT = 0;
private static final String DIALOG_TAG = "Dialog";
-
- /* same as those listed in the manifest */
- private static final String[] ACCEPTED_MIME_TYPES = {
- "application/x-x509-ca-cert",
- "application/x-x509-server-cert",
- "application/x-pem-file",
- "application/pkix-cert"
- };
private Uri mCertificateUri;
@TargetApi(Build.VERSION_CODES.KITKAT)
@@ -75,8 +68,15 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
{
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openIntent.setType("*/*");
- openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES);
- startActivityForResult(openIntent, OPEN_DOCUMENT);
+ try
+ {
+ startActivityForResult(openIntent, OPEN_DOCUMENT);
+ }
+ catch (ActivityNotFoundException e)
+ { /* some devices are unable to browse for files */
+ finish();
+ return;
+ }
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
new file mode 100644
index 000000000..78cbebfae
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2012-2018 Tobias Brunner
+ * HSR 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.ui;
+
+import android.app.Dialog;
+import android.app.Service;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.VpnService;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType.VpnTypeFeature;
+import org.strongswan.android.logic.VpnStateService;
+import org.strongswan.android.logic.VpnStateService.State;
+
+public class VpnProfileControlActivity extends AppCompatActivity
+{
+ public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
+ public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
+ public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
+
+ private static final int PREPARE_VPN_SERVICE = 0;
+ private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT";
+ private static final String PROFILE_NAME = "PROFILE_NAME";
+ private static final String PROFILE_REQUIRES_PASSWORD = "REQUIRES_PASSWORD";
+ private static final String PROFILE_RECONNECT = "RECONNECT";
+ private static final String PROFILE_DISCONNECT = "DISCONNECT";
+ private static final String DIALOG_TAG = "Dialog";
+
+ private Bundle mProfileInfo;
+ private boolean mWaitingForResult;
+ private VpnStateService mService;
+ private final ServiceConnection mServiceConnection = new ServiceConnection()
+ {
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ mService = ((VpnStateService.LocalBinder)service).getService();
+ handleIntent();
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null)
+ {
+ mWaitingForResult = savedInstanceState.getBoolean(WAITING_FOR_RESULT, false);
+ }
+ this.bindService(new Intent(this, VpnStateService.class),
+ mServiceConnection, Service.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(WAITING_FOR_RESULT, mWaitingForResult);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ if (mService != null)
+ {
+ this.unbindService(mServiceConnection);
+ }
+ }
+
+ /**
+ * Due to launchMode=singleTop this is called if the Activity already exists
+ */
+ @Override
+ protected void onNewIntent(Intent intent)
+ {
+ super.onNewIntent(intent);
+
+ /* store this intent in case the service is not yet connected or the activity is restarted */
+ setIntent(intent);
+
+ if (mService != null)
+ {
+ handleIntent();
+ }
+ }
+
+ /**
+ * Prepare the VpnService. If this succeeds the current VPN profile is
+ * started.
+ *
+ * @param profileInfo a bundle containing the information about the profile to be started
+ */
+ protected void prepareVpnService(Bundle profileInfo)
+ {
+ Intent intent;
+
+ if (mWaitingForResult)
+ {
+ mProfileInfo = profileInfo;
+ return;
+ }
+
+ try
+ {
+ intent = VpnService.prepare(this);
+ }
+ catch (IllegalStateException ex)
+ {
+ /* this happens if the always-on VPN feature (Android 4.2+) is activated */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
+ return;
+ }
+ catch (NullPointerException ex)
+ {
+ /* not sure when this happens exactly, but apparently it does */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
+ return;
+ }
+ /* store profile info until the user grants us permission */
+ mProfileInfo = profileInfo;
+ if (intent != null)
+ {
+ try
+ {
+ mWaitingForResult = true;
+ startActivityForResult(intent, PREPARE_VPN_SERVICE);
+ }
+ catch (ActivityNotFoundException ex)
+ {
+ /* it seems some devices, even though they come with Android 4,
+ * don't have the VPN components built into the system image.
+ * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
+ * will not be found then */
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
+ mWaitingForResult = false;
+ }
+ }
+ else
+ { /* user already granted permission to use VpnService */
+ onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ switch (requestCode)
+ {
+ case PREPARE_VPN_SERVICE:
+ mWaitingForResult = false;
+ if (resultCode == RESULT_OK && mProfileInfo != null)
+ {
+ if (mService != null)
+ {
+ mService.connect(mProfileInfo, true);
+ }
+ finish();
+ }
+ else
+ { /* this happens if the always-on VPN feature is activated by a different app or the user declined */
+ if (getSupportFragmentManager().isStateSaved())
+ { /* onActivityResult() might be called when we aren't active anymore e.g. if the
+ * user pressed the home button, if the activity is started again we land here
+ * before onNewIntent() is called */
+ return;
+ }
+ VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
+ }
+ break;
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ /**
+ * Check if we are currently connected to a VPN connection
+ *
+ * @return true if currently connected
+ */
+ private boolean isConnected()
+ {
+ if (mService == null)
+ {
+ return false;
+ }
+ if (mService.getErrorState() != VpnStateService.ErrorState.NO_ERROR)
+ { /* allow reconnecting (even to a different profile) without confirmation if there is an error */
+ return false;
+ }
+ return (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING);
+ }
+
+ /**
+ * Start the given VPN profile
+ *
+ * @param profile VPN profile
+ */
+ public void startVpnProfile(VpnProfile profile)
+ {
+ Bundle profileInfo = new Bundle();
+ profileInfo.putString(VpnProfileDataSource.KEY_UUID, profile.getUUID().toString());
+ profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
+ profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
+ profileInfo.putString(PROFILE_NAME, profile.getName());
+
+ removeFragmentByTag(DIALOG_TAG);
+
+ if (isConnected())
+ {
+ profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getUUID().equals(profile.getUUID()));
+
+ ConfirmationDialog dialog = new ConfirmationDialog();
+ dialog.setArguments(profileInfo);
+ dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
+ return;
+ }
+ startVpnProfile(profileInfo);
+ }
+
+ /**
+ * Start the given VPN profile asking the user for a password if required.
+ *
+ * @param profileInfo data about the profile
+ */
+ private void startVpnProfile(Bundle profileInfo)
+ {
+ if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
+ profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
+ {
+ LoginDialog login = new LoginDialog();
+ login.setArguments(profileInfo);
+ login.show(getSupportFragmentManager(), DIALOG_TAG);
+ return;
+ }
+ prepareVpnService(profileInfo);
+ }
+
+ /**
+ * Start the VPN profile referred to by the given intent. Displays an error
+ * if the profile doesn't exist.
+ *
+ * @param intent Intent that caused us to start this
+ */
+ private void startVpnProfile(Intent intent)
+ {
+ VpnProfile profile = null;
+
+ VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
+ dataSource.open();
+ String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
+ if (profileUUID != null)
+ {
+ profile = dataSource.getVpnProfile(profileUUID);
+ }
+ else
+ {
+ long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
+ if (profileId > 0)
+ {
+ profile = dataSource.getVpnProfile(profileId);
+ }
+ }
+ dataSource.close();
+
+ if (profile != null)
+ {
+ startVpnProfile(profile);
+ }
+ else
+ {
+ Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ /**
+ * Disconnect the current connection, if any (silently ignored if there is no connection).
+ *
+ * @param intent Intent that caused us to start this
+ */
+ private void disconnect(Intent intent)
+ {
+ VpnProfile profile = null;
+
+ removeFragmentByTag(DIALOG_TAG);
+
+ String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
+ if (profileUUID != null)
+ {
+ VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
+ dataSource.open();
+ profile = dataSource.getVpnProfile(profileUUID);
+ dataSource.close();
+ }
+
+ if (mService != null)
+ {
+ if (mService.getState() == State.CONNECTED ||
+ mService.getState() == State.CONNECTING)
+ {
+ if (profile != null && profile.equals(mService.getProfile()))
+ { /* allow explicit termination without confirmation */
+ mService.disconnect();
+ finish();
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putBoolean(PROFILE_DISCONNECT, true);
+
+ ConfirmationDialog dialog = new ConfirmationDialog();
+ dialog.setArguments(args);
+ dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
+ }
+ else
+ {
+ finish();
+ }
+ }
+ }
+
+ /**
+ * Handle the Intent of this Activity depending on its action
+ */
+ private void handleIntent()
+ {
+ Intent intent = getIntent();
+
+ if (START_PROFILE.equals(intent.getAction()))
+ {
+ startVpnProfile(intent);
+ }
+ else if (DISCONNECT.equals(intent.getAction()))
+ {
+ disconnect(intent);
+ }
+ }
+
+ /**
+ * Dismiss dialog if shown
+ */
+ public void removeFragmentByTag(String tag)
+ {
+ FragmentManager fm = getSupportFragmentManager();
+ Fragment login = fm.findFragmentByTag(tag);
+ if (login != null)
+ {
+ FragmentTransaction ft = fm.beginTransaction();
+ ft.remove(login);
+ ft.commit();
+ }
+ }
+
+ /**
+ * Class that displays a confirmation dialog if a VPN profile is already connected
+ * and then initiates the selected VPN profile if the user confirms the dialog.
+ */
+ public static class ConfirmationDialog extends AppCompatDialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle profileInfo = getArguments();
+ int icon = android.R.drawable.ic_dialog_alert;
+ int title = R.string.connect_profile_question;
+ int message = R.string.replaces_active_connection;
+ int button = R.string.connect;
+
+ if (profileInfo.getBoolean(PROFILE_RECONNECT))
+ {
+ icon = android.R.drawable.ic_dialog_info;
+ title = R.string.vpn_connected;
+ message = R.string.vpn_profile_connected;
+ button = R.string.reconnect;
+ }
+ else if (profileInfo.getBoolean(PROFILE_DISCONNECT))
+ {
+ title = R.string.disconnect_question;
+ message = R.string.disconnect_active_connection;
+ button = R.string.disconnect;
+ }
+
+ DialogInterface.OnClickListener connectListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ activity.startVpnProfile(profileInfo);
+ }
+ };
+ DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ if (activity.mService != null)
+ {
+ activity.mService.disconnect();
+ }
+ activity.finish();
+ }
+ };
+ DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ getActivity().finish();
+ }
+ };
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setIcon(icon)
+ .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME)))
+ .setMessage(message);
+
+ if (profileInfo.getBoolean(PROFILE_DISCONNECT))
+ {
+ builder.setPositiveButton(button, disconnectListener);
+ }
+ else
+ {
+ builder.setPositiveButton(button, connectListener);
+ }
+
+ if (profileInfo.getBoolean(PROFILE_RECONNECT))
+ {
+ builder.setNegativeButton(R.string.disconnect, disconnectListener);
+ builder.setNeutralButton(android.R.string.cancel, cancelListener);
+ }
+ else
+ {
+ builder.setNegativeButton(android.R.string.cancel, cancelListener);
+ }
+ return builder.create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+
+ /**
+ * Class that displays a login dialog and initiates the selected VPN
+ * profile if the user confirms the dialog.
+ */
+ public static class LoginDialog extends AppCompatDialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle profileInfo = getArguments();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.login_dialog, null);
+ EditText username = (EditText)view.findViewById(R.id.username);
+ username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
+ final EditText password = (EditText)view.findViewById(R.id.password);
+
+ AlertDialog.Builder adb = new AlertDialog.Builder(getActivity());
+ adb.setView(view);
+ adb.setTitle(getString(R.string.login_title));
+ adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton)
+ {
+ VpnProfileControlActivity activity = (VpnProfileControlActivity)getActivity();
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
+ activity.prepareVpnService(profileInfo);
+ }
+ });
+ adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ getActivity().finish();
+ }
+ });
+ return adb.create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+
+ /**
+ * Class representing an error message which is displayed if VpnService is
+ * not supported on the current device.
+ */
+ public static class VpnNotSupportedError extends AppCompatDialogFragment
+ {
+ static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
+
+ public static void showWithMessage(AppCompatActivity activity, int messageId)
+ {
+ Bundle bundle = new Bundle();
+ bundle.putInt(ERROR_MESSAGE_ID, messageId);
+ VpnNotSupportedError dialog = new VpnNotSupportedError();
+ dialog.setArguments(bundle);
+ dialog.show(activity.getSupportFragmentManager(), DIALOG_TAG);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle arguments = getArguments();
+ final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.vpn_not_supported_title)
+ .setMessage(messageId)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int id)
+ {
+ getActivity().finish();
+ }
+ }).create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 37c5b3357..75618b564 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -118,6 +118,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private EditText mPort;
private TextInputLayoutHelper mPortWrap;
private Switch mCertReq;
+ private Switch mUseCrl;
+ private Switch mUseOcsp;
+ private Switch mStrictRevocation;
+ private Switch mRsaPss;
private EditText mNATKeepalive;
private TextInputLayoutHelper mNATKeepaliveWrap;
private EditText mIncludedSubnets;
@@ -132,6 +136,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private EditText mIkeProposal;
private TextInputLayoutHelper mEspProposalWrap;
private EditText mEspProposal;
+ private TextView mProfileIdLabel;
+ private TextView mProfileId;
@Override
public void onCreate(Bundle savedInstanceState)
@@ -177,6 +183,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive);
mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap);
mCertReq = (Switch)findViewById(R.id.cert_req);
+ mUseCrl = findViewById(R.id.use_crl);
+ mUseOcsp = findViewById(R.id.use_ocsp);
+ mStrictRevocation= findViewById(R.id.strict_revocation);
+ mRsaPss= findViewById(R.id.rsa_pss);
mIncludedSubnets = (EditText)findViewById(R.id.included_subnets);
mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap);
mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
@@ -194,6 +204,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity
/* make the link clickable */
((TextView)findViewById(R.id.proposal_intro)).setMovementMethod(LinkMovementMethod.getInstance());
+ mProfileIdLabel = (TextView)findViewById(R.id.profile_id_label);
+ mProfileId = (TextView)findViewById(R.id.profile_id);
+
final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
mName.setTokenizer(spaceTokenizer);
mRemoteId.setTokenizer(spaceTokenizer);
@@ -564,6 +577,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
+
+ if (show && mProfile == null)
+ {
+ mProfileIdLabel.setVisibility(View.GONE);
+ mProfileId.setVisibility(View.GONE);
+ }
}
/**
@@ -699,6 +718,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mProfile.setNATKeepAlive(getInteger(mNATKeepalive));
int flags = 0;
flags |= !mCertReq.isChecked() ? VpnProfile.FLAGS_SUPPRESS_CERT_REQS : 0;
+ flags |= !mUseCrl.isChecked() ? VpnProfile.FLAGS_DISABLE_CRL : 0;
+ flags |= !mUseOcsp.isChecked() ? VpnProfile.FLAGS_DISABLE_OCSP : 0;
+ flags |= mStrictRevocation.isChecked() ? VpnProfile.FLAGS_STRICT_REVOCATION : 0;
+ flags |= mRsaPss.isChecked() ? VpnProfile.FLAGS_RSA_PSS : 0;
mProfile.setFlags(flags);
String included = mIncludedSubnets.getText().toString().trim();
mProfile.setIncludedSubnets(included.isEmpty() ? null : included);
@@ -749,6 +772,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mSelectedApps = mProfile.getSelectedAppsSet();
mIkeProposal.setText(mProfile.getIkeProposal());
mEspProposal.setText(mProfile.getEspProposal());
+ mProfileId.setText(mProfile.getUUID().toString());
flags = mProfile.getFlags();
useralias = mProfile.getUserCertificateAlias();
local_id = mProfile.getLocalId();
@@ -765,6 +789,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mSelectVpnType.setSelection(mVpnType.ordinal());
mCertReq.setChecked(flags == null || (flags & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
+ mUseCrl.setChecked(flags == null || (flags & VpnProfile.FLAGS_DISABLE_CRL) == 0);
+ mUseOcsp.setChecked(flags == null || (flags & VpnProfile.FLAGS_DISABLE_OCSP) == 0);
+ mStrictRevocation.setChecked(flags != null && (flags & VpnProfile.FLAGS_STRICT_REVOCATION) != 0);
+ mRsaPss.setChecked(flags != null && (flags & VpnProfile.FLAGS_RSA_PSS) != 0);
/* check if the user selected a user certificate previously */
useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
index 43c0035cf..1d8b677ee 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016-2017 Tobias Brunner
+ * Copyright (C) 2016-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -17,11 +17,10 @@ package org.strongswan.android.ui;
import android.app.Activity;
import android.app.LoaderManager;
-import android.app.ProgressDialog;
+import android.content.ActivityNotFoundException;
import android.content.AsyncTaskLoader;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.net.Uri;
@@ -66,7 +65,6 @@ import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.OutOfMemoryError;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyStore;
@@ -96,7 +94,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
private TrustedCertificateEntry mUserCertEntry;
private String mUserCertLoading;
private boolean mHideImport;
- private ProgressDialog mProgress;
+ private android.support.v4.widget.ContentLoadingProgressBar mProgressBar;
private TextView mExistsWarning;
private ViewGroup mBasicDataGroup;
private TextView mName;
@@ -167,6 +165,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
setContentView(R.layout.profile_import_view);
+ mProgressBar = findViewById(R.id.progress_bar);
mExistsWarning = (TextView)findViewById(R.id.exists_warning);
mBasicDataGroup = (ViewGroup)findViewById(R.id.basic_data_group);
mName = (TextView)findViewById(R.id.name);
@@ -213,7 +212,15 @@ public class VpnProfileImportActivity extends AppCompatActivity
{
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openIntent.setType("*/*");
- startActivityForResult(openIntent, OPEN_DOCUMENT);
+ try
+ {
+ startActivityForResult(openIntent, OPEN_DOCUMENT);
+ }
+ catch (ActivityNotFoundException e)
+ { /* some devices are unable to browse for files */
+ finish();
+ return;
+ }
}
if (savedInstanceState != null)
@@ -300,14 +307,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
private void loadProfile(Uri uri)
{
- mProgress = ProgressDialog.show(this, null, getString(R.string.loading),
- true, true, new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog)
- {
- finish();
- }
- });
+ mProgressBar.show();
Bundle args = new Bundle();
args.putParcelable(PROFILE_URI, uri);
@@ -316,7 +316,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
public void handleProfile(ProfileLoadResult data)
{
- mProgress.dismiss();
+ mProgressBar.hide();
mProfile = null;
if (data != null && data.ThrownException == null)
@@ -478,11 +478,28 @@ public class VpnProfileImportActivity extends AppCompatActivity
profile.setRemoteId(remote.optString("id", null));
profile.Certificate = decodeBase64(remote.optString("cert", null));
- if (remote.optBoolean("certreq", false))
+ if (!remote.optBoolean("certreq", true))
{
flags |= VpnProfile.FLAGS_SUPPRESS_CERT_REQS;
}
+ JSONObject revocation = remote.optJSONObject("revocation");
+ if (revocation != null)
+ {
+ if (!revocation.optBoolean("crl", true))
+ {
+ flags |= VpnProfile.FLAGS_DISABLE_CRL;
+ }
+ if (!revocation.optBoolean("ocsp", true))
+ {
+ flags |= VpnProfile.FLAGS_DISABLE_OCSP;
+ }
+ if (revocation.optBoolean("strict", false))
+ {
+ flags |= VpnProfile.FLAGS_STRICT_REVOCATION;
+ }
+ }
+
JSONObject local = obj.optJSONObject("local");
if (local != null)
{
@@ -495,6 +512,11 @@ public class VpnProfileImportActivity extends AppCompatActivity
{
profile.setLocalId(local.optString("id", null));
profile.PKCS12 = decodeBase64(local.optString("p12", null));
+
+ if (local.optBoolean("rsa-pss", false))
+ {
+ flags |= VpnProfile.FLAGS_RSA_PSS;
+ }
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
index 93af5f147..fe3d01322 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -17,6 +17,9 @@ package org.strongswan.android.ui;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.content.pm.ShortcutInfoCompat;
+import android.support.v4.content.pm.ShortcutManagerCompat;
+import android.support.v4.graphics.drawable.IconCompat;
import android.support.v7.app.AppCompatActivity;
import org.strongswan.android.R;
@@ -39,14 +42,14 @@ public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpn
@Override
public void onVpnProfileSelected(VpnProfile profile)
{
- Intent shortcut = new Intent(MainActivity.START_PROFILE);
- shortcut.putExtra(MainActivity.EXTRA_VPN_PROFILE_ID, profile.getId());
+ Intent shortcut = new Intent(VpnProfileControlActivity.START_PROFILE);
+ shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName());
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher));
- setResult(RESULT_OK, intent);
+ ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(this, profile.getUUID().toString());
+ builder.setIntent(shortcut);
+ builder.setShortLabel(profile.getName());
+ builder.setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut));
+ setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, builder.build()));
finish();
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
index 0a8e1f342..edc70fa96 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2016 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@@ -20,19 +20,17 @@ package org.strongswan.android.ui;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
-import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -42,17 +40,10 @@ import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.logic.VpnStateService.ErrorState;
import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.logic.VpnStateService.VpnStateListener;
-import org.strongswan.android.logic.imc.ImcState;
-import org.strongswan.android.logic.imc.RemediationInstruction;
-
-import java.util.ArrayList;
-import java.util.List;
public class VpnStateFragment extends Fragment implements VpnStateListener
{
- private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id";
- private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id";
-
+ private boolean mVisible;
private TextView mProfileNameView;
private TextView mProfileView;
private TextView mStateView;
@@ -61,9 +52,10 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
private int mColorStateSuccess;
private Button mActionButton;
private ProgressBar mProgress;
- private AlertDialog mErrorDialog;
- private long mErrorConnectionID;
- private long mDismissedConnectionID;
+ private LinearLayout mErrorView;
+ private TextView mErrorText;
+ private Button mErrorRetry;
+ private Button mShowLog;
private VpnStateService mService;
private final ServiceConnection mServiceConnection = new ServiceConnection()
{
@@ -77,8 +69,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = ((VpnStateService.LocalBinder)service).getService();
- mService.registerListener(VpnStateFragment.this);
- updateView();
+ if (mVisible)
+ {
+ mService.registerListener(VpnStateFragment.this);
+ updateView();
+ }
}
};
@@ -94,23 +89,6 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
Context context = getActivity().getApplicationContext();
context.bindService(new Intent(context, VpnStateService.class),
mServiceConnection, Service.BIND_AUTO_CREATE);
-
- mErrorConnectionID = 0;
- mDismissedConnectionID = 0;
- if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID))
- {
- mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID);
- mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState)
- {
- super.onSaveInstanceState(outState);
-
- outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID);
- outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID);
}
@Override
@@ -120,25 +98,35 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
View view = inflater.inflate(R.layout.vpn_state_fragment, null);
mActionButton = (Button)view.findViewById(R.id.action);
- mActionButton.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
+ mActionButton.setOnClickListener(v -> {
+ if (mService != null)
{
- if (mService != null)
- {
- mService.disconnect();
- }
+ mService.disconnect();
}
});
enableActionButton(null);
+ mErrorView = view.findViewById(R.id.vpn_error);
+ mErrorText = view.findViewById(R.id.vpn_error_text);
+ mErrorRetry = view.findViewById(R.id.retry);
+ mShowLog = view.findViewById(R.id.show_log);
mProgress = (ProgressBar)view.findViewById(R.id.progress);
mStateView = (TextView)view.findViewById(R.id.vpn_state);
mColorStateBase = mStateView.getCurrentTextColor();
mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label);
mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name);
+ mErrorRetry.setOnClickListener(v -> {
+ if (mService != null)
+ {
+ mService.reconnect();
+ }
+ });
+ mShowLog.setOnClickListener(v -> {
+ Intent intent = new Intent(getActivity(), LogActivity.class);
+ startActivity(intent);
+ });
+
return view;
}
@@ -146,6 +134,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
public void onStart()
{
super.onStart();
+ mVisible = true;
if (mService != null)
{
mService.registerListener(this);
@@ -157,11 +146,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
public void onStop()
{
super.onStop();
+ mVisible = false;
if (mService != null)
{
mService.unregisterListener(this);
}
- hideErrorDialog();
}
@Override
@@ -186,7 +175,6 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
VpnProfile profile = mService.getProfile();
State state = mService.getState();
ErrorState error = mService.getErrorState();
- ImcState imcState = mService.getImcState();
String name = "";
if (getActivity() == null)
@@ -199,12 +187,13 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
name = profile.getName();
}
- if (reportError(connectionID, name, error, imcState))
+ if (reportError(connectionID, name, error))
{
return;
}
mProfileNameView.setText(name);
+ mProgress.setIndeterminate(true);
switch (state)
{
@@ -239,56 +228,36 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
}
}
- private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
+ private boolean reportError(long connectionID, String name, ErrorState error)
{
- if (connectionID > mDismissedConnectionID)
- { /* report error if it hasn't been dismissed yet */
- mErrorConnectionID = connectionID;
- }
- else
- { /* ignore all other errors */
- error = ErrorState.NO_ERROR;
- }
if (error == ErrorState.NO_ERROR)
{
- hideErrorDialog();
+ mErrorView.setVisibility(View.GONE);
return false;
}
- else if (mErrorDialog != null)
- { /* we already show the dialog */
- return true;
- }
mProfileNameView.setText(name);
showProfile(true);
- mProgress.setVisibility(View.GONE);
- enableActionButton(null);
mStateView.setText(R.string.state_error);
mStateView.setTextColor(mColorStateError);
- switch (error)
+ enableActionButton(getString(android.R.string.cancel));
+
+ int retry = mService.getRetryIn();
+ if (retry > 0)
{
- case AUTH_FAILED:
- if (imcState == ImcState.BLOCK)
- {
- showErrorDialog(R.string.error_assessment_failed);
- }
- else
- {
- showErrorDialog(R.string.error_auth_failed);
- }
- break;
- case PEER_AUTH_FAILED:
- showErrorDialog(R.string.error_peer_auth_failed);
- break;
- case LOOKUP_FAILED:
- showErrorDialog(R.string.error_lookup_failed);
- break;
- case UNREACHABLE:
- showErrorDialog(R.string.error_unreachable);
- break;
- default:
- showErrorDialog(R.string.error_generic);
- break;
+ mProgress.setIndeterminate(false);
+ mProgress.setMax(mService.getRetryTimeout());
+ mProgress.setProgress(retry);
+ mProgress.setVisibility(View.VISIBLE);
+ mStateView.setText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
}
+ else if (mService.getRetryTimeout() <= 0)
+ {
+ mProgress.setVisibility(View.GONE);
+ }
+
+ String text = getString(R.string.error_format, getString(mService.getErrorText()));
+ mErrorText.setText(text);
+ mErrorView.setVisibility(View.VISIBLE);
return true;
}
@@ -304,73 +273,4 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
mActionButton.setEnabled(text != null);
mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
}
-
- private void hideErrorDialog()
- {
- if (mErrorDialog != null)
- {
- mErrorDialog.dismiss();
- mErrorDialog = null;
- }
- }
-
- private void clearError()
- {
- if (mService != null)
- {
- mService.disconnect();
- }
- mDismissedConnectionID = mErrorConnectionID;
- updateView();
- }
-
- private void showErrorDialog(int textid)
- {
- final List instructions = mService.getRemediationInstructions();
- final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty();
- int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log;
-
- mErrorDialog = new AlertDialog.Builder(getActivity())
- .setMessage(getString(R.string.error_introduction) + " " + getString(textid))
- .setCancelable(false)
- .setNeutralButton(text, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- clearError();
- dialog.dismiss();
- Intent intent;
- if (show_instructions)
- {
- intent = new Intent(getActivity(), RemediationInstructionsActivity.class);
- intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS,
- new ArrayList(instructions));
- }
- else
- {
- intent = new Intent(getActivity(), LogActivity.class);
- }
- startActivity(intent);
- }
- })
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int id)
- {
- clearError();
- dialog.dismiss();
- }
- }).create();
- mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener()
- {
- @Override
- public void onDismiss(DialogInterface dialog)
- {
- mErrorDialog = null;
- }
- });
- mErrorDialog.show();
- }
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
new file mode 100644
index 000000000..6b5cca0eb
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 Tobias Brunner
+ * HSR 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.ui;
+
+import android.annotation.TargetApi;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType;
+import org.strongswan.android.logic.VpnStateService;
+import org.strongswan.android.utils.Constants;
+
+@TargetApi(Build.VERSION_CODES.N)
+public class VpnTileService extends TileService implements VpnStateService.VpnStateListener
+{
+ private boolean mListening;
+ private VpnProfileDataSource mDataSource;
+ private VpnStateService mService;
+ private final ServiceConnection mServiceConnection = new ServiceConnection()
+ {
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ mService = ((VpnStateService.LocalBinder)service).getService();
+ if (mListening)
+ {
+ mService.registerListener(VpnTileService.this);
+ updateTile();
+ }
+ }
+ };
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+
+ Context context = getApplicationContext();
+ context.bindService(new Intent(context, VpnStateService.class),
+ mServiceConnection, Service.BIND_AUTO_CREATE);
+
+ mDataSource = new VpnProfileDataSource(this);
+ mDataSource.open();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+ if (mService != null)
+ {
+ getApplicationContext().unbindService(mServiceConnection);
+ }
+ mDataSource.close();
+ }
+
+ @Override
+ public void onStartListening()
+ {
+ super.onStartListening();
+ mListening = true;
+ if (mService != null)
+ {
+ mService.registerListener(this);
+ updateTile();
+ }
+ }
+
+ @Override
+ public void onStopListening()
+ {
+ super.onStopListening();
+ mListening = false;
+ if (mService != null)
+ {
+ mService.unregisterListener(this);
+ }
+ }
+
+ private VpnProfile getProfile()
+ {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+ String uuid = pref.getString(Constants.PREF_DEFAULT_VPN_PROFILE, null);
+ if (uuid == null || uuid.equals(Constants.PREF_DEFAULT_VPN_PROFILE_MRU))
+ {
+ uuid = pref.getString(Constants.PREF_MRU_VPN_PROFILE, null);
+ }
+
+ return mDataSource.getVpnProfile(uuid);
+ }
+
+ @Override
+ public void onClick()
+ {
+ if (mService != null)
+ {
+ /* we operate on the current/most recently used profile, but fall back to configuration */
+ VpnProfile profile = mService.getProfile();
+ if (profile == null)
+ {
+ profile = getProfile();
+ }
+ else
+ { /* always get the plain profile without cached password */
+ profile = mDataSource.getVpnProfile(profile.getId());
+ }
+ /* reconnect the profile in case of an error */
+ if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR)
+ {
+ switch (mService.getState())
+ {
+ case CONNECTING:
+ case CONNECTED:
+ Runnable disconnect = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mService.disconnect();
+ }
+ };
+ if (isLocked())
+ {
+ unlockAndRun(disconnect);
+ }
+ else
+ {
+ disconnect.run();
+ }
+ return;
+ }
+ }
+ if (profile != null)
+ {
+ Intent intent = new Intent(this, VpnProfileControlActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setAction(VpnProfileControlActivity.START_PROFILE);
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ if (profile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS) &&
+ profile.getPassword() == null)
+ { /* the user will have to enter the password, so collapse the drawer */
+ startActivityAndCollapse(intent);
+ }
+ else
+ {
+ startActivity(intent);
+ }
+ return;
+ }
+ }
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityAndCollapse(intent);
+ }
+
+ @Override
+ public void stateChanged()
+ {
+ updateTile();
+ }
+
+ private void updateTile()
+ {
+ VpnProfile profile = mService.getProfile();
+ VpnStateService.State state = mService.getState();
+ VpnStateService.ErrorState error = mService.getErrorState();
+
+ /* same as above, only use the configured profile if we have no active profile */
+ if (profile == null)
+ {
+ profile = getProfile();
+ }
+
+ Tile tile = getQsTile();
+
+ if (error != VpnStateService.ErrorState.NO_ERROR)
+ {
+ tile.setState(Tile.STATE_INACTIVE);
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_warning));
+ tile.setLabel(getString(R.string.tile_connect));
+ }
+ else
+ {
+ switch (state)
+ {
+ case DISCONNECTING:
+ case DISABLED:
+ tile.setState(Tile.STATE_INACTIVE);
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_disconnected));
+ tile.setLabel(getString(R.string.tile_connect));
+ break;
+ case CONNECTING:
+ tile.setState(Tile.STATE_ACTIVE);
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_connecting));
+ tile.setLabel(getString(R.string.tile_disconnect));
+ break;
+ case CONNECTED:
+ tile.setState(Tile.STATE_ACTIVE);
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification));
+ tile.setLabel(getString(R.string.tile_disconnect));
+ break;
+ }
+ }
+ if (profile != null && !isSecure())
+ {
+ tile.setLabel(profile.getName());
+ }
+ tile.updateTile();
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java
index 16fdcdad1..52234933b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -43,4 +43,19 @@ public final class Constants
*/
public static final int NAT_KEEPALIVE_MAX = 120;
public static final int NAT_KEEPALIVE_MIN = 10;
+
+ /**
+ * Preference key for default VPN profile
+ */
+ public static final String PREF_DEFAULT_VPN_PROFILE = "pref_default_vpn_profile";
+
+ /**
+ * Value used to signify that the most recently used profile should be used as default
+ */
+ public static final String PREF_DEFAULT_VPN_PROFILE_MRU = "pref_default_vpn_profile_mru";
+
+ /**
+ * Preference key to store the most recently used VPN profile
+ */
+ public static final String PREF_MRU_VPN_PROFILE = "pref_mru_vpn_profile";
}
diff --git a/src/frontends/android/app/src/main/jni/Android.mk b/src/frontends/android/app/src/main/jni/Android.mk
index a46139421..2a04b789a 100644
--- a/src/frontends/android/app/src/main/jni/Android.mk
+++ b/src/frontends/android/app/src/main/jni/Android.mk
@@ -7,10 +7,10 @@ strongswan_USE_BYOD := true
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
chapoly curve25519 pkcs1 pkcs8 pem xcbc hmac socket-default revocation \
- eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls x509
+ eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls eap-ttls eap-peap x509
ifneq ($(strongswan_USE_BYOD),)
-strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20
+strongswan_BYOD_PLUGINS := eap-tnc tnc-imc tnc-tnccs tnccs-20
endif
strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \
@@ -35,6 +35,7 @@ strongswan_CFLAGS := \
-Wno-strict-aliasing \
-Wno-unused-parameter \
-Wno-missing-field-initializers \
+ -Wno-self-assign \
-DHAVE___BOOL \
-DHAVE_STDBOOL_H \
-DHAVE_ALLOCA_H \
diff --git a/src/frontends/android/app/src/main/jni/Application.mk b/src/frontends/android/app/src/main/jni/Application.mk
index 25b1aa2bd..00a46d70e 100644
--- a/src/frontends/android/app/src/main/jni/Application.mk
+++ b/src/frontends/android/app/src/main/jni/Application.mk
@@ -1,3 +1 @@
-# select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs).
-APP_ABI := armeabi arm64-v8a x86 x86_64 mips mips64
APP_PLATFORM := android-19
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h
index aab12c412..918c14c5e 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h
@@ -57,6 +57,7 @@ typedef enum {
ANDROID_JELLY_BEAN = 16,
ANDROID_JELLY_BEAN_MR1 = 17,
ANDROID_JELLY_BEAN_MR2 = 18,
+ ANDROID_LOLLIPOP = 21,
} android_sdk_version_t;
/**
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
index b1a095bcd..a1a51b7b5 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2017 Tobias Brunner
+ * Copyright (C) 2010-2018 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@@ -18,6 +18,7 @@
#include
#include
+#include "android_jni.h"
#include "android_service.h"
#include "android_dns_proxy.h"
#include "../charonservice.h"
@@ -761,6 +762,11 @@ static job_requeue_t initiate(private_android_service_t *this)
int port;
bool certreq;
+ if (android_sdk_version >= ANDROID_LOLLIPOP)
+ { /* only try once and notify the GUI on Android 5+ where we have a blocking TUN device */
+ peer.keyingtries = 1;
+ }
+
server = this->settings->get_str(this->settings, "connection.server", NULL);
port = this->settings->get_int(this->settings, "connection.port",
IKEV2_UDP_PORT);
@@ -794,7 +800,7 @@ static job_requeue_t initiate(private_android_service_t *this)
{
peer_cfg->destroy(peer_cfg);
charonservice->update_status(charonservice,
- CHARONSERVICE_GENERIC_ERROR);
+ CHARONSERVICE_CERTIFICATE_UNAVAILABLE);
return JOB_REQUEUE_NONE;
}
}
@@ -822,6 +828,10 @@ static job_requeue_t initiate(private_android_service_t *this)
}
auth->add(auth, AUTH_RULE_IDENTITY, gateway);
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
+ if (this->settings->get_bool(this->settings, "connection.strict_revocation", FALSE))
+ {
+ auth->add(auth, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD);
+ }
peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
child_cfg = child_cfg_create("android", &child);
@@ -834,24 +844,16 @@ static job_requeue_t initiate(private_android_service_t *this)
{ /* create ESP proposals with and without DH groups, let responder decide
* if PFS is used */
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128gcm16-aes256gcm16-chacha20poly1305-"
- "curve25519-ecp256-modp3072"));
+ "aes256gcm16-aes128gcm16-chacha20poly1305-"
+ "curve25519-ecp384-ecp521-modp3072-modp4096-ecp256-modp8192"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128-sha256-curve25519-ecp256-modp3072"));
+ "aes256-aes192-aes128-sha384-sha256-sha512-sha1-"
+ "curve25519-ecp384-ecp521-modp3072-modp4096-ecp256-modp2048-"
+ "modp8192"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes256-sha384-ecp521-modp8192"));
+ "aes256gcm16-aes128gcm16-chacha20poly1305"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128-aes192-aes256-sha1-sha256-sha384-sha512-"
- "curve25519-ecp256-ecp384-ecp521-"
- "modp2048-modp3072-modp4096"));
- child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128gcm16-aes256gcm16-chacha20poly1305"));
- child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128-sha256"));
- child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes256-sha384"));
- child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
- "aes128-aes192-aes256-sha1-sha256-sha384-sha512"));
+ "aes256-aes192-aes128-sha384-sha256-sha512-sha1"));
}
ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535);
child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
index 06c5cada4..c8c300a8a 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2017 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@@ -412,6 +412,15 @@ static void initiate(settings_t *settings)
lib->settings->set_str(lib->settings,
"charon.plugins.tnc-imc.preferred_language",
settings->get_str(settings, "global.language", "en"));
+ lib->settings->set_bool(lib->settings,
+ "charon.plugins.revocation.enable_crl",
+ settings->get_bool(settings, "global.crl", TRUE));
+ lib->settings->set_bool(lib->settings,
+ "charon.plugins.revocation.enable_ocsp",
+ settings->get_bool(settings, "global.ocsp", TRUE));
+ lib->settings->set_bool(lib->settings,
+ "charon.rsa_pss",
+ settings->get_bool(settings, "global.rsa_pss", FALSE));
/* 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
@@ -426,6 +435,9 @@ static void initiate(settings_t *settings)
settings->get_int(settings, "global.nat_keepalive",
ANDROID_KEEPALIVE_INTERVAL));
+ /* reload plugins after changing settings */
+ lib->plugins->reload(lib->plugins, NULL);
+
this->creds->clear(this->creds);
DESTROY_IF(this->service);
this->service = android_service_create(this->creds, settings);
@@ -469,7 +481,7 @@ static void set_options(char *logfile)
lib->settings->set_str(lib->settings,
"charon.filelog.%s.time_format", "%b %e %T", logfile);
lib->settings->set_bool(lib->settings,
- "charon.filelog.%s.append", FALSE, logfile);
+ "charon.filelog.%s.append", TRUE, logfile);
lib->settings->set_bool(lib->settings,
"charon.filelog.%s.flush_line", TRUE, logfile);
lib->settings->set_int(lib->settings,
@@ -621,6 +633,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean,
/* set options before initializing other libraries that might read them */
logfile = androidjni_convert_jstring(env, jlogfile);
+
set_options(logfile);
free(logfile);
@@ -649,7 +662,8 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean,
{
memset(&utsname, 0, sizeof(utsname));
}
- DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s, %s, "
+ DBG1(DBG_DMN, "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+");
+ DBG1(DBG_DMN, "Starting IKE service (strongSwan "VERSION", %s, %s, "
"%s %s, %s)", android_version_string, android_device_string,
utsname.sysname, utsname.release, utsname.machine);
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h
index fa2fb42b2..12353777b 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h
@@ -59,6 +59,7 @@ enum android_vpn_state_t {
CHARONSERVICE_PEER_AUTH_ERROR,
CHARONSERVICE_LOOKUP_ERROR,
CHARONSERVICE_UNREACHABLE_ERROR,
+ CHARONSERVICE_CERTIFICATE_UNAVAILABLE,
CHARONSERVICE_GENERIC_ERROR,
};
diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_connecting.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_connecting.png
new file mode 100755
index 000000000..6ae76a1dc
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_connecting.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png
new file mode 100755
index 000000000..039877b01
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_connecting.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_connecting.png
new file mode 100755
index 000000000..b0791310f
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_connecting.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png
new file mode 100755
index 000000000..1adedbe10
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_connecting.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_connecting.png
new file mode 100755
index 000000000..d3206fede
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_connecting.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png
new file mode 100755
index 000000000..e5ddb244e
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable/error_background.xml b/src/frontends/android/app/src/main/res/drawable/error_background.xml
new file mode 100644
index 000000000..9f827573b
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/drawable/error_background.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml
index 43219cf3f..ef5a63d87 100644
--- a/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml
+++ b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml
@@ -16,7 +16,6 @@
diff --git a/src/frontends/android/app/src/main/res/layout/log_fragment.xml b/src/frontends/android/app/src/main/res/layout/log_fragment.xml
index 6d6abfe16..a51ad4d79 100644
--- a/src/frontends/android/app/src/main/res/layout/log_fragment.xml
+++ b/src/frontends/android/app/src/main/res/layout/log_fragment.xml
@@ -1,8 +1,6 @@
+
diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
index d4d88314d..49a918f12 100644
--- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
+++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
@@ -1,6 +1,6 @@
+
+
+
+
diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut.xml b/src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut.xml
new file mode 100644
index 000000000..6aea2ffbf
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 000000000..653d08006
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml b/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml
new file mode 100644
index 000000000..653d08006
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_bg.png b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_bg.png
new file mode 100644
index 000000000..df827eef2
Binary files /dev/null and b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_bg.png differ
diff --git a/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_fg.png b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_fg.png
new file mode 100644
index 000000000..3d0c9bfab
Binary files /dev/null and b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_fg.png differ
diff --git a/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_bg.png b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_bg.png
new file mode 100644
index 000000000..76532253c
Binary files /dev/null and b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_bg.png differ
diff --git a/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_fg.png b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_fg.png
new file mode 100644
index 000000000..6e62e6d69
Binary files /dev/null and b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_fg.png differ
diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml
index 49a0ab411..ee9bbcb5f 100644
--- a/src/frontends/android/app/src/main/res/values-de/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-de/strings.xml
@@ -1,6 +1,6 @@
+ Einstellungen
+ Voreingestelltes VPN Profil
+ Verbinden mit zuletzt verwendetem Profil
Log
@@ -73,8 +81,8 @@
Erweiterte Einstellungen
Erweiterte Einstellungen anzeigen
Server-Identität
- Standardwert ist der konfigurierte Server. Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen
- Standardwert ist \"%1$s\". Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen
+ Standardwert ist der konfigurierte Server. Eigene Werte werden explizit an den Server gesendet und während der Authentifizierung erzwungen
+ Standardwert ist \"%1$s\". Eigene Werte werden explizit an den Server gesendet und während der Authentifizierung erzwungen
MTU des VPN Tunnel-Device
Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist
Server Port
@@ -83,6 +91,14 @@
Kleine Pakete werden gesendet, um Mappings auf NAT-Routern am Leben zu erhalten, wenn sonst nichts gesendet wird. Um Energie zu sparen, ist das Standardintervall auf 45 Sekunden gesetzt. Hinter NAT-Routern die Mappings früh entfernen, ist dies möglicherweise zu hoch. 20 Sekunden oder weniger können in diesem Fall helfen.
Zertifikatsanforderungen senden
Zertifikatsanforderungen werden für alle oder ausgewählte CA-Zertifikate gesendet. Um die Grösse der IKE_AUTH Nachricht zu reduzieren, kann dies deaktiviert werden. Allerdings funktioniert dies nur, falls der Server sein Zertifikat auch sendet, wenn er zuvor keine Zertifikatsanforderungen erhalten hat.
+ OCSP zur Zertifikatsprüfung verwenden
+ Prüft mit dem Online Certificate Status Protocol (OCSP), sofern verfügbar, ob das Server-Zertifikat gesperrt wurde.
+ CRLs zur Zertifikatsprüfung verwenden
+ Verwendet Zertifikatssperrlisten (CRL), sofern verfügbar, um zu prüfen, ob das Server-Zertifikat gesperrt wurde. CRLs werden nur verwendet, wenn OCSP kein Resultat liefert.
+ Strikte Zertifikatsprüfung verwenden
+ Im strikten Modus schlägt die Authentisierung nicht nur dann fehl, wenn das Server-Zertifikat gesperrt wurde, sondern auch wenn der Status des Zertifikats unbekannt ist (z.B. weil OCSP fehl schlug und keine gültige CRL verfügbar war).
+ RSA/PSS Signaturen verwenden
+ Verwendet das stärkere PSS Encoding anstatt des klassischen PKCS#1 Encoding für RSA Signaturen. Die Authentisierung wird fehlschlagen, wenn der Server solche Signaturen nicht unterstützt.
Split-Tunneling
Standardmässig leitet der Client allen Netzwerkverkehr durch den VPN Tunnel, ausser der Server schränkt die Subnetze beim Verbindungsaufbau ein, in welchem Fall nur der Verkehr via VPN geleitet wird, den der Server erlaubt (der Rest wird standardmässig behandelt, als ob kein VPN vorhanden wäre).
Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist
@@ -102,7 +118,7 @@
Für non-AEAD/klassische Verschlüsselungsalgorithmen wird ein Integritätsalgorithmus, eine pseudozufällige Funktion (PRF, optional, ansonsten wird eine auf dem Integritätsalgorithmus basierende verwendet) und eine Diffie-Hellman Gruppe benötigt (z.B. aes256-sha256-ecp256). Für kombinierte/AEAD Algorithmen wird der Integritätsalgorithmus weggelassen aber eine PRF wird benötigt (z.B. aes256gcm16-prfsha256-ecp256).
IPsec/ESP Algorithmen
Für non-AEAD/klassische Verschlüsselungsalgorithmen wird ein Integritätsalgorithmus benötigt, eine Diffie-Hellman Gruppe ist optional (z.B. aes256-sha256 oder aes256-sha256-ecp256). Für kombinierte/AEAD Algorithmen wird der Integritätsalgorithmus weggelassen (z.B. aes256gcm16 oder aes256gcm16-ecp256). Falls eine DH Gruppe angegeben wird, kommt während dem IPsec SA Rekeying ein DH Schlüsselaustausch zur Anwendung. Beim initialen Verbindungsaufbau hat eine DH Gruppe hier keinen Einfluss, weil die Schlüssel dort von der IKE SA abgeleitet werden. Deshalb wird eine Fehlkonfiguration mit dem Server erst später während dem Rekeying zu einem Fehler führen.
- VPN Profile importieren
+ VPN Profil importieren
VPN Profil-Import fehlgeschlagen
VPN Profil-Import fehlgeschlagen: %1$s
Datei nicht gefunden
@@ -112,6 +128,7 @@
Dieses VPN Profil existiert bereits, die bestehenden Einstellungen werden ersetzt.
Zertifikat aus VPN Profil importieren
Zertifikat für \"%1$s\"
+ Profil-ID
Ein Wert wird benötigt, um die Verbindung aufbauen zu können
Bitte geben Sie Ihren Benutzernamen ein
@@ -154,6 +171,7 @@
Trennen…
Kein aktives Profil
Fehler
+ Ausblenden
Assessment:
@@ -169,13 +187,15 @@
Benutzername
Passwort
Verbinden
- Fehler beim Aufsetzen des VPN:
- Server-Adresse konnte nicht aufgelöst werden.
- Server ist nicht erreichbar.
- Authentifizierung des Servers ist fehlgeschlagen.
- Benutzerauthentifizierung ist fehlgeschlagen.
- Sicherheitsassessment ist fehlgeschlagen.
- Unbekannter Fehler während des Verbindens.
+ Fehler beim Aufsetzen des VPN: %1$s.
+ Server-Adresse konnte nicht aufgelöst werden
+ Server ist nicht erreichbar
+ Authentifizierung des Servers ist fehlgeschlagen
+ Benutzerauthentifizierung ist fehlgeschlagen
+ Sicherheitsassessment ist fehlgeschlagen
+ Unbekannter Fehler während des Verbindens
+ Passwort nicht verfügbar
+ Benutzer-Zertifikat nicht verfügbar
VPN verbunden
Dieses VPN Profil ist momentan verbunden!
Neu verbinden
@@ -184,5 +204,16 @@
VPN Verbindung trennen?
Dies trennt die aktuelle VPN Verbindung!
Verbinden
+ Wiederholen
+
+ - Wiederholen in %1$d Sekunde
+ - Wiederholen in %1$d Sekunden
+
+ Wiederholen abbrechen
+
+
+ VPN umschalten
+ VPN verbinden
+ VPN trennen
diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml
index 61fd3f09e..6297ceca1 100644
--- a/src/frontends/android/app/src/main/res/values-pl/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml
@@ -24,10 +24,18 @@
Szukaj
Nie obsługiwany VPN
Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem.
- Polączenia nie sa możliwe w trybie zamkniętym
+ VPN connections are not supported if a built-in VPN has the always-on feature enabled.
+ Unable to get permission to create VPN connections. Either because it was denied by the user, or because a different VPN app has the always-on feature enabled.
Wczytywanie…
Nie znaleziono profilu
Skrót strongSwan
+ VPN connection state
+ Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.
+
+
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
Log
@@ -83,6 +91,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
Split tunneling
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
Block IPv4 traffic not destined for the VPN
@@ -112,6 +128,7 @@
This VPN profile already exists, its current settings will be replaced.
Import certificate from VPN profile
Certificate for \"%1$s\"
+ Profile ID
A value is required to initiate the connection
Wprowadź swoją nazwę użytkownika
@@ -154,6 +171,7 @@
Przerywam połączenie…
Brak aktywnego VPN
Błąd
+ Dismiss
Assessment:
@@ -169,13 +187,15 @@
Użytkownik
Hasło
Połącz
- Nie udało się utworzyć tunelu VPN:
+ Nie udało się utworzyć tunelu VPN: %1$s.
Nie znaleziono adresu serwer
Serwer jest nieosiągalna
Błąd przy weryfikacji serwer
Błąd przy autoryzacji użytkownika
- Security assessment failed.
+ Security assessment failed
Nieznany błąd w czasie połączenia
+ Password unavailable
+ Client certificate unavailable
Połączenie z VPN
Ten profil VPN jest obecnie połaczony!
Połączyć ponownie
@@ -184,5 +204,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
Połącz
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml
index a2b3ada45..b26ca1e8c 100644
--- a/src/frontends/android/app/src/main/res/values-ru/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml
@@ -21,10 +21,18 @@
Поиск
VPN не поддерживается
Ваше устройство не поддерживат VPN приложение.\nПожалуйста свяжитесь с производителем.
- VPN соединения не поддерживаются в режиме lockdown.
+ VPN connections are not supported if a built-in VPN has the always-on feature enabled.
+ Unable to get permission to create VPN connections. Either because it was denied by the user, or because a different VPN app has the always-on feature enabled.
Загрузка…
Профиль не найден
Ссылка на strongSwan
+ VPN connection state
+ Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.
+
+
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
Журнал
@@ -80,6 +88,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
Split tunneling
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
Block IPv4 traffic not destined for the VPN
@@ -109,6 +125,7 @@
This VPN profile already exists, its current settings will be replaced.
Import certificate from VPN profile
Certificate for \"%1$s\"
+ Profile ID
A value is required to initiate the connection
Пожалуйста введите имя пользователя
@@ -151,6 +168,7 @@
Отключение…
Нет активных VPN
Ошибка
+ Dismiss
Assessment:
@@ -166,13 +184,15 @@
Логин
Пароль
Соединить
- Ошибка подключения к VPN:
- Не найден адрес сервер.
- Сервер недоступен.
- Ошибка авторизаци при подключении к сервер.
- Ошибка авторизации пользователя.
- Security assessment failed.
- Неизвестная ошибка.
+ Ошибка подключения к VPN: %1$s.
+ Не найден адрес сервер
+ Сервер недоступен
+ Ошибка авторизаци при подключении к сервер
+ Ошибка авторизации пользователя
+ Security assessment failed
+ Неизвестная ошибка
+ Password unavailable
+ Client certificate unavailable
Соединение с VPN установлено
Подключение к этому профилю VPN уже существует!
Переподключить
@@ -181,5 +201,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
Соединить
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml
index bfe4719cd..53eb2ba46 100644
--- a/src/frontends/android/app/src/main/res/values-ua/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml
@@ -22,10 +22,18 @@
Пошук
VPN не підтримуеться
Ваш пристрій не підтримує VPN.\nЗв\'яжіться з виробником.
- VPN з\'єднання не пітримується у режимі lockdown.
+ VPN connections are not supported if a built-in VPN has the always-on feature enabled.
+ Unable to get permission to create VPN connections. Either because it was denied by the user, or because a different VPN app has the always-on feature enabled.
Завантаження…
Профіль не знайдено
strongSwan посилання
+ VPN connection state
+ Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.
+
+
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
Журнал
@@ -81,6 +89,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
Split tunneling
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
Block IPv4 traffic not destined for the VPN
@@ -110,6 +126,7 @@
This VPN profile already exists, its current settings will be replaced.
Import certificate from VPN profile
Certificate for \"%1$s\"
+ Profile ID
A value is required to initiate the connection
Введіть ім\'я користувача
@@ -152,6 +169,7 @@
Роз\'єднання…
Немає активних VPN
Помилка
+ Dismiss
Assessment:
@@ -167,13 +185,15 @@
Логін
Пароль
Підключити
- Помилка підлючення VPN:
- Помилка пошуку адреси сервер.
- Сервер зв\'язку зі шлюзом.
- Помилка перевірки данних аутентифікації сервер.
- Помилка аутентифікації користувача.
- Security assessment failed.
- Невідома помилка під час підключення.
+ Помилка підлючення VPN: %1$s.
+ Помилка пошуку адреси сервер
+ Сервер зв\'язку зі шлюзом
+ Помилка перевірки данних аутентифікації сервер
+ Помилка аутентифікації користувача
+ Security assessment failed
+ Невідома помилка під час підключення
+ Password unavailable
+ Client certificate unavailable
VPN підключено
Цей VPN профіль зараз підключений!
Перепідключитися
@@ -182,5 +202,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
Підключити
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
index bda732443..85d536f12 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -21,10 +21,18 @@
搜索
无法支持VPN
您的设备无法支持VPN应用。\n请联系供应商。
- 锁定模式下无法支持VPN连接
+ VPN connections are not supported if a built-in VPN has the always-on feature enabled.
+ Unable to get permission to create VPN connections. Either because it was denied by the user, or because a different VPN app has the always-on feature enabled.
载入中…
未找到配置
strongSwan快捷方式
+ VPN connection state
+ Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.
+
+
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
日志
@@ -80,6 +88,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
拆分隧道
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
屏蔽不通过VPN的IPV4流量
@@ -109,6 +125,7 @@
此VPN配置已经存在,当前设定将被覆盖。
从VPN配置导入证书
\"%1$s\" 所对应的证书
+ Profile ID
必填信息以初始化连接
请输入您的用户名
@@ -151,6 +168,7 @@
断开连接中…
无活跃VPN
错误
+ Dismiss
评估详情:
@@ -166,13 +184,15 @@
用户名
密码
连接
- 无法建立VPN:
- 服务器地址查找失败。
- 服务器地址无法连接。
- 核验服务器鉴权失败。
- 用户鉴权失败。
- 可靠性评估失败。
- 连接中遭遇未知失败。
+ 无法建立VPN:%1$s。
+ 服务器地址查找失败
+ 服务器地址无法连接
+ 核验服务器鉴权失败
+ 用户鉴权失败
+ 可靠性评估失败
+ 连接中遭遇未知失败
+ Password unavailable
+ Client certificate unavailable
VPN已连接
此VPN配置目前已连接。
重连
@@ -181,5 +201,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
连接
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
index 2d4992333..9299d241e 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -21,10 +21,18 @@
搜尋
無法支援VPN
您的設備無法使用VPN。\n請聯絡供應商。
- 鎖定模式無法連線VPN
+ VPN connections are not supported if a built-in VPN has the always-on feature enabled.
+ Unable to get permission to create VPN connections. Either because it was denied by the user, or because a different VPN app has the always-on feature enabled.
載入中…
沒有找到設定檔
strongSwan快速選單
+ VPN connection state
+ Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.
+
+
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
日誌
@@ -80,6 +88,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
拆分隧道
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
屏蔽不通过VPN的IPV4流量
@@ -109,6 +125,7 @@
這個VPN設定檔已經存在,當前設定檔會被覆蓋。
從VPN設定檔匯入憑證
\"%1$s\" 對應的憑證
+ Profile ID
請填寫必要訊息才能初始化連線
請輸入您的用戶名稱
@@ -151,6 +168,7 @@
結束連線中…
無運作中的VPN
錯誤
+ Dismiss
評估詳情:
@@ -166,13 +184,15 @@
用戶名稱
密碼
連線
- 無法建立VPN:
- 伺服器位置查詢失敗。
- 伺服器位置無法連線。
- 驗證伺服器授權失敗。
- 用戶授權失敗。
- 穩定性評估失敗。
- 連線中遇到不明錯誤。
+ 無法建立VPN:%1$s。
+ 伺服器位置查詢失敗
+ 伺服器位置無法連線
+ 驗證伺服器授權失敗
+ 用戶授權失敗
+ 穩定性評估失敗
+ 連線中遇到不明錯誤
+ Password unavailable
+ Client certificate unavailable
VPN已連線
這個VPN設定檔目前已經連線。
重新連線
@@ -181,5 +201,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
連線
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values/colors.xml b/src/frontends/android/app/src/main/res/values/colors.xml
index eedfe2397..11d4e24a2 100644
--- a/src/frontends/android/app/src/main/res/values/colors.xml
+++ b/src/frontends/android/app/src/main/res/values/colors.xml
@@ -22,7 +22,7 @@
name="primary">#A2042C
#000000
+ name="primary_dark">#323232
#D9192C
diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml
index dd22d3be7..fb8cf8559 100644
--- a/src/frontends/android/app/src/main/res/values/strings.xml
+++ b/src/frontends/android/app/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
+ Settings
+ Default VPN profile
+ Connect to most recently used profile
Log
@@ -83,6 +91,14 @@
Small packets are sent to keep mappings on NAT routers alive if there is no other traffic. In order to save energy the default interval is 45 seconds. Behind NAT routers that remove mappings early this might be too high, try 20 seconds or less in that case.
Send certificate requests
Certificate requests are sent for all available or selected CA certificates. To reduce the size of the IKE_AUTH message this can be disabled. However, this only works if the server sends its certificate even if it didn\'t receive any certificate requests.
+ Use OCSP to check certificate
+ Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.
+ Use CRLs to check certificate
+ Use Certificate Revocation Lists (CRL), if available, to check that the server certificate has not been revoked. CRLs are only used if OCSP doesn\'t yield a result.
+ Use strict revocation checking
+ In strict mode the authentication will fail not only if the server certificate has been revoked but also if its status is unknown (e.g. because OCSP failed and no valid CRL was available).
+ Use RSA/PSS signatures
+ Use the stronger PSS encoding instead of the classic PKCS#1 encoding for RSA signatures. Authentication will fail if the server does not support such signatures.
Split tunneling
By default, the client will route all network traffic through the VPN, unless the server narrows the subnets when the connection is established, in which case only traffic the server allows will be routed via VPN (by default, all other traffic is routed as if there was no VPN).
Block IPv4 traffic not destined for the VPN
@@ -112,6 +128,7 @@
This VPN profile already exists, its current settings will be replaced.
Import certificate from VPN profile
Certificate for \"%1$s\"
+ Profile ID
A value is required to initiate the connection
Please enter your username
@@ -154,6 +171,7 @@
Disconnecting…
No active VPN
Error
+ Dismiss
Assessment:
@@ -169,13 +187,15 @@
Username
Password
Connect
- Failed to establish VPN:
- Server address lookup failed.
- Server is unreachable.
- Verifying server authentication failed.
- User authentication failed.
- Security assessment failed.
- Unspecified failure while connecting.
+ Failed to establish VPN: %1$s.
+ Server address lookup failed
+ Server is unreachable
+ Verifying server authentication failed
+ User authentication failed
+ Security assessment failed
+ Unspecified failure while connecting
+ Password unavailable
+ Client certificate unavailable
VPN connected
This VPN profile is currently connected!
Reconnect
@@ -184,5 +204,16 @@
Disconnect VPN?
This will disconnect the active VPN connection!
Connect
+ Retry
+
+ - Retry in %1$d second
+ - Retry in %1$d seconds
+
+ Cancel retry
+
+
+ Toggle VPN
+ Connect VPN
+ Disconnect VPN
diff --git a/src/frontends/android/app/src/main/res/values/styles.xml b/src/frontends/android/app/src/main/res/values/styles.xml
index 82a957813..4b7cc4c5c 100644
--- a/src/frontends/android/app/src/main/res/values/styles.xml
+++ b/src/frontends/android/app/src/main/res/values/styles.xml
@@ -1,6 +1,6 @@
+
+
+
+
+
diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle
index e52ed0aca..365aa1177 100644
--- a/src/frontends/android/build.gradle
+++ b/src/frontends/android/build.gradle
@@ -1,14 +1,16 @@
buildscript {
repositories {
jcenter()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'com.android.tools.build:gradle:3.1.3'
}
}
allprojects {
repositories {
jcenter()
+ google()
}
}
diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
index 8c08a361c..8a32ae179 100644
--- a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
+++ b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Apr 18 15:57:06 CEST 2017
+#Mon Jun 04 11:56:43 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c
index 2a499db40..7c4b692a5 100644
--- a/src/libcharon/sa/ike_sa_manager.c
+++ b/src/libcharon/sa/ike_sa_manager.c
@@ -2,7 +2,7 @@
* Copyright (C) 2005-2011 Martin Willi
* Copyright (C) 2011 revosec AG
*
- * Copyright (C) 2008-2017 Tobias Brunner
+ * Copyright (C) 2008-2018 Tobias Brunner
* Copyright (C) 2005 Jan Hutter
* HSR Hochschule fuer Technik Rapperswil
*
@@ -1620,17 +1620,6 @@ METHOD(ike_sa_manager_t, new_initiator_spi, bool,
unlock_single_segment(this, segment);
return FALSE;
}
- /* threads waiting for this entry do so using the (soon) wrong IKE_SA
- * ID and, therefore, likely on the wrong segment, so drive them out */
- entry->driveout_waiting_threads = TRUE;
- entry->driveout_new_threads = TRUE;
- while (entry->waiting_threads)
- {
- entry->condvar->broadcast(entry->condvar);
- entry->condvar->wait(entry->condvar, this->segments[segment].mutex);
- }
- remove_entry(this, entry);
- unlock_single_segment(this, segment);
}
else
{
@@ -1638,7 +1627,19 @@ METHOD(ike_sa_manager_t, new_initiator_spi, bool,
return FALSE;
}
+ /* the hashtable row and segment are determined by the local SPI as
+ * initiator, so if we change it the row and segment derived from it might
+ * change as well. This could be a problem for threads waiting for the
+ * entry (in particular those enumerating entries to check them out by
+ * unique ID or name). In order to avoid having to drive them out and thus
+ * preventing them from checking out the entry (even though the ID or name
+ * will not change and enumerating it is also fine), we mask the new SPI and
+ * merge it with the old SPI so the entry ends up in the same row/segment.
+ * Since SPIs are 64-bit and the number of rows/segments is usually
+ * relatively low this should not be a problem. */
spi = ike_sa_id->get_initiator_spi(ike_sa_id);
+ new_spi = (spi & (uint64_t)this->table_mask) |
+ (new_spi & ~(uint64_t)this->table_mask);
DBG2(DBG_MGR, "change initiator SPI of IKE_SA %s[%u] from %.16"PRIx64" to "
"%.16"PRIx64, ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa),
@@ -1647,10 +1648,7 @@ METHOD(ike_sa_manager_t, new_initiator_spi, bool,
ike_sa_id->set_initiator_spi(ike_sa_id, new_spi);
entry->ike_sa_id->replace_values(entry->ike_sa_id, ike_sa_id);
- entry->driveout_waiting_threads = FALSE;
- entry->driveout_new_threads = FALSE;
-
- segment = put_entry(this, entry);
+ entry->condvar->signal(entry->condvar);
unlock_single_segment(this, segment);
return TRUE;
}
diff --git a/src/libstrongswan/plugins/revocation/revocation_plugin.c b/src/libstrongswan/plugins/revocation/revocation_plugin.c
index fe7eaa765..f688577e1 100644
--- a/src/libstrongswan/plugins/revocation/revocation_plugin.c
+++ b/src/libstrongswan/plugins/revocation/revocation_plugin.c
@@ -76,6 +76,13 @@ METHOD(plugin_t, get_features, int,
return countof(f);
}
+METHOD(plugin_t, reload, bool,
+ private_revocation_plugin_t *this)
+{
+ this->validator->reload(this->validator);
+ return TRUE;
+}
+
METHOD(plugin_t, destroy, void,
private_revocation_plugin_t *this)
{
@@ -95,6 +102,7 @@ plugin_t *revocation_plugin_create()
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
+ .reload = _reload,
.destroy = _destroy,
},
},
diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.c b/src/libstrongswan/plugins/revocation/revocation_validator.c
index f8e78ac0c..68292e3cd 100644
--- a/src/libstrongswan/plugins/revocation/revocation_validator.c
+++ b/src/libstrongswan/plugins/revocation/revocation_validator.c
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
typedef struct private_revocation_validator_t private_revocation_validator_t;
@@ -50,6 +51,10 @@ struct private_revocation_validator_t {
*/
bool enable_crl;
+ /**
+ * Lock to access flags
+ */
+ spinlock_t *lock;
};
/**
@@ -795,14 +800,21 @@ METHOD(cert_validator_t, validate, bool,
certificate_t *issuer, bool online, u_int pathlen, bool anchor,
auth_cfg_t *auth)
{
- if (online && (this->enable_ocsp || this->enable_crl) &&
+ bool enable_ocsp, enable_crl;
+
+ this->lock->lock(this->lock);
+ enable_ocsp = this->enable_ocsp;
+ enable_crl = this->enable_crl;
+ this->lock->unlock(this->lock);
+
+ if (online && (enable_ocsp || enable_crl) &&
subject->get_type(subject) == CERT_X509 &&
issuer->get_type(issuer) == CERT_X509)
{
DBG1(DBG_CFG, "checking certificate status of \"%Y\"",
subject->get_subject(subject));
- if (this->enable_ocsp)
+ if (enable_ocsp)
{
switch (check_ocsp((x509_t*)subject, (x509_t*)issuer, auth))
{
@@ -831,7 +843,7 @@ METHOD(cert_validator_t, validate, bool,
auth->add(auth, AUTH_RULE_OCSP_VALIDATION, VALIDATION_SKIPPED);
}
- if (this->enable_crl)
+ if (enable_crl)
{
switch (check_crl((x509_t*)subject, (x509_t*)issuer, auth))
{
@@ -865,9 +877,35 @@ METHOD(cert_validator_t, validate, bool,
return TRUE;
}
+METHOD(revocation_validator_t, reload, void,
+ private_revocation_validator_t *this)
+{
+ bool enable_ocsp, enable_crl;
+
+ enable_ocsp = lib->settings->get_bool(lib->settings,
+ "%s.plugins.revocation.enable_ocsp", TRUE, lib->ns);
+ enable_crl = lib->settings->get_bool(lib->settings,
+ "%s.plugins.revocation.enable_crl", TRUE, lib->ns);
+
+ this->lock->lock(this->lock);
+ this->enable_ocsp = enable_ocsp;
+ this->enable_crl = enable_crl;
+ this->lock->unlock(this->lock);
+
+ if (!enable_ocsp)
+ {
+ DBG1(DBG_LIB, "all OCSP validation disabled");
+ }
+ if (!enable_crl)
+ {
+ DBG1(DBG_LIB, "all CRL validation disabled");
+ }
+}
+
METHOD(revocation_validator_t, destroy, void,
private_revocation_validator_t *this)
{
+ this->lock->destroy(this->lock);
free(this);
}
@@ -881,21 +919,13 @@ revocation_validator_t *revocation_validator_create()
INIT(this,
.public = {
.validator.validate = _validate,
+ .reload = _reload,
.destroy = _destroy,
},
- .enable_ocsp = lib->settings->get_bool(lib->settings,
- "%s.plugins.revocation.enable_ocsp", TRUE, lib->ns),
- .enable_crl = lib->settings->get_bool(lib->settings,
- "%s.plugins.revocation.enable_crl", TRUE, lib->ns),
+ .lock = spinlock_create(),
);
- if (!this->enable_ocsp)
- {
- DBG1(DBG_LIB, "all OCSP validation disabled");
- }
- if (!this->enable_crl)
- {
- DBG1(DBG_LIB, "all CRL validation disabled");
- }
+ reload(this);
+
return &this->public;
}
diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.h b/src/libstrongswan/plugins/revocation/revocation_validator.h
index 82cbde26b..9128787f1 100644
--- a/src/libstrongswan/plugins/revocation/revocation_validator.h
+++ b/src/libstrongswan/plugins/revocation/revocation_validator.h
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2018 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
* Copyright (C) 2010 Martin Willi
* Copyright (C) 2010 revosec AG
*
@@ -35,6 +38,11 @@ struct revocation_validator_t {
*/
cert_validator_t validator;
+ /**
+ * Reload the configuration
+ */
+ void (*reload)(revocation_validator_t *this);
+
/**
* Destroy a revocation_validator_t.
*/
diff --git a/src/libstrongswan/utils/utils/atomics.h b/src/libstrongswan/utils/utils/atomics.h
index a973b1adc..c23b361ec 100644
--- a/src/libstrongswan/utils/utils/atomics.h
+++ b/src/libstrongswan/utils/utils/atomics.h
@@ -27,8 +27,14 @@
*/
typedef u_int refcount_t;
+/* use __atomic* built-ins with clang, if available (note that clang also
+ * defines __GNUC__, however only claims to be GCC 4.2) */
+#if defined(__clang__)
+# if __has_builtin(__atomic_add_fetch)
+# define HAVE_GCC_ATOMIC_OPERATIONS
+# endif
/* use __atomic* built-ins with GCC 4.7 and newer */
-#ifdef __GNUC__
+#elif defined(__GNUC__)
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6))
# define HAVE_GCC_ATOMIC_OPERATIONS
# endif
@@ -47,7 +53,7 @@ typedef u_int refcount_t;
#define ref_put(ref) (!__atomic_sub_fetch(ref, 1, __ATOMIC_ACQ_REL))
#define ref_cur(ref) __atomic_load_n(ref, __ATOMIC_RELAXED)
-#define _cas_impl(ptr, oldval, newval) ({ typeof(oldval) _old = oldval; \
+#define _cas_impl(ptr, oldval, newval) ({ typeof(*ptr) _old = oldval; \
__atomic_compare_exchange_n(ptr, &_old, newval, FALSE, \
__ATOMIC_SEQ_CST, __ATOMIC_RELAXED); })
#define cas_bool(ptr, oldval, newval) _cas_impl(ptr, oldval, newval)