android: Migrate to the Gradle build system
This uses a manual way to trigger the NDK build (the default with on-the-fly Android.mk files does not work for us).
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.strongswan.android"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 22
|
||||
versionCode 28
|
||||
versionName "1.5.0"
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
jni.srcDirs = [] // disables the default ndk-build call (with on-the-fly Android.mk files)
|
||||
jniLibs.srcDir 'src/main/libs'
|
||||
}
|
||||
|
||||
task buildNative(type: Exec) {
|
||||
workingDir 'src/main/jni'
|
||||
commandLine "${android.ndkDirectory}/ndk-build", '-j', Runtime.runtime.availableProcessors()
|
||||
}
|
||||
|
||||
task cleanNative(type: Exec) {
|
||||
workingDir 'src/main/jni'
|
||||
commandLine "${android.ndkDirectory}/ndk-build", 'clean'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
compileTask -> compileTask.dependsOn buildNative
|
||||
}
|
||||
|
||||
clean.dependsOn 'cleanNative'
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012-2015 Tobias Brunner
|
||||
Copyright (C) 2012 Giuliano Grassi
|
||||
Copyright (C) 2012 Ralf Sager
|
||||
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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.strongswan.android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:name=".logic.StrongSwanApplication"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ApplicationTheme"
|
||||
android:allowBackup="false" >
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:label="@string/main_activity_name"
|
||||
android:launchMode="singleTop" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="org.strongswan.android.action.START_PROFILE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.VpnProfileDetailActivity" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.TrustedCertificatesActivity"
|
||||
android:label="@string/trusted_certs_title" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.LogActivity"
|
||||
android:label="@string/log_title" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.RemediationInstructionsActivity"
|
||||
android:label="@string/remediation_instructions_title" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.VpnProfileSelectActivity"
|
||||
android:label="@string/strongswan_shortcut" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.TrustedCertificateImportActivity"
|
||||
android:label="@string/import_certificate"
|
||||
android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/x-x509-ca-cert" />
|
||||
<data android:mimeType="application/x-x509-server-cert" />
|
||||
<data android:mimeType="application/x-pem-file" />
|
||||
<data android:mimeType="application/pkix-cert" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".logic.VpnStateService"
|
||||
android:exported="false" >
|
||||
</service>
|
||||
<service
|
||||
android:name=".logic.CharonVpnService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".data.LogContentProvider"
|
||||
android:authorities="org.strongswan.android.content.log"
|
||||
android:exported="true" >
|
||||
<!-- android:grantUriPermissions="true" combined with a custom permission does
|
||||
not work (probably too many indirections with ACTION_SEND) so we secure
|
||||
this provider with a custom ticketing system -->
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.OpenableColumns;
|
||||
|
||||
public class LogContentProvider extends ContentProvider
|
||||
{
|
||||
private static final String AUTHORITY = "org.strongswan.android.content.log";
|
||||
/* an Uri is valid for 30 minutes */
|
||||
private static final long URI_VALIDITY = 30 * 60 * 1000;
|
||||
private static ConcurrentHashMap<Uri, Long> mUris = new ConcurrentHashMap<Uri, Long>();
|
||||
private File mLogFile;
|
||||
|
||||
public LogContentProvider()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate()
|
||||
{
|
||||
mLogFile = new File(getContext().getFilesDir(), CharonVpnService.LOG_FILE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The log file can only be accessed by Uris created with this method
|
||||
* @return null if failed to create the Uri
|
||||
*/
|
||||
public static Uri createContentUri()
|
||||
{
|
||||
SecureRandom random;
|
||||
try
|
||||
{
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Uri uri = Uri.parse("content://" + AUTHORITY + "/" + random.nextLong());
|
||||
mUris.put(uri, SystemClock.uptimeMillis());
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri)
|
||||
{
|
||||
/* MIME type for our log file */
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder)
|
||||
{
|
||||
/* this is called by apps to find out the name and size of the file.
|
||||
* since we only provide a single file this is simple to implement */
|
||||
if (projection == null || projection.length < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Long timestamp = mUris.get(uri);
|
||||
if (timestamp == null)
|
||||
{ /* don't check the validity as this information is not really private */
|
||||
return null;
|
||||
}
|
||||
MatrixCursor cursor = new MatrixCursor(projection, 1);
|
||||
if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0)))
|
||||
{
|
||||
cursor.newRow().add(CharonVpnService.LOG_FILE);
|
||||
}
|
||||
else if (OpenableColumns.SIZE.equals(cursor.getColumnName(0)))
|
||||
{
|
||||
cursor.newRow().add(mLogFile.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
|
||||
{
|
||||
Long timestamp = mUris.get(uri);
|
||||
if (timestamp != null)
|
||||
{
|
||||
long elapsed = SystemClock.uptimeMillis() - timestamp;
|
||||
if (elapsed > 0 && elapsed < URI_VALIDITY)
|
||||
{ /* we fail if clock wrapped, should happen rarely though */
|
||||
return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
mUris.remove(uri);
|
||||
}
|
||||
return super.openFile(uri, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values)
|
||||
{
|
||||
/* not supported */
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs)
|
||||
{
|
||||
/* not supported */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection,
|
||||
String[] selectionArgs)
|
||||
{
|
||||
/* not supported */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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.data;
|
||||
|
||||
|
||||
public class VpnProfile implements Cloneable
|
||||
{
|
||||
/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
|
||||
public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1;
|
||||
public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2;
|
||||
|
||||
private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
|
||||
private Integer mMTU, mPort, mSplitTunneling;
|
||||
private VpnType mVpnType;
|
||||
private long mId = -1;
|
||||
|
||||
public long getId()
|
||||
{
|
||||
return mId;
|
||||
}
|
||||
|
||||
public void setId(long id)
|
||||
{
|
||||
this.mId = id;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
public String getGateway()
|
||||
{
|
||||
return mGateway;
|
||||
}
|
||||
|
||||
public void setGateway(String gateway)
|
||||
{
|
||||
this.mGateway = gateway;
|
||||
}
|
||||
|
||||
public VpnType getVpnType()
|
||||
{
|
||||
return mVpnType;
|
||||
}
|
||||
|
||||
public void setVpnType(VpnType type)
|
||||
{
|
||||
this.mVpnType = type;
|
||||
}
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return mUsername;
|
||||
}
|
||||
|
||||
public void setUsername(String username)
|
||||
{
|
||||
this.mUsername = username;
|
||||
}
|
||||
|
||||
public String getPassword()
|
||||
{
|
||||
return mPassword;
|
||||
}
|
||||
|
||||
public void setPassword(String password)
|
||||
{
|
||||
this.mPassword = password;
|
||||
}
|
||||
|
||||
public String getCertificateAlias()
|
||||
{
|
||||
return mCertificate;
|
||||
}
|
||||
|
||||
public void setCertificateAlias(String alias)
|
||||
{
|
||||
this.mCertificate = alias;
|
||||
}
|
||||
|
||||
public String getUserCertificateAlias()
|
||||
{
|
||||
return mUserCertificate;
|
||||
}
|
||||
|
||||
public void setUserCertificateAlias(String alias)
|
||||
{
|
||||
this.mUserCertificate = alias;
|
||||
}
|
||||
|
||||
public Integer getMTU()
|
||||
{
|
||||
return mMTU;
|
||||
}
|
||||
|
||||
public void setMTU(Integer mtu)
|
||||
{
|
||||
this.mMTU = mtu;
|
||||
}
|
||||
|
||||
public Integer getPort()
|
||||
{
|
||||
return mPort;
|
||||
}
|
||||
|
||||
public void setPort(Integer port)
|
||||
{
|
||||
this.mPort = port;
|
||||
}
|
||||
|
||||
public Integer getSplitTunneling()
|
||||
{
|
||||
return mSplitTunneling;
|
||||
}
|
||||
|
||||
public void setSplitTunneling(Integer splitTunneling)
|
||||
{
|
||||
this.mSplitTunneling = splitTunneling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o != null && o instanceof VpnProfile)
|
||||
{
|
||||
return this.mId == ((VpnProfile)o).getId();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VpnProfile clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (VpnProfile)super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
public class VpnProfileDataSource
|
||||
{
|
||||
private static final String TAG = VpnProfileDataSource.class.getSimpleName();
|
||||
public static final String KEY_ID = "_id";
|
||||
public static final String KEY_NAME = "name";
|
||||
public static final String KEY_GATEWAY = "gateway";
|
||||
public static final String KEY_VPN_TYPE = "vpn_type";
|
||||
public static final String KEY_USERNAME = "username";
|
||||
public static final String KEY_PASSWORD = "password";
|
||||
public static final String KEY_CERTIFICATE = "certificate";
|
||||
public static final String KEY_USER_CERTIFICATE = "user_certificate";
|
||||
public static final String KEY_MTU = "mtu";
|
||||
public static final String KEY_PORT = "port";
|
||||
public static final String KEY_SPLIT_TUNNELING = "split_tunneling";
|
||||
|
||||
private DatabaseHelper mDbHelper;
|
||||
private SQLiteDatabase mDatabase;
|
||||
private final Context mContext;
|
||||
|
||||
private static final String DATABASE_NAME = "strongswan.db";
|
||||
private static final String TABLE_VPNPROFILE = "vpnprofile";
|
||||
|
||||
private static final int DATABASE_VERSION = 7;
|
||||
|
||||
public static final String DATABASE_CREATE =
|
||||
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
|
||||
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
KEY_NAME + " TEXT NOT NULL," +
|
||||
KEY_GATEWAY + " TEXT NOT NULL," +
|
||||
KEY_VPN_TYPE + " TEXT NOT NULL," +
|
||||
KEY_USERNAME + " TEXT," +
|
||||
KEY_PASSWORD + " TEXT," +
|
||||
KEY_CERTIFICATE + " TEXT," +
|
||||
KEY_USER_CERTIFICATE + " TEXT," +
|
||||
KEY_MTU + " INTEGER," +
|
||||
KEY_PORT + " INTEGER," +
|
||||
KEY_SPLIT_TUNNELING + " INTEGER" +
|
||||
");";
|
||||
private static final String[] ALL_COLUMNS = new String[] {
|
||||
KEY_ID,
|
||||
KEY_NAME,
|
||||
KEY_GATEWAY,
|
||||
KEY_VPN_TYPE,
|
||||
KEY_USERNAME,
|
||||
KEY_PASSWORD,
|
||||
KEY_CERTIFICATE,
|
||||
KEY_USER_CERTIFICATE,
|
||||
KEY_MTU,
|
||||
KEY_PORT,
|
||||
KEY_SPLIT_TUNNELING,
|
||||
};
|
||||
|
||||
private static class DatabaseHelper extends SQLiteOpenHelper
|
||||
{
|
||||
public DatabaseHelper(Context context)
|
||||
{
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase database)
|
||||
{
|
||||
database.execSQL(DATABASE_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
|
||||
{
|
||||
Log.w(TAG, "Upgrading database from version " + oldVersion +
|
||||
" to " + newVersion);
|
||||
if (oldVersion < 2)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
|
||||
" TEXT;");
|
||||
}
|
||||
if (oldVersion < 3)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
|
||||
" TEXT DEFAULT '';");
|
||||
}
|
||||
if (oldVersion < 4)
|
||||
{ /* remove NOT NULL constraint from username column */
|
||||
updateColumns(db);
|
||||
}
|
||||
if (oldVersion < 5)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU +
|
||||
" INTEGER;");
|
||||
}
|
||||
if (oldVersion < 6)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT +
|
||||
" INTEGER;");
|
||||
}
|
||||
if (oldVersion < 7)
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
|
||||
" INTEGER;");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateColumns(SQLiteDatabase db)
|
||||
{
|
||||
db.beginTransaction();
|
||||
try
|
||||
{
|
||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
|
||||
db.execSQL(DATABASE_CREATE);
|
||||
StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
|
||||
SQLiteQueryBuilder.appendColumns(insert, ALL_COLUMNS);
|
||||
db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
|
||||
db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new VPN profile data source. The context is used to
|
||||
* open/create the database.
|
||||
* @param context context used to access the database
|
||||
*/
|
||||
public VpnProfileDataSource(Context context)
|
||||
{
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the VPN profile data source. The database is automatically created
|
||||
* if it does not yet exist. If that fails an exception is thrown.
|
||||
* @return itself (allows to chain initialization calls)
|
||||
* @throws SQLException if the database could not be opened or created
|
||||
*/
|
||||
public VpnProfileDataSource open() throws SQLException
|
||||
{
|
||||
if (mDbHelper == null)
|
||||
{
|
||||
mDbHelper = new DatabaseHelper(mContext);
|
||||
mDatabase = mDbHelper.getWritableDatabase();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the data source.
|
||||
*/
|
||||
public void close()
|
||||
{
|
||||
if (mDbHelper != null)
|
||||
{
|
||||
mDbHelper.close();
|
||||
mDbHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given VPN profile into the database. On success the Id of
|
||||
* the object is updated and the object returned.
|
||||
*
|
||||
* @param profile the profile to add
|
||||
* @return the added VPN profile or null, if failed
|
||||
*/
|
||||
public VpnProfile insertProfile(VpnProfile profile)
|
||||
{
|
||||
ContentValues values = ContentValuesFromVpnProfile(profile);
|
||||
long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values);
|
||||
if (insertId == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
profile.setId(insertId);
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given VPN profile in the database.
|
||||
* @param profile the profile to update
|
||||
* @return true if update succeeded, false otherwise
|
||||
*/
|
||||
public boolean updateVpnProfile(VpnProfile profile)
|
||||
{
|
||||
long id = profile.getId();
|
||||
ContentValues values = ContentValuesFromVpnProfile(profile);
|
||||
return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given VPN profile from the database.
|
||||
* @param profile the profile to delete
|
||||
* @return true if deleted, false otherwise
|
||||
*/
|
||||
public boolean deleteVpnProfile(VpnProfile profile)
|
||||
{
|
||||
long id = profile.getId();
|
||||
return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single VPN profile from the database.
|
||||
* @param id the ID of the VPN profile
|
||||
* @return the profile or null, if not found
|
||||
*/
|
||||
public VpnProfile getVpnProfile(long id)
|
||||
{
|
||||
VpnProfile profile = null;
|
||||
Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
|
||||
KEY_ID + "=" + id, null, null, null, null);
|
||||
if (cursor.moveToFirst())
|
||||
{
|
||||
profile = VpnProfileFromCursor(cursor);
|
||||
}
|
||||
cursor.close();
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all VPN profiles stored in the database.
|
||||
* @return list of VPN profiles
|
||||
*/
|
||||
public List<VpnProfile> getAllVpnProfiles()
|
||||
{
|
||||
List<VpnProfile> vpnProfiles = new ArrayList<VpnProfile>();
|
||||
|
||||
Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null);
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast())
|
||||
{
|
||||
VpnProfile vpnProfile = VpnProfileFromCursor(cursor);
|
||||
vpnProfiles.add(vpnProfile);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
cursor.close();
|
||||
return vpnProfiles;
|
||||
}
|
||||
|
||||
private VpnProfile VpnProfileFromCursor(Cursor cursor)
|
||||
{
|
||||
VpnProfile profile = new VpnProfile();
|
||||
profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
|
||||
profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
|
||||
profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
|
||||
profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
|
||||
profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME)));
|
||||
profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD)));
|
||||
profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE)));
|
||||
profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE)));
|
||||
profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU)));
|
||||
profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT)));
|
||||
profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING)));
|
||||
return profile;
|
||||
}
|
||||
|
||||
private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
|
||||
{
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_NAME, profile.getName());
|
||||
values.put(KEY_GATEWAY, profile.getGateway());
|
||||
values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
|
||||
values.put(KEY_USERNAME, profile.getUsername());
|
||||
values.put(KEY_PASSWORD, profile.getPassword());
|
||||
values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
|
||||
values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
|
||||
values.put(KEY_MTU, profile.getMTU());
|
||||
values.put(KEY_PORT, profile.getPort());
|
||||
values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
|
||||
return values;
|
||||
}
|
||||
|
||||
private Integer getInt(Cursor cursor, int columnIndex)
|
||||
{
|
||||
return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.data;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public enum VpnType
|
||||
{
|
||||
/* the order here must match the items in R.array.vpn_types */
|
||||
IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)),
|
||||
IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD));
|
||||
|
||||
/**
|
||||
* Features of a VPN type.
|
||||
*/
|
||||
public enum VpnTypeFeature
|
||||
{
|
||||
/** client certificate is required */
|
||||
CERTIFICATE,
|
||||
/** username and password are required */
|
||||
USER_PASS,
|
||||
/** enable BYOD features */
|
||||
BYOD;
|
||||
}
|
||||
|
||||
private String mIdentifier;
|
||||
private EnumSet<VpnTypeFeature> mFeatures;
|
||||
|
||||
/**
|
||||
* Enum which provides additional information about the supported VPN types.
|
||||
*
|
||||
* @param id identifier used to store and transmit this specific type
|
||||
* @param features of the given VPN type
|
||||
* @param certificate true if a client certificate is required
|
||||
*/
|
||||
VpnType(String id, EnumSet<VpnTypeFeature> features)
|
||||
{
|
||||
mIdentifier = id;
|
||||
mFeatures = features;
|
||||
}
|
||||
|
||||
/**
|
||||
* The identifier used to store this value in the database
|
||||
* @return identifier
|
||||
*/
|
||||
public String getIdentifier()
|
||||
{
|
||||
return mIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a feature is supported/required by this type of VPN.
|
||||
*
|
||||
* @return true if the feature is supported/required
|
||||
*/
|
||||
public boolean has(VpnTypeFeature feature)
|
||||
{
|
||||
return mFeatures.contains(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum entry with the given identifier.
|
||||
*
|
||||
* @param identifier get the enum entry with this identifier
|
||||
* @return the enum entry, or the default if not found
|
||||
*/
|
||||
public static VpnType fromIdentifier(String identifier)
|
||||
{
|
||||
for (VpnType type : VpnType.values())
|
||||
{
|
||||
if (identifier.equals(type.mIdentifier))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return VpnType.IKEV2_EAP;
|
||||
}
|
||||
}
|
||||
+857
@@ -0,0 +1,857 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.io.File;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.VpnStateService.ErrorState;
|
||||
import org.strongswan.android.logic.VpnStateService.State;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
import org.strongswan.android.ui.MainActivity;
|
||||
import org.strongswan.android.utils.SettingsWriter;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainException;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
public class CharonVpnService extends VpnService implements Runnable
|
||||
{
|
||||
private static final String TAG = CharonVpnService.class.getSimpleName();
|
||||
public static final String LOG_FILE = "charon.log";
|
||||
|
||||
private String mLogFile;
|
||||
private VpnProfileDataSource mDataSource;
|
||||
private Thread mConnectionHandler;
|
||||
private VpnProfile mCurrentProfile;
|
||||
private volatile String mCurrentCertificateAlias;
|
||||
private volatile String mCurrentUserCertificateAlias;
|
||||
private VpnProfile mNextProfile;
|
||||
private volatile boolean mProfileUpdated;
|
||||
private volatile boolean mTerminate;
|
||||
private volatile boolean mIsDisconnecting;
|
||||
private VpnStateService mService;
|
||||
private final Object mServiceLock = new Object();
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name)
|
||||
{ /* since the service is local this is theoretically only called when the process is terminated */
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
mService = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
mService = ((VpnStateService.LocalBinder)service).getService();
|
||||
}
|
||||
/* we are now ready to start the handler thread */
|
||||
mConnectionHandler.start();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* as defined in charonservice.h
|
||||
*/
|
||||
static final int STATE_CHILD_SA_UP = 1;
|
||||
static final int STATE_CHILD_SA_DOWN = 2;
|
||||
static final int STATE_AUTH_ERROR = 3;
|
||||
static final int STATE_PEER_AUTH_ERROR = 4;
|
||||
static final int STATE_LOOKUP_ERROR = 5;
|
||||
static final int STATE_UNREACHABLE_ERROR = 6;
|
||||
static final int STATE_GENERIC_ERROR = 7;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId)
|
||||
{
|
||||
if (intent != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
|
||||
|
||||
mDataSource = new VpnProfileDataSource(this);
|
||||
mDataSource.open();
|
||||
/* use a separate thread as main thread for charon */
|
||||
mConnectionHandler = new Thread(this);
|
||||
/* the thread is started when the service is bound */
|
||||
bindService(new Intent(this, VpnStateService.class),
|
||||
mServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoke()
|
||||
{ /* the system revoked the rights grated with the initial prepare() call.
|
||||
* called when the user clicks disconnect in the system's VPN dialog */
|
||||
setNextProfile(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
mTerminate = true;
|
||||
setNextProfile(null);
|
||||
try
|
||||
{
|
||||
mConnectionHandler.join();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (mService != null)
|
||||
{
|
||||
unbindService(mServiceConnection);
|
||||
}
|
||||
mDataSource.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the profile that is to be initiated next. Notify the handler thread.
|
||||
*
|
||||
* @param profile the profile to initiate
|
||||
*/
|
||||
private void setNextProfile(VpnProfile profile)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
this.mNextProfile = profile;
|
||||
mProfileUpdated = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!mProfileUpdated)
|
||||
{
|
||||
wait();
|
||||
}
|
||||
|
||||
mProfileUpdated = false;
|
||||
stopCurrentConnection();
|
||||
if (mNextProfile == null)
|
||||
{
|
||||
setState(State.DISABLED);
|
||||
if (mTerminate)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentProfile = mNextProfile;
|
||||
mNextProfile = null;
|
||||
|
||||
/* store this in a separate (volatile) variable to avoid
|
||||
* a possible deadlock during deinitialization */
|
||||
mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
|
||||
mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
|
||||
|
||||
startConnection(mCurrentProfile);
|
||||
mIsDisconnecting = false;
|
||||
|
||||
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling());
|
||||
if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
|
||||
{
|
||||
Log.i(TAG, "charon started");
|
||||
SettingsWriter writer = new SettingsWriter();
|
||||
writer.setValue("global.language", Locale.getDefault().getLanguage());
|
||||
writer.setValue("global.mtu", mCurrentProfile.getMTU());
|
||||
writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
|
||||
writer.setValue("connection.server", mCurrentProfile.getGateway());
|
||||
writer.setValue("connection.port", mCurrentProfile.getPort());
|
||||
writer.setValue("connection.username", mCurrentProfile.getUsername());
|
||||
writer.setValue("connection.password", mCurrentProfile.getPassword());
|
||||
initiate(writer.serialize());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(TAG, "failed to start charon");
|
||||
setError(ErrorState.GENERIC_ERROR);
|
||||
setState(State.DISABLED);
|
||||
mCurrentProfile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex)
|
||||
{
|
||||
stopCurrentConnection();
|
||||
setState(State.DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any existing connection by deinitializing charon.
|
||||
*/
|
||||
private void stopCurrentConnection()
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (mCurrentProfile != null)
|
||||
{
|
||||
setState(State.DISCONNECTING);
|
||||
mIsDisconnecting = true;
|
||||
deinitializeCharon();
|
||||
Log.i(TAG, "charon stopped");
|
||||
mCurrentProfile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the state service about a new connection attempt.
|
||||
* Called by the handler thread.
|
||||
*
|
||||
* @param profile currently active VPN profile
|
||||
*/
|
||||
private void startConnection(VpnProfile profile)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.startConnection(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current VPN state on the state service. Called by the handler
|
||||
* thread and any of charon's threads.
|
||||
*
|
||||
* @param state current state
|
||||
*/
|
||||
private void setState(State state)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.setState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an error on the state service. Called by the handler thread and any
|
||||
* of charon's threads.
|
||||
*
|
||||
* @param error error state
|
||||
*/
|
||||
private void setError(ErrorState error)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.setError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IMC state on the state service. Called by the handler thread and
|
||||
* any of charon's threads.
|
||||
*
|
||||
* @param state IMC state
|
||||
*/
|
||||
private void setImcState(ImcState state)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.setImcState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an error on the state service. Called by the handler thread and any
|
||||
* of charon's threads.
|
||||
*
|
||||
* @param error error state
|
||||
*/
|
||||
private void setErrorDisconnect(ErrorState error)
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
if (!mIsDisconnecting)
|
||||
{
|
||||
mService.setError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the current connection.
|
||||
* Called via JNI by different threads (but not concurrently).
|
||||
*
|
||||
* @param status new state
|
||||
*/
|
||||
public void updateStatus(int status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case STATE_CHILD_SA_DOWN:
|
||||
if (!mIsDisconnecting)
|
||||
{
|
||||
setState(State.CONNECTING);
|
||||
}
|
||||
break;
|
||||
case STATE_CHILD_SA_UP:
|
||||
setState(State.CONNECTED);
|
||||
break;
|
||||
case STATE_AUTH_ERROR:
|
||||
setErrorDisconnect(ErrorState.AUTH_FAILED);
|
||||
break;
|
||||
case STATE_PEER_AUTH_ERROR:
|
||||
setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
|
||||
break;
|
||||
case STATE_LOOKUP_ERROR:
|
||||
setErrorDisconnect(ErrorState.LOOKUP_FAILED);
|
||||
break;
|
||||
case STATE_UNREACHABLE_ERROR:
|
||||
setErrorDisconnect(ErrorState.UNREACHABLE);
|
||||
break;
|
||||
case STATE_GENERIC_ERROR:
|
||||
setErrorDisconnect(ErrorState.GENERIC_ERROR);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown status code received");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the IMC state of the current connection.
|
||||
* Called via JNI by different threads (but not concurrently).
|
||||
*
|
||||
* @param value new state
|
||||
*/
|
||||
public void updateImcState(int value)
|
||||
{
|
||||
ImcState state = ImcState.fromValue(value);
|
||||
if (state != null)
|
||||
{
|
||||
setImcState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a remediation instruction to the VPN state service.
|
||||
* Called via JNI by different threads (but not concurrently).
|
||||
*
|
||||
* @param xml XML text
|
||||
*/
|
||||
public void addRemediationInstruction(String xml)
|
||||
{
|
||||
for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
|
||||
{
|
||||
synchronized (mServiceLock)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.addRemediationInstruction(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called via JNI to generate a list of DER encoded CA certificates
|
||||
* as byte array.
|
||||
*
|
||||
* @return a list of DER encoded CA certificates
|
||||
*/
|
||||
private byte[][] getTrustedCertificates()
|
||||
{
|
||||
ArrayList<byte[]> certs = new ArrayList<byte[]>();
|
||||
TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
|
||||
try
|
||||
{
|
||||
String alias = this.mCurrentCertificateAlias;
|
||||
if (alias != null)
|
||||
{
|
||||
X509Certificate cert = certman.getCACertificateFromAlias(alias);
|
||||
if (cert == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
certs.add(cert.getEncoded());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (X509Certificate cert : certman.getAllCACertificates().values())
|
||||
{
|
||||
certs.add(cert.getEncoded());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CertificateEncodingException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return certs.toArray(new byte[certs.size()][]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called via JNI to get a list containing the DER encoded certificates
|
||||
* of the user selected certificate chain (beginning with the user certificate).
|
||||
*
|
||||
* Since this method is called from a thread of charon's thread pool we are safe
|
||||
* to call methods on KeyChain directly.
|
||||
*
|
||||
* @return list containing the certificates (first element is the user certificate)
|
||||
* @throws InterruptedException
|
||||
* @throws KeyChainException
|
||||
* @throws CertificateEncodingException
|
||||
*/
|
||||
private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
|
||||
{
|
||||
ArrayList<byte[]> encodings = new ArrayList<byte[]>();
|
||||
X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
|
||||
if (chain == null || chain.length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (X509Certificate cert : chain)
|
||||
{
|
||||
encodings.add(cert.getEncoded());
|
||||
}
|
||||
return encodings.toArray(new byte[encodings.size()][]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called via JNI to get the private key the user selected.
|
||||
*
|
||||
* Since this method is called from a thread of charon's thread pool we are safe
|
||||
* to call methods on KeyChain directly.
|
||||
*
|
||||
* @return the private key
|
||||
* @throws InterruptedException
|
||||
* @throws KeyChainException
|
||||
* @throws CertificateEncodingException
|
||||
*/
|
||||
private PrivateKey getUserKey() throws KeyChainException, InterruptedException
|
||||
{
|
||||
return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of charon, provided by libandroidbridge.so
|
||||
*
|
||||
* @param builder BuilderAdapter for this connection
|
||||
* @param logfile absolute path to the logfile
|
||||
* @param boyd enable BYOD features
|
||||
* @return TRUE if initialization was successful
|
||||
*/
|
||||
public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
|
||||
|
||||
/**
|
||||
* Deinitialize charon, provided by libandroidbridge.so
|
||||
*/
|
||||
public native void deinitializeCharon();
|
||||
|
||||
/**
|
||||
* Initiate VPN, provided by libandroidbridge.so
|
||||
*/
|
||||
public native void initiate(String config);
|
||||
|
||||
/**
|
||||
* Adapter for VpnService.Builder which is used to access it safely via JNI.
|
||||
* There is a corresponding C object to access it from native code.
|
||||
*/
|
||||
public class BuilderAdapter
|
||||
{
|
||||
private final String mName;
|
||||
private final Integer mSplitTunneling;
|
||||
private VpnService.Builder mBuilder;
|
||||
private BuilderCache mCache;
|
||||
private BuilderCache mEstablishedCache;
|
||||
|
||||
public BuilderAdapter(String name, Integer splitTunneling)
|
||||
{
|
||||
mName = name;
|
||||
mSplitTunneling = splitTunneling;
|
||||
mBuilder = createBuilder(name);
|
||||
mCache = new BuilderCache(mSplitTunneling);
|
||||
}
|
||||
|
||||
private VpnService.Builder createBuilder(String name)
|
||||
{
|
||||
VpnService.Builder builder = new CharonVpnService.Builder();
|
||||
builder.setSession(mName);
|
||||
|
||||
/* even though the option displayed in the system dialog says "Configure"
|
||||
* we just use our main Activity */
|
||||
Context context = getApplicationContext();
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setConfigureIntent(pending);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public synchronized boolean addAddress(String address, int prefixLength)
|
||||
{
|
||||
try
|
||||
{
|
||||
mCache.addAddress(address, prefixLength);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean addDnsServer(String address)
|
||||
{
|
||||
try
|
||||
{
|
||||
mBuilder.addDnsServer(address);
|
||||
mCache.recordAddressFamily(address);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean addRoute(String address, int prefixLength)
|
||||
{
|
||||
try
|
||||
{
|
||||
mCache.addRoute(address, prefixLength);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean addSearchDomain(String domain)
|
||||
{
|
||||
try
|
||||
{
|
||||
mBuilder.addSearchDomain(domain);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean setMtu(int mtu)
|
||||
{
|
||||
try
|
||||
{
|
||||
mCache.setMtu(mtu);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized int establish()
|
||||
{
|
||||
ParcelFileDescriptor fd;
|
||||
try
|
||||
{
|
||||
mCache.applyData(mBuilder);
|
||||
fd = mBuilder.establish();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
if (fd == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
/* 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);
|
||||
mEstablishedCache = mCache;
|
||||
mCache = new BuilderCache(mSplitTunneling);
|
||||
return fd.detachFd();
|
||||
}
|
||||
|
||||
public synchronized int establishNoDns()
|
||||
{
|
||||
ParcelFileDescriptor fd;
|
||||
|
||||
if (mEstablishedCache == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
try
|
||||
{
|
||||
Builder builder = createBuilder(mName);
|
||||
mEstablishedCache.applyData(builder);
|
||||
fd = builder.establish();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
if (fd == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return fd.detachFd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache non DNS related information so we can recreate the builder without
|
||||
* that information when reestablishing IKE_SAs
|
||||
*/
|
||||
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 int mSplitTunneling;
|
||||
private int mMtu;
|
||||
private boolean mIPv4Seen, mIPv6Seen;
|
||||
|
||||
public BuilderCache(Integer splitTunneling)
|
||||
{
|
||||
mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
|
||||
}
|
||||
|
||||
public void addAddress(String address, int prefixLength)
|
||||
{
|
||||
mAddresses.add(new PrefixedAddress(address, prefixLength));
|
||||
recordAddressFamily(address);
|
||||
}
|
||||
|
||||
public void addRoute(String address, int prefixLength)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isIPv6(address))
|
||||
{
|
||||
mRoutesIPv6.add(new PrefixedAddress(address, prefixLength));
|
||||
}
|
||||
else
|
||||
{
|
||||
mRoutesIPv4.add(new PrefixedAddress(address, prefixLength));
|
||||
}
|
||||
}
|
||||
catch (UnknownHostException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMtu(int mtu)
|
||||
{
|
||||
mMtu = mtu;
|
||||
}
|
||||
|
||||
public void recordAddressFamily(String address)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isIPv6(address))
|
||||
{
|
||||
mIPv6Seen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mIPv4Seen = true;
|
||||
}
|
||||
}
|
||||
catch (UnknownHostException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void applyData(VpnService.Builder builder)
|
||||
{
|
||||
for (PrefixedAddress address : mAddresses)
|
||||
{
|
||||
builder.addAddress(address.mAddress, address.mPrefix);
|
||||
}
|
||||
/* 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)
|
||||
{
|
||||
builder.addRoute(route.mAddress, route.mPrefix);
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{ /* allow traffic that would otherwise be blocked to bypass the VPN */
|
||||
builder.allowFamily(OsConstants.AF_INET);
|
||||
}
|
||||
}
|
||||
else if (mIPv4Seen)
|
||||
{ /* only needed if we've seen any addresses. otherwise, traffic
|
||||
* is blocked by default (we also install no routes in that case) */
|
||||
builder.addRoute("0.0.0.0", 0);
|
||||
}
|
||||
/* same thing for IPv6 */
|
||||
if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
|
||||
{
|
||||
if (mIPv6Seen)
|
||||
{
|
||||
for (PrefixedAddress route : mRoutesIPv6)
|
||||
{
|
||||
builder.addRoute(route.mAddress, route.mPrefix);
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
builder.allowFamily(OsConstants.AF_INET6);
|
||||
}
|
||||
}
|
||||
else if (mIPv6Seen)
|
||||
{
|
||||
builder.addRoute("::", 0);
|
||||
}
|
||||
builder.setMtu(mMtu);
|
||||
}
|
||||
|
||||
private boolean isIPv6(String address) throws UnknownHostException
|
||||
{
|
||||
InetAddress addr = InetAddress.getByName(address);
|
||||
if (addr instanceof Inet4Address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (addr instanceof Inet6Address)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class PrefixedAddress
|
||||
{
|
||||
public String mAddress;
|
||||
public int mPrefix;
|
||||
|
||||
public PrefixedAddress(String address, int prefix)
|
||||
{
|
||||
this.mAddress = address;
|
||||
this.mPrefix = prefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The libraries are extracted to /data/data/org.strongswan.android/...
|
||||
* during installation. On newer releases most are loaded in JNI_OnLoad.
|
||||
*/
|
||||
static
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
{
|
||||
System.loadLibrary("strongswan");
|
||||
|
||||
if (MainActivity.USE_BYOD)
|
||||
{
|
||||
System.loadLibrary("tncif");
|
||||
System.loadLibrary("tnccs");
|
||||
System.loadLibrary("imcv");
|
||||
}
|
||||
|
||||
System.loadLibrary("hydra");
|
||||
System.loadLibrary("charon");
|
||||
System.loadLibrary("ipsec");
|
||||
}
|
||||
System.loadLibrary("androidbridge");
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
public class NetworkManager extends BroadcastReceiver
|
||||
{
|
||||
private final Context mContext;
|
||||
private boolean mRegistered;
|
||||
|
||||
public NetworkManager(Context context)
|
||||
{
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void Register()
|
||||
{
|
||||
mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
}
|
||||
|
||||
public void Unregister()
|
||||
{
|
||||
mContext.unregisterReceiver(this);
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo info = null;
|
||||
if (cm != null)
|
||||
{
|
||||
info = cm.getActiveNetworkInfo();
|
||||
}
|
||||
return info != null && info.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
networkChanged(!isConnected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the native parts about a network change
|
||||
*
|
||||
* @param disconnected true if no connection is available at the moment
|
||||
*/
|
||||
public native void networkChanged(boolean disconnected);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.security.Security;
|
||||
|
||||
import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
public class StrongSwanApplication extends Application
|
||||
{
|
||||
private static Context mContext;
|
||||
|
||||
static {
|
||||
Security.addProvider(new LocalCertificateKeyStoreProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
StrongSwanApplication.mContext = getApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current application context
|
||||
* @return context
|
||||
*/
|
||||
public static Context getContext()
|
||||
{
|
||||
return StrongSwanApplication.mContext;
|
||||
}
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class TrustedCertificateManager
|
||||
{
|
||||
private static final String TAG = TrustedCertificateManager.class.getSimpleName();
|
||||
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
|
||||
private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
|
||||
private volatile boolean mReload;
|
||||
private boolean mLoaded;
|
||||
private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
|
||||
|
||||
public enum TrustedCertificateSource
|
||||
{
|
||||
SYSTEM("system:"),
|
||||
USER("user:"),
|
||||
LOCAL("local:");
|
||||
|
||||
private final String mPrefix;
|
||||
|
||||
private TrustedCertificateSource(String prefix)
|
||||
{
|
||||
mPrefix = prefix;
|
||||
}
|
||||
|
||||
private String getPrefix()
|
||||
{
|
||||
return mPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation from other classes.
|
||||
*/
|
||||
private TrustedCertificateManager()
|
||||
{
|
||||
for (String name : new String[] { "LocalCertificateStore", "AndroidCAStore" })
|
||||
{
|
||||
KeyStore store;
|
||||
try
|
||||
{
|
||||
store = KeyStore.getInstance(name);
|
||||
store.load(null,null);
|
||||
mKeyStores.add(store);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "Unable to load KeyStore: " + name);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not instantiated until the first call to getInstance()
|
||||
*/
|
||||
private static class Singleton {
|
||||
public static final TrustedCertificateManager mInstance = new TrustedCertificateManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the single instance of the CA certificate manager.
|
||||
* @return CA certificate manager
|
||||
*/
|
||||
public static TrustedCertificateManager getInstance()
|
||||
{
|
||||
return Singleton.mInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the current load state so that the next call to load()
|
||||
* will force a reload of the cached CA certificates.
|
||||
* @return reference to itself
|
||||
*/
|
||||
public TrustedCertificateManager reset()
|
||||
{
|
||||
Log.d(TAG, "Force reload of cached CA certificates on next load");
|
||||
this.mReload = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the certificates are loaded but does not force a reload.
|
||||
* As this takes a while if the certificates are not loaded yet it should
|
||||
* be called asynchronously.
|
||||
* @return reference to itself
|
||||
*/
|
||||
public TrustedCertificateManager load()
|
||||
{
|
||||
Log.d(TAG, "Ensure cached CA certificates are loaded");
|
||||
this.mLock.writeLock().lock();
|
||||
if (!this.mLoaded || this.mReload)
|
||||
{
|
||||
this.mReload = false;
|
||||
loadCertificates();
|
||||
}
|
||||
this.mLock.writeLock().unlock();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the CA certificate KeyStore and loads the cached certificates.
|
||||
* The lock must be locked when calling this method.
|
||||
*/
|
||||
private void loadCertificates()
|
||||
{
|
||||
Log.d(TAG, "Load cached CA certificates");
|
||||
Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
|
||||
for (KeyStore store : this.mKeyStores)
|
||||
{
|
||||
fetchCertificates(certs, store);
|
||||
}
|
||||
this.mCACerts = certs;
|
||||
this.mLoaded = true;
|
||||
Log.d(TAG, "Cached CA certificates loaded");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all X.509 certificates from the given KeyStore.
|
||||
* @param certs Hashtable to store certificates in
|
||||
* @param store KeyStore to load certificates from
|
||||
*/
|
||||
private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
|
||||
{
|
||||
try
|
||||
{
|
||||
Enumeration<String> aliases = store.aliases();
|
||||
while (aliases.hasMoreElements())
|
||||
{
|
||||
String alias = aliases.nextElement();
|
||||
Certificate cert;
|
||||
cert = store.getCertificate(alias);
|
||||
if (cert != null && cert instanceof X509Certificate)
|
||||
{
|
||||
certs.put(alias, (X509Certificate)cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (KeyStoreException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CA certificate with the given alias.
|
||||
* @param alias alias of the certificate to get
|
||||
* @return the certificate, null if not found
|
||||
*/
|
||||
public X509Certificate getCACertificateFromAlias(String alias)
|
||||
{
|
||||
X509Certificate certificate = null;
|
||||
|
||||
if (this.mLock.readLock().tryLock())
|
||||
{
|
||||
certificate = this.mCACerts.get(alias);
|
||||
this.mLock.readLock().unlock();
|
||||
}
|
||||
else
|
||||
{ /* if we cannot get the lock load it directly from the KeyStore,
|
||||
* should be fast for a single certificate */
|
||||
for (KeyStore store : this.mKeyStores)
|
||||
{
|
||||
try
|
||||
{
|
||||
Certificate cert = store.getCertificate(alias);
|
||||
if (cert != null && cert instanceof X509Certificate)
|
||||
{
|
||||
certificate = (X509Certificate)cert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (KeyStoreException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all CA certificates (from all keystores).
|
||||
* @return Hashtable mapping aliases to certificates
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Hashtable<String, X509Certificate> getAllCACertificates()
|
||||
{
|
||||
Hashtable<String, X509Certificate> certs;
|
||||
this.mLock.readLock().lock();
|
||||
certs = (Hashtable<String, X509Certificate>)this.mCACerts.clone();
|
||||
this.mLock.readLock().unlock();
|
||||
return certs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all certificates from the given source.
|
||||
* @param source type to filter certificates
|
||||
* @return Hashtable mapping aliases to certificates
|
||||
*/
|
||||
public Hashtable<String, X509Certificate> getCACertificates(TrustedCertificateSource source)
|
||||
{
|
||||
Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
|
||||
this.mLock.readLock().lock();
|
||||
for (String alias : this.mCACerts.keySet())
|
||||
{
|
||||
if (alias.startsWith(source.getPrefix()))
|
||||
{
|
||||
certs.put(alias, this.mCACerts.get(alias));
|
||||
}
|
||||
}
|
||||
this.mLock.readLock().unlock();
|
||||
return certs;
|
||||
}
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 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;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class VpnStateService extends Service
|
||||
{
|
||||
private final List<VpnStateListener> mListeners = new ArrayList<VpnStateListener>();
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
private long mConnectionID = 0;
|
||||
private Handler mHandler;
|
||||
private VpnProfile mProfile;
|
||||
private State mState = State.DISABLED;
|
||||
private ErrorState mError = ErrorState.NO_ERROR;
|
||||
private ImcState mImcState = ImcState.UNKNOWN;
|
||||
private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
|
||||
|
||||
public enum State
|
||||
{
|
||||
DISABLED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
}
|
||||
|
||||
public enum ErrorState
|
||||
{
|
||||
NO_ERROR,
|
||||
AUTH_FAILED,
|
||||
PEER_AUTH_FAILED,
|
||||
LOOKUP_FAILED,
|
||||
UNREACHABLE,
|
||||
GENERIC_ERROR,
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface for bound clients that are interested in changes to
|
||||
* this Service.
|
||||
*/
|
||||
public interface VpnStateListener
|
||||
{
|
||||
public void stateChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Binder that allows to directly access this Service class itself
|
||||
* after binding to it.
|
||||
*/
|
||||
public class LocalBinder extends Binder
|
||||
{
|
||||
public VpnStateService getService()
|
||||
{
|
||||
return VpnStateService.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
/* this handler allows us to notify listeners from the UI thread and
|
||||
* not from the threads that actually report any state changes */
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent)
|
||||
{
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener with this Service. We assume this is called from
|
||||
* the main thread so no synchronization is happening.
|
||||
*
|
||||
* @param listener listener to register
|
||||
*/
|
||||
public void registerListener(VpnStateListener listener)
|
||||
{
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener from this Service.
|
||||
*
|
||||
* @param listener listener to unregister
|
||||
*/
|
||||
public void unregisterListener(VpnStateListener listener)
|
||||
{
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current VPN profile.
|
||||
*
|
||||
* @return profile
|
||||
*/
|
||||
public VpnProfile getProfile()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return mProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current connection ID. May be used to track which state
|
||||
* changes have already been handled.
|
||||
*
|
||||
* Is increased when startConnection() is called.
|
||||
*
|
||||
* @return connection ID
|
||||
*/
|
||||
public long getConnectionID()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return mConnectionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state.
|
||||
*
|
||||
* @return state
|
||||
*/
|
||||
public State getState()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current error, if any.
|
||||
*
|
||||
* @return error
|
||||
*/
|
||||
public ErrorState getErrorState()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current IMC state, if any.
|
||||
*
|
||||
* @return imc state
|
||||
*/
|
||||
public ImcState getImcState()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return mImcState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remediation instructions, if any.
|
||||
*
|
||||
* @return read-only list of instructions
|
||||
*/
|
||||
public List<RemediationInstruction> getRemediationInstructions()
|
||||
{ /* only updated from the main thread so no synchronization needed */
|
||||
return Collections.unmodifiableList(mRemediationInstructions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect any existing connection and shutdown the daemon, the
|
||||
* VpnService is not stopped but it is reset so new connections can be
|
||||
* started.
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
/* as soon as the TUN device is created by calling establish() on the
|
||||
* VpnService.Builder object the system binds to the service and keeps
|
||||
* bound until the file descriptor of the TUN device is closed. thus
|
||||
* calling stopService() here would not stop (destroy) the service yet,
|
||||
* instead we call startService() with an empty Intent which shuts down
|
||||
* the daemon (and closes the TUN device, if any) */
|
||||
Context context = getApplicationContext();
|
||||
Intent intent = new Intent(context, CharonVpnService.class);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state and notify all listeners about the change. By using a Handler
|
||||
* this is done from the main UI thread and not the initial reporter thread.
|
||||
* Also, in doing the actual state change from the main thread, listeners
|
||||
* see all changes and none are skipped.
|
||||
*
|
||||
* @param change the state update to perform before notifying listeners, returns true if state changed
|
||||
*/
|
||||
private void notifyListeners(final Callable<Boolean> change)
|
||||
{
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (change.call())
|
||||
{ /* otherwise there is no need to notify the listeners */
|
||||
for (VpnStateListener listener : mListeners)
|
||||
{
|
||||
listener.stateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a connection is started. Sets the currently active VPN
|
||||
* profile, resets IMC and Error state variables, sets the State to
|
||||
* CONNECTING, increases the connection ID, and notifies all listeners.
|
||||
*
|
||||
* May be called from threads other than the main thread.
|
||||
*
|
||||
* @param profile current profile
|
||||
*/
|
||||
public void startConnection(final VpnProfile profile)
|
||||
{
|
||||
notifyListeners(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception
|
||||
{
|
||||
VpnStateService.this.mConnectionID++;
|
||||
VpnStateService.this.mProfile = profile;
|
||||
VpnStateService.this.mState = State.CONNECTING;
|
||||
VpnStateService.this.mError = ErrorState.NO_ERROR;
|
||||
VpnStateService.this.mImcState = ImcState.UNKNOWN;
|
||||
VpnStateService.this.mRemediationInstructions.clear();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state and notify all listeners, if changed.
|
||||
*
|
||||
* May be called from threads other than the main thread.
|
||||
*
|
||||
* @param state new state
|
||||
*/
|
||||
public void setState(final State state)
|
||||
{
|
||||
notifyListeners(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception
|
||||
{
|
||||
if (VpnStateService.this.mState != state)
|
||||
{
|
||||
VpnStateService.this.mState = state;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current error state and notify all listeners, if changed.
|
||||
*
|
||||
* May be called from threads other than the main thread.
|
||||
*
|
||||
* @param error error state
|
||||
*/
|
||||
public void setError(final ErrorState error)
|
||||
{
|
||||
notifyListeners(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception
|
||||
{
|
||||
if (VpnStateService.this.mError != error)
|
||||
{
|
||||
VpnStateService.this.mError = error;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current IMC state and notify all listeners, if changed.
|
||||
*
|
||||
* Setting the state to UNKNOWN clears all remediation instructions.
|
||||
*
|
||||
* May be called from threads other than the main thread.
|
||||
*
|
||||
* @param error error state
|
||||
*/
|
||||
public void setImcState(final ImcState state)
|
||||
{
|
||||
notifyListeners(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception
|
||||
{
|
||||
if (state == ImcState.UNKNOWN)
|
||||
{
|
||||
VpnStateService.this.mRemediationInstructions.clear();
|
||||
}
|
||||
if (VpnStateService.this.mImcState != state)
|
||||
{
|
||||
VpnStateService.this.mImcState = state;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given remediation instruction to the internal list. Listeners
|
||||
* are not notified.
|
||||
*
|
||||
* Instructions are cleared if the IMC state is set to UNKNOWN.
|
||||
*
|
||||
* May be called from threads other than the main thread.
|
||||
*
|
||||
* @param instruction remediation instruction
|
||||
*/
|
||||
public void addRemediationInstruction(final RemediationInstruction instruction)
|
||||
{
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
VpnStateService.this.mRemediationInstructions.add(instruction);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.AttributeType;
|
||||
import org.strongswan.android.logic.imc.collectors.Collector;
|
||||
import org.strongswan.android.logic.imc.collectors.DeviceIdCollector;
|
||||
import org.strongswan.android.logic.imc.collectors.InstalledPackagesCollector;
|
||||
import org.strongswan.android.logic.imc.collectors.PortFilterCollector;
|
||||
import org.strongswan.android.logic.imc.collectors.ProductInformationCollector;
|
||||
import org.strongswan.android.logic.imc.collectors.SettingsCollector;
|
||||
import org.strongswan.android.logic.imc.collectors.StringVersionCollector;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class AndroidImc
|
||||
{
|
||||
private final Context mContext;
|
||||
|
||||
public AndroidImc(Context context)
|
||||
{
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a measurement (the binary encoding of the requested attribute) for
|
||||
* the given vendor specific attribute type.
|
||||
*
|
||||
* @param vendor vendor ID
|
||||
* @param type vendor specific attribute type
|
||||
* @return encoded attribute, or null if not available or failed
|
||||
*/
|
||||
public byte[] getMeasurement(int vendor, int type)
|
||||
{
|
||||
return getMeasurement(vendor, type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a measurement (the binary encoding of the requested attribute) for
|
||||
* the given vendor specific attribute type.
|
||||
*
|
||||
* @param vendor vendor ID
|
||||
* @param type vendor specific attribute type
|
||||
* @param args optional arguments for a measurement
|
||||
* @return encoded attribute, or null if not available or failed
|
||||
*/
|
||||
public byte[] getMeasurement(int vendor, int type, String[] args)
|
||||
{
|
||||
AttributeType attributeType = AttributeType.fromValues(vendor, type);
|
||||
Collector collector = null;
|
||||
|
||||
switch (attributeType)
|
||||
{
|
||||
case IETF_PRODUCT_INFORMATION:
|
||||
collector = new ProductInformationCollector();
|
||||
break;
|
||||
case IETF_STRING_VERSION:
|
||||
collector = new StringVersionCollector();
|
||||
break;
|
||||
case IETF_PORT_FILTER:
|
||||
collector = new PortFilterCollector();
|
||||
break;
|
||||
case IETF_INSTALLED_PACKAGES:
|
||||
collector = new InstalledPackagesCollector(mContext);
|
||||
break;
|
||||
case ITA_SETTINGS:
|
||||
collector = new SettingsCollector(mContext, args);
|
||||
break;
|
||||
case ITA_DEVICE_ID:
|
||||
collector = new DeviceIdCollector(mContext);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (collector != null)
|
||||
{
|
||||
Attribute attribute = collector.getMeasurement();
|
||||
if (attribute != null)
|
||||
{
|
||||
return attribute.getEncoding();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc;
|
||||
|
||||
public enum ImcState
|
||||
{
|
||||
UNKNOWN(0),
|
||||
ALLOW(1),
|
||||
BLOCK(2),
|
||||
ISOLATE(3);
|
||||
|
||||
private final int mValue;
|
||||
|
||||
private ImcState(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the numeric value of the IMC state.
|
||||
* @return numeric value
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum entry from a numeric value, if defined
|
||||
*
|
||||
* @param value numeric value
|
||||
* @return the enum entry or null
|
||||
*/
|
||||
public static ImcState fromValue(int value)
|
||||
{
|
||||
for (ImcState state : ImcState.values())
|
||||
{
|
||||
if (state.mValue == value)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Xml;
|
||||
|
||||
public class RemediationInstruction implements Parcelable
|
||||
{
|
||||
private String mTitle;
|
||||
private String mDescription;
|
||||
private String mHeader;
|
||||
private final List<String> mItems = new LinkedList<String>();
|
||||
|
||||
@Override
|
||||
public int describeContents()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags)
|
||||
{
|
||||
dest.writeString(mTitle);
|
||||
dest.writeString(mDescription);
|
||||
dest.writeString(mHeader);
|
||||
dest.writeStringList(mItems);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<RemediationInstruction> CREATOR = new Creator<RemediationInstruction>() {
|
||||
|
||||
@Override
|
||||
public RemediationInstruction[] newArray(int size)
|
||||
{
|
||||
return new RemediationInstruction[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemediationInstruction createFromParcel(Parcel source)
|
||||
{
|
||||
return new RemediationInstruction(source);
|
||||
}
|
||||
};
|
||||
|
||||
private RemediationInstruction()
|
||||
{
|
||||
}
|
||||
|
||||
private RemediationInstruction(Parcel source)
|
||||
{
|
||||
mTitle = source.readString();
|
||||
mDescription = source.readString();
|
||||
mHeader = source.readString();
|
||||
source.readStringList(mItems);
|
||||
}
|
||||
|
||||
public String getTitle()
|
||||
{
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
private void setTitle(String title)
|
||||
{
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
private void setDescription(String description)
|
||||
{
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
public String getHeader()
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
private void setHeader(String header)
|
||||
{
|
||||
mHeader = header;
|
||||
}
|
||||
|
||||
public List<String> getItems()
|
||||
{
|
||||
return Collections.unmodifiableList(mItems);
|
||||
}
|
||||
|
||||
private void addItem(String item)
|
||||
{
|
||||
mItems.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of RemediationInstruction objects from the given XML data.
|
||||
*
|
||||
* @param xml XML data
|
||||
* @return list of RemediationInstruction objects
|
||||
*/
|
||||
public static List<RemediationInstruction> fromXml(String xml)
|
||||
{
|
||||
List<RemediationInstruction> instructions = new LinkedList<RemediationInstruction>();
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
try
|
||||
{
|
||||
parser.setInput(new StringReader(xml));
|
||||
parser.nextTag();
|
||||
readInstructions(parser, instructions);
|
||||
}
|
||||
catch (XmlPullParserException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a <remediationinstructions> element and store the extracted
|
||||
* RemediationInstruction objects in the given list.
|
||||
*
|
||||
* @param parser
|
||||
* @param instructions
|
||||
* @throws XmlPullParserException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void readInstructions(XmlPullParser parser, List<RemediationInstruction> instructions) throws XmlPullParserException, IOException
|
||||
{
|
||||
parser.require(XmlPullParser.START_TAG, null, "remediationinstructions");
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
{
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (parser.getName().equals("instruction"))
|
||||
{
|
||||
RemediationInstruction instruction = new RemediationInstruction();
|
||||
readInstruction(parser, instruction);
|
||||
instructions.add(instruction);
|
||||
}
|
||||
else
|
||||
{
|
||||
skipTag(parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an <instruction> element and store the information in the
|
||||
* given RemediationInstruction object.
|
||||
*
|
||||
* @param parser
|
||||
* @param instruction
|
||||
* @throws XmlPullParserException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void readInstruction(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException
|
||||
{
|
||||
parser.require(XmlPullParser.START_TAG, null, "instruction");
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
{
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String name = parser.getName();
|
||||
if (name.equals("title"))
|
||||
{
|
||||
instruction.setTitle(parser.nextText());
|
||||
}
|
||||
else if (name.equals("description"))
|
||||
{
|
||||
instruction.setDescription(parser.nextText());
|
||||
}
|
||||
else if (name.equals("itemsheader"))
|
||||
{
|
||||
instruction.setHeader(parser.nextText());
|
||||
}
|
||||
else if (name.equals("items"))
|
||||
{
|
||||
readItems(parser, instruction);
|
||||
}
|
||||
else
|
||||
{
|
||||
skipTag(parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all items of an <items> node and add them to the given
|
||||
* RemediationInstruction object.
|
||||
*
|
||||
* @param parser
|
||||
* @param instruction
|
||||
* @throws XmlPullParserException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void readItems(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException
|
||||
{
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
{
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (parser.getName().equals("item"))
|
||||
{
|
||||
instruction.addItem(parser.nextText());
|
||||
}
|
||||
else
|
||||
{
|
||||
skipTag(parser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the current tag and all child elements.
|
||||
*
|
||||
* @param parser
|
||||
* @throws XmlPullParserException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void skipTag(XmlPullParser parser) throws XmlPullParserException, IOException
|
||||
{
|
||||
int depth = 1;
|
||||
|
||||
parser.require(XmlPullParser.START_TAG, null, null);
|
||||
while (depth != 0)
|
||||
{
|
||||
switch (parser.next())
|
||||
{
|
||||
case XmlPullParser.END_TAG:
|
||||
depth--;
|
||||
break;
|
||||
case XmlPullParser.START_TAG:
|
||||
depth++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.attributes;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by attribute classes
|
||||
*/
|
||||
public interface Attribute
|
||||
{
|
||||
/**
|
||||
* Returns the binary encoding of the attribute
|
||||
* @return binary encoding
|
||||
*/
|
||||
public byte[] getEncoding();
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
public enum AttributeType
|
||||
{
|
||||
/* IETF standard PA-TNC attribute types defined by RFC 5792 */
|
||||
IETF_TESTING(PrivateEnterpriseNumber.IETF, 0),
|
||||
IETF_ATTRIBUTE_REQUEST(PrivateEnterpriseNumber.IETF, 1),
|
||||
IETF_PRODUCT_INFORMATION(PrivateEnterpriseNumber.IETF, 2),
|
||||
IETF_NUMERIC_VERSION(PrivateEnterpriseNumber.IETF, 3),
|
||||
IETF_STRING_VERSION(PrivateEnterpriseNumber.IETF, 4),
|
||||
IETF_OPERATIONAL_STATUS(PrivateEnterpriseNumber.IETF, 5),
|
||||
IETF_PORT_FILTER(PrivateEnterpriseNumber.IETF, 6),
|
||||
IETF_INSTALLED_PACKAGES(PrivateEnterpriseNumber.IETF, 7),
|
||||
IETF_PA_TNC_ERROR(PrivateEnterpriseNumber.IETF, 8),
|
||||
IETF_ASSESSMENT_RESULT(PrivateEnterpriseNumber.IETF, 9),
|
||||
IETF_REMEDIATION_INSTRUCTIONS(PrivateEnterpriseNumber.IETF, 10),
|
||||
IETF_FORWARDING_ENABLED(PrivateEnterpriseNumber.IETF, 11),
|
||||
IETF_FACTORY_DEFAULT_PWD_ENABLED(PrivateEnterpriseNumber.IETF, 12),
|
||||
IETF_RESERVED(PrivateEnterpriseNumber.IETF, 0xffffffff),
|
||||
/* ITA attributes */
|
||||
ITA_SETTINGS(PrivateEnterpriseNumber.ITA, 4),
|
||||
ITA_DEVICE_ID(PrivateEnterpriseNumber.ITA, 8);
|
||||
|
||||
private PrivateEnterpriseNumber mVendor;
|
||||
private int mType;
|
||||
|
||||
/**
|
||||
* Enum type for vendor specific attributes (defined in their namespace)
|
||||
*
|
||||
* @param vendor private enterprise number of vendor
|
||||
* @param type vendor specific attribute type
|
||||
*/
|
||||
private AttributeType(PrivateEnterpriseNumber vendor, int type)
|
||||
{
|
||||
mVendor = vendor;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get private enterprise number of vendor
|
||||
*
|
||||
* @return PEN
|
||||
*/
|
||||
public PrivateEnterpriseNumber getVendor()
|
||||
{
|
||||
return mVendor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vendor specific type
|
||||
*
|
||||
* @return type
|
||||
*/
|
||||
public int getType()
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum entry from the given numeric values, if defined
|
||||
*
|
||||
* @param vendor vendor id
|
||||
* @param type vendor specific type
|
||||
* @return enum entry or null
|
||||
*/
|
||||
public static AttributeType fromValues(int vendor, int type)
|
||||
{
|
||||
PrivateEnterpriseNumber pen = PrivateEnterpriseNumber.fromValue(vendor);
|
||||
|
||||
if (pen == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
for (AttributeType attr : AttributeType.values())
|
||||
{
|
||||
if (attr.mVendor == pen && attr.mType == type)
|
||||
{
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.attributes;
|
||||
|
||||
/**
|
||||
* ITA Device ID attribute
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Device ID (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
public class DeviceIdAttribute implements Attribute
|
||||
{
|
||||
private String mDeviceId;
|
||||
|
||||
/**
|
||||
* Set the device ID
|
||||
* @param version version number
|
||||
*/
|
||||
public void setDeviceId(String deviceId)
|
||||
{
|
||||
this.mDeviceId = deviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
return mDeviceId.getBytes();
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.strongswan.android.utils.BufferedByteWriter;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
/**
|
||||
* PA-TNC Installed Packages attribute (see section 4.2.7 of RFC 5792)
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Reserved | Package Count |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Pkg Name Len | Package Name (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Version Len | Package Version Number (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
public class InstalledPackagesAttribute implements Attribute
|
||||
{
|
||||
private final short RESERVED = 0;
|
||||
private final LinkedList<Pair<String, String>> mPackages = new LinkedList<Pair<String, String>>();
|
||||
|
||||
/**
|
||||
* Add an installed package to this attribute.
|
||||
* @param name name of the package
|
||||
* @param version version number of the package
|
||||
*/
|
||||
public void addPackage(String name, String version)
|
||||
{
|
||||
mPackages.add(new Pair<String, String>(name, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
BufferedByteWriter writer = new BufferedByteWriter();
|
||||
writer.put16(RESERVED);
|
||||
writer.put16((short)mPackages.size());
|
||||
for (Pair<String, String> pair : mPackages)
|
||||
{
|
||||
writer.putLen8(pair.first.getBytes());
|
||||
writer.putLen8(pair.second.getBytes());
|
||||
}
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.strongswan.android.logic.imc.collectors.Protocol;
|
||||
import org.strongswan.android.utils.BufferedByteWriter;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
/**
|
||||
* PA-TNC Port Filter attribute (see section 4.2.6 of RFC 5792)
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Reserved |B| Protocol | Port Number |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Reserved |B| Protocol | Port Number |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
public class PortFilterAttribute implements Attribute
|
||||
{
|
||||
private final LinkedList<Pair<Protocol, Short>> mPorts = new LinkedList<Pair<Protocol, Short>>();
|
||||
|
||||
/**
|
||||
* Add an open port with the given protocol and port number
|
||||
* @param protocol transport protocol
|
||||
* @param port port number
|
||||
*/
|
||||
public void addPort(Protocol protocol, short port)
|
||||
{
|
||||
mPorts.add(new Pair<Protocol, Short>(protocol, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
BufferedByteWriter writer = new BufferedByteWriter();
|
||||
for (Pair<Protocol, Short> port : mPorts)
|
||||
{
|
||||
/* we report open ports, so the BLOCKED flag is not set */
|
||||
writer.put((byte)0);
|
||||
writer.put(port.first.getValue());
|
||||
writer.put16(port.second);
|
||||
}
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.attributes;
|
||||
|
||||
public enum PrivateEnterpriseNumber
|
||||
{
|
||||
IETF(0x000000),
|
||||
GOOGLE(0x002B79),
|
||||
ITA(0x00902a),
|
||||
UNASSIGNED(0xfffffe),
|
||||
RESERVED(0xffffff);
|
||||
|
||||
private int mValue;
|
||||
|
||||
/**
|
||||
* Enum for private enterprise numbers (PEN) as allocated by IANA
|
||||
*
|
||||
* @param value numeric value
|
||||
*/
|
||||
private PrivateEnterpriseNumber(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the numeric value of a PEN
|
||||
*
|
||||
* @return numeric value
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum entry from a numeric value, if defined
|
||||
*
|
||||
* @param value numeric value
|
||||
* @return the enum entry or null
|
||||
*/
|
||||
public static PrivateEnterpriseNumber fromValue(int value)
|
||||
{
|
||||
for (PrivateEnterpriseNumber pen : PrivateEnterpriseNumber.values())
|
||||
{
|
||||
if (pen.mValue == value)
|
||||
{
|
||||
return pen;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
import org.strongswan.android.utils.BufferedByteWriter;
|
||||
|
||||
/**
|
||||
* PA-TNC Product Information attribute (see section 4.2.2 of RFC 5792)
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Product Vendor ID | Product ID |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Product ID | Product Name (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
public class ProductInformationAttribute implements Attribute
|
||||
{
|
||||
private final String PRODUCT_NAME = "Android";
|
||||
private final short PRODUCT_ID = 0;
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
BufferedByteWriter writer = new BufferedByteWriter();
|
||||
writer.put24(PrivateEnterpriseNumber.GOOGLE.getValue());
|
||||
writer.put16(PRODUCT_ID);
|
||||
writer.put(PRODUCT_NAME.getBytes());
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.strongswan.android.utils.BufferedByteWriter;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
/**
|
||||
* ITA Settings attribute
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Settings Count |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Name Length | Name (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ~ Name (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Value Length | Value (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ~ Value (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Name Length | Name (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ~ Name (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Value Length | Value (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ~ Value (Variable Length) ~
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ...........................
|
||||
*/
|
||||
public class SettingsAttribute implements Attribute
|
||||
{
|
||||
private final LinkedList<Pair<String, String>> mSettings = new LinkedList<Pair<String, String>>();
|
||||
|
||||
/**
|
||||
* Add a setting to this attribute.
|
||||
* @param name name of the setting
|
||||
* @param value value of the setting
|
||||
*/
|
||||
public void addSetting(String name, String value)
|
||||
{
|
||||
mSettings.add(new Pair<String, String>(name, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
BufferedByteWriter writer = new BufferedByteWriter();
|
||||
writer.put32(mSettings.size());
|
||||
for (Pair<String, String> pair : mSettings)
|
||||
{
|
||||
writer.putLen16(pair.first.getBytes());
|
||||
writer.putLen16(pair.second.getBytes());
|
||||
}
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.attributes;
|
||||
|
||||
import org.strongswan.android.utils.BufferedByteWriter;
|
||||
|
||||
/**
|
||||
* PA-TNC String Version attribute (see section 4.2.4 of RFC 5792)
|
||||
*
|
||||
* 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Version Len | Product Version Number (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Build Num Len | Internal Build Number (Variable Length) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Config. Len | Configuration Version Number (Variable Length)|
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
public class StringVersionAttribute implements Attribute
|
||||
{
|
||||
private String mVersionNumber;
|
||||
private String mBuildNumber;
|
||||
|
||||
/**
|
||||
* Set the product version number
|
||||
* @param version version number
|
||||
*/
|
||||
public void setProductVersionNumber(String version)
|
||||
{
|
||||
this.mVersionNumber = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the internal build number
|
||||
* @param build build number
|
||||
*/
|
||||
public void setInternalBuildNumber(String build)
|
||||
{
|
||||
this.mBuildNumber = build;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoding()
|
||||
{
|
||||
BufferedByteWriter writer = new BufferedByteWriter();
|
||||
writer.putLen8(mVersionNumber.getBytes());
|
||||
writer.putLen8(mBuildNumber.getBytes());
|
||||
/* we don't provide a configuration number */
|
||||
writer.put((byte)0);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.collectors;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
|
||||
/**
|
||||
* Interface for measurement collectors
|
||||
*/
|
||||
public interface Collector
|
||||
{
|
||||
/**
|
||||
* This method shall return the result of a measurement, if available
|
||||
* @return attribute or null
|
||||
*/
|
||||
public abstract Attribute getMeasurement();
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.collectors;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.DeviceIdAttribute;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
||||
public class DeviceIdCollector implements Collector
|
||||
{
|
||||
private final ContentResolver mContentResolver;
|
||||
|
||||
public DeviceIdCollector(Context context)
|
||||
{
|
||||
mContentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{
|
||||
String id = android.provider.Settings.Secure.getString(mContentResolver, "android_id");
|
||||
if (id != null)
|
||||
{
|
||||
DeviceIdAttribute attribute = new DeviceIdAttribute();
|
||||
attribute.setDeviceId(id);
|
||||
return attribute;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.collectors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.InstalledPackagesAttribute;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
public class InstalledPackagesCollector implements Collector
|
||||
{
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
public InstalledPackagesCollector(Context context)
|
||||
{
|
||||
mPackageManager = context.getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{
|
||||
InstalledPackagesAttribute attribute = new InstalledPackagesAttribute();
|
||||
List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
|
||||
for (PackageInfo info : packages)
|
||||
{
|
||||
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ||
|
||||
info.packageName == null || info.versionName == null)
|
||||
{ /* ignore packages installed in the system image */
|
||||
continue;
|
||||
}
|
||||
attribute.addPackage(info.packageName, info.versionName);
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.collectors;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.PortFilterAttribute;
|
||||
|
||||
public class PortFilterCollector implements Collector
|
||||
{
|
||||
private static Pattern LISTEN = Pattern.compile("\\bLISTEN\\b");
|
||||
private static Pattern PROTOCOL = Pattern.compile("\\b(tcp|udp)6?\\b");
|
||||
private static Pattern PORT = Pattern.compile("[:]{1,3}(\\d{1,5})\\b(?!\\.)");
|
||||
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{
|
||||
PortFilterAttribute attribute = null;
|
||||
try
|
||||
{
|
||||
Process netstat = Runtime.getRuntime().exec("netstat -n");
|
||||
try
|
||||
{
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(netstat.getInputStream()));
|
||||
String line;
|
||||
attribute = new PortFilterAttribute();
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if (!LISTEN.matcher(line).find())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Matcher protocolMatcher = PROTOCOL.matcher(line);
|
||||
Matcher portMatcher = PORT.matcher(line);
|
||||
if (protocolMatcher.find() && portMatcher.find())
|
||||
{
|
||||
Protocol protocol = Protocol.fromName(protocolMatcher.group());
|
||||
if (protocol == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int port = Integer.parseInt(portMatcher.group(1));
|
||||
attribute.addPort(protocol, (short)port);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
netstat.destroy();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.collectors;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.ProductInformationAttribute;
|
||||
|
||||
public class ProductInformationCollector implements Collector
|
||||
{
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{ /* this is currently hardcoded in the attribute */
|
||||
return new ProductInformationAttribute();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.imc.collectors;
|
||||
|
||||
public enum Protocol
|
||||
{
|
||||
TCP((byte)6, "tcp", "tcp6"),
|
||||
UDP((byte)17, "udp", "udp6");
|
||||
|
||||
private final byte mValue;
|
||||
private String[] mNames;
|
||||
|
||||
private Protocol(byte value, String... names)
|
||||
{
|
||||
mValue = value;
|
||||
mNames = names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the numeric value of the protocol.
|
||||
* @return numeric value
|
||||
*/
|
||||
public byte getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the protocol from the given protocol name, if found.
|
||||
* @param name protocol name (e.g. "udp" or "tcp")
|
||||
* @return enum entry or null
|
||||
*/
|
||||
public static Protocol fromName(String name)
|
||||
{
|
||||
for (Protocol protocol : Protocol.values())
|
||||
{
|
||||
for (String keyword : protocol.mNames)
|
||||
{
|
||||
if (keyword.equalsIgnoreCase(name))
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.collectors;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.SettingsAttribute;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
||||
public class SettingsCollector implements Collector
|
||||
{
|
||||
private final ContentResolver mContentResolver;
|
||||
private final String[] mSettings;
|
||||
|
||||
public SettingsCollector(Context context, String[] args)
|
||||
{
|
||||
mContentResolver = context.getContentResolver();
|
||||
mSettings = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{
|
||||
if (mSettings == null || mSettings.length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
SettingsAttribute attribute = new SettingsAttribute();
|
||||
for (String name : mSettings)
|
||||
{
|
||||
String value = android.provider.Settings.Secure.getString(mContentResolver, name.toLowerCase(Locale.US));
|
||||
if (value == null)
|
||||
{
|
||||
value = android.provider.Settings.System.getString(mContentResolver, name.toLowerCase(Locale.US));
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
attribute.addSetting(name, value);
|
||||
}
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* 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.imc.collectors;
|
||||
|
||||
import org.strongswan.android.logic.imc.attributes.Attribute;
|
||||
import org.strongswan.android.logic.imc.attributes.StringVersionAttribute;
|
||||
|
||||
public class StringVersionCollector implements Collector
|
||||
{
|
||||
@Override
|
||||
public Attribute getMeasurement()
|
||||
{
|
||||
StringVersionAttribute attribute = new StringVersionAttribute();
|
||||
attribute.setProductVersionNumber(android.os.Build.VERSION.RELEASE);
|
||||
attribute.setInternalBuildNumber(android.os.Build.DISPLAY);
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.security;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
public class LocalCertificateKeyStoreProvider extends Provider
|
||||
{
|
||||
private static final long serialVersionUID = 3515038332469843219L;
|
||||
|
||||
public LocalCertificateKeyStoreProvider()
|
||||
{
|
||||
super("LocalCertificateKeyStoreProvider", 1.0, "KeyStore provider for local certificates");
|
||||
put("KeyStore.LocalCertificateStore", LocalCertificateKeyStoreSpi.class.getName());
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class LocalCertificateKeyStoreSpi extends KeyStoreSpi
|
||||
{
|
||||
private final LocalCertificateStore mStore = new LocalCertificateStore();
|
||||
|
||||
@Override
|
||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate engineGetCertificate(String alias)
|
||||
{
|
||||
return mStore.getCertificate(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date engineGetCreationDate(String alias)
|
||||
{
|
||||
return mStore.getCreationDate(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException
|
||||
{
|
||||
/* we ignore the given alias as the store calculates it on its own,
|
||||
* duplicates are replaced */
|
||||
if (!mStore.addCertificate(cert))
|
||||
{
|
||||
throw new KeyStoreException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineDeleteEntry(String alias) throws KeyStoreException
|
||||
{
|
||||
mStore.deleteCertificate(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases()
|
||||
{
|
||||
return Collections.enumeration(mStore.aliases());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineContainsAlias(String alias)
|
||||
{
|
||||
return mStore.containsAlias(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize()
|
||||
{
|
||||
return mStore.aliases().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsCertificateEntry(String alias)
|
||||
{
|
||||
return engineContainsAlias(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String engineGetCertificateAlias(Certificate cert)
|
||||
{
|
||||
return mStore.getCertificateAlias(cert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.security;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.strongswan.android.logic.StrongSwanApplication;
|
||||
import org.strongswan.android.utils.Utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class LocalCertificateStore
|
||||
{
|
||||
private static final String FILE_PREFIX = "certificate-";
|
||||
private static final String ALIAS_PREFIX = "local:";
|
||||
private static final Pattern ALIAS_PATTERN = Pattern.compile("^" + ALIAS_PREFIX + "[0-9a-f]{40}$");
|
||||
|
||||
/**
|
||||
* Add the given certificate to the store
|
||||
* @param cert the certificate to add
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean addCertificate(Certificate cert)
|
||||
{
|
||||
if (!(cert instanceof X509Certificate))
|
||||
{ /* only accept X.509 certificates */
|
||||
return false;
|
||||
}
|
||||
String keyid = getKeyId(cert);
|
||||
if (keyid == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FileOutputStream out;
|
||||
try
|
||||
{
|
||||
/* we replace any existing file with the same alias */
|
||||
out = StrongSwanApplication.getContext().openFileOutput(FILE_PREFIX + keyid, Context.MODE_PRIVATE);
|
||||
try
|
||||
{
|
||||
out.write(cert.getEncoded());
|
||||
return true;
|
||||
}
|
||||
catch (CertificateEncodingException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
out.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the certificate with the given alias
|
||||
* @param alias a certificate's alias
|
||||
*/
|
||||
public void deleteCertificate(String alias)
|
||||
{
|
||||
if (ALIAS_PATTERN.matcher(alias).matches())
|
||||
{
|
||||
alias = alias.substring(ALIAS_PREFIX.length());
|
||||
StrongSwanApplication.getContext().deleteFile(FILE_PREFIX + alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the certificate with the given alias
|
||||
* @param alias a certificate's alias
|
||||
* @return certificate object or null
|
||||
*/
|
||||
public X509Certificate getCertificate(String alias)
|
||||
{
|
||||
if (!ALIAS_PATTERN.matcher(alias).matches())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
alias = alias.substring(ALIAS_PREFIX.length());
|
||||
try
|
||||
{
|
||||
FileInputStream in = StrongSwanApplication.getContext().openFileInput(FILE_PREFIX + alias);
|
||||
try
|
||||
{
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate certificate = (X509Certificate)factory.generateCertificate(in);
|
||||
return certificate;
|
||||
}
|
||||
catch (CertificateException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creation date of the certificate with the given alias
|
||||
* @param alias certificate alias
|
||||
* @return creation date or null if not found
|
||||
*/
|
||||
public Date getCreationDate(String alias)
|
||||
{
|
||||
if (!ALIAS_PATTERN.matcher(alias).matches())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
alias = alias.substring(ALIAS_PREFIX.length());
|
||||
File file = StrongSwanApplication.getContext().getFileStreamPath(FILE_PREFIX + alias);
|
||||
return file.exists() ? new Date(file.lastModified()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all known certificate aliases
|
||||
* @return list of aliases
|
||||
*/
|
||||
public ArrayList<String> aliases()
|
||||
{
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
for (String file : StrongSwanApplication.getContext().fileList())
|
||||
{
|
||||
if (file.startsWith(FILE_PREFIX))
|
||||
{
|
||||
list.add(ALIAS_PREFIX + file.substring(FILE_PREFIX.length()));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store contains a certificate with the given alias
|
||||
* @param alias certificate alias
|
||||
* @return true if the store contains the certificate
|
||||
*/
|
||||
public boolean containsAlias(String alias)
|
||||
{
|
||||
return getCreationDate(alias) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a certificate alias based on a SHA-1 hash of the public key.
|
||||
*
|
||||
* @param cert certificate to get an alias for
|
||||
* @return hex encoded alias, or null if failed
|
||||
*/
|
||||
public String getCertificateAlias(Certificate cert)
|
||||
{
|
||||
String keyid = getKeyId(cert);
|
||||
return keyid != null ? ALIAS_PREFIX + keyid : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the SHA-1 hash of the public key of the given certificate.
|
||||
* @param cert certificate to get the key ID from
|
||||
* @return hex encoded SHA-1 hash of the public key or null if failed
|
||||
*/
|
||||
private String getKeyId(Certificate cert)
|
||||
{
|
||||
MessageDigest md;
|
||||
try
|
||||
{
|
||||
md = java.security.MessageDigest.getInstance("SHA1");
|
||||
byte[] hash = md.digest(cert.getPublicKey().getEncoded());
|
||||
return Utils.bytesToHex(hash);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.security;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import android.net.http.SslCertificate;
|
||||
|
||||
public class TrustedCertificateEntry implements Comparable<TrustedCertificateEntry>
|
||||
{
|
||||
private final X509Certificate mCert;
|
||||
private final String mAlias;
|
||||
private String mSubjectPrimary;
|
||||
private String mSubjectSecondary = "";
|
||||
private String mString;
|
||||
|
||||
/**
|
||||
* Create an entry for certificate lists.
|
||||
*
|
||||
* @param alias alias of the certificate (as used in the KeyStore)
|
||||
* @param cert certificate associated with that alias
|
||||
*/
|
||||
public TrustedCertificateEntry(String alias, X509Certificate cert)
|
||||
{
|
||||
mCert = cert;
|
||||
mAlias = alias;
|
||||
|
||||
SslCertificate ssl = new SslCertificate(mCert);
|
||||
String o = ssl.getIssuedTo().getOName();
|
||||
String ou = ssl.getIssuedTo().getUName();
|
||||
String cn = ssl.getIssuedTo().getCName();
|
||||
if (!o.isEmpty())
|
||||
{
|
||||
mSubjectPrimary = o;
|
||||
if (!cn.isEmpty())
|
||||
{
|
||||
mSubjectSecondary = cn;
|
||||
}
|
||||
else if (!ou.isEmpty())
|
||||
{
|
||||
mSubjectSecondary = ou;
|
||||
}
|
||||
}
|
||||
else if (!cn.isEmpty())
|
||||
{
|
||||
mSubjectPrimary = cn;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSubjectPrimary = ssl.getIssuedTo().getDName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main subject of this certificate (O, CN or the complete DN, whatever
|
||||
* is found first).
|
||||
*
|
||||
* @return the main subject
|
||||
*/
|
||||
public String getSubjectPrimary()
|
||||
{
|
||||
return mSubjectPrimary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the secondary subject of this certificate (either CN or OU if primary
|
||||
* subject is O, empty otherwise)
|
||||
*
|
||||
* @return the secondary subject
|
||||
*/
|
||||
public String getSubjectSecondary()
|
||||
{
|
||||
return mSubjectSecondary;
|
||||
}
|
||||
|
||||
/**
|
||||
* The alias associated with this certificate.
|
||||
*
|
||||
* @return KeyStore alias of this certificate
|
||||
*/
|
||||
public String getAlias()
|
||||
{
|
||||
return mAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* The certificate.
|
||||
*
|
||||
* @return certificate
|
||||
*/
|
||||
public X509Certificate getCertificate()
|
||||
{
|
||||
return mCert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{ /* combination of both subject lines, used for filtering lists */
|
||||
if (mString == null)
|
||||
{
|
||||
mString = mSubjectPrimary;
|
||||
if (!mSubjectSecondary.isEmpty())
|
||||
{
|
||||
mString += ", " + mSubjectSecondary;
|
||||
}
|
||||
}
|
||||
return mString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TrustedCertificateEntry another)
|
||||
{
|
||||
int diff = mSubjectPrimary.compareToIgnoreCase(another.mSubjectPrimary);
|
||||
if (diff == 0)
|
||||
{
|
||||
diff = mSubjectSecondary.compareToIgnoreCase(another.mSubjectSecondary);
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 org.strongswan.android.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Class that displays a confirmation dialog to delete a selected local
|
||||
* certificate.
|
||||
*/
|
||||
public class CertificateDeleteConfirmationDialog extends DialogFragment
|
||||
{
|
||||
public static final String ALIAS = "alias";
|
||||
OnCertificateDeleteListener mListener;
|
||||
|
||||
/**
|
||||
* Interface that can be implemented by parent activities to get the
|
||||
* alias of the certificate to delete, if the user confirms the deletion.
|
||||
*/
|
||||
public interface OnCertificateDeleteListener
|
||||
{
|
||||
public void onDelete(String alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
if (activity instanceof OnCertificateDeleteListener)
|
||||
{
|
||||
mListener = (OnCertificateDeleteListener)activity;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.delete_certificate_question)
|
||||
.setMessage(R.string.delete_certificate)
|
||||
.setPositiveButton(R.string.delete_profile, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton)
|
||||
{
|
||||
if (mListener != null)
|
||||
{
|
||||
mListener.onDelete(getArguments().getString(ALIAS));
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.util.ArrayList;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.VpnStateService;
|
||||
import org.strongswan.android.logic.VpnStateService.VpnStateListener;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ImcStateFragment extends Fragment implements VpnStateListener
|
||||
{
|
||||
private TextView mStateView;
|
||||
private TextView mAction;
|
||||
private LinearLayout mButton;
|
||||
private VpnStateService mService;
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name)
|
||||
{
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
mService = ((VpnStateService.LocalBinder)service).getService();
|
||||
mService.registerListener(ImcStateFragment.this);
|
||||
updateView();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/* bind to the service only seems to work from the ApplicationContext */
|
||||
Context context = getActivity().getApplicationContext();
|
||||
context.bindService(new Intent(context, VpnStateService.class),
|
||||
mServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
/* hide it initially */
|
||||
getFragmentManager().beginTransaction().hide(this).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.imc_state_fragment, container, false);
|
||||
|
||||
mButton = (LinearLayout)view.findViewById(R.id.imc_state_button);
|
||||
mButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Intent intent;
|
||||
if (mService != null && !mService.getRemediationInstructions().isEmpty())
|
||||
{
|
||||
intent = new Intent(getActivity(), RemediationInstructionsActivity.class);
|
||||
intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS,
|
||||
new ArrayList<RemediationInstruction>(mService.getRemediationInstructions()));
|
||||
}
|
||||
else
|
||||
{
|
||||
intent = new Intent(getActivity(), LogActivity.class);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
final GestureDetector gestures = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() {
|
||||
/* a better value would be getScaledTouchExplorationTapSlop() but that is hidden */
|
||||
private final int mMinDistance = ViewConfiguration.get(getActivity()).getScaledTouchSlop() * 4;
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
|
||||
{
|
||||
if (Math.abs(e1.getX() - e2.getX()) >= mMinDistance)
|
||||
{ /* only if the user swiped a minimum horizontal distance */
|
||||
if (mService != null)
|
||||
{
|
||||
mService.setImcState(ImcState.UNKNOWN);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
mButton.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event)
|
||||
{
|
||||
return gestures.onTouchEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
mStateView = (TextView)view.findViewById(R.id.imc_state);
|
||||
mAction = (TextView)view.findViewById(R.id.action);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
if (mService != null)
|
||||
{
|
||||
mService.registerListener(this);
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
if (mService != null)
|
||||
{
|
||||
mService.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
if (mService != null)
|
||||
{
|
||||
getActivity().getApplicationContext().unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged()
|
||||
{
|
||||
updateView();
|
||||
}
|
||||
|
||||
public void updateView()
|
||||
{
|
||||
FragmentManager fm = getFragmentManager();
|
||||
if (fm == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FragmentTransaction ft = fm.beginTransaction();
|
||||
|
||||
switch (mService.getImcState())
|
||||
{
|
||||
case UNKNOWN:
|
||||
case ALLOW:
|
||||
ft.hide(this);
|
||||
break;
|
||||
case ISOLATE:
|
||||
mStateView.setText(R.string.imc_state_isolate);
|
||||
mStateView.setTextColor(getResources().getColor(R.color.warning_text));
|
||||
ft.show(this);
|
||||
break;
|
||||
case BLOCK:
|
||||
mStateView.setText(R.string.imc_state_block);
|
||||
mStateView.setTextColor(getResources().getColor(R.color.error_text));
|
||||
ft.show(this);
|
||||
break;
|
||||
}
|
||||
ft.commit();
|
||||
|
||||
mAction.setText(mService.getRemediationInstructions().isEmpty() ? R.string.show_log
|
||||
: R.string.show_remediation_instructions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.io.File;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.LogContentProvider;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class LogActivity extends Activity
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.log_activity);
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.log, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
case R.id.menu_send_log:
|
||||
File logfile = new File(getFilesDir(), CharonVpnService.LOG_FILE);
|
||||
if (!logfile.exists() || logfile.length() == 0)
|
||||
{
|
||||
Toast.makeText(this, getString(R.string.empty_log), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
String version = "";
|
||||
try
|
||||
{
|
||||
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
}
|
||||
catch (NameNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { MainActivity.CONTACT_EMAIL });
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.log_mail_subject), version));
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, LogContentProvider.createContentUri());
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.send_log)));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.os.FileObserver;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class LogFragment extends Fragment implements Runnable
|
||||
{
|
||||
private String mLogFilePath;
|
||||
private Handler mLogHandler;
|
||||
private TextView mLogView;
|
||||
private LogScrollView mScrollView;
|
||||
private BufferedReader mReader;
|
||||
private Thread mThread;
|
||||
private volatile boolean mRunning;
|
||||
private FileObserver mDirectoryObserver;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE;
|
||||
/* use a handler to update the log view */
|
||||
mLogHandler = new Handler();
|
||||
|
||||
mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.log_fragment, null);
|
||||
mLogView = (TextView)view.findViewById(R.id.log_view);
|
||||
mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
startLogReader();
|
||||
mDirectoryObserver.startWatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
mDirectoryObserver.stopWatching();
|
||||
stopLogReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start reading from the log file
|
||||
*/
|
||||
private void startLogReader()
|
||||
{
|
||||
try
|
||||
{
|
||||
mReader = new BufferedReader(new FileReader(mLogFilePath));
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
mReader = new BufferedReader(new StringReader(""));
|
||||
}
|
||||
|
||||
mLogView.setText("");
|
||||
mRunning = true;
|
||||
mThread = new Thread(this);
|
||||
mThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop reading from the log file
|
||||
*/
|
||||
private void stopLogReader()
|
||||
{
|
||||
try
|
||||
{
|
||||
mRunning = false;
|
||||
mThread.interrupt();
|
||||
mThread.join();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given log line to the TextView. We strip the prefix off to save
|
||||
* some space (it is not that helpful for regular users anyway).
|
||||
*
|
||||
* @param line log line to log
|
||||
*/
|
||||
public void logLine(final String line)
|
||||
{
|
||||
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');
|
||||
/* calling autoScroll() directly does not work, probably because content
|
||||
* is not yet updated, so we post this to be done later */
|
||||
mScrollView.post(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mScrollView.autoScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
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 */
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
logLine(line);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FileObserver that checks for changes regarding the log file. Since charon
|
||||
* truncates it (for which there is no explicit event) we check for any modification
|
||||
* to the file, keep track of the file size and reopen it if it got smaller.
|
||||
*/
|
||||
private class LogDirectoryObserver extends FileObserver
|
||||
{
|
||||
private final File mFile;
|
||||
private long mSize;
|
||||
|
||||
public LogDirectoryObserver(String path)
|
||||
{
|
||||
super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE);
|
||||
mFile = new File(mLogFilePath);
|
||||
mSize = mFile.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event, String path)
|
||||
{
|
||||
if (path == null || !path.equals(CharonVpnService.LOG_FILE))
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (event)
|
||||
{ /* even though we only subscribed for these we check them,
|
||||
* as strange events are sometimes received */
|
||||
case FileObserver.CREATE:
|
||||
case FileObserver.DELETE:
|
||||
restartLogReader();
|
||||
break;
|
||||
case FileObserver.MODIFY:
|
||||
/* if the size got smaller reopen the log file, as it was probably truncated */
|
||||
long size = mFile.length();
|
||||
if (size < mSize)
|
||||
{
|
||||
restartLogReader();
|
||||
}
|
||||
mSize = size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void restartLogReader()
|
||||
{
|
||||
/* we are called from a separate thread, so we use the handler */
|
||||
mLogHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
stopLogReader();
|
||||
startLogReader();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.strongswan.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class LogScrollView extends ScrollView
|
||||
{
|
||||
private boolean mAutoScroll = true;
|
||||
|
||||
public LogScrollView(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public LogScrollView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LogScrollView(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev)
|
||||
{
|
||||
/* disable auto-scrolling when the user starts scrolling around */
|
||||
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN)
|
||||
{
|
||||
mAutoScroll = false;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to move newly added content into view by scrolling to the bottom.
|
||||
* Nothing happens if auto-scrolling is disabled.
|
||||
*/
|
||||
public void autoScroll()
|
||||
{
|
||||
if (mAutoScroll)
|
||||
{
|
||||
fullScroll(View.FOCUS_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt)
|
||||
{
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
/* if the user scrolls to the bottom we enable auto-scrolling again */
|
||||
if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight())
|
||||
{
|
||||
mAutoScroll = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.logic.VpnStateService;
|
||||
import org.strongswan.android.logic.VpnStateService.State;
|
||||
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.Service;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.net.VpnService;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MainActivity extends Activity implements OnVpnProfileSelectedListener
|
||||
{
|
||||
public static final String CONTACT_EMAIL = "[email protected]";
|
||||
public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
|
||||
public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
|
||||
/** Use "bring your own device" (BYOD) features */
|
||||
public static final boolean USE_BYOD = true;
|
||||
private static final int PREPARE_VPN_SERVICE = 0;
|
||||
private static final String PROFILE_NAME = "org.strongswan.android.MainActivity.PROFILE_NAME";
|
||||
private static final String PROFILE_REQUIRES_PASSWORD = "org.strongswan.android.MainActivity.REQUIRES_PASSWORD";
|
||||
private static final String PROFILE_RECONNECT = "org.strongswan.android.MainActivity.RECONNECT";
|
||||
private static final String DIALOG_TAG = "Dialog";
|
||||
|
||||
private Bundle mProfileInfo;
|
||||
private VpnStateService mService;
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name)
|
||||
{
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
mService = ((VpnStateService.LocalBinder)service).getService();
|
||||
|
||||
if (START_PROFILE.equals(getIntent().getAction()))
|
||||
{
|
||||
startVpnProfile(getIntent());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
this.bindService(new Intent(this, VpnStateService.class),
|
||||
mServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
|
||||
ActionBar bar = getActionBar();
|
||||
bar.setDisplayShowTitleEnabled(false);
|
||||
|
||||
/* load CA certificates in a background task */
|
||||
new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
if (mService != null)
|
||||
{
|
||||
this.unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to launchMode=singleTop this is called if the Activity already exists
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if (START_PROFILE.equals(intent.getAction()))
|
||||
{
|
||||
startVpnProfile(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.menu_manage_certs:
|
||||
Intent certIntent = new Intent(this, TrustedCertificatesActivity.class);
|
||||
startActivity(certIntent);
|
||||
return true;
|
||||
case R.id.menu_show_log:
|
||||
Intent logIntent = new Intent(this, LogActivity.class);
|
||||
startActivity(logIntent);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the VpnService. If this succeeds the current VPN profile is
|
||||
* started.
|
||||
* @param profileInfo a bundle containing the information about the profile to be started
|
||||
*/
|
||||
protected void prepareVpnService(Bundle profileInfo)
|
||||
{
|
||||
Intent intent;
|
||||
try
|
||||
{
|
||||
intent = VpnService.prepare(this);
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
/* this happens if the always-on VPN feature (Android 4.2+) is activated */
|
||||
VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
|
||||
return;
|
||||
}
|
||||
/* store profile info until the user grants us permission */
|
||||
mProfileInfo = profileInfo;
|
||||
if (intent != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
startActivityForResult(intent, PREPARE_VPN_SERVICE);
|
||||
}
|
||||
catch (ActivityNotFoundException ex)
|
||||
{
|
||||
/* it seems some devices, even though they come with Android 4,
|
||||
* don't have the VPN components built into the system image.
|
||||
* com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
|
||||
* will not be found then */
|
||||
VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ /* user already granted permission to use VpnService */
|
||||
onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (requestCode)
|
||||
{
|
||||
case PREPARE_VPN_SERVICE:
|
||||
if (resultCode == RESULT_OK && mProfileInfo != null)
|
||||
{
|
||||
Intent intent = new Intent(this, CharonVpnService.class);
|
||||
intent.putExtras(mProfileInfo);
|
||||
this.startService(intent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVpnProfileSelected(VpnProfile profile)
|
||||
{
|
||||
Bundle profileInfo = new Bundle();
|
||||
profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
|
||||
profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
|
||||
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
|
||||
profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
|
||||
profileInfo.putString(PROFILE_NAME, profile.getName());
|
||||
|
||||
removeFragmentByTag(DIALOG_TAG);
|
||||
|
||||
if (mService != null && mService.getState() == State.CONNECTED)
|
||||
{
|
||||
profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId());
|
||||
|
||||
ConfirmationDialog dialog = new ConfirmationDialog();
|
||||
dialog.setArguments(profileInfo);
|
||||
dialog.show(this.getFragmentManager(), DIALOG_TAG);
|
||||
return;
|
||||
}
|
||||
startVpnProfile(profileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the given VPN profile asking the user for a password if required.
|
||||
* @param profileInfo data about the profile
|
||||
*/
|
||||
private void startVpnProfile(Bundle profileInfo)
|
||||
{
|
||||
if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
|
||||
profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
|
||||
{
|
||||
LoginDialog login = new LoginDialog();
|
||||
login.setArguments(profileInfo);
|
||||
login.show(getFragmentManager(), DIALOG_TAG);
|
||||
return;
|
||||
}
|
||||
prepareVpnService(profileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the VPN profile referred to by the given intent. Displays an error
|
||||
* if the profile doesn't exist.
|
||||
* @param intent Intent that caused us to start this
|
||||
*/
|
||||
private void startVpnProfile(Intent intent)
|
||||
{
|
||||
long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
|
||||
if (profileId <= 0)
|
||||
{ /* invalid invocation */
|
||||
return;
|
||||
}
|
||||
VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
|
||||
dataSource.open();
|
||||
VpnProfile profile = dataSource.getVpnProfile(profileId);
|
||||
dataSource.close();
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
onVpnProfileSelected(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that loads the cached CA certificates.
|
||||
*/
|
||||
private class LoadCertificatesTask extends AsyncTask<Void, Void, TrustedCertificateManager>
|
||||
{
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
}
|
||||
@Override
|
||||
protected TrustedCertificateManager doInBackground(Void... params)
|
||||
{
|
||||
return TrustedCertificateManager.getInstance().load();
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(TrustedCertificateManager result)
|
||||
{
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss dialog if shown
|
||||
*/
|
||||
public void removeFragmentByTag(String tag)
|
||||
{
|
||||
FragmentManager fm = getFragmentManager();
|
||||
Fragment login = fm.findFragmentByTag(tag);
|
||||
if (login != null)
|
||||
{
|
||||
FragmentTransaction ft = fm.beginTransaction();
|
||||
ft.remove(login);
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that displays a confirmation dialog if a VPN profile is already connected
|
||||
* and then initiates the selected VPN profile if the user confirms the dialog.
|
||||
*/
|
||||
public static class ConfirmationDialog extends DialogFragment
|
||||
{
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
final Bundle profileInfo = getArguments();
|
||||
int icon = android.R.drawable.ic_dialog_alert;
|
||||
int title = R.string.connect_profile_question;
|
||||
int message = R.string.replaces_active_connection;
|
||||
int button = R.string.connect;
|
||||
|
||||
if (profileInfo.getBoolean(PROFILE_RECONNECT))
|
||||
{
|
||||
icon = android.R.drawable.ic_dialog_info;
|
||||
title = R.string.vpn_connected;
|
||||
message = R.string.vpn_profile_connected;
|
||||
button = R.string.reconnect;
|
||||
}
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(icon)
|
||||
.setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME)))
|
||||
.setMessage(message)
|
||||
.setPositiveButton(button, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton)
|
||||
{
|
||||
MainActivity activity = (MainActivity)getActivity();
|
||||
activity.startVpnProfile(profileInfo);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that displays a login dialog and initiates the selected VPN
|
||||
* profile if the user confirms the dialog.
|
||||
*/
|
||||
public static class LoginDialog extends DialogFragment
|
||||
{
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
final Bundle profileInfo = getArguments();
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.login_dialog, null);
|
||||
EditText username = (EditText)view.findViewById(R.id.username);
|
||||
username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
|
||||
final EditText password = (EditText)view.findViewById(R.id.password);
|
||||
|
||||
Builder adb = new AlertDialog.Builder(getActivity());
|
||||
adb.setView(view);
|
||||
adb.setTitle(getString(R.string.login_title));
|
||||
adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton)
|
||||
{
|
||||
MainActivity activity = (MainActivity)getActivity();
|
||||
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
|
||||
activity.prepareVpnService(profileInfo);
|
||||
}
|
||||
});
|
||||
adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return adb.create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an error message which is displayed if VpnService is
|
||||
* not supported on the current device.
|
||||
*/
|
||||
public static class VpnNotSupportedError extends DialogFragment
|
||||
{
|
||||
static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
|
||||
|
||||
public static void showWithMessage(Activity activity, int messageId)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(ERROR_MESSAGE_ID, messageId);
|
||||
VpnNotSupportedError dialog = new VpnNotSupportedError();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(activity.getFragmentManager(), DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
final Bundle arguments = getArguments();
|
||||
final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.vpn_not_supported_title)
|
||||
.setMessage(messageId)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import android.app.ListFragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class RemediationInstructionFragment extends ListFragment
|
||||
{
|
||||
public static final String ARG_REMEDIATION_INSTRUCTION = "instruction";
|
||||
private RemediationInstruction mInstruction = null;
|
||||
private TextView mTitle;
|
||||
private TextView mDescription;
|
||||
private TextView mHeader;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
return inflater.inflate(R.layout.remediation_instruction, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState)
|
||||
{
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
mInstruction = savedInstanceState.getParcelable(ARG_REMEDIATION_INSTRUCTION);
|
||||
}
|
||||
/* show dividers only between list items */
|
||||
getListView().setHeaderDividersEnabled(false);
|
||||
getListView().setFooterDividersEnabled(false);
|
||||
/* don't show loader while adapter is not set */
|
||||
setListShown(true);
|
||||
mTitle = (TextView)getView().findViewById(R.id.title);
|
||||
mDescription = (TextView)getView().findViewById(R.id.description);
|
||||
mHeader = (TextView)getView().findViewById(R.id.list_header);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(ARG_REMEDIATION_INSTRUCTION, mInstruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args != null)
|
||||
{
|
||||
mInstruction = args.getParcelable(ARG_REMEDIATION_INSTRUCTION);
|
||||
}
|
||||
updateView(mInstruction);
|
||||
}
|
||||
|
||||
public void updateView(RemediationInstruction instruction)
|
||||
{
|
||||
mInstruction = instruction;
|
||||
if (mInstruction != null)
|
||||
{
|
||||
mTitle.setText(mInstruction.getTitle());
|
||||
mDescription.setText(mInstruction.getDescription());
|
||||
if (mInstruction.getHeader() != null)
|
||||
{
|
||||
mHeader.setText(mInstruction.getHeader());
|
||||
setListAdapter(new ArrayAdapter<String>(getActivity(),
|
||||
android.R.layout.simple_list_item_1, mInstruction.getItems()));
|
||||
}
|
||||
else
|
||||
{
|
||||
mHeader.setText("");
|
||||
setListAdapter(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mTitle.setText("");
|
||||
mDescription.setText("");
|
||||
mHeader.setText("");
|
||||
setListAdapter(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.util.ArrayList;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
import org.strongswan.android.ui.RemediationInstructionsFragment.OnRemediationInstructionSelectedListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class RemediationInstructionsActivity extends Activity implements OnRemediationInstructionSelectedListener
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.remediation_instructions);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{ /* only update if we're not restoring */
|
||||
return;
|
||||
}
|
||||
RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getFragmentManager().findFragmentById(R.id.remediation_instructions_fragment);
|
||||
if (frag != null)
|
||||
{ /* two-pane layout, update fragment */
|
||||
Bundle extras = getIntent().getExtras();
|
||||
ArrayList<RemediationInstruction> list = extras.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS);
|
||||
frag.updateView(list);
|
||||
}
|
||||
else
|
||||
{ /* one-pane layout, create fragment */
|
||||
frag = new RemediationInstructionsFragment();
|
||||
frag.setArguments(getIntent().getExtras());
|
||||
getFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
/* one-pane layout, pop possible fragment from stack, finish otherwise */
|
||||
if (!getFragmentManager().popBackStackImmediate())
|
||||
{
|
||||
finish();
|
||||
}
|
||||
getActionBar().setTitle(getTitle());
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemediationInstructionSelected(RemediationInstruction instruction)
|
||||
{
|
||||
RemediationInstructionFragment frag = (RemediationInstructionFragment)getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment);
|
||||
|
||||
if (frag != null)
|
||||
{ /* two-pane layout, update directly */
|
||||
frag.updateView(instruction);
|
||||
}
|
||||
else
|
||||
{ /* one-pane layout, replace fragment */
|
||||
frag = new RemediationInstructionFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(RemediationInstructionFragment.ARG_REMEDIATION_INSTRUCTION, instruction);
|
||||
frag.setArguments(args);
|
||||
|
||||
getFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit();
|
||||
getActionBar().setTitle(instruction.getTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.util.ArrayList;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
import org.strongswan.android.ui.adapter.RemediationInstructionAdapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListFragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class RemediationInstructionsFragment extends ListFragment
|
||||
{
|
||||
public static final String EXTRA_REMEDIATION_INSTRUCTIONS = "instructions";
|
||||
private static final String KEY_POSITION = "position";
|
||||
private ArrayList<RemediationInstruction> mInstructions = null;
|
||||
private OnRemediationInstructionSelectedListener mListener;
|
||||
private RemediationInstructionAdapter mAdapter;
|
||||
private int mCurrentPosition = -1;
|
||||
|
||||
/**
|
||||
* The activity containing this fragment should implement this interface
|
||||
*/
|
||||
public interface OnRemediationInstructionSelectedListener
|
||||
{
|
||||
public void onRemediationInstructionSelected(RemediationInstruction instruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState)
|
||||
{
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
mInstructions = savedInstanceState.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS);
|
||||
mCurrentPosition = savedInstanceState.getInt(KEY_POSITION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, mInstructions);
|
||||
outState.putInt(KEY_POSITION, mCurrentPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
|
||||
if (activity instanceof OnRemediationInstructionSelectedListener)
|
||||
{
|
||||
mListener = (OnRemediationInstructionSelectedListener)activity;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
boolean two_pane = getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment) != null;
|
||||
if (two_pane)
|
||||
{ /* two-pane layout, make list items selectable */
|
||||
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
}
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (mInstructions == null && args != null)
|
||||
{
|
||||
mInstructions = args.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS);
|
||||
}
|
||||
updateView(mInstructions);
|
||||
|
||||
if (two_pane && mCurrentPosition == -1 && mInstructions.size() > 0)
|
||||
{ /* two-pane layout, select first instruction */
|
||||
mCurrentPosition = 0;
|
||||
mListener.onRemediationInstructionSelected(mInstructions.get(0));
|
||||
}
|
||||
getListView().setItemChecked(mCurrentPosition, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id)
|
||||
{
|
||||
mCurrentPosition = position;
|
||||
mListener.onRemediationInstructionSelected(mInstructions.get(position));
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
public void updateView(ArrayList<RemediationInstruction> instructions)
|
||||
{
|
||||
if (mAdapter == null)
|
||||
{
|
||||
mAdapter = new RemediationInstructionAdapter(getActivity());
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
mInstructions = instructions;
|
||||
mAdapter.setData(mInstructions);
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class TrustedCertificateImportActivity extends Activity
|
||||
{
|
||||
private static final int OPEN_DOCUMENT = 0;
|
||||
private static final String DIALOG_TAG = "Dialog";
|
||||
|
||||
/* same as those listed in the manifest */
|
||||
private static final String[] ACCEPTED_MIME_TYPES = {
|
||||
"application/x-x509-ca-cert",
|
||||
"application/x-x509-server-cert",
|
||||
"application/x-pem-file",
|
||||
"application/pkix-cert"
|
||||
};
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{ /* do nothing when we are restoring */
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_VIEW.equals(action))
|
||||
{
|
||||
importCertificate(intent.getData());
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||
{
|
||||
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
openIntent.setType("*/*");
|
||||
openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES);
|
||||
startActivityForResult(openIntent, OPEN_DOCUMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (requestCode)
|
||||
{
|
||||
case OPEN_DOCUMENT:
|
||||
if (resultCode == Activity.RESULT_OK && data != null)
|
||||
{
|
||||
importCertificate(data.getData());
|
||||
return;
|
||||
}
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the file pointed to by the given URI as a certificate.
|
||||
* @param uri
|
||||
*/
|
||||
private void importCertificate(Uri uri)
|
||||
{
|
||||
X509Certificate certificate = parseCertificate(uri);
|
||||
if (certificate == null)
|
||||
{
|
||||
Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
/* Ask the user whether to import the certificate. This is particularly
|
||||
* necessary because the import activity can be triggered by any app on
|
||||
* the system. Also, if our app is the only one that is registered to
|
||||
* open certificate files by MIME type the user would have no idea really
|
||||
* where the file was imported just by reading the Toast we display. */
|
||||
ConfirmImportDialog dialog = new ConfirmImportDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
|
||||
dialog.setArguments(args);
|
||||
FragmentTransaction ft = getFragmentManager().beginTransaction();
|
||||
ft.add(dialog, DIALOG_TAG);
|
||||
ft.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the file from the given URI and try to parse it as X.509 certificate.
|
||||
* @param uri
|
||||
* @return certificate or null
|
||||
*/
|
||||
private X509Certificate parseCertificate(Uri uri)
|
||||
{
|
||||
X509Certificate certificate = null;
|
||||
try
|
||||
{
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
InputStream in = getContentResolver().openInputStream(uri);
|
||||
certificate = (X509Certificate)factory.generateCertificate(in);
|
||||
/* we don't check whether it's actually a CA certificate or not */
|
||||
}
|
||||
catch (CertificateException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to store the given certificate in the KeyStore.
|
||||
* @param certificate
|
||||
* @return whether it was successfully stored
|
||||
*/
|
||||
private boolean storeCertificate(X509Certificate certificate)
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyStore store = KeyStore.getInstance("LocalCertificateStore");
|
||||
store.load(null, null);
|
||||
store.setCertificateEntry(null, certificate);
|
||||
TrustedCertificateManager.getInstance().reset();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that displays a confirmation dialog when a certificate should get
|
||||
* imported. If the user confirms the import we try to store it.
|
||||
*/
|
||||
public static class ConfirmImportDialog extends DialogFragment
|
||||
{
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
final X509Certificate certificate;
|
||||
|
||||
certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_launcher)
|
||||
.setTitle(R.string.import_certificate)
|
||||
.setMessage(certificate.getSubjectDN().toString())
|
||||
.setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton)
|
||||
{
|
||||
TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
|
||||
if (activity.storeCertificate(certificate))
|
||||
{
|
||||
Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
getActivity().finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
getActivity().finish();
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog)
|
||||
{
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
import org.strongswan.android.ui.adapter.TrustedCertificateAdapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListFragment;
|
||||
import android.app.LoaderManager.LoaderCallbacks;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.SearchView.OnQueryTextListener;
|
||||
|
||||
public class TrustedCertificateListFragment extends ListFragment implements LoaderCallbacks<List<TrustedCertificateEntry>>, OnQueryTextListener
|
||||
{
|
||||
public static final String EXTRA_CERTIFICATE_SOURCE = "certificate_source";
|
||||
private OnTrustedCertificateSelectedListener mListener;
|
||||
private TrustedCertificateAdapter mAdapter;
|
||||
private TrustedCertificateSource mSource = TrustedCertificateSource.SYSTEM;
|
||||
|
||||
/**
|
||||
* The activity containing this fragment should implement this interface
|
||||
*/
|
||||
public interface OnTrustedCertificateSelectedListener {
|
||||
public void onTrustedCertificateSelected(TrustedCertificateEntry selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState)
|
||||
{
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
setEmptyText(getString(R.string.no_certificates));
|
||||
|
||||
mAdapter = new TrustedCertificateAdapter(getActivity());
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
setListShown(false);
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null)
|
||||
{
|
||||
mSource = (TrustedCertificateSource)arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE);
|
||||
}
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
|
||||
if (activity instanceof OnTrustedCertificateSelectedListener)
|
||||
{
|
||||
mListener = (OnTrustedCertificateSelectedListener)activity;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query)
|
||||
{ /* already handled when the text changes */
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText)
|
||||
{
|
||||
String search = TextUtils.isEmpty(newText) ? null : newText;
|
||||
mAdapter.getFilter().filter(search);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the loader of this list fragment
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
if (isResumed())
|
||||
{
|
||||
setListShown(false);
|
||||
}
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<List<TrustedCertificateEntry>> onCreateLoader(int id, Bundle args)
|
||||
{ /* we don't need the id as we have only one loader */
|
||||
return new CertificateListLoader(getActivity(), mSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<TrustedCertificateEntry>> loader, List<TrustedCertificateEntry> data)
|
||||
{
|
||||
mAdapter.setData(data);
|
||||
|
||||
if (isResumed())
|
||||
{
|
||||
setListShown(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<TrustedCertificateEntry>> loader)
|
||||
{
|
||||
mAdapter.setData(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id)
|
||||
{
|
||||
if (mListener != null)
|
||||
{
|
||||
mListener.onTrustedCertificateSelected(mAdapter.getItem(position));
|
||||
}
|
||||
}
|
||||
|
||||
public static class CertificateListLoader extends AsyncTaskLoader<List<TrustedCertificateEntry>>
|
||||
{
|
||||
private List<TrustedCertificateEntry> mData;
|
||||
private final TrustedCertificateSource mSource;
|
||||
|
||||
public CertificateListLoader(Context context, TrustedCertificateSource source)
|
||||
{
|
||||
super(context);
|
||||
mSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TrustedCertificateEntry> loadInBackground()
|
||||
{
|
||||
TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
|
||||
Hashtable<String,X509Certificate> certificates = certman.getCACertificates(mSource);
|
||||
List<TrustedCertificateEntry> selected;
|
||||
|
||||
selected = new ArrayList<TrustedCertificateEntry>();
|
||||
for (Entry<String, X509Certificate> entry : certificates.entrySet())
|
||||
{
|
||||
selected.add(new TrustedCertificateEntry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Collections.sort(selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading()
|
||||
{
|
||||
if (mData != null)
|
||||
{ /* if we have data ready, deliver it directly */
|
||||
deliverResult(mData);
|
||||
return;
|
||||
}
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<TrustedCertificateEntry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.security.KeyStore;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertificateDeleteListener;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.ActionBar.Tab;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener
|
||||
{
|
||||
public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE";
|
||||
private static final String DIALOG_TAG = "Dialog";
|
||||
private static final int IMPORT_CERTIFICATE = 0;
|
||||
private boolean mSelect;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.trusted_certificates_activity);
|
||||
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
|
||||
TrustedCertificatesTabListener listener;
|
||||
listener = new TrustedCertificatesTabListener(this, "system", TrustedCertificateSource.SYSTEM);
|
||||
actionBar.addTab(actionBar
|
||||
.newTab()
|
||||
.setText(R.string.system_tab)
|
||||
.setTag(listener)
|
||||
.setTabListener(listener));
|
||||
listener = new TrustedCertificatesTabListener(this, "user", TrustedCertificateSource.USER);
|
||||
actionBar.addTab(actionBar
|
||||
.newTab()
|
||||
.setText(R.string.user_tab)
|
||||
.setTag(listener)
|
||||
.setTabListener(listener));
|
||||
listener = new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL);
|
||||
actionBar.addTab(actionBar
|
||||
.newTab()
|
||||
.setText(R.string.local_tab)
|
||||
.setTag(listener)
|
||||
.setTabListener(listener));
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
actionBar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
|
||||
}
|
||||
mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.certificates, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
|
||||
{
|
||||
menu.removeItem(R.id.menu_import_certificate);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
case R.id.menu_reload_certs:
|
||||
reloadCertificates();
|
||||
return true;
|
||||
case R.id.menu_import_certificate:
|
||||
Intent intent = new Intent(this, TrustedCertificateImportActivity.class);
|
||||
startActivityForResult(intent, IMPORT_CERTIFICATE);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (requestCode)
|
||||
{
|
||||
case IMPORT_CERTIFICATE:
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
reloadCertificates();
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrustedCertificateSelected(TrustedCertificateEntry selected)
|
||||
{
|
||||
if (mSelect)
|
||||
{
|
||||
/* the user selected a certificate, return to calling activity */
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias());
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
TrustedCertificatesTabListener listener;
|
||||
listener = (TrustedCertificatesTabListener)getActionBar().getSelectedTab().getTag();
|
||||
if (listener.mTag == "local")
|
||||
{
|
||||
Bundle args = new Bundle();
|
||||
args.putString(CertificateDeleteConfirmationDialog.ALIAS, selected.getAlias());
|
||||
CertificateDeleteConfirmationDialog dialog = new CertificateDeleteConfirmationDialog();
|
||||
dialog.setArguments(args);
|
||||
dialog.show(this.getFragmentManager(), DIALOG_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDelete(String alias)
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyStore store = KeyStore.getInstance("LocalCertificateStore");
|
||||
store.load(null, null);
|
||||
store.deleteEntry(alias);
|
||||
reloadCertificates();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadCertificates()
|
||||
{
|
||||
TrustedCertificateManager.getInstance().reset();
|
||||
for (int i = 0; i < getActionBar().getTabCount(); i++)
|
||||
{
|
||||
Tab tab = getActionBar().getTabAt(i);
|
||||
TrustedCertificatesTabListener listener = (TrustedCertificatesTabListener)tab.getTag();
|
||||
listener.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrustedCertificatesTabListener implements ActionBar.TabListener
|
||||
{
|
||||
private final String mTag;
|
||||
private final TrustedCertificateSource mSource;
|
||||
private Fragment mFragment;
|
||||
|
||||
public TrustedCertificatesTabListener(Activity activity, String tag, TrustedCertificateSource source)
|
||||
{
|
||||
mTag = tag;
|
||||
mSource = source;
|
||||
/* check to see if we already have a fragment for this tab, probably
|
||||
* from a previously saved state. if so, deactivate it, because the
|
||||
* initial state is that no tab is shown */
|
||||
mFragment = activity.getFragmentManager().findFragmentByTag(mTag);
|
||||
if (mFragment != null && !mFragment.isDetached())
|
||||
{
|
||||
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
|
||||
ft.detach(mFragment);
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft)
|
||||
{
|
||||
if (mFragment == null)
|
||||
{
|
||||
mFragment = new TrustedCertificateListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(TrustedCertificateListFragment.EXTRA_CERTIFICATE_SOURCE, mSource);
|
||||
mFragment.setArguments(args);
|
||||
ft.add(android.R.id.content, mFragment, mTag);
|
||||
}
|
||||
else
|
||||
{
|
||||
ft.attach(mFragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(Tab tab, FragmentTransaction ft)
|
||||
{
|
||||
if (mFragment != null)
|
||||
{
|
||||
ft.detach(mFragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(Tab tab, FragmentTransaction ft)
|
||||
{
|
||||
/* nothing to be done */
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
if (mFragment != null)
|
||||
{
|
||||
((TrustedCertificateListFragment)mFragment).reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+653
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.security.cert.X509Certificate;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainAliasCallback;
|
||||
import android.security.KeyChainException;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class VpnProfileDetailActivity extends Activity
|
||||
{
|
||||
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
|
||||
private static final int MTU_MIN = 1280;
|
||||
private static final int MTU_MAX = 1500;
|
||||
|
||||
private VpnProfileDataSource mDataSource;
|
||||
private Long mId;
|
||||
private TrustedCertificateEntry mCertEntry;
|
||||
private String mUserCertLoading;
|
||||
private TrustedCertificateEntry mUserCertEntry;
|
||||
private VpnType mVpnType = VpnType.IKEV2_EAP;
|
||||
private VpnProfile mProfile;
|
||||
private EditText mName;
|
||||
private EditText mGateway;
|
||||
private Spinner mSelectVpnType;
|
||||
private ViewGroup mUsernamePassword;
|
||||
private EditText mUsername;
|
||||
private EditText mPassword;
|
||||
private ViewGroup mUserCertificate;
|
||||
private RelativeLayout mSelectUserCert;
|
||||
private CheckBox mCheckAuto;
|
||||
private RelativeLayout mSelectCert;
|
||||
private RelativeLayout mTncNotice;
|
||||
private CheckBox mShowAdvanced;
|
||||
private ViewGroup mAdvancedSettings;
|
||||
private EditText mMTU;
|
||||
private EditText mPort;
|
||||
private CheckBox mBlockIPv4;
|
||||
private CheckBox mBlockIPv6;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/* the title is set when we load the profile, if any */
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mDataSource = new VpnProfileDataSource(this);
|
||||
mDataSource.open();
|
||||
|
||||
setContentView(R.layout.profile_detail_view);
|
||||
|
||||
mName = (EditText)findViewById(R.id.name);
|
||||
mGateway = (EditText)findViewById(R.id.gateway);
|
||||
mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
|
||||
mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
|
||||
|
||||
mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
|
||||
mUsername = (EditText)findViewById(R.id.username);
|
||||
mPassword = (EditText)findViewById(R.id.password);
|
||||
|
||||
mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
|
||||
mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
|
||||
|
||||
mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
|
||||
mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
|
||||
|
||||
mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
|
||||
mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
|
||||
|
||||
mMTU = (EditText)findViewById(R.id.mtu);
|
||||
mPort = (EditText)findViewById(R.id.port);
|
||||
mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
|
||||
mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
|
||||
|
||||
mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
mVpnType = VpnType.values()[position];
|
||||
updateCredentialView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent)
|
||||
{ /* should not happen */
|
||||
mVpnType = VpnType.IKEV2_EAP;
|
||||
updateCredentialView();
|
||||
}
|
||||
});
|
||||
|
||||
((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
|
||||
((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
|
||||
mTncNotice.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
new TncNoticeDialog().show(VpnProfileDetailActivity.this.getFragmentManager(), "TncNotice");
|
||||
}
|
||||
});
|
||||
|
||||
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
|
||||
|
||||
mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
|
||||
{
|
||||
updateCertificateSelector();
|
||||
}
|
||||
});
|
||||
|
||||
mSelectCert.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
|
||||
intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
|
||||
startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
|
||||
}
|
||||
});
|
||||
|
||||
mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
|
||||
{
|
||||
updateAdvancedSettings();
|
||||
}
|
||||
});
|
||||
|
||||
mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
|
||||
if (mId == null)
|
||||
{
|
||||
Bundle extras = getIntent().getExtras();
|
||||
mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID);
|
||||
}
|
||||
|
||||
loadProfileData(savedInstanceState);
|
||||
|
||||
updateCredentialView();
|
||||
updateCertificateSelector();
|
||||
updateAdvancedSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
mDataSource.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mId != null)
|
||||
{
|
||||
outState.putLong(VpnProfileDataSource.KEY_ID, mId);
|
||||
}
|
||||
if (mUserCertEntry != null)
|
||||
{
|
||||
outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
|
||||
}
|
||||
if (mCertEntry != null)
|
||||
{
|
||||
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.profile_edit, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
case R.id.menu_cancel:
|
||||
finish();
|
||||
return true;
|
||||
case R.id.menu_accept:
|
||||
saveProfile();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (requestCode)
|
||||
{
|
||||
case SELECT_TRUSTED_CERTIFICATE:
|
||||
if (resultCode == RESULT_OK)
|
||||
{
|
||||
String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
|
||||
X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
|
||||
mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
|
||||
updateCertificateSelector();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UI to enter credentials depending on the type of VPN currently selected
|
||||
*/
|
||||
private void updateCredentialView()
|
||||
{
|
||||
mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
|
||||
mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
|
||||
mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
if (mUserCertLoading != null)
|
||||
{
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.loading);
|
||||
}
|
||||
else if (mUserCertEntry != null)
|
||||
{ /* clear any errors and set the new data */
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an alert in case the previously selected certificate is not found anymore
|
||||
* or the user did not select a certificate in the spinner.
|
||||
*/
|
||||
private void showCertificateAlert()
|
||||
{
|
||||
AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this);
|
||||
adb.setTitle(R.string.alert_text_nocertfound_title);
|
||||
adb.setMessage(R.string.alert_text_nocertfound);
|
||||
adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id)
|
||||
{
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
adb.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the CA certificate selection UI depending on whether the
|
||||
* certificate should be automatically selected or not.
|
||||
*/
|
||||
private void updateCertificateSelector()
|
||||
{
|
||||
if (!mCheckAuto.isChecked())
|
||||
{
|
||||
mSelectCert.setEnabled(true);
|
||||
mSelectCert.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mCertEntry != null)
|
||||
{
|
||||
((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
|
||||
((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
|
||||
}
|
||||
else
|
||||
{
|
||||
((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(R.string.profile_ca_select_certificate_label);
|
||||
((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(R.string.profile_ca_select_certificate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mSelectCert.setEnabled(false);
|
||||
mSelectCert.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the advanced settings UI depending on whether any advanced
|
||||
* settings have already been made.
|
||||
*/
|
||||
private void updateAdvancedSettings()
|
||||
{
|
||||
boolean show = mShowAdvanced.isChecked();
|
||||
if (!show && mProfile != null)
|
||||
{
|
||||
Integer st = mProfile.getSplitTunneling();
|
||||
show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0);
|
||||
}
|
||||
mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
|
||||
mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or update the profile depending on whether we actually have a
|
||||
* profile object or not (this was created in updateProfileData)
|
||||
*/
|
||||
private void saveProfile()
|
||||
{
|
||||
if (verifyInput())
|
||||
{
|
||||
if (mProfile != null)
|
||||
{
|
||||
updateProfileData();
|
||||
mDataSource.updateVpnProfile(mProfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
mProfile = new VpnProfile();
|
||||
updateProfileData();
|
||||
mDataSource.insertProfile(mProfile);
|
||||
}
|
||||
setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the user input and display error messages.
|
||||
* @return true if the input is valid
|
||||
*/
|
||||
private boolean verifyInput()
|
||||
{
|
||||
boolean valid = true;
|
||||
if (mGateway.getText().toString().trim().isEmpty())
|
||||
{
|
||||
mGateway.setError(getString(R.string.alert_text_no_input_gateway));
|
||||
valid = false;
|
||||
}
|
||||
if (mVpnType.has(VpnTypeFeature.USER_PASS))
|
||||
{
|
||||
if (mUsername.getText().toString().trim().isEmpty())
|
||||
{
|
||||
mUsername.setError(getString(R.string.alert_text_no_input_username));
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
|
||||
{ /* let's show an error icon */
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
|
||||
valid = false;
|
||||
}
|
||||
if (!mCheckAuto.isChecked() && mCertEntry == null)
|
||||
{
|
||||
showCertificateAlert();
|
||||
valid = false;
|
||||
}
|
||||
Integer mtu = getInteger(mMTU);
|
||||
if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX))
|
||||
{
|
||||
mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX));
|
||||
valid = false;
|
||||
}
|
||||
Integer port = getInteger(mPort);
|
||||
if (port != null && (port < 1 || port > 65535))
|
||||
{
|
||||
mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the profile object with the data entered by the user
|
||||
*/
|
||||
private void updateProfileData()
|
||||
{
|
||||
/* the name is optional, we default to the gateway if none is given */
|
||||
String name = mName.getText().toString().trim();
|
||||
String gateway = mGateway.getText().toString().trim();
|
||||
mProfile.setName(name.isEmpty() ? gateway : name);
|
||||
mProfile.setGateway(gateway);
|
||||
mProfile.setVpnType(mVpnType);
|
||||
if (mVpnType.has(VpnTypeFeature.USER_PASS))
|
||||
{
|
||||
mProfile.setUsername(mUsername.getText().toString().trim());
|
||||
String password = mPassword.getText().toString().trim();
|
||||
password = password.isEmpty() ? null : password;
|
||||
mProfile.setPassword(password);
|
||||
}
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
|
||||
}
|
||||
String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
|
||||
mProfile.setCertificateAlias(certAlias);
|
||||
mProfile.setMTU(getInteger(mMTU));
|
||||
mProfile.setPort(getInteger(mPort));
|
||||
int st = 0;
|
||||
st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
|
||||
st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
|
||||
mProfile.setSplitTunneling(st == 0 ? null : st);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an existing profile if we got an ID
|
||||
*
|
||||
* @param savedInstanceState previously saved state
|
||||
*/
|
||||
private void loadProfileData(Bundle savedInstanceState)
|
||||
{
|
||||
String useralias = null, alias = null;
|
||||
|
||||
getActionBar().setTitle(R.string.add_profile);
|
||||
if (mId != null && mId != 0)
|
||||
{
|
||||
mProfile = mDataSource.getVpnProfile(mId);
|
||||
if (mProfile != null)
|
||||
{
|
||||
mName.setText(mProfile.getName());
|
||||
mGateway.setText(mProfile.getGateway());
|
||||
mVpnType = mProfile.getVpnType();
|
||||
mUsername.setText(mProfile.getUsername());
|
||||
mPassword.setText(mProfile.getPassword());
|
||||
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
|
||||
mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
|
||||
mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false);
|
||||
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false);
|
||||
useralias = mProfile.getUserCertificateAlias();
|
||||
alias = mProfile.getCertificateAlias();
|
||||
getActionBar().setTitle(mProfile.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.e(VpnProfileDetailActivity.class.getSimpleName(),
|
||||
"VPN profile with id " + mId + " not found");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
mSelectVpnType.setSelection(mVpnType.ordinal());
|
||||
|
||||
/* check if the user selected a user certificate previously */
|
||||
useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
|
||||
if (useralias != null)
|
||||
{
|
||||
UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
|
||||
mUserCertLoading = useralias;
|
||||
loader.execute();
|
||||
}
|
||||
|
||||
/* check if the user selected a CA certificate previously */
|
||||
alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
|
||||
mCheckAuto.setChecked(alias == null);
|
||||
if (alias != null)
|
||||
{
|
||||
X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
|
||||
if (certificate != null)
|
||||
{
|
||||
mCertEntry = new TrustedCertificateEntry(alias, certificate);
|
||||
}
|
||||
else
|
||||
{ /* previously selected certificate is not here anymore */
|
||||
showCertificateAlert();
|
||||
mCertEntry = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer value in the given text box or null if empty
|
||||
*
|
||||
* @param view text box (numeric entry assumed)
|
||||
*/
|
||||
private Integer getInteger(EditText view)
|
||||
{
|
||||
String value = view.getText().toString().trim();
|
||||
return value.isEmpty() ? null : Integer.valueOf(value);
|
||||
}
|
||||
|
||||
private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null;
|
||||
KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alias(final String alias)
|
||||
{
|
||||
if (alias != null)
|
||||
{ /* otherwise the dialog was canceled, the request denied */
|
||||
try
|
||||
{
|
||||
final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
|
||||
/* alias() is not called from our main thread */
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (chain != null && chain.length > 0)
|
||||
{
|
||||
mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]);
|
||||
}
|
||||
updateCredentialView();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (KeyChainException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the selected user certificate asynchronously. This cannot be done
|
||||
* from the main thread as getCertificateChain() calls back to our main
|
||||
* thread to bind to the KeyChain service resulting in a deadlock.
|
||||
*/
|
||||
private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
|
||||
{
|
||||
private final Context mContext;
|
||||
private final String mAlias;
|
||||
|
||||
public UserCertificateLoader(Context context, String alias)
|
||||
{
|
||||
mContext = context;
|
||||
mAlias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X509Certificate doInBackground(Void... params)
|
||||
{
|
||||
X509Certificate[] chain = null;
|
||||
try
|
||||
{
|
||||
chain = KeyChain.getCertificateChain(mContext, mAlias);
|
||||
}
|
||||
catch (KeyChainException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (chain != null && chain.length > 0)
|
||||
{
|
||||
return chain[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(X509Certificate result)
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
mUserCertEntry = new TrustedCertificateEntry(mAlias, result);
|
||||
}
|
||||
else
|
||||
{ /* previously selected certificate is not here anymore */
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
|
||||
mUserCertEntry = null;
|
||||
}
|
||||
mUserCertLoading = null;
|
||||
updateCredentialView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog with notification message if EAP-TNC is used.
|
||||
*/
|
||||
public static class TncNoticeDialog extends DialogFragment
|
||||
{
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.tnc_notice_title)
|
||||
.setMessage(Html.fromHtml(getString(R.string.tnc_notice_details)))
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class VpnProfileListFragment extends Fragment
|
||||
{
|
||||
private static final int ADD_REQUEST = 1;
|
||||
private static final int EDIT_REQUEST = 2;
|
||||
|
||||
private List<VpnProfile> mVpnProfiles;
|
||||
private VpnProfileDataSource mDataSource;
|
||||
private VpnProfileAdapter mListAdapter;
|
||||
private ListView mListView;
|
||||
private OnVpnProfileSelectedListener mListener;
|
||||
private boolean mReadOnly;
|
||||
|
||||
/**
|
||||
* The activity containing this fragment should implement this interface
|
||||
*/
|
||||
public interface OnVpnProfileSelectedListener {
|
||||
public void onVpnProfileSelected(VpnProfile profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState)
|
||||
{
|
||||
super.onInflate(activity, attrs, savedInstanceState);
|
||||
TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.Fragment);
|
||||
mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.profile_list_fragment, null);
|
||||
|
||||
mListView = (ListView)view.findViewById(R.id.profile_list);
|
||||
mListView.setAdapter(mListAdapter);
|
||||
mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
|
||||
mListView.setOnItemClickListener(mVpnProfileClicked);
|
||||
|
||||
if (!mReadOnly)
|
||||
{
|
||||
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
mListView.setMultiChoiceModeListener(mVpnProfileSelected);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args != null)
|
||||
{
|
||||
mReadOnly = args.getBoolean("read_only", mReadOnly);
|
||||
}
|
||||
|
||||
if (!mReadOnly)
|
||||
{
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
Context context = getActivity().getApplicationContext();
|
||||
|
||||
mDataSource = new VpnProfileDataSource(this.getActivity());
|
||||
mDataSource.open();
|
||||
|
||||
/* cached list of profiles used as backend for the ListView */
|
||||
mVpnProfiles = mDataSource.getAllVpnProfiles();
|
||||
|
||||
mListAdapter = new VpnProfileAdapter(context, R.layout.profile_list_item, mVpnProfiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
mDataSource.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
|
||||
if (activity instanceof OnVpnProfileSelectedListener)
|
||||
{
|
||||
mListener = (OnVpnProfileSelectedListener)activity;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
|
||||
{
|
||||
inflater.inflate(R.menu.profile_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.add_profile:
|
||||
Intent connectionIntent = new Intent(getActivity(),
|
||||
VpnProfileDetailActivity.class);
|
||||
startActivityForResult(connectionIntent, ADD_REQUEST);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (requestCode)
|
||||
{
|
||||
case ADD_REQUEST:
|
||||
case EDIT_REQUEST:
|
||||
if (resultCode != Activity.RESULT_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0);
|
||||
VpnProfile profile = mDataSource.getVpnProfile(id);
|
||||
if (profile != null)
|
||||
{ /* in case this was an edit, we remove it first */
|
||||
mVpnProfiles.remove(profile);
|
||||
mVpnProfiles.add(profile);
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> a, View v, int position, long id)
|
||||
{
|
||||
if (mListener != null)
|
||||
{
|
||||
mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() {
|
||||
private HashSet<Integer> mSelected;
|
||||
private MenuItem mEditProfile;
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
inflater.inflate(R.menu.profile_list_context, menu);
|
||||
mEditProfile = menu.findItem(R.id.edit_profile);
|
||||
mSelected = new HashSet<Integer>();
|
||||
mode.setTitle(R.string.select_profiles);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.edit_profile:
|
||||
{
|
||||
int position = mSelected.iterator().next();
|
||||
VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
|
||||
Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
|
||||
connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
|
||||
startActivityForResult(connectionIntent, EDIT_REQUEST);
|
||||
break;
|
||||
}
|
||||
case R.id.delete_profile:
|
||||
{
|
||||
ArrayList<VpnProfile> profiles = new ArrayList<VpnProfile>();
|
||||
for (int position : mSelected)
|
||||
{
|
||||
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
|
||||
}
|
||||
for (VpnProfile profile : profiles)
|
||||
{
|
||||
mDataSource.deleteVpnProfile(profile);
|
||||
mVpnProfiles.remove(profile);
|
||||
}
|
||||
mListAdapter.notifyDataSetChanged();
|
||||
Toast.makeText(VpnProfileListFragment.this.getActivity(),
|
||||
R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
mode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode, int position,
|
||||
long id, boolean checked)
|
||||
{
|
||||
if (checked)
|
||||
{
|
||||
mSelected.add(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSelected.remove(position);
|
||||
}
|
||||
final int checkedCount = mSelected.size();
|
||||
mEditProfile.setEnabled(checkedCount == 1);
|
||||
switch (checkedCount)
|
||||
{
|
||||
case 0:
|
||||
mode.setSubtitle(R.string.no_profile_selected);
|
||||
break;
|
||||
case 1:
|
||||
mode.setSubtitle(R.string.one_profile_selected);
|
||||
break;
|
||||
default:
|
||||
mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class VpnProfileSelectActivity extends Activity implements OnVpnProfileSelectedListener
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.vpn_profile_select);
|
||||
|
||||
/* we should probably return a result also if the user clicks the back
|
||||
* button before selecting a profile */
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVpnProfileSelected(VpnProfile profile)
|
||||
{
|
||||
Intent shortcut = new Intent(MainActivity.START_PROFILE);
|
||||
shortcut.putExtra(MainActivity.EXTRA_VPN_PROFILE_ID, profile.getId());
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName());
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher));
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.logic.VpnStateService;
|
||||
import org.strongswan.android.logic.VpnStateService.ErrorState;
|
||||
import org.strongswan.android.logic.VpnStateService.State;
|
||||
import org.strongswan.android.logic.VpnStateService.VpnStateListener;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
{
|
||||
private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id";
|
||||
private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id";
|
||||
|
||||
private TextView mProfileNameView;
|
||||
private TextView mProfileView;
|
||||
private TextView mStateView;
|
||||
private int stateBaseColor;
|
||||
private Button mActionButton;
|
||||
private ProgressDialog mConnectDialog;
|
||||
private ProgressDialog mDisconnectDialog;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private AlertDialog mErrorDialog;
|
||||
private long mErrorConnectionID;
|
||||
private long mDismissedConnectionID;
|
||||
private VpnStateService mService;
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name)
|
||||
{
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
mService = ((VpnStateService.LocalBinder)service).getService();
|
||||
mService.registerListener(VpnStateFragment.this);
|
||||
updateView();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/* bind to the service only seems to work from the ApplicationContext */
|
||||
Context context = getActivity().getApplicationContext();
|
||||
context.bindService(new Intent(context, VpnStateService.class),
|
||||
mServiceConnection, Service.BIND_AUTO_CREATE);
|
||||
|
||||
mErrorConnectionID = 0;
|
||||
mDismissedConnectionID = 0;
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID))
|
||||
{
|
||||
mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID);
|
||||
mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID);
|
||||
outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.vpn_state_fragment, null);
|
||||
|
||||
mActionButton = (Button)view.findViewById(R.id.action);
|
||||
mActionButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
enableActionButton(false);
|
||||
|
||||
mStateView = (TextView)view.findViewById(R.id.vpn_state);
|
||||
stateBaseColor = mStateView.getCurrentTextColor();
|
||||
mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label);
|
||||
mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
if (mService != null)
|
||||
{
|
||||
mService.registerListener(this);
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
if (mService != null)
|
||||
{
|
||||
mService.unregisterListener(this);
|
||||
}
|
||||
hideErrorDialog();
|
||||
hideProgressDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
if (mService != null)
|
||||
{
|
||||
getActivity().getApplicationContext().unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged()
|
||||
{
|
||||
updateView();
|
||||
}
|
||||
|
||||
public void updateView()
|
||||
{
|
||||
long connectionID = mService.getConnectionID();
|
||||
VpnProfile profile = mService.getProfile();
|
||||
State state = mService.getState();
|
||||
ErrorState error = mService.getErrorState();
|
||||
ImcState imcState = mService.getImcState();
|
||||
String name = "", gateway = "";
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
name = profile.getName();
|
||||
gateway = profile.getGateway();
|
||||
}
|
||||
|
||||
if (reportError(connectionID, name, error, imcState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
enableActionButton(false);
|
||||
mProfileNameView.setText(name);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case DISABLED:
|
||||
showProfile(false);
|
||||
hideProgressDialog();
|
||||
mStateView.setText(R.string.state_disabled);
|
||||
mStateView.setTextColor(stateBaseColor);
|
||||
break;
|
||||
case CONNECTING:
|
||||
showProfile(true);
|
||||
showConnectDialog(name, gateway);
|
||||
mStateView.setText(R.string.state_connecting);
|
||||
mStateView.setTextColor(stateBaseColor);
|
||||
break;
|
||||
case CONNECTED:
|
||||
showProfile(true);
|
||||
hideProgressDialog();
|
||||
enableActionButton(true);
|
||||
mStateView.setText(R.string.state_connected);
|
||||
mStateView.setTextColor(getResources().getColor(R.color.success_text));
|
||||
break;
|
||||
case DISCONNECTING:
|
||||
showProfile(true);
|
||||
showDisconnectDialog(name);
|
||||
mStateView.setText(R.string.state_disconnecting);
|
||||
mStateView.setTextColor(stateBaseColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
|
||||
{
|
||||
if (connectionID > mDismissedConnectionID)
|
||||
{ /* report error if it hasn't been dismissed yet */
|
||||
mErrorConnectionID = connectionID;
|
||||
}
|
||||
else
|
||||
{ /* ignore all other errors */
|
||||
error = ErrorState.NO_ERROR;
|
||||
}
|
||||
if (error == ErrorState.NO_ERROR)
|
||||
{
|
||||
hideErrorDialog();
|
||||
return false;
|
||||
}
|
||||
else if (mErrorDialog != null)
|
||||
{ /* we already show the dialog */
|
||||
return true;
|
||||
}
|
||||
hideProgressDialog();
|
||||
mProfileNameView.setText(name);
|
||||
showProfile(true);
|
||||
enableActionButton(false);
|
||||
mStateView.setText(R.string.state_error);
|
||||
mStateView.setTextColor(getResources().getColor(R.color.error_text));
|
||||
switch (error)
|
||||
{
|
||||
case AUTH_FAILED:
|
||||
if (imcState == ImcState.BLOCK)
|
||||
{
|
||||
showErrorDialog(R.string.error_assessment_failed);
|
||||
}
|
||||
else
|
||||
{
|
||||
showErrorDialog(R.string.error_auth_failed);
|
||||
}
|
||||
break;
|
||||
case PEER_AUTH_FAILED:
|
||||
showErrorDialog(R.string.error_peer_auth_failed);
|
||||
break;
|
||||
case LOOKUP_FAILED:
|
||||
showErrorDialog(R.string.error_lookup_failed);
|
||||
break;
|
||||
case UNREACHABLE:
|
||||
showErrorDialog(R.string.error_unreachable);
|
||||
break;
|
||||
default:
|
||||
showErrorDialog(R.string.error_generic);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showProfile(boolean show)
|
||||
{
|
||||
mProfileView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void enableActionButton(boolean enable)
|
||||
{
|
||||
mActionButton.setEnabled(enable);
|
||||
mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void hideProgressDialog()
|
||||
{
|
||||
if (mProgressDialog != null)
|
||||
{
|
||||
mProgressDialog.dismiss();
|
||||
mProgressDialog = null;
|
||||
mDisconnectDialog = mConnectDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void hideErrorDialog()
|
||||
{
|
||||
if (mErrorDialog != null)
|
||||
{
|
||||
mErrorDialog.dismiss();
|
||||
mErrorDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearError()
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.disconnect();
|
||||
}
|
||||
mDismissedConnectionID = mErrorConnectionID;
|
||||
updateView();
|
||||
}
|
||||
|
||||
private void showConnectDialog(String profile, String gateway)
|
||||
{
|
||||
if (mConnectDialog != null)
|
||||
{ /* already showing the dialog */
|
||||
return;
|
||||
}
|
||||
hideProgressDialog();
|
||||
mConnectDialog = new ProgressDialog(getActivity());
|
||||
mConnectDialog.setTitle(String.format(getString(R.string.connecting_title), profile));
|
||||
mConnectDialog.setMessage(String.format(getString(R.string.connecting_message), gateway));
|
||||
mConnectDialog.setIndeterminate(true);
|
||||
mConnectDialog.setCancelable(false);
|
||||
mConnectDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
getString(android.R.string.cancel),
|
||||
new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
mConnectDialog.show();
|
||||
mProgressDialog = mConnectDialog;
|
||||
}
|
||||
|
||||
private void showDisconnectDialog(String profile)
|
||||
{
|
||||
if (mDisconnectDialog != null)
|
||||
{ /* already showing the dialog */
|
||||
return;
|
||||
}
|
||||
hideProgressDialog();
|
||||
mDisconnectDialog = new ProgressDialog(getActivity());
|
||||
mDisconnectDialog.setMessage(getString(R.string.state_disconnecting));
|
||||
mDisconnectDialog.setIndeterminate(true);
|
||||
mDisconnectDialog.setCancelable(false);
|
||||
mDisconnectDialog.show();
|
||||
mProgressDialog = mDisconnectDialog;
|
||||
}
|
||||
|
||||
private void showErrorDialog(int textid)
|
||||
{
|
||||
final List<RemediationInstruction> instructions = mService.getRemediationInstructions();
|
||||
final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty();
|
||||
int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log;
|
||||
|
||||
mErrorDialog = new AlertDialog.Builder(getActivity())
|
||||
.setMessage(getString(R.string.error_introduction) + " " + getString(textid))
|
||||
.setCancelable(false)
|
||||
.setNeutralButton(text, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
clearError();
|
||||
dialog.dismiss();
|
||||
Intent intent;
|
||||
if (show_instructions)
|
||||
{
|
||||
intent = new Intent(getActivity(), RemediationInstructionsActivity.class);
|
||||
intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS,
|
||||
new ArrayList<RemediationInstruction>(instructions));
|
||||
}
|
||||
else
|
||||
{
|
||||
intent = new Intent(getActivity(), LogActivity.class);
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id)
|
||||
{
|
||||
clearError();
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).create();
|
||||
mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog)
|
||||
{
|
||||
mErrorDialog = null;
|
||||
}
|
||||
});
|
||||
mErrorDialog.show();
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class RemediationInstructionAdapter extends ArrayAdapter<RemediationInstruction>
|
||||
{
|
||||
public RemediationInstructionAdapter(Context context)
|
||||
{
|
||||
super(context, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new data for this adapter.
|
||||
*
|
||||
* @param data the new data (null to clear)
|
||||
*/
|
||||
public void setData(List<RemediationInstruction> data)
|
||||
{
|
||||
clear();
|
||||
if (data != null)
|
||||
{
|
||||
addAll(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
View view;
|
||||
if (convertView != null)
|
||||
{
|
||||
view = convertView;
|
||||
}
|
||||
else
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
view = inflater.inflate(R.layout.remediation_instruction_item, parent, false);
|
||||
}
|
||||
RemediationInstruction item = getItem(position);
|
||||
TextView text = (TextView)view.findViewById(android.R.id.text1);
|
||||
text.setText(item.getTitle());
|
||||
text = (TextView)view.findViewById(android.R.id.text2);
|
||||
text.setText(item.getDescription());
|
||||
return view;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class TrustedCertificateAdapter extends ArrayAdapter<TrustedCertificateEntry>
|
||||
{
|
||||
public TrustedCertificateAdapter(Context context)
|
||||
{
|
||||
super(context, R.layout.trusted_certificates_item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new data for this adapter.
|
||||
*
|
||||
* @param data the new data (null to clear)
|
||||
*/
|
||||
public void setData(List<TrustedCertificateEntry> data)
|
||||
{
|
||||
clear();
|
||||
if (data != null)
|
||||
{
|
||||
addAll(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
View view;
|
||||
if (convertView != null)
|
||||
{
|
||||
view = convertView;
|
||||
}
|
||||
else
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
view = inflater.inflate(R.layout.trusted_certificates_item, parent, false);
|
||||
}
|
||||
TrustedCertificateEntry item = getItem(position);
|
||||
TextView text = (TextView)view.findViewById(R.id.subject_primary);
|
||||
text.setText(item.getSubjectPrimary());
|
||||
text = (TextView)view.findViewById(R.id.subject_secondary);
|
||||
text.setText(item.getSubjectSecondary());
|
||||
return view;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
|
||||
{
|
||||
private final int resource;
|
||||
private final List<VpnProfile> items;
|
||||
|
||||
public VpnProfileAdapter(Context context, int resource,
|
||||
List<VpnProfile> items)
|
||||
{
|
||||
super(context, resource, items);
|
||||
this.resource = resource;
|
||||
this.items = items;
|
||||
sortItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
View vpnProfileView;
|
||||
if (convertView != null)
|
||||
{
|
||||
vpnProfileView = convertView;
|
||||
}
|
||||
else
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
vpnProfileView = inflater.inflate(resource, null);
|
||||
}
|
||||
VpnProfile profile = getItem(position);
|
||||
TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name);
|
||||
tv.setText(profile.getName());
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
|
||||
tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway());
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username);
|
||||
if (profile.getVpnType().has(VpnTypeFeature.USER_PASS))
|
||||
{ /* if the view is reused we make sure it is visible */
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername());
|
||||
}
|
||||
else
|
||||
{
|
||||
tv.setVisibility(View.GONE);
|
||||
}
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate);
|
||||
if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias());
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
tv.setVisibility(View.GONE);
|
||||
}
|
||||
return vpnProfileView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged()
|
||||
{
|
||||
sortItems();
|
||||
super.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void sortItems()
|
||||
{
|
||||
Collections.sort(this.items, new Comparator<VpnProfile>() {
|
||||
@Override
|
||||
public int compare(VpnProfile lhs, VpnProfile rhs)
|
||||
{
|
||||
return lhs.getName().compareToIgnoreCase(rhs.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Very similar to ByteBuffer (although with a stripped interface) but it
|
||||
* automatically resizes the underlying buffer.
|
||||
*/
|
||||
public class BufferedByteWriter
|
||||
{
|
||||
/**
|
||||
* The underlying byte buffer
|
||||
*/
|
||||
private byte[] mBuffer;
|
||||
|
||||
/**
|
||||
* ByteBuffer used as wrapper around the buffer to easily convert values
|
||||
*/
|
||||
private ByteBuffer mWriter;
|
||||
|
||||
/**
|
||||
* Create a writer with a default initial capacity
|
||||
*/
|
||||
public BufferedByteWriter()
|
||||
{
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a writer with the given initial capacity (helps avoid expensive
|
||||
* resizing if known).
|
||||
* @param capacity initial capacity
|
||||
*/
|
||||
public BufferedByteWriter(int capacity)
|
||||
{
|
||||
capacity = capacity > 4 ? capacity : 32;
|
||||
mBuffer = new byte[capacity];
|
||||
mWriter = ByteBuffer.wrap(mBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there is enough space available to write the requested
|
||||
* number of bytes. If necessary the internal buffer is resized.
|
||||
* @param required required number of bytes
|
||||
*/
|
||||
private void ensureCapacity(int required)
|
||||
{
|
||||
if (mWriter.remaining() >= required)
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[] buffer = new byte[(mBuffer.length + required) * 2];
|
||||
System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position());
|
||||
mBuffer = buffer;
|
||||
ByteBuffer writer = ByteBuffer.wrap(buffer);
|
||||
writer.position(mWriter.position());
|
||||
mWriter = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given byte array to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put(byte[] value)
|
||||
{
|
||||
ensureCapacity(value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given byte to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put(byte value)
|
||||
{
|
||||
ensureCapacity(1);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 8-bit length of the given data followed by the data itself
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter putLen8(byte[] value)
|
||||
{
|
||||
ensureCapacity(1 + value.length);
|
||||
mWriter.put((byte)value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 16-bit length of the given data followed by the data itself
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter putLen16(byte[] value)
|
||||
{
|
||||
ensureCapacity(2 + value.length);
|
||||
mWriter.putShort((short)value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given short value (16-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put16(short value)
|
||||
{
|
||||
ensureCapacity(2);
|
||||
mWriter.putShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 24-bit of the given value in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put24(int value)
|
||||
{
|
||||
ensureCapacity(3);
|
||||
mWriter.put((byte)(value >> 16));
|
||||
mWriter.putShort((short)value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given int value (32-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put32(int value)
|
||||
{
|
||||
ensureCapacity(4);
|
||||
mWriter.putInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given long value (64-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put64(long value)
|
||||
{
|
||||
ensureCapacity(8);
|
||||
mWriter.putLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the internal buffer to a new byte array.
|
||||
* @return byte array
|
||||
*/
|
||||
public byte[] toByteArray()
|
||||
{
|
||||
int length = mWriter.position();
|
||||
byte[] bytes = new byte[length];
|
||||
System.arraycopy(mBuffer, 0, bytes, 0, length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Simple generator for data/files that may be parsed by libstrongswan's
|
||||
* settings_t class.
|
||||
*/
|
||||
public class SettingsWriter
|
||||
{
|
||||
/**
|
||||
* Top-level section
|
||||
*/
|
||||
private final SettingsSection mTop = new SettingsSection();
|
||||
|
||||
/**
|
||||
* Set a string value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, String value)
|
||||
{
|
||||
Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+");
|
||||
if (key == null || !pattern.matcher(key).matches())
|
||||
{
|
||||
return this;
|
||||
}
|
||||
String[] keys = key.split("\\.");
|
||||
SettingsSection section = mTop;
|
||||
section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1));
|
||||
section.Settings.put(keys[keys.length-1], value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an integer value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, Integer value)
|
||||
{
|
||||
return setValue(key, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a boolean value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, Boolean value)
|
||||
{
|
||||
return setValue(key, value == null ? null : value ? "1" : "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the settings to a string in the format understood by
|
||||
* libstrongswan's settings_t parser.
|
||||
* @return serialized settings
|
||||
*/
|
||||
public String serialize()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
serializeSection(mTop, builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the settings in a section and recursively serialize sub-sections
|
||||
* @param section
|
||||
* @param builder
|
||||
*/
|
||||
private void serializeSection(SettingsSection section, StringBuilder builder)
|
||||
{
|
||||
for (Entry<String, String> setting : section.Settings.entrySet())
|
||||
{
|
||||
builder.append(setting.getKey()).append('=');
|
||||
if (setting.getValue() != null)
|
||||
{
|
||||
builder.append("\"").append(escapeValue(setting.getValue())).append("\"");
|
||||
}
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
for (Entry<String, SettingsSection> subsection : section.Sections.entrySet())
|
||||
{
|
||||
builder.append(subsection.getKey()).append(" {\n");
|
||||
serializeSection(subsection.getValue(), builder);
|
||||
builder.append("}\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape value so it may be wrapped in "
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
private String escapeValue(String value)
|
||||
{
|
||||
return value.replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create the nested sections with the given names
|
||||
* @param sections list of section names
|
||||
* @return final section
|
||||
*/
|
||||
private SettingsSection findOrCreateSection(String[] sections)
|
||||
{
|
||||
SettingsSection section = mTop;
|
||||
for (String name : sections)
|
||||
{
|
||||
SettingsSection subsection = section.Sections.get(name);
|
||||
if (subsection == null)
|
||||
{
|
||||
subsection = new SettingsSection();
|
||||
section.Sections.put(name, subsection);
|
||||
}
|
||||
section = subsection;
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* A section containing sub-sections and settings.
|
||||
*/
|
||||
private class SettingsSection
|
||||
{
|
||||
/**
|
||||
* Assigned key/value pairs
|
||||
*/
|
||||
LinkedHashMap<String,String> Settings = new LinkedHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Assigned sub-sections
|
||||
*/
|
||||
LinkedHashMap<String,SettingsSection> Sections = new LinkedHashMap<String, SettingsWriter.SettingsSection>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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;
|
||||
|
||||
|
||||
public class Utils
|
||||
{
|
||||
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Converts the given byte array to a hexadecimal string encoding.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return hex string
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
char[] hex = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int value = bytes[i];
|
||||
hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
|
||||
hex[i*2+1] = HEXDIGITS[ value & 0x0f];
|
||||
}
|
||||
return new String(hex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
openssl
|
||||
strongswan
|
||||
@@ -0,0 +1,84 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# use "bring your own device" (BYOD) features (also see USE_BYOD in
|
||||
# MainActivity.java)
|
||||
strongswan_USE_BYOD := true
|
||||
|
||||
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
|
||||
pkcs1 pkcs8 pem xcbc hmac socket-default \
|
||||
eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20
|
||||
endif
|
||||
|
||||
strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \
|
||||
$(strongswan_BYOD_PLUGINS)
|
||||
|
||||
include $(LOCAL_PATH)/strongswan/Android.common.mk
|
||||
|
||||
# includes
|
||||
strongswan_PATH := $(LOCAL_PATH)/strongswan
|
||||
openssl_PATH := $(LOCAL_PATH)/openssl/include
|
||||
|
||||
# CFLAGS (partially from a configure run using droid-gcc)
|
||||
strongswan_CFLAGS := \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-format \
|
||||
-Wno-pointer-sign \
|
||||
-Wno-pointer-arith \
|
||||
-Wno-sign-compare \
|
||||
-Wno-strict-aliasing \
|
||||
-Wno-unused-parameter \
|
||||
-DHAVE___BOOL \
|
||||
-DHAVE_STDBOOL_H \
|
||||
-DHAVE_ALLOCA_H \
|
||||
-DHAVE_ALLOCA \
|
||||
-DHAVE_CLOCK_GETTIME \
|
||||
-DHAVE_DLADDR \
|
||||
-DHAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC \
|
||||
-DHAVE_PRCTL \
|
||||
-DHAVE_LINUX_UDP_H \
|
||||
-DHAVE_STRUCT_SADB_X_POLICY_SADB_X_POLICY_PRIORITY \
|
||||
-DHAVE_IPSEC_MODE_BEET \
|
||||
-DHAVE_IPSEC_DIR_FWD \
|
||||
-DHAVE_IN6ADDR_ANY \
|
||||
-DHAVE_NETINET_IP6_H \
|
||||
-DOPENSSL_NO_ENGINE \
|
||||
-DCONFIG_H_INCLUDED \
|
||||
-DCAPABILITIES \
|
||||
-DCAPABILITIES_NATIVE \
|
||||
-DMONOLITHIC \
|
||||
-DUSE_IKEV1 \
|
||||
-DUSE_IKEV2 \
|
||||
-DUSE_BUILTIN_PRINTF \
|
||||
-DDEBUG \
|
||||
-DCHARON_UDP_PORT=0 \
|
||||
-DCHARON_NATT_PORT=0 \
|
||||
-DVERSION=\"$(strongswan_VERSION)\" \
|
||||
-DDEV_RANDOM=\"/dev/random\" \
|
||||
-DDEV_URANDOM=\"/dev/urandom\"
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
strongswan_CFLAGS += -DUSE_BYOD
|
||||
endif
|
||||
|
||||
strongswan_BUILD := \
|
||||
openssl \
|
||||
libandroidbridge \
|
||||
strongswan/src/libipsec \
|
||||
strongswan/src/libcharon \
|
||||
strongswan/src/libhydra \
|
||||
strongswan/src/libstrongswan
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
strongswan_BUILD += \
|
||||
strongswan/src/libtnccs \
|
||||
strongswan/src/libtncif \
|
||||
strongswan/src/libimcv
|
||||
endif
|
||||
|
||||
include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
|
||||
$(strongswan_BUILD)))
|
||||
@@ -0,0 +1,3 @@
|
||||
# select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs).
|
||||
APP_ABI := armeabi x86 mips
|
||||
APP_PLATFORM := android-19
|
||||
@@ -0,0 +1,63 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# copy-n-paste from Makefile.am
|
||||
LOCAL_SRC_FILES := \
|
||||
android_jni.c \
|
||||
backend/android_attr.c \
|
||||
backend/android_creds.c \
|
||||
backend/android_dns_proxy.c \
|
||||
backend/android_private_key.c \
|
||||
backend/android_service.c \
|
||||
charonservice.c \
|
||||
kernel/android_ipsec.c \
|
||||
kernel/android_net.c \
|
||||
kernel/network_manager.c \
|
||||
vpnservice_builder.c
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
LOCAL_SRC_FILES += \
|
||||
byod/imc_android_state.c \
|
||||
byod/imc_android.c
|
||||
endif
|
||||
|
||||
# build libandroidbridge -------------------------------------------------------
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(strongswan_PATH)/src/libipsec \
|
||||
$(strongswan_PATH)/src/libhydra \
|
||||
$(strongswan_PATH)/src/libcharon \
|
||||
$(strongswan_PATH)/src/libstrongswan
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(strongswan_PATH)/src/libimcv \
|
||||
$(strongswan_PATH)/src/libtncif \
|
||||
$(strongswan_PATH)/src/libtnccs \
|
||||
$(strongswan_PATH)/src/libtls
|
||||
endif
|
||||
|
||||
LOCAL_CFLAGS := $(strongswan_CFLAGS) \
|
||||
-DPLUGINS='"$(strongswan_CHARON_PLUGINS)"'
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
LOCAL_CFLAGS += -DPLUGINS_BYOD='"$(strongswan_BYOD_PLUGINS)"'
|
||||
endif
|
||||
|
||||
LOCAL_MODULE := libandroidbridge
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_ARM_MODE := arm
|
||||
|
||||
LOCAL_PRELINK_MODULE := false
|
||||
|
||||
LOCAL_LDLIBS := -llog
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := libstrongswan libhydra libipsec libcharon
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
LOCAL_SHARED_LIBRARIES += libimcv libtncif libtnccs
|
||||
endif
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 <dlfcn.h>
|
||||
|
||||
#include "android_jni.h"
|
||||
|
||||
#include <library.h>
|
||||
#include <threading/thread_value.h>
|
||||
|
||||
/**
|
||||
* JVM
|
||||
*/
|
||||
static JavaVM *android_jvm;
|
||||
|
||||
static struct {
|
||||
char name[32];
|
||||
void *handle;
|
||||
} libs[] = {
|
||||
{ "libstrongswan.so", NULL },
|
||||
#ifdef USE_BYOD
|
||||
{ "libtncif.so", NULL },
|
||||
{ "libtnccs.so", NULL },
|
||||
{ "libimcv.so", NULL },
|
||||
#endif
|
||||
{ "libhydra.so", NULL },
|
||||
{ "libcharon.so", NULL },
|
||||
{ "libipsec.so", NULL },
|
||||
};
|
||||
|
||||
jclass *android_charonvpnservice_class;
|
||||
jclass *android_charonvpnservice_builder_class;
|
||||
android_sdk_version_t android_sdk_version;
|
||||
|
||||
/**
|
||||
* Thread-local variable. Only used because of the destructor
|
||||
*/
|
||||
static thread_value_t *androidjni_threadlocal;
|
||||
|
||||
/**
|
||||
* Thread-local destructor to ensure that a native thread is detached
|
||||
* from the JVM even if androidjni_detach_thread() is not called.
|
||||
*/
|
||||
static void attached_thread_cleanup(void *arg)
|
||||
{
|
||||
(*android_jvm)->DetachCurrentThread(android_jvm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void androidjni_attach_thread(JNIEnv **env)
|
||||
{
|
||||
if ((*android_jvm)->GetEnv(android_jvm, (void**)env,
|
||||
JNI_VERSION_1_6) == JNI_OK)
|
||||
{ /* already attached or even a Java thread */
|
||||
return;
|
||||
}
|
||||
(*android_jvm)->AttachCurrentThread(android_jvm, env, NULL);
|
||||
/* use a thread-local value with a destructor that automatically detaches
|
||||
* the thread from the JVM before it terminates, if not done manually */
|
||||
androidjni_threadlocal->set(androidjni_threadlocal, (void*)*env);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void androidjni_detach_thread()
|
||||
{
|
||||
if (androidjni_threadlocal->get(androidjni_threadlocal))
|
||||
{ /* only do this if we actually attached this thread */
|
||||
androidjni_threadlocal->set(androidjni_threadlocal, NULL);
|
||||
(*android_jvm)->DetachCurrentThread(android_jvm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this library is loaded by the JVM
|
||||
*/
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jclass jversion;
|
||||
jfieldID jsdk_int;
|
||||
int i;
|
||||
|
||||
android_jvm = vm;
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < countof(libs); i++)
|
||||
{
|
||||
libs[i].handle = dlopen(libs[i].name, RTLD_GLOBAL);
|
||||
if (!libs[i].handle)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
androidjni_threadlocal = thread_value_create(attached_thread_cleanup);
|
||||
|
||||
android_charonvpnservice_class =
|
||||
(*env)->NewGlobalRef(env, (*env)->FindClass(env,
|
||||
JNI_PACKAGE_STRING "/CharonVpnService"));
|
||||
android_charonvpnservice_builder_class =
|
||||
(*env)->NewGlobalRef(env, (*env)->FindClass(env,
|
||||
JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter"));
|
||||
|
||||
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);
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this library is unloaded by the JVM (which never happens on
|
||||
* Android)
|
||||
*/
|
||||
void JNI_OnUnload(JavaVM *vm, void *reserved)
|
||||
{
|
||||
int i;
|
||||
|
||||
androidjni_threadlocal->destroy(androidjni_threadlocal);
|
||||
|
||||
for (i = countof(libs) - 1; i >= 0; i--)
|
||||
{
|
||||
if (libs[i].handle)
|
||||
{
|
||||
dlclose(libs[i].handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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_jni android_jni
|
||||
* @{ @ingroup libandroidbridge
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_JNI_H_
|
||||
#define ANDROID_JNI_H_
|
||||
|
||||
#include <jni.h>
|
||||
#include <library.h>
|
||||
|
||||
#define JNI_PACKAGE org_strongswan_android_logic
|
||||
#define JNI_PACKAGE_STRING "org/strongswan/android/logic"
|
||||
|
||||
#define JNI_METHOD_PP(pack, klass, name, ret, ...) \
|
||||
ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__)
|
||||
|
||||
#define JNI_METHOD_P(pack, klass, name, ret, ...) \
|
||||
JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__)
|
||||
|
||||
#define JNI_METHOD(klass, name, ret, ...) \
|
||||
JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Java classes
|
||||
* Initialized in JNI_OnLoad()
|
||||
*/
|
||||
extern jclass *android_charonvpnservice_class;
|
||||
extern jclass *android_charonvpnservice_builder_class;
|
||||
|
||||
/**
|
||||
* Currently known (supported) SDK versions
|
||||
*
|
||||
* see android.os.Build.VERSION_CODES for definitions
|
||||
*/
|
||||
typedef enum {
|
||||
ANDROID_ICE_CREAM_SANDWICH = 14,
|
||||
ANDROID_ICE_CREAM_SANDWICH_MR1 = 15,
|
||||
ANDROID_JELLY_BEAN = 16,
|
||||
ANDROID_JELLY_BEAN_MR1 = 17,
|
||||
ANDROID_JELLY_BEAN_MR2 = 18,
|
||||
} android_sdk_version_t;
|
||||
|
||||
/**
|
||||
* The current SDK version of the Android framework
|
||||
*
|
||||
* see android.os.Build.VERSION.SDK_INT
|
||||
*/
|
||||
extern android_sdk_version_t android_sdk_version;
|
||||
|
||||
/**
|
||||
* Attach the current thread to the JVM
|
||||
*
|
||||
* As local JNI references are not freed until the thread detaches
|
||||
* androidjni_detach_thread() should be called as soon as possible.
|
||||
* If it is not called a thread-local destructor ensures that the
|
||||
* thread is at least detached as soon as it terminates.
|
||||
*
|
||||
* @param env JNIEnv
|
||||
*/
|
||||
void androidjni_attach_thread(JNIEnv **env);
|
||||
|
||||
/**
|
||||
* Detach the current thread from the JVM
|
||||
*
|
||||
* Call this as soon as possible to ensure that local JNI references are freed.
|
||||
*/
|
||||
void androidjni_detach_thread();
|
||||
|
||||
/**
|
||||
* Handle exceptions thrown by a JNI call
|
||||
*
|
||||
* @param env JNIEnv
|
||||
* @return TRUE if an exception was thrown
|
||||
*/
|
||||
static inline bool androidjni_exception_occurred(JNIEnv *env)
|
||||
{
|
||||
if ((*env)->ExceptionOccurred(env))
|
||||
{ /* clear any exception, otherwise the VM is terminated */
|
||||
(*env)->ExceptionDescribe(env);
|
||||
(*env)->ExceptionClear(env);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java string to a C string. Memory is allocated.
|
||||
*
|
||||
* @param env JNIEnv
|
||||
* @param jstr Java string
|
||||
* @return native C string (allocated)
|
||||
*/
|
||||
static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr)
|
||||
{
|
||||
char *str = NULL;
|
||||
jsize bytes, chars;
|
||||
|
||||
if (jstr)
|
||||
{
|
||||
chars = (*env)->GetStringLength(env, jstr);
|
||||
bytes = (*env)->GetStringUTFLength(env, jstr);
|
||||
str = malloc(bytes + 1);
|
||||
(*env)->GetStringUTFRegion(env, jstr, 0, chars, str);
|
||||
str[bytes] = '\0';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Java byte array to a chunk
|
||||
*
|
||||
* @param env JNIEnv
|
||||
* @param jbytearray Java byte array
|
||||
* @return allocated chunk
|
||||
*/
|
||||
static inline chunk_t chunk_from_byte_array(JNIEnv *env, jbyteArray jbytearray)
|
||||
{
|
||||
chunk_t chunk;
|
||||
|
||||
chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray));
|
||||
(*env)->GetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given chunk to a Java byte array
|
||||
*
|
||||
* @param env JNIEnv
|
||||
* @param chunk native chunk
|
||||
* @return allocated Java byte array
|
||||
*/
|
||||
static inline jbyteArray byte_array_from_chunk(JNIEnv *env, chunk_t chunk)
|
||||
{
|
||||
jbyteArray jbytearray;
|
||||
|
||||
jbytearray = (*env)->NewByteArray(env, chunk.len);
|
||||
(*env)->SetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr);
|
||||
return jbytearray;
|
||||
}
|
||||
|
||||
#endif /** ANDROID_JNI_H_ @}*/
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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_attr.h"
|
||||
#include "../charonservice.h"
|
||||
|
||||
#include <hydra.h>
|
||||
#include <utils/debug.h>
|
||||
#include <library.h>
|
||||
|
||||
typedef struct private_android_attr_t private_android_attr_t;
|
||||
|
||||
/**
|
||||
* Private data of an android_attr_t object.
|
||||
*/
|
||||
struct private_android_attr_t {
|
||||
|
||||
/**
|
||||
* Public interface.
|
||||
*/
|
||||
android_attr_t public;
|
||||
};
|
||||
|
||||
METHOD(attribute_handler_t, handle, bool,
|
||||
private_android_attr_t *this, ike_sa_t *ike_sa,
|
||||
configuration_attribute_type_t type, chunk_t data)
|
||||
{
|
||||
vpnservice_builder_t *builder;
|
||||
host_t *dns;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case INTERNAL_IP4_DNS:
|
||||
dns = host_create_from_chunk(AF_INET, data, 0);
|
||||
break;
|
||||
case INTERNAL_IP6_DNS:
|
||||
dns = host_create_from_chunk(AF_INET6, data, 0);
|
||||
break;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!dns || dns->is_anyaddr(dns))
|
||||
{
|
||||
DESTROY_IF(dns);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
builder = charonservice->get_vpnservice_builder(charonservice);
|
||||
builder->add_dns(builder, dns);
|
||||
dns->destroy(dns);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(attribute_handler_t, release, void,
|
||||
private_android_attr_t *this, ike_sa_t *ike_sa,
|
||||
configuration_attribute_type_t type, chunk_t data)
|
||||
{
|
||||
/* DNS servers cannot be removed from an existing TUN device */
|
||||
}
|
||||
|
||||
METHOD(enumerator_t, enumerate_dns6, bool,
|
||||
enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data)
|
||||
{
|
||||
*type = INTERNAL_IP6_DNS;
|
||||
*data = chunk_empty;
|
||||
this->enumerate = (void*)return_false;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(enumerator_t, enumerate_dns4, bool,
|
||||
enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data)
|
||||
{
|
||||
*type = INTERNAL_IP4_DNS;
|
||||
*data = chunk_empty;
|
||||
this->enumerate = (void*)_enumerate_dns6;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*,
|
||||
private_android_attr_t *this, ike_sa_t *ike_sa, linked_list_t *vips)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
|
||||
INIT(enumerator,
|
||||
.enumerate = (void*)_enumerate_dns4,
|
||||
.destroy = (void*)free,
|
||||
);
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
METHOD(android_attr_t, destroy, void,
|
||||
private_android_attr_t *this)
|
||||
{
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header
|
||||
*/
|
||||
android_attr_t *android_attr_create()
|
||||
{
|
||||
private_android_attr_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.handler = {
|
||||
.handle = _handle,
|
||||
.release = _release,
|
||||
.create_attribute_enumerator = _create_attribute_enumerator,
|
||||
},
|
||||
.destroy = _destroy,
|
||||
},
|
||||
);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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_attr android_attr
|
||||
* @{ @ingroup android_backend
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_ATTR_H_
|
||||
#define ANDROID_ATTR_H_
|
||||
|
||||
#include <library.h>
|
||||
#include <attributes/attribute_handler.h>
|
||||
|
||||
typedef struct android_attr_t android_attr_t;
|
||||
|
||||
/**
|
||||
* Handler for DNS configuration
|
||||
*/
|
||||
struct android_attr_t {
|
||||
|
||||
/**
|
||||
* implements the attribute_handler_t interface
|
||||
*/
|
||||
attribute_handler_t handler;
|
||||
|
||||
/**
|
||||
* Destroy a android_attr_t
|
||||
*/
|
||||
void (*destroy)(android_attr_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a android_attr_t instance.
|
||||
*/
|
||||
android_attr_t *android_attr_create(void);
|
||||
|
||||
#endif /** ANDROID_ATTR_H_ @}*/
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_creds.h"
|
||||
#include "../charonservice.h"
|
||||
|
||||
#include <daemon.h>
|
||||
#include <library.h>
|
||||
#include <credentials/sets/mem_cred.h>
|
||||
#include <threading/rwlock.h>
|
||||
|
||||
typedef struct private_android_creds_t private_android_creds_t;
|
||||
|
||||
/**
|
||||
* Private data of an android_creds_t object
|
||||
*/
|
||||
struct private_android_creds_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
*/
|
||||
android_creds_t public;
|
||||
|
||||
/**
|
||||
* Credential set storing trusted certificates and user credentials
|
||||
*/
|
||||
mem_cred_t *creds;
|
||||
|
||||
/**
|
||||
* read/write lock to make sure certificates are only loaded once
|
||||
*/
|
||||
rwlock_t *lock;
|
||||
|
||||
/**
|
||||
* TRUE if certificates have been loaded via JNI
|
||||
*/
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Free allocated DER encoding
|
||||
*/
|
||||
static void free_encoding(chunk_t *chunk)
|
||||
{
|
||||
chunk_free(chunk);
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load trusted certificates via charonservice (JNI).
|
||||
*/
|
||||
static void load_trusted_certificates(private_android_creds_t *this)
|
||||
{
|
||||
linked_list_t *certs;
|
||||
certificate_t *cert;
|
||||
chunk_t *current;
|
||||
|
||||
certs = charonservice->get_trusted_certificates(charonservice);
|
||||
if (certs)
|
||||
{
|
||||
while (certs->remove_first(certs, (void**)¤t) == SUCCESS)
|
||||
{
|
||||
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
|
||||
BUILD_BLOB_ASN1_DER, *current, BUILD_END);
|
||||
if (cert)
|
||||
{
|
||||
DBG2(DBG_CFG, "loaded CA certificate '%Y'",
|
||||
cert->get_subject(cert));
|
||||
this->creds->add_cert(this->creds, TRUE, cert);
|
||||
}
|
||||
free_encoding(current);
|
||||
}
|
||||
certs->destroy(certs);
|
||||
}
|
||||
}
|
||||
|
||||
METHOD(credential_set_t, create_cert_enumerator, enumerator_t*,
|
||||
private_android_creds_t *this, certificate_type_t cert, key_type_t key,
|
||||
identification_t *id, bool trusted)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
|
||||
if (cert != CERT_ANY && cert != CERT_X509)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
this->lock->read_lock(this->lock);
|
||||
if (!this->loaded)
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
this->lock->write_lock(this->lock);
|
||||
/* check again after acquiring the write lock */
|
||||
if (!this->loaded)
|
||||
{
|
||||
load_trusted_certificates(this);
|
||||
this->loaded = TRUE;
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
this->lock->read_lock(this->lock);
|
||||
}
|
||||
enumerator = this->creds->set.create_cert_enumerator(&this->creds->set,
|
||||
cert, key, id, trusted);
|
||||
return enumerator_create_cleaner(enumerator, (void*)this->lock->unlock,
|
||||
this->lock);
|
||||
}
|
||||
|
||||
METHOD(android_creds_t, add_username_password, void,
|
||||
private_android_creds_t *this, char *username, char *password)
|
||||
{
|
||||
shared_key_t *shared_key;
|
||||
identification_t *id;
|
||||
chunk_t secret;
|
||||
|
||||
secret = chunk_create(password, strlen(password));
|
||||
shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret));
|
||||
id = identification_create_from_string(username);
|
||||
|
||||
this->creds->add_shared(this->creds, shared_key, id, NULL);
|
||||
}
|
||||
|
||||
METHOD(credential_set_t, create_shared_enumerator, enumerator_t*,
|
||||
private_android_creds_t *this, shared_key_type_t type,
|
||||
identification_t *me, identification_t *other)
|
||||
{
|
||||
return this->creds->set.create_shared_enumerator(&this->creds->set,
|
||||
type, me, other);
|
||||
}
|
||||
|
||||
METHOD(android_creds_t, load_user_certificate, certificate_t*,
|
||||
private_android_creds_t *this)
|
||||
{
|
||||
linked_list_t *encodings;
|
||||
certificate_t *cert = NULL, *ca_cert;
|
||||
chunk_t *current;
|
||||
|
||||
encodings = charonservice->get_user_certificate(charonservice);
|
||||
if (!encodings)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (encodings->remove_first(encodings, (void**)¤t) == SUCCESS)
|
||||
{
|
||||
if (!cert)
|
||||
{ /* the first element is the user certificate */
|
||||
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
|
||||
BUILD_BLOB_ASN1_DER, *current, BUILD_END);
|
||||
if (!cert)
|
||||
{
|
||||
DBG1(DBG_CFG, "failed to load user certificate");
|
||||
free_encoding(current);
|
||||
break;
|
||||
}
|
||||
DBG1(DBG_CFG, "loaded user certificate '%Y' and private key",
|
||||
cert->get_subject(cert));
|
||||
cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
|
||||
free_encoding(current);
|
||||
continue;
|
||||
}
|
||||
/* the rest are CA certificates, we ignore failures */
|
||||
ca_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
|
||||
BUILD_BLOB_ASN1_DER, *current, BUILD_END);
|
||||
if (ca_cert)
|
||||
{
|
||||
DBG1(DBG_CFG, "loaded CA certificate '%Y'",
|
||||
ca_cert->get_subject(ca_cert));
|
||||
this->creds->add_cert(this->creds, TRUE, ca_cert);
|
||||
}
|
||||
free_encoding(current);
|
||||
}
|
||||
encodings->destroy_function(encodings, (void*)free_encoding);
|
||||
|
||||
if (cert)
|
||||
{
|
||||
private_key_t *key;
|
||||
|
||||
key = charonservice->get_user_key(charonservice,
|
||||
cert->get_public_key(cert));
|
||||
if (key)
|
||||
{
|
||||
this->creds->add_key(this->creds, key);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG1(DBG_CFG, "failed to load private key");
|
||||
cert->destroy(cert);
|
||||
cert = NULL;
|
||||
}
|
||||
}
|
||||
return cert;
|
||||
}
|
||||
|
||||
METHOD(credential_set_t, create_private_enumerator, enumerator_t*,
|
||||
private_android_creds_t *this, key_type_t type, identification_t *id)
|
||||
{
|
||||
return this->creds->set.create_private_enumerator(&this->creds->set,
|
||||
type, id);
|
||||
}
|
||||
|
||||
METHOD(android_creds_t, clear, void,
|
||||
private_android_creds_t *this)
|
||||
{
|
||||
this->lock->write_lock(this->lock);
|
||||
this->creds->clear(this->creds);
|
||||
this->loaded = FALSE;
|
||||
this->lock->unlock(this->lock);
|
||||
}
|
||||
|
||||
METHOD(android_creds_t, destroy, void,
|
||||
private_android_creds_t *this)
|
||||
{
|
||||
clear(this);
|
||||
this->creds->destroy(this->creds);
|
||||
this->lock->destroy(this->lock);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header.
|
||||
*/
|
||||
android_creds_t *android_creds_create()
|
||||
{
|
||||
private_android_creds_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.set = {
|
||||
.create_cert_enumerator = _create_cert_enumerator,
|
||||
.create_shared_enumerator = _create_shared_enumerator,
|
||||
.create_private_enumerator = _create_private_enumerator,
|
||||
.create_cdp_enumerator = (void*)return_null,
|
||||
.cache_cert = (void*)nop,
|
||||
},
|
||||
.add_username_password = _add_username_password,
|
||||
.load_user_certificate = _load_user_certificate,
|
||||
.clear = _clear,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.creds = mem_cred_create(),
|
||||
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
||||
);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_creds android_creds
|
||||
* @{ @ingroup android_backend
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_CREDS_H_
|
||||
#define ANDROID_CREDS_H_
|
||||
|
||||
#include <library.h>
|
||||
#include <credentials/credential_set.h>
|
||||
|
||||
typedef struct android_creds_t android_creds_t;
|
||||
|
||||
/**
|
||||
* Android credential set that provides CA certificates via JNI and supplied
|
||||
* user credentials.
|
||||
*/
|
||||
struct android_creds_t {
|
||||
|
||||
/**
|
||||
* Implements credential_set_t
|
||||
*/
|
||||
credential_set_t set;
|
||||
|
||||
/**
|
||||
* Add user name and password for EAP authentication
|
||||
*
|
||||
* @param username user name
|
||||
* @param password password
|
||||
*/
|
||||
void (*add_username_password)(android_creds_t *this, char *username,
|
||||
char *password);
|
||||
|
||||
/**
|
||||
* Load the user certificate and private key
|
||||
*
|
||||
* @return loaded client certificate, NULL on failure
|
||||
*/
|
||||
certificate_t *(*load_user_certificate)(android_creds_t *this);
|
||||
|
||||
/**
|
||||
* Clear the cached certificates and stored credentials.
|
||||
*/
|
||||
void (*clear)(android_creds_t *this);
|
||||
|
||||
/**
|
||||
* Destroy a android_creds instance.
|
||||
*/
|
||||
void (*destroy)(android_creds_t *this);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an android_creds instance.
|
||||
*/
|
||||
android_creds_t *android_creds_create();
|
||||
|
||||
#endif /** ANDROID_CREDS_H_ @}*/
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "android_dns_proxy.h"
|
||||
|
||||
#include <hydra.h>
|
||||
#include <threading/rwlock.h>
|
||||
#include <collections/hashtable.h>
|
||||
#include <processing/jobs/callback_job.h>
|
||||
|
||||
/**
|
||||
* Timeout in seconds for sockets (i.e. not used for x seconds -> delete)
|
||||
*/
|
||||
#define SOCKET_TIMEOUT 30
|
||||
|
||||
typedef struct private_android_dns_proxy_t private_android_dns_proxy_t;
|
||||
|
||||
struct private_android_dns_proxy_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
*/
|
||||
android_dns_proxy_t public;
|
||||
|
||||
/**
|
||||
* Mapping from source address to sockets
|
||||
*/
|
||||
hashtable_t *sockets;
|
||||
|
||||
/**
|
||||
* Registered callback
|
||||
*/
|
||||
dns_proxy_response_cb_t cb;
|
||||
|
||||
/**
|
||||
* Data passed to callback
|
||||
*/
|
||||
void *data;
|
||||
|
||||
/**
|
||||
* Lock used to synchronize access to the private members
|
||||
*/
|
||||
rwlock_t *lock;
|
||||
|
||||
/**
|
||||
* Hostnames to filter queries by
|
||||
*/
|
||||
hashtable_t *hostnames;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data for proxy sockets
|
||||
*/
|
||||
typedef struct {
|
||||
private_android_dns_proxy_t *proxy;
|
||||
time_t last_use;
|
||||
host_t *src;
|
||||
int fd;
|
||||
} proxy_socket_t;
|
||||
|
||||
/**
|
||||
* Destroy a socket
|
||||
*/
|
||||
static void socket_destroy(proxy_socket_t *this)
|
||||
{
|
||||
this->src->destroy(this->src);
|
||||
if (this->fd != -1)
|
||||
{
|
||||
close(this->fd);
|
||||
}
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a proxy socket by src address
|
||||
*/
|
||||
static u_int socket_hash(host_t *src)
|
||||
{
|
||||
u_int16_t port = src->get_port(src);
|
||||
return chunk_hash_inc(src->get_address(src),
|
||||
chunk_hash(chunk_from_thing(port)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare proxy sockets by src address
|
||||
*/
|
||||
static bool socket_equals(host_t *a, host_t *b)
|
||||
{
|
||||
return a->equals(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a UDP socket for the given address family
|
||||
*/
|
||||
static int open_socket(int family)
|
||||
{
|
||||
int skt;
|
||||
|
||||
skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (skt < 0)
|
||||
{
|
||||
DBG1(DBG_NET, "could not open proxy socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface,
|
||||
skt, family))
|
||||
{
|
||||
DBG1(DBG_NET, "installing bypass policy for proxy socket failed");
|
||||
close(skt);
|
||||
return -1;
|
||||
}
|
||||
return skt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a proxy socket for the given source
|
||||
*/
|
||||
static proxy_socket_t *create_socket(private_android_dns_proxy_t *this,
|
||||
host_t *src)
|
||||
{
|
||||
proxy_socket_t *skt;
|
||||
|
||||
INIT(skt,
|
||||
.proxy = this,
|
||||
.src = src->clone(src),
|
||||
.fd = open_socket(src->get_family(src)),
|
||||
);
|
||||
if (skt->fd == -1)
|
||||
{
|
||||
socket_destroy(skt);
|
||||
return NULL;
|
||||
}
|
||||
return skt;
|
||||
}
|
||||
|
||||
CALLBACK(handle_response, bool,
|
||||
proxy_socket_t *this, int fd, watcher_event_t event)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
char buf[4096];
|
||||
ssize_t len;
|
||||
host_t *src;
|
||||
|
||||
len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&addr,
|
||||
&addr_len);
|
||||
if (len > 0)
|
||||
{
|
||||
ip_packet_t *packet;
|
||||
|
||||
src = host_create_from_sockaddr((sockaddr_t*)&addr);
|
||||
if (!src)
|
||||
{
|
||||
DBG1(DBG_NET, "failed to parse source address");
|
||||
return TRUE;
|
||||
}
|
||||
packet = ip_packet_create_udp_from_data(src, this->src,
|
||||
chunk_create(buf, len));
|
||||
if (!packet)
|
||||
{
|
||||
DBG1(DBG_NET, "failed to parse DNS response");
|
||||
return TRUE;
|
||||
}
|
||||
this->proxy->lock->read_lock(this->proxy->lock);
|
||||
this->last_use = time_monotonic(NULL);
|
||||
if (this->proxy->cb)
|
||||
{
|
||||
this->proxy->cb(this->proxy->data, packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
packet->destroy(packet);
|
||||
}
|
||||
this->proxy->lock->unlock(this->proxy->lock);
|
||||
}
|
||||
else if (errno != EWOULDBLOCK)
|
||||
{
|
||||
DBG1(DBG_NET, "receiving DNS response failed: %s", strerror(errno));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
CALLBACK(handle_timeout, job_requeue_t,
|
||||
proxy_socket_t *this)
|
||||
{
|
||||
time_t now, diff;
|
||||
|
||||
now = time_monotonic(NULL);
|
||||
this->proxy->lock->write_lock(this->proxy->lock);
|
||||
diff = now - this->last_use;
|
||||
if (diff >= SOCKET_TIMEOUT)
|
||||
{
|
||||
this->proxy->sockets->remove(this->proxy->sockets, this->src);
|
||||
lib->watcher->remove(lib->watcher, this->fd);
|
||||
this->proxy->lock->unlock(this->proxy->lock);
|
||||
socket_destroy(this);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
this->proxy->lock->unlock(this->proxy->lock);
|
||||
return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* DNS header and masks to access flags
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
u_int16_t id;
|
||||
u_int16_t flags;
|
||||
#define DNS_QR_MASK 0x8000
|
||||
#define DNS_OPCODE_MASK 0x7800
|
||||
u_int16_t qdcount;
|
||||
u_int16_t ancount;
|
||||
u_int16_t nscount;
|
||||
u_int16_t arcount;
|
||||
} dns_header_t;
|
||||
|
||||
/**
|
||||
* Extract the hostname in the question section data points to.
|
||||
* Hostnames can be at most 255 characters (including dots separating labels),
|
||||
* each label must be between 1 and 63 characters.
|
||||
* The format is [len][label][len][label], followed by a null byte to indicate
|
||||
* the null label of the root.
|
||||
*/
|
||||
static char *extract_hostname(chunk_t data)
|
||||
{
|
||||
char *hostname, *pos, *end;
|
||||
u_int8_t label;
|
||||
|
||||
if (!data.len || data.len > 255)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
label = *data.ptr;
|
||||
data = chunk_skip(data, 1);
|
||||
hostname = strndup(data.ptr, data.len);
|
||||
/* replace all label lengths with dots */
|
||||
pos = hostname + label;
|
||||
end = hostname + strlen(hostname);
|
||||
while (pos < end)
|
||||
{
|
||||
label = *pos;
|
||||
*pos++ = '.';
|
||||
pos += label;
|
||||
}
|
||||
return hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the DNS query is for one of the allowed hostnames
|
||||
*/
|
||||
static bool check_hostname(private_android_dns_proxy_t *this, chunk_t data)
|
||||
{
|
||||
dns_header_t *dns;
|
||||
char *hostname;
|
||||
bool success = FALSE;
|
||||
|
||||
this->lock->read_lock(this->lock);
|
||||
if (!this->hostnames->get_count(this->hostnames))
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
return TRUE;
|
||||
}
|
||||
if (data.len < sizeof(dns_header_t))
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
return FALSE;
|
||||
}
|
||||
dns = (dns_header_t*)data.ptr;
|
||||
if ((ntohs(dns->flags) & DNS_QR_MASK) == 0 &&
|
||||
(ntohs(dns->flags) & DNS_OPCODE_MASK) == 0 &&
|
||||
dns->qdcount)
|
||||
{
|
||||
data = chunk_skip(data, sizeof(dns_header_t));
|
||||
hostname = extract_hostname(data);
|
||||
if (hostname && this->hostnames->get(this->hostnames, hostname))
|
||||
{
|
||||
success = TRUE;
|
||||
}
|
||||
free(hostname);
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
return success;
|
||||
}
|
||||
|
||||
METHOD(android_dns_proxy_t, handle, bool,
|
||||
private_android_dns_proxy_t *this, ip_packet_t *packet)
|
||||
{
|
||||
proxy_socket_t *skt;
|
||||
host_t *dst, *src;
|
||||
chunk_t data;
|
||||
|
||||
if (packet->get_next_header(packet) != IPPROTO_UDP)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
dst = packet->get_destination(packet);
|
||||
if (dst->get_port(dst) != 53)
|
||||
{ /* no DNS packet */
|
||||
return FALSE;
|
||||
}
|
||||
data = packet->get_payload(packet);
|
||||
/* remove UDP header */
|
||||
data = chunk_skip(data, 8);
|
||||
if (!check_hostname(this, data))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
src = packet->get_source(packet);
|
||||
this->lock->write_lock(this->lock);
|
||||
skt = this->sockets->get(this->sockets, src);
|
||||
if (!skt)
|
||||
{
|
||||
skt = create_socket(this, src);
|
||||
if (!skt)
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
return FALSE;
|
||||
}
|
||||
this->sockets->put(this->sockets, skt->src, skt);
|
||||
lib->watcher->add(lib->watcher, skt->fd, WATCHER_READ, handle_response,
|
||||
skt);
|
||||
lib->scheduler->schedule_job(lib->scheduler,
|
||||
(job_t*)callback_job_create(handle_timeout, skt,
|
||||
NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT);
|
||||
}
|
||||
skt->last_use = time_monotonic(NULL);
|
||||
if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst),
|
||||
*dst->get_sockaddr_len(dst)) != data.len)
|
||||
{
|
||||
DBG1(DBG_NET, "sending DNS request failed: %s", strerror(errno));
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(android_dns_proxy_t, register_cb, void,
|
||||
private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb, void *data)
|
||||
{
|
||||
this->lock->write_lock(this->lock);
|
||||
this->cb = cb;
|
||||
this->data = data;
|
||||
this->lock->unlock(this->lock);
|
||||
}
|
||||
|
||||
METHOD(android_dns_proxy_t, unregister_cb, void,
|
||||
private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb)
|
||||
{
|
||||
this->lock->write_lock(this->lock);
|
||||
if (this->cb == cb)
|
||||
{
|
||||
this->cb = NULL;
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
}
|
||||
|
||||
METHOD(android_dns_proxy_t, add_hostname, void,
|
||||
private_android_dns_proxy_t *this, char *hostname)
|
||||
{
|
||||
char *existing;
|
||||
|
||||
hostname = strdup(hostname);
|
||||
this->lock->write_lock(this->lock);
|
||||
existing = this->hostnames->put(this->hostnames, hostname, hostname);
|
||||
this->lock->unlock(this->lock);
|
||||
free(existing);
|
||||
}
|
||||
|
||||
METHOD(android_dns_proxy_t, destroy, void,
|
||||
private_android_dns_proxy_t *this)
|
||||
{
|
||||
this->hostnames->destroy_function(this->hostnames, (void*)free);
|
||||
this->sockets->destroy_function(this->sockets, (void*)socket_destroy);
|
||||
this->lock->destroy(this->lock);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header.
|
||||
*/
|
||||
android_dns_proxy_t *android_dns_proxy_create()
|
||||
{
|
||||
private_android_dns_proxy_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.handle = _handle,
|
||||
.register_cb = _register_cb,
|
||||
.unregister_cb = _unregister_cb,
|
||||
.add_hostname = _add_hostname,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.sockets = hashtable_create((hashtable_hash_t)socket_hash,
|
||||
(hashtable_equals_t)socket_equals, 4),
|
||||
.hostnames = hashtable_create(hashtable_hash_str,
|
||||
hashtable_equals_str, 4),
|
||||
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
||||
);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_dns_proxy android_dns_proxy
|
||||
* @{ @ingroup android_backend
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_DNS_PROXY_H_
|
||||
#define ANDROID_DNS_PROXY_H_
|
||||
|
||||
#include <ip_packet.h>
|
||||
|
||||
typedef struct android_dns_proxy_t android_dns_proxy_t;
|
||||
|
||||
/**
|
||||
* Callback called to deliver a DNS response packet.
|
||||
*
|
||||
* @param data data supplied during registration of the callback
|
||||
* @param packet DNS response packet (has to be destroyed)
|
||||
*/
|
||||
typedef void (*dns_proxy_response_cb_t)(void *data, ip_packet_t *packet);
|
||||
|
||||
/**
|
||||
* DNS proxy class
|
||||
*/
|
||||
struct android_dns_proxy_t {
|
||||
|
||||
/**
|
||||
* Handle an outbound DNS packet (if the packet is one)
|
||||
*
|
||||
* @param packet packet to handle
|
||||
* @return TRUE if handled, FALSE otherwise (no DNS)
|
||||
*/
|
||||
bool (*handle)(android_dns_proxy_t *this, ip_packet_t *packet);
|
||||
|
||||
/**
|
||||
* Register the callback used to deliver DNS response packets.
|
||||
*
|
||||
* @param cb the callback function
|
||||
* @param data optional data provided to callback
|
||||
*/
|
||||
void (*register_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb,
|
||||
void *data);
|
||||
|
||||
/**
|
||||
* Unregister the callback used to deliver DNS response packets.
|
||||
*
|
||||
* @param cb the callback function
|
||||
* @param data optional data provided to callback
|
||||
*/
|
||||
void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb);
|
||||
|
||||
/**
|
||||
* Add a hostname for which queries are proxied. If at least one hostname
|
||||
* is configured DNS queries for others will not be handled.
|
||||
*
|
||||
* @param hostname hostname to add (gets cloned)
|
||||
*/
|
||||
void (*add_hostname)(android_dns_proxy_t *this, char *hostname);
|
||||
|
||||
/**
|
||||
* Destroy an instance.
|
||||
*/
|
||||
void (*destroy)(android_dns_proxy_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
android_dns_proxy_t *android_dns_proxy_create();
|
||||
|
||||
#endif /** ANDROID_DNS_PROXY_H_ @}*/
|
||||
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_private_key.h"
|
||||
|
||||
#include "../android_jni.h"
|
||||
#include <utils/debug.h>
|
||||
#include <asn1/asn1.h>
|
||||
|
||||
typedef struct private_private_key_t private_private_key_t;
|
||||
|
||||
/**
|
||||
* Private data of a android_private_key_t object.
|
||||
*/
|
||||
struct private_private_key_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
*/
|
||||
private_key_t public;
|
||||
|
||||
/**
|
||||
* reference to the Java PrivateKey object
|
||||
*/
|
||||
jobject key;
|
||||
|
||||
/**
|
||||
* Java class used to build signatures
|
||||
*/
|
||||
jclass signature_class;
|
||||
|
||||
/**
|
||||
* public key that belongs to this private key
|
||||
*/
|
||||
public_key_t *pubkey;
|
||||
|
||||
/**
|
||||
* reference count
|
||||
*/
|
||||
refcount_t ref;
|
||||
};
|
||||
|
||||
METHOD(private_key_t, sign, bool,
|
||||
private_private_key_t *this, signature_scheme_t scheme,
|
||||
chunk_t data, chunk_t *signature)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
const char *method = NULL;
|
||||
jstring jmethod;
|
||||
jobject jsignature;
|
||||
jbyteArray jdata, jsigarray;
|
||||
|
||||
switch (this->pubkey->get_type(this->pubkey))
|
||||
{
|
||||
case KEY_RSA:
|
||||
switch (scheme)
|
||||
{
|
||||
case SIGN_RSA_EMSA_PKCS1_NULL:
|
||||
method = "NONEwithRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_MD5:
|
||||
method = "MD5withRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_SHA1:
|
||||
method = "SHA1withRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_SHA224:
|
||||
method = "SHA224withRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_SHA256:
|
||||
method = "SHA256withRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_SHA384:
|
||||
method = "SHA384withRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_SHA512:
|
||||
method = "SHA512withRSA";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case KEY_ECDSA:
|
||||
switch (scheme)
|
||||
{
|
||||
case SIGN_ECDSA_WITH_SHA1_DER:
|
||||
method = "SHA1withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA256_DER:
|
||||
case SIGN_ECDSA_256:
|
||||
method = "SHA256withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA384_DER:
|
||||
case SIGN_ECDSA_384:
|
||||
method = "SHA384withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA512_DER:
|
||||
case SIGN_ECDSA_521:
|
||||
method = "SHA512withECDSA";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!method)
|
||||
{
|
||||
DBG1(DBG_LIB, "signature scheme %N not supported via JNI",
|
||||
signature_scheme_names, scheme);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
/* we use java.security.Signature to create the signature without requiring
|
||||
* access to the actual private key */
|
||||
method_id = (*env)->GetStaticMethodID(env, this->signature_class,
|
||||
"getInstance", "(Ljava/lang/String;)Ljava/security/Signature;");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jmethod = (*env)->NewStringUTF(env, method);
|
||||
if (!jmethod)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jsignature = (*env)->CallStaticObjectMethod(env, this->signature_class,
|
||||
method_id, jmethod);
|
||||
if (!jsignature)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
method_id = (*env)->GetMethodID(env, this->signature_class, "initSign",
|
||||
"(Ljava/security/PrivateKey;)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, jsignature, method_id, this->key);
|
||||
if (androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
method_id = (*env)->GetMethodID(env, this->signature_class, "update",
|
||||
"([B)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jdata = byte_array_from_chunk(env, data);
|
||||
(*env)->CallVoidMethod(env, jsignature, method_id, jdata);
|
||||
if (androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
method_id = (*env)->GetMethodID(env, this->signature_class, "sign",
|
||||
"()[B");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jsigarray = (*env)->CallObjectMethod(env, jsignature, method_id);
|
||||
if (!jsigarray)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (this->pubkey->get_type(this->pubkey) == KEY_ECDSA)
|
||||
{
|
||||
chunk_t encoded, parse, r, s;
|
||||
size_t len = 0;
|
||||
|
||||
switch (scheme)
|
||||
{
|
||||
case SIGN_ECDSA_256:
|
||||
len = 32;
|
||||
break;
|
||||
case SIGN_ECDSA_384:
|
||||
len = 48;
|
||||
break;
|
||||
case SIGN_ECDSA_521:
|
||||
len = 66;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (len)
|
||||
{
|
||||
/* we get an ASN.1 encoded sequence of integers r and s */
|
||||
parse = encoded = chunk_from_byte_array(env, jsigarray);
|
||||
if (asn1_unwrap(&parse, &parse) != ASN1_SEQUENCE ||
|
||||
asn1_unwrap(&parse, &r) != ASN1_INTEGER ||
|
||||
asn1_unwrap(&parse, &s) != ASN1_INTEGER)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
r = chunk_skip_zero(r);
|
||||
s = chunk_skip_zero(s);
|
||||
if (r.len > len || s.len > len)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* concatenate r and s (forced to the defined length) */
|
||||
*signature = chunk_alloc(2*len);
|
||||
memset(signature->ptr, 0, signature->len);
|
||||
memcpy(signature->ptr + (len - r.len), r.ptr, r.len);
|
||||
memcpy(signature->ptr + len + (len - s.len), s.ptr, s.len);
|
||||
chunk_free(&encoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
*signature = chunk_from_byte_array(env, jsigarray);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*signature = chunk_from_byte_array(env, jsigarray);
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "failed to build %N signature via JNI",
|
||||
signature_scheme_names, scheme);
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_type, key_type_t,
|
||||
private_private_key_t *this)
|
||||
{
|
||||
return this->pubkey->get_type(this->pubkey);
|
||||
}
|
||||
|
||||
METHOD(private_key_t, decrypt, bool,
|
||||
private_private_key_t *this, encryption_scheme_t scheme,
|
||||
chunk_t crypto, chunk_t *plain)
|
||||
{
|
||||
DBG1(DBG_LIB, "private key decryption is currently not supported via JNI");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_keysize, int,
|
||||
private_private_key_t *this)
|
||||
{
|
||||
return this->pubkey->get_keysize(this->pubkey);
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_public_key, public_key_t*,
|
||||
private_private_key_t *this)
|
||||
{
|
||||
return this->pubkey->get_ref(this->pubkey);
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_encoding, bool,
|
||||
private_private_key_t *this, cred_encoding_type_t type,
|
||||
chunk_t *encoding)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_fingerprint, bool,
|
||||
private_private_key_t *this, cred_encoding_type_t type, chunk_t *fp)
|
||||
{
|
||||
return this->pubkey->get_fingerprint(this->pubkey, type, fp);
|
||||
}
|
||||
|
||||
METHOD(private_key_t, get_ref, private_key_t*,
|
||||
private_private_key_t *this)
|
||||
{
|
||||
ref_get(&this->ref);
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
METHOD(private_key_t, destroy, void,
|
||||
private_private_key_t *this)
|
||||
{
|
||||
if (ref_put(&this->ref))
|
||||
{
|
||||
JNIEnv *env;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
if (android_sdk_version == ANDROID_JELLY_BEAN)
|
||||
{ /* there is a bug in JB that causes a SIGSEGV if the key object is
|
||||
* garbage collected so we intentionally leak the reference to it */
|
||||
DBG1(DBG_LIB, "intentionally leaking private key reference due to "
|
||||
"a bug in the framework");
|
||||
}
|
||||
else
|
||||
{
|
||||
(*env)->DeleteGlobalRef(env, this->key);
|
||||
}
|
||||
(*env)->DeleteGlobalRef(env, this->signature_class);
|
||||
androidjni_detach_thread();
|
||||
this->pubkey->destroy(this->pubkey);
|
||||
free(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* See header
|
||||
*/
|
||||
private_key_t *android_private_key_create(jobject key, public_key_t *pubkey)
|
||||
{
|
||||
JNIEnv *env;
|
||||
private_private_key_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.get_type = _get_type,
|
||||
.sign = _sign,
|
||||
.decrypt = _decrypt,
|
||||
.get_keysize = _get_keysize,
|
||||
.get_public_key = _get_public_key,
|
||||
.belongs_to = private_key_belongs_to,
|
||||
.equals = private_key_equals,
|
||||
.get_fingerprint = _get_fingerprint,
|
||||
.has_fingerprint = private_key_has_fingerprint,
|
||||
.get_encoding = _get_encoding,
|
||||
.get_ref = _get_ref,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.ref = 1,
|
||||
.pubkey = pubkey,
|
||||
);
|
||||
|
||||
if (!pubkey)
|
||||
{
|
||||
free(this);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* in ICS we could simply call getEncoded and use the PKCS#8/DER encoded
|
||||
* private key, since JB that's not possible as there is no direct access
|
||||
* to private keys anymore (as these could now be hardware backed) */
|
||||
androidjni_attach_thread(&env);
|
||||
this->key = (*env)->NewGlobalRef(env, key);
|
||||
this->signature_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env,
|
||||
"java/security/Signature"));
|
||||
androidjni_detach_thread();
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_private_key android_private_key
|
||||
* @{ @ingroup android_backend
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_PRIVATE_KEY_H_
|
||||
#define ANDROID_PRIVATE_KEY_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <credentials/keys/private_key.h>
|
||||
|
||||
/**
|
||||
* Create a JNI backed key, stored in the Android KeyChain
|
||||
*
|
||||
* @param key PrivateKey instance
|
||||
* @param pubkey public key as extracted from the certificate (gets adopted)
|
||||
* @return private_key_t instance
|
||||
*/
|
||||
private_key_t *android_private_key_create(jobject key, public_key_t *pubkey);
|
||||
|
||||
#endif /** ANDROID_PRIVATE_KEY_H_ @}*/
|
||||
|
||||
@@ -0,0 +1,848 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "android_service.h"
|
||||
#include "android_dns_proxy.h"
|
||||
#include "../charonservice.h"
|
||||
#include "../vpnservice_builder.h"
|
||||
|
||||
#include <daemon.h>
|
||||
#include <library.h>
|
||||
#include <ipsec.h>
|
||||
#include <processing/jobs/callback_job.h>
|
||||
#include <threading/rwlock.h>
|
||||
#include <threading/thread.h>
|
||||
|
||||
typedef struct private_android_service_t private_android_service_t;
|
||||
|
||||
/**
|
||||
* private data of Android service
|
||||
*/
|
||||
struct private_android_service_t {
|
||||
|
||||
/**
|
||||
* public interface
|
||||
*/
|
||||
android_service_t public;
|
||||
|
||||
/**
|
||||
* credential set
|
||||
*/
|
||||
android_creds_t *creds;
|
||||
|
||||
/**
|
||||
* current IKE_SA
|
||||
*/
|
||||
ike_sa_t *ike_sa;
|
||||
|
||||
/**
|
||||
* configuration setttings
|
||||
*/
|
||||
settings_t *settings;
|
||||
|
||||
/**
|
||||
* lock to safely access the TUN device fd
|
||||
*/
|
||||
rwlock_t *lock;
|
||||
|
||||
/**
|
||||
* TUN device file descriptor
|
||||
*/
|
||||
int tunfd;
|
||||
|
||||
/**
|
||||
* MTU of TUN device
|
||||
*/
|
||||
int mtu;
|
||||
|
||||
/**
|
||||
* DNS proxy
|
||||
*/
|
||||
android_dns_proxy_t *dns_proxy;
|
||||
|
||||
/**
|
||||
* Whether to use the DNS proxy or not
|
||||
*/
|
||||
bool use_dns_proxy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Outbound callback
|
||||
*/
|
||||
static void send_esp(void *data, esp_packet_t *packet)
|
||||
{
|
||||
charon->sender->send_no_marker(charon->sender, (packet_t*)packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inbound callback
|
||||
*/
|
||||
static void deliver_plain(private_android_service_t *this,
|
||||
ip_packet_t *packet)
|
||||
{
|
||||
chunk_t encoding;
|
||||
ssize_t len;
|
||||
|
||||
encoding = packet->get_encoding(packet);
|
||||
|
||||
this->lock->read_lock(this->lock);
|
||||
if (this->tunfd < 0)
|
||||
{ /* the TUN device is already closed */
|
||||
this->lock->unlock(this->lock);
|
||||
packet->destroy(packet);
|
||||
return;
|
||||
}
|
||||
len = write(this->tunfd, encoding.ptr, encoding.len);
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
if (len < 0 || len != encoding.len)
|
||||
{
|
||||
DBG1(DBG_DMN, "failed to write packet to TUN device: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
packet->destroy(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver callback
|
||||
*/
|
||||
static void receiver_esp_cb(void *data, packet_t *packet)
|
||||
{
|
||||
esp_packet_t *esp_packet;
|
||||
|
||||
esp_packet = esp_packet_create_from_packet(packet);
|
||||
ipsec->processor->queue_inbound(ipsec->processor, esp_packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Job handling outbound plaintext packets
|
||||
*/
|
||||
static job_requeue_t handle_plain(private_android_service_t *this)
|
||||
{
|
||||
ip_packet_t *packet;
|
||||
chunk_t raw;
|
||||
fd_set set;
|
||||
ssize_t len;
|
||||
int tunfd;
|
||||
bool old, dns_proxy;
|
||||
timeval_t tv = {
|
||||
/* check every second if tunfd is still valid */
|
||||
.tv_sec = 1,
|
||||
};
|
||||
|
||||
FD_ZERO(&set);
|
||||
|
||||
this->lock->read_lock(this->lock);
|
||||
if (this->tunfd < 0)
|
||||
{ /* the TUN device is already closed */
|
||||
this->lock->unlock(this->lock);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
tunfd = this->tunfd;
|
||||
FD_SET(tunfd, &set);
|
||||
/* cache this while we have the lock */
|
||||
dns_proxy = this->use_dns_proxy;
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
old = thread_cancelability(TRUE);
|
||||
len = select(tunfd + 1, &set, NULL, NULL, &tv);
|
||||
thread_cancelability(old);
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
if (errno == EBADF)
|
||||
{ /* the TUN device got closed just before calling select(), retry */
|
||||
return JOB_REQUEUE_FAIR;
|
||||
}
|
||||
DBG1(DBG_DMN, "select on TUN device failed: %s", strerror(errno));
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
else if (len == 0)
|
||||
{ /* timeout, check again right away */
|
||||
return JOB_REQUEUE_DIRECT;
|
||||
}
|
||||
|
||||
raw = chunk_alloc(this->mtu);
|
||||
len = read(tunfd, raw.ptr, raw.len);
|
||||
if (len < 0)
|
||||
{
|
||||
DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno));
|
||||
chunk_free(&raw);
|
||||
return JOB_REQUEUE_FAIR;
|
||||
}
|
||||
raw.len = len;
|
||||
|
||||
packet = ip_packet_create(raw);
|
||||
if (packet)
|
||||
{
|
||||
if (!dns_proxy || !this->dns_proxy->handle(this->dns_proxy, packet))
|
||||
{
|
||||
ipsec->processor->queue_outbound(ipsec->processor, packet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG1(DBG_DMN, "invalid IP packet read from TUN device");
|
||||
}
|
||||
return JOB_REQUEUE_DIRECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a route to the TUN device builder
|
||||
*/
|
||||
static bool add_route(vpnservice_builder_t *builder, host_t *net,
|
||||
u_int8_t prefix)
|
||||
{
|
||||
/* if route is 0.0.0.0/0, split it into two routes 0.0.0.0/1 and
|
||||
* 128.0.0.0/1 because otherwise it would conflict with the current default
|
||||
* route. likewise for IPv6 with ::/0. */
|
||||
if (net->is_anyaddr(net) && prefix == 0)
|
||||
{
|
||||
bool success;
|
||||
|
||||
success = add_route(builder, net, 1);
|
||||
if (net->get_family(net) == AF_INET)
|
||||
{
|
||||
net = host_create_from_string("128.0.0.0", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
net = host_create_from_string("8000::", 0);
|
||||
}
|
||||
success = success && add_route(builder, net, 1);
|
||||
net->destroy(net);
|
||||
return success;
|
||||
}
|
||||
return builder->add_route(builder, net, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and set routes from installed IPsec policies
|
||||
*/
|
||||
static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa)
|
||||
{
|
||||
traffic_selector_t *src_ts, *dst_ts;
|
||||
enumerator_t *enumerator;
|
||||
bool success = TRUE;
|
||||
|
||||
enumerator = child_sa->create_policy_enumerator(child_sa);
|
||||
while (success && enumerator->enumerate(enumerator, &src_ts, &dst_ts))
|
||||
{
|
||||
host_t *net;
|
||||
u_int8_t prefix;
|
||||
|
||||
dst_ts->to_subnet(dst_ts, &net, &prefix);
|
||||
success = add_route(builder, net, prefix);
|
||||
net->destroy(net);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a new TUN device for the supplied SAs, also queues a job that
|
||||
* reads packets from this device.
|
||||
* Additional information such as DNS servers are gathered in appropriate
|
||||
* listeners asynchronously. To be sure every required bit of information is
|
||||
* available this should be called after the CHILD_SA has been established.
|
||||
*/
|
||||
static bool setup_tun_device(private_android_service_t *this,
|
||||
ike_sa_t *ike_sa, child_sa_t *child_sa)
|
||||
{
|
||||
vpnservice_builder_t *builder;
|
||||
enumerator_t *enumerator;
|
||||
bool vip_found = FALSE, already_registered = FALSE;
|
||||
host_t *vip;
|
||||
int tunfd;
|
||||
|
||||
DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}",
|
||||
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa));
|
||||
|
||||
builder = charonservice->get_vpnservice_builder(charonservice);
|
||||
|
||||
enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE);
|
||||
while (enumerator->enumerate(enumerator, &vip))
|
||||
{
|
||||
if (!vip->is_anyaddr(vip))
|
||||
{
|
||||
if (!builder->add_address(builder, vip))
|
||||
{
|
||||
break;
|
||||
}
|
||||
vip_found = TRUE;
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
if (!vip_found)
|
||||
{
|
||||
DBG1(DBG_DMN, "setting up TUN device failed, no virtual IP found");
|
||||
return FALSE;
|
||||
}
|
||||
if (!add_routes(builder, child_sa) ||
|
||||
!builder->set_mtu(builder, this->mtu))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tunfd = builder->establish(builder);
|
||||
if (tunfd == -1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
this->lock->write_lock(this->lock);
|
||||
if (this->tunfd > 0)
|
||||
{ /* close previously opened TUN device */
|
||||
close(this->tunfd);
|
||||
already_registered = true;
|
||||
}
|
||||
this->tunfd = tunfd;
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
DBG1(DBG_DMN, "successfully created TUN device");
|
||||
|
||||
if (!already_registered)
|
||||
{
|
||||
charon->receiver->add_esp_cb(charon->receiver,
|
||||
(receiver_esp_cb_t)receiver_esp_cb, NULL);
|
||||
ipsec->processor->register_inbound(ipsec->processor,
|
||||
(ipsec_inbound_cb_t)deliver_plain, this);
|
||||
ipsec->processor->register_outbound(ipsec->processor,
|
||||
(ipsec_outbound_cb_t)send_esp, NULL);
|
||||
this->dns_proxy->register_cb(this->dns_proxy,
|
||||
(dns_proxy_response_cb_t)deliver_plain, this);
|
||||
|
||||
lib->processor->queue_job(lib->processor,
|
||||
(job_t*)callback_job_create((callback_job_cb_t)handle_plain, this,
|
||||
NULL, (callback_job_cancel_t)return_false));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a new TUN device based on the existing one, but without DNS server.
|
||||
*/
|
||||
static bool setup_tun_device_without_dns(private_android_service_t *this)
|
||||
{
|
||||
vpnservice_builder_t *builder;
|
||||
int tunfd;
|
||||
|
||||
DBG1(DBG_DMN, "setting up TUN device without DNS");
|
||||
|
||||
builder = charonservice->get_vpnservice_builder(charonservice);
|
||||
|
||||
tunfd = builder->establish_no_dns(builder);
|
||||
if (tunfd == -1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
this->lock->write_lock(this->lock);
|
||||
if (this->tunfd > 0)
|
||||
{ /* close previously opened TUN device, this should always be the case */
|
||||
close(this->tunfd);
|
||||
}
|
||||
this->tunfd = tunfd;
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
DBG1(DBG_DMN, "successfully created TUN device without DNS");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current tun device
|
||||
*/
|
||||
static void close_tun_device(private_android_service_t *this)
|
||||
{
|
||||
int tunfd;
|
||||
|
||||
this->lock->write_lock(this->lock);
|
||||
if (this->tunfd < 0)
|
||||
{ /* already closed (or never created) */
|
||||
this->lock->unlock(this->lock);
|
||||
return;
|
||||
}
|
||||
tunfd = this->tunfd;
|
||||
this->tunfd = -1;
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
this->dns_proxy->unregister_cb(this->dns_proxy,
|
||||
(dns_proxy_response_cb_t)deliver_plain);
|
||||
ipsec->processor->unregister_outbound(ipsec->processor,
|
||||
(ipsec_outbound_cb_t)send_esp);
|
||||
ipsec->processor->unregister_inbound(ipsec->processor,
|
||||
(ipsec_inbound_cb_t)deliver_plain);
|
||||
charon->receiver->del_esp_cb(charon->receiver,
|
||||
(receiver_esp_cb_t)receiver_esp_cb);
|
||||
close(tunfd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate the IKE_SA with the given unique ID
|
||||
*/
|
||||
CALLBACK(terminate, job_requeue_t,
|
||||
u_int32_t *id)
|
||||
{
|
||||
charon->controller->terminate_ike(charon->controller, *id,
|
||||
controller_cb_empty, NULL, 0);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reestablish the IKE_SA with the given unique ID
|
||||
*/
|
||||
CALLBACK(reestablish, job_requeue_t,
|
||||
u_int32_t *id)
|
||||
{
|
||||
ike_sa_t *ike_sa;
|
||||
|
||||
ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager, *id);
|
||||
if (ike_sa)
|
||||
{
|
||||
if (ike_sa->reauth(ike_sa) == DESTROY_ME)
|
||||
{
|
||||
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
|
||||
ike_sa);
|
||||
}
|
||||
else
|
||||
{
|
||||
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
|
||||
}
|
||||
}
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, child_updown, bool,
|
||||
private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
|
||||
bool up)
|
||||
{
|
||||
if (this->ike_sa == ike_sa)
|
||||
{
|
||||
if (up)
|
||||
{
|
||||
/* disable the hooks registered to catch initiation failures */
|
||||
this->public.listener.ike_updown = NULL;
|
||||
/* CHILD_SA is up so we can disable the DNS proxy we enabled to
|
||||
* reestablish the SA */
|
||||
this->lock->write_lock(this->lock);
|
||||
this->use_dns_proxy = FALSE;
|
||||
this->lock->unlock(this->lock);
|
||||
if (!setup_tun_device(this, ike_sa, child_sa))
|
||||
{
|
||||
DBG1(DBG_DMN, "failed to setup TUN device");
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_GENERIC_ERROR);
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_CHILD_STATE_UP);
|
||||
}
|
||||
else
|
||||
{
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_CHILD_STATE_DOWN);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, ike_updown, bool,
|
||||
private_android_service_t *this, ike_sa_t *ike_sa, bool up)
|
||||
{
|
||||
/* this callback is only registered during initiation, so if the IKE_SA
|
||||
* goes down we assume some kind of authentication error, more specific
|
||||
* errors are catched in the alert() handler */
|
||||
if (this->ike_sa == ike_sa && !up)
|
||||
{
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_AUTH_ERROR);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, alert, bool,
|
||||
private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert,
|
||||
va_list args)
|
||||
{
|
||||
if (this->ike_sa == ike_sa)
|
||||
{
|
||||
switch (alert)
|
||||
{
|
||||
case ALERT_PEER_ADDR_FAILED:
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_LOOKUP_ERROR);
|
||||
break;
|
||||
case ALERT_PEER_AUTH_FAILED:
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_PEER_AUTH_ERROR);
|
||||
break;
|
||||
case ALERT_KEEP_ON_CHILD_SA_FAILURE:
|
||||
{
|
||||
u_int32_t *id = malloc_thing(u_int32_t);
|
||||
|
||||
/* because close_ike_on_child_failure is set this is only
|
||||
* triggered when CHILD_SA rekeying failed. reestablish it in
|
||||
* the hope that the initial setup works again. */
|
||||
*id = ike_sa->get_unique_id(ike_sa);
|
||||
lib->processor->queue_job(lib->processor,
|
||||
(job_t*)callback_job_create_with_prio(
|
||||
(callback_job_cb_t)reestablish, id, free,
|
||||
(callback_job_cancel_t)return_false, JOB_PRIO_HIGH));
|
||||
break;
|
||||
}
|
||||
case ALERT_PEER_INIT_UNREACHABLE:
|
||||
this->lock->read_lock(this->lock);
|
||||
if (this->tunfd < 0)
|
||||
{
|
||||
u_int32_t *id = malloc_thing(u_int32_t);
|
||||
|
||||
/* always fail if we are not able to initiate the IKE_SA
|
||||
* initially */
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_UNREACHABLE_ERROR);
|
||||
/* terminate the IKE_SA so no further keying tries are
|
||||
* attempted */
|
||||
*id = ike_sa->get_unique_id(ike_sa);
|
||||
lib->processor->queue_job(lib->processor,
|
||||
(job_t*)callback_job_create_with_prio(
|
||||
(callback_job_cb_t)terminate, id, free,
|
||||
(callback_job_cancel_t)return_false, JOB_PRIO_HIGH));
|
||||
}
|
||||
else
|
||||
{
|
||||
peer_cfg_t *peer_cfg;
|
||||
u_int32_t tries, try;
|
||||
|
||||
/* when reestablishing and if keyingtries is not %forever
|
||||
* the IKE_SA is destroyed after the set number of tries,
|
||||
* so notify the GUI */
|
||||
peer_cfg = ike_sa->get_peer_cfg(ike_sa);
|
||||
tries = peer_cfg->get_keyingtries(peer_cfg);
|
||||
try = va_arg(args, u_int32_t);
|
||||
if (tries != 0 && try == tries-1)
|
||||
{
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_UNREACHABLE_ERROR);
|
||||
}
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, ike_rekey, bool,
|
||||
private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
|
||||
{
|
||||
if (this->ike_sa == old)
|
||||
{
|
||||
this->ike_sa = new;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, ike_reestablish_pre, bool,
|
||||
private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
|
||||
{
|
||||
if (this->ike_sa == old)
|
||||
{
|
||||
/* enable DNS proxy so hosts are properly resolved while the TUN device
|
||||
* is still active */
|
||||
this->lock->write_lock(this->lock);
|
||||
this->use_dns_proxy = TRUE;
|
||||
this->lock->unlock(this->lock);
|
||||
/* if DNS servers are installed that are only reachable through the VPN
|
||||
* the DNS proxy doesn't help, so uninstall DNS servers */
|
||||
if (!setup_tun_device_without_dns(this))
|
||||
{
|
||||
DBG1(DBG_DMN, "failed to setup TUN device without DNS");
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_GENERIC_ERROR);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(listener_t, ike_reestablish_post, bool,
|
||||
private_android_service_t *this, ike_sa_t *old, ike_sa_t *new,
|
||||
bool initiated)
|
||||
{
|
||||
if (this->ike_sa == old && initiated)
|
||||
{
|
||||
this->ike_sa = new;
|
||||
/* re-register hook to detect initiation failures */
|
||||
this->public.listener.ike_updown = _ike_updown;
|
||||
/* if the IKE_SA got deleted by the responder we get the child_down()
|
||||
* event on the old IKE_SA after this hook has been called, so they
|
||||
* get ignored and thus we trigger the event here */
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_CHILD_STATE_DOWN);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void add_auth_cfg_pw(private_android_service_t *this,
|
||||
peer_cfg_t *peer_cfg, bool byod)
|
||||
{
|
||||
identification_t *user;
|
||||
auth_cfg_t *auth;
|
||||
char *username, *password;
|
||||
|
||||
auth = auth_cfg_create();
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
|
||||
if (byod)
|
||||
{ /* use EAP-TTLS if BYOD is enabled */
|
||||
auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TTLS);
|
||||
}
|
||||
|
||||
username = this->settings->get_str(this->settings, "connection.username",
|
||||
NULL);
|
||||
password = this->settings->get_str(this->settings, "connection.password",
|
||||
NULL);
|
||||
user = identification_create_from_string(username);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY, user);
|
||||
|
||||
this->creds->add_username_password(this->creds, username, password);
|
||||
peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
|
||||
}
|
||||
|
||||
static bool add_auth_cfg_cert(private_android_service_t *this,
|
||||
peer_cfg_t *peer_cfg)
|
||||
{
|
||||
certificate_t *cert;
|
||||
identification_t *id;
|
||||
auth_cfg_t *auth;
|
||||
char *type;
|
||||
|
||||
cert = this->creds->load_user_certificate(this->creds);
|
||||
if (!cert)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
type = this->settings->get_str(this->settings, "connection.type", NULL);
|
||||
auth = auth_cfg_create();
|
||||
if (strpfx("ikev2-eap-tls", type))
|
||||
{
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
|
||||
auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS);
|
||||
id = identification_create_from_string("%any");
|
||||
auth->add(auth, AUTH_RULE_AAA_IDENTITY, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
}
|
||||
auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert);
|
||||
|
||||
id = cert->get_subject(cert);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id));
|
||||
peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static job_requeue_t initiate(private_android_service_t *this)
|
||||
{
|
||||
identification_t *gateway;
|
||||
ike_cfg_t *ike_cfg;
|
||||
peer_cfg_t *peer_cfg;
|
||||
child_cfg_t *child_cfg;
|
||||
traffic_selector_t *ts;
|
||||
ike_sa_t *ike_sa;
|
||||
auth_cfg_t *auth;
|
||||
lifetime_cfg_t lifetime = {
|
||||
.time = {
|
||||
.life = 3600, /* 1h */
|
||||
.rekey = 3000, /* 50min */
|
||||
.jitter = 300 /* 5min */
|
||||
}
|
||||
};
|
||||
char *type, *server;
|
||||
int port;
|
||||
|
||||
server = this->settings->get_str(this->settings, "connection.server", NULL);
|
||||
port = this->settings->get_int(this->settings, "connection.port",
|
||||
IKEV2_UDP_PORT);
|
||||
ike_cfg = ike_cfg_create(IKEV2, TRUE, TRUE, "0.0.0.0",
|
||||
charon->socket->get_port(charon->socket, FALSE),
|
||||
server, port, FRAGMENTATION_YES, 0);
|
||||
ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
|
||||
ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE));
|
||||
|
||||
peer_cfg = peer_cfg_create("android", ike_cfg, CERT_SEND_IF_ASKED,
|
||||
UNIQUE_REPLACE, 0, /* keyingtries */
|
||||
36000, 0, /* rekey 10h, reauth none */
|
||||
600, 600, /* jitter, over 10min */
|
||||
TRUE, FALSE, TRUE, /* mobike, aggressive, pull */
|
||||
0, 0, /* DPD delay, timeout */
|
||||
FALSE, NULL, NULL); /* mediation */
|
||||
peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET));
|
||||
peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6));
|
||||
|
||||
type = this->settings->get_str(this->settings, "connection.type", NULL);
|
||||
/* local auth config */
|
||||
if (streq("ikev2-cert", type) ||
|
||||
streq("ikev2-cert-eap", type) ||
|
||||
streq("ikev2-eap-tls", type))
|
||||
{
|
||||
if (!add_auth_cfg_cert(this, peer_cfg))
|
||||
{
|
||||
peer_cfg->destroy(peer_cfg);
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_GENERIC_ERROR);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
}
|
||||
if (streq("ikev2-eap", type) ||
|
||||
streq("ikev2-cert-eap", type) ||
|
||||
streq("ikev2-byod-eap", type))
|
||||
{
|
||||
add_auth_cfg_pw(this, peer_cfg, strpfx(type, "ikev2-byod"));
|
||||
}
|
||||
|
||||
/* remote auth config */
|
||||
auth = auth_cfg_create();
|
||||
gateway = identification_create_from_string(server);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY, gateway);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
|
||||
|
||||
child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL,
|
||||
ACTION_NONE, ACTION_RESTART, ACTION_RESTART,
|
||||
FALSE, 0, 0, NULL, NULL, 0);
|
||||
/* create ESP proposals with and without DH groups, let responder decide
|
||||
* if PFS is used */
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128gcm16-aes256gcm16-ecp256"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128-sha256-ecp256-modp3072"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes256-sha384-ecp521-modp8192"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128-aes192-aes256-sha1-sha256-sha384-sha512-"
|
||||
"ecp256-ecp384-ecp521-"
|
||||
"modp2048-modp3072-modp4096-modp1024"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128gcm16-aes256gcm16"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128-sha256"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes256-sha384"));
|
||||
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
|
||||
"aes128-aes192-aes256-sha1-sha256-sha384-sha512"));
|
||||
ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535);
|
||||
child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
|
||||
ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535);
|
||||
child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
|
||||
ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535);
|
||||
child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
|
||||
ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535);
|
||||
child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
|
||||
peer_cfg->add_child_cfg(peer_cfg, child_cfg);
|
||||
|
||||
/* get us an IKE_SA */
|
||||
ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager,
|
||||
peer_cfg);
|
||||
if (!ike_sa)
|
||||
{
|
||||
peer_cfg->destroy(peer_cfg);
|
||||
charonservice->update_status(charonservice,
|
||||
CHARONSERVICE_GENERIC_ERROR);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
if (!ike_sa->get_peer_cfg(ike_sa))
|
||||
{
|
||||
ike_sa->set_peer_cfg(ike_sa, peer_cfg);
|
||||
}
|
||||
peer_cfg->destroy(peer_cfg);
|
||||
|
||||
/* store the IKE_SA so we can track its progress */
|
||||
this->ike_sa = ike_sa;
|
||||
|
||||
/* get an additional reference because initiate consumes one */
|
||||
child_cfg->get_ref(child_cfg);
|
||||
if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS)
|
||||
{
|
||||
DBG1(DBG_CFG, "failed to initiate tunnel");
|
||||
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
|
||||
ike_sa);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
|
||||
METHOD(android_service_t, destroy, void,
|
||||
private_android_service_t *this)
|
||||
{
|
||||
charon->bus->remove_listener(charon->bus, &this->public.listener);
|
||||
/* make sure the tun device is actually closed */
|
||||
close_tun_device(this);
|
||||
this->dns_proxy->destroy(this->dns_proxy);
|
||||
this->lock->destroy(this->lock);
|
||||
this->settings->destroy(this->settings);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
android_service_t *android_service_create(android_creds_t *creds,
|
||||
settings_t *settings)
|
||||
{
|
||||
private_android_service_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.listener = {
|
||||
.ike_rekey = _ike_rekey,
|
||||
.ike_reestablish_pre = _ike_reestablish_pre,
|
||||
.ike_reestablish_post = _ike_reestablish_post,
|
||||
.ike_updown = _ike_updown,
|
||||
.child_updown = _child_updown,
|
||||
.alert = _alert,
|
||||
},
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
||||
.dns_proxy = android_dns_proxy_create(),
|
||||
.settings = settings,
|
||||
.creds = creds,
|
||||
.tunfd = -1,
|
||||
.mtu = settings->get_int(settings, "global.mtu", ANDROID_DEFAULT_MTU),
|
||||
);
|
||||
/* only allow queries for the VPN gateway */
|
||||
this->dns_proxy->add_hostname(this->dns_proxy,
|
||||
this->settings->get_str(this->settings, "connection.server", NULL));
|
||||
|
||||
charon->bus->add_listener(charon->bus, &this->public.listener);
|
||||
|
||||
lib->processor->queue_job(lib->processor,
|
||||
(job_t*)callback_job_create((callback_job_cb_t)initiate, this,
|
||||
NULL, NULL));
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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_service android_service
|
||||
* @{ @ingroup android_backend
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SERVICE_H_
|
||||
#define ANDROID_SERVICE_H_
|
||||
|
||||
#include "android_creds.h"
|
||||
|
||||
#include <library.h>
|
||||
#include <bus/listeners/listener.h>
|
||||
|
||||
typedef struct android_service_t android_service_t;
|
||||
|
||||
/**
|
||||
* Service that sets up an IKE_SA/CHILD_SA and handles events
|
||||
*/
|
||||
struct android_service_t {
|
||||
|
||||
/**
|
||||
* Implements listener_t.
|
||||
*/
|
||||
listener_t listener;
|
||||
|
||||
/**
|
||||
* Destroy a android_service_t.
|
||||
*/
|
||||
void (*destroy)(android_service_t *this);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an Android service instance. Queues a job that starts initiation of a
|
||||
* new IKE SA.
|
||||
*
|
||||
* @param creds Android specific credential set
|
||||
* @param settings configuration settings (gets adopted)
|
||||
*/
|
||||
android_service_t *android_service_create(android_creds_t *creds,
|
||||
settings_t *settings);
|
||||
|
||||
#endif /** ANDROID_SERVICE_H_ @}*/
|
||||
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Christoph Buehler
|
||||
* Copyright (C) 2012 Patrick Loetscher
|
||||
* Copyright (C) 2011-2012 Andreas Steffen
|
||||
* 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 "imc_android_state.h"
|
||||
#include "../android_jni.h"
|
||||
#include "../charonservice.h"
|
||||
|
||||
#include <tnc/tnc.h>
|
||||
#include <imcv.h>
|
||||
#include <imc/imc_agent.h>
|
||||
#include <imc/imc_msg.h>
|
||||
#include <pa_tnc/pa_tnc_msg.h>
|
||||
#include <ietf/ietf_attr.h>
|
||||
#include <ietf/ietf_attr_attr_request.h>
|
||||
#include <ietf/ietf_attr_installed_packages.h>
|
||||
#include <ietf/ietf_attr_pa_tnc_error.h>
|
||||
#include <ietf/ietf_attr_product_info.h>
|
||||
#include <ietf/ietf_attr_remediation_instr.h>
|
||||
#include <ietf/ietf_attr_string_version.h>
|
||||
#include <ita/ita_attr.h>
|
||||
#include <ita/ita_attr_get_settings.h>
|
||||
#include <tcg/pts/tcg_pts_attr_file_meas.h>
|
||||
#include <tcg/pts/tcg_pts_attr_meas_algo.h>
|
||||
#include <tcg/pts/tcg_pts_attr_proto_caps.h>
|
||||
#include <tcg/pts/tcg_pts_attr_req_file_meas.h>
|
||||
#include <os_info/os_info.h>
|
||||
|
||||
#include <tncif_pa_subtypes.h>
|
||||
|
||||
#include <pen/pen.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* IMC definitions */
|
||||
|
||||
static const char imc_name[] = "Android";
|
||||
|
||||
static pen_type_t msg_types[] = {
|
||||
{ PEN_IETF, PA_SUBTYPE_IETF_OPERATING_SYSTEM },
|
||||
{ PEN_IETF, PA_SUBTYPE_IETF_VPN },
|
||||
{ PEN_TCG, PA_SUBTYPE_TCG_PTS },
|
||||
};
|
||||
|
||||
static imc_agent_t *imc_android;
|
||||
|
||||
/**
|
||||
* AndroidImc object accessed via JNI
|
||||
*/
|
||||
static jobject android_imc;
|
||||
|
||||
/**
|
||||
* AndroidImc class object
|
||||
*/
|
||||
static jclass android_imc_cls;
|
||||
|
||||
/**
|
||||
* see section 3.8.1 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_initialize(TNC_IMCID imc_id,
|
||||
TNC_Version min_version,
|
||||
TNC_Version max_version,
|
||||
TNC_Version *actual_version)
|
||||
{
|
||||
if (imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has already been initialized", imc_name);
|
||||
return TNC_RESULT_ALREADY_INITIALIZED;
|
||||
}
|
||||
imc_android = imc_agent_create(imc_name, msg_types, countof(msg_types),
|
||||
imc_id, actual_version);
|
||||
if (!imc_android)
|
||||
{
|
||||
return TNC_RESULT_FATAL;
|
||||
}
|
||||
|
||||
if (min_version > TNC_IFIMC_VERSION_1 || max_version < TNC_IFIMC_VERSION_1)
|
||||
{
|
||||
DBG1(DBG_IMC, "no common IF-IMC version");
|
||||
return TNC_RESULT_NO_COMMON_VERSION;
|
||||
}
|
||||
return TNC_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state in the GUI.
|
||||
*/
|
||||
static void update_imc_state(TNC_ConnectionState state)
|
||||
{
|
||||
android_imc_state_t imc_state = ANDROID_IMC_STATE_UNKNOWN;
|
||||
|
||||
switch (state)
|
||||
{ /* map connection states to the values used by the GUI */
|
||||
case TNC_CONNECTION_STATE_ACCESS_ALLOWED:
|
||||
imc_state = ANDROID_IMC_STATE_ALLOW;
|
||||
break;
|
||||
case TNC_CONNECTION_STATE_ACCESS_ISOLATED:
|
||||
imc_state = ANDROID_IMC_STATE_ISOLATE;
|
||||
break;
|
||||
case TNC_CONNECTION_STATE_ACCESS_NONE:
|
||||
imc_state = ANDROID_IMC_STATE_BLOCK;
|
||||
break;
|
||||
}
|
||||
|
||||
charonservice->update_imc_state(charonservice, imc_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.2 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_notifyconnectionchange(TNC_IMCID imc_id,
|
||||
TNC_ConnectionID connection_id,
|
||||
TNC_ConnectionState new_state)
|
||||
{
|
||||
imc_state_t *state;
|
||||
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
switch (new_state)
|
||||
{
|
||||
case TNC_CONNECTION_STATE_CREATE:
|
||||
state = imc_android_state_create(connection_id);
|
||||
return imc_android->create_state(imc_android, state);
|
||||
case TNC_CONNECTION_STATE_HANDSHAKE:
|
||||
if (imc_android->change_state(imc_android, connection_id, new_state,
|
||||
&state) != TNC_RESULT_SUCCESS)
|
||||
{
|
||||
return TNC_RESULT_FATAL;
|
||||
}
|
||||
state->set_result(state, imc_id,
|
||||
TNC_IMV_EVALUATION_RESULT_DONT_KNOW);
|
||||
return TNC_RESULT_SUCCESS;
|
||||
case TNC_CONNECTION_STATE_DELETE:
|
||||
return imc_android->delete_state(imc_android, connection_id);
|
||||
case TNC_CONNECTION_STATE_ACCESS_ALLOWED:
|
||||
case TNC_CONNECTION_STATE_ACCESS_ISOLATED:
|
||||
case TNC_CONNECTION_STATE_ACCESS_NONE:
|
||||
update_imc_state(new_state);
|
||||
/* fall-through */
|
||||
default:
|
||||
return imc_android->change_state(imc_android, connection_id,
|
||||
new_state, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the native C strings in the enumerator to a Java String array.
|
||||
* The given enumerator gets destroyed.
|
||||
*/
|
||||
static jobjectArray string_array_create(JNIEnv *env, enumerator_t *enumerator)
|
||||
{
|
||||
linked_list_t *list;
|
||||
jobjectArray jarray;
|
||||
jstring jstring;
|
||||
char *native;
|
||||
jclass cls;
|
||||
int i = 0;
|
||||
|
||||
cls = (*env)->FindClass(env, "java/lang/String");
|
||||
list = linked_list_create_from_enumerator(enumerator);
|
||||
jarray = (*env)->NewObjectArray(env, list->get_count(list), cls, NULL);
|
||||
if (!jarray)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
enumerator = list->create_enumerator(list);
|
||||
while (enumerator->enumerate(enumerator, (void**)&native))
|
||||
{
|
||||
jstring = (*env)->NewStringUTF(env, native);
|
||||
if (!jstring)
|
||||
{
|
||||
enumerator->destroy(enumerator);
|
||||
goto failed;
|
||||
}
|
||||
(*env)->SetObjectArrayElement(env, jarray, i++, jstring);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
list->destroy(list);
|
||||
return jarray;
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
list->destroy(list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a measurement for the given attribute type from the Android IMC.
|
||||
* NULL is returned if no measurement is available or an error occurred.
|
||||
*
|
||||
* The optional args is an enumerator over char* (gets destroyed).
|
||||
*/
|
||||
static pa_tnc_attr_t *get_measurement(pen_type_t attr_type, enumerator_t *args)
|
||||
{
|
||||
JNIEnv *env;
|
||||
pa_tnc_attr_t *attr;
|
||||
jmethodID method_id;
|
||||
jbyteArray jmeasurement;
|
||||
jobjectArray jargs = NULL;
|
||||
chunk_t data;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
if (args)
|
||||
{
|
||||
jargs = string_array_create(env, args);
|
||||
if (!jargs)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement",
|
||||
"(II[Ljava/lang/String;)[B");
|
||||
}
|
||||
else
|
||||
{
|
||||
method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement",
|
||||
"(II)[B");
|
||||
}
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jmeasurement = (*env)->CallObjectMethod(env, android_imc, method_id,
|
||||
attr_type.vendor_id, attr_type.type,
|
||||
jargs);
|
||||
if (!jmeasurement || androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
data = chunk_create((*env)->GetByteArrayElements(env, jmeasurement, NULL),
|
||||
(*env)->GetArrayLength(env, jmeasurement));
|
||||
if (!data.ptr)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
attr = imcv_pa_tnc_attributes->construct(imcv_pa_tnc_attributes,
|
||||
attr_type.vendor_id, attr_type.type, data);
|
||||
(*env)->ReleaseByteArrayElements(env, jmeasurement, data.ptr, JNI_ABORT);
|
||||
androidjni_detach_thread();
|
||||
return attr;
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the measurement for the requested attribute type with optional
|
||||
* arguments (enumerator over char*, gets destroyed).
|
||||
*/
|
||||
static void add_measurement(pen_type_t attr_type, imc_msg_t *msg,
|
||||
enumerator_t *args)
|
||||
{
|
||||
pa_tnc_attr_t *attr;
|
||||
enum_name_t *pa_attr_names;
|
||||
|
||||
attr = get_measurement(attr_type, args);
|
||||
if (attr)
|
||||
{
|
||||
msg->add_attribute(msg, attr);
|
||||
return;
|
||||
}
|
||||
pa_attr_names = imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes,
|
||||
attr_type.vendor_id);
|
||||
if (pa_attr_names)
|
||||
{
|
||||
DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type "
|
||||
"'%N/%N' 0x%06x/0x%08x", pen_names, attr_type.vendor_id,
|
||||
pa_attr_names, attr_type.type, attr_type.vendor_id, attr_type.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type '%N' "
|
||||
"0x%06x/0x%08x", pen_names, attr_type.vendor_id,
|
||||
attr_type.vendor_id, attr_type.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an IETF attribute
|
||||
*/
|
||||
static void handle_ietf_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr,
|
||||
imc_msg_t *out_msg)
|
||||
{
|
||||
if (attr_type.type == IETF_ATTR_ATTRIBUTE_REQUEST)
|
||||
{
|
||||
ietf_attr_attr_request_t *attr_cast;
|
||||
pen_type_t *entry;
|
||||
enumerator_t *enumerator;
|
||||
|
||||
attr_cast = (ietf_attr_attr_request_t*)attr;
|
||||
enumerator = attr_cast->create_enumerator(attr_cast);
|
||||
while (enumerator->enumerate(enumerator, &entry))
|
||||
{
|
||||
add_measurement(*entry, out_msg, NULL);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
}
|
||||
else if (attr_type.type == IETF_ATTR_REMEDIATION_INSTRUCTIONS)
|
||||
{
|
||||
ietf_attr_remediation_instr_t *attr_cast;
|
||||
pen_type_t param;
|
||||
chunk_t str;
|
||||
char *instr;
|
||||
|
||||
attr_cast = (ietf_attr_remediation_instr_t*)attr;
|
||||
param = attr_cast->get_parameters_type(attr_cast);
|
||||
|
||||
if (pen_type_is(param, PEN_IETF, IETF_REMEDIATION_PARAMETERS_STRING))
|
||||
{
|
||||
str = attr_cast->get_string(attr_cast, NULL);
|
||||
instr = strndup(str.ptr, str.len);
|
||||
charonservice->add_remediation_instr(charonservice, instr);
|
||||
free (instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an ITA attribute
|
||||
*/
|
||||
static void handle_ita_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr,
|
||||
imc_msg_t *out_msg)
|
||||
{
|
||||
if (attr_type.type == ITA_ATTR_GET_SETTINGS)
|
||||
{
|
||||
ita_attr_get_settings_t *attr_cast;
|
||||
|
||||
attr_cast = (ita_attr_get_settings_t*)attr;
|
||||
add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_SETTINGS },
|
||||
out_msg, attr_cast->create_enumerator(attr_cast));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a TCG attribute
|
||||
*/
|
||||
static void handle_tcg_attribute(imc_android_state_t *state,
|
||||
pen_type_t attr_type, pa_tnc_attr_t *attr,
|
||||
imc_msg_t *out_msg)
|
||||
{
|
||||
pts_t *pts;
|
||||
|
||||
pts = state->get_pts(state);
|
||||
switch (attr_type.type)
|
||||
{
|
||||
case TCG_PTS_REQ_PROTO_CAPS:
|
||||
{
|
||||
tcg_pts_attr_proto_caps_t *attr_cast;
|
||||
pts_proto_caps_flag_t caps;
|
||||
|
||||
attr_cast = (tcg_pts_attr_proto_caps_t*)attr;
|
||||
caps = attr_cast->get_flags(attr_cast) & pts->get_proto_caps(pts);
|
||||
pts->set_proto_caps(pts, caps);
|
||||
attr = tcg_pts_attr_proto_caps_create(caps, FALSE);
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
case TCG_PTS_MEAS_ALGO:
|
||||
{
|
||||
tcg_pts_attr_meas_algo_t *attr_cast;
|
||||
pts_meas_algorithms_t supported, algo;
|
||||
|
||||
if (!pts_meas_algo_probe(&supported))
|
||||
{
|
||||
attr = pts_hash_alg_error_create(PTS_MEAS_ALGO_NONE);
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
attr_cast = (tcg_pts_attr_meas_algo_t*)attr;
|
||||
algo = pts_meas_algo_select(supported,
|
||||
attr_cast->get_algorithms(attr_cast));
|
||||
if (algo == PTS_MEAS_ALGO_NONE)
|
||||
{
|
||||
attr = pts_hash_alg_error_create(supported);
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
pts->set_meas_algorithm(pts, algo);
|
||||
attr = tcg_pts_attr_meas_algo_create(algo, TRUE);
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
case TCG_PTS_REQ_FILE_MEAS:
|
||||
{
|
||||
tcg_pts_attr_req_file_meas_t *attr_cast;
|
||||
pts_file_meas_t *measurements;
|
||||
pts_error_code_t pts_error;
|
||||
u_int32_t delim;
|
||||
u_int16_t req_id;
|
||||
bool is_dir;
|
||||
char *path;
|
||||
|
||||
attr_cast = (tcg_pts_attr_req_file_meas_t*)attr;
|
||||
path = attr_cast->get_pathname(attr_cast);
|
||||
if (!pts->is_path_valid(pts, path, &pts_error))
|
||||
{ /* silently ignore internal errors */
|
||||
break;
|
||||
}
|
||||
else if (pts_error)
|
||||
{
|
||||
attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG,
|
||||
pts_error), attr->get_value(attr));
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
delim = attr_cast->get_delimiter(attr_cast);
|
||||
if (delim != SOLIDUS_UTF && delim != REVERSE_SOLIDUS_UTF)
|
||||
{
|
||||
attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG,
|
||||
TCG_PTS_INVALID_DELIMITER), attr->get_value(attr));
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
req_id = attr_cast->get_request_id(attr_cast);
|
||||
is_dir = attr_cast->get_directory_flag(attr_cast);
|
||||
|
||||
DBG1(DBG_IMC, "measurement request %d for %s '%s'", req_id,
|
||||
is_dir ? "directory" : "file", path);
|
||||
measurements = pts_file_meas_create_from_path(req_id, path, is_dir,
|
||||
TRUE, pts->get_meas_algorithm(pts));
|
||||
if (!measurements)
|
||||
{
|
||||
attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG,
|
||||
TCG_PTS_FILE_NOT_FOUND), attr->get_value(attr));
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
attr = tcg_pts_attr_file_meas_create(measurements);
|
||||
attr->set_noskip_flag(attr, TRUE);
|
||||
out_msg->add_attribute(out_msg, attr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DBG1(DBG_IMC, "received unsupported TCG attribute '%N'",
|
||||
tcg_attr_names, attr_type.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.3 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_beginhandshake(TNC_IMCID imc_id,
|
||||
TNC_ConnectionID connection_id)
|
||||
{
|
||||
imc_state_t *state;
|
||||
imc_msg_t *out_msg;
|
||||
TNC_Result result = TNC_RESULT_SUCCESS;
|
||||
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
if (!imc_android->get_state(imc_android, connection_id, &state))
|
||||
{
|
||||
return TNC_RESULT_FATAL;
|
||||
}
|
||||
if (lib->settings->get_bool(lib->settings,
|
||||
"android.imc.send_os_info", TRUE))
|
||||
{
|
||||
out_msg = imc_msg_create(imc_android, state, connection_id, imc_id,
|
||||
TNC_IMVID_ANY, msg_types[0]);
|
||||
add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_PRODUCT_INFORMATION },
|
||||
out_msg, NULL);
|
||||
add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_STRING_VERSION },
|
||||
out_msg, NULL);
|
||||
add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_DEVICE_ID },
|
||||
out_msg, NULL);
|
||||
/* send PA-TNC message with the excl flag not set */
|
||||
result = out_msg->send(out_msg, FALSE);
|
||||
out_msg->destroy(out_msg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static TNC_Result receive_message(imc_android_state_t *state, imc_msg_t *in_msg)
|
||||
{
|
||||
imc_msg_t *out_msg;
|
||||
enumerator_t *enumerator;
|
||||
pa_tnc_attr_t *attr;
|
||||
pen_type_t attr_type;
|
||||
TNC_Result result;
|
||||
bool fatal_error = FALSE;
|
||||
|
||||
out_msg = imc_msg_create_as_reply(in_msg);
|
||||
|
||||
/* parse received PA-TNC message and handle local and remote errors */
|
||||
result = in_msg->receive(in_msg, out_msg, &fatal_error);
|
||||
if (result != TNC_RESULT_SUCCESS)
|
||||
{
|
||||
out_msg->destroy(out_msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* analyze PA-TNC attributes */
|
||||
enumerator = in_msg->create_attribute_enumerator(in_msg);
|
||||
while (enumerator->enumerate(enumerator, &attr))
|
||||
{
|
||||
attr_type = attr->get_type(attr);
|
||||
|
||||
switch (attr_type.vendor_id)
|
||||
{
|
||||
case PEN_IETF:
|
||||
handle_ietf_attribute(attr_type, attr, out_msg);
|
||||
continue;
|
||||
case PEN_ITA:
|
||||
handle_ita_attribute(attr_type, attr, out_msg);
|
||||
continue;
|
||||
case PEN_TCG:
|
||||
handle_tcg_attribute(state, attr_type, attr, out_msg);
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
if (fatal_error)
|
||||
{
|
||||
result = TNC_RESULT_FATAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = out_msg->send(out_msg, TRUE);
|
||||
}
|
||||
out_msg->destroy(out_msg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.4 of TCG TNC IF-IMC Specification 1.3
|
||||
|
||||
*/
|
||||
static TNC_Result tnc_imc_receivemessage(TNC_IMCID imc_id,
|
||||
TNC_ConnectionID connection_id,
|
||||
TNC_BufferReference msg,
|
||||
TNC_UInt32 msg_len,
|
||||
TNC_MessageType msg_type)
|
||||
{
|
||||
imc_state_t *state;
|
||||
imc_msg_t *in_msg;
|
||||
TNC_Result result;
|
||||
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
if (!imc_android->get_state(imc_android, connection_id, &state))
|
||||
{
|
||||
return TNC_RESULT_FATAL;
|
||||
}
|
||||
in_msg = imc_msg_create_from_data(imc_android, state, connection_id,
|
||||
msg_type, chunk_create(msg, msg_len));
|
||||
result = receive_message((imc_android_state_t*)state, in_msg);
|
||||
in_msg->destroy(in_msg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.6 of TCG TNC IF-IMV Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_receivemessagelong(TNC_IMCID imc_id,
|
||||
TNC_ConnectionID connection_id,
|
||||
TNC_UInt32 msg_flags,
|
||||
TNC_BufferReference msg,
|
||||
TNC_UInt32 msg_len,
|
||||
TNC_VendorID msg_vid,
|
||||
TNC_MessageSubtype msg_subtype,
|
||||
TNC_UInt32 src_imv_id,
|
||||
TNC_UInt32 dst_imc_id)
|
||||
{
|
||||
imc_state_t *state;
|
||||
imc_msg_t *in_msg;
|
||||
TNC_Result result;
|
||||
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
if (!imc_android->get_state(imc_android, connection_id, &state))
|
||||
{
|
||||
return TNC_RESULT_FATAL;
|
||||
}
|
||||
in_msg = imc_msg_create_from_long_data(imc_android, state, connection_id,
|
||||
src_imv_id, dst_imc_id,msg_vid, msg_subtype,
|
||||
chunk_create(msg, msg_len));
|
||||
result = receive_message((imc_android_state_t*)state, in_msg);
|
||||
in_msg->destroy(in_msg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.7 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_batchending(TNC_IMCID imc_id,
|
||||
TNC_ConnectionID connection_id)
|
||||
{
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
return TNC_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 3.8.8 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_terminate(TNC_IMCID imc_id)
|
||||
{
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
imc_android->destroy(imc_android);
|
||||
imc_android = NULL;
|
||||
return TNC_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* see section 4.2.8.1 of TCG TNC IF-IMC Specification 1.3
|
||||
*/
|
||||
static TNC_Result tnc_imc_providebindfunction(TNC_IMCID imc_id,
|
||||
TNC_TNCC_BindFunctionPointer bind_function)
|
||||
{
|
||||
if (!imc_android)
|
||||
{
|
||||
DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name);
|
||||
return TNC_RESULT_NOT_INITIALIZED;
|
||||
}
|
||||
return imc_android->bind_functions(imc_android, bind_function);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature,
|
||||
bool reg, void *data)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jobject obj, context = (jobject)data;
|
||||
jclass cls;
|
||||
bool success = TRUE;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
if (reg)
|
||||
{
|
||||
cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/imc/AndroidImc");
|
||||
if (!cls)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
android_imc_cls = (*env)->NewGlobalRef(env, cls);
|
||||
method_id = (*env)->GetMethodID(env, cls, "<init>",
|
||||
"(Landroid/content/Context;)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
obj = (*env)->NewObject(env, cls, method_id, context);
|
||||
if (!obj)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
android_imc = (*env)->NewGlobalRef(env, obj);
|
||||
androidjni_detach_thread();
|
||||
|
||||
if (tnc->imcs->load_from_functions(tnc->imcs, "Android",
|
||||
tnc_imc_initialize, tnc_imc_notifyconnectionchange,
|
||||
tnc_imc_beginhandshake, tnc_imc_receivemessage,
|
||||
tnc_imc_receivemessagelong, tnc_imc_batchending,
|
||||
tnc_imc_terminate, tnc_imc_providebindfunction))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
failed:
|
||||
DBG1(DBG_IMC, "initialization of Android IMC failed");
|
||||
androidjni_exception_occurred(env);
|
||||
success = FALSE;
|
||||
}
|
||||
|
||||
if (android_imc)
|
||||
{
|
||||
(*env)->DeleteGlobalRef(env, android_imc);
|
||||
android_imc = NULL;
|
||||
}
|
||||
if (android_imc_cls)
|
||||
{
|
||||
(*env)->DeleteGlobalRef(env, android_imc_cls);
|
||||
android_imc_cls = NULL;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_imc android_imc
|
||||
* @{ @ingroup android_byod
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_IMC_H_
|
||||
#define ANDROID_IMC_H_
|
||||
|
||||
/**
|
||||
* Callback for the Android IMC plugin
|
||||
*/
|
||||
bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature,
|
||||
bool reg, void *data);
|
||||
|
||||
#endif /** ANDROID_IMC_H_ @}*/
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Andreas Steffen
|
||||
* 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 "imc_android_state.h"
|
||||
|
||||
#include <tncif_names.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
typedef struct private_imc_android_state_t private_imc_android_state_t;
|
||||
|
||||
/**
|
||||
* Private data of an imc_state_t object.
|
||||
*/
|
||||
struct private_imc_android_state_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
*/
|
||||
imc_android_state_t public;
|
||||
|
||||
/**
|
||||
* TNCCS connection ID
|
||||
*/
|
||||
TNC_ConnectionID connection_id;
|
||||
|
||||
/**
|
||||
* TNCCS connection state
|
||||
*/
|
||||
TNC_ConnectionState state;
|
||||
|
||||
/**
|
||||
* Assessment/Evaluation Result
|
||||
*/
|
||||
TNC_IMV_Evaluation_Result result;
|
||||
|
||||
/**
|
||||
* Does the TNCCS connection support long message types?
|
||||
*/
|
||||
bool has_long;
|
||||
|
||||
/**
|
||||
* Does the TNCCS connection support exclusive delivery?
|
||||
*/
|
||||
bool has_excl;
|
||||
|
||||
/**
|
||||
* Maximum PA-TNC message size for this TNCCS connection
|
||||
*/
|
||||
u_int32_t max_msg_len;
|
||||
|
||||
/**
|
||||
* PA-TNC attribute segmentation contracts associated with TNCCS connection
|
||||
*/
|
||||
seg_contract_manager_t *contracts;
|
||||
|
||||
/**
|
||||
* TCG Platform Trust Service (PTS)
|
||||
*/
|
||||
pts_t *pts;
|
||||
};
|
||||
|
||||
METHOD(imc_state_t, get_connection_id, TNC_ConnectionID,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->connection_id;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, has_long, bool,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->has_long;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, has_excl, bool,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->has_excl;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, set_flags, void,
|
||||
private_imc_android_state_t *this, bool has_long, bool has_excl)
|
||||
{
|
||||
this->has_long = has_long;
|
||||
this->has_excl = has_excl;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, set_max_msg_len, void,
|
||||
private_imc_android_state_t *this, u_int32_t max_msg_len)
|
||||
{
|
||||
this->max_msg_len = max_msg_len;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, get_max_msg_len, u_int32_t,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->max_msg_len;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, get_contracts, seg_contract_manager_t*,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->contracts;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, change_state, void,
|
||||
private_imc_android_state_t *this, TNC_ConnectionState new_state)
|
||||
{
|
||||
this->state = new_state;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, set_result, void,
|
||||
private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result result)
|
||||
{
|
||||
this->result = result;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, get_result, bool,
|
||||
private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result *result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
*result = this->result;
|
||||
}
|
||||
return this->result != TNC_IMV_EVALUATION_RESULT_DONT_KNOW;
|
||||
}
|
||||
|
||||
METHOD(imc_state_t, destroy, void,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
this->contracts->destroy(this->contracts);
|
||||
this->pts->destroy(this->pts);
|
||||
free(this);
|
||||
}
|
||||
|
||||
METHOD(imc_android_state_t, get_pts, pts_t*,
|
||||
private_imc_android_state_t *this)
|
||||
{
|
||||
return this->pts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header.
|
||||
*/
|
||||
imc_state_t *imc_android_state_create(TNC_ConnectionID connection_id)
|
||||
{
|
||||
private_imc_android_state_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.interface = {
|
||||
.get_connection_id = _get_connection_id,
|
||||
.has_long = _has_long,
|
||||
.has_excl = _has_excl,
|
||||
.set_flags = _set_flags,
|
||||
.set_max_msg_len = _set_max_msg_len,
|
||||
.get_max_msg_len = _get_max_msg_len,
|
||||
.get_contracts = _get_contracts,
|
||||
.change_state = _change_state,
|
||||
.set_result = _set_result,
|
||||
.get_result = _get_result,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.get_pts = _get_pts,
|
||||
},
|
||||
.state = TNC_CONNECTION_STATE_CREATE,
|
||||
.result = TNC_IMV_EVALUATION_RESULT_DONT_KNOW,
|
||||
.connection_id = connection_id,
|
||||
.contracts = seg_contract_manager_create(),
|
||||
.pts = pts_create(TRUE),
|
||||
);
|
||||
|
||||
return &this->public.interface;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 imc_android_state imc_android_state
|
||||
* @{ @ingroup android_byod
|
||||
*/
|
||||
|
||||
#ifndef IMC_ANDROID_STATE_H_
|
||||
#define IMC_ANDROID_STATE_H_
|
||||
|
||||
#include <imc/imc_state.h>
|
||||
#include <pts/pts.h>
|
||||
|
||||
typedef struct imc_android_state_t imc_android_state_t;
|
||||
|
||||
/**
|
||||
* Internal state of an imc_android_t connection instance
|
||||
*/
|
||||
struct imc_android_state_t {
|
||||
|
||||
/**
|
||||
* imc_state_t interface
|
||||
*/
|
||||
imc_state_t interface;
|
||||
|
||||
/**
|
||||
* Get TCG Platform Trust Service (PTS) object
|
||||
*/
|
||||
pts_t *(*get_pts)(imc_android_state_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an imc_android_state_t instance
|
||||
*
|
||||
* @param id connection ID
|
||||
*/
|
||||
imc_state_t* imc_android_state_create(TNC_ConnectionID id);
|
||||
|
||||
#endif /** IMC_ANDROID_STATE_H_ @}*/
|
||||
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "charonservice.h"
|
||||
#include "android_jni.h"
|
||||
#include "backend/android_attr.h"
|
||||
#include "backend/android_creds.h"
|
||||
#include "backend/android_private_key.h"
|
||||
#include "backend/android_service.h"
|
||||
#include "kernel/android_ipsec.h"
|
||||
#include "kernel/android_net.h"
|
||||
|
||||
#ifdef USE_BYOD
|
||||
#include "byod/imc_android.h"
|
||||
#endif
|
||||
|
||||
#include <daemon.h>
|
||||
#include <hydra.h>
|
||||
#include <ipsec.h>
|
||||
#include <library.h>
|
||||
#include <threading/thread.h>
|
||||
|
||||
#define ANDROID_DEBUG_LEVEL 1
|
||||
#define ANDROID_RETRASNMIT_TRIES 3
|
||||
#define ANDROID_RETRANSMIT_TIMEOUT 2.0
|
||||
#define ANDROID_RETRANSMIT_BASE 1.4
|
||||
|
||||
typedef struct private_charonservice_t private_charonservice_t;
|
||||
|
||||
/**
|
||||
* private data of charonservice
|
||||
*/
|
||||
struct private_charonservice_t {
|
||||
|
||||
/**
|
||||
* public interface
|
||||
*/
|
||||
charonservice_t public;
|
||||
|
||||
/**
|
||||
* android_attr instance
|
||||
*/
|
||||
android_attr_t *attr;
|
||||
|
||||
/**
|
||||
* android_creds instance
|
||||
*/
|
||||
android_creds_t *creds;
|
||||
|
||||
/**
|
||||
* android_service instance
|
||||
*/
|
||||
android_service_t *service;
|
||||
|
||||
/**
|
||||
* VpnService builder (accessed via JNI)
|
||||
*/
|
||||
vpnservice_builder_t *builder;
|
||||
|
||||
/**
|
||||
* NetworkManager instance (accessed via JNI)
|
||||
*/
|
||||
network_manager_t *network_manager;
|
||||
|
||||
/**
|
||||
* CharonVpnService reference
|
||||
*/
|
||||
jobject vpn_service;
|
||||
|
||||
/**
|
||||
* Sockets that were bypassed and we keep track for
|
||||
*/
|
||||
linked_list_t *sockets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single instance of charonservice_t.
|
||||
*/
|
||||
charonservice_t *charonservice;
|
||||
|
||||
/**
|
||||
* hook in library for debugging messages
|
||||
*/
|
||||
extern void (*dbg)(debug_t group, level_t level, char *fmt, ...);
|
||||
|
||||
/**
|
||||
* Logging hook for library logs, using android specific logging
|
||||
*/
|
||||
static void dbg_android(debug_t group, level_t level, char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
if (level <= ANDROID_DEBUG_LEVEL)
|
||||
{
|
||||
char sgroup[16], buffer[8192];
|
||||
char *current = buffer, *next;
|
||||
|
||||
snprintf(sgroup, sizeof(sgroup), "%N", debug_names, group);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
while (current)
|
||||
{ /* log each line separately */
|
||||
next = strchr(current, '\n');
|
||||
if (next)
|
||||
{
|
||||
*(next++) = '\0';
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "charon", "00[%s] %s\n",
|
||||
sgroup, current);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, update_status, bool,
|
||||
private_charonservice_t *this, android_vpn_state_t code)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
bool success = FALSE;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||
"updateStatus", "(I)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)code);
|
||||
success = !androidjni_exception_occurred(env);
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return success;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, update_imc_state, bool,
|
||||
private_charonservice_t *this, android_imc_state_t state)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
bool success = FALSE;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||
"updateImcState", "(I)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)state);
|
||||
success = !androidjni_exception_occurred(env);
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return success;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, add_remediation_instr, bool,
|
||||
private_charonservice_t *this, char *instr)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jstring jinstr;
|
||||
bool success = FALSE;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||
"addRemediationInstruction",
|
||||
"(Ljava/lang/String;)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jinstr = (*env)->NewStringUTF(env, instr);
|
||||
if (!jinstr)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, this->vpn_service, method_id, jinstr);
|
||||
success = !androidjni_exception_occurred(env);
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypass a single socket
|
||||
*/
|
||||
static bool bypass_single_socket(intptr_t fd, private_charonservice_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_class,
|
||||
"protect", "(I)Z");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd))
|
||||
{
|
||||
DBG2(DBG_KNL, "VpnService.protect() failed");
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, bypass_socket, bool,
|
||||
private_charonservice_t *this, int fd, int family)
|
||||
{
|
||||
if (fd >= 0)
|
||||
{
|
||||
this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd);
|
||||
return bypass_single_socket((intptr_t)fd, this);
|
||||
}
|
||||
this->sockets->invoke_function(this->sockets, (void*)bypass_single_socket,
|
||||
this);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given Java array of byte arrays (byte[][]) to a linked list
|
||||
* of chunk_t objects.
|
||||
*/
|
||||
static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env,
|
||||
jobjectArray jarray)
|
||||
{
|
||||
linked_list_t *list;
|
||||
jsize i;
|
||||
|
||||
list = linked_list_create();
|
||||
for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i)
|
||||
{
|
||||
chunk_t *chunk;
|
||||
jbyteArray jbytearray;
|
||||
|
||||
chunk = malloc_thing(chunk_t);
|
||||
list->insert_last(list, chunk);
|
||||
|
||||
jbytearray = (*env)->GetObjectArrayElement(env, jarray, i);
|
||||
*chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray));
|
||||
(*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr);
|
||||
(*env)->DeleteLocalRef(env, jbytearray);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
|
||||
private_charonservice_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jobjectArray jcerts;
|
||||
linked_list_t *list;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env,
|
||||
android_charonvpnservice_class,
|
||||
"getTrustedCertificates", "()[[B");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id);
|
||||
if (!jcerts || androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
list = convert_array_of_byte_arrays(env, jcerts);
|
||||
androidjni_detach_thread();
|
||||
return list;
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, get_user_certificate, linked_list_t*,
|
||||
private_charonservice_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jobjectArray jencodings;
|
||||
linked_list_t *list;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env,
|
||||
android_charonvpnservice_class,
|
||||
"getUserCertificate", "()[[B");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id);
|
||||
if (!jencodings || androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
list = convert_array_of_byte_arrays(env, jencodings);
|
||||
androidjni_detach_thread();
|
||||
return list;
|
||||
|
||||
failed:
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, get_user_key, private_key_t*,
|
||||
private_charonservice_t *this, public_key_t *pubkey)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
private_key_t *key;
|
||||
jobject jkey;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
method_id = (*env)->GetMethodID(env,
|
||||
android_charonvpnservice_class,
|
||||
"getUserKey", "()Ljava/security/PrivateKey;");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
jkey = (*env)->CallObjectMethod(env, this->vpn_service, method_id);
|
||||
if (!jkey || androidjni_exception_occurred(env))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
key = android_private_key_create(jkey, pubkey);
|
||||
androidjni_detach_thread();
|
||||
return key;
|
||||
|
||||
failed:
|
||||
DESTROY_IF(pubkey);
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*,
|
||||
private_charonservice_t *this)
|
||||
{
|
||||
return this->builder;
|
||||
}
|
||||
|
||||
METHOD(charonservice_t, get_network_manager, network_manager_t*,
|
||||
private_charonservice_t *this)
|
||||
{
|
||||
return this->network_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a new connection
|
||||
*
|
||||
* @param settings configuration settings (gets owned)
|
||||
*/
|
||||
static void initiate(settings_t *settings)
|
||||
{
|
||||
private_charonservice_t *this = (private_charonservice_t*)charonservice;
|
||||
|
||||
lib->settings->set_str(lib->settings,
|
||||
"charon.plugins.tnc-imc.preferred_language",
|
||||
settings->get_str(settings, "global.language", "en"));
|
||||
/* this is actually the size of the complete IKE/IP packet, so if the MTU
|
||||
* for the TUN devices has to be reduced to pass traffic the IKE packets
|
||||
* will be a bit smaller than necessary as there is no IPsec overhead like
|
||||
* for the tunneled traffic (but compensating that seems like overkill) */
|
||||
lib->settings->set_int(lib->settings,
|
||||
"charon.fragment_size",
|
||||
settings->get_int(settings, "global.mtu",
|
||||
ANDROID_DEFAULT_MTU));
|
||||
|
||||
this->creds->clear(this->creds);
|
||||
DESTROY_IF(this->service);
|
||||
this->service = android_service_create(this->creds, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize/deinitialize Android backend
|
||||
*/
|
||||
static bool charonservice_register(plugin_t *plugin, plugin_feature_t *feature,
|
||||
bool reg, void *data)
|
||||
{
|
||||
private_charonservice_t *this = (private_charonservice_t*)charonservice;
|
||||
if (reg)
|
||||
{
|
||||
lib->credmgr->add_set(lib->credmgr, &this->creds->set);
|
||||
charon->attributes->add_handler(charon->attributes,
|
||||
&this->attr->handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
|
||||
charon->attributes->remove_handler(charon->attributes,
|
||||
&this->attr->handler);
|
||||
if (this->service)
|
||||
{
|
||||
this->service->destroy(this->service);
|
||||
this->service = NULL;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set strongswan.conf options
|
||||
*/
|
||||
static void set_options(char *logfile)
|
||||
{
|
||||
lib->settings->set_int(lib->settings,
|
||||
"charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL);
|
||||
/* setup file logger */
|
||||
lib->settings->set_str(lib->settings,
|
||||
"charon.filelog.%s.time_format", "%b %e %T", logfile);
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.filelog.%s.append", FALSE, logfile);
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.filelog.%s.flush_line", TRUE, logfile);
|
||||
lib->settings->set_int(lib->settings,
|
||||
"charon.filelog.%s.default", ANDROID_DEBUG_LEVEL, logfile);
|
||||
|
||||
lib->settings->set_int(lib->settings,
|
||||
"charon.retransmit_tries", ANDROID_RETRASNMIT_TRIES);
|
||||
lib->settings->set_double(lib->settings,
|
||||
"charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT);
|
||||
lib->settings->set_double(lib->settings,
|
||||
"charon.retransmit_base", ANDROID_RETRANSMIT_BASE);
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.initiator_only", TRUE);
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.close_ike_on_child_failure", TRUE);
|
||||
/* setting the source address breaks the VpnService.protect() function which
|
||||
* uses SO_BINDTODEVICE internally. the addresses provided to the kernel as
|
||||
* auxiliary data have precedence over this option causing a routing loop if
|
||||
* the gateway is contained in the VPN routes. alternatively, providing an
|
||||
* explicit device (in addition or instead of the source address) in the
|
||||
* auxiliary data would also work, but we currently don't have that
|
||||
* information */
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.plugins.socket-default.set_source", FALSE);
|
||||
/* the Linux kernel does currently not support UDP encaspulation for IPv6
|
||||
* so lets disable IPv6 for now to avoid issues with dual-stack gateways */
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"charon.plugins.socket-default.use_ipv6", FALSE);
|
||||
|
||||
#ifdef USE_BYOD
|
||||
lib->settings->set_str(lib->settings,
|
||||
"charon.plugins.eap-tnc.protocol", "tnccs-2.0");
|
||||
lib->settings->set_int(lib->settings,
|
||||
"charon.plugins.eap-ttls.max_message_count", 0);
|
||||
lib->settings->set_bool(lib->settings,
|
||||
"android.imc.send_os_info", TRUE);
|
||||
lib->settings->set_str(lib->settings,
|
||||
"libtnccs.tnc_config", "");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the charonservice object
|
||||
*/
|
||||
static void charonservice_init(JNIEnv *env, jobject service, jobject builder,
|
||||
jboolean byod)
|
||||
{
|
||||
private_charonservice_t *this;
|
||||
static plugin_feature_t features[] = {
|
||||
PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create),
|
||||
PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"),
|
||||
PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create),
|
||||
PLUGIN_PROVIDE(CUSTOM, "kernel-net"),
|
||||
PLUGIN_CALLBACK(charonservice_register, NULL),
|
||||
PLUGIN_PROVIDE(CUSTOM, "android-backend"),
|
||||
PLUGIN_DEPENDS(CUSTOM, "libcharon"),
|
||||
};
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.update_status = _update_status,
|
||||
.update_imc_state = _update_imc_state,
|
||||
.add_remediation_instr = _add_remediation_instr,
|
||||
.bypass_socket = _bypass_socket,
|
||||
.get_trusted_certificates = _get_trusted_certificates,
|
||||
.get_user_certificate = _get_user_certificate,
|
||||
.get_user_key = _get_user_key,
|
||||
.get_vpnservice_builder = _get_vpnservice_builder,
|
||||
.get_network_manager = _get_network_manager,
|
||||
},
|
||||
.attr = android_attr_create(),
|
||||
.creds = android_creds_create(),
|
||||
.builder = vpnservice_builder_create(builder),
|
||||
.network_manager = network_manager_create(service),
|
||||
.sockets = linked_list_create(),
|
||||
.vpn_service = (*env)->NewGlobalRef(env, service),
|
||||
);
|
||||
charonservice = &this->public;
|
||||
|
||||
lib->plugins->add_static_features(lib->plugins, "androidbridge", features,
|
||||
countof(features), TRUE, NULL, NULL);
|
||||
|
||||
#ifdef USE_BYOD
|
||||
if (byod)
|
||||
{
|
||||
plugin_feature_t byod_features[] = {
|
||||
PLUGIN_CALLBACK(imc_android_register, this->vpn_service),
|
||||
PLUGIN_PROVIDE(CUSTOM, "android-imc"),
|
||||
PLUGIN_DEPENDS(CUSTOM, "android-backend"),
|
||||
PLUGIN_DEPENDS(CUSTOM, "imc-manager"),
|
||||
};
|
||||
|
||||
lib->plugins->add_static_features(lib->plugins, "android-byod",
|
||||
byod_features, countof(byod_features), TRUE, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitialize the charonservice object
|
||||
*/
|
||||
static void charonservice_deinit(JNIEnv *env)
|
||||
{
|
||||
private_charonservice_t *this = (private_charonservice_t*)charonservice;
|
||||
|
||||
this->network_manager->destroy(this->network_manager);
|
||||
this->sockets->destroy(this->sockets);
|
||||
this->builder->destroy(this->builder);
|
||||
this->creds->destroy(this->creds);
|
||||
this->attr->destroy(this->attr);
|
||||
(*env)->DeleteGlobalRef(env, this->vpn_service);
|
||||
free(this);
|
||||
charonservice = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SIGSEGV/SIGILL signals raised by threads
|
||||
*/
|
||||
static void segv_handler(int signal)
|
||||
{
|
||||
dbg_android(DBG_DMN, 1, "thread %u received %d", thread_current_id(),
|
||||
signal);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize charon and the libraries via JNI
|
||||
*/
|
||||
JNI_METHOD(CharonVpnService, initializeCharon, jboolean,
|
||||
jobject builder, jstring jlogfile, jboolean byod)
|
||||
{
|
||||
struct sigaction action;
|
||||
struct utsname utsname;
|
||||
char *logfile, *plugins;
|
||||
|
||||
/* logging for library during initialization, as we have no bus yet */
|
||||
dbg = dbg_android;
|
||||
|
||||
/* initialize library */
|
||||
if (!library_init(NULL, "charon"))
|
||||
{
|
||||
library_deinit();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set options before initializing other libraries that might read them */
|
||||
logfile = androidjni_convert_jstring(env, jlogfile);
|
||||
set_options(logfile);
|
||||
free(logfile);
|
||||
|
||||
if (!libhydra_init())
|
||||
{
|
||||
libhydra_deinit();
|
||||
library_deinit();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!libipsec_init())
|
||||
{
|
||||
libipsec_deinit();
|
||||
libhydra_deinit();
|
||||
library_deinit();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!libcharon_init())
|
||||
{
|
||||
libcharon_deinit();
|
||||
libipsec_deinit();
|
||||
libhydra_deinit();
|
||||
library_deinit();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
charon->load_loggers(charon, NULL, FALSE);
|
||||
|
||||
charonservice_init(env, this, builder, byod);
|
||||
|
||||
if (uname(&utsname) != 0)
|
||||
{
|
||||
memset(&utsname, 0, sizeof(utsname));
|
||||
}
|
||||
DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)",
|
||||
utsname.sysname, utsname.release, utsname.machine);
|
||||
|
||||
#ifdef PLUGINS_BYOD
|
||||
if (byod)
|
||||
{
|
||||
plugins = PLUGINS " " PLUGINS_BYOD;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
plugins = PLUGINS;
|
||||
}
|
||||
|
||||
if (!charon->initialize(charon, plugins))
|
||||
{
|
||||
libcharon_deinit();
|
||||
charonservice_deinit(env);
|
||||
libipsec_deinit();
|
||||
libhydra_deinit();
|
||||
library_deinit();
|
||||
return FALSE;
|
||||
}
|
||||
lib->plugins->status(lib->plugins, LEVEL_CTRL);
|
||||
|
||||
/* add handler for SEGV and ILL etc. */
|
||||
action.sa_handler = segv_handler;
|
||||
action.sa_flags = 0;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGSEGV, &action, NULL);
|
||||
sigaction(SIGILL, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &action, NULL);
|
||||
|
||||
/* start daemon (i.e. the threads in the thread-pool) */
|
||||
charon->start(charon);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitialize charon and all libraries
|
||||
*/
|
||||
JNI_METHOD(CharonVpnService, deinitializeCharon, void)
|
||||
{
|
||||
/* deinitialize charon before we destroy our own objects */
|
||||
libcharon_deinit();
|
||||
charonservice_deinit(env);
|
||||
libipsec_deinit();
|
||||
libhydra_deinit();
|
||||
library_deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate SA
|
||||
*/
|
||||
JNI_METHOD(CharonVpnService, initiate, void,
|
||||
jstring jconfig)
|
||||
{
|
||||
settings_t *settings;
|
||||
char *config;
|
||||
|
||||
config = androidjni_convert_jstring(env, jconfig);
|
||||
settings = settings_create_string(config);
|
||||
initiate(settings);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 libandroidbridge libandroidbridge
|
||||
*
|
||||
* @defgroup android_backend backend
|
||||
* @ingroup libandroidbridge
|
||||
*
|
||||
* @defgroup android_byod byod
|
||||
* @ingroup libandroidbridge
|
||||
*
|
||||
* @defgroup android_kernel kernel
|
||||
* @ingroup libandroidbridge
|
||||
*
|
||||
* @defgroup charonservice charonservice
|
||||
* @{ @ingroup libandroidbridge
|
||||
*/
|
||||
|
||||
#ifndef CHARONSERVICE_H_
|
||||
#define CHARONSERVICE_H_
|
||||
|
||||
#include "vpnservice_builder.h"
|
||||
#include "kernel/network_manager.h"
|
||||
|
||||
#include <library.h>
|
||||
#include <collections/linked_list.h>
|
||||
|
||||
typedef enum android_vpn_state_t android_vpn_state_t;
|
||||
typedef enum android_imc_state_t android_imc_state_t;
|
||||
typedef struct charonservice_t charonservice_t;
|
||||
|
||||
/**
|
||||
* Default value for the MTU of TUN device and the size of IKE fragments
|
||||
*/
|
||||
#define ANDROID_DEFAULT_MTU 1400
|
||||
|
||||
/**
|
||||
* VPN status codes. As defined in CharonVpnService.java
|
||||
*/
|
||||
enum android_vpn_state_t {
|
||||
CHARONSERVICE_CHILD_STATE_UP = 1,
|
||||
CHARONSERVICE_CHILD_STATE_DOWN,
|
||||
CHARONSERVICE_AUTH_ERROR,
|
||||
CHARONSERVICE_PEER_AUTH_ERROR,
|
||||
CHARONSERVICE_LOOKUP_ERROR,
|
||||
CHARONSERVICE_UNREACHABLE_ERROR,
|
||||
CHARONSERVICE_GENERIC_ERROR,
|
||||
};
|
||||
|
||||
/**
|
||||
* Final IMC state as defined in ImcState.java
|
||||
*/
|
||||
enum android_imc_state_t {
|
||||
ANDROID_IMC_STATE_UNKNOWN = 0,
|
||||
ANDROID_IMC_STATE_ALLOW = 1,
|
||||
ANDROID_IMC_STATE_BLOCK = 2,
|
||||
ANDROID_IMC_STATE_ISOLATE = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Public interface of charonservice.
|
||||
*
|
||||
* Used to communicate with CharonVpnService via JNI
|
||||
*/
|
||||
struct charonservice_t {
|
||||
|
||||
/**
|
||||
* Update the status in the Java domain (UI)
|
||||
*
|
||||
* @param code status code
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*update_status)(charonservice_t *this, android_vpn_state_t code);
|
||||
|
||||
/**
|
||||
* Update final IMC state in the Java domain (UI)
|
||||
*
|
||||
* @param state IMC state
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*update_imc_state)(charonservice_t *this, android_imc_state_t state);
|
||||
|
||||
/**
|
||||
* Add a remediation instruction via JNI
|
||||
*
|
||||
* @param instr remediation instruction
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*add_remediation_instr)(charonservice_t *this, char *instr);
|
||||
|
||||
/**
|
||||
* Install a bypass policy for the given socket using the protect() Method
|
||||
* of the Android VpnService interface.
|
||||
*
|
||||
* Use -1 as fd to re-bypass previously bypassed sockets.
|
||||
*
|
||||
* @param fd socket file descriptor
|
||||
* @param family socket protocol family
|
||||
* @return TRUE if operation successful
|
||||
*/
|
||||
bool (*bypass_socket)(charonservice_t *this, int fd, int family);
|
||||
|
||||
/**
|
||||
* Get a list of trusted certificates via JNI
|
||||
*
|
||||
* @return list of DER encoded certificates (as chunk_t*),
|
||||
* NULL on failure
|
||||
*/
|
||||
linked_list_t *(*get_trusted_certificates)(charonservice_t *this);
|
||||
|
||||
/**
|
||||
* Get the configured user certificate chain via JNI
|
||||
*
|
||||
* The first item in the returned list is the user certificate followed
|
||||
* by any remaining elements of the certificate chain.
|
||||
*
|
||||
* @return list of DER encoded certificates (as chunk_t*),
|
||||
* NULL on failure
|
||||
*/
|
||||
linked_list_t *(*get_user_certificate)(charonservice_t *this);
|
||||
|
||||
/**
|
||||
* Get the configured private key via JNI
|
||||
*
|
||||
* @param pubkey the public key as extracted from the certificate
|
||||
* @return PrivateKey object, NULL on failure
|
||||
*/
|
||||
private_key_t *(*get_user_key)(charonservice_t *this, public_key_t *pubkey);
|
||||
|
||||
/**
|
||||
* Get the current vpnservice_builder_t object
|
||||
*
|
||||
* @return VpnService.Builder instance
|
||||
*/
|
||||
vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this);
|
||||
|
||||
/**
|
||||
* Get the current network_manager_t object
|
||||
*
|
||||
* @return NetworkManager instance
|
||||
*/
|
||||
network_manager_t *(*get_network_manager)(charonservice_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* The single instance of charonservice_t.
|
||||
*
|
||||
* Set between JNI calls to initializeCharon() and deinitializeCharon().
|
||||
*/
|
||||
extern charonservice_t *charonservice;
|
||||
|
||||
#endif /** CHARONSERVICE_H_ @}*/
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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_ipsec.h"
|
||||
#include "../charonservice.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <library.h>
|
||||
#include <hydra.h>
|
||||
#include <ipsec.h>
|
||||
|
||||
typedef struct private_kernel_android_ipsec_t private_kernel_android_ipsec_t;
|
||||
|
||||
struct private_kernel_android_ipsec_t {
|
||||
|
||||
/**
|
||||
* Public kernel interface
|
||||
*/
|
||||
kernel_android_ipsec_t public;
|
||||
|
||||
/**
|
||||
* Listener for lifetime expire events
|
||||
*/
|
||||
ipsec_event_listener_t ipsec_listener;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback registrered with libipsec.
|
||||
*/
|
||||
static void expire(u_int8_t protocol, u_int32_t spi, host_t *dst, bool hard)
|
||||
{
|
||||
hydra->kernel_interface->expire(hydra->kernel_interface, protocol,
|
||||
spi, dst, hard);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, get_spi, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
u_int8_t protocol, u_int32_t *spi)
|
||||
{
|
||||
return ipsec->sas->get_spi(ipsec->sas, src, dst, protocol, spi);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, get_cpi, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
u_int16_t *cpi)
|
||||
{
|
||||
return NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, add_sa, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark,
|
||||
u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key,
|
||||
u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode,
|
||||
u_int16_t ipcomp, u_int16_t cpi, u_int32_t replay_window,
|
||||
bool initiator, bool encap, bool esn, bool inbound, bool update,
|
||||
linked_list_t *src_ts, linked_list_t *dst_ts)
|
||||
{
|
||||
return ipsec->sas->add_sa(ipsec->sas, src, dst, spi, protocol, reqid, mark,
|
||||
tfc, lifetime, enc_alg, enc_key, int_alg, int_key,
|
||||
mode, ipcomp, cpi, initiator, encap, esn,
|
||||
inbound, update);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, update_sa, status_t,
|
||||
private_kernel_android_ipsec_t *this, u_int32_t spi, u_int8_t protocol,
|
||||
u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst,
|
||||
bool encap, bool new_encap, mark_t mark)
|
||||
{
|
||||
return ipsec->sas->update_sa(ipsec->sas, spi, protocol, cpi, src, dst,
|
||||
new_src, new_dst, encap, new_encap, mark);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, query_sa, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
u_int32_t spi, u_int8_t protocol, mark_t mark,
|
||||
u_int64_t *bytes, u_int64_t *packets, time_t *time)
|
||||
{
|
||||
return ipsec->sas->query_sa(ipsec->sas, src, dst, spi, protocol, mark,
|
||||
bytes, packets, time);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, del_sa, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark)
|
||||
{
|
||||
return ipsec->sas->del_sa(ipsec->sas, src, dst, spi, protocol, cpi, mark);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, flush_sas, status_t,
|
||||
private_kernel_android_ipsec_t *this)
|
||||
{
|
||||
return ipsec->sas->flush_sas(ipsec->sas);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, add_policy, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
traffic_selector_t *src_ts, traffic_selector_t *dst_ts,
|
||||
policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark,
|
||||
policy_priority_t priority)
|
||||
{
|
||||
return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts,
|
||||
dst_ts, direction, type, sa, mark,
|
||||
priority);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, query_policy, status_t,
|
||||
private_kernel_android_ipsec_t *this, traffic_selector_t *src_ts,
|
||||
traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark,
|
||||
time_t *use_time)
|
||||
{
|
||||
return NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, del_policy, status_t,
|
||||
private_kernel_android_ipsec_t *this, host_t *src, host_t *dst,
|
||||
traffic_selector_t *src_ts, traffic_selector_t *dst_ts,
|
||||
policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa,
|
||||
mark_t mark, policy_priority_t priority)
|
||||
{
|
||||
return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts,
|
||||
direction, sa->reqid, mark, priority);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, flush_policies, status_t,
|
||||
private_kernel_android_ipsec_t *this)
|
||||
{
|
||||
ipsec->policies->flush_policies(ipsec->policies);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, bypass_socket, bool,
|
||||
private_kernel_android_ipsec_t *this, int fd, int family)
|
||||
{
|
||||
return charonservice->bypass_socket(charonservice, fd, family);
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, enable_udp_decap, bool,
|
||||
private_kernel_android_ipsec_t *this, int fd, int family, u_int16_t port)
|
||||
{
|
||||
return NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
METHOD(kernel_ipsec_t, destroy, void,
|
||||
private_kernel_android_ipsec_t *this)
|
||||
{
|
||||
ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header.
|
||||
*/
|
||||
kernel_android_ipsec_t *kernel_android_ipsec_create()
|
||||
{
|
||||
private_kernel_android_ipsec_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.interface = {
|
||||
.get_spi = _get_spi,
|
||||
.get_cpi = _get_cpi,
|
||||
.add_sa = _add_sa,
|
||||
.update_sa = _update_sa,
|
||||
.query_sa = _query_sa,
|
||||
.del_sa = _del_sa,
|
||||
.flush_sas = _flush_sas,
|
||||
.add_policy = _add_policy,
|
||||
.query_policy = _query_policy,
|
||||
.del_policy = _del_policy,
|
||||
.flush_policies = _flush_policies,
|
||||
.bypass_socket = _bypass_socket,
|
||||
.enable_udp_decap = _enable_udp_decap,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
},
|
||||
.ipsec_listener = {
|
||||
.expire = expire,
|
||||
},
|
||||
);
|
||||
|
||||
ipsec->events->register_listener(ipsec->events, &this->ipsec_listener);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 kernel_android_ipsec kernel_android_ipsec
|
||||
* @{ @ingroup android_kernel
|
||||
*/
|
||||
|
||||
#ifndef KERNEL_ANDROID_IPSEC_H_
|
||||
#define KERNEL_ANDROID_IPSEC_H_
|
||||
|
||||
#include <library.h>
|
||||
#include <kernel/kernel_ipsec.h>
|
||||
|
||||
typedef struct kernel_android_ipsec_t kernel_android_ipsec_t;
|
||||
|
||||
/**
|
||||
* Implementation of the ipsec interface using libipsec on Android
|
||||
*/
|
||||
struct kernel_android_ipsec_t {
|
||||
|
||||
/**
|
||||
* Implements kernel_ipsec_t interface
|
||||
*/
|
||||
kernel_ipsec_t interface;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a android ipsec interface instance.
|
||||
*
|
||||
* @return kernel_android_ipsec_t instance
|
||||
*/
|
||||
kernel_android_ipsec_t *kernel_android_ipsec_create();
|
||||
|
||||
#endif /** KERNEL_ANDROID_IPSEC_H_ @}*/
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "android_net.h"
|
||||
|
||||
#include "../android_jni.h"
|
||||
#include "../charonservice.h"
|
||||
#include <hydra.h>
|
||||
#include <processing/jobs/callback_job.h>
|
||||
#include <threading/mutex.h>
|
||||
|
||||
/** delay before firing roam events (ms) */
|
||||
#define ROAM_DELAY 100
|
||||
#define ROAM_DELAY_RECHECK 1000
|
||||
|
||||
typedef struct private_android_net_t private_android_net_t;
|
||||
|
||||
struct private_android_net_t {
|
||||
|
||||
/**
|
||||
* Public kernel interface
|
||||
*/
|
||||
kernel_net_t public;
|
||||
|
||||
/**
|
||||
* Reference to NetworkManager object
|
||||
*/
|
||||
network_manager_t *network_manager;
|
||||
|
||||
/**
|
||||
* Earliest time of the next roam event
|
||||
*/
|
||||
timeval_t next_roam;
|
||||
|
||||
/**
|
||||
* Mutex to check and update roam event time, and other private members
|
||||
*/
|
||||
mutex_t *mutex;
|
||||
|
||||
/**
|
||||
* List of virtual IPs
|
||||
*/
|
||||
linked_list_t *vips;
|
||||
|
||||
/**
|
||||
* Socket used to determine source address
|
||||
*/
|
||||
int socket_v4;
|
||||
|
||||
/**
|
||||
* Whether the device is currently connected
|
||||
*/
|
||||
bool connected;
|
||||
};
|
||||
|
||||
/**
|
||||
* callback function that raises the delayed roam event
|
||||
*/
|
||||
static job_requeue_t roam_event()
|
||||
{
|
||||
/* this will fail if no connection is up */
|
||||
charonservice->bypass_socket(charonservice, -1, 0);
|
||||
hydra->kernel_interface->roam(hydra->kernel_interface, TRUE);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for connectivity change events and queue a roam event
|
||||
*/
|
||||
static void connectivity_cb(private_android_net_t *this,
|
||||
bool disconnected)
|
||||
{
|
||||
timeval_t now;
|
||||
job_t *job;
|
||||
|
||||
time_monotonic(&now);
|
||||
this->mutex->lock(this->mutex);
|
||||
this->connected = !disconnected;
|
||||
if (!timercmp(&now, &this->next_roam, >))
|
||||
{
|
||||
this->mutex->unlock(this->mutex);
|
||||
return;
|
||||
}
|
||||
timeval_add_ms(&now, ROAM_DELAY);
|
||||
this->next_roam = now;
|
||||
this->mutex->unlock(this->mutex);
|
||||
|
||||
job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, NULL,
|
||||
NULL, NULL);
|
||||
lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY);
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, get_source_addr, host_t*,
|
||||
private_android_net_t *this, host_t *dest, host_t *src)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sockaddr;
|
||||
struct sockaddr_in sin;
|
||||
struct sockaddr_in6 sin6;
|
||||
} addr;
|
||||
socklen_t addrlen;
|
||||
timeval_t now;
|
||||
job_t *job;
|
||||
|
||||
addrlen = *dest->get_sockaddr_len(dest);
|
||||
addr.sockaddr.sa_family = AF_UNSPEC;
|
||||
if (connect(this->socket_v4, &addr.sockaddr, addrlen) < 0)
|
||||
{
|
||||
DBG1(DBG_KNL, "failed to disconnect socket: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2)
|
||||
{ /* this seems to help avoiding the VIP, unless there is no connectivity
|
||||
* at all */
|
||||
charonservice->bypass_socket(charonservice, -1, 0);
|
||||
}
|
||||
if (connect(this->socket_v4, dest->get_sockaddr(dest), addrlen) < 0)
|
||||
{
|
||||
/* don't report an error if we are not connected (ENETUNREACH) */
|
||||
if (errno != ENETUNREACH)
|
||||
{
|
||||
DBG1(DBG_KNL, "failed to connect socket: %s", strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
time_monotonic(&now);
|
||||
this->mutex->lock(this->mutex);
|
||||
if (this->connected && timercmp(&now, &this->next_roam, >))
|
||||
{ /* we were not able to find a source address but reportedly are
|
||||
* connected, trigger a recheck in case an IP address appears
|
||||
* delayed but the callback is not triggered again */
|
||||
timeval_add_ms(&now, ROAM_DELAY_RECHECK);
|
||||
this->next_roam = now;
|
||||
this->mutex->unlock(this->mutex);
|
||||
job = (job_t*)callback_job_create((callback_job_cb_t)roam_event,
|
||||
NULL, NULL, NULL);
|
||||
lib->scheduler->schedule_job_ms(lib->scheduler, job,
|
||||
ROAM_DELAY_RECHECK);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->mutex->unlock(this->mutex);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (getsockname(this->socket_v4, &addr.sockaddr, &addrlen) < 0)
|
||||
{
|
||||
DBG1(DBG_KNL, "failed to determine src address: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
return host_create_from_sockaddr((sockaddr_t*)&addr);
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, get_source_addr_old, host_t*,
|
||||
private_android_net_t *this, host_t *dest, host_t *src)
|
||||
{
|
||||
host_t *host;
|
||||
|
||||
/* on older Android versions we might get the virtual IP back because
|
||||
* the protect() implementation there and connect() don't properly work
|
||||
* together, on newer releases (using fwmarks) that's not a problem */
|
||||
host = get_source_addr(this, dest, src);
|
||||
if (host)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
if (this->vips->find_first(this->vips, (void*)host->ip_equals,
|
||||
NULL, host) == SUCCESS)
|
||||
{
|
||||
host->destroy(host);
|
||||
host = NULL;
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, get_nexthop, host_t*,
|
||||
private_android_net_t *this, host_t *dest, int prefix, host_t *src)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, get_interface, bool,
|
||||
private_android_net_t *this, host_t *host, char **name)
|
||||
{
|
||||
if (name)
|
||||
{ /* the actual name does not matter in our case */
|
||||
*name = strdup("strongswan");
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, create_address_enumerator, enumerator_t*,
|
||||
private_android_net_t *this, kernel_address_type_t which)
|
||||
{
|
||||
/* return virtual IPs if requested, nothing otherwise */
|
||||
if (which & ADDR_TYPE_VIRTUAL)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
return enumerator_create_cleaner(
|
||||
this->vips->create_enumerator(this->vips),
|
||||
(void*)this->mutex->unlock, this->mutex);
|
||||
}
|
||||
return enumerator_create_empty();
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, add_ip, status_t,
|
||||
private_android_net_t *this, host_t *virtual_ip, int prefix, char *iface)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
this->vips->insert_last(this->vips, virtual_ip->clone(virtual_ip));
|
||||
this->mutex->unlock(this->mutex);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, del_ip, status_t,
|
||||
private_android_net_t *this, host_t *virtual_ip, int prefix, bool wait)
|
||||
{
|
||||
host_t *vip;
|
||||
|
||||
this->mutex->lock(this->mutex);
|
||||
if (this->vips->find_first(this->vips, (void*)virtual_ip->ip_equals,
|
||||
(void**)&vip, virtual_ip) == SUCCESS)
|
||||
{
|
||||
this->vips->remove(this->vips, vip, NULL);
|
||||
vip->destroy(vip);
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, add_route, status_t,
|
||||
private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen,
|
||||
host_t *gateway, host_t *src_ip, char *if_name)
|
||||
{
|
||||
return NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, del_route, status_t,
|
||||
private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen,
|
||||
host_t *gateway, host_t *src_ip, char *if_name)
|
||||
{
|
||||
return NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
METHOD(kernel_net_t, destroy, void,
|
||||
private_android_net_t *this)
|
||||
{
|
||||
this->network_manager->remove_connectivity_cb(this->network_manager,
|
||||
(void*)connectivity_cb);
|
||||
this->mutex->destroy(this->mutex);
|
||||
this->vips->destroy(this->vips);
|
||||
close(this->socket_v4);
|
||||
free(this);
|
||||
}
|
||||
|
||||
kernel_net_t *kernel_android_net_create()
|
||||
{
|
||||
private_android_net_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.get_source_addr = _get_source_addr,
|
||||
.get_nexthop = _get_nexthop,
|
||||
.get_interface = _get_interface,
|
||||
.create_address_enumerator = _create_address_enumerator,
|
||||
.add_ip = _add_ip,
|
||||
.del_ip = _del_ip,
|
||||
.add_route = _add_route,
|
||||
.del_route = _del_route,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
||||
.vips = linked_list_create(),
|
||||
.network_manager = charonservice->get_network_manager(charonservice),
|
||||
);
|
||||
timerclear(&this->next_roam);
|
||||
|
||||
if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2)
|
||||
{
|
||||
this->public.get_source_addr = _get_source_addr_old;
|
||||
}
|
||||
|
||||
this->socket_v4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (this->socket_v4 < 0)
|
||||
{
|
||||
DBG1(DBG_KNL, "failed to create socket to lookup src addresses: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
charonservice->bypass_socket(charonservice, this->socket_v4, AF_INET);
|
||||
|
||||
this->mutex->lock(this->mutex);
|
||||
this->network_manager->add_connectivity_cb(
|
||||
this->network_manager, (void*)connectivity_cb, this);
|
||||
this->connected = this->network_manager->is_connected(this->network_manager);
|
||||
this->mutex->unlock(this->mutex);
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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_net android_net
|
||||
* @{ @ingroup android_kernel
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_NET_H_
|
||||
#define ANDROID_NET_H_
|
||||
|
||||
#include <library.h>
|
||||
#include <kernel/kernel_net.h>
|
||||
|
||||
/**
|
||||
* Create an Android-specific kernel_net_t instance.
|
||||
*
|
||||
* @return kernel_net_t instance
|
||||
*/
|
||||
kernel_net_t *kernel_android_net_create();
|
||||
|
||||
|
||||
#endif /** ANDROID_NET_H_ @}*/
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 "network_manager.h"
|
||||
|
||||
#include "../android_jni.h"
|
||||
#include "../charonservice.h"
|
||||
#include <utils/debug.h>
|
||||
#include <threading/mutex.h>
|
||||
|
||||
typedef struct private_network_manager_t private_network_manager_t;
|
||||
|
||||
struct private_network_manager_t {
|
||||
|
||||
/**
|
||||
* Public interface
|
||||
*/
|
||||
network_manager_t public;
|
||||
|
||||
/**
|
||||
* Reference to NetworkManager object
|
||||
*/
|
||||
jobject obj;
|
||||
|
||||
/**
|
||||
* Java class for NetworkManager
|
||||
*/
|
||||
jclass cls;
|
||||
|
||||
/**
|
||||
* Registered callback
|
||||
*/
|
||||
struct {
|
||||
connectivity_cb_t cb;
|
||||
void *data;
|
||||
} connectivity_cb;
|
||||
|
||||
/**
|
||||
* Mutex to access callback
|
||||
*/
|
||||
mutex_t *mutex;
|
||||
};
|
||||
|
||||
JNI_METHOD(NetworkManager, networkChanged, void,
|
||||
bool disconnected)
|
||||
{
|
||||
private_network_manager_t *nm;
|
||||
|
||||
nm = (private_network_manager_t*)charonservice->get_network_manager(
|
||||
charonservice);
|
||||
nm->mutex->lock(nm->mutex);
|
||||
if (nm->connectivity_cb.cb)
|
||||
{
|
||||
nm->connectivity_cb.cb(nm->connectivity_cb.data, disconnected);
|
||||
}
|
||||
nm->mutex->unlock(nm->mutex);
|
||||
}
|
||||
|
||||
METHOD(network_manager_t, add_connectivity_cb, void,
|
||||
private_network_manager_t *this, connectivity_cb_t cb, void *data)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
if (!this->connectivity_cb.cb)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
method_id = (*env)->GetMethodID(env, this->cls, "Register", "()V");
|
||||
if (!method_id)
|
||||
{
|
||||
androidjni_exception_occurred(env);
|
||||
}
|
||||
else
|
||||
{
|
||||
(*env)->CallVoidMethod(env, this->obj, method_id);
|
||||
if (!androidjni_exception_occurred(env))
|
||||
{
|
||||
this->connectivity_cb.cb = cb;
|
||||
this->connectivity_cb.data = data;
|
||||
}
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the NetworkManager via JNI.
|
||||
*
|
||||
* this->mutex has to be locked
|
||||
*/
|
||||
static void unregister_network_manager(private_network_manager_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
method_id = (*env)->GetMethodID(env, this->cls, "Unregister", "()V");
|
||||
if (!method_id)
|
||||
{
|
||||
androidjni_exception_occurred(env);
|
||||
}
|
||||
else
|
||||
{
|
||||
(*env)->CallVoidMethod(env, this->obj, method_id);
|
||||
androidjni_exception_occurred(env);
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
}
|
||||
|
||||
METHOD(network_manager_t, remove_connectivity_cb, void,
|
||||
private_network_manager_t *this, connectivity_cb_t cb)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
if (this->connectivity_cb.cb == cb)
|
||||
{
|
||||
this->connectivity_cb.cb = NULL;
|
||||
unregister_network_manager(this);
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
}
|
||||
|
||||
METHOD(network_manager_t, is_connected, bool,
|
||||
private_network_manager_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
bool connected = FALSE;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
method_id = (*env)->GetMethodID(env, this->cls, "isConnected", "()Z");
|
||||
if (!method_id)
|
||||
{
|
||||
androidjni_exception_occurred(env);
|
||||
}
|
||||
else
|
||||
{
|
||||
connected = (*env)->CallBooleanMethod(env, this->obj, method_id);
|
||||
connected = !androidjni_exception_occurred(env) && connected;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return connected;
|
||||
}
|
||||
|
||||
METHOD(network_manager_t, destroy, void,
|
||||
private_network_manager_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
|
||||
this->mutex->lock(this->mutex);
|
||||
if (this->connectivity_cb.cb)
|
||||
{
|
||||
this->connectivity_cb.cb = NULL;
|
||||
unregister_network_manager(this);
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
if (this->obj)
|
||||
{
|
||||
(*env)->DeleteGlobalRef(env, this->obj);
|
||||
}
|
||||
if (this->cls)
|
||||
{
|
||||
(*env)->DeleteGlobalRef(env, this->cls);
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
this->mutex->destroy(this->mutex);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header.
|
||||
*/
|
||||
network_manager_t *network_manager_create(jobject context)
|
||||
{
|
||||
private_network_manager_t *this;
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jobject obj;
|
||||
jclass cls;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.add_connectivity_cb = _add_connectivity_cb,
|
||||
.remove_connectivity_cb = _remove_connectivity_cb,
|
||||
.is_connected = _is_connected,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
||||
);
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/NetworkManager");
|
||||
if (!cls)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
this->cls = (*env)->NewGlobalRef(env, cls);
|
||||
method_id = (*env)->GetMethodID(env, cls, "<init>",
|
||||
"(Landroid/content/Context;)V");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
obj = (*env)->NewObject(env, cls, method_id, context);
|
||||
if (!obj)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
this->obj = (*env)->NewGlobalRef(env, obj);
|
||||
androidjni_detach_thread();
|
||||
return &this->public;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_KNL, "failed to build NetworkManager object");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
destroy(this);
|
||||
return NULL;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <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 network_manager network_manager
|
||||
* @{ @ingroup android_kernel
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_MANAGER_H_
|
||||
#define NETWORK_MANAGER_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <library.h>
|
||||
#include <networking/host.h>
|
||||
|
||||
typedef struct network_manager_t network_manager_t;
|
||||
|
||||
/**
|
||||
* Callback called if connectivity changes somehow.
|
||||
*
|
||||
* Implementation should be quick as the call is made by the Java apps main
|
||||
* thread.
|
||||
*
|
||||
* @param data data supplied during registration
|
||||
* @param disconnected TRUE if currently disconnected
|
||||
*/
|
||||
typedef void (*connectivity_cb_t)(void *data, bool disconnected);
|
||||
|
||||
/**
|
||||
* NetworkManager, used to listen for network changes.
|
||||
*
|
||||
* Communicates with NetworkManager via JNI
|
||||
*/
|
||||
struct network_manager_t {
|
||||
|
||||
/**
|
||||
* Register a callback that is called if connectivity changes
|
||||
*
|
||||
* @note Only the first registered callback is currently used
|
||||
*
|
||||
* @param cb callback to register
|
||||
* @param data data provided to callback
|
||||
*/
|
||||
void (*add_connectivity_cb)(network_manager_t *this, connectivity_cb_t cb,
|
||||
void *data);
|
||||
|
||||
/**
|
||||
* Unregister a previously registered callback for connectivity changes
|
||||
*
|
||||
* @param cb previously registered callback
|
||||
*/
|
||||
void (*remove_connectivity_cb)(network_manager_t *this,
|
||||
connectivity_cb_t cb);
|
||||
|
||||
/**
|
||||
* Check whether we currently have connectivity
|
||||
*
|
||||
* @return TRUE if currently connected
|
||||
*/
|
||||
bool (*is_connected)(network_manager_t *this);
|
||||
|
||||
/**
|
||||
* Destroy a network_manager_t instance
|
||||
*/
|
||||
void (*destroy)(network_manager_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a network_manager_t instance
|
||||
*
|
||||
* @param context Context object
|
||||
* @return network_manager_t instance
|
||||
*/
|
||||
network_manager_t *network_manager_create(jobject context);
|
||||
|
||||
#endif /** NETWORK_MANAGER_H_ @}*/
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 "vpnservice_builder.h"
|
||||
#include "android_jni.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <library.h>
|
||||
|
||||
typedef struct private_vpnservice_builder_t private_vpnservice_builder_t;
|
||||
|
||||
/**
|
||||
* private data of vpnservice_builder
|
||||
*/
|
||||
struct private_vpnservice_builder_t {
|
||||
|
||||
/**
|
||||
* public interface
|
||||
*/
|
||||
vpnservice_builder_t public;
|
||||
|
||||
/**
|
||||
* Java object
|
||||
*/
|
||||
jobject builder;
|
||||
};
|
||||
|
||||
METHOD(vpnservice_builder_t, add_address, bool,
|
||||
private_vpnservice_builder_t *this, host_t *addr)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jstring str;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
int prefix;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
DBG2(DBG_LIB, "builder: adding interface address %H", addr);
|
||||
|
||||
prefix = addr->get_family(addr) == AF_INET ? 32 : 128;
|
||||
if (snprintf(buf, sizeof(buf), "%H", addr) >= sizeof(buf))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
|
||||
"addAddress", "(Ljava/lang/String;I)Z");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
str = (*env)->NewStringUTF(env, buf);
|
||||
if (!str)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "builder: failed to add address");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, set_mtu, bool,
|
||||
private_vpnservice_builder_t *this, int mtu)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
DBG2(DBG_LIB, "builder: setting MTU to %d", mtu);
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
|
||||
"setMtu", "(I)Z");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (!(*env)->CallBooleanMethod(env, this->builder, method_id, mtu))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "builder: failed to set MTU");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, add_route, bool,
|
||||
private_vpnservice_builder_t *this, host_t *net, int prefix)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jstring str;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
DBG2(DBG_LIB, "builder: adding route %+H/%d", net, prefix);
|
||||
|
||||
if (snprintf(buf, sizeof(buf), "%+H", net) >= sizeof(buf))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
|
||||
"addRoute", "(Ljava/lang/String;I)Z");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
str = (*env)->NewStringUTF(env, buf);
|
||||
if (!str)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "builder: failed to add route");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, add_dns, bool,
|
||||
private_vpnservice_builder_t *this, host_t *dns)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
jstring str;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
DBG2(DBG_LIB, "builder: adding DNS server %H", dns);
|
||||
|
||||
if (snprintf(buf, sizeof(buf), "%H", dns) >= sizeof(buf))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
|
||||
"addDnsServer", "(Ljava/lang/String;)Z");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
str = (*env)->NewStringUTF(env, buf);
|
||||
if (!str)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str))
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "builder: failed to add DNS server");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish or reestablish the TUN device
|
||||
*/
|
||||
static int establish_internal(private_vpnservice_builder_t *this, char *method)
|
||||
{
|
||||
JNIEnv *env;
|
||||
jmethodID method_id;
|
||||
int fd;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
|
||||
DBG2(DBG_LIB, "builder: building TUN device");
|
||||
|
||||
method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class,
|
||||
method, "()I");
|
||||
if (!method_id)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
fd = (*env)->CallIntMethod(env, this->builder, method_id);
|
||||
if (fd == -1)
|
||||
{
|
||||
goto failed;
|
||||
}
|
||||
androidjni_detach_thread();
|
||||
return fd;
|
||||
|
||||
failed:
|
||||
DBG1(DBG_LIB, "builder: failed to build TUN device");
|
||||
androidjni_exception_occurred(env);
|
||||
androidjni_detach_thread();
|
||||
return -1;
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, establish, int,
|
||||
private_vpnservice_builder_t *this)
|
||||
{
|
||||
return establish_internal(this, "establish");
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, establish_no_dns, int,
|
||||
private_vpnservice_builder_t *this)
|
||||
{
|
||||
return establish_internal(this, "establishNoDns");
|
||||
}
|
||||
|
||||
METHOD(vpnservice_builder_t, destroy, void,
|
||||
private_vpnservice_builder_t *this)
|
||||
{
|
||||
JNIEnv *env;
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
(*env)->DeleteGlobalRef(env, this->builder);
|
||||
androidjni_detach_thread();
|
||||
free(this);
|
||||
}
|
||||
|
||||
vpnservice_builder_t *vpnservice_builder_create(jobject builder)
|
||||
{
|
||||
JNIEnv *env;
|
||||
private_vpnservice_builder_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.add_address = _add_address,
|
||||
.add_route = _add_route,
|
||||
.add_dns = _add_dns,
|
||||
.set_mtu = _set_mtu,
|
||||
.establish = _establish,
|
||||
.establish_no_dns = _establish_no_dns,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
);
|
||||
|
||||
androidjni_attach_thread(&env);
|
||||
this->builder = (*env)->NewGlobalRef(env, builder);
|
||||
androidjni_detach_thread();
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Copyright (C) 2012 Giuliano Grassi
|
||||
* Copyright (C) 2012 Ralf Sager
|
||||
* 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 vpnservice_builder vpnservice_builder
|
||||
* @{ @ingroup libandroidbridge
|
||||
*/
|
||||
|
||||
#ifndef VPNSERVICE_BUILDER_H_
|
||||
#define VPNSERVICE_BUILDER_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <library.h>
|
||||
#include <networking/host.h>
|
||||
|
||||
typedef struct vpnservice_builder_t vpnservice_builder_t;
|
||||
|
||||
/**
|
||||
* VpnService.Builder, used to build a TUN device.
|
||||
*
|
||||
* Communicates with CharonVpnService.BuilderAdapter via JNI
|
||||
*/
|
||||
struct vpnservice_builder_t {
|
||||
|
||||
/**
|
||||
* Add an interface address
|
||||
*
|
||||
* @param addr the desired interface address
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*add_address)(vpnservice_builder_t *this, host_t *addr);
|
||||
|
||||
/**
|
||||
* Add a route
|
||||
*
|
||||
* @param net the network address
|
||||
* @param prefix_length the prefix length
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*add_route)(vpnservice_builder_t *this, host_t *net, int prefix);
|
||||
|
||||
/**
|
||||
* Add a DNS server
|
||||
*
|
||||
* @param dns the address of the DNS server
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*add_dns)(vpnservice_builder_t *this, host_t *dns);
|
||||
|
||||
/**
|
||||
* Set the MTU for the TUN device
|
||||
*
|
||||
* @param mtu the MTU to set
|
||||
* @return TRUE on success
|
||||
*/
|
||||
bool (*set_mtu)(vpnservice_builder_t *this, int mtu);
|
||||
|
||||
/**
|
||||
* Build the TUN device
|
||||
*
|
||||
* @return the TUN file descriptor, -1 if failed
|
||||
*/
|
||||
int (*establish)(vpnservice_builder_t *this);
|
||||
|
||||
/**
|
||||
* Build the TUN device without DNS related data
|
||||
*
|
||||
* @return the TUN file descriptor, -1 if failed
|
||||
*/
|
||||
int (*establish_no_dns)(vpnservice_builder_t *this);
|
||||
|
||||
/**
|
||||
* Destroy a vpnservice_builder
|
||||
*/
|
||||
void (*destroy)(vpnservice_builder_t *this);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a vpnservice_builder instance
|
||||
*
|
||||
* @param builder CharonVpnService.BuilderAdapter object
|
||||
* @return vpnservice_builder_t instance
|
||||
*/
|
||||
vpnservice_builder_t *vpnservice_builder_create(jobject builder);
|
||||
|
||||
#endif /** VPNSERVICE_BUILDER_H_ @}*/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_separator" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:left="2dp">
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_background" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012-2013 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item>
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_separator" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:bottom="2dp">
|
||||
<shape>
|
||||
<solid
|
||||
android:color="@color/panel_background" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.RemediationInstructionsFragment"
|
||||
android:id="@+id/remediation_instructions_fragment"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="2"
|
||||
android:layout_width="0dp"
|
||||
android:background="@drawable/remediation_instruction_background_large"
|
||||
android:padding="5dp" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.RemediationInstructionFragment"
|
||||
android:id="@+id/remediation_instruction_fragment"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="2dp"
|
||||
android:background="@drawable/state_background"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/imc_state_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="5dp"
|
||||
android:text="@string/imc_state_label"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/imc_state"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="20sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/action"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:text="@string/show_remediation_instructions"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.LogFragment"
|
||||
android:id="@+id/log_frag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Tobias Brunner
|
||||
Copyright (C) 2012 Giuliano Grassi
|
||||
Copyright (C) 2012 Ralf Sager
|
||||
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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<org.strongswan.android.ui.LogScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp"
|
||||
android:scrollbarFadeDuration="0"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="9sp"
|
||||
android:typeface="monospace" >
|
||||
</TextView>
|
||||
|
||||
</org.strongswan.android.ui.LogScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Tobias Brunner
|
||||
Copyright (C) 2012 Giuliano Grassi
|
||||
Copyright (C) 2012 Ralf Sager
|
||||
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.
|
||||
-->
|
||||
<LinearLayout
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="5dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/profile_username_label"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/username"
|
||||
android:enabled="false"
|
||||
android:inputType="none" />
|
||||
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/profile_password_label"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/password"
|
||||
android:inputType="textPassword|textNoSuggestions"
|
||||
android:singleLine="true" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012-2013 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.VpnStateFragment"
|
||||
android:id="@+id/vpn_state_frag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.ImcStateFragment"
|
||||
android:id="@+id/imc_state_frag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<fragment
|
||||
class="org.strongswan.android.ui.VpnProfileListFragment"
|
||||
android:id="@+id/profile_list_frag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012-2015 Tobias Brunner
|
||||
Copyright (C) 2012 Giuliano Grassi
|
||||
Copyright (C) 2012 Ralf Sager
|
||||
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.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_name_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="@string/profile_name_hint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_gateway_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/gateway"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_vpn_type_label" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/vpn_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:spinnerMode="dropdown"
|
||||
android:entries="@array/vpn_types" />
|
||||
|
||||
<include
|
||||
android:id="@+id/tnc_notice"
|
||||
layout="@layout/two_line_button"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/username_password_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_username_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_password_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textPassword|textNoSuggestions"
|
||||
android:hint="@string/profile_password_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/user_certificate_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_user_certificate_label" />
|
||||
|
||||
<include
|
||||
android:id="@+id/select_user_certificate"
|
||||
layout="@layout/two_line_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_ca_label" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ca_auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/profile_ca_auto_label" />
|
||||
|
||||
<include
|
||||
android:id="@+id/select_certificate"
|
||||
layout="@layout/two_line_button" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/show_advanced"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/profile_show_advanced_label" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/advanced_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_mtu_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/mtu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="number|textNoSuggestions"
|
||||
android:hint="@string/profile_use_default_hint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_port_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="number|textNoSuggestions"
|
||||
android:hint="@string/profile_use_default_hint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/profile_split_tunneling_label" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/split_tunneling_v4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/profile_split_tunnelingv4_title" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/split_tunneling_v6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/profile_split_tunnelingv6_title" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 2 of the License, or (at your
|
||||
option) any later version. See <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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingLeft="5dp"
|
||||
android:paddingRight="5dp" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/profile_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:dividerHeight="1dp"
|
||||
android:divider="?android:attr/listDivider"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true" />
|
||||
|
||||
<TextView android:id="@+id/profile_list_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:text="@string/no_profiles"/>
|
||||
|
||||
</FrameLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user