]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
Merge branch 'android-updates'
authorTobias Brunner <tobias@strongswan.org>
Tue, 3 Jul 2018 10:15:52 +0000 (12:15 +0200)
committerTobias Brunner <tobias@strongswan.org>
Tue, 3 Jul 2018 10:15:52 +0000 (12:15 +0200)
Lots of new features, e.g. Quick Settings tile, Always-on VPN, error
recovery, and lots of improvements under the hood.

67 files changed:
src/frontends/android/app/build.gradle
src/frontends/android/app/src/main/AndroidManifest.xml
src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java
src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java [deleted file]
src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java
src/frontends/android/app/src/main/jni/Android.mk
src/frontends/android/app/src/main/jni/Application.mk
src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h
src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h
src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_connecting.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_connecting.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_connecting.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png [new file with mode: 0755]
src/frontends/android/app/src/main/res/drawable/error_background.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml
src/frontends/android/app/src/main/res/layout/log_fragment.xml
src/frontends/android/app/src/main/res/layout/log_list_item.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
src/frontends/android/app/src/main/res/layout/profile_import_view.xml
src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml
src/frontends/android/app/src/main/res/menu/main.xml
src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_bg.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_fg.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_bg.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_fg.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/values-de/strings.xml
src/frontends/android/app/src/main/res/values-pl/strings.xml
src/frontends/android/app/src/main/res/values-ru/strings.xml
src/frontends/android/app/src/main/res/values-ua/strings.xml
src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
src/frontends/android/app/src/main/res/values/colors.xml
src/frontends/android/app/src/main/res/values/strings.xml
src/frontends/android/app/src/main/res/values/styles.xml
src/frontends/android/app/src/main/res/xml/settings.xml [new file with mode: 0644]
src/frontends/android/build.gradle
src/frontends/android/gradle/wrapper/gradle-wrapper.properties
src/libcharon/sa/ike_sa_manager.c
src/libstrongswan/plugins/revocation/revocation_plugin.c
src/libstrongswan/plugins/revocation/revocation_validator.c
src/libstrongswan/plugins/revocation/revocation_validator.h
src/libstrongswan/utils/utils/atomics.h

index b708be4cbaf011e523571529e1a0b10a887a3f31..6767dab1eb750af81d96583a2d699fa7c0703238 100644 (file)
@@ -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'
 }
index 287208238f21cae7b7994d84825795428dcdddeb..2a75d05abab7bed36800d6dbf533f008b96cedf1 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2012-2015 Tobias Brunner
+    Copyright (C) 2012-2018 Tobias Brunner
     Copyright (C) 2012 Giuliano Grassi
     Copyright (C) 2012 Ralf Sager
     HSR Hochschule fuer Technik Rapperswil
@@ -24,7 +24,7 @@
 
     <application
         android:name=".logic.StrongSwanApplication"
-        android:icon="@drawable/ic_launcher"
+        android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/ApplicationTheme"
         android:allowBackup="false" >
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".ui.VpnProfileControlActivity"
+            android:theme="@style/TransparentActivity"
+            android:taskAffinity=""
+            android:excludeFromRecents="true"
+            android:launchMode="singleTask" >
             <intent-filter>
                 <action android:name="org.strongswan.android.action.START_PROFILE" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="org.strongswan.android.action.DISCONNECT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
         <activity
             android:name=".ui.VpnProfileDetailActivity" >
         <activity
             android:name=".ui.LogActivity"
             android:label="@string/log_title" >
+        </activity>
+               <activity
+            android:name=".ui.SettingsActivity"
+            android:label="@string/pref_title">
         </activity>
         <activity
             android:name=".ui.RemediationInstructionsActivity"
@@ -62,7 +80,8 @@
         </activity>
         <activity
             android:name=".ui.VpnProfileSelectActivity"
-            android:label="@string/strongswan_shortcut" >
+            android:label="@string/strongswan_shortcut"
+            android:icon="@mipmap/ic_shortcut" >
             <intent-filter>
                 <action android:name="android.intent.action.CREATE_SHORTCUT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="content" />
                 <data android:mimeType="application/vnd.strongswan.profile" />
             </intent-filter>
+            <!-- this matches by file extension if no MIME type is provided -->
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:pathPattern=".*\\..*\\.sswan" />
                 <data android:pathPattern=".*\\.sswan" />
             </intent-filter>
+            <!-- this matches by file extension if any MIME type (but not ours) is provided -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="file" />
+                <data android:scheme="content" />
+                <data android:host="*" />
+                <data android:mimeType="*/*" />
+                <data android:pathPattern=".*\\..*\\..*\\..*\\.sswan" />
+                <data android:pathPattern=".*\\..*\\..*\\.sswan" />
+                <data android:pathPattern=".*\\..*\\.sswan" />
+                <data android:pathPattern=".*\\.sswan" />
+            </intent-filter>
         </activity>
         <activity
             android:name=".ui.TrustedCertificateImportActivity"
             android:label="@string/import_certificate"
-            android:theme="@style/AlertDialogTheme" >
+            android:theme="@style/TransparentActivity" >
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <action android:name="android.net.VpnService" />
             </intent-filter>
         </service>
+        <service
+            android:name=".ui.VpnTileService"
+            android:label="@string/tile_default"
+            android:icon="@drawable/ic_notification"
+            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+        </service>
 
         <provider
             android:name=".data.LogContentProvider"
index 00cd393ca6f5fa07dc799129295b96792b1cc306..8e19d99e81dceecdd1285dc0adadd5e1aebedf51 100644 (file)
@@ -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
@@ -30,7 +30,12 @@ public class VpnProfile implements Cloneable
        /* While storing this as EnumSet would be nicer this simplifies storing it in a database */
        public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1;
        public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2;
-       public static final int FLAGS_SUPPRESS_CERT_REQS = 1;
+
+       public static final int FLAGS_SUPPRESS_CERT_REQS = 1 << 0;
+       public static final int FLAGS_DISABLE_CRL = 1 << 1;
+       public static final int FLAGS_DISABLE_OCSP = 1 << 2;
+       public static final int FLAGS_STRICT_REVOCATION = 1 << 3;
+       public static final int FLAGS_RSA_PSS = 1 << 4;
 
        private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
        private String mRemoteId, mLocalId, mExcludedSubnets, mIncludedSubnets, mSelectedApps;
index 2fef57770e7be5e2aadc45ab865947a44b152fb9..afd0d4fb1ae8b9c3ea3792a49369e51e60142010 100644 (file)
@@ -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
@@ -63,7 +63,7 @@ public class VpnProfileDataSource
        private static final String DATABASE_NAME = "strongswan.db";
        private static final String TABLE_VPNPROFILE = "vpnprofile";
 
-       private static final int DATABASE_VERSION = 15;
+       private static final int DATABASE_VERSION = 16;
 
        public static final DbColumn[] COLUMNS = new DbColumn[] {
                                                                new DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
@@ -223,6 +223,26 @@ public class VpnProfileDataSource
                                db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL +
                                                   " TEXT;");
                        }
+                       if (oldVersion < 16)
+                       {       /* add a UUID to all entries that haven't one yet */
+                               db.beginTransaction();
+                               try
+                               {
+                                       Cursor cursor = db.query(TABLE_VPNPROFILE, ALL_COLUMNS, KEY_UUID + " is NULL", null, null, null, null);
+                                       for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
+                                       {
+                                               ContentValues values = new ContentValues();
+                                               values.put(KEY_UUID, UUID.randomUUID().toString());
+                                               db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndex(KEY_ID)), null);
+                                       }
+                                       cursor.close();
+                                       db.setTransactionSuccessful();
+                               }
+                               finally
+                               {
+                                       db.endTransaction();
+                               }
+                       }
                }
 
                private void updateColumns(SQLiteDatabase db, int version)
@@ -361,6 +381,28 @@ public class VpnProfileDataSource
                return profile;
        }
 
