Merge branch 'android-updates'

This adds support for configuring split-tunneling and per-app VPN, adds
a simple HTTP/S fetcher and enables the revocation plugin, makes the log
view more efficient, imports profiles via SAF and changes multiple other
things.
This commit is contained in:
Tobias Brunner 2017-07-03 10:32:35 +02:00
commit 209a611530
54 changed files with 3446 additions and 122 deletions

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
buildToolsVersion '25.0.0'
defaultConfig {
applicationId "org.strongswan.android"

View File

@ -48,6 +48,10 @@
android:name=".ui.TrustedCertificatesActivity"
android:label="@string/trusted_certs_title" >
</activity>
<activity
android:name=".ui.SelectedApplicationsActivity"
android:label="@string/profile_select_apps" >
</activity>
<activity
android:name=".ui.LogActivity"
android:label="@string/log_title" >

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@ -18,6 +18,11 @@
package org.strongswan.android.data;
import android.text.TextUtils;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
public class VpnProfile implements Cloneable
@ -27,12 +32,32 @@ public class VpnProfile implements Cloneable
public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2;
private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
private String mRemoteId, mLocalId;
private String mRemoteId, mLocalId, mExcludedSubnets, mIncludedSubnets, mSelectedApps;
private Integer mMTU, mPort, mSplitTunneling;
private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
private VpnType mVpnType;
private UUID mUUID;
private long mId = -1;
public enum SelectedAppsHandling
{
SELECTED_APPS_DISABLE(0),
SELECTED_APPS_EXCLUDE(1),
SELECTED_APPS_ONLY(2);
private Integer mValue;
SelectedAppsHandling(int value)
{
mValue = value;
}
public Integer getValue()
{
return mValue;
}
}
public VpnProfile()
{
this.mUUID = UUID.randomUUID();
@ -168,6 +193,74 @@ public class VpnProfile implements Cloneable
this.mPort = port;
}
public void setExcludedSubnets(String excludedSubnets)
{
this.mExcludedSubnets = excludedSubnets;
}
public String getExcludedSubnets()
{
return mExcludedSubnets;
}
public void setIncludedSubnets(String includedSubnets)
{
this.mIncludedSubnets = includedSubnets;
}
public String getIncludedSubnets()
{
return mIncludedSubnets;
}
public void setSelectedApps(String selectedApps)
{
this.mSelectedApps = selectedApps;
}
public void setSelectedApps(SortedSet<String> selectedApps)
{
this.mSelectedApps = selectedApps.size() > 0 ? TextUtils.join(" ", selectedApps) : null;
}
public String getSelectedApps()
{
return mSelectedApps;
}
public SortedSet<String> getSelectedAppsSet()
{
TreeSet<String> set = new TreeSet<>();
if (!TextUtils.isEmpty(mSelectedApps))
{
set.addAll(Arrays.asList(mSelectedApps.split("\\s+")));
}
return set;
}
public void setSelectedAppsHandling(SelectedAppsHandling selectedAppsHandling)
{
this.mSelectedAppsHandling = selectedAppsHandling;
}
public void setSelectedAppsHandling(Integer value)
{
mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
for (SelectedAppsHandling handling : SelectedAppsHandling.values())
{
if (handling.mValue.equals(value))
{
mSelectedAppsHandling = handling;
break;
}
}
}
public SelectedAppsHandling getSelectedAppsHandling()
{
return mSelectedAppsHandling;
}
public Integer getSplitTunneling()
{
return mSplitTunneling;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@ -17,10 +17,6 @@
package org.strongswan.android.data;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -30,6 +26,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class VpnProfileDataSource
{
private static final String TAG = VpnProfileDataSource.class.getSimpleName();
@ -47,6 +47,10 @@ public class VpnProfileDataSource
public static final String KEY_SPLIT_TUNNELING = "split_tunneling";
public static final String KEY_LOCAL_ID = "local_id";
public static final String KEY_REMOTE_ID = "remote_id";
public static final String KEY_EXCLUDED_SUBNETS = "excluded_subnets";
public static final String KEY_INCLUDED_SUBNETS = "included_subnets";
public static final String KEY_SELECTED_APPS = "selected_apps";
public static final String KEY_SELECTED_APPS_LIST = "selected_apps_list";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDatabase;
@ -55,7 +59,7 @@ public class VpnProfileDataSource
private static final String DATABASE_NAME = "strongswan.db";
private static final String TABLE_VPNPROFILE = "vpnprofile";
private static final int DATABASE_VERSION = 9;
private static final int DATABASE_VERSION = 12;
public static final String DATABASE_CREATE =
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
@ -72,7 +76,11 @@ public class VpnProfileDataSource
KEY_PORT + " INTEGER," +
KEY_SPLIT_TUNNELING + " INTEGER," +
KEY_LOCAL_ID + " TEXT," +
KEY_REMOTE_ID + " TEXT" +
KEY_REMOTE_ID + " TEXT," +
KEY_EXCLUDED_SUBNETS + " TEXT," +
KEY_INCLUDED_SUBNETS + " TEXT," +
KEY_SELECTED_APPS + " INTEGER," +
KEY_SELECTED_APPS_LIST + " TEXT" +
");";
private static final String[] ALL_COLUMNS = new String[] {
KEY_ID,
@ -89,6 +97,10 @@ public class VpnProfileDataSource
KEY_SPLIT_TUNNELING,
KEY_LOCAL_ID,
KEY_REMOTE_ID,
KEY_EXCLUDED_SUBNETS,
KEY_INCLUDED_SUBNETS,
KEY_SELECTED_APPS,
KEY_SELECTED_APPS_LIST,
};
private static class DatabaseHelper extends SQLiteOpenHelper
@ -151,6 +163,23 @@ public class VpnProfileDataSource
" TEXT;");
updateColumns(db);
}
if (oldVersion < 10)
{
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS +
" TEXT;");
}
if (oldVersion < 11)
{
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS +
" TEXT;");
}
if (oldVersion < 12)
{
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS +
" INTEGER;");
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST +
" TEXT;");
}
}
private void updateColumns(SQLiteDatabase db)
@ -326,6 +355,10 @@ public class VpnProfileDataSource
profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING)));
profile.setLocalId(cursor.getString(cursor.getColumnIndex(KEY_LOCAL_ID)));
profile.setRemoteId(cursor.getString(cursor.getColumnIndex(KEY_REMOTE_ID)));
profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndex(KEY_EXCLUDED_SUBNETS)));
profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndex(KEY_INCLUDED_SUBNETS)));
profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndex(KEY_SELECTED_APPS)));
profile.setSelectedApps(cursor.getString(cursor.getColumnIndex(KEY_SELECTED_APPS_LIST)));
return profile;
}
@ -345,6 +378,10 @@ public class VpnProfileDataSource
values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
values.put(KEY_LOCAL_ID, profile.getLocalId());
values.put(KEY_REMOTE_ID, profile.getRemoteId());
values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets());
values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets());
values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue());
values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps());
return values;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@ -26,6 +26,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
@ -40,6 +41,7 @@ import android.util.Log;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.VpnStateService.ErrorState;
@ -47,6 +49,8 @@ import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
import org.strongswan.android.ui.MainActivity;
import org.strongswan.android.utils.IPRange;
import org.strongswan.android.utils.IPRangeSet;
import org.strongswan.android.utils.SettingsWriter;
import java.io.File;
@ -60,10 +64,12 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.SortedSet;
public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
{
private static final String TAG = CharonVpnService.class.getSimpleName();
public static final String DISCONNECT_ACTION = "org.strongswan.android.CharonVpnService.DISCONNECT";
public static final String LOG_FILE = "charon.log";
public static final int VPN_STATE_NOTIFICATION_ID = 1;
@ -119,18 +125,25 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
if (intent != null)
{
Bundle bundle = intent.getExtras();
VpnProfile profile = null;
if (bundle != null)
if (DISCONNECT_ACTION.equals(intent.getAction()))
{
profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
if (profile != null)
{
String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
profile.setPassword(password);
}
setNextProfile(null);
}
else
{
Bundle bundle = intent.getExtras();
VpnProfile profile = null;
if (bundle != null)
{
profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
if (profile != null)
{
String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
profile.setPassword(password);
}
}
setNextProfile(profile);
}
setNextProfile(profile);
}
return START_NOT_STICKY;
}
@ -230,7 +243,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
mIsDisconnecting = false;
addNotification();
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling());
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile);
if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
{
Log.i(TAG, "charon started");
@ -640,24 +653,22 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
*/
public class BuilderAdapter
{
private final String mName;
private final Integer mSplitTunneling;
private final VpnProfile mProfile;
private VpnService.Builder mBuilder;
private BuilderCache mCache;
private BuilderCache mEstablishedCache;
public BuilderAdapter(String name, Integer splitTunneling)
public BuilderAdapter(VpnProfile profile)
{
mName = name;
mSplitTunneling = splitTunneling;
mBuilder = createBuilder(name);
mCache = new BuilderCache(mSplitTunneling);
mProfile = profile;
mBuilder = createBuilder(mProfile.getName());
mCache = new BuilderCache(mProfile);
}
private VpnService.Builder createBuilder(String name)
{
VpnService.Builder builder = new CharonVpnService.Builder();
builder.setSession(mName);
builder.setSession(name);
/* even though the option displayed in the system dialog says "Configure"
* we just use our main Activity */
@ -754,9 +765,9 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
/* now that the TUN device is created we don't need the current
* builder anymore, but we might need another when reestablishing */
mBuilder = createBuilder(mName);
mBuilder = createBuilder(mProfile.getName());
mEstablishedCache = mCache;
mCache = new BuilderCache(mSplitTunneling);
mCache = new BuilderCache(mProfile);
return fd.detachFd();
}
@ -770,7 +781,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
try
{
Builder builder = createBuilder(mName);
Builder builder = createBuilder(mProfile.getName());
mEstablishedCache.applyData(builder);
fd = builder.establish();
}
@ -793,22 +804,50 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
*/
public class BuilderCache
{
private final List<PrefixedAddress> mAddresses = new ArrayList<PrefixedAddress>();
private final List<PrefixedAddress> mRoutesIPv4 = new ArrayList<PrefixedAddress>();
private final List<PrefixedAddress> mRoutesIPv6 = new ArrayList<PrefixedAddress>();
private final List<IPRange> mAddresses = new ArrayList<>();
private final List<IPRange> mRoutesIPv4 = new ArrayList<>();
private final List<IPRange> mRoutesIPv6 = new ArrayList<>();
private final IPRangeSet mIncludedSubnetsv4 = new IPRangeSet();
private final IPRangeSet mIncludedSubnetsv6 = new IPRangeSet();
private final IPRangeSet mExcludedSubnets;
private final int mSplitTunneling;
private final SelectedAppsHandling mAppHandling;
private final SortedSet<String> mSelectedApps;
private int mMtu;
private boolean mIPv4Seen, mIPv6Seen;
public BuilderCache(Integer splitTunneling)
public BuilderCache(VpnProfile profile)
{
IPRangeSet included = IPRangeSet.fromString(profile.getIncludedSubnets());
for (IPRange range : included)
{
if (range.getFrom() instanceof Inet4Address)
{
mIncludedSubnetsv4.add(range);
}
else if (range.getFrom() instanceof Inet6Address)
{
mIncludedSubnetsv6.add(range);
}
}
mExcludedSubnets = IPRangeSet.fromString(profile.getExcludedSubnets());
Integer splitTunneling = profile.getSplitTunneling();
mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
mAppHandling = profile.getSelectedAppsHandling();
mSelectedApps = profile.getSelectedAppsSet();
}
public void addAddress(String address, int prefixLength)
{
mAddresses.add(new PrefixedAddress(address, prefixLength));
recordAddressFamily(address);
try
{
mAddresses.add(new IPRange(address, prefixLength));
recordAddressFamily(address);
}
catch (UnknownHostException ex)
{
ex.printStackTrace();
}
}
public void addRoute(String address, int prefixLength)
@ -817,11 +856,11 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
if (isIPv6(address))
{
mRoutesIPv6.add(new PrefixedAddress(address, prefixLength));
mRoutesIPv6.add(new IPRange(address, prefixLength));
}
else
{
mRoutesIPv4.add(new PrefixedAddress(address, prefixLength));
mRoutesIPv4.add(new IPRange(address, prefixLength));
}
}
catch (UnknownHostException ex)
@ -857,19 +896,29 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void applyData(VpnService.Builder builder)
{
for (PrefixedAddress address : mAddresses)
for (IPRange address : mAddresses)
{
builder.addAddress(address.mAddress, address.mPrefix);
builder.addAddress(address.getFrom(), address.getPrefix());
}
/* add routes depending on whether split tunneling is allowed or not,
* that is, whether we have to handle and block non-VPN traffic */
if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
{
if (mIPv4Seen)
{ /* split tunneling is used depending on the routes */
for (PrefixedAddress route : mRoutesIPv4)
{ /* split tunneling is used depending on the routes and configuration */
IPRangeSet ranges = new IPRangeSet();
if (mIncludedSubnetsv4.size() > 0)
{
builder.addRoute(route.mAddress, route.mPrefix);
ranges.add(mIncludedSubnetsv4);
}
else
{
ranges.addAll(mRoutesIPv4);
}
ranges.remove(mExcludedSubnets);
for (IPRange subnet : ranges.subnets())
{
builder.addRoute(subnet.getFrom(), subnet.getPrefix());
}
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
@ -887,9 +936,19 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
if (mIPv6Seen)
{
for (PrefixedAddress route : mRoutesIPv6)
IPRangeSet ranges = new IPRangeSet();
if (mIncludedSubnetsv6.size() > 0)
{
builder.addRoute(route.mAddress, route.mPrefix);
ranges.add(mIncludedSubnetsv6);
}
else
{
ranges.addAll(mRoutesIPv6);
}
ranges.remove(mExcludedSubnets);
for (IPRange subnet : ranges.subnets())
{
builder.addRoute(subnet.getFrom(), subnet.getPrefix());
}
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
@ -901,6 +960,41 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
builder.addRoute("::", 0);
}
/* apply selected applications */
if (mSelectedApps.size() > 0)
{
switch (mAppHandling)
{
case SELECTED_APPS_EXCLUDE:
for (String app : mSelectedApps)
{
try
{
builder.addDisallowedApplication(app);
}
catch (PackageManager.NameNotFoundException e)
{
// possible if not configured via GUI or app was uninstalled
}
}
break;
case SELECTED_APPS_ONLY:
for (String app : mSelectedApps)
{
try
{
builder.addAllowedApplication(app);
}
catch (PackageManager.NameNotFoundException e)
{
// possible if not configured via GUI or app was uninstalled
}
}
break;
default:
break;
}
}
builder.setMtu(mMtu);
}
@ -917,18 +1011,23 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
return false;
}
}
private class PrefixedAddress
{
public String mAddress;
public int mPrefix;
/**
* Function called via JNI to determine information about the Android version.
*/
private static String getAndroidVersion()
{
return "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY +
"/" + Build.VERSION.SECURITY_PATCH;
}
public PrefixedAddress(String address, int prefix)
{
this.mAddress = address;
this.mPrefix = prefix;
}
}
/**
* Function called via JNI to determine information about the device.
*/
private static String getDeviceString()
{
return Build.MODEL + " - " + Build.BRAND + "/" + Build.PRODUCT + "/" + Build.MANUFACTURER;
}
/*

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2017 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.logic;
import android.support.annotation.Keep;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@Keep
public class SimpleFetcher
{
public static byte[] fetch(String uri) throws IOException
{
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
try
{
return streamToArray(conn.getInputStream());
}
finally
{
conn.disconnect();
}
}
private static byte[] streamToArray(InputStream in)
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
try
{
while ((len = in.read(buf)) != -1)
{
out.write(buf, 0, len);
}
return out.toByteArray();
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2012-2013 Tobias Brunner
* Hochschule fuer Technik Rapperswil
* Copyright (C) 2012-2017 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
@ -15,16 +15,6 @@
package org.strongswan.android.logic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@ -32,9 +22,19 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.imc.ImcState;
import org.strongswan.android.logic.imc.RemediationInstruction;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
public class VpnStateService extends Service
{
private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>();
private final HashSet<VpnStateListener> mListeners = new HashSet<VpnStateListener>();
private final IBinder mBinder = new LocalBinder();
private long mConnectionID = 0;
private Handler mHandler;
@ -197,10 +197,11 @@ public class VpnStateService extends Service
* VpnService.Builder object the system binds to the service and keeps
* bound until the file descriptor of the TUN device is closed. thus
* calling stopService() here would not stop (destroy) the service yet,
* instead we call startService() with an empty Intent which shuts down
* instead we call startService() with a specific action which shuts down
* the daemon (and closes the TUN device, if any) */
Context context = getApplicationContext();
Intent intent = new Intent(context, CharonVpnService.class);
intent.setAction(CharonVpnService.DISCONNECT_ACTION);
context.startService(intent);
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Hochschule fuer Technik Rapperswil
* Copyright (C) 2012-2017 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
@ -32,6 +32,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringReader;
import java.util.ArrayList;
public class LogFragment extends Fragment implements Runnable
{
@ -121,16 +122,20 @@ public class LogFragment extends Fragment implements Runnable
* 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 line log line to log
* @param lines log lines to log
*/
public void logLine(final String line)
public void logLines(final ArrayList<String> lines)
{
mLogHandler.post(new Runnable() {
@Override
public void run()
{
/* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */
mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n');
mLogView.beginBatchEdit();
for (String line : lines)
{ /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */
mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n');
}
mLogView.endBatchEdit();
/* calling autoScroll() directly does not work, probably because content
* is not yet updated, so we post this to be done later */
mScrollView.post(new Runnable() {
@ -147,18 +152,30 @@ public class LogFragment extends Fragment implements Runnable
@Override
public void run()
{
ArrayList<String> lines = null;
while (mRunning)
{
try
{ /* this works as long as the file is not truncated */
String line = mReader.readLine();
if (line == null)
{ /* wait until there is more to log */
{
if (lines != null)
{
logLines(lines);
lines = null;
}
/* wait until there is more to log */
Thread.sleep(1000);
}
else
{
logLine(line);
if (lines == null)
{
lines = new ArrayList<>();
}
lines.add(line);
}
}
catch (Exception e)
@ -166,6 +183,10 @@ public class LogFragment extends Fragment implements Runnable
break;
}
}
if (lines != null)
{
logLines(lines);
}
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@ -26,6 +26,7 @@ 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;
@ -140,11 +141,25 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
{
menu.removeItem(R.id.menu_import_profile);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.menu_import_profile:
Intent intent = new Intent(this, VpnProfileImportActivity.class);
startActivity(intent);
return true;
case R.id.menu_manage_certs:
Intent certIntent = new Intent(this, TrustedCertificatesActivity.class);
startActivity(certIntent);

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2017 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.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import org.strongswan.android.data.VpnProfileDataSource;
public class SelectedApplicationsActivity extends AppCompatActivity
{
private static final String LIST_TAG = "ApplicationList";
private SelectedApplicationsListFragment mApps;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
FragmentManager fm = getSupportFragmentManager();
mApps = (SelectedApplicationsListFragment)fm.findFragmentByTag(LIST_TAG);
if (mApps == null)
{
mApps = new SelectedApplicationsListFragment();
fm.beginTransaction().add(android.R.id.content, mApps, LIST_TAG).commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home:
prepareResult();
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed()
{
prepareResult();
super.onBackPressed();
}
private void prepareResult()
{
Intent data = new Intent();
data.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, mApps.getSelectedApplications());
setResult(RESULT_OK, data);
}
}

View File

@ -0,0 +1,267 @@
/*
* Copyright (C) 2017 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.Manifest;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Filter;
import android.widget.ListView;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.ui.adapter.SelectedApplicationEntry;
import org.strongswan.android.ui.adapter.SelectedApplicationsAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Pair<List<SelectedApplicationEntry>, List<String>>>, SearchView.OnQueryTextListener
{
private SelectedApplicationsAdapter mAdapter;
private SortedSet<String> mSelection;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mAdapter = new SelectedApplicationsAdapter(getActivity());
setListAdapter(mAdapter);
setListShown(false);
ArrayList<String> selection;
if (savedInstanceState == null)
{
selection = getActivity().getIntent().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
}
else
{
selection = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
}
mSelection = new TreeSet<>(selection);
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelection));
}
/**
* Returns the package names of all selected apps
*/
public ArrayList<String> getSelectedApplications()
{
return new ArrayList<>(mSelection);
}
/**
* Track check state as ListView is unable to do that when using filters
*/
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
item.setSelected(!item.isSelected());
if (item.isSelected())
{
mSelection.add(item.getInfo().packageName);
}
else
{
mSelection.remove(item.getInfo().packageName);
}
}
/**
* Manually set the check state as ListView is unable to track that when using filters
*/
private void setCheckState()
{
for (int i = 0; i < getListView().getCount(); i++)
{
SelectedApplicationEntry item = mAdapter.getItem(i);
getListView().setItemChecked(i, item.isSelected());
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
MenuItem item = menu.add(R.string.search);
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public Loader<Pair<List<SelectedApplicationEntry>, List<String>>> onCreateLoader(int id, Bundle args)
{
return new InstalledPackagesLoader(getActivity(), mSelection);
}
@Override
public void onLoadFinished(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader, Pair<List<SelectedApplicationEntry>, List<String>> data)
{
mAdapter.setData(data.first);
mSelection.removeAll(data.second);
setCheckState();
if (isResumed())
{
setListShown(true);
}
else
{
setListShownNoAnimation(true);
}
}
@Override
public void onLoaderReset(Loader<Pair<List<SelectedApplicationEntry>, List<String>>> loader)
{
mAdapter.setData(null);
}
@Override
public boolean onQueryTextSubmit(String query)
{
return true;
}
@Override
public boolean onQueryTextChange(String newText)
{
String search = TextUtils.isEmpty(newText) ? null : newText;
mAdapter.getFilter().filter(search, new Filter.FilterListener()
{
@Override
public void onFilterComplete(int count)
{
setCheckState();
}
});
return true;
}
public static class InstalledPackagesLoader extends AsyncTaskLoader<Pair<List<SelectedApplicationEntry>, List<String>>>
{
private final PackageManager mPackageManager;
private final SortedSet<String> mSelection;
private Pair<List<SelectedApplicationEntry>, List<String>> mData;
public InstalledPackagesLoader(Context context, SortedSet<String> selection)
{
super(context);
mPackageManager = context.getPackageManager();
mSelection = selection;
}
@Override
public Pair<List<SelectedApplicationEntry>, List<String>> loadInBackground()
{
List<SelectedApplicationEntry> apps = new ArrayList<>();
SortedSet<String> seen = new TreeSet<>();
for (ApplicationInfo info : mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA))
{
/* skip apps that can't access the network anyway */
if (mPackageManager.checkPermission(Manifest.permission.INTERNET, info.packageName) == PackageManager.PERMISSION_GRANTED)
{
SelectedApplicationEntry entry = new SelectedApplicationEntry(mPackageManager, info);
entry.setSelected(mSelection.contains(info.packageName));
apps.add(entry);
seen.add(info.packageName);
}
}
Collections.sort(apps);
/* check for selected packages that don't exist anymore */
List<String> missing = new ArrayList<>();
for (String pkg : mSelection)
{
if (!seen.contains(pkg))
{
missing.add(pkg);
}
}
return new Pair<>(apps, missing);
}
@Override
protected void onStartLoading()
{
if (mData != null)
{ /* if we have data ready, deliver it directly */
deliverResult(mData);
}
if (takeContentChanged() || mData == null)
{
forceLoad();
}
}
@Override
public void deliverResult(Pair<List<SelectedApplicationEntry>, List<String>> data)
{
if (isReset())
{
return;
}
mData = data;
if (isStarted())
{ /* if it is started we deliver the data directly,
* otherwise this is handled in onStartLoading */
super.deliverResult(data);
}
}
@Override
protected void onReset()
{
mData = null;
super.onReset();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
@ -57,6 +57,7 @@ import android.widget.TextView;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
@ -65,13 +66,18 @@ import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
import org.strongswan.android.ui.widget.TextInputLayoutHelper;
import org.strongswan.android.utils.Constants;
import org.strongswan.android.utils.IPRangeSet;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
public class VpnProfileDetailActivity extends AppCompatActivity
{
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
private static final int SELECT_APPLICATIONS = 1;
private VpnProfileDataSource mDataSource;
private Long mId;
@ -81,6 +87,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private String mSelectedUserId;
private TrustedCertificateEntry mUserCertEntry;
private VpnType mVpnType = VpnType.IKEV2_EAP;
private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
private SortedSet<String> mSelectedApps = new TreeSet<>();
private VpnProfile mProfile;
private MultiAutoCompleteTextView mName;
private TextInputLayoutHelper mNameWrap;
@ -105,8 +113,14 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private TextInputLayoutHelper mMTUWrap;
private EditText mPort;
private TextInputLayoutHelper mPortWrap;
private EditText mIncludedSubnets;
private TextInputLayoutHelper mIncludedSubnetsWrap;
private EditText mExcludedSubnets;
private TextInputLayoutHelper mExcludedSubnetsWrap;
private CheckBox mBlockIPv4;
private CheckBox mBlockIPv6;
private Spinner mSelectSelectedAppsHandling;
private RelativeLayout mSelectApps;
@Override
public void onCreate(Bundle savedInstanceState)
@ -149,9 +163,16 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
mPort = (EditText)findViewById(R.id.port);
mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
mIncludedSubnets = (EditText)findViewById(R.id.included_subnets);
mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap);
mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap);
mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling);
mSelectApps = (RelativeLayout)findViewById(R.id.select_applications);
final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
mName.setTokenizer(spaceTokenizer);
mRemoteId.setTokenizer(spaceTokenizer);
@ -256,6 +277,32 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
mSelectedAppsHandling = SelectedAppsHandling.values()[position];
updateAppsSelector();
}
@Override
public void onNothingSelected(AdapterView<?> parent)
{ /* should not happen */
mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
updateAppsSelector();
}
});
mSelectApps.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
{
Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
startActivityForResult(intent, SELECT_APPLICATIONS);
}
});
mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
if (mId == null)
{
@ -268,6 +315,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
updateCredentialView();
updateCertificateSelector();
updateAdvancedSettings();
updateAppsSelector();
}
@Override
@ -297,6 +345,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
}
outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
}
@Override
@ -338,6 +387,14 @@ public class VpnProfileDetailActivity extends AppCompatActivity
updateCertificateSelector();
}
break;
case SELECT_APPLICATIONS:
if (resultCode == RESULT_OK)
{
ArrayList<String> selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
mSelectedApps = new TreeSet<>(selection);
updateAppsSelector();
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
@ -426,6 +483,40 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
/**
* Update the application selection UI
*/
private void updateAppsSelector()
{
if (mSelectedAppsHandling == SelectedAppsHandling.SELECTED_APPS_DISABLE)
{
mSelectApps.setEnabled(false);
mSelectApps.setVisibility(View.GONE);
}
else
{
mSelectApps.setEnabled(true);
mSelectApps.setVisibility(View.VISIBLE);
((TextView)mSelectApps.findViewById(android.R.id.text1)).setText(R.string.profile_select_apps);
String selected;
switch (mSelectedApps.size())
{
case 0:
selected = getString(R.string.profile_select_no_apps);
break;
case 1:
selected = getString(R.string.profile_select_one_app);
break;
default:
selected = getString(R.string.profile_select_x_apps, mSelectedApps.size());
break;
}
((TextView)mSelectApps.findViewById(android.R.id.text2)).setText(selected);
}
}
/**
* Update the advanced settings UI depending on whether any advanced
* settings have already been made.
@ -437,7 +528,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
Integer st = mProfile.getSplitTunneling();
show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
mProfile.getPort() != null || (st != null && st != 0);
mProfile.getPort() != null || (st != null && st != 0) ||
mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null ||
mProfile.getSelectedAppsHandling() != SelectedAppsHandling.SELECTED_APPS_DISABLE;
}
mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
@ -510,6 +603,16 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), Constants.MTU_MIN, Constants.MTU_MAX));
valid = false;
}
if (!validateSubnets(mIncludedSubnets))
{
mIncludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
valid = false;
}
if (!validateSubnets(mExcludedSubnets))
{
mExcludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
valid = false;
}
if (!validateInteger(mPort, 1, 65535))
{
mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
@ -547,10 +650,16 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
mProfile.setMTU(getInteger(mMTU));
mProfile.setPort(getInteger(mPort));
String included = mIncludedSubnets.getText().toString().trim();
mProfile.setIncludedSubnets(included.isEmpty() ? null : included);
String excluded = mExcludedSubnets.getText().toString().trim();
mProfile.setExcludedSubnets(excluded.isEmpty() ? null : excluded);
int st = 0;
st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
mProfile.setSplitTunneling(st == 0 ? null : st);
mProfile.setSelectedAppsHandling(mSelectedAppsHandling);
mProfile.setSelectedApps(mSelectedApps);
}
/**
@ -576,8 +685,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mRemoteId.setText(mProfile.getRemoteId());
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
mIncludedSubnets.setText(mProfile.getIncludedSubnets());
mExcludedSubnets.setText(mProfile.getExcludedSubnets());
mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
mSelectedAppsHandling = mProfile.getSelectedAppsHandling();
mSelectedApps = mProfile.getSelectedAppsSet();
useralias = mProfile.getUserCertificateAlias();
local_id = mProfile.getLocalId();
alias = mProfile.getCertificateAlias();
@ -620,6 +733,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mCertEntry = null;
}
}
mSelectSelectedAppsHandling.setSelection(mSelectedAppsHandling.ordinal());
if (savedInstanceState != null)
{
ArrayList<String> selectedApps = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
mSelectedApps = new TreeSet<>(selectedApps);
}
}
/**
@ -665,6 +785,17 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
/**
* Check that the value in the given text box is a valid list of subnets/ranges
*
* @param view text box
*/
private boolean validateSubnets(EditText view)
{
String value = view.getText().toString().trim();
return value.isEmpty() || IPRangeSet.fromString(value) != null;
}
private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
{
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2016-2017 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@ -25,12 +25,14 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Base64;
import android.view.Menu;
import android.view.MenuInflater;
@ -43,10 +45,12 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
@ -54,6 +58,7 @@ import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.widget.TextInputLayoutHelper;
import org.strongswan.android.utils.Constants;
import org.strongswan.android.utils.IPRangeSet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -68,6 +73,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.UUID;
import javax.net.ssl.SSLHandshakeException;
@ -75,7 +81,9 @@ import javax.net.ssl.SSLHandshakeException;
public class VpnProfileImportActivity extends AppCompatActivity
{
private static final String PKCS12_INSTALLED = "PKCS12_INSTALLED";
private static final String PROFILE_URI = "PROFILE_URI";
private static final int INSTALL_PKCS12 = 0;
private static final int OPEN_DOCUMENT = 1;
private static final int PROFILE_LOADER = 0;
private static final int USER_CERT_LOADER = 1;
@ -107,7 +115,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
@Override
public Loader<ProfileLoadResult> onCreateLoader(int id, Bundle args)
{
return new ProfileLoader(VpnProfileImportActivity.this, getIntent().getData());
return new ProfileLoader(VpnProfileImportActivity.this, (Uri)args.getParcelable(PROFILE_URI));
}
@Override
@ -197,16 +205,13 @@ public class VpnProfileImportActivity extends AppCompatActivity
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action))
{
mProgress = ProgressDialog.show(this, null, getString(R.string.loading),
true, true, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog)
{
finish();
}
});
getLoaderManager().initLoader(PROFILE_LOADER, null, mProfileLoaderCallbacks);
loadProfile(getIntent().getData());
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openIntent.setType("*/*");
startActivityForResult(openIntent, OPEN_DOCUMENT);
}
if (savedInstanceState != null)
@ -279,9 +284,34 @@ public class VpnProfileImportActivity extends AppCompatActivity
mImportUserCert.setEnabled(false);
mSelectUserCert.performClick();
}
break;
case OPEN_DOCUMENT:
if (resultCode == Activity.RESULT_OK && data != null)
{
loadProfile(data.getData());
return;
}
finish();
break;
}
}
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();
}
});
Bundle args = new Bundle();
args.putParcelable(PROFILE_URI, uri);
getLoaderManager().initLoader(PROFILE_LOADER, args, mProfileLoaderCallbacks);
}
public void handleProfile(ProfileLoadResult data)
{
mProgress.dismiss();
@ -362,7 +392,15 @@ public class VpnProfileImportActivity extends AppCompatActivity
mRemoteCertificate.setVisibility(mProfile.Certificate != null ? View.VISIBLE : View.GONE);
mImportUserCert.setVisibility(mProfile.PKCS12 != null ? View.VISIBLE : View.GONE);
updateUserCertView();
if (mProfile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
{ /* try to load an existing certificate with the default name */
if (mUserCertLoading == null)
{
mUserCertLoading = getString(R.string.profile_cert_alias, mProfile.getName());
getLoaderManager().initLoader(USER_CERT_LOADER, null, mUserCertificateLoaderCallbacks);
}
updateUserCertView();
}
if (mProfile.Certificate != null)
{
@ -456,11 +494,28 @@ public class VpnProfileImportActivity extends AppCompatActivity
JSONObject split = obj.optJSONObject("split-tunneling");
if (split != null)
{
String included = getSubnets(split, "subnets");
profile.setIncludedSubnets(included != null ? included : null);
String excluded = getSubnets(split, "excluded");
profile.setExcludedSubnets(excluded != null ? excluded : null);
int st = 0;
st |= split.optBoolean("block-ipv4") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
st |= split.optBoolean("block-ipv6") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
profile.setSplitTunneling(st == 0 ? null : st);
}
/* only one of these can be set, prefer specific apps */
String selectedApps = getApps(obj.optJSONArray("apps"));
String excludedApps = getApps(obj.optJSONArray("excluded-apps"));
if (!TextUtils.isEmpty(selectedApps))
{
profile.setSelectedApps(selectedApps);
profile.setSelectedAppsHandling(SelectedAppsHandling.SELECTED_APPS_ONLY);
}
else if (!TextUtils.isEmpty(excludedApps))
{
profile.setSelectedApps(excludedApps);
profile.setSelectedAppsHandling(SelectedAppsHandling.SELECTED_APPS_EXCLUDE);
}
return profile;
}
@ -470,6 +525,52 @@ public class VpnProfileImportActivity extends AppCompatActivity
return res < min || res > max ? null : res;
}
private String getSubnets(JSONObject split, String key) throws JSONException
{
ArrayList<String> subnets = new ArrayList<>();
JSONArray arr = split.optJSONArray(key);
if (arr != null)
{
for (int i = 0; i < arr.length(); i++)
{ /* replace all spaces, e.g. in "192.168.1.1 - 192.168.1.10" */
subnets.add(arr.getString(i).replace(" ", ""));
}
}
else
{
String value = split.optString(key, null);
if (!TextUtils.isEmpty(value))
{
subnets.add(value);
}
}
if (subnets.size() > 0)
{
String joined = TextUtils.join(" ", subnets);
IPRangeSet ranges = IPRangeSet.fromString(joined);
if (ranges == null)
{
throw new JSONException(getString(R.string.profile_import_failed_value,
"split-tunneling." + key));
}
return ranges.toString();
}
return null;
}
private String getApps(JSONArray arr) throws JSONException
{
ArrayList<String> apps = new ArrayList<>();
if (arr != null)
{
for (int i = 0; i < arr.length(); i++)
{
apps.add(arr.getString(i));
}
}
return TextUtils.join(" ", apps);
}
/**
* Save or update the profile depending on whether we actually have a
* profile object or not (this was created in updateProfileData)

View File

@ -189,6 +189,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
ImcState imcState = mService.getImcState();
String name = "";
if (getActivity() == null)
{
return;
}
if (profile != null)
{
name = profile.getName();

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2017 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.adapter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import java.text.Collator;
public class SelectedApplicationEntry implements Comparable<SelectedApplicationEntry>
{
private final ApplicationInfo mInfo;
private final Drawable mIcon;
private final String mName;
private boolean mSelected;
public SelectedApplicationEntry(PackageManager packageManager, ApplicationInfo info)
{
mInfo = info;
CharSequence name = info.loadLabel(packageManager);
mName = name == null ? info.packageName : name.toString();
mIcon = info.loadIcon(packageManager);
}
public void setSelected(boolean selected)
{
mSelected = selected;
}
public boolean isSelected()
{
return mSelected;
}
public ApplicationInfo getInfo()
{
return mInfo;
}
public Drawable getIcon()
{
return mIcon;
}
@Override
public String toString()
{
return mName;
}
@Override
public int compareTo(@NonNull SelectedApplicationEntry another)
{
return Collator.getInstance().compare(toString(), another.toString());
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2017 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.adapter;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import org.strongswan.android.R;
import org.strongswan.android.ui.widget.CheckableLinearLayout;
import java.util.ArrayList;
import java.util.List;
public class SelectedApplicationsAdapter extends BaseAdapter implements Filterable
{
private Context mContext;
private final Object mLock = new Object();
private List<SelectedApplicationEntry> mData;
private List<SelectedApplicationEntry> mDataFiltered;
private SelectedApplicationsFilter mFilter;
public SelectedApplicationsAdapter(Context context)
{
mContext = context;
mData = mDataFiltered = new ArrayList<>();
}
/**
* Set new data for this adapter.
*
* @param data the new data (null to clear)
*/
public void setData(List<SelectedApplicationEntry> data)
{
synchronized (mLock)
{
mData.clear();
mDataFiltered = mData;
if (data != null)
{
mData.addAll(data);
}
}
notifyDataSetChanged();
}
@Override
public int getCount()
{
return mDataFiltered.size();
}
@Override
public SelectedApplicationEntry getItem(int position)
{
return mDataFiltered.get(position);
}
@Override
public long getItemId(int position)
{
return mDataFiltered.get(position).toString().hashCode();
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view;
if (convertView != null)
{
view = convertView;
}
else
{
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.selected_application_item, parent, false);
}
SelectedApplicationEntry item = getItem(position);
CheckableLinearLayout checkable = (CheckableLinearLayout)view;
checkable.setChecked(item.isSelected());
ImageView icon = (ImageView)view.findViewById(R.id.app_icon);
icon.setImageDrawable(item.getIcon());
TextView text = (TextView)view.findViewById(R.id.app_name);
text.setText(item.toString());
return view;
}
@Override
public Filter getFilter()
{
if (mFilter == null)
{
mFilter = new SelectedApplicationsFilter();
}
return mFilter;
}
private class SelectedApplicationsFilter extends Filter
{
@Override
protected FilterResults performFiltering(CharSequence constraint)
{
FilterResults results = new FilterResults();
ArrayList<SelectedApplicationEntry> data, filtered;
synchronized (mLock)
{
data = new ArrayList<>(mData);
}
if (TextUtils.isEmpty(constraint))
{
filtered = data;
}
else
{
String filter = constraint.toString().toLowerCase();
filtered = new ArrayList<>();
for (SelectedApplicationEntry entry : data)
{
if (entry.toString().toLowerCase().contains(filter))
{
filtered.add(entry);
}
}
}
results.values = filtered;
results.count = filtered.size();
return results;
}
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results)
{
mDataFiltered = (List<SelectedApplicationEntry>)results.values;
notifyDataSetChanged();
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 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.widget;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.Checkable;
import android.widget.LinearLayout;
public class CheckableLinearLayout extends LinearLayout implements Checkable
{
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private boolean mChecked;
public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
}
@Override
public void setChecked(boolean checked)
{
if (mChecked != checked)
{
mChecked = checked;
refreshDrawableState();
}
}
@Override
public boolean isChecked()
{
return mChecked;
}
@Override
public void toggle()
{
setChecked(!mChecked);
}
@Override
protected int[] onCreateDrawableState(int extraSpace)
{
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked())
{
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
}

View File

@ -0,0 +1,509 @@
/*
* Copyright (C) 2012-2017 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.utils;
import android.support.annotation.NonNull;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Class that represents a range of IP addresses. This range could be a proper subnet, but that's
* not necessarily the case (see {@code getPrefix} and {@code toSubnets}).
*/
public class IPRange implements Comparable<IPRange>
{
private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
private byte[] mFrom;
private byte[] mTo;
private Integer mPrefix;
/**
* Determine if the range is a proper subnet and, if so, what the network prefix is.
*/
private void determinePrefix()
{
boolean matching = true;
mPrefix = mFrom.length * 8;
for (int i = 0; i < mFrom.length; i++)
{
for (int bit = 0; bit < 8; bit++)
{
if (matching)
{
if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit]))
{
mPrefix = (i * 8) + bit;
matching = false;
}
}
else
{
if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0)
{
mPrefix = null;
return;
}
}
}
}
}
private IPRange(byte[] from, byte[] to)
{
mFrom = from;
mTo = to;
determinePrefix();
}
public IPRange(String from, String to) throws UnknownHostException
{
this(InetAddress.getByName(from), InetAddress.getByName(to));
}
public IPRange(InetAddress from, InetAddress to)
{
initializeFromRange(from, to);
}
private void initializeFromRange(InetAddress from, InetAddress to)
{
byte[] fa = from.getAddress(), ta = to.getAddress();
if (fa.length != ta.length)
{
throw new IllegalArgumentException("Invalid range");
}
if (compareAddr(fa, ta) < 0)
{
mFrom = fa;
mTo = ta;
}
else
{
mTo = fa;
mFrom = ta;
}
determinePrefix();
}
public IPRange(String base, int prefix) throws UnknownHostException
{
this(InetAddress.getByName(base), prefix);
}
public IPRange(InetAddress base, int prefix)
{
this(base.getAddress(), prefix);
}
private IPRange(byte[] from, int prefix)
{
initializeFromCIDR(from, prefix);
}
private void initializeFromCIDR(byte[] from, int prefix)
{
if (from.length != 4 && from.length != 16)
{
throw new IllegalArgumentException("Invalid address");
}
if (prefix < 0 || prefix > from.length * 8)
{
throw new IllegalArgumentException("Invalid prefix");
}
byte[] to = from.clone();
byte mask = (byte)(0xff << (8 - prefix % 8));
int i = prefix / 8;
if (i < from.length)
{
from[i] = (byte)(from[i] & mask);
to[i] = (byte)(to[i] | ~mask);
Arrays.fill(from, i+1, from.length, (byte)0);
Arrays.fill(to, i+1, to.length, (byte)0xff);
}
mFrom = from;
mTo = to;
mPrefix = prefix;
}
public IPRange(String cidr) throws UnknownHostException
{
/* only verify the basic structure */
if (!cidr.matches("(?i)^(([0-9.]+)|([0-9a-f:]+))(-(([0-9.]+)|([0-9a-f:]+))|(/\\d+))?$"))
{
throw new IllegalArgumentException("Invalid CIDR or range notation");
}
if (cidr.contains("-"))
{
String[] parts = cidr.split("-");
InetAddress from = InetAddress.getByName(parts[0]);
InetAddress to = InetAddress.getByName(parts[1]);
initializeFromRange(from, to);
}
else
{
String[] parts = cidr.split("/");
InetAddress addr = InetAddress.getByName(parts[0]);
byte[] base = addr.getAddress();
int prefix = base.length * 8;
if (parts.length > 1)
{
prefix = Integer.parseInt(parts[1]);
}
initializeFromCIDR(base, prefix);
}
}
/**
* Returns the first address of the range. The network ID in case this is a proper subnet.
*/
public InetAddress getFrom()
{
try
{
return InetAddress.getByAddress(mFrom);
}
catch (UnknownHostException ignored)
{
return null;
}
}
/**
* Returns the last address of the range.
*/
public InetAddress getTo()
{
try
{
return InetAddress.getByAddress(mTo);
}
catch (UnknownHostException ignored)
{
return null;
}
}
/**
* If this range is a proper subnet returns its prefix, otherwise returns null.
*/
public Integer getPrefix()
{
return mPrefix;
}
@Override
public int compareTo(@NonNull IPRange other)
{
int cmp = compareAddr(mFrom, other.mFrom);
if (cmp == 0)
{ /* smaller ranges first */
cmp = compareAddr(mTo, other.mTo);
}
return cmp;
}
@Override
public boolean equals(Object o)
{
if (o == null || !(o instanceof IPRange))
{
return false;
}
return this == o || compareTo((IPRange)o) == 0;
}
@Override
public String toString()
{
try
{
if (mPrefix != null)
{
return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix;
}
return InetAddress.getByAddress(mFrom).getHostAddress() + "-" +
InetAddress.getByAddress(mTo).getHostAddress();
}
catch (UnknownHostException ignored)
{
return super.toString();
}
}
private int compareAddr(byte a[], byte b[])
{
if (a.length != b.length)
{
return (a.length < b.length) ? -1 : 1;
}
for (int i = 0; i < a.length; i++)
{
if (a[i] != b[i])
{
if (((int)a[i] & 0xff) < ((int)b[i] & 0xff))
{
return -1;
}
else
{
return 1;
}
}
}
return 0;
}
/**
* Check if this range fully contains the given range.
*/
public boolean contains(IPRange range)
{
return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0;
}
/**
* Check if this and the given range overlap.
*/
public boolean overlaps(IPRange range)
{
return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0);
}
private byte[] dec(byte[] addr)
{
for (int i = addr.length - 1; i >= 0; i--)
{
if (--addr[i] != (byte)0xff)
{
break;
}
}
return addr;
}
private byte[] inc(byte[] addr)
{
for (int i = addr.length - 1; i >= 0; i--)
{
if (++addr[i] != 0)
{
break;
}
}
return addr;
}
/**
* Remove the given range from the current range. Returns a list of resulting ranges (these are
* not proper subnets). At most two ranges are returned, in case the given range is contained in
* this but does not equal it, which would result in an empty list (which is also the case if
* this range is fully contained in the given range).
*/
public List<IPRange> remove(IPRange range)
{
ArrayList<IPRange> list = new ArrayList<>();
if (!overlaps(range))
{ /* | this | or | this |
* | range | | range | */
list.add(this);
}
else if (!range.contains(this))
{ /* we are not completely removed, so none of these cases applies:
* | this | or | this | or | this |
* | range | | range | | range | */
if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0)
{ /* the removed range is completely within our boundaries:
* | this |
* | range | */
list.add(new IPRange(mFrom, dec(range.mFrom.clone())));
list.add(new IPRange(inc(range.mTo.clone()), mTo));
}
else
{ /* one end is within our boundaries the other at or outside it:
* | this | or | this | or | this | or | this |
* | range | | range | | range | | range | */
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone());
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone());
list.add(new IPRange(from, to));
}
}
return list;
}
private boolean adjacent(IPRange range)
{
if (compareAddr(mTo, range.mFrom) < 0)
{
byte[] to = inc(mTo.clone());
return compareAddr(to, range.mFrom) == 0;
}
byte[] from = dec(mFrom.clone());
return compareAddr(from, range.mTo) == 0;
}
/**
* Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them.
*/
public IPRange merge(IPRange range)
{
if (overlaps(range))
{
if (contains(range))
{
return this;
}
else if (range.contains(this))
{
return range;
}
}
else if (!adjacent(range))
{
return null;
}
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom;
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo;
return new IPRange(from, to);
}
/**
* Split the given range into a sorted list of proper subnets.
*/
public List<IPRange> toSubnets()
{
ArrayList<IPRange> list = new ArrayList<>();
if (mPrefix != null)
{
list.add(this);
}
else
{
int i = 0, bit = 0, prefix, netmask, common_byte, common_bit;
int from_cur, from_prev = 0, to_cur, to_prev = 1;
boolean from_full = true, to_full = true;
byte[] from = mFrom.clone();
byte[] to = mTo.clone();
/* find a common prefix */
while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit]))
{
if (++bit == 8)
{
bit = 0;
i++;
}
}
prefix = i * 8 + bit;
/* at this point we know that the addresses are either equal, or that the
* current bits in the 'from' and 'to' addresses are 0 and 1, respectively.
* we now look at the rest of the bits as two binary trees (0=left, 1=right)
* where 'from' and 'to' are both leaf nodes. all leaf nodes between these
* nodes are addresses contained in the range. to collect them as subnets
* we follow the trees from both leaf nodes to their root node and record
* all complete subtrees (right for from, left for to) we come across as
* subnets. in that process host bits are zeroed out. if both addresses
* are equal we won't enter the loop below.
* 0_____|_____1 for the 'from' address we assume we start on a
* 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until
* _|_ _|_ _|_ _|_ we reach the root of this subtree, which is
* | | | | | | | | either the root of this whole 'from'-subtree
* 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node
* of the right subtree (1) of another node (which actually could be the
* leaf node we start from). that whole subtree gets recorded as subnet.
* next we follow the right edges to the root of that subtree which again is
* either the 'from'-root or the root node in the left subtree (0) of
* another node. the complete right subtree of that node is the next subnet
* we record. from there we assume that we are in that right subtree and
* recursively follow right edges to its root. for the 'to' address the
* procedure is exactly the same but with left and right reversed.
*/
if (++bit == 8)
{
bit = 0;
i++;
}
common_byte = i;
common_bit = bit;
netmask = from.length * 8;
for (i = from.length - 1; i >= common_byte; i--)
{
int bit_min = (i == common_byte) ? common_bit : 0;
for (bit = 7; bit >= bit_min; bit--)
{
byte mask = mBitmask[bit];
from_cur = from[i] & mask;
if (from_prev == 0 && from_cur != 0)
{ /* 0 -> 1: subnet is the whole current (right) subtree */
list.add(new IPRange(from.clone(), netmask));
from_full = false;
}
else if (from_prev != 0 && from_cur == 0)
{ /* 1 -> 0: invert bit to switch to right subtree and add it */
from[i] ^= mask;
list.add(new IPRange(from.clone(), netmask));
from_cur = 1;
}
/* clear the current bit */
from[i] &= ~mask;
from_prev = from_cur;
to_cur = to[i] & mask;
if (to_prev != 0 && to_cur == 0)
{ /* 1 -> 0: subnet is the whole current (left) subtree */
list.add(new IPRange(to.clone(), netmask));
to_full = false;
}
else if (to_prev == 0 && to_cur != 0)
{ /* 0 -> 1: invert bit to switch to left subtree and add it */
to[i] ^= mask;
list.add(new IPRange(to.clone(), netmask));
to_cur = 0;
}
/* clear the current bit */
to[i] &= ~mask;
to_prev = to_cur;
netmask--;
}
}
if (from_full && to_full)
{ /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable
* due to the shortcut at the top */
list.add(new IPRange(from.clone(), prefix));
}
else if (from_full)
{ /* full from subnet (from=0.. after prefix) */
list.add(new IPRange(from.clone(), prefix + 1));
}
else if (to_full)
{ /* full to subnet (to=1.. after prefix) */
list.add(new IPRange(to.clone(), prefix + 1));
}
}
Collections.sort(list);
return list;
}
}

