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 {
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'
}
<?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
<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"
</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"
/*
- * 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
/* 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;
/*
- * 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
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),
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)
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
{
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))));
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());
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;
/*
- * 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
import android.annotation.TargetApi;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
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;
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;
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;
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() {
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;
}
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 */
/* the thread is started when the service is bound */
bindService(new Intent(this, VpnStateService.class),
mServiceConnection, Service.BIND_AUTO_CREATE);
+
+ createNotificationChannel();
}
@Override
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());
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());
{
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();
+ }
}
}
}
*/
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));
+ }
+ });
}
/**
*/
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
*/
{
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
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;
{
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));
}
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;
* @return the private key
* @throws InterruptedException
* @throws KeyChainException
- * @throws CertificateEncodingException
*/
private PrivateKey getUserKey() throws KeyChainException, InterruptedException
{
*/
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());
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()
}
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();
+ }
+ }
+ }
}
/**
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)
/*
- * 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
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);
+ }
}
}
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;
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
{
LOOKUP_FAILED,
UNREACHABLE,
GENERIC_ERROR,
+ PASSWORD_MISSING,
+ CERTIFICATE_UNAVAILABLE,
}
/**
{
/* 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
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.
*
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.
*
*/
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
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.
@Override
public Boolean call() throws Exception
{
+ resetRetryTimer();
VpnStateService.this.mConnectionID++;
VpnStateService.this.mProfile = profile;
VpnStateService.this.mState = State.CONNECTING;
@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;
{
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;
}
}
});
}
+
+ /**
+ * 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;
+ }
+ }
}
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;
}
@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;
}
}
{
private int mColorIsolate;
private int mColorBlock;
+ private boolean mVisible;
private TextView mStateView;
private TextView mAction;
private LinearLayout mButton;
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = ((VpnStateService.LocalBinder)service).getService();
- mService.registerListener(ImcStateFragment.this);
+ if (mVisible)
+ {
+ mService.registerListener(ImcStateFragment.this);
+ updateView();
+ }
}
};
public void onResume()
{
super.onResume();
+ mVisible = true;
if (mService != null)
{
mService.registerListener(this);
public void onPause()
{
super.onPause();
+ mVisible = false;
if (mService != null)
{
mService.unregisterListener(this);
/*
- * 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;
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)
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());
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();
}
{
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);
- }
}
/**
@Override
public void run()
{
- stopLogReader();
- startLogReader();
+ mLogAdapter.restart();
}
});
}
+++ /dev/null
-/*
- * 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;
- }
- }
-}
/*
- * 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;
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;
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);
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)
{
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);
}
/**
*/
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);
- }
}
/**
}
}
- /**
- * 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
*/
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
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;
{
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)
{
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;
+ }
}
}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
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;
private EditText mIkeProposal;
private TextInputLayoutHelper mEspProposalWrap;
private EditText mEspProposal;
+ private TextView mProfileIdLabel;
+ private TextView mProfileId;
@Override
public void onCreate(Bundle savedInstanceState)
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);
/* 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);
}
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);
+ }
}
/**
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);
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();
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);
/*
- * 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
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;
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;
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;
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);
{
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)
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);
public void handleProfile(ProfileLoadResult data)
{
- mProgress.dismiss();
+ mProgressBar.hide();
mProfile = null;
if (data != null && data.ThrownException == null)
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)
{
{
profile.setLocalId(local.optString("id", null));
profile.PKCS12 = decodeBase64(local.optString("p12", null));
+
+ if (local.optBoolean("rsa-pss", false))
+ {
+ flags |= VpnProfile.FLAGS_RSA_PSS;
+ }
}
}
/*
- * 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
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;
@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();
}
}
/*
- * 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
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;
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;
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()
{
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = ((VpnStateService.LocalBinder)service).getService();
- mService.registerListener(VpnStateFragment.this);
- updateView();
+ if (mVisible)
+ {
+ mService.registerListener(VpnStateFragment.this);
+ updateView();
+ }
}
};
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
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;
}
public void onStart()
{
super.onStart();
+ mVisible = true;
if (mService != null)
{
mService.registerListener(this);
public void onStop()
{
super.onStop();
+ mVisible = false;
if (mService != null)
{
mService.unregisterListener(this);
}
- hideErrorDialog();
}
@Override
VpnProfile profile = mService.getProfile();
State state = mService.getState();
ErrorState error = mService.getErrorState();
- ImcState imcState = mService.getImcState();
String name = "";
if (getActivity() == null)
name = profile.getName();
}
- if (reportError(connectionID, name, error, imcState))
+ if (reportError(connectionID, name, error))
{
return;
}
mProfileNameView.setText(name);
+ mProgress.setIndeterminate(true);
switch (state)
{
}
}
- 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;
}
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();
- }
}
--- /dev/null
+/*
+ * 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();
+ }
+}
/*
- * 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
*/
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";
}
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) \
-Wno-strict-aliasing \
-Wno-unused-parameter \
-Wno-missing-field-initializers \
+ -Wno-self-assign \
-DHAVE___BOOL \
-DHAVE_STDBOOL_H \
-DHAVE_ALLOCA_H \
-# 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
ANDROID_JELLY_BEAN = 16,
ANDROID_JELLY_BEAN_MR1 = 17,
ANDROID_JELLY_BEAN_MR2 = 18,
+ ANDROID_LOLLIPOP = 21,
} android_sdk_version_t;
/**
/*
- * 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
#include <errno.h>
#include <unistd.h>
+#include "android_jni.h"
#include "android_service.h"
#include "android_dns_proxy.h"
#include "../charonservice.h"
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);
{
peer_cfg->destroy(peer_cfg);
charonservice->update_status(charonservice,
- CHARONSERVICE_GENERIC_ERROR);
+ CHARONSERVICE_CERTIFICATE_UNAVAILABLE);
return JOB_REQUEUE_NONE;
}
}
}
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);
{ /* 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);
/*
- * 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
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
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);
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,
/* set options before initializing other libraries that might read them */
logfile = androidjni_convert_jstring(env, jlogfile);
+
set_options(logfile);
free(logfile);
{
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);
CHARONSERVICE_PEER_AUTH_ERROR,
CHARONSERVICE_LOOKUP_ERROR,
CHARONSERVICE_UNREACHABLE_ERROR,
+ CHARONSERVICE_CERTIFICATE_UNAVAILABLE,
CHARONSERVICE_GENERIC_ERROR,
};
--- /dev/null
+<?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>
<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" >
<?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>
--- /dev/null
+<?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" />
<?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>
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"
<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>
android:title="@string/show_log"
app:showAsAction="withText" />
+ <item
+ android:id="@+id/menu_settings"
+ android:title="@string/pref_title"
+ app:showAsAction="withText" />
+
</menu>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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
--- /dev/null
+<?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
<?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…</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>
<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…</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>
<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…</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…</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>
<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">Загрузка…</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">Отключение…</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>
<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">Завантаження…</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">Роз\'єднання…</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>
<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">载入中…</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">断开连接中…</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>
<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">載入中…</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">結束連線中…</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>
name="primary">#A2042C</color>
<color
- name="primary_dark">#000000</color>
+ name="primary_dark">#323232</color>
<color
name="error_text">#D9192C</color>
<?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…</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…</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>
<?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>
--- /dev/null
+<?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>
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()
}
}
-#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
* 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
*
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
{
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),
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;
}
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)
{
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
+ .reload = _reload,
.destroy = _destroy,
},
},
#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;
*/
bool enable_crl;
+ /**
+ * Lock to access flags
+ */
+ spinlock_t *lock;
};
/**
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))
{
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))
{
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);
}
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;
}
/*
+ * Copyright (C) 2018 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
* Copyright (C) 2010 Martin Willi
* Copyright (C) 2010 revosec AG
*
*/
cert_validator_t validator;
+ /**
+ * Reload the configuration
+ */
+ void (*reload)(revocation_validator_t *this);
+
/**
* Destroy a revocation_validator_t.
*/
*/
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
#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)