+       /**
+        * Get a single VPN profile from the database by its UUID as String.
+        * @param uuid the UUID of the VPN profile as String
+        * @return the profile or null, if not found
+        */
+       public VpnProfile getVpnProfile(String uuid)
+       {
+               try
+               {
+                       if (uuid != null)
+                       {
+                               return getVpnProfile(UUID.fromString(uuid));
+                       }
+                       return null;
+               }
+               catch (IllegalArgumentException e)
+               {
+                       e.printStackTrace();
+                       return null;
+               }
+       }
+
        /**
         * Get a list of all VPN profiles stored in the database.
         * @return list of VPN profiles
@@ -385,7 +427,7 @@ public class VpnProfileDataSource
        {
                VpnProfile profile = new VpnProfile();
                profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
-               profile.setUUID(getUUID(cursor, cursor.getColumnIndex(KEY_UUID)));
+               profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndex(KEY_UUID))));
                profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
                profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
                profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
@@ -412,7 +454,7 @@ public class VpnProfileDataSource
        private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
        {
                ContentValues values = new ContentValues();
-               values.put(KEY_UUID, profile.getUUID() != null ? profile.getUUID().toString() : null);
+               values.put(KEY_UUID, profile.getUUID().toString());
                values.put(KEY_NAME, profile.getName());
                values.put(KEY_GATEWAY, profile.getGateway());
                values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
@@ -441,18 +483,6 @@ public class VpnProfileDataSource
                return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
        }
 
-       private UUID getUUID(Cursor cursor, int columnIndex)
-       {
-               try
-               {
-                       return cursor.isNull(columnIndex) ? null : UUID.fromString(cursor.getString(columnIndex));
-               }
-               catch (Exception e)
-               {
-                       return null;
-               }
-       }
-
        private static class DbColumn
        {
                public final String Name;
index b6e7e2d644dff3a897c2ce54ee482b5125ae3b24..ac22cf5ff2a1e89342b6eea1cd32869bbfd4c487 100644 (file)
@@ -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
@@ -19,6 +19,7 @@ package org.strongswan.android.logic;
 
 import android.annotation.TargetApi;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -26,12 +27,15 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.net.VpnService;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.preference.PreferenceManager;
 import android.security.KeyChain;
 import android.security.KeyChainException;
 import android.support.v4.app.NotificationCompat;
@@ -49,15 +53,21 @@ import org.strongswan.android.logic.VpnStateService.State;
 import org.strongswan.android.logic.imc.ImcState;
 import org.strongswan.android.logic.imc.RemediationInstruction;
 import org.strongswan.android.ui.MainActivity;
+import org.strongswan.android.ui.VpnProfileControlActivity;
+import org.strongswan.android.utils.Constants;
 import org.strongswan.android.utils.IPRange;
 import org.strongswan.android.utils.IPRangeSet;
 import org.strongswan.android.utils.SettingsWriter;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
@@ -69,8 +79,11 @@ import java.util.SortedSet;
 public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
 {
        private static final String TAG = CharonVpnService.class.getSimpleName();
+       private static final String VPN_SERVICE_ACTION = "android.net.VpnService";
        public static final String DISCONNECT_ACTION = "org.strongswan.android.CharonVpnService.DISCONNECT";
+       private static final String NOTIFICATION_CHANNEL = "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
        public static final String LOG_FILE = "charon.log";
+       public static final String KEY_IS_RETRY = "retry";
        public static final int VPN_STATE_NOTIFICATION_ID = 1;
 
        private String mLogFile;
@@ -85,6 +98,8 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
        private volatile boolean mTerminate;
        private volatile boolean mIsDisconnecting;
        private volatile boolean mShowNotification;
+       private BuilderAdapter mBuilderAdapter = new BuilderAdapter();
+       private Handler mHandler;
        private VpnStateService mService;
        private final Object mServiceLock = new Object();
        private final ServiceConnection mServiceConnection = new ServiceConnection() {
@@ -119,32 +134,51 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
        static final int STATE_PEER_AUTH_ERROR = 4;
        static final int STATE_LOOKUP_ERROR = 5;
        static final int STATE_UNREACHABLE_ERROR = 6;
-       static final int STATE_GENERIC_ERROR = 7;
+       static final int STATE_CERTIFICATE_UNAVAILABLE = 7;
+       static final int STATE_GENERIC_ERROR = 8;
 
        @Override
        public int onStartCommand(Intent intent, int flags, int startId)
        {
                if (intent != null)
                {
-                       if (DISCONNECT_ACTION.equals(intent.getAction()))
-                       {
-                               setNextProfile(null);
+                       VpnProfile profile = null;
+                       boolean retry = false;
+
+                       if (VPN_SERVICE_ACTION.equals(intent.getAction()))
+                       {       /* triggered when Always-on VPN is activated */
+                               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);
+                               }
+                               profile = mDataSource.getVpnProfile(uuid);
                        }
-                       else
+                       else if (!DISCONNECT_ACTION.equals(intent.getAction()))
                        {
                                Bundle bundle = intent.getExtras();
-                               VpnProfile profile = null;
                                if (bundle != null)
                                {
-                                       profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
+                                       profile = mDataSource.getVpnProfile(bundle.getString(VpnProfileDataSource.KEY_UUID));
                                        if (profile != null)
                                        {
                                                String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
                                                profile.setPassword(password);
+
+                                               retry = bundle.getBoolean(CharonVpnService.KEY_IS_RETRY, false);
+
+                                               SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+                                               pref.edit().putString(Constants.PREF_MRU_VPN_PROFILE, profile.getUUID().toString())
+                                                       .apply();
                                        }
                                }
-                               setNextProfile(profile);
                        }
+                       if (profile != null && !retry)
+                       {       /* delete the log file if this is not an automatic retry */
+                               deleteFile(LOG_FILE);
+                       }
+                       setNextProfile(profile);
                }
                return START_NOT_STICKY;
        }
@@ -155,6 +189,9 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
                mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
                mAppDir = getFilesDir().getAbsolutePath();
 
+               /* handler used to do changes in the main UI thread */
+               mHandler = new Handler();
+
                mDataSource = new VpnProfileDataSource(this);
                mDataSource.open();
                /* use a separate thread as main thread for charon */
@@ -162,6 +199,8 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
                /* the thread is started when the service is bound */
                bindService(new Intent(this, VpnStateService.class),
                                        mServiceConnection, Service.BIND_AUTO_CREATE);
+
+               createNotificationChannel();
        }
 
        @Override
@@ -244,15 +283,27 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
                                                startConnection(mCurrentProfile);
                                                mIsDisconnecting = false;
 
+                                               SimpleFetcher.enable();
                                                addNotification();