View File

@ -0,0 +1,223 @@
/*
* Copyright (C) 2012-2017 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.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
/**
* Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
* modifying the set and enumerating the resulting subnets.
*/
public class IPRangeSet implements Iterable<IPRange>
{
private TreeSet<IPRange> mRanges = new TreeSet<>();
/**
* Parse the given string (space separated ranges in CIDR or range notation) and return the
* resulting set or {@code null} if the string was invalid. An empty set is returned if the given string
* is {@code null}.
*/
public static IPRangeSet fromString(String ranges)
{
IPRangeSet set = new IPRangeSet();
if (ranges != null)
{
for (String range : ranges.split("\\s+"))
{
try
{
set.add(new IPRange(range));
}
catch (Exception unused)
{ /* besides due to invalid strings exceptions might get thrown if the string
* contains a hostname (NetworkOnMainThreadException) */
return null;
}
}
}
return set;
}
/**
* Add a range to this set. Automatically gets merged with existing ranges.
*/
public void add(IPRange range)
{
if (mRanges.contains(range))
{
return;
}
reinsert:
while (true)
{
Iterator<IPRange> iterator = mRanges.iterator();
while (iterator.hasNext())
{
IPRange existing = iterator.next();
IPRange replacement = existing.merge(range);
if (replacement != null)
{
iterator.remove();
range = replacement;
continue reinsert;
}
}
mRanges.add(range);
break;
}
}
/**
* Add all ranges from the given set.
*/
public void add(IPRangeSet ranges)
{
if (ranges == this)
{
return;
}
for (IPRange range : ranges.mRanges)
{
add(range);
}
}
/**
* Add all ranges from the given collection to this set.
*/
public void addAll(Collection<? extends IPRange> coll)
{
for (IPRange range : coll)
{
add(range);
}
}
/**
* Remove the given range from this set. Existing ranges are automatically adjusted.
*/
public void remove(IPRange range)
{
ArrayList <IPRange> additions = new ArrayList<>();
Iterator<IPRange> iterator = mRanges.iterator();
while (iterator.hasNext())
{
IPRange existing = iterator.next();
List<IPRange> result = existing.remove(range);
if (result.size() == 0)
{
iterator.remove();
}
else if (!result.get(0).equals(existing))
{
iterator.remove();
additions.addAll(result);
}
}
mRanges.addAll(additions);
}
/**
* Remove the given ranges from ranges in this set.
*/
public void remove(IPRangeSet ranges)
{
if (ranges == this)
{
mRanges.clear();
return;
}
for (IPRange range : ranges.mRanges)
{
remove(range);
}
}
/**
* Get all the subnets derived from all the ranges in this set.
*/
public Iterable<IPRange> subnets()
{
return new Iterable<IPRange>()
{
@Override
public Iterator<IPRange> iterator()
{
return new Iterator<IPRange>()
{
private Iterator<IPRange> mIterator = mRanges.iterator();
private List<IPRange> mSubnets;
@Override
public boolean hasNext()
{
return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
}
@Override
public IPRange next()
{
if (mSubnets == null || mSubnets.size() == 0)
{
IPRange range = mIterator.next();
mSubnets = range.toSubnets();
}
return mSubnets.remove(0);
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
@Override
public Iterator<IPRange> iterator()
{
return mRanges.iterator();
}
/**
* Returns the number of ranges, not subnets.
*/
public int size()
{
return mRanges.size();
}
@Override
public String toString()
{ /* we could use TextUtils, but that causes the unit tests to fail */
StringBuilder sb = new StringBuilder();
for (IPRange range : mRanges)
{
if (sb.length() > 0)
{
sb.append(" ");
}
sb.append(range.toString());
}
return sb.toString();
}
}

View File

@ -118,7 +118,7 @@ public class SettingsWriter
*/
private String escapeValue(String value)
{
return value.replace("\"", "\\\"");
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}
/**

View File

@ -6,7 +6,7 @@ include $(CLEAR_VARS)
strongswan_USE_BYOD := true
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
chapoly curve25519 pkcs1 pkcs8 pem xcbc hmac socket-default \
chapoly curve25519 pkcs1 pkcs8 pem xcbc hmac socket-default revocation \
eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls
ifneq ($(strongswan_USE_BYOD),)

View File

@ -6,6 +6,7 @@ LOCAL_SRC_FILES := \
android_jni.c \
backend/android_attr.c \
backend/android_creds.c \
backend/android_fetcher.c \
backend/android_dns_proxy.c \
backend/android_private_key.c \
backend/android_service.c \

View File

@ -1,8 +1,8 @@
/*
* Copyright (C) 2012-2015 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
* 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
@ -44,7 +44,10 @@ static struct {
jclass *android_charonvpnservice_class;
jclass *android_charonvpnservice_builder_class;
jclass *android_simple_fetcher_class;
android_sdk_version_t android_sdk_version;
char *android_version_string;
char *android_device_string;
/**
* Thread-local variable. Only used because of the destructor
@ -96,6 +99,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved)
JNIEnv *env;
jclass jversion;
jfieldID jsdk_int;
jmethodID method_id;
jstring jstr;
int i;
android_jvm = vm;
@ -122,11 +127,30 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved)
android_charonvpnservice_builder_class =
(*env)->NewGlobalRef(env, (*env)->FindClass(env,
JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter"));
android_simple_fetcher_class =
(*env)->NewGlobalRef(env, (*env)->FindClass(env,
JNI_PACKAGE_STRING "/SimpleFetcher"));
jversion = (*env)->FindClass(env, "android/os/Build$VERSION");
jsdk_int = (*env)->GetStaticFieldID(env, jversion, "SDK_INT", "I");
android_sdk_version = (*env)->GetStaticIntField(env, jversion, jsdk_int);
method_id = (*env)->GetStaticMethodID(env, android_charonvpnservice_class,
"getAndroidVersion", "()Ljava/lang/String;");
jstr = (*env)->CallStaticObjectMethod(env,
android_charonvpnservice_class, method_id);
if (jstr)
{
android_version_string = androidjni_convert_jstring(env, jstr);
}
method_id = (*env)->GetStaticMethodID(env, android_charonvpnservice_class,
"getDeviceString", "()Ljava/lang/String;");
jstr = (*env)->CallStaticObjectMethod(env,
android_charonvpnservice_class, method_id);
if (jstr)
{
android_device_string = androidjni_convert_jstring(env, jstr);
}
return JNI_VERSION_1_6;
}
@ -147,5 +171,6 @@ void JNI_OnUnload(JavaVM *vm, void *reserved)
dlclose(libs[i].handle);
}
}
free(android_version_string);
free(android_device_string);
}

View File

@ -1,8 +1,8 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
* 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
@ -44,6 +44,7 @@
*/
extern jclass *android_charonvpnservice_class;
extern jclass *android_charonvpnservice_builder_class;
extern jclass *android_simple_fetcher_class;
/**
* Currently known (supported) SDK versions
@ -65,6 +66,20 @@ typedef enum {
*/
extern android_sdk_version_t android_sdk_version;
/**
* A description of the current Android release
*
* see android.os.Build
*/
extern char *android_version_string;
/**
* A description of the current device
*
* see android.os.Build
*/
extern char *android_device_string;
/**
* Attach the current thread to the JVM
*

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2017 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.
*/
#include "android_fetcher.h"
#include "../android_jni.h"
#include "../charonservice.h"
#include <utils/debug.h>
typedef struct android_fetcher_t android_fetcher_t;
struct android_fetcher_t {
/**
* Public interface
*/
fetcher_t public;
/**
* Callback function
*/
fetcher_callback_t cb;
};
METHOD(fetcher_t, fetch, status_t,
android_fetcher_t *this, char *uri, void *userdata)
{
JNIEnv *env;
jmethodID method_id;
jobjectArray jdata;
jstring juri;
chunk_t data;
status_t status = FAILED;
if (this->cb == fetcher_default_callback)
{
*(chunk_t*)userdata = chunk_empty;
}
androidjni_attach_thread(&env);
/* can't use FindClass here as this is not called by the main thread */
method_id = (*env)->GetStaticMethodID(env, android_simple_fetcher_class,
"fetch", "(Ljava/lang/String;)[B");
if (!method_id)
{
goto failed;
}
juri = (*env)->NewStringUTF(env, uri);
if (!juri)
{
goto failed;
}
jdata = (*env)->CallStaticObjectMethod(env, android_simple_fetcher_class,
method_id, juri);
if (!jdata || androidjni_exception_occurred(env))
{
goto failed;
}
data = chunk_from_byte_array(env, jdata);
if (this->cb(userdata, data))
{
status = SUCCESS;
}
chunk_free(&data);
androidjni_detach_thread();
return status;
failed:
DBG1(DBG_LIB, "failed to fetch from '%s'", uri);
androidjni_exception_occurred(env);
androidjni_detach_thread();
return FAILED;
}
METHOD(fetcher_t, set_option, bool,
android_fetcher_t *this, fetcher_option_t option, ...)
{
bool supported = TRUE;
va_list args;
va_start(args, option);
switch (option)
{
case FETCH_CALLBACK:
{
this->cb = va_arg(args, fetcher_callback_t);
break;
}
default:
supported = FALSE;
break;
}
va_end(args);
return supported;
}
METHOD(fetcher_t, destroy, void,
android_fetcher_t *this)
{
free(this);
}
/*
* Described in header.
*/
fetcher_t *android_fetcher_create()
{
android_fetcher_t *this;
INIT(this,
.public = {
.fetch = _fetch,
.set_option = _set_option,
.destroy = _destroy,
},
.cb = fetcher_default_callback,
);
return &this->public;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2017 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.
*/
/**
* @defgroup android_fetcher android_fetcher
* @{ @ingroup android_backend
*/
#ifndef ANDROID_FETCHER_H_
#define ANDROID_FETCHER_H_
#include <library.h>
/**
* Create an Android-specific fetcher instance based on SimpleFetcher
*
* @return fetcher_t instance
*/
fetcher_t *android_fetcher_create();
#endif /** ANDROID_FETCHER_H_ @}*/

View File

@ -1,8 +1,8 @@
/*
* Copyright (C) 2012-2015 Tobias Brunner
* Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
* 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
@ -25,6 +25,7 @@
#include "android_jni.h"
#include "backend/android_attr.h"
#include "backend/android_creds.h"
#include "backend/android_fetcher.h"
#include "backend/android_private_key.h"
#include "backend/android_service.h"
#include "kernel/android_ipsec.h"
@ -523,6 +524,9 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder,
PLUGIN_CALLBACK(charonservice_register, NULL),
PLUGIN_PROVIDE(CUSTOM, "android-backend"),
PLUGIN_DEPENDS(CUSTOM, "libcharon"),
PLUGIN_REGISTER(FETCHER, android_fetcher_create),
PLUGIN_PROVIDE(FETCHER, "http://"),
PLUGIN_PROVIDE(FETCHER, "https://"),
};
INIT(this,
@ -640,7 +644,8 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean,
{
memset(&utsname, 0, sizeof(utsname));
}
DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)",
DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s, %s, "
"%s %s, %s)", android_version_string, android_device_string,
utsname.sysname, utsname.release, utsname.machine);
#ifdef PLUGINS_BYOD

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false" android:color="@android:color/secondary_text_dark" />
<item android:color="@android:color/primary_text_dark" />
</selector>

View File

@ -23,6 +23,10 @@
android:state_activated="true"
android:drawable="@color/accent" />
<item
android:state_checked="true"
android:drawable="@color/checked" />
<item
android:drawable="@android:color/transparent" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2016 Tobias Brunner
Copyright (C) 2012-2017 Tobias Brunner
Copyright (C) 2012 Giuliano Grassi
Copyright (C) 2012 Ralf Sager
HSR Hochschule fuer Technik Rapperswil
@ -248,9 +248,52 @@
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="20sp"
android:text="@string/profile_split_tunneling_label" />
<TextView
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:text="@string/profile_split_tunneling_label" />
android:text="@string/profile_split_tunneling_intro" />
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/included_subnets_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helper_text="@string/profile_included_subnets_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/included_subnets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:hint="@string/profile_included_subnets_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/excluded_subnets_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helper_text="@string/profile_excluded_subnets_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/excluded_subnets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:hint="@string/profile_excluded_subnets_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<CheckBox
android:id="@+id/split_tunneling_v4"
@ -264,6 +307,27 @@
android:layout_height="wrap_content"
android:text="@string/profile_split_tunnelingv6_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:textSize="20sp"
android:text="@string/profile_select_apps_label" />
<Spinner
android:id="@+id/apps_handling"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dropdown"
android:entries="@array/apps_handling" />
<include
android:id="@+id/select_applications"
layout="@layout/two_line_button" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<org.strongswan.android.ui.widget.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:minHeight="?android:listPreferredItemHeight"
android:background="@drawable/activated_background"
android:gravity="center_vertical" >
<ImageView android:id="@+id/app_icon"
android:duplicateParentState="true"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:layout_marginRight="8dip"
android:layout_marginEnd="8dip"
android:scaleType="centerInside" />
<TextView android:id="@+id/app_name"
android:duplicateParentState="true"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="@color/checkable_text_color" />
<ImageView android:src="?android:listChoiceIndicatorMultiple"
android:duplicateParentState="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp" />
</org.strongswan.android.ui.widget.CheckableLinearLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2014 Tobias Brunner
Copyright (C) 2012-2017 Tobias Brunner
Hochschule fuer Technik Rapperswil
This program is free software; you can redistribute it and/or modify it
@ -16,6 +16,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_import_profile"
android:title="@string/profile_import"
app:showAsAction="withText" />
<item
android:id="@+id/menu_manage_certs"
android:title="@string/trusted_certs_title"

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2013 Tobias Brunner
Hochschule fuer Technik Rapperswil
Copyright (C) 2012-2017 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
@ -22,4 +22,11 @@
<item>IKEv2 EAP-TLS (Zertifikat)</item>
<item>IKEv2 EAP-TNC (Benutzername/Passwort)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>Alle Apps verwenden das VPN</item>
<item>Ausgewählte Apps vom VPN ausschliessen</item>
<item>Nur ausgewählte Apps verwenden das VPN</item>
</string-array>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2016 Tobias Brunner
Copyright (C) 2012-2017 Tobias Brunner
Copyright (C) 2012 Giuliano Grassi
Copyright (C) 2012 Ralf Sager
HSR Hochschule fuer Technik Rapperswil
@ -80,14 +80,25 @@
<string name="profile_port_label">Server Port</string>
<string name="profile_port_hint">UDP-Port zu dem verbunden wird, falls dieser vom Standard-Port abweicht</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_split_tunnelingv6_title">Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist</string>
<string name="profile_included_subnets_label">Benutzerdefinierte Subnetze</string>
<string name="profile_included_subnets_hint">Nur Verkehr in die spezifizierten Subnetze wird via VPN geleitet, der Rest wird behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Ausgeschlossene Subnetze</string>
<string name="profile_excluded_subnets_hint">Verkehr in diese Subnetze wird vom VPN ausgeschlossen und behandelt, als ob kein VPN vorhanden wäre (mit Leerzeichen getrennt, z.B. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Apps</string>
<string name="profile_select_apps">Apps auswählen</string>
<string name="profile_select_no_apps">Keine Apps ausgewählt</string>
<string name="profile_select_one_app">Eine App ausgewählt</string>
<string name="profile_select_x_apps">%1$d Apps ausgewählt</string>
<string name="profile_import">VPN Profile 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_failed_host">Host unbekannt</string>
<string name="profile_import_failed_tls">TLS-Handshake fehlgeschlagen</string>
<string name="profile_import_failed_value">Ungültiger Wert in \"%1$s\"</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>
@ -97,6 +108,7 @@
<string name="alert_text_nocertfound_title">Kein CA-Zertifikat ausgewählt</string>
<string name="alert_text_nocertfound">Bitte wählen Sie eines aus oder aktivieren Sie <i>Automatisch wählen</i></string>
<string name="alert_text_out_of_range">Bitte geben Sie eine Nummer von %1$d - %2$d ein</string>
<string name="alert_text_no_subnets">Bitte geben Sie mit Leerzeichen getrennte, gültige Subnetzte und/oder IP-Adressen ein</string>
<string name="tnc_notice_title">EAP-TNC kann Ihre Privatsphäre beeinträchtigen</string>
<string name="tnc_notice_subtitle">Gerätedaten werden an den Server-Betreiber gesendet</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) erlaubt Server-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.</p><p>Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.</p><b>Solche Daten werden nur übermittelt nachdem die Identität des Servers geprüft wurde.</b>]]></string>

View File

@ -22,4 +22,11 @@
<item>IKEv2 EAP-TLS (certyfikat)</item>
<item>IKEv2 EAP-TNC (użytkownik/hasło)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -80,14 +80,25 @@
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</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_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">Import VPN profile</string>
<string name="profile_import_failed">Failed to import VPN profile</string>
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
<string name="profile_import_failed_not_found">File not found</string>
<string name="profile_import_failed_host">Host unknown</string>
<string name="profile_import_failed_tls">TLS handshake failed</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</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>
@ -97,6 +108,7 @@
<string name="alert_text_nocertfound_title">Nie wybrano żadnego certyfikatu CA</string>
<string name="alert_text_nocertfound">Wybierz lub uaktywnij jeden <i>Wybierz automatycznie</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC may affect your privacy</string>
<string name="tnc_notice_subtitle">Device data is sent to the server operator</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b>]]></string>

View File

@ -21,4 +21,11 @@
<item>IKEv2 EAP-TLS (Сертификат)</item>
<item>IKEv2 EAP-TNC (Логин/Пароль)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -77,14 +77,25 @@
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</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_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">Import VPN profile</string>
<string name="profile_import_failed">Failed to import VPN profile</string>
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
<string name="profile_import_failed_not_found">File not found</string>
<string name="profile_import_failed_host">Host unknown</string>
<string name="profile_import_failed_tls">TLS handshake failed</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</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>
@ -94,6 +105,7 @@
<string name="alert_text_nocertfound_title">Не выбран сертификат CA</string>
<string name="alert_text_nocertfound">Пожалуйста выберите один <i>Выбрать автоматически</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC may affect your privacy</string>
<string name="tnc_notice_subtitle">Device data is sent to the server operator</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b>]]></string>

View File

@ -21,4 +21,11 @@
<item>IKEv2 EAP-TLS (Сертифікати)</item>
<item>IKEv2 EAP-TNC (Логін/Пароль)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -78,14 +78,25 @@
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</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_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">Import VPN profile</string>
<string name="profile_import_failed">Failed to import VPN profile</string>
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
<string name="profile_import_failed_not_found">File not found</string>
<string name="profile_import_failed_host">Host unknown</string>
<string name="profile_import_failed_tls">TLS handshake failed</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</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>
@ -95,6 +106,7 @@
<string name="alert_text_nocertfound_title">Не вибрано сертифікат CA</string>
<string name="alert_text_nocertfound">Будь ласка виберіть один <i>Вибрати автоматично</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC may affect your privacy</string>
<string name="tnc_notice_subtitle">Device data is sent to the server operator</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b>]]></string>

View File

@ -21,4 +21,11 @@
<item>IKEv2 EAP-TLS (证书)</item>
<item>IKEv2 EAP-TNC (用户名/密码)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -77,14 +77,25 @@
<string name="profile_port_label">服务器端口</string>
<string name="profile_port_hint">如不同于默认值则所需连接的UDP端口</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_split_tunnelingv6_title">屏蔽不通过VPN的IPV6流量</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">导入VPN配置</string>
<string name="profile_import_failed">导入VPN配置失败</string>
<string name="profile_import_failed_detail">导入VPN配置失败: %1$s</string>
<string name="profile_import_failed_not_found">文件未找到</string>
<string name="profile_import_failed_host">未知主机</string>
<string name="profile_import_failed_tls">TLS握手失败</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</string>
<string name="profile_import_exists">此VPN配置已经存在当前设定将被覆盖。</string>
<string name="profile_cert_import">从VPN配置导入证书</string>
<string name="profile_cert_alias">\"%1$s\" 所对应的证书</string>
@ -94,6 +105,7 @@
<string name="alert_text_nocertfound_title">未选择CA证书</string>
<string name="alert_text_nocertfound">请选择一项或激活 <i>自动选择</i></string>
<string name="alert_text_out_of_range">请输入一个数字范围从%1$d到%2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC可能会影响您的隐私</string>
<string name="tnc_notice_subtitle">设备数据已被发送至服务器管理员</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) 允许服务器管理员评定一个用户设备的状况。</p><p>出于此目的服务器管理员可能要求以下数据如独立ID、已安装软件列表、系统设置、或加密过的文件校验值。</p><b>任何数据都仅将在验证过服务器的身份ID之后被发出。</b>]]></string>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 Chris Chiang
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.
-->
<resources>
<!-- the order here must match the enum entries in VpnType.java -->
<string-array name="vpn_types">
<item>IKEv2 EAP (用戶名稱/密碼)</item>
<item>IKEv2 憑證</item>
<item>IKEv2 憑證 + EAP (用戶名稱/密碼)</item>
<item>IKEv2 EAP-TLS (憑證)</item>
<item>IKEv2 EAP-TNC (用戶名稱/密碼)</item>
</string-array>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 Chris Chiang
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.
-->
<resources>
<!-- Application -->
<string name="app_name">strongSwan VPN 用戶端</string>
<string name="main_activity_name">strongSwan</string>
<string name="show_log">觀看日誌</string>
<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="loading">載入中&#8230;</string>
<string name="profile_not_found">沒有找到設定檔</string>
<string name="strongswan_shortcut">strongSwan快速選單</string>
<!-- Log view -->
<string name="log_title">日誌</string>
<string name="send_log">發送日誌檔</string>
<string name="empty_log">沒有日誌檔</string>
<string name="log_mail_subject">strongSwan %1$s 日誌檔</string>
<!-- VPN profile list -->
<string name="no_profiles">沒有設定檔</string>
<string name="add_profile">新增VPN設定檔</string>
<string name="edit_profile">編輯</string>
<string name="delete_profile">刪除</string>
<string name="select_profiles">選擇設定檔</string>
<string name="profiles_deleted">選擇的設定檔已經刪除</string>
<string name="no_profile_selected">沒有選擇設定檔</string>
<string name="one_profile_selected">已選擇1個設定檔</string>
<string name="x_profiles_selected">已選擇%1$d個設定檔</string>
<!-- VPN profile details -->
<string name="profile_edit_save">儲存</string>
<string name="profile_edit_import">匯入</string>
<string name="profile_edit_cancel">取消</string>
<string name="profile_name_label">設定檔名稱(選填)</string>
<string name="profile_name_label_simple">設定檔名稱</string>
<string name="profile_name_hint">預設為已設定的伺服器位置</string>
<string name="profile_name_hint_gateway">預設為 \"%1$s\"</string>
<string name="profile_gateway_label">伺服器位置</string>
<string name="profile_gateway_hint">IP位置或伺服器網域</string>
<string name="profile_vpn_type_label">VPN類型</string>
<string name="profile_username_label">用戶ID</string>
<string name="profile_password_label">密碼(選填)</string>
<string name="profile_password_hint">可留空白在需要時才請您設定</string>
<string name="profile_user_certificate_label">用戶憑證</string>
<string name="profile_user_select_certificate_label">選擇用戶憑證</string>
<string name="profile_user_select_certificate">選擇指定的用戶憑證</string>
<string name="profile_user_select_id_label">用戶帳號</string>
<string name="profile_user_select_id_init">請先選擇一個憑證</string>
<string name="profile_user_select_id_default">預設(%1$s)</string>
<string name="profile_ca_label">CA憑證</string>
<string name="profile_ca_auto_label">自動選擇</string>
<string name="profile_ca_select_certificate_label">選擇CA憑證</string>
<string name="profile_ca_select_certificate">選擇一個指定的CA憑證</string>
<string name="profile_advanced_label">進階設定</string>
<string name="profile_show_advanced_label">顯示進階設定</string>
<string name="profile_remote_id_label">伺服器ID</string>
<string name="profile_remote_id_hint">預設為已設定的伺服器位置。自訂值會在授權期間送到伺服器</string>
<string name="profile_remote_id_hint_gateway">預設為 \"%1$s\"。自訂值會在授權期間送到伺服器</string>
<string name="profile_mtu_label">VPN通道裝置的MTU值</string>
<string name="profile_mtu_hint">如果在某個網路下預設值不適合</string>
<string name="profile_port_label">伺服器Port</string>
<string name="profile_port_hint">如果和預設值不同則需要連接的UDP Port</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_split_tunnelingv6_title">屏蔽不通过VPN的IPV6流量</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">匯入VPN設定檔</string>
<string name="profile_import_failed">匯入VPN設定檔失敗</string>
<string name="profile_import_failed_detail">匯入VPN設定檔失敗: %1$s</string>
<string name="profile_import_failed_not_found">沒有找到檔案</string>
<string name="profile_import_failed_host">不明主機</string>
<string name="profile_import_failed_tls">TLS連線失敗</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</string>
<string name="profile_import_exists">這個VPN設定檔已經存在當前設定檔會被覆蓋。</string>
<string name="profile_cert_import">從VPN設定檔匯入憑證</string>
<string name="profile_cert_alias">\"%1$s\" 對應的憑證</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">請填寫必要訊息才能初始化連線</string>
<string name="alert_text_no_input_username">請輸入您的用戶名稱</string>
<string name="alert_text_nocertfound_title">沒有選擇CA憑證</string>
<string name="alert_text_nocertfound">請選擇一項或啟動 <i>自動選擇</i></string>
<string name="alert_text_out_of_range">請輸入一個數字範圍從%1$d到%2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC可能會影響您的隱私安全</string>
<string name="tnc_notice_subtitle">裝置資料已經發送給伺服器管理者</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) 可以讓伺服器管理者評估用戶裝置的狀況。</p><p>在這個目的下伺服器管理者可能會要求以下資料例如ID、已安裝的App項目、系統設定、或加密檔案驗證值。</p><b>任何資料都只有在驗證伺服器的身分ID之後才會被送出。</b>]]></string>
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">CA憑證</string>
<string name="no_certificates">沒有憑證</string>
<string name="reload_trusted_certs">重新載入CA憑證</string>
<string name="system_tab">系統</string>
<string name="user_tab">用戶</string>
<string name="local_tab">已經匯入</string>
<string name="delete_certificate_question">是否刪除憑證?</string>
<string name="delete_certificate">憑證將被永久移除!</string>
<string name="import_certificate">導入憑證</string>
<string name="cert_imported_successfully">憑證已經成功匯入</string>
<string name="cert_import_failed">憑證匯入失敗</string>
<!-- VPN state fragment -->
<string name="state_label">狀態:</string>
<string name="profile_label">設定檔:</string>
<string name="disconnect">結束連線</string>
<string name="state_connecting">連線中&#8230;</string>
<string name="state_connected">已連線</string>
<string name="state_disconnecting">結束連線中&#8230;</string>
<string name="state_disabled">無運作中的VPN</string>
<string name="state_error">錯誤</string>
<!-- IMC state fragment -->
<string name="imc_state_label">評估詳情:</string>
<string name="imc_state_isolate">受限的</string>
<string name="imc_state_block">失敗的</string>
<string name="show_remediation_instructions">觀看修復說明</string>
<!-- Remediation instructions -->
<string name="remediation_instructions_title">修復說明</string>
<!-- Dialogs -->
<string name="login_title">輸入密碼進行連線</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="vpn_connected">VPN已連線</string>
<string name="vpn_profile_connected">這個VPN設定檔目前已經連線。</string>
<string name="reconnect">重新連線</string>
<string name="connect_profile_question">是否連線%1$s</string>
<string name="replaces_active_connection">這將會覆蓋您當前運作的VPN連線</string>
<string name="connect">連線</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2014 Tobias Brunner
Hochschule fuer Technik Rapperswil
Copyright (C) 2012-2017 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
@ -22,4 +22,11 @@
<item>IKEv2 EAP-TLS (Certificate)</item>
<item>IKEv2 EAP-TNC (Username/Password)</item>
</string-array>
</resources>
<!-- the order here must match the enum entries in VpnProfile.java -->
<string-array name="apps_handling">
<item>All applications use the VPN</item>
<item>Exclude selected applications from the VPN</item>
<item>Only selected applications use the VPN</item>
</string-array>
</resources>

View File

@ -39,6 +39,9 @@
<color
name="panel_separator">#5a5a5a</color>
<color
name="checked">#4a4a4a</color>
<color
name="pressed">#5a5a5a</color>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-2016 Tobias Brunner
Copyright (C) 2012-2017 Tobias Brunner
Copyright (C) 2012 Giuliano Grassi
Copyright (C) 2012 Ralf Sager
HSR Hochschule fuer Technik Rapperswil
@ -80,14 +80,25 @@
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</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_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<string name="profile_included_subnets_label">Custom subnets</string>
<string name="profile_included_subnets_hint">Only route traffic to specific subnets via VPN, everything else is routed as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_excluded_subnets_label">Excluded subnets</string>
<string name="profile_excluded_subnets_hint">Traffic to these subnets will not be routed via VPN, but as if there was no VPN (separated by spaces, e.g. \"192.168.1.0/24 2001:db8::/64\")</string>
<string name="profile_select_apps_label">Applications</string>
<string name="profile_select_apps">Select applications</string>
<string name="profile_select_no_apps">No applications selected</string>
<string name="profile_select_one_app">One application selected</string>
<string name="profile_select_x_apps">%1$d applications selected</string>
<string name="profile_import">Import VPN profile</string>
<string name="profile_import_failed">Failed to import VPN profile</string>
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
<string name="profile_import_failed_not_found">File not found</string>
<string name="profile_import_failed_host">Host unknown</string>
<string name="profile_import_failed_tls">TLS handshake failed</string>
<string name="profile_import_failed_value">Invalid value in \"%1$s\"</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>
@ -97,6 +108,7 @@
<string name="alert_text_nocertfound_title">No CA certificate selected</string>
<string name="alert_text_nocertfound">Please select one or activate <i>Select automatically</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
<string name="alert_text_no_subnets">Please enter valid subnets and/or IP addresses, separated by spaces</string>
<string name="tnc_notice_title">EAP-TNC may affect your privacy</string>
<string name="tnc_notice_subtitle">Device data is sent to the server operator</string>
<string name="tnc_notice_details"><![CDATA[<p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b>]]></string>

View File

@ -0,0 +1,312 @@
/*
* Copyright (C) 2017 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.test;
import org.junit.Test;
import org.strongswan.android.utils.IPRange;
import org.strongswan.android.utils.IPRangeSet;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class IPRangeSetTest
{
private void assertSubnets(IPRangeSet set, IPRange...exp)
{
Iterator<IPRange> subnets = set.subnets().iterator();
if (exp.length == 0)
{
assertEquals("no subnets", false, subnets.hasNext());
return;
}
for (IPRange e : exp)
{
assertEquals("has subnet", true, subnets.hasNext());
assertEquals("range", e, subnets.next());
}
assertEquals("done", false, subnets.hasNext());
}
@Test
public void testEmpty()
{
IPRangeSet set = new IPRangeSet();
assertEquals("size", 0, set.size());
assertSubnets(set);
}
@Test
public void testAddDistinct() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
set.add(new IPRange("10.0.1.0/24"));
assertEquals("size", 2, set.size());
assertSubnets(set, new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"));
}
@Test
public void testAddAdjacent() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
set.add(new IPRange("192.168.2.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/24"));
}
@Test
public void testAddAdjacentJoin() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.add(new IPRange("192.168.3.0/24"));
assertEquals("size", 2, set.size());
set.add(new IPRange("192.168.2.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/23"));
}
@Test
public void testAddSame() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
set.add(new IPRange("192.168.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
}
@Test
public void testAddLarger() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
set.add(new IPRange("192.168.0.0/16"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.0.0/16"));
set.add(new IPRange("0.0.0.0/0"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("0.0.0.0/0"));
}
@Test
public void testAddLargerMulti() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.add(new IPRange("10.0.1.0/24"));
set.add(new IPRange("255.255.255.255/32"));
assertEquals("size", 3, set.size());
set.add(new IPRange("0.0.0.0/0"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("0.0.0.0/0"));
}
@Test
public void testAddAll() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
List<IPRange> list = new ArrayList<>();
list.add(new IPRange("192.168.1.0/24"));
list.add(new IPRange("10.0.1.0/24"));
list.add(new IPRange("255.255.255.255/32"));
set.addAll(list);
assertEquals("size", 3, set.size());
assertSubnets(set, new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"),
new IPRange("255.255.255.255/32"));
}
@Test
public void testAddSet() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
IPRangeSet other = new IPRangeSet();
other.add(new IPRange("192.168.1.0/24"));
other.add(new IPRange("10.0.1.0/24"));
other.add(new IPRange("255.255.255.255/32"));
set.add(other);
assertEquals("size", 3, set.size());
assertSubnets(set, new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"),
new IPRange("255.255.255.255/32"));
}
@Test
public void testAddSetIdent() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.add(new IPRange("10.0.1.0/24"));
set.add(new IPRange("255.255.255.255/32"));
set.add(set);
assertEquals("size", 3, set.size());
assertSubnets(set, new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"),
new IPRange("255.255.255.255/32"));
}
@Test
public void testRemoveNothing() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.remove(new IPRange("192.168.2.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
set.remove(new IPRange("10.0.1.0/24"));
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
}
@Test
public void testRemoveAll() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.remove(new IPRange("192.168.0.0/16"));
assertEquals("size", 0, set.size());
assertSubnets(set);
set.add(new IPRange("192.168.1.0/24"));
set.add(new IPRange("10.0.1.0/24"));
set.add(new IPRange("255.255.255.255/32"));
assertEquals("size", 3, set.size());
set.remove(new IPRange("0.0.0.0/0"));
assertEquals("size", 0, set.size());
assertSubnets(set);
}
@Test
public void testRemoveOverlap() throws UnknownHostException
{
IPRangeSet set = new IPRangeSet();
set.add(new IPRange("192.168.1.0/24"));
set.add(new IPRange("192.168.2.0/24"));
set.remove(new IPRange("192.168.1.128", "192.168.2.127"));
assertEquals("size", 2, set.size());
assertSubnets(set, new IPRange("192.168.1.0/25"), new IPRange("192.168.2.128/25"));
}
@Test
public void testRemoveRangesIdent() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 192.168.4.0/24");
set.remove(set);
assertEquals("size", 0, set.size());
assertSubnets(set);
}
@Test
public void testRemoveRanges() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.0.0/16");
IPRangeSet remove = IPRangeSet.fromString("192.168.1.0/24 192.168.3.0/24 192.168.16.0-192.168.255.255");
set.remove(remove);
assertEquals("size", 3, set.size());
assertSubnets(set, new IPRange("192.168.0.0/24"), new IPRange("192.168.2.0/24"),
new IPRange("192.168.4.0/22"), new IPRange("192.168.8.0/21"));
}
@Test
public void testFromStringSingle() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24");
assertEquals("size", 1, set.size());
assertSubnets(set, new IPRange("192.168.1.0/24"));
}
@Test
public void testFromString() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 fec1::/64 10.0.1.0/24 255.255.255.255");
assertEquals("size", 4, set.size());
assertSubnets(set, new IPRange("10.0.1.0/24"), new IPRange("192.168.1.0/24"),
new IPRange("255.255.255.255/32"), new IPRange("fec1::/64"));
}
@Test
public void testFromStringRange() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 10.0.1.0-10.0.1.16");
assertEquals("size", 2, set.size());
assertSubnets(set, new IPRange("10.0.1.0/28"), new IPRange("10.0.1.16/32"),
new IPRange("192.168.1.0/24"));
}
@Test
public void testFromStringInvalidPrefix() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/65");
assertEquals("failed", null, set);
}
@Test
public void testFromStringInvalidRange() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.1 - 192.168.1.10");
assertEquals("failed", null, set);
}
@Test
public void testIteratorRanges() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24 192.168.2.0/24");
assertSubnets(set, new IPRange("192.168.1.0/24"), new IPRange("192.168.2.0/24"));
Iterator<IPRange> iterator = set.iterator();
assertEquals("hasNext", true, iterator.hasNext());
assertEquals("next", new IPRange("192.168.1.0-192.168.2.255"), iterator.next());
assertEquals("hasNext", false, iterator.hasNext());
}
@Test
public void testIteratorRemove() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24");
assertSubnets(set, new IPRange("192.168.1.0/24"));
Iterator<IPRange> iterator = set.iterator();
assertEquals("next", new IPRange("192.168.1.0/24"), iterator.next());
iterator.remove();
assertEquals("hasNext", false, iterator.hasNext());
assertEquals("size", 0, set.size());
}
@Test(expected = UnsupportedOperationException.class)
public void testIteratorSubnetRemove() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.1.0/24");
Iterator<IPRange> iterator = set.subnets().iterator();
assertEquals("hasNext", true, iterator.hasNext());
assertEquals("next", new IPRange("192.168.1.0/24"), iterator.next());
iterator.remove();
}
@Test
public void testToString() throws UnknownHostException
{
IPRangeSet set = IPRangeSet.fromString("192.168.3.10/24 192.168.1.0/24 192.168.1.1-192.168.1.10");
assertEquals("string", "192.168.1.0/24 192.168.3.0/24", set.toString());
}
}

View File

@ -0,0 +1,397 @@
/*
* Copyright (C) 2017 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.test;
import org.junit.Test;
import org.strongswan.android.utils.IPRange;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class IPRangeTest
{
@Test
public void testRangeReversed() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.10", "192.168.0.1");
assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress());
assertEquals("to", "192.168.0.10", test.getTo().getHostAddress());
}
@Test(expected = IllegalArgumentException.class)
public void testRangeInvalid() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1", "fec1::1");
assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress());
}
@Test(expected = UnknownHostException.class)
public void testPrefixAddrInvalid() throws UnknownHostException
{
IPRange test = new IPRange("a.b.c.d", 24);
assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress());
}
@Test(expected = IllegalArgumentException.class)
public void testPrefixNegative() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1", -5);
assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress());
}
@Test(expected = IllegalArgumentException.class)
public void testPrefixLarge() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1", 42);
assertEquals("from", "192.168.0.1", test.getFrom().getHostAddress());
}
private void testPrefix(String from, String to, Integer prefix) throws UnknownHostException
{
IPRange test = new IPRange(from, to);
assertEquals("prefix", prefix, test.getPrefix());
}
@Test
public void testPrefix() throws UnknownHostException
{
testPrefix("192.168.0.0", "192.168.0.255", 24);
testPrefix("192.168.0.8", "192.168.0.15", 29);
testPrefix("192.168.0.1", "192.168.0.255", null);
testPrefix("192.168.0.1", "192.168.0.1", 32);
testPrefix("fec1::0", "fec1::ffff", 112);
testPrefix("fec1::1", "fec1::ffff", null);
testPrefix("fec1::1", "fec1::1", 128);
}
private void testPrefixRange(String base, int prefix, String from, String to) throws UnknownHostException
{
IPRange test = new IPRange(InetAddress.getByName(base), prefix);
assertEquals("from", from, test.getFrom().getHostAddress());
assertEquals("to", to, test.getTo().getHostAddress());
}
@Test
public void testPrefixRange() throws UnknownHostException
{
testPrefixRange("0.0.0.0", 0, "0.0.0.0", "255.255.255.255");
testPrefixRange("0.0.0.0", 32, "0.0.0.0", "0.0.0.0");
testPrefixRange("192.168.1.0", 24, "192.168.1.0", "192.168.1.255");
testPrefixRange("192.168.1.10", 24, "192.168.1.0", "192.168.1.255");
testPrefixRange("192.168.1.64", 26, "192.168.1.64", "192.168.1.127");
testPrefixRange("192.168.1.64", 27, "192.168.1.64", "192.168.1.95");
testPrefixRange("192.168.1.64", 28, "192.168.1.64", "192.168.1.79");
testPrefixRange("192.168.1.255", 29, "192.168.1.248", "192.168.1.255");
testPrefixRange("192.168.1.255", 30, "192.168.1.252", "192.168.1.255");
testPrefixRange("192.168.1.255", 31, "192.168.1.254", "192.168.1.255");
testPrefixRange("192.168.1.255", 32, "192.168.1.255", "192.168.1.255");
testPrefixRange("::", 0, "0:0:0:0:0:0:0:0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
testPrefixRange("fec1::", 64, "fec1:0:0:0:0:0:0:0", "fec1:0:0:0:ffff:ffff:ffff:ffff");
testPrefixRange("fec1::1", 128, "fec1:0:0:0:0:0:0:1", "fec1:0:0:0:0:0:0:1");
testPrefixRange("fec1::10:0", 108, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:1f:ffff");
testPrefixRange("fec1::10:0", 112, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:ffff");
testPrefixRange("fec1::10:0", 113, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:7fff");
testPrefixRange("fec1::10:0", 116, "fec1:0:0:0:0:0:10:0", "fec1:0:0:0:0:0:10:fff");
testPrefixRange("fec1::1:ffff", 112, "fec1:0:0:0:0:0:1:0", "fec1:0:0:0:0:0:1:ffff");
testPrefixRange("fec1::1:ffff", 113, "fec1:0:0:0:0:0:1:8000", "fec1:0:0:0:0:0:1:ffff");
testPrefixRange("fec1::1:ffff", 114, "fec1:0:0:0:0:0:1:c000", "fec1:0:0:0:0:0:1:ffff");
testPrefixRange("fec1::1:ffff", 115, "fec1:0:0:0:0:0:1:e000", "fec1:0:0:0:0:0:1:ffff");
testPrefixRange("fec1::1:ffff", 116, "fec1:0:0:0:0:0:1:f000", "fec1:0:0:0:0:0:1:ffff");
testPrefixRange("fec1::1:ffff", 117, "fec1:0:0:0:0:0:1:f800", "fec1:0:0:0:0:0:1:ffff");
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRAddressInvalid() throws UnknownHostException
{
IPRange test = new IPRange("asdf");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRIncomplete() throws UnknownHostException
{
IPRange test = new IPRange("/32");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRIncompletePrefix() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1/");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRPrefixNegative() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1/-5");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRPrefixLarge() throws UnknownHostException
{
IPRange test = new IPRange("192.168.0.1/33");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testCIDRPrefixLargev6() throws UnknownHostException
{
IPRange test = new IPRange("fec1::1/129");
assertEquals("not reached", null, test);
}
@Test(expected = IllegalArgumentException.class)
public void testRangeMixed() throws UnknownHostException
{
IPRange test = new IPRange("192.168.1.1-fec1::1");
assertEquals("not reached", null, test);
}
private void testCIDR(String cidr, IPRange exp) throws UnknownHostException
{
IPRange test = new IPRange(cidr);
assertEquals("cidr", exp, test);
}
@Test
public void testCIDR() throws UnknownHostException
{
testCIDR("0.0.0.0/0", new IPRange("0.0.0.0", 0));
testCIDR("192.168.1.0/24", new IPRange("192.168.1.0", 24));
testCIDR("192.168.1.10/24", new IPRange("192.168.1.0", 24));
testCIDR("192.168.1.1/32", new IPRange("192.168.1.1", 32));
testCIDR("192.168.1.1", new IPRange("192.168.1.1", 32));
testCIDR("192.168.1.1-192.168.1.10", new IPRange("192.168.1.1", "192.168.1.10"));
testCIDR("::/0", new IPRange("::", 0));
testCIDR("fec1::/64", new IPRange("fec1::", 64));
testCIDR("fec1::10/64", new IPRange("fec1::", 64));
testCIDR("fec1::1/128", new IPRange("fec1::1", 128));
testCIDR("fec1::1", new IPRange("fec1::1", 128));
testCIDR("fec1::1-fec1::5", new IPRange("fec1::1", "fec1::5"));
}
private void testToString(String f, String t, String exp) throws UnknownHostException
{
IPRange a = new IPRange(f, t);
assertEquals("string", exp, a.toString());
}
@Test
public void testToString() throws UnknownHostException
{
testToString("192.168.1.1", "192.168.1.1", "192.168.1.1/32");
testToString("192.168.1.0", "192.168.1.255", "192.168.1.0/24");
testToString("192.168.1.1", "192.168.1.255", "192.168.1.1-192.168.1.255");
testToString("0.0.0.0", "255.255.255.255", "0.0.0.0/0");
testToString("fec1::1", "fec1::1", "fec1:0:0:0:0:0:0:1/128");
testToString("fec1::", "fec1::ffff", "fec1:0:0:0:0:0:0:0/112");
testToString("::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "0:0:0:0:0:0:0:0/0");
}
private void compare(String af, String at, String bf, String bt, int exp) throws UnknownHostException
{
IPRange a = new IPRange(af, at);
IPRange b = new IPRange(bf, bt);
assertEquals("compare", exp, a.compareTo(b));
}
private void compare(String a, int pa, String b, int pb, int exp) throws UnknownHostException
{
IPRange ar = new IPRange(a, pa);
IPRange br = new IPRange(b, pb);
assertEquals("compare", exp, ar.compareTo(br));
}
@Test
public void testCompareTo() throws UnknownHostException
{
compare("192.168.0.0", "192.168.0.10", "192.168.0.0", "192.168.0.10", 0);
compare("192.168.0.1", "192.168.0.10", "192.168.0.0", "192.168.0.10", 1);
compare("192.168.0.0", "192.168.0.9", "192.168.0.0", "192.168.0.10", -1);
compare("192.168.0.0", 24, "192.168.0.0", 24, 0);
compare("192.168.0.0", 24, "192.168.0.0", 28, 1);
compare("192.168.0.0", 28, "192.168.0.0", 24, -1);
compare("192.168.0.0", 32, "192.168.0.255", 32, -1);
compare("10.0.1.0", 24, "192.168.1.0", 24, -1);
compare("10.0.1.0", 24, "fec1::", 64, -1);
compare("fec1::1", 128, "fec1::2", 128, -1);
compare("fec1::1", 126, "fec1::2", 126, 0);
}
@Test
public void testEquals() throws UnknownHostException
{
IPRange a = new IPRange("192.168.1.0/24");
IPRange b = new IPRange("192.168.1.0/24");
assertEquals("same", true, a.equals(a));
assertEquals("equals", true, a.equals(b));
InetAddress c = InetAddress.getByName("192.168.1.0");
assertEquals("null", false, a.equals(c));
assertEquals("null", false, a.equals(null));
}
private void contains(String af, String at, String bf, String bt, boolean exp) throws UnknownHostException
{
IPRange a = new IPRange(af, at);
IPRange b = new IPRange(bf, bt);
assertEquals("contains", exp, a.contains(b));
}
@Test
public void testContains() throws UnknownHostException
{
contains("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", true);
contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.10", true);
contains("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.9", true);
contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9", true);
contains("192.168.1.0", "192.168.1.10", "192.168.1.2", "192.168.1.2", true);
contains("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.11", false);
contains("192.168.1.0", "192.168.1.10", "192.168.0.255", "192.168.1.10", false);
contains("192.168.1.0", "192.168.1.10", "192.168.0.255", "192.168.1.11", false);
contains("192.168.1.1", "192.168.1.1", "192.168.1.0", "192.168.1.0", false);
contains("192.168.1.1", "192.168.1.1", "192.168.1.2", "192.168.1.2", false);
contains("192.168.1.0", "192.168.1.10", "fec1::", "fec1::10", false);
contains("fec1::", "fec1::10", "192.168.1.0", "192.168.1.10", false);
}
private void overlaps(String af, String at, String bf, String bt, boolean exp) throws UnknownHostException
{
IPRange a = new IPRange(af, at);
IPRange b = new IPRange(bf, bt);
assertEquals("b overlaps with a", exp, a.overlaps(b));
assertEquals("a overlaps with b", exp, b.overlaps(a));
}
@Test
public void testOverlaps() throws UnknownHostException
{
overlaps("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.1.10", "192.168.1.20", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.1.9", "192.168.1.20", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.1.11", "192.168.1.20", false);
overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.1.1", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.1.0", true);
overlaps("192.168.1.0", "192.168.1.10", "192.168.0.245", "192.168.0.255", false);
overlaps("192.168.1.0", "192.168.1.10", "fec1::", "fec1::10", false);
}
private void remove(String af, String at, String bf, String bt, IPRange...exp) throws UnknownHostException
{
IPRange a = new IPRange(af, at);
IPRange b = new IPRange(bf, bt);
List<IPRange> l = a.remove(b);
assertEquals("ranges", exp.length, l.size());
for (int i = 0; i < exp.length; i++)
{
assertEquals("range", exp[i], l.get(i));
}
}
@Test
public void testRemove() throws UnknownHostException
{
remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10");
remove("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.255");
remove("192.168.1.0", "192.168.1.10", "10.0.1.0", "10.0.1.10",
new IPRange("192.168.1.0", "192.168.1.10"));
remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.5",
new IPRange("192.168.1.6", "192.168.1.10"));
remove("192.168.1.0", "192.168.1.10", "192.168.1.6", "192.168.1.10",
new IPRange("192.168.1.0", "192.168.1.5"));
remove("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.5",
new IPRange("192.168.1.6", "192.168.1.10"));
remove("192.168.1.0", "192.168.1.10", "192.168.1.6", "192.168.1.255",
new IPRange("192.168.1.0", "192.168.1.5"));
remove("192.168.1.0", "192.168.1.10", "192.168.1.1", "192.168.1.9",
new IPRange("192.168.1.0", "192.168.1.0"), new IPRange("192.168.1.10", "192.168.1.10"));
remove("192.168.1.0", "192.168.1.10", "192.168.1.3", "192.168.1.7",
new IPRange("192.168.1.0", "192.168.1.2"), new IPRange("192.168.1.8", "192.168.1.10"));
remove("192.168.0.0", "192.168.1.255", "192.168.1.0", "192.168.1.255",
new IPRange("192.168.0.0", "192.168.0.255"));
remove("192.168.0.0", "192.168.1.255", "192.168.0.0", "192.168.0.255",
new IPRange("192.168.1.0", "192.168.1.255"));
remove("192.168.1.0", "192.168.1.10", "0.0.0.0", "192.168.1.10");
remove("192.168.1.0", "192.168.1.10", "192.168.1.0", "255.255.255.255");
}
private void merge(String af, String at, String bf, String bt, IPRange exp) throws UnknownHostException
{
IPRange a = new IPRange(af, at);
IPRange b = new IPRange(bf, bt);
IPRange r = a.merge(b);
assertEquals("merge", exp, r);
r = b.merge(a);
assertEquals("reverse", exp, r);
}
@Test
public void testMerge() throws UnknownHostException
{
merge("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.10", new IPRange("192.168.1.0", "192.168.1.10"));
merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.10", new IPRange("192.168.0.0", "192.168.1.10"));
merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.0.255", new IPRange("192.168.0.0", "192.168.1.10"));
merge("192.168.1.0", "192.168.1.10", "192.168.1.0", "192.168.1.255", new IPRange("192.168.1.0", "192.168.1.255"));
merge("192.168.1.0", "192.168.1.10", "192.168.1.11", "192.168.1.255", new IPRange("192.168.1.0", "192.168.1.255"));
merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.1.255", new IPRange("192.168.0.0", "192.168.1.255"));
merge("255.255.255.0", "255.255.255.255", "0.0.0.0", "0.0.0.255", null);
merge("0.0.0.1", "255.255.255.255", "0.0.0.0", "0.0.0.0", new IPRange("0.0.0.0", 0));
merge("0.0.0.0", "255.255.255.254", "255.255.255.255", "255.255.255.255", new IPRange("0.0.0.0", 0));
merge("192.168.1.0", "192.168.1.10", "192.168.0.0", "192.168.0.254", null);
merge("192.168.1.0", "192.168.1.10", "192.168.1.12", "192.168.1.255", null);
merge("192.168.1.0", "192.168.1.10", "fec1::0", "fec1::10", null);
merge("fec1::1:0", "fec1::1:10", "fec1::1:8", "fec1::1:20", new IPRange("fec1::1:0", "fec1::1:20"));
}
private void toSubnet(String f, String t, IPRange...exp) throws UnknownHostException
{
IPRange a = new IPRange(f, t);
List<IPRange> l = a.toSubnets();
assertEquals("ranges", exp.length, l.size());
for (int i = 0; i < exp.length; i++)
{
assertEquals("range", exp[i], l.get(i));
}
}
@Test
public void testToSubnet() throws UnknownHostException
{
toSubnet("0.0.0.0", "255.255.255.255", new IPRange("0.0.0.0", 0));
toSubnet("192.168.1.1", "192.168.1.1", new IPRange("192.168.1.1", 32));
toSubnet("192.168.1.0", "192.168.1.255", new IPRange("192.168.1.0", 24));
toSubnet("192.168.1.0", "192.168.1.10", new IPRange("192.168.1.0", 29),
new IPRange("192.168.1.8", 31), new IPRange("192.168.1.10", 32));
toSubnet("192.168.1.1", "192.168.1.10", new IPRange("192.168.1.1", 32),
new IPRange("192.168.1.2", 31), new IPRange("192.168.1.4", 30),
new IPRange("192.168.1.8", 31), new IPRange("192.168.1.10", 32));
toSubnet("192.168.1.241", "192.168.1.255", new IPRange("192.168.1.241", 32),
new IPRange("192.168.1.242", 31), new IPRange("192.168.1.244", 30),
new IPRange("192.168.1.248", 29));
toSubnet("192.168.254.255", "192.168.255.1", new IPRange("192.168.254.255", 32),
new IPRange("192.168.255.0", 31));
toSubnet("::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", new IPRange("::", 0));
toSubnet("fec1::0", "fec1::a", new IPRange("fec1::0", 125), new IPRange("fec1::8", 127),
new IPRange("fec1::a", 128));
}
}

View File

@ -95,7 +95,8 @@ public class SettingsWriterTest
SettingsWriter writer = new SettingsWriter();
writer.setValue("key", "val\"ue");
writer.setValue("nl", "val\nue");
assertEquals("serialized", "key=\"val\\\"ue\"\nnl=\"val\nue\"\n", writer.serialize());
writer.setValue("bs", "val\\ue");
assertEquals("serialized", "key=\"val\\\"ue\"\nnl=\"val\nue\"\nbs=\"val\\\\ue\"\n", writer.serialize());
}
@Test

View File

@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}

View File

@ -1,6 +1,6 @@
#Tue Sep 20 17:56:35 CEST 2016
#Tue Apr 18 15:57:06 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

View File

@ -113,6 +113,8 @@ LOCAL_SRC_FILES += $(call add_plugin, pubkey)
LOCAL_SRC_FILES += $(call add_plugin, random)
LOCAL_SRC_FILES += $(call add_plugin, revocation)
LOCAL_SRC_FILES += $(call add_plugin, sha1)
LOCAL_SRC_FILES += $(call add_plugin, sha2)
@ -133,6 +135,6 @@ LOCAL_ARM_MODE := arm
LOCAL_PRELINK_MODULE := false
LOCAL_SHARED_LIBRARIES += libdl
LOCAL_LDLIBS += -ldl
include $(BUILD_SHARED_LIBRARY)

View File

@ -16,7 +16,7 @@ LOCAL_SRC_FILES := $(filter %.c,$(libtnccs_la_SOURCES))
LOCAL_SRC_FILES += $(call add_plugin, tnc-imc)
ifneq ($(call plugin_enabled, tnc-imc),)
LOCAL_SHARED_LIBRARIES += libdl
LOCAL_LDLIBS += -ldl
endif
LOCAL_SRC_FILES += $(call add_plugin, tnc-tnccs)