-                                               BuilderAdapter builder = new BuilderAdapter(mCurrentProfile);
-                                               if (initializeCharon(builder, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
+                                               mBuilderAdapter.setProfile(mCurrentProfile);
+                                               if (initializeCharon(mBuilderAdapter, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
                                                {
                                                        Log.i(TAG, "charon started");
+
+                                                       if (mCurrentProfile.getVpnType().has(VpnTypeFeature.USER_PASS) &&
+                                                               mCurrentProfile.getPassword() == null)
+                                                       {       /* this can happen if Always-on VPN is enabled with an incomplete profile */
+                                                               setError(ErrorState.PASSWORD_MISSING);
+                                                               continue;
+                                                       }
+
                                                        SettingsWriter writer = new SettingsWriter();
                                                        writer.setValue("global.language", Locale.getDefault().getLanguage());
                                                        writer.setValue("global.mtu", mCurrentProfile.getMTU());
                                                        writer.setValue("global.nat_keepalive", mCurrentProfile.getNATKeepAlive());
+                                                       writer.setValue("global.rsa_pss", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_RSA_PSS) != 0);
+                                                       writer.setValue("global.crl", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_CRL) == 0);
+                                                       writer.setValue("global.ocsp", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_OCSP) == 0);
                                                        writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
                                                        writer.setValue("connection.server", mCurrentProfile.getGateway());
                                                        writer.setValue("connection.port", mCurrentProfile.getPort());
@@ -261,6 +312,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
                                                        writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
                                                        writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
                                                        writer.setValue("connection.certreq", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
+                                                       writer.setValue("connection.strict_revocation", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_STRICT_REVOCATION) != 0);
                                                        writer.setValue("connection.ike_proposal", mCurrentProfile.getIkeProposal());
                                                        writer.setValue("connection.esp_proposal", mCurrentProfile.getEspProposal());
                                                        initiate(writer.serialize());
@@ -290,14 +342,25 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
        {
                synchronized (this)
                {
+                       if (mNextProfile != null && Build.VERSION.SDK_INT >= 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)
index b7334cee1df8f7298f591f9d8b13d6134181390b..6eaccc4d8d8ef3314d40f07aec47a394a9bdb3ae 100644 (file)
@@ -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<Future> 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);
-               try
+               Future<byte[]> future;
+
+               synchronized (mLock)
                {
-                       if (contentType != null)
+                       if (mDisabled)
                        {
-                               conn.setRequestProperty("Content-Type", contentType);
+                               return null;
                        }
-                       if (data != 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
+               {
+                       /* 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
+               {
+                       synchronized (mLock)
                        {
-                               conn.setDoOutput(true);
-                               conn.setFixedLengthStreamingMode(data.length);
-                               OutputStream out = new BufferedOutputStream(conn.getOutputStream());
-                               out.write(data);
-                               out.close();
+                               mFutures.remove(future);
                        }
-                       return streamToArray(conn.getInputStream());
                }
-               finally
+       }
+
+       /**
+        * 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)
                {
-                       conn.disconnect();
+                       mDisabled = true;
+                       for (Future future : mFutures)
+                       {
+                               future.cancel(true);
+                       }
                }
        }
 
index cd30049f76fb8ef868e35a6f0772c714721d073b..5889293616e65481d52a0e7f16ba2cbd7411ddc2 100644 (file)
@@ -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<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
+       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<VpnStateService> 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;
+               }
+       }
 }
index f3123f681d92b0107da70264c9ea9bf58f20daae..5e36b11ffae822b3e10fe7aac930b4834465b132 100644 (file)
@@ -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;
                }
        }
 
index c6a80a1001875e613a44988c236427d4c769504e..a3cb245e8cffce4394d0ddf3d9d5aaf6e1bdc014 100644 (file)
@@ -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);
index f68d0c617e05a3b299cfa5939d20b7cc1fc67897..625940fd4f19419d97bb48193121a9abecaf5952 100644 (file)
@@ -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
 
 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,114 +104,115 @@ 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<String> implements Runnable
        {
-               try
+               private BufferedReader mReader;
+               private Thread mThread;
+               private volatile boolean mRunning;
+
+               public LogAdapter(@NonNull Context context)
                {
-                       mReader = new BufferedReader(new FileReader(mLogFilePath));
+                       super(context, R.layout.log_list_item, R.id.log_line);
                }
-               catch (FileNotFoundException e)
+
+               public void restart()
                {
-                       mReader = new BufferedReader(new StringReader(""));
-               }
+                       if (mRunning)
+                       {
+                               stop();
+                       }
 
-               mLogView.setText("");
-               mRunning = true;
-               mThread = new Thread(this);
-               mThread.start();
-       }
+                       clear();
 
-       /**
-        * Stop reading from the log file
-        */
-       private void stopLogReader()
-       {
-               try
-               {
-                       mRunning = false;
-                       mThread.interrupt();
-                       mThread.join();
+                       try
+                       {
+                               mReader = new BufferedReader(new FileReader(mLogFilePath));
+                       }
+                       catch (FileNotFoundException e)
+                       {
+                               mReader = new BufferedReader(new StringReader(""));
+                       }
+                       mRunning = true;
+                       mThread = new Thread(this);
+                       mThread.start();
                }
-               catch (InterruptedException e)
+
+               public void stop()
                {
+                       try
+                       {
+                               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<String> lines)
-       {
-               mLogHandler.post(new Runnable() {
-                       @Override
-                       public void run()
-                       {
-                               mLogView.beginBatchEdit();
+               private void logLines(final ArrayList<String> lines)
+               {
+                       mLogHandler.post(() -> {
+                               boolean scroll = getCount() == 0;
+                               setNotifyOnChange(false);
                                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();
+                               {
+                                       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;
                                        }
-                               });
-                       }
-               });
-       }
-
-       @Override
-       public void run()
-       {
-               ArrayList<String> lines = null;
+                                       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));
+                               }
+                       });
+               }
 
-               while (mRunning)
+               @Override
+               public void run()
                {
-                       try
-                       {       /* this works as long as the file is not truncated */
-                               String line = mReader.readLine();
-                               if (line == null)
-                               {
-                                       if (lines != null)
+                       ArrayList<String> lines = null;
+
+                       while (mRunning)
+                       {
+                               try
+                               {       /* this works as long as the file is not truncated */
+                                       String line = mReader.readLine();
+                                       if (line == null)
                                        {
-                                               logLines(lines);
-                                               lines = null;
+                                               if (lines != null)
+                                               {
+                                                       logLines(lines);
+                                                       lines = null;
+                                               }
+                                               /* wait until there is more to log */
+                                               Thread.sleep(1000);
                                        }
-                                       /* wait until there is more to log */
-                                       Thread.sleep(1000);
-                               }
-                               else
-                               {
-                                       if (lines == null)
+                                       else
                                        {
-                                               lines = new ArrayList<>();
+                                               if (lines == null)
+                                               {
+                                                       lines = new ArrayList<>();
+                                               }
+                                               lines.add(line);
                                        }
-                                       lines.add(line);
+                               }
+                               catch (Exception e)
+                               {
+                                       break;
                                }
                        }
-                       catch (Exception e)
+                       if (lines != null)
                        {
-                               break;
+                               logLines(lines);
                        }
                }
-               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 (file)
index cf25022..0000000
+++ /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 <http://www.fsf.org/copyleft/gpl.txt>.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * for more details.
- */
-
-package org.strongswan.android.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;
-               }
-       }
-}
index c0e375da45a5fd866fd3b4c21f6b13230b180b95..bfef8e98caf21f216ee68e9532c81aa61229ca59 100644 (file)
@@ -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
 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);
-                       }
-               }
-       };
+       private static final String DIALOG_TAG = "Dialog";
 
        @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<Void, Void, TrustedCertificateManager>
        {
-               @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 (file)
index 0000000..ad28529
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.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 (file)
index 0000000..b710c82
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.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<VpnProfile> all = profiles.getAllVpnProfiles();
+               Collections.sort(all, new Comparator<VpnProfile>() {
+                       @Override
+                       public int compare(VpnProfile lhs, VpnProfile rhs)
+                       {
+                               return lhs.getName().compareToIgnoreCase(rhs.getName());
+                       }
+               });
+
+               ArrayList<CharSequence> entries = new ArrayList<>();
+               ArrayList<CharSequence> 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();
+       }
+}
index ed950cd11f18971a5f6231d9073a5e6547458cf7..9cfecf34850121e7a27af58ec1a13e3b69ff9532 100644 (file)
@@ -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 (file)
index 0000000..78cbebf
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.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();
+               }
+       }
+}
index 37c5b3357f09c68b42ec26fea15c48f922e57344..75618b5648f1bda8ee3d79f67c30b0c4ecf88c9c 100644 (file)
@@ -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);
index 43c0035cf920c03832d539bb753bbde92c552f02..1d8b677eed0473ff67154065f8a30308cb4d3dcf 100644 (file)
@@ -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;
+                               }
                        }
                }
 
index 93af5f147b4df0ff03aeefb9c7f0caf863696417..fe3d01322a661b622e429b655e6a48c2cae51808 100644 (file)
@@ -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();
        }
 }
index 0a8e1f3425edc6e6d58e65431e6e0ab1783a3579..edc70fa9642ac45f7c34676a2e18a75f061f9860 100644 (file)
@@ -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<RemediationInstruction> 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<RemediationInstruction>(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 (file)
index 0000000..6b5cca0
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.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();
+       }
+}
index 16fdcdad12da2970593ebda2b764ec13b6320561..52234933b70f87a6500c4ad35a923abcccce1649 100644 (file)
@@ -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";
 }
index a46139421ffd019939945716e2051f4e4b9ff83f..2a04b789ad37cd202a071f71b8a8f904a4e50320 100644 (file)
@@ -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 \
index 25b1aa2bdbd164620af9f85563fb2da0e3fdd96b..00a46d70e61b05c9e62c274834ec0015324e8f87 100644 (file)
@@ -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
index aab12c4128a459d9a5a9866177ad9cbdb38ee4b5..918c14c5ef1ae4f43f44b06e50b892d438e5ee47 100644 (file)
@@ -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;
 
 /**
index b1a095bcdf73f92bc1646d4a0feaa16e8f68769a..a1a51b7b52c5d8daff0728f5c8449aefa57664a7 100644 (file)
@@ -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 <errno.h>
 #include <unistd.h>
 
+#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"));
-               child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                               "aes128-sha256-curve25519-ecp256-modp3072"));
-               child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                               "aes256-sha384-ecp521-modp8192"));
-               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"));
+                                                               "aes256gcm16-aes128gcm16-chacha20poly1305-"
+                                                               "curve25519-ecp384-ecp521-modp3072-modp4096-ecp256-modp8192"));
                child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                               "aes128-sha256"));
+                                                               "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"));
+                                                               "aes256gcm16-aes128gcm16-chacha20poly1305"));
                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);
index 06c5cada47d8dd38446380eb801ca4d65ba1cfb3..c8c300a8a5da151c777fcaa939682cb56331d1ea 100644 (file)
@@ -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);
 
index fa2fb42b24f5c70dca93b03c04b87461243e62c2..12353777beeeedc8a36e46343ecf3db0119d3f5e 100644 (file)
@@ -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 (executable)
index 0000000..6ae76a1
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 (executable)
index 0000000..039877b
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 (executable)
index 0000000..b079131
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 (executable)
index 0000000..1adedbe
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 (executable)
index 0000000..d3206fe
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 (executable)
index 0000000..e5ddb24
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 (file)
index 0000000..9f82757
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item>
+        <shape>
+            <solid
+                android:color="@color/panel_separator" />
+        </shape>
+    </item>
+
+    <item android:bottom="2dp">
+        <shape>
+            <solid
+                android:color="@android:color/white" />
+        </shape>
+    </item>
+
+</layer-list>
index 43219cf3fcb68e9f2e94ceaad6be9441afb54a37..ef5a63d87a42a0c418f3e7fb29b77344f5b29999 100644 (file)
@@ -16,7 +16,6 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingBottom="2dp"
     android:background="@drawable/state_background"
     android:orientation="vertical" >
 
index 6d6abfe16440c741c919808cbc3d63cd6a6ab261..a51ad4d79a212d549166b86cb882595abf482443 100644 (file)
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2012 Tobias Brunner
-    Copyright (C) 2012 Giuliano Grassi
-    Copyright (C) 2012 Ralf Sager
+    Copyright (C) 2012-2018 Tobias Brunner
     HSR Hochschule fuer Technik Rapperswil
 
     This program is free software; you can redistribute it and/or modify it
     android:layout_height="match_parent"
     android:orientation="vertical" >
 
-    <org.strongswan.android.ui.LogScrollView
-        android:id="@+id/scroll_view"
+    <ListView
+        android:id="@+id/log"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="10dp"
+        android:dividerHeight="0dp"
+        android:divider="@null"
+        android:fadeScrollbars="false"
         android:scrollbarFadeDuration="0"
-        android:scrollbarAlwaysDrawVerticalTrack="true" >
+        android:scrollbarAlwaysDrawVerticalTrack="true"
+        android:transcriptMode="normal">
 
-        <TextView
-            android:id="@+id/log_view"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="9sp"
-            android:typeface="monospace"
-            android:fontFamily="monospace" >
-        </TextView>
-
-    </org.strongswan.android.ui.LogScrollView>
+    </ListView>
 
 </LinearLayout>
diff --git a/src/frontends/android/app/src/main/res/layout/log_list_item.xml b/src/frontends/android/app/src/main/res/layout/log_list_item.xml
new file mode 100644 (file)
index 0000000..1fd42ce
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/log_line"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:textSize="9sp"
+    android:typeface="monospace"
+    app:fontFamily="monospace" />
index d4d88314d013ca0b4d6b2691e4996ab08c9a8291..49a918f12ed30dce0faa59e719ed1e931aafa73e 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    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
                 android:textSize="12sp"
                 android:text="@string/profile_cert_req_hint" />
 
+            <Switch
+                android:id="@+id/use_ocsp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:text="@string/profile_use_ocsp_label" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:textSize="12sp"
+                android:text="@string/profile_use_ocsp_hint" />
+
+            <Switch
+                android:id="@+id/use_crl"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:text="@string/profile_use_crl_label" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:textSize="12sp"
+                android:text="@string/profile_use_crl_hint" />
+
+            <Switch
+                android:id="@+id/strict_revocation"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:text="@string/profile_strict_revocation_label" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:textSize="12sp"
+                android:text="@string/profile_strict_revocation_hint" />
+
+            <Switch
+                android:id="@+id/rsa_pss"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:text="@string/profile_rsa_pss_label" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:textSize="12sp"
+                android:text="@string/profile_rsa_pss_hint" />
+
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
 
             </org.strongswan.android.ui.widget.TextInputLayoutHelper>
 
+            <TextView
+                android:id="@+id/profile_id_label"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
+                android:textSize="16sp"
+                android:text="@string/profile_profile_id" />
+
+            <TextView
+                android:id="@+id/profile_id"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="10dp"
+                android:layout_marginLeft="4dp"
+                android:textSize="12sp"
+                android:textIsSelectable="true" />
+
         </LinearLayout>
 
     </LinearLayout>
index fc06aa5d4ad30ffc179136042839aab1e59289dc..cabfd2944deb7f79392b3762fa9b0d9a41a222f9 100644 (file)
         android:padding="10dp"
         android:animateLayoutChanges="true" >
 
+        <android.support.v4.widget.ContentLoadingProgressBar
+            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
+            android:id="@+id/progress_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:indeterminate="true" />
+
         <TextView
             android:id="@+id/exists_warning"
             android:background="@drawable/state_background"
index 92ad5360711605e12c91b69c3648d1c8c3244ea9..fece125dd37ba6ca2f0f1f0a66b2b31057515b97 100644 (file)
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingBottom="2dp"
-    android:background="@drawable/state_background"
-    android:orientation="vertical" >
+    android:orientation="vertical"
+    android:animateLayoutChanges="true" >
 
-    <GridLayout
+    <LinearLayout
+        android:id="@+id/vpn_error"
+        android:visibility="gone"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:layout_marginLeft="20dp"
-        android:layout_marginRight="20dp"
-        android:layout_marginTop="10dp"
-        android:columnCount="2"
-        android:rowCount="2" >
+        android:layout_height="match_parent"
+        android:background="@drawable/error_background"
+        android:orientation="vertical" >
 
         <TextView
+            android:id="@+id/vpn_error_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginRight="5dp"
-            android:gravity="top"
-            android:text="@string/state_label"
-            android:textColor="?android:textColorPrimary"
-            android:textSize="20sp" />
+            android:layout_marginLeft="20dp"
+            android:layout_marginRight="20dp"
+            android:layout_marginTop="24dp"
+            android:layout_marginBottom="12dp"
+            android:text="Failed to establish VPN: Server is unreachable"
+            android:textColor="@color/primary_dark"
+            android:textSize="16sp" />
 
-        <TextView
-            android:id="@+id/vpn_state"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="top"
-            android:text="@string/state_disabled"
-            android:textColor="?android:textColorSecondary"
-            android:textSize="20sp" />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginBottom="8dp"
+            android:orientation="horizontal"
+            android:gravity="end" >
 
-        <TextView
-            android:id="@+id/vpn_profile_label"
-            android:layout_width="wrap_content"
+            <Button
+                android:id="@+id/show_log"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="8dp"
+                android:text="@string/show_log"
+                android:textColor="@color/primary"
+                android:textSize="14sp"
+                android:textStyle="bold"
+                style="?android:attr/borderlessButtonStyle" >
+            </Button>
+
+            <Button
+                android:id="@+id/retry"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/retry"
+                android:textColor="@color/primary"
+                android:textSize="14sp"
+                android:textStyle="bold"
+                style="?android:attr/borderlessButtonStyle" >
+            </Button>
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"
+                  android:background="@drawable/state_background"
+                  android:orientation="vertical" >
+
+        <GridLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginRight="5dp"
-            android:gravity="top"
-            android:text="@string/profile_label"
-            android:textColor="?android:textColorPrimary"
-            android:textSize="20sp"
-            android:visibility="gone" >
-        </TextView>
+            android:layout_marginBottom="10dp"
+            android:layout_marginLeft="20dp"
+            android:layout_marginRight="20dp"
+            android:layout_marginTop="10dp"
+            android:columnCount="2"
+            android:rowCount="2" >
 
-        <TextView
-            android:id="@+id/vpn_profile_name"
-            android:layout_width="wrap_content"
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="5dp"
+                android:gravity="top"
+                android:text="@string/state_label"
+                android:textColor="?android:textColorPrimary"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/vpn_state"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="top"
+                android:text="@string/state_disabled"
+                android:textColor="?android:textColorSecondary"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/vpn_profile_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="5dp"
+                android:gravity="top"
+                android:text="@string/profile_label"
+                android:textColor="?android:textColorPrimary"
+                android:textSize="20sp"
+                android:visibility="gone" >
+            </TextView>
+
+            <TextView
+                android:id="@+id/vpn_profile_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="top"
+                android:textSize="20sp"
+                android:visibility="gone" >
+            </TextView>
+        </GridLayout>
+
+        <ProgressBar
+            android:id="@+id/progress"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:gravity="top"
-            android:textSize="20sp"
-            android:visibility="gone" >
-        </TextView>
-    </GridLayout>
-
-    <ProgressBar
-        android:id="@+id/progress"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:layout_marginLeft="20dp"
-        android:layout_marginRight="20dp"
-        android:indeterminate="true"
-        android:visibility="gone"
-        style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
+            android:layout_marginBottom="10dp"
+            android:layout_marginLeft="20dp"
+            android:layout_marginRight="20dp"
+            android:indeterminate="true"
+            android:visibility="gone"
+            style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
 
-    <Button
-        android:id="@+id/action"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:layout_marginLeft="20dp"
-        android:layout_marginRight="20dp"
-        android:text="@string/disconnect"
-        style="?android:attr/borderlessButtonStyle" >
-    </Button>
+        <Button
+            android:id="@+id/action"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:layout_marginLeft="20dp"
+            android:layout_marginRight="20dp"
+            android:text="@string/disconnect"
+            style="?android:attr/borderlessButtonStyle" >
+        </Button>
 
+    </LinearLayout>
 </LinearLayout>
index 48a541b3d5a84e30757964ed7b0fd2949c8387bf..7cb0917ec6fb74cd478ff7e77354b85e30bc6d61 100644 (file)
@@ -36,4 +36,9 @@
         android:title="@string/show_log"
         app:showAsAction="withText" />
 
+    <item
+        android:id="@+id/menu_settings"
+        android:title="@string/pref_title"
+        app:showAsAction="withText" />
+
 </menu>
diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/frontends/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644 (file)
index 0000000..c389ee9
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@mipmap/ic_launcher_bg" />
+    <foreground android:drawable="@mipmap/ic_launcher_fg" />
+</adaptive-icon>
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 (file)
index 0000000..6aea2ff
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@mipmap/ic_shortcut_bg" />
+    <foreground android:drawable="@mipmap/ic_shortcut_fg" />
+</adaptive-icon>
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 (file)
index 0000000..653d080
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ic_launcher" />
\ 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 (file)
index 0000000..653d080
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ic_launcher" />
\ 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 (file)
index 0000000..df827ee
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 (file)
index 0000000..3d0c9bf
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 (file)
index 0000000..7653225
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 (file)
index 0000000..6e62e6d
Binary files /dev/null and b/src/frontends/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_shortcut_fg.png differ
index 49a0ab411e85e1ec6b59bd7b48e07ea85ceff457..ee9bbcb5f03720223a765da4e2ff35ac06f76110 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    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
     <string name="search">Suchen</string>
     <string name="vpn_not_supported_title">VPN nicht unterstützt</string>
     <string name="vpn_not_supported">Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller.</string>
-    <string name="vpn_not_supported_during_lockdown">VPN Verbindungen sind nicht möglich im abgeriegelten Modus.</string>
+    <string name="vpn_not_supported_during_lockdown">VPN Verbindungen sind nicht möglich, wenn für ein eingebautes VPN der Always-On-Modus aktiviert ist.</string>
+    <string name="vpn_not_supported_no_permission">Keine Berechtigung, um VPN Verbindungen zu erstellen. Entweder weil diese vom Benutzer verweigert wurde oder weil für eine andere VPN Anwendung der Always-On-Modus aktiviert ist.</string>
     <string name="loading">Laden&#8230;</string>
     <string name="profile_not_found">Profil nicht gefunden</string>
     <string name="strongswan_shortcut">strongSwan-Verknüpfung</string>
+    <string name="permanent_notification_name">VPN Verbindungsstatus</string>
+    <string name="permanent_notification_description">Zeigt Informationen zum Verbindungsstatus der VPN Verbindung und dient als permanente Notification dazu, den VPN Dienst im Hintergrund am Laufen zu halten.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Einstellungen</string>
+    <string name="pref_default_vpn_profile">Voreingestelltes VPN Profil</string>
+    <string name="pref_default_vpn_profile_mru">Verbinden mit zuletzt verwendetem Profil</string>
 
     <!-- Log view -->
     <string name="log_title">Log</string>
@@ -73,8 +81,8 @@
     <string name="profile_advanced_label">Erweiterte Einstellungen</string>
     <string name="profile_show_advanced_label">Erweiterte Einstellungen anzeigen</string>
     <string name="profile_remote_id_label">Server-Identität</string>
-    <string name="profile_remote_id_hint">Standardwert ist der konfigurierte Server. Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen</string>
-    <string name="profile_remote_id_hint_gateway">Standardwert ist \"%1$s\". Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen</string>
+    <string name="profile_remote_id_hint">Standardwert ist der konfigurierte Server. Eigene Werte werden explizit an den Server gesendet und während der Authentifizierung erzwungen</string>
+    <string name="profile_remote_id_hint_gateway">Standardwert ist \"%1$s\". Eigene Werte werden explizit an den Server gesendet und während der Authentifizierung erzwungen</string>
     <string name="profile_mtu_label">MTU des VPN Tunnel-Device</string>
     <string name="profile_mtu_hint">Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist</string>
     <string name="profile_port_label">Server Port</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Zertifikatsanforderungen senden</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">OCSP zur Zertifikatsprüfung verwenden</string>
+    <string name="profile_use_ocsp_hint">Prüft mit dem Online Certificate Status Protocol (OCSP), sofern verfügbar, ob das Server-Zertifikat gesperrt wurde.</string>
+    <string name="profile_use_crl_label">CRLs zur Zertifikatsprüfung verwenden</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Strikte Zertifikatsprüfung verwenden</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">RSA/PSS Signaturen verwenden</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">Split-Tunneling</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist</string>
     <string name="profile_proposals_ike_hint">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).</string>
     <string name="profile_proposals_esp_label">IPsec/ESP Algorithmen</string>
     <string name="profile_proposals_esp_hint">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.</string>
-    <string name="profile_import">VPN Profile importieren</string>
+    <string name="profile_import">VPN Profil importieren</string>
     <string name="profile_import_failed">VPN Profil-Import fehlgeschlagen</string>
     <string name="profile_import_failed_detail">VPN Profil-Import fehlgeschlagen: %1$s</string>
     <string name="profile_import_failed_not_found">Datei nicht gefunden</string>
     <string name="profile_import_exists">Dieses VPN Profil existiert bereits, die bestehenden Einstellungen werden ersetzt.</string>
     <string name="profile_cert_import">Zertifikat aus VPN Profil importieren</string>
     <string name="profile_cert_alias">Zertifikat für \"%1$s\"</string>
+    <string name="profile_profile_id">Profil-ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">Ein Wert wird benötigt, um die Verbindung aufbauen zu können</string>
     <string name="alert_text_no_input_username">Bitte geben Sie Ihren Benutzernamen ein</string>
     <string name="state_disconnecting">Trennen&#8230;</string>
     <string name="state_disabled">Kein aktives Profil</string>
     <string name="state_error">Fehler</string>
+    <string name="dismiss">Ausblenden</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">Assessment:</string>
     <string name="login_username">Benutzername</string>
     <string name="login_password">Passwort</string>
     <string name="login_confirm">Verbinden</string>
-    <string name="error_introduction">Fehler beim Aufsetzen des VPN:</string>
-    <string name="error_lookup_failed">Server-Adresse konnte nicht aufgelöst werden.</string>
-    <string name="error_unreachable">Server ist nicht erreichbar.</string>
-    <string name="error_peer_auth_failed">Authentifizierung des Servers ist fehlgeschlagen.</string>
-    <string name="error_auth_failed">Benutzerauthentifizierung ist fehlgeschlagen.</string>
-    <string name="error_assessment_failed">Sicherheitsassessment ist fehlgeschlagen.</string>
-    <string name="error_generic">Unbekannter Fehler während des Verbindens.</string>
+    <string name="error_format">Fehler beim Aufsetzen des VPN: %1$s.</string>
+    <string name="error_lookup_failed">Server-Adresse konnte nicht aufgelöst werden</string>
+    <string name="error_unreachable">Server ist nicht erreichbar</string>
+    <string name="error_peer_auth_failed">Authentifizierung des Servers ist fehlgeschlagen</string>
+    <string name="error_auth_failed">Benutzerauthentifizierung ist fehlgeschlagen</string>
+    <string name="error_assessment_failed">Sicherheitsassessment ist fehlgeschlagen</string>
+    <string name="error_generic">Unbekannter Fehler während des Verbindens</string>
+    <string name="error_password_missing">Passwort nicht verfügbar</string>
+    <string name="error_certificate_unavailable">Benutzer-Zertifikat nicht verfügbar</string>
     <string name="vpn_connected">VPN verbunden</string>
     <string name="vpn_profile_connected">Dieses VPN Profil ist momentan verbunden!</string>
     <string name="reconnect">Neu verbinden</string>
     <string name="disconnect_question">VPN Verbindung trennen?</string>
     <string name="disconnect_active_connection">Dies trennt die aktuelle VPN Verbindung!</string>
     <string name="connect">Verbinden</string>
+    <string name="retry">Wiederholen</string>
+    <plurals name="retry_in">
+        <item quantity="one">Wiederholen in %1$d Sekunde</item>
+        <item quantity="other">Wiederholen in %1$d Sekunden</item>
+    </plurals>
+    <string name="cancel_retry">Wiederholen abbrechen</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">VPN umschalten</string>
+    <string name="tile_connect">VPN verbinden</string>
+    <string name="tile_disconnect">VPN trennen</string>
 
 </resources>
index 61fd3f09eefdaae7008e92604b249cf758a2732b..6297ceca18e3baba79230d20e28cbfe3648dee84 100644 (file)
     <string name="search">Szukaj</string>
     <string name="vpn_not_supported_title">Nie obsługiwany VPN</string>
     <string name="vpn_not_supported">Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem.</string>
-    <string name="vpn_not_supported_during_lockdown">Polączenia nie sa możliwe w trybie zamkniętym</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">Wczytywanie&#8230;</string>
     <string name="profile_not_found">Nie znaleziono profilu</string>
     <string name="strongswan_shortcut">Skrót strongSwan</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">Log</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">Split tunneling</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
     <string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
     <string name="profile_cert_import">Import certificate from VPN profile</string>
     <string name="profile_cert_alias">Certificate for \"%1$s\"</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
     <string name="alert_text_no_input_username">Wprowadź swoją nazwę użytkownika</string>
     <string name="state_disconnecting">Przerywam połączenie&#8230;</string>
     <string name="state_disabled">Brak aktywnego VPN</string>
     <string name="state_error">Błąd</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">Assessment:</string>
     <string name="login_username">Użytkownik</string>
     <string name="login_password">Hasło</string>
     <string name="login_confirm">Połącz</string>
-    <string name="error_introduction">Nie udało się utworzyć tunelu VPN:</string>
+    <string name="error_format">Nie udało się utworzyć tunelu VPN: %1$s.</string>
     <string name="error_lookup_failed">Nie znaleziono adresu serwer</string>
     <string name="error_unreachable">Serwer jest nieosiągalna</string>
     <string name="error_peer_auth_failed">Błąd przy weryfikacji serwer</string>
     <string name="error_auth_failed">Błąd przy autoryzacji użytkownika</string>
-    <string name="error_assessment_failed">Security assessment failed.</string>
+    <string name="error_assessment_failed">Security assessment failed</string>
     <string name="error_generic">Nieznany błąd w czasie połączenia</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">Połączenie z VPN</string>
     <string name="vpn_profile_connected">Ten profil VPN jest obecnie połaczony!</string>
     <string name="reconnect">Połączyć ponownie</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">Połącz</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index a2b3ada45df2b2caa6da504c06e534ca08d14158..b26ca1e8c351830eda0b9cee6a4b6f2a238dd3a5 100644 (file)
     <string name="search">Поиск</string>
     <string name="vpn_not_supported_title">VPN не поддерживается</string>
     <string name="vpn_not_supported">Ваше устройство не поддерживат VPN приложение.\nПожалуйста свяжитесь с производителем.</string>
-    <string name="vpn_not_supported_during_lockdown">VPN соединения не поддерживаются в режиме lockdown.</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">Загрузка&#8230;</string>
     <string name="profile_not_found">Профиль не найден</string>
     <string name="strongswan_shortcut">Ссылка на strongSwan</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">Журнал</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">Split tunneling</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
     <string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
     <string name="profile_cert_import">Import certificate from VPN profile</string>
     <string name="profile_cert_alias">Certificate for \"%1$s\"</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
     <string name="alert_text_no_input_username">Пожалуйста введите имя пользователя</string>
     <string name="state_disconnecting">Отключение&#8230;</string>
     <string name="state_disabled">Нет активных VPN</string>
     <string name="state_error">Ошибка</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">Assessment:</string>
     <string name="login_username">Логин</string>
     <string name="login_password">Пароль</string>
     <string name="login_confirm">Соединить</string>
-    <string name="error_introduction">Ошибка подключения к VPN:</string>
-    <string name="error_lookup_failed">Не найден адрес сервер.</string>
-    <string name="error_unreachable">Сервер недоступен.</string>
-    <string name="error_peer_auth_failed">Ошибка авторизаци при подключении к сервер.</string>
-    <string name="error_auth_failed">Ошибка авторизации пользователя.</string>
-    <string name="error_assessment_failed">Security assessment failed.</string>
-    <string name="error_generic">Неизвестная ошибка.</string>
+    <string name="error_format">Ошибка подключения к VPN: %1$s.</string>
+    <string name="error_lookup_failed">Не найден адрес сервер</string>
+    <string name="error_unreachable">Сервер недоступен</string>
+    <string name="error_peer_auth_failed">Ошибка авторизаци при подключении к сервер</string>
+    <string name="error_auth_failed">Ошибка авторизации пользователя</string>
+    <string name="error_assessment_failed">Security assessment failed</string>
+    <string name="error_generic">Неизвестная ошибка</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">Соединение с VPN установлено</string>
     <string name="vpn_profile_connected">Подключение к этому профилю VPN уже существует!</string>
     <string name="reconnect">Переподключить</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">Соединить</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index bfe4719cda61da01af1e512eb07df6b0360e8883..53eb2ba469bf936b9950d3bbe681f293fb0c439e 100644 (file)
     <string name="search">Пошук</string>
     <string name="vpn_not_supported_title">VPN не підтримуеться</string>
     <string name="vpn_not_supported">Ваш пристрій не підтримує VPN.\nЗв\'яжіться з виробником.</string>
-    <string name="vpn_not_supported_during_lockdown">VPN з\'єднання не пітримується у режимі lockdown.</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">Завантаження&#8230;</string>
     <string name="profile_not_found">Профіль не знайдено</string>
     <string name="strongswan_shortcut">strongSwan посилання</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">Журнал</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">Split tunneling</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
     <string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
     <string name="profile_cert_import">Import certificate from VPN profile</string>
     <string name="profile_cert_alias">Certificate for \"%1$s\"</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
     <string name="alert_text_no_input_username">Введіть ім\'я користувача </string>
     <string name="state_disconnecting">Роз\'єднання&#8230;</string>
     <string name="state_disabled">Немає активних VPN</string>
     <string name="state_error">Помилка</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">Assessment:</string>
     <string name="login_username">Логін</string>
     <string name="login_password">Пароль</string>
     <string name="login_confirm">Підключити</string>
-    <string name="error_introduction">Помилка підлючення VPN:</string>
-    <string name="error_lookup_failed">Помилка пошуку адреси сервер.</string>
-    <string name="error_unreachable">Сервер зв\'язку зі шлюзом.</string>
-    <string name="error_peer_auth_failed">Помилка перевірки данних аутентифікації сервер.</string>
-    <string name="error_auth_failed">Помилка аутентифікації користувача.</string>
-    <string name="error_assessment_failed">Security assessment failed.</string>
-    <string name="error_generic">Невідома помилка під час підключення.</string>
+    <string name="error_format">Помилка підлючення VPN: %1$s.</string>
+    <string name="error_lookup_failed">Помилка пошуку адреси сервер</string>
+    <string name="error_unreachable">Сервер зв\'язку зі шлюзом</string>
+    <string name="error_peer_auth_failed">Помилка перевірки данних аутентифікації сервер</string>
+    <string name="error_auth_failed">Помилка аутентифікації користувача</string>
+    <string name="error_assessment_failed">Security assessment failed</string>
+    <string name="error_generic">Невідома помилка під час підключення</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">VPN підключено</string>
     <string name="vpn_profile_connected">Цей VPN профіль зараз підключений!</string>
     <string name="reconnect">Перепідключитися</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">Підключити</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index bda7324437d232a31645c227e81f18b46a898efe..85d536f126099154fd672cca65c86420517f4124 100644 (file)
     <string name="search">搜索</string>
     <string name="vpn_not_supported_title">无法支持VPN</string>
     <string name="vpn_not_supported">您的设备无法支持VPN应用。\n请联系供应商。</string>
-    <string name="vpn_not_supported_during_lockdown">锁定模式下无法支持VPN连接</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">载入中&#8230;</string>
     <string name="profile_not_found">未找到配置</string>
     <string name="strongswan_shortcut">strongSwan快捷方式</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">日志</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">拆分隧道</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">屏蔽不通过VPN的IPV4流量</string>
     <string name="profile_import_exists">此VPN配置已经存在,当前设定将被覆盖。</string>
     <string name="profile_cert_import">从VPN配置导入证书</string>
     <string name="profile_cert_alias">\"%1$s\" 所对应的证书</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">必填信息以初始化连接</string>
     <string name="alert_text_no_input_username">请输入您的用户名</string>
     <string name="state_disconnecting">断开连接中&#8230;</string>
     <string name="state_disabled">无活跃VPN</string>
     <string name="state_error">错误</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">评估详情:</string>
     <string name="login_username">用户名</string>
     <string name="login_password">密码</string>
     <string name="login_confirm">连接</string>
-    <string name="error_introduction">无法建立VPN:</string>
-    <string name="error_lookup_failed">服务器地址查找失败。</string>
-    <string name="error_unreachable">服务器地址无法连接。</string>
-    <string name="error_peer_auth_failed">核验服务器鉴权失败。</string>
-    <string name="error_auth_failed">用户鉴权失败。</string>
-    <string name="error_assessment_failed">可靠性评估失败。</string>
-    <string name="error_generic">连接中遭遇未知失败。</string>
+    <string name="error_format">无法建立VPN:%1$s。</string>
+    <string name="error_lookup_failed">服务器地址查找失败</string>
+    <string name="error_unreachable">服务器地址无法连接</string>
+    <string name="error_peer_auth_failed">核验服务器鉴权失败</string>
+    <string name="error_auth_failed">用户鉴权失败</string>
+    <string name="error_assessment_failed">可靠性评估失败</string>
+    <string name="error_generic">连接中遭遇未知失败</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">VPN已连接</string>
     <string name="vpn_profile_connected">此VPN配置目前已连接。</string>
     <string name="reconnect">重连</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">连接</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index 2d4992333563be09fc93137703382da26eb4fd27..9299d241e06e01de9f829789621a4027d9ced8ee 100644 (file)
     <string name="search">搜尋</string>
     <string name="vpn_not_supported_title">無法支援VPN</string>
     <string name="vpn_not_supported">您的設備無法使用VPN。\n請聯絡供應商。</string>
-    <string name="vpn_not_supported_during_lockdown">鎖定模式無法連線VPN</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">載入中&#8230;</string>
     <string name="profile_not_found">沒有找到設定檔</string>
     <string name="strongswan_shortcut">strongSwan快速選單</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">日誌</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">拆分隧道</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">屏蔽不通过VPN的IPV4流量</string>
     <string name="profile_import_exists">這個VPN設定檔已經存在,當前設定檔會被覆蓋。</string>
     <string name="profile_cert_import">從VPN設定檔匯入憑證</string>
     <string name="profile_cert_alias">\"%1$s\" 對應的憑證</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">請填寫必要訊息才能初始化連線</string>
     <string name="alert_text_no_input_username">請輸入您的用戶名稱</string>
     <string name="state_disconnecting">結束連線中&#8230;</string>
     <string name="state_disabled">無運作中的VPN</string>
     <string name="state_error">錯誤</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">評估詳情:</string>
     <string name="login_username">用戶名稱</string>
     <string name="login_password">密碼</string>
     <string name="login_confirm">連線</string>
-    <string name="error_introduction">無法建立VPN:</string>
-    <string name="error_lookup_failed">伺服器位置查詢失敗。</string>
-    <string name="error_unreachable">伺服器位置無法連線。</string>
-    <string name="error_peer_auth_failed">驗證伺服器授權失敗。</string>
-    <string name="error_auth_failed">用戶授權失敗。</string>
-    <string name="error_assessment_failed">穩定性評估失敗。</string>
-    <string name="error_generic">連線中遇到不明錯誤。</string>
+    <string name="error_format">無法建立VPN:%1$s。</string>
+    <string name="error_lookup_failed">伺服器位置查詢失敗</string>
+    <string name="error_unreachable">伺服器位置無法連線</string>
+    <string name="error_peer_auth_failed">驗證伺服器授權失敗</string>
+    <string name="error_auth_failed">用戶授權失敗</string>
+    <string name="error_assessment_failed">穩定性評估失敗</string>
+    <string name="error_generic">連線中遇到不明錯誤</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">VPN已連線</string>
     <string name="vpn_profile_connected">這個VPN設定檔目前已經連線。</string>
     <string name="reconnect">重新連線</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">連線</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index eedfe239769fbd58107f8985888e2e90c775382d..11d4e24a2c6031ccebeece973ac8cfd69ff64c32 100644 (file)
@@ -22,7 +22,7 @@
         name="primary">#A2042C</color>
 
     <color
-        name="primary_dark">#000000</color>
+        name="primary_dark">#323232</color>
 
     <color
         name="error_text">#D9192C</color>
index dd22d3be7184372e5ba7f3ccbd3f3f7ee018c992..fb8cf8559987460311cbf38a9c4fadc5f5ca6d10 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    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
     <string name="search">Search</string>
     <string name="vpn_not_supported_title">VPN not supported</string>
     <string name="vpn_not_supported">Your device does not support VPN applications.\nPlease contact the manufacturer.</string>
-    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported in lockdown mode.</string>
+    <string name="vpn_not_supported_during_lockdown">VPN connections are not supported if a built-in VPN has the always-on feature enabled.</string>
+    <string name="vpn_not_supported_no_permission">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.</string>
     <string name="loading">Loading&#8230;</string>
     <string name="profile_not_found">Profile not found</string>
     <string name="strongswan_shortcut">strongSwan shortcut</string>
+    <string name="permanent_notification_name">VPN connection state</string>
+    <string name="permanent_notification_description">Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background.</string>
+
+    <!-- Settings -->
+    <string name="pref_title">Settings</string>
+    <string name="pref_default_vpn_profile">Default VPN profile</string>
+    <string name="pref_default_vpn_profile_mru">Connect to most recently used profile</string>
 
     <!-- Log view -->
     <string name="log_title">Log</string>
     <string name="profile_nat_keepalive_hint">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.</string>
     <string name="profile_cert_req_label">Send certificate requests</string>
     <string name="profile_cert_req_hint">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.</string>
+    <string name="profile_use_ocsp_label">Use OCSP to check certificate</string>
+    <string name="profile_use_ocsp_hint">Use the Online Certificate Status Protocol (OCSP), if available, to check that the server certificate has not been revoked.</string>
+    <string name="profile_use_crl_label">Use CRLs to check certificate</string>
+    <string name="profile_use_crl_hint">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.</string>
+    <string name="profile_strict_revocation_label">Use strict revocation checking</string>
+    <string name="profile_strict_revocation_hint">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).</string>
+    <string name="profile_rsa_pss_label">Use RSA/PSS signatures</string>
+    <string name="profile_rsa_pss_hint">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.</string>
     <string name="profile_split_tunneling_label">Split tunneling</string>
     <string name="profile_split_tunneling_intro">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).</string>
     <string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
     <string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
     <string name="profile_cert_import">Import certificate from VPN profile</string>
     <string name="profile_cert_alias">Certificate for \"%1$s\"</string>
+    <string name="profile_profile_id">Profile ID</string>
     <!-- Warnings/Notifications in the details view -->
     <string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
     <string name="alert_text_no_input_username">Please enter your username </string>
     <string name="state_disconnecting">Disconnecting&#8230;</string>
     <string name="state_disabled">No active VPN</string>
     <string name="state_error">Error</string>
+    <string name="dismiss">Dismiss</string>
 
     <!-- IMC state fragment -->
     <string name="imc_state_label">Assessment:</string>
     <string name="login_username">Username</string>
     <string name="login_password">Password</string>
     <string name="login_confirm">Connect</string>
-    <string name="error_introduction">Failed to establish VPN:</string>
-    <string name="error_lookup_failed">Server address lookup failed.</string>
-    <string name="error_unreachable">Server is unreachable.</string>
-    <string name="error_peer_auth_failed">Verifying server authentication failed.</string>
-    <string name="error_auth_failed">User authentication failed.</string>
-    <string name="error_assessment_failed">Security assessment failed.</string>
-    <string name="error_generic">Unspecified failure while connecting.</string>
+    <string name="error_format">Failed to establish VPN: %1$s.</string>
+    <string name="error_lookup_failed">Server address lookup failed</string>
+    <string name="error_unreachable">Server is unreachable</string>
+    <string name="error_peer_auth_failed">Verifying server authentication failed</string>
+    <string name="error_auth_failed">User authentication failed</string>
+    <string name="error_assessment_failed">Security assessment failed</string>
+    <string name="error_generic">Unspecified failure while connecting</string>
+    <string name="error_password_missing">Password unavailable</string>
+    <string name="error_certificate_unavailable">Client certificate unavailable</string>
     <string name="vpn_connected">VPN connected</string>
     <string name="vpn_profile_connected">This VPN profile is currently connected!</string>
     <string name="reconnect">Reconnect</string>
     <string name="disconnect_question">Disconnect VPN?</string>
     <string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
     <string name="connect">Connect</string>
+    <string name="retry">Retry</string>
+    <plurals name="retry_in">
+        <item quantity="one">Retry in %1$d second</item>
+        <item quantity="other">Retry in %1$d seconds</item>
+    </plurals>
+    <string name="cancel_retry">Cancel retry</string>
+
+    <!-- Quick Settings tile -->
+    <string name="tile_default">Toggle VPN</string>
+    <string name="tile_connect">Connect VPN</string>
+    <string name="tile_disconnect">Disconnect VPN</string>
 
 </resources>
index 82a957813b527bfa07d2b534d253d03e2e5e697a..4b7cc4c5c9816a03153a6416a74a6467d0dd2e62 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-    Copyright (C) 2012-2016 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
         <item name="colorPrimary">@color/primary</item>
         <item name="colorPrimaryDark">@color/primary_dark</item>
         <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
+        <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
     </style>
 
     <style name="AlertDialogTheme.Base" parent="Theme.AppCompat.Dialog.Alert">
         <item name="colorAccent">@color/accent</item>
     </style>
 
+    <style name="TransparentActivity" parent="Theme.AppCompat.NoActionBar">
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
 </resources>
diff --git a/src/frontends/android/app/src/main/res/xml/settings.xml b/src/frontends/android/app/src/main/res/xml/settings.xml
new file mode 100644 (file)
index 0000000..908a888
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+    This program is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ListPreference
+        android:key="pref_default_vpn_profile"
+        android:title="@string/pref_default_vpn_profile"
+        android:summary="@string/pref_default_vpn_profile_mru" />
+
+</PreferenceScreen>
index e52ed0aca1f86bc66121face572732e3c7a6fcea..365aa1177688d1142fd0b41f141128c3d67c4c5d 100644 (file)
@@ -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()
     }
 }
index 8c08a361cfea5d61e130b9306348fa906f4c18c2..8a32ae179a55e36b8309e7317f802d03b02db9ff 100644 (file)
@@ -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
index 2a499db405a6c22dd3a851339d7267b3e1a71cd1..7c4b692a5d561f3009962b81ffa0f09644a01291 100644 (file)
@@ -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;
 }
index fe7eaa765acb64dd9c0ae48277fa656ce758321a..f688577e164d879093524eac3c5510e059636ffe 100644 (file)
@@ -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,
                        },
                },
index f8e78ac0c9dd1e5b08d6d801a10376b9220813d6..68292e3cd5aaac1833d17cc5e728232133e54160 100644 (file)
@@ -27,6 +27,7 @@
 #include <credentials/certificates/ocsp_response.h>
 #include <credentials/sets/ocsp_response_wrapper.h>
 #include <selectors/traffic_selector.h>
+#include <threading/spinlock.h>
 
 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;
 }
index 82cbde26b15c5919c85536857712b4f1c249dfcd..9128787f18424e985e20bea9e0429cbfc92f0818 100644 (file)
@@ -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.
         */
index a973b1adc092915df9b6a4c19cf7045655c1a6c1..c23b361ec198ff027d657dfbd40f630cb4b8ea38 100644 (file)
  */
 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)