From 2bb6e8c9a956c56658807c7f2d25ab850243bbe6 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Tue, 22 Jan 2013 22:26:08 -0700 Subject: Start rebranding: a whole lotta string replacement, moving src/ file tree --- .project | 2 +- AndroidManifest.xml | 44 +- README.txt | 6 +- openvpn/src/openvpn/breakpad.cpp | 2 +- project.properties | 2 +- res/layout/basic_settings.xml | 18 +- res/values/untranslatable.xml | 4 +- res/xml/main_headers.xml | 8 +- res/xml/vpn_headers.xml | 12 +- src/de/blinkt/openvpn/AboutFragment.java | 59 -- src/de/blinkt/openvpn/CIDRIP.java | 58 -- src/de/blinkt/openvpn/ConfigConverter.java | 452 ------------- src/de/blinkt/openvpn/ConfigParser.java | 559 --------------- src/de/blinkt/openvpn/FaqFragment.java | 42 -- src/de/blinkt/openvpn/FileProvider.java | 157 ----- src/de/blinkt/openvpn/FileSelect.java | 207 ------ src/de/blinkt/openvpn/FileSelectLayout.java | 91 --- src/de/blinkt/openvpn/FileSelectionFragment.java | 264 -------- src/de/blinkt/openvpn/GeneralSettings.java | 30 - src/de/blinkt/openvpn/InlineFileTab.java | 65 -- src/de/blinkt/openvpn/LaunchVPN.java | 366 ---------- src/de/blinkt/openvpn/LogWindow.java | 350 ---------- src/de/blinkt/openvpn/MainActivity.java | 43 -- src/de/blinkt/openvpn/NetworkSateReceiver.java | 85 --- src/de/blinkt/openvpn/OnBootReceiver.java | 33 - src/de/blinkt/openvpn/OpenVPN.java | 243 ------- src/de/blinkt/openvpn/OpenVPNThread.java | 128 ---- src/de/blinkt/openvpn/OpenVpnManagementThread.java | 576 ---------------- .../blinkt/openvpn/OpenVpnPreferencesFragment.java | 51 -- src/de/blinkt/openvpn/OpenVpnService.java | 508 -------------- src/de/blinkt/openvpn/ProfileManager.java | 221 ------ src/de/blinkt/openvpn/ProxyDetection.java | 52 -- src/de/blinkt/openvpn/SendDumpActivity.java | 60 -- src/de/blinkt/openvpn/Settings_Authentication.java | 135 ---- src/de/blinkt/openvpn/Settings_Basic.java | 346 ---------- src/de/blinkt/openvpn/Settings_IP.java | 128 ---- src/de/blinkt/openvpn/Settings_Obscure.java | 115 ---- src/de/blinkt/openvpn/Settings_Routing.java | 72 -- src/de/blinkt/openvpn/ShowConfigFragment.java | 66 -- src/de/blinkt/openvpn/VPNLaunchHelper.java | 74 -- src/de/blinkt/openvpn/VPNPreferences.java | 137 ---- src/de/blinkt/openvpn/VPNProfileList.java | 316 --------- src/de/blinkt/openvpn/VpnProfile.java | 745 -------------------- src/se/leap/openvpn/AboutFragment.java | 60 ++ src/se/leap/openvpn/CIDRIP.java | 58 ++ src/se/leap/openvpn/ConfigConverter.java | 453 +++++++++++++ src/se/leap/openvpn/ConfigParser.java | 559 +++++++++++++++ src/se/leap/openvpn/FaqFragment.java | 43 ++ src/se/leap/openvpn/FileProvider.java | 156 +++++ src/se/leap/openvpn/FileSelect.java | 208 ++++++ src/se/leap/openvpn/FileSelectLayout.java | 92 +++ src/se/leap/openvpn/FileSelectionFragment.java | 265 ++++++++ src/se/leap/openvpn/GeneralSettings.java | 31 + src/se/leap/openvpn/InlineFileTab.java | 66 ++ src/se/leap/openvpn/LaunchVPN.java | 367 ++++++++++ src/se/leap/openvpn/LogWindow.java | 351 ++++++++++ src/se/leap/openvpn/MainActivity.java | 44 ++ src/se/leap/openvpn/NetworkSateReceiver.java | 86 +++ src/se/leap/openvpn/OnBootReceiver.java | 33 + src/se/leap/openvpn/OpenVPN.java | 244 +++++++ src/se/leap/openvpn/OpenVPNThread.java | 129 ++++ src/se/leap/openvpn/OpenVpnManagementThread.java | 577 ++++++++++++++++ .../leap/openvpn/OpenVpnPreferencesFragment.java | 52 ++ src/se/leap/openvpn/OpenVpnService.java | 509 ++++++++++++++ src/se/leap/openvpn/ProfileManager.java | 220 ++++++ src/se/leap/openvpn/ProxyDetection.java | 53 ++ src/se/leap/openvpn/SendDumpActivity.java | 59 ++ src/se/leap/openvpn/Settings_Authentication.java | 136 ++++ src/se/leap/openvpn/Settings_Basic.java | 347 ++++++++++ src/se/leap/openvpn/Settings_IP.java | 129 ++++ src/se/leap/openvpn/Settings_Obscure.java | 116 ++++ src/se/leap/openvpn/Settings_Routing.java | 73 ++ src/se/leap/openvpn/ShowConfigFragment.java | 67 ++ src/se/leap/openvpn/VPNLaunchHelper.java | 75 +++ src/se/leap/openvpn/VPNPreferences.java | 138 ++++ src/se/leap/openvpn/VPNProfileList.java | 317 +++++++++ src/se/leap/openvpn/VpnProfile.java | 746 +++++++++++++++++++++ 77 files changed, 6908 insertions(+), 6883 deletions(-) delete mode 100644 src/de/blinkt/openvpn/AboutFragment.java delete mode 100644 src/de/blinkt/openvpn/CIDRIP.java delete mode 100644 src/de/blinkt/openvpn/ConfigConverter.java delete mode 100644 src/de/blinkt/openvpn/ConfigParser.java delete mode 100644 src/de/blinkt/openvpn/FaqFragment.java delete mode 100644 src/de/blinkt/openvpn/FileProvider.java delete mode 100644 src/de/blinkt/openvpn/FileSelect.java delete mode 100644 src/de/blinkt/openvpn/FileSelectLayout.java delete mode 100644 src/de/blinkt/openvpn/FileSelectionFragment.java delete mode 100644 src/de/blinkt/openvpn/GeneralSettings.java delete mode 100644 src/de/blinkt/openvpn/InlineFileTab.java delete mode 100644 src/de/blinkt/openvpn/LaunchVPN.java delete mode 100644 src/de/blinkt/openvpn/LogWindow.java delete mode 100644 src/de/blinkt/openvpn/MainActivity.java delete mode 100644 src/de/blinkt/openvpn/NetworkSateReceiver.java delete mode 100644 src/de/blinkt/openvpn/OnBootReceiver.java delete mode 100644 src/de/blinkt/openvpn/OpenVPN.java delete mode 100644 src/de/blinkt/openvpn/OpenVPNThread.java delete mode 100644 src/de/blinkt/openvpn/OpenVpnManagementThread.java delete mode 100644 src/de/blinkt/openvpn/OpenVpnPreferencesFragment.java delete mode 100644 src/de/blinkt/openvpn/OpenVpnService.java delete mode 100644 src/de/blinkt/openvpn/ProfileManager.java delete mode 100644 src/de/blinkt/openvpn/ProxyDetection.java delete mode 100644 src/de/blinkt/openvpn/SendDumpActivity.java delete mode 100644 src/de/blinkt/openvpn/Settings_Authentication.java delete mode 100644 src/de/blinkt/openvpn/Settings_Basic.java delete mode 100644 src/de/blinkt/openvpn/Settings_IP.java delete mode 100644 src/de/blinkt/openvpn/Settings_Obscure.java delete mode 100644 src/de/blinkt/openvpn/Settings_Routing.java delete mode 100644 src/de/blinkt/openvpn/ShowConfigFragment.java delete mode 100644 src/de/blinkt/openvpn/VPNLaunchHelper.java delete mode 100644 src/de/blinkt/openvpn/VPNPreferences.java delete mode 100644 src/de/blinkt/openvpn/VPNProfileList.java delete mode 100644 src/de/blinkt/openvpn/VpnProfile.java create mode 100644 src/se/leap/openvpn/AboutFragment.java create mode 100644 src/se/leap/openvpn/CIDRIP.java create mode 100644 src/se/leap/openvpn/ConfigConverter.java create mode 100644 src/se/leap/openvpn/ConfigParser.java create mode 100644 src/se/leap/openvpn/FaqFragment.java create mode 100644 src/se/leap/openvpn/FileProvider.java create mode 100644 src/se/leap/openvpn/FileSelect.java create mode 100644 src/se/leap/openvpn/FileSelectLayout.java create mode 100644 src/se/leap/openvpn/FileSelectionFragment.java create mode 100644 src/se/leap/openvpn/GeneralSettings.java create mode 100644 src/se/leap/openvpn/InlineFileTab.java create mode 100644 src/se/leap/openvpn/LaunchVPN.java create mode 100644 src/se/leap/openvpn/LogWindow.java create mode 100644 src/se/leap/openvpn/MainActivity.java create mode 100644 src/se/leap/openvpn/NetworkSateReceiver.java create mode 100644 src/se/leap/openvpn/OnBootReceiver.java create mode 100644 src/se/leap/openvpn/OpenVPN.java create mode 100644 src/se/leap/openvpn/OpenVPNThread.java create mode 100644 src/se/leap/openvpn/OpenVpnManagementThread.java create mode 100644 src/se/leap/openvpn/OpenVpnPreferencesFragment.java create mode 100644 src/se/leap/openvpn/OpenVpnService.java create mode 100644 src/se/leap/openvpn/ProfileManager.java create mode 100644 src/se/leap/openvpn/ProxyDetection.java create mode 100644 src/se/leap/openvpn/SendDumpActivity.java create mode 100644 src/se/leap/openvpn/Settings_Authentication.java create mode 100644 src/se/leap/openvpn/Settings_Basic.java create mode 100644 src/se/leap/openvpn/Settings_IP.java create mode 100644 src/se/leap/openvpn/Settings_Obscure.java create mode 100644 src/se/leap/openvpn/Settings_Routing.java create mode 100644 src/se/leap/openvpn/ShowConfigFragment.java create mode 100644 src/se/leap/openvpn/VPNLaunchHelper.java create mode 100644 src/se/leap/openvpn/VPNPreferences.java create mode 100644 src/se/leap/openvpn/VPNProfileList.java create mode 100644 src/se/leap/openvpn/VpnProfile.java diff --git a/.project b/.project index b9e0fd8f..1a0e1cda 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - icsopenvpn + leap_android diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 07fbde8a..b4df9219 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,7 +16,7 @@ --> @@ -33,7 +33,7 @@ android:targetSdkVersion="15" /> - + - - - + + + @@ -62,36 +62,36 @@ + android:name="se.leap.openvpn.api.ExternalOpenVPNService" + android:permission="se.leap.openvpn.REMOTE_API" > - + + android:name="se.leap.openvpn.api.GrantPermissionsActivity" + android:permission="se.leap.openvpn.REMOTE_API" > - + @@ -121,7 +121,7 @@ @@ -131,9 +131,9 @@ + android:targetActivity="se.leap.openvpn.LaunchVPN" > @@ -142,10 +142,10 @@ - \ No newline at end of file + diff --git a/README.txt b/README.txt index 4a2c6bd4..17b9f50a 100644 --- a/README.txt +++ b/README.txt @@ -28,10 +28,10 @@ public class StartOpenVPNActivity extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.main); - final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; + final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClassName("de.blinkt.openvpn", "de.blinkt.openvpn.LaunchVPN"); + shortcutIntent.setClassName("se.leap.openvpn", "se.leap.openvpn.LaunchVPN"); shortcutIntent.putExtra(EXTRA_NAME,"upb ssl"); startActivity(shortcutIntent); } @@ -39,5 +39,5 @@ public class StartOpenVPNActivity extends Activity { or from the shell: -am start -a android.intent.action.MAIN -n de.blinkt.openvpn/.LaunchVPN -e de.blinkt.openvpn.shortcutProfileName Home +am start -a android.intent.action.MAIN -n se.leap.openvpn/.LaunchVPN -e se.leap.openvpn.shortcutProfileName Home diff --git a/openvpn/src/openvpn/breakpad.cpp b/openvpn/src/openvpn/breakpad.cpp index cfcc10a8..77c469f3 100644 --- a/openvpn/src/openvpn/breakpad.cpp +++ b/openvpn/src/openvpn/breakpad.cpp @@ -19,7 +19,7 @@ static google_breakpad::ExceptionHandler* eh; void breakpad_setup(void) { printf("Initializing Google Breakpad!\n"); - desc = new google_breakpad::MinidumpDescriptor("/data/data/de.blinkt.openvpn/cache"); + desc = new google_breakpad::MinidumpDescriptor("/data/data/se.leap.openvpn/cache"); eh = new google_breakpad::ExceptionHandler(*desc, NULL, DumpCallback, NULL, true,-1); } diff --git a/project.properties b/project.properties index c4f09d2b..895c9ce2 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-17 +target=android-16 diff --git a/res/layout/basic_settings.xml b/res/layout/basic_settings.xml index d37bc37a..d56f6ad5 100644 --- a/res/layout/basic_settings.xml +++ b/res/layout/basic_settings.xml @@ -15,7 +15,7 @@ limitations under the License. --> @@ -122,11 +122,11 @@ android:orientation="vertical" android:visibility="gone" > - + leap:title="@string/ca_title" /> - + leap:title="@string/client_certificate_title" /> - + leap:title="@string/client_key_title" /> - + leap:title="@string/client_pkcs12_title" /> - Copyright 2012\nArne Schwabe <arne@rfc2549.org> + Copyright 2012\nLEAP <info@leap.se> Copyright © 2002–2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\n "OpenVPN" is a trademark of OpenVPN Technologies, Inc.\n @@ -41,4 +41,4 @@ -1 - \ No newline at end of file + diff --git a/res/xml/main_headers.xml b/res/xml/main_headers.xml index 92740382..1ee238ea 100644 --- a/res/xml/main_headers.xml +++ b/res/xml/main_headers.xml @@ -2,18 +2,18 @@
diff --git a/res/xml/vpn_headers.xml b/res/xml/vpn_headers.xml index 495c2c72..96b49bf8 100644 --- a/res/xml/vpn_headers.xml +++ b/res/xml/vpn_headers.xml @@ -3,28 +3,28 @@
diff --git a/src/de/blinkt/openvpn/AboutFragment.java b/src/de/blinkt/openvpn/AboutFragment.java deleted file mode 100644 index 85f48bc7..00000000 --- a/src/de/blinkt/openvpn/AboutFragment.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.blinkt.openvpn; - -import android.app.Fragment; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -public class AboutFragment extends Fragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v= inflater.inflate(R.layout.about, container, false); - TextView ver = (TextView) v.findViewById(R.id.version); - - String version; - String name="Openvpn"; - try { - PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); - version = packageinfo.versionName; - name = getString(R.string.app); - } catch (NameNotFoundException e) { - version = "error fetching version"; - } - - - ver.setText(getString(R.string.version_info,name,version)); - - TextView paypal = (TextView) v.findViewById(R.id.donatestring); - - String donatetext = getActivity().getString(R.string.donatewithpaypal); - Spanned htmltext = Html.fromHtml(donatetext); - paypal.setText(htmltext); - paypal.setMovementMethod(LinkMovementMethod.getInstance()); - - TextView translation = (TextView) v.findViewById(R.id.translation); - - // Don't print a text for myself - if ( getString(R.string.translationby).contains("Arne Schwabe")) - translation.setText(""); - else - translation.setText(R.string.translationby); - return v; - } - -} diff --git a/src/de/blinkt/openvpn/CIDRIP.java b/src/de/blinkt/openvpn/CIDRIP.java deleted file mode 100644 index ccb3836a..00000000 --- a/src/de/blinkt/openvpn/CIDRIP.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.blinkt.openvpn; - -class CIDRIP{ - String mIp; - int len; - public CIDRIP(String ip, String mask){ - mIp=ip; - long netmask=getInt(mask); - - // Add 33. bit to ensure the loop terminates - netmask += 1l << 32; - - int lenZeros = 0; - while((netmask & 0x1) == 0) { - lenZeros++; - netmask = netmask >> 1; - } - // Check if rest of netmask is only 1s - if(netmask != (0x1ffffffffl >> lenZeros)) { - // Asume no CIDR, set /32 - len=32; - } else { - len =32 -lenZeros; - } - - } - @Override - public String toString() { - return String.format("%s/%d",mIp,len); - } - - public boolean normalise(){ - long ip=getInt(mIp); - - long newip = ip & (0xffffffffl << (32 -len)); - if (newip != ip){ - mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff); - return true; - } else { - return false; - } - } - static long getInt(String ipaddr) { - String[] ipt = ipaddr.split("\\."); - long ip=0; - - ip += Long.parseLong(ipt[0])<< 24; - ip += Integer.parseInt(ipt[1])<< 16; - ip += Integer.parseInt(ipt[2])<< 8; - ip += Integer.parseInt(ipt[3]); - - return ip; - } - public long getInt() { - return getInt(mIp); - } - -} \ No newline at end of file diff --git a/src/de/blinkt/openvpn/ConfigConverter.java b/src/de/blinkt/openvpn/ConfigConverter.java deleted file mode 100644 index 5065436c..00000000 --- a/src/de/blinkt/openvpn/ConfigConverter.java +++ /dev/null @@ -1,452 +0,0 @@ - -package de.blinkt.openvpn; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; -import java.util.Vector; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.ListActivity; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.util.Base64; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import de.blinkt.openvpn.ConfigParser.ConfigParseError; - -public class ConfigConverter extends ListActivity { - - public static final String IMPORT_PROFILE = "de.blinkt.openvpn.IMPORT_PROFILE"; - - private VpnProfile mResult; - private ArrayAdapter mArrayAdapter; - - private List mPathsegments; - - private String mAliasName=null; - - private int RESULT_INSTALLPKCS12 = 7; - - private String mPossibleName=null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.config_converter); - } - - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==R.id.cancel){ - setResult(Activity.RESULT_CANCELED); - finish(); - } else if(item.getItemId()==R.id.ok) { - if(mResult==null) { - log("Importing the config had error, cannot save it"); - return true; - } - - Intent in = installPKCS12(); - - if(in != null) - startActivityForResult(in, RESULT_INSTALLPKCS12); - else - saveProfile(); - - return true; - } - - return super.onOptionsItemSelected(item); - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if(requestCode==RESULT_INSTALLPKCS12) { - if(resultCode==Activity.RESULT_OK) { - showCertDialog(); - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - private void saveProfile() { - Intent result = new Intent(); - ProfileManager vpl = ProfileManager.getInstance(this); - - if(((CheckBox)findViewById(R.id.correcttls)).isChecked() && isOldCNFormat()) { - convertTLSRemote(); - } - - setUniqueProfileName(vpl); - vpl.addProfile(mResult); - vpl.saveProfile(this, mResult); - vpl.saveProfileList(this); - result.putExtra(VpnProfile.EXTRA_PROFILEUUID,mResult.getUUID().toString()); - setResult(Activity.RESULT_OK, result); - finish(); - } - - - - private void convertTLSRemote() { - if(mResult.mRemoteCN.startsWith("/")) - mResult.mRemoteCN = mResult.mRemoteCN.substring(1); - mResult.mRemoteCN = mResult.mRemoteCN.replace("/", ", "); - } - - public void showCertDialog () { - try { - KeyChain.choosePrivateKeyAlias(this, - new KeyChainAliasCallback() { - - public void alias(String alias) { - // Credential alias selected. Remember the alias selection for future use. - mResult.mAlias=alias; - saveProfile(); - } - - - }, - new String[] {"RSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - mResult.mServerName, // host name of server requesting the cert, null if unavailable - -1, // port of server requesting the cert, -1 if unavailable - mAliasName); // alias to preselect, null if unavailable - } catch (ActivityNotFoundException anf) { - Builder ab = new AlertDialog.Builder(this); - ab.setTitle(R.string.broken_image_cert_title); - ab.setMessage(R.string.broken_image_cert); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - - private Intent installPKCS12() { - - if(!((CheckBox)findViewById(R.id.importpkcs12)).isChecked()) { - setAuthTypeToEmbeddedPKCS12(); - return null; - - } - String pkcs12datastr = mResult.mPKCS12Filename; - if(pkcs12datastr!=null && pkcs12datastr.startsWith(VpnProfile.INLINE_TAG)) { - Intent inkeyintent = KeyChain.createInstallIntent(); - - pkcs12datastr= pkcs12datastr.substring(VpnProfile.INLINE_TAG.length()); - - - byte[] pkcs12data = Base64.decode(pkcs12datastr, Base64.DEFAULT); - - - inkeyintent.putExtra(KeyChain.EXTRA_PKCS12,pkcs12data ); - - if(mAliasName.equals("")) - mAliasName=null; - - if(mAliasName!=null){ - inkeyintent.putExtra(KeyChain.EXTRA_NAME, mAliasName); - } - return inkeyintent; - - } - return null; - } - - - - private void setAuthTypeToEmbeddedPKCS12() { - if(mResult.mPKCS12Filename!=null && mResult.mPKCS12Filename.startsWith(VpnProfile.INLINE_TAG)) { - if(mResult.mAuthenticationType==VpnProfile.TYPE_USERPASS_KEYSTORE) - mResult.mAuthenticationType=VpnProfile.TYPE_USERPASS_PKCS12; - - if(mResult.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) - mResult.mAuthenticationType=VpnProfile.TYPE_PKCS12; - - } - } - - - - - - private void setUniqueProfileName(ProfileManager vpl) { - int i=0; - - String newname = mPossibleName; - - while(vpl.getProfileByName(newname)!=null) { - i++; - if(i==1) - newname = getString(R.string.converted_profile); - else - newname = getString(R.string.converted_profile_i,i); - } - - mResult.mName=newname; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.import_menu, menu); - return true; - } - - private String embedFile(String filename) { - return embedFile(filename, false); - } - - private String embedFile(String filename, boolean base64encode) - { - if(filename==null) - return null; - - // Already embedded, nothing to do - if(filename.startsWith(VpnProfile.INLINE_TAG)) - return filename; - - File possibleFile = findFile(filename); - if(possibleFile==null) - return filename; - else - return readFileContent(possibleFile,base64encode); - - } - - private File findFile(String filename) { - File foundfile =findFileRaw(filename); - - if (foundfile==null && filename!=null && !filename.equals("")) - log(R.string.import_could_not_open,filename); - - return foundfile; - } - - - - private File findFileRaw(String filename) - { - if(filename == null || filename.equals("")) - return null; - - // Try diffent path relative to /mnt/sdcard - File sdcard = Environment.getExternalStorageDirectory(); - File root = new File("/"); - - Vector dirlist = new Vector(); - - for(int i=mPathsegments.size()-1;i >=0 ;i--){ - String path = ""; - for (int j = 0;j<=i;j++) { - path += "/" + mPathsegments.get(j); - } - dirlist.add(new File(path)); - } - dirlist.add(sdcard); - dirlist.add(root); - - - String[] fileparts = filename.split("/"); - for(File rootdir:dirlist){ - String suffix=""; - for(int i=fileparts.length-1; i >=0;i--) { - if(i==fileparts.length-1) - suffix = fileparts[i]; - else - suffix = fileparts[i] + "/" + suffix; - - File possibleFile = new File(rootdir,suffix); - if(!possibleFile.canRead()) - continue; - - // read the file inline - return possibleFile; - - } - } - return null; - } - - String readFileContent(File possibleFile, boolean base64encode) { - byte [] filedata; - try { - filedata = readBytesFromFile(possibleFile); - } catch (IOException e) { - log(e.getLocalizedMessage()); - return null; - } - - String data; - if(base64encode) { - data = Base64.encodeToString(filedata, Base64.DEFAULT); - } else { - data = new String(filedata); - - } - return VpnProfile.INLINE_TAG + data; - - } - - - private byte[] readBytesFromFile(File file) throws IOException { - InputStream input = new FileInputStream(file); - - long len= file.length(); - - - // Create the byte array to hold the data - byte[] bytes = new byte[(int) len]; - - // Read in the bytes - int offset = 0; - int bytesRead = 0; - while (offset < bytes.length - && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { - offset += bytesRead; - } - - input.close(); - return bytes; - } - - void embedFiles() { - // This where I would like to have a c++ style - // void embedFile(std::string & option) - - if (mResult.mPKCS12Filename!=null) { - File pkcs12file = findFileRaw(mResult.mPKCS12Filename); - if(pkcs12file!=null) { - mAliasName = pkcs12file.getName().replace(".p12", ""); - } else { - mAliasName = "Imported PKCS12"; - } - } - - - mResult.mCaFilename = embedFile(mResult.mCaFilename); - mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename); - mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename); - mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename); - mResult.mPKCS12Filename = embedFile(mResult.mPKCS12Filename,true); - - - if(mResult.mUsername == null && mResult.mPassword != null ){ - String data =embedFile(mResult.mPassword); - ConfigParser.useEmbbedUserAuth(mResult, data); - } - } - - - @Override - protected void onStart() { - super.onStart(); - - mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1); - getListView().setAdapter(mArrayAdapter); - final android.content.Intent intent = getIntent (); - - if (intent != null) - { - final android.net.Uri data = intent.getData (); - if (data != null) - { - //log(R.string.import_experimental); - log(R.string.importing_config,data.toString()); - try { - if(data.getScheme().equals("file")) { - mPossibleName = data.getLastPathSegment(); - if(mPossibleName!=null){ - mPossibleName =mPossibleName.replace(".ovpn", ""); - mPossibleName =mPossibleName.replace(".conf", ""); - } - } - InputStream is = getContentResolver().openInputStream(data); - mPathsegments = data.getPathSegments(); - - doImport(is); - } catch (FileNotFoundException e) { - log(R.string.import_content_resolve_error); - } - } - } - - return; - } - - private void log(String logmessage) { - mArrayAdapter.add(logmessage); - } - - private void doImport(InputStream is) { - ConfigParser cp = new ConfigParser(); - try { - InputStreamReader isr = new InputStreamReader(is); - - cp.parseConfig(isr); - VpnProfile vp = cp.convertProfile(); - mResult = vp; - embedFiles(); - displayWarnings(); - log(R.string.import_done); - return; - - } catch (IOException e) { - log(R.string.error_reading_config_file); - log(e.getLocalizedMessage()); - } catch (ConfigParseError e) { - log(R.string.error_reading_config_file); - log(e.getLocalizedMessage()); - } - mResult=null; - - } - - private void displayWarnings() { - if(mResult.mUseCustomConfig) { - log(R.string.import_warning_custom_options); - String copt = mResult.mCustomConfigOptions; - if(copt.startsWith("#")) { - int until = copt.indexOf('\n'); - copt = copt.substring(until+1); - } - - log(copt); - } - - if(mResult.mAuthenticationType==VpnProfile.TYPE_KEYSTORE || - mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - findViewById(R.id.importpkcs12).setVisibility(View.VISIBLE); - } - - if (isOldCNFormat()) - findViewById(R.id.correcttls).setVisibility(View.VISIBLE); - } - - private boolean isOldCNFormat() { - return mResult.mCheckRemoteCN && mResult.mRemoteCN.contains("/") && ! mResult.mRemoteCN.contains("_"); - } - - private void log(int ressourceId, Object... formatArgs) { - log(getString(ressourceId,formatArgs)); - } -} diff --git a/src/de/blinkt/openvpn/ConfigParser.java b/src/de/blinkt/openvpn/ConfigParser.java deleted file mode 100644 index dc605bbc..00000000 --- a/src/de/blinkt/openvpn/ConfigParser.java +++ /dev/null @@ -1,559 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.util.HashMap; -import java.util.Locale; -import java.util.Vector; - -//! Openvpn Config FIle Parser, probably not 100% accurate but close enough - -// And rember, this is valid :) -// -- -// bar -// -public class ConfigParser { - - - private HashMap>> options = new HashMap>>(); - public void parseConfig(Reader reader) throws IOException, ConfigParseError { - - - BufferedReader br =new BufferedReader(reader); - - @SuppressWarnings("unused") - int lineno=0; - - while (true){ - String line = br.readLine(); - if(line==null) - break; - lineno++; - Vector args = parseline(line); - if(args.size() ==0) - continue; - - - if(args.get(0).startsWith("--")) - args.set(0, args.get(0).substring(2)); - - checkinlinefile(args,br); - - String optionname = args.get(0); - if(!options.containsKey(optionname)) { - options.put(optionname, new Vector>()); - } - options.get(optionname).add(args); - } - } - - private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { - String arg0 = args.get(0); - // CHeck for - if(arg0.startsWith("<") && arg0.endsWith(">")) { - String argname = arg0.substring(1, arg0.length()-1); - String inlinefile = VpnProfile.INLINE_TAG; - - String endtag = String.format("",argname); - do { - String line = br.readLine(); - if(line==null){ - throw new ConfigParseError(String.format("No endtag for starttag <%s> found",argname,argname)); - } - if(line.equals(endtag)) - break; - else { - inlinefile+=line; - inlinefile+= "\n"; - } - } while(true); - - args.clear(); - args.add(argname); - args.add(inlinefile); - } - - } - - enum linestate { - initial, - readin_single_quote - , reading_quoted, reading_unquoted, done} - - private boolean space(char c) { - // I really hope nobody is using zero bytes inside his/her config file - // to sperate parameter but here we go: - return Character.isWhitespace(c) || c == '\0'; - - } - - public class ConfigParseError extends Exception { - private static final long serialVersionUID = -60L; - - public ConfigParseError(String msg) { - super(msg); - } - } - - - // adapted openvpn's parse function to java - private Vector parseline(String line) throws ConfigParseError { - Vector parameters = new Vector(); - - if (line.length()==0) - return parameters; - - - linestate state = linestate.initial; - boolean backslash = false; - char out=0; - - int pos=0; - String currentarg=""; - - do { - // Emulate the c parsing ... - char in; - if(pos < line.length()) - in = line.charAt(pos); - else - in = '\0'; - - if (!backslash && in == '\\' && state != linestate.readin_single_quote) - { - backslash = true; - } - else - { - if (state == linestate.initial) - { - if (!space (in)) - { - if (in == ';' || in == '#') /* comment */ - break; - if (!backslash && in == '\"') - state = linestate.reading_quoted; - else if (!backslash && in == '\'') - state = linestate.readin_single_quote; - else - { - out = in; - state = linestate.reading_unquoted; - } - } - } - else if (state == linestate.reading_unquoted) - { - if (!backslash && space (in)) - state = linestate.done; - else - out = in; - } - else if (state == linestate.reading_quoted) - { - if (!backslash && in == '\"') - state = linestate.done; - else - out = in; - } - else if (state == linestate.readin_single_quote) - { - if (in == '\'') - state = linestate.done; - else - out = in; - } - - if (state == linestate.done) - { - /* ASSERT (parm_len > 0); */ - state = linestate.initial; - parameters.add(currentarg); - currentarg = ""; - out =0; - } - - if (backslash && out!=0) - { - if (!(out == '\\' || out == '\"' || space (out))) - { - throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); - } - } - backslash = false; - } - - /* store parameter character */ - if (out!=0) - { - currentarg+=out; - } - } while (pos++ < line.length()); - - return parameters; - } - - - final String[] unsupportedOptions = { "config", - "connection", - "proto-force", - "remote-random", - "tls-server" - - }; - - // Ignore all scripts - // in most cases these won't work and user who wish to execute scripts will - // figure out themselves - final String[] ignoreOptions = { "tls-client", - "askpass", - "auth-nocache", - "up", - "down", - "route-up", - "ipchange", - "route-up", - "route-pre-down", - "auth-user-pass-verify", - "dhcp-release", - "dhcp-renew", - "dh", - "management-hold", - "management", - "management-query-passwords", - "pause-exit", - "persist-key", - "register-dns", - "route-delay", - "route-gateway", - "route-metric", - "route-method", - "status", - "script-security", - "show-net-up", - "suppress-timestamps", - "tmp-dir", - "tun-ipv6", - "topology", - "win-sys", - }; - - - // This method is far too long - public VpnProfile convertProfile() throws ConfigParseError{ - boolean noauthtypeset=true; - VpnProfile np = new VpnProfile("converted Profile"); - // Pull, client, tls-client - np.clearDefaults(); - - if(options.containsKey("client") || options.containsKey("pull")) { - np.mUsePull=true; - options.remove("pull"); - options.remove("client"); - } - - Vector secret = getOption("secret", 1, 2); - if(secret!=null) - { - np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; - noauthtypeset=false; - np.mUseTLSAuth=true; - np.mTLSAuthFilename=secret.get(1); - if(secret.size()==3) - np.mTLSAuthDirection=secret.get(2); - - } - - Vector> routes = getAllOption("route", 1, 4); - if(routes!=null) { - String routeopt = ""; - for(Vector route:routes){ - String netmask = "255.255.255.255"; - if(route.size() >= 3) - netmask = route.get(2); - String net = route.get(1); - try { - CIDRIP cidr = new CIDRIP(net, netmask); - routeopt+=cidr.toString() + " "; - } catch (ArrayIndexOutOfBoundsException aioob) { - throw new ConfigParseError("Could not parse netmask of route " + netmask); - } catch (NumberFormatException ne) { - throw new ConfigParseError("Could not parse netmask of route " + netmask); - } - - } - np.mCustomRoutes=routeopt; - } - - // Also recognize tls-auth [inline] direction ... - Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); - if(tlsauthoptions!=null) { - for(Vector tlsauth:tlsauthoptions) { - if(tlsauth!=null) - { - if(!tlsauth.get(1).equals("[inline]")) { - np.mTLSAuthFilename=tlsauth.get(1); - np.mUseTLSAuth=true; - } - if(tlsauth.size()==3) - np.mTLSAuthDirection=tlsauth.get(2); - } - } - } - - Vector direction = getOption("key-direction", 1, 1); - if(direction!=null) - np.mTLSAuthDirection=direction.get(1); - - - if(getAllOption("redirect-gateway", 0, 5) != null) - np.mUseDefaultRoute=true; - - Vector dev =getOption("dev",1,1); - Vector devtype =getOption("dev-type",1,1); - - if( (devtype !=null && devtype.get(1).equals("tun")) || - (dev!=null && dev.get(1).startsWith("tun")) || - (devtype ==null && dev == null) ) { - //everything okay - } else { - throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); - } - - - - Vector mode =getOption("mode",1,1); - if (mode != null){ - if(!mode.get(1).equals("p2p")) - throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); - } - - Vector port = getOption("port", 1,1); - if(port!=null){ - np.mServerPort = port.get(1); - } - - Vector proto = getOption("proto", 1,1); - if(proto!=null){ - np.mUseUdp=isUdpProto(proto.get(1));; - } - - // Parse remote config - Vector remote = getOption("remote",1,3); - if(remote != null){ - switch (remote.size()) { - case 4: - np.mUseUdp=isUdpProto(remote.get(3)); - case 3: - np.mServerPort = remote.get(2); - case 2: - np.mServerName = remote.get(1); - } - } - - Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); - if(dhcpoptions!=null) { - for(Vector dhcpoption:dhcpoptions) { - String type=dhcpoption.get(1); - String arg = dhcpoption.get(2); - if(type.equals("DOMAIN")) { - np.mSearchDomain=dhcpoption.get(2); - } else if(type.equals("DNS")) { - np.mOverrideDNS=true; - if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) - np.mDNS1=arg; - else - np.mDNS2=arg; - } - } - } - - Vector ifconfig = getOption("ifconfig", 2, 2); - if(ifconfig!=null) { - CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); - np.mIPv4Address=cidr.toString(); - } - - if(getOption("remote-random-hostname", 0, 0)!=null) - np.mUseRandomHostname=true; - - if(getOption("float", 0, 0)!=null) - np.mUseFloat=true; - - if(getOption("comp-lzo", 0, 1)!=null) - np.mUseLzo=true; - - Vector cipher = getOption("cipher", 1, 1); - if(cipher!=null) - np.mCipher= cipher.get(1); - - Vector ca = getOption("ca",1,1); - if(ca!=null){ - np.mCaFilename = ca.get(1); - } - - Vector cert = getOption("cert",1,1); - if(cert!=null){ - np.mClientCertFilename = cert.get(1); - np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; - noauthtypeset=false; - } - Vector key= getOption("key",1,1); - if(key!=null) - np.mClientKeyFilename=key.get(1); - - Vector pkcs12 = getOption("pkcs12",1,1); - if(pkcs12!=null) { - np.mPKCS12Filename = pkcs12.get(1); - np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; - noauthtypeset=false; - } - - Vector tlsremote = getOption("tls-remote",1,1); - if(tlsremote!=null){ - np.mRemoteCN = tlsremote.get(1); - np.mCheckRemoteCN=true; - } - - Vector verb = getOption("verb",1,1); - if(verb!=null){ - np.mVerb=verb.get(1); - } - - - if(getOption("nobind", 0, 0) != null) - np.mNobind=true; - - if(getOption("persist-tun", 0,0) != null) - np.mPersistTun=true; - - Vector connectretry = getOption("connect-retry", 1, 1); - if(connectretry!=null) - np.mConnectRetry =connectretry.get(1); - - Vector connectretrymax = getOption("connect-retry-max", 1, 1); - if(connectretrymax!=null) - np.mConnectRetryMax =connectretrymax.get(1); - - Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); - if(remotetls!=null) - if(remotetls.get(0).get(1).equals("server")) - np.mExpectTLSCert=true; - else - options.put("remotetls",remotetls); - - Vector authuser = getOption("auth-user-pass",0,1); - if(authuser !=null){ - if(noauthtypeset) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS; - } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES; - } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; - } - if(authuser.size()>1) { - // Set option value to password get to get canche to embed later. - np.mUsername=null; - np.mPassword=authuser.get(1); - useEmbbedUserAuth(np,authuser.get(1)); - } - - } - - - // Check the other options - - checkIgnoreAndInvalidOptions(np); - fixup(np); - - return np; - } - - private boolean isUdpProto(String proto) throws ConfigParseError { - boolean isudp; - if(proto.equals("udp") || proto.equals("udp6")) - isudp=true; - else if (proto.equals("tcp-client") || - proto.equals("tcp") || - proto.equals("tcp6") || - proto.endsWith("tcp6-client")) - isudp =false; - else - throw new ConfigParseError("Unsupported option to --proto " + proto); - return isudp; - } - - static public void useEmbbedUserAuth(VpnProfile np,String inlinedata) - { - String data = inlinedata.replace(VpnProfile.INLINE_TAG, ""); - String[] parts = data.split("\n"); - if(parts.length >= 2) { - np.mUsername=parts[0]; - np.mPassword=parts[1]; - } - } - - private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { - for(String option:unsupportedOptions) - if(options.containsKey(option)) - throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option)); - - for(String option:ignoreOptions) - // removing an item which is not in the map is no error - options.remove(option); - - if(options.size()> 0) { - String custom = "# These Options were found in the config file do not map to config settings:\n"; - - for(Vector> option:options.values()) { - for(Vector optionsline: option) { - for (String arg : optionsline) - custom+= VpnProfile.openVpnEscape(arg) + " "; - } - custom+="\n"; - - } - np.mCustomConfigOptions = custom; - np.mUseCustomConfig=true; - - } - } - - - private void fixup(VpnProfile np) { - if(np.mRemoteCN.equals(np.mServerName)) { - np.mRemoteCN=""; - } - } - - private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { - Vector> alloptions = getAllOption(option, minarg, maxarg); - if(alloptions==null) - return null; - else - return alloptions.lastElement(); - } - - - private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { - Vector> args = options.get(option); - if(args==null) - return null; - - for(Vector optionline:args) - - if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) { - String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d", - option,optionline.size()-1,minarg,maxarg ); - throw new ConfigParseError(err); - } - options.remove(option); - return args; - } - -} - - - - diff --git a/src/de/blinkt/openvpn/FaqFragment.java b/src/de/blinkt/openvpn/FaqFragment.java deleted file mode 100644 index a358dc9a..00000000 --- a/src/de/blinkt/openvpn/FaqFragment.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.blinkt.openvpn; - -import android.app.Fragment; -import android.os.Bundle; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -public class FaqFragment extends Fragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v= inflater.inflate(R.layout.faq, container, false); - - insertHtmlEntry(v,R.id.broken_images_faq,R.string.broken_images_faq); - insertHtmlEntry(v,R.id.faq_howto,R.string.faq_howto); - insertHtmlEntry(v, R.id.baterry_consumption, R.string.baterry_consumption); - insertHtmlEntry(v, R.id.faq_tethering, R.string.faq_tethering); - - return v; - - - - } - - private void insertHtmlEntry (View v, int viewId, int stringId) { - TextView faqitem = (TextView) v.findViewById(viewId); - faqitem.setText(Html.fromHtml(getActivity().getString(stringId))); - faqitem.setMovementMethod(LinkMovementMethod.getInstance()); - } - -} diff --git a/src/de/blinkt/openvpn/FileProvider.java b/src/de/blinkt/openvpn/FileProvider.java deleted file mode 100644 index e86b544f..00000000 --- a/src/de/blinkt/openvpn/FileProvider.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.blinkt.openvpn; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import android.content.ContentProvider; -import android.content.ContentProvider.PipeDataWriter; -import android.content.ContentValues; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.provider.OpenableColumns; -import android.util.Log; - -/** - * A very simple content provider that can serve arbitrary asset files from - * our .apk. - */ -public class FileProvider extends ContentProvider -implements PipeDataWriter { - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - try { - File dumpfile = getFileFromURI(uri); - - - MatrixCursor c = new MatrixCursor(projection); - - Object[] row = new Object[projection.length]; - int i=0; - for (String r:projection) { - if(r.equals(OpenableColumns.SIZE)) - row[i] = dumpfile.length(); - if(r.equals(OpenableColumns.DISPLAY_NAME)) - row[i] = dumpfile.getName(); - i++; - } - c.addRow(row); - return c; - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; - } - - - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - // Don't support inserts. - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - // Don't support deletes. - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - // Don't support updates. - return 0; - } - - @Override - public String getType(Uri uri) { - // For this sample, assume all files are .apks. - return "application/octet-stream"; - } - - @Override - public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { - File dumpfile = getFileFromURI(uri); - - try { - - InputStream is = new FileInputStream(dumpfile); - // Start a new thread that pipes the stream data back to the caller. - return new AssetFileDescriptor( - openPipeHelper(uri, null, null, is, this), 0, - dumpfile.length()); - } catch (IOException e) { - FileNotFoundException fnf = new FileNotFoundException("Unable to open minidump " + uri); - throw fnf; - } - } - - private File getFileFromURI(Uri uri) throws FileNotFoundException { - // Try to open an asset with the given name. - String path = uri.getPath(); - if(path.startsWith("/")) - path = path.replaceFirst("/", ""); - - // I think this already random enough, no need for magic secure cookies - // 1f9563a4-a1f5-2165-255f2219-111823ef.dmp - if (!path.matches("^[0-9a-z-.]*(dmp|dmp.log)$")) - throw new FileNotFoundException("url not in expect format " + uri); - File cachedir = getContext().getCacheDir(); - File dumpfile = new File(cachedir,path); - return dumpfile; - } - - @Override - public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, - Bundle opts, InputStream args) { - // Transfer data from the asset to the pipe the client is reading. - byte[] buffer = new byte[8192]; - int n; - FileOutputStream fout = new FileOutputStream(output.getFileDescriptor()); - try { - while ((n=args.read(buffer)) >= 0) { - fout.write(buffer, 0, n); - } - } catch (IOException e) { - Log.i("OpenVPNFileProvider", "Failed transferring", e); - } finally { - try { - args.close(); - } catch (IOException e) { - } - try { - fout.close(); - } catch (IOException e) { - } - } - } -} diff --git a/src/de/blinkt/openvpn/FileSelect.java b/src/de/blinkt/openvpn/FileSelect.java deleted file mode 100644 index b6239433..00000000 --- a/src/de/blinkt/openvpn/FileSelect.java +++ /dev/null @@ -1,207 +0,0 @@ -package de.blinkt.openvpn; - - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import android.app.ActionBar; -import android.app.ActionBar.Tab; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.util.Base64; - -public class FileSelect extends Activity { - public static final String RESULT_DATA = "RESULT_PATH"; - public static final String START_DATA = "START_DATA"; - public static final String WINDOW_TITLE = "WINDOW_TILE"; - public static final String NO_INLINE_SELECTION = "de.blinkt.openvpn.NO_INLINE_SELECTION"; - public static final String SHOW_CLEAR_BUTTON = "de.blinkt.openvpn.SHOW_CLEAR_BUTTON"; - public static final String DO_BASE64_ENCODE = "de.blinkt.openvpn.BASE64ENCODE"; - - private FileSelectionFragment mFSFragment; - private InlineFileTab mInlineFragment; - private String mData; - private Tab inlineFileTab; - private Tab fileExplorerTab; - private boolean mNoInline; - private boolean mShowClear; - private boolean mBase64Encode; - - - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.file_dialog); - - mData = getIntent().getStringExtra(START_DATA); - if(mData==null) - mData=Environment.getExternalStorageDirectory().getPath(); - - String title = getIntent().getStringExtra(WINDOW_TITLE); - int titleId = getIntent().getIntExtra(WINDOW_TITLE, 0); - if(titleId!=0) - title =getString(titleId); - if(title!=null) - setTitle(title); - - mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false); - mShowClear = getIntent().getBooleanExtra(SHOW_CLEAR_BUTTON, false); - mBase64Encode = getIntent().getBooleanExtra(DO_BASE64_ENCODE, false); - - ActionBar bar = getActionBar(); - bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - fileExplorerTab = bar.newTab().setText(R.string.file_explorer_tab); - inlineFileTab = bar.newTab().setText(R.string.inline_file_tab); - - mFSFragment = new FileSelectionFragment(); - fileExplorerTab.setTabListener(new MyTabsListener(this, mFSFragment)); - bar.addTab(fileExplorerTab); - - if(!mNoInline) { - mInlineFragment = new InlineFileTab(); - inlineFileTab.setTabListener(new MyTabsListener(this, mInlineFragment)); - bar.addTab(inlineFileTab); - } else { - mFSFragment.setNoInLine(); - } - - - } - - protected boolean showClear() { - if(mData == null || mData.equals("")) - return false; - else - return mShowClear; - } - - protected class MyTabsListener implements ActionBar.TabListener - { - private Fragment mFragment; - private boolean mAdded=false; - - public MyTabsListener( Activity activity, Fragment fragment){ - this.mFragment = fragment; - } - - public void onTabSelected(Tab tab, FragmentTransaction ft) { - // Check if the fragment is already initialized - if (!mAdded) { - // If not, instantiate and add it to the activity - ft.add(android.R.id.content, mFragment); - mAdded =true; - } else { - // If it exists, simply attach it in order to show it - ft.attach(mFragment); - } - } - - @Override - public void onTabUnselected(Tab tab, FragmentTransaction ft) { - ft.detach(mFragment); - } - - @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) { - - } - } - - public void importFile(String path) { - File ifile = new File(path); - Exception fe = null; - try { - - String data =VpnProfile.INLINE_TAG; - - byte[] filedata = readBytesFromFile(ifile) ; - if(mBase64Encode) - data += Base64.encodeToString(filedata, Base64.DEFAULT); - else - data += new String(filedata); - - mData =data; - mInlineFragment.setData(data); - getActionBar().selectTab(inlineFileTab); - } catch (FileNotFoundException e) { - fe = e; - } catch (IOException e) { - fe =e; - } - if(fe!=null) { - Builder ab = new AlertDialog.Builder(this); - ab.setTitle(R.string.error_importing_file); - ab.setMessage(getString(R.string.import_error_message) + "\n" + fe.getLocalizedMessage()); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - private byte[] readBytesFromFile(File file) throws IOException { - InputStream input = new FileInputStream(file); - - long len= file.length(); - - - // Create the byte array to hold the data - byte[] bytes = new byte[(int) len]; - - // Read in the bytes - int offset = 0; - int bytesRead = 0; - while (offset < bytes.length - && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { - offset += bytesRead; - } - - input.close(); - return bytes; - } - - - public void setFile(String path) { - Intent intent = new Intent(); - intent.putExtra(RESULT_DATA, path); - setResult(Activity.RESULT_OK,intent); - finish(); - } - - public String getSelectPath() { - if(!mData.startsWith(VpnProfile.INLINE_TAG)) - return mData; - else - return Environment.getExternalStorageDirectory().getPath(); - } - - public CharSequence getInlineData() { - if(mData.startsWith(VpnProfile.INLINE_TAG)) - return mData.substring(VpnProfile.INLINE_TAG.length()); - else - return ""; - } - - public void clearData() { - Intent intent = new Intent(); - intent.putExtra(RESULT_DATA, (String)null); - setResult(Activity.RESULT_OK,intent); - finish(); - - } - - public void saveInlineData(String string) { - Intent intent = new Intent(); - intent.putExtra(RESULT_DATA, string); - setResult(Activity.RESULT_OK,intent); - finish(); - - } -} diff --git a/src/de/blinkt/openvpn/FileSelectLayout.java b/src/de/blinkt/openvpn/FileSelectLayout.java deleted file mode 100644 index 93e438fc..00000000 --- a/src/de/blinkt/openvpn/FileSelectLayout.java +++ /dev/null @@ -1,91 +0,0 @@ -package de.blinkt.openvpn; - -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; - - -public class FileSelectLayout extends LinearLayout implements OnClickListener { - - private TextView mDataView; - private String mData; - private Fragment mFragment; - private int mTaskId; - private Button mSelectButton; - private boolean mBase64Encode; - private String mTitle; - private boolean mShowClear; - - public FileSelectLayout( Context context,AttributeSet attrset) { - super(context,attrset); - inflate(getContext(), R.layout.file_select, this); - - TypedArray ta = context.obtainStyledAttributes(attrset,R.styleable.FileSelectLayout); - - mTitle = ta.getString(R.styleable.FileSelectLayout_title); - - TextView tview = (TextView) findViewById(R.id.file_title); - tview.setText(mTitle); - - mDataView = (TextView) findViewById(R.id.file_selected_item); - mSelectButton = (Button) findViewById(R.id.file_select_button); - mSelectButton.setOnClickListener(this); - - } - - public void setFragment(Fragment fragment, int i) - { - mTaskId = i; - mFragment = fragment; - } - - public void getCertificateFileDialog() { - Intent startFC = new Intent(getContext(),FileSelect.class); - startFC.putExtra(FileSelect.START_DATA, mData); - startFC.putExtra(FileSelect.WINDOW_TITLE,mTitle); - if(mBase64Encode) - startFC.putExtra(FileSelect.DO_BASE64_ENCODE, true); - if(mShowClear) - startFC.putExtra(FileSelect.SHOW_CLEAR_BUTTON, true); - mFragment.startActivityForResult(startFC,mTaskId); - } - - - public String getData() { - return mData; - } - - public void setData(String data) { - mData = data; - if(data==null) - mDataView.setText(mFragment.getString(R.string.no_data)); - else if(mData.startsWith(VpnProfile.INLINE_TAG)) - mDataView.setText(R.string.inline_file_data); - else - mDataView.setText(data); - - } - - @Override - public void onClick(View v) { - if(v == mSelectButton) { - getCertificateFileDialog(); - } - } - - public void setBase64Encode() { - mBase64Encode =true; - } - - public void setShowClear() { - mShowClear=true; - } - -} diff --git a/src/de/blinkt/openvpn/FileSelectionFragment.java b/src/de/blinkt/openvpn/FileSelectionFragment.java deleted file mode 100644 index 2d8110e1..00000000 --- a/src/de/blinkt/openvpn/FileSelectionFragment.java +++ /dev/null @@ -1,264 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.TreeMap; - -import android.app.AlertDialog; -import android.app.ListFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ListView; -import android.widget.SimpleAdapter; -import android.widget.TextView; - -/** - * Activity para escolha de arquivos/diretorios. - * - * @author android - * - */ -public class FileSelectionFragment extends ListFragment { - - private static final String ITEM_KEY = "key"; - private static final String ITEM_IMAGE = "image"; - private static final String ROOT = "/"; - - - private List path = null; - private TextView myPath; - private ArrayList> mList; - - private Button selectButton; - - - private String parentPath; - private String currentPath = ROOT; - - - private String[] formatFilter = null; - - private File selectedFile; - private HashMap lastPositions = new HashMap(); - private String mStartPath; - private Button mImportFile; - private Button mClearButton; - private boolean mHideImport=false; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.file_dialog_main, container,false); - - myPath = (TextView) v.findViewById(R.id.path); - - selectButton = (Button) v.findViewById(R.id.fdButtonSelect); - selectButton.setEnabled(false); - selectButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (selectedFile != null) { - ((FileSelect) getActivity()).setFile(selectedFile.getPath()); - - } - } - }); - - mClearButton = (Button) v.findViewById(R.id.fdClear); - mClearButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - ((FileSelect) getActivity()).clearData(); - } - }); - if(!((FileSelect) getActivity()).showClear()) { - mClearButton.setVisibility(View.GONE); - mClearButton.setEnabled(false); - } - - - mImportFile = (Button) v.findViewById(R.id.importfile); - mImportFile.setEnabled(false); - mImportFile.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - ((FileSelect) getActivity()).importFile(selectedFile.getPath()); - } - }); - - if(mHideImport== true) { - mImportFile.setVisibility(View.GONE); - } - - - - return v; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mStartPath = ((FileSelect) getActivity()).getSelectPath(); - getDir(mStartPath); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - private void getDir(String dirPath) { - - boolean useAutoSelection = dirPath.length() < currentPath.length(); - - Integer position = lastPositions.get(parentPath); - - getDirImpl(dirPath); - - if (position != null && useAutoSelection) { - getListView().setSelection(position); - } - - } - - /** - * Monta a estrutura de arquivos e diretorios filhos do diretorio fornecido. - * - * @param dirPath - * Diretorio pai. - */ - private void getDirImpl(final String dirPath) { - - currentPath = dirPath; - - final List item = new ArrayList(); - path = new ArrayList(); - mList = new ArrayList>(); - - File f = new File(currentPath); - File[] files = f.listFiles(); - if (files == null) { - currentPath = ROOT; - f = new File(currentPath); - files = f.listFiles(); - } - - myPath.setText(getText(R.string.location) + ": " + currentPath); - - if (!currentPath.equals(ROOT)) { - - item.add(ROOT); - addItem(ROOT, R.drawable.folder); - path.add(ROOT); - - item.add("../"); - addItem("../", R.drawable.folder); - path.add(f.getParent()); - parentPath = f.getParent(); - - } - - TreeMap dirsMap = new TreeMap(); - TreeMap dirsPathMap = new TreeMap(); - TreeMap filesMap = new TreeMap(); - TreeMap filesPathMap = new TreeMap(); - for (File file : files) { - if (file.isDirectory()) { - String dirName = file.getName(); - dirsMap.put(dirName, dirName); - dirsPathMap.put(dirName, file.getPath()); - } else { - final String fileName = file.getName(); - final String fileNameLwr = fileName.toLowerCase(); - // se ha um filtro de formatos, utiliza-o - if (formatFilter != null) { - boolean contains = false; - for (int i = 0; i < formatFilter.length; i++) { - final String formatLwr = formatFilter[i].toLowerCase(); - if (fileNameLwr.endsWith(formatLwr)) { - contains = true; - break; - } - } - if (contains) { - filesMap.put(fileName, fileName); - filesPathMap.put(fileName, file.getPath()); - } - // senao, adiciona todos os arquivos - } else { - filesMap.put(fileName, fileName); - filesPathMap.put(fileName, file.getPath()); - } - } - } - item.addAll(dirsMap.tailMap("").values()); - item.addAll(filesMap.tailMap("").values()); - path.addAll(dirsPathMap.tailMap("").values()); - path.addAll(filesPathMap.tailMap("").values()); - - SimpleAdapter fileList = new SimpleAdapter(getActivity(), mList, R.layout.file_dialog_row, new String[] { - ITEM_KEY, ITEM_IMAGE }, new int[] { R.id.fdrowtext, R.id.fdrowimage }); - - for (String dir : dirsMap.tailMap("").values()) { - addItem(dir, R.drawable.folder); - } - - for (String file : filesMap.tailMap("").values()) { - addItem(file, R.drawable.file); - } - - fileList.notifyDataSetChanged(); - - setListAdapter(fileList); - - } - - private void addItem(String fileName, int imageId) { - HashMap item = new HashMap(); - item.put(ITEM_KEY, fileName); - item.put(ITEM_IMAGE, imageId); - mList.add(item); - } - - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - - File file = new File(path.get(position)); - - if (file.isDirectory()) { - selectButton.setEnabled(false); - mImportFile.setEnabled(false); - - if (file.canRead()) { - lastPositions.put(currentPath, position); - getDir(path.get(position)); - } else { - new AlertDialog.Builder(getActivity()).setIcon(R.drawable.icon) - .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder)) - .setPositiveButton("OK", null).show(); - } - } else { - selectedFile = file; - v.setSelected(true); - selectButton.setEnabled(true); - mImportFile.setEnabled(true); - } - } - - public void setNoInLine() { - mHideImport=true; - } - -} diff --git a/src/de/blinkt/openvpn/GeneralSettings.java b/src/de/blinkt/openvpn/GeneralSettings.java deleted file mode 100644 index b9c412c0..00000000 --- a/src/de/blinkt/openvpn/GeneralSettings.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.blinkt.openvpn; -import java.io.File; - -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; - -public class GeneralSettings extends PreferenceFragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.general_settings); - Preference loadtun = findPreference("loadTunModule"); - if(!isTunModuleAvailable()) - loadtun.setEnabled(false); - } - - private boolean isTunModuleAvailable() { - // Check if the tun module exists on the file system - if(new File("/system/lib/modules/tun.ko").length() > 10) - return true; - return false; - } - - - } \ No newline at end of file diff --git a/src/de/blinkt/openvpn/InlineFileTab.java b/src/de/blinkt/openvpn/InlineFileTab.java deleted file mode 100644 index 1104d290..00000000 --- a/src/de/blinkt/openvpn/InlineFileTab.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.blinkt.openvpn; - -import android.app.Fragment; -import android.os.Bundle; -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.EditText; - -public class InlineFileTab extends Fragment -{ - - private static final int MENU_SAVE = 0; - private EditText mInlineData; - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mInlineData.setText(((FileSelect)getActivity()).getInlineData()); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - - View v = inflater.inflate(R.layout.file_dialog_inline, container, false); - mInlineData =(EditText) v.findViewById(R.id.inlineFileData); - return v; - } - - public void setData(String data) { - if(mInlineData!=null) - mInlineData.setText(data); - - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_SAVE, 0, "Use inline data") - .setIcon(android.R.drawable.ic_menu_save) - .setAlphabeticShortcut('u') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==MENU_SAVE){ - ((FileSelect)getActivity()).saveInlineData(mInlineData.getText().toString()); - return true; - } - return super.onOptionsItemSelected(item); - } - -} \ No newline at end of file diff --git a/src/de/blinkt/openvpn/LaunchVPN.java b/src/de/blinkt/openvpn/LaunchVPN.java deleted file mode 100644 index 32ebe33d..00000000 --- a/src/de/blinkt/openvpn/LaunchVPN.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.blinkt.openvpn; - -import java.io.IOException; -import java.util.Collection; -import java.util.Vector; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ListActivity; -import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.VpnService; -import android.os.Bundle; -import android.os.Parcelable; -import android.preference.PreferenceManager; -import android.text.InputType; -import android.text.method.PasswordTransformationMethod; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -/** - * This Activity actually handles two stages of a launcher shortcut's life cycle. - * - * 1. Your application offers to provide shortcuts to the launcher. When - * the user installs a shortcut, an activity within your application - * generates the actual shortcut and returns it to the launcher, where it - * is shown to the user as an icon. - * - * 2. Any time the user clicks on an installed shortcut, an intent is sent. - * Typically this would then be handled as necessary by an activity within - * your application. - * - * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form - * of an {@link android.content.Intent} that the launcher will use to create the shortcut. - * - * You can also implement this in an interactive way, by having your activity actually present - * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, - * media item, or action. - * - * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents - * of the incoming {@link android.content.Intent}. - * - * In a real application, you would probably use the shortcut intent to display specific content - * or start a particular operation. - */ -public class LaunchVPN extends ListActivity implements OnItemClickListener { - - public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID"; - public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; - public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";; - - private static final int START_VPN_PROFILE= 70; - - - private ProfileManager mPM; - private VpnProfile mSelectedProfile; - private boolean mhideLog=false; - - private boolean mCmfixed=false; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mPM =ProfileManager.getInstance(this); - - } - - @Override - protected void onStart() { - super.onStart(); - // Resolve the intent - - final Intent intent = getIntent(); - final String action = intent.getAction(); - - // If the intent is a request to create a shortcut, we'll do that and exit - - - if(Intent.ACTION_MAIN.equals(action)) { - // we got called to be the starting point, most likely a shortcut - String shortcutUUID = intent.getStringExtra( EXTRA_KEY); - String shortcutName = intent.getStringExtra( EXTRA_NAME); - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - - VpnProfile profileToConnect = ProfileManager.get(shortcutUUID); - if(shortcutName != null && profileToConnect ==null) - profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); - - if(profileToConnect ==null) { - OpenVPN.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - return; - } - - mSelectedProfile = profileToConnect; - launchVPN(); - - } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { - createListView(); - } - } - - private void createListView() { - ListView lv = getListView(); - //lv.setTextFilterEnabled(true); - - Collection vpnlist = mPM.getProfiles(); - - Vector vpnnames=new Vector(); - for (VpnProfile vpnProfile : vpnlist) { - vpnnames.add(vpnProfile.mName); - } - - - - ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnnames); - lv.setAdapter(adapter); - - lv.setOnItemClickListener(this); - } - - /** - * This function creates a shortcut and returns it to the caller. There are actually two - * intents that you will send back. - * - * The first intent serves as a container for the shortcut and is returned to the launcher by - * setResult(). This intent must contain three fields: - * - *
    - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with - * the shortcut.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a - * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as - * a drawable resource.
  • - *
- * - * If you use a simple drawable resource, note that you must wrapper it using - * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so - * that the launcher can access resources that are stored in your application's .apk file. If - * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras - * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. - * - * The shortcut intent can be any intent that you wish the launcher to send, when the user - * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} - * with an appropriate Uri for your content, but any Intent will work here as long as it - * triggers the desired action within your Activity. - * @param profile - */ - private void setupShortcut(VpnProfile profile) { - // First, set up the shortcut intent. For this example, we simply create an intent that - // will bring us directly back to this activity. A more typical implementation would use a - // data Uri in order to display a more specific result, or a custom action in order to - // launch a specific operation. - - Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClass(this, LaunchVPN.class); - shortcutIntent.putExtra(EXTRA_KEY,profile.getUUID().toString()); - - // Then, set up the container intent (the response to the caller) - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); - Parcelable iconResource = Intent.ShortcutIconResource.fromContext( - this, R.drawable.icon); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); - - // Now, return the result to the launcher - - setResult(RESULT_OK, intent); - } - - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - String profilename = ((TextView) view).getText().toString(); - - VpnProfile profile = mPM.getProfileByName(profilename); - - // if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { - setupShortcut(profile); - finish(); - return; - // } - - } - - - - private void askForPW(final int type) { - - final EditText entry = new EditText(this); - entry.setSingleLine(); - entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - entry.setTransformationMethod(new PasswordTransformationMethod()); - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Need " + getString(type)); - dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); - dialog.setView(entry); - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String pw = entry.getText().toString(); - if(type == R.string.password) { - mSelectedProfile.mTransientPW = pw; - } else { - mSelectedProfile.mTransientPCKS12PW = pw; - } - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); - - } - - }); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - - dialog.create().show(); - - } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if(requestCode==START_VPN_PROFILE) { - if(resultCode == Activity.RESULT_OK) { - int needpw = mSelectedProfile.needUserPWInput(); - if(needpw !=0) { - askForPW(needpw); - } else { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showlogwindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showlogwindow) - showLogWindow(); - new startOpenVpnThread().start(); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish - finish(); - } - } - } - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(),LogWindow.class); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } - - void showConfigErrorDialog(int vpnok) { - AlertDialog.Builder d = new AlertDialog.Builder(this); - d.setTitle(R.string.config_error_found); - d.setMessage(vpnok); - d.setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - - } - }); - d.show(); - } - - void launchVPN () { - int vpnok = mSelectedProfile.checkProfile(this); - if(vpnok!= R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } - - Intent intent = VpnService.prepare(this); - // Check if we want to fix /dev/tun - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); - boolean loadTunModule = prefs.getBoolean("loadTunModule", false); - - if(loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - - if(usecm9fix && !mCmfixed ) { - execeuteSUcmd("chown system /dev/tun"); - } - - - if (intent != null) { - // Start the query - try { - startActivityForResult(intent, START_VPN_PROFILE); - } catch (ActivityNotFoundException ane) { - // Shame on you Sony! At least one user reported that - // an official Sony Xperia Arc S image triggers this exception - OpenVPN.logError(R.string.no_vpn_support_image); - showLogWindow(); - } - } else { - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); - } - - } - - private void execeuteSUcmd(String command) { - ProcessBuilder pb = new ProcessBuilder(new String[] {"su","-c",command}); - try { - Process p = pb.start(); - int ret = p.waitFor(); - if(ret ==0) - mCmfixed=true; - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private class startOpenVpnThread extends Thread { - - @Override - public void run() { - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); - finish(); - - } - - } - - -} diff --git a/src/de/blinkt/openvpn/LogWindow.java b/src/de/blinkt/openvpn/LogWindow.java deleted file mode 100644 index 88615e12..00000000 --- a/src/de/blinkt/openvpn/LogWindow.java +++ /dev/null @@ -1,350 +0,0 @@ -package de.blinkt.openvpn; - -import java.util.Vector; - -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.ListActivity; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.database.DataSetObserver; -import android.os.Bundle; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; -import de.blinkt.openvpn.OpenVPN.LogItem; -import de.blinkt.openvpn.OpenVPN.LogListener; -import de.blinkt.openvpn.OpenVPN.StateListener; - -public class LogWindow extends ListActivity implements StateListener { - private static final int START_VPN_CONFIG = 0; - private String[] mBconfig=null; - - - class LogWindowListAdapter implements ListAdapter, LogListener, Callback { - - private static final int MESSAGE_NEWLOG = 0; - - private static final int MESSAGE_CLEARLOG = 1; - - private Vector myEntries=new Vector(); - - private Handler mHandler; - - private Vector observers=new Vector(); - - - public LogWindowListAdapter() { - initLogBuffer(); - - if (mHandler == null) { - mHandler = new Handler(this); - } - - OpenVPN.addLogListener(this); - } - - - - private void initLogBuffer() { - myEntries.clear(); - for (LogItem litem : OpenVPN.getlogbuffer()) { - myEntries.add(litem.getString(getContext())); - } - } - - String getLogStr() { - String str = ""; - for(String entry:myEntries) { - str+=entry + '\n'; - } - return str; - } - - - private void shareLog() { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file)); - shareIntent.setType("text/plain"); - startActivity(Intent.createChooser(shareIntent, "Send Logfile")); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - observers.add(observer); - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - observers.remove(observer); - } - - @Override - public int getCount() { - return myEntries.size(); - } - - @Override - public Object getItem(int position) { - return myEntries.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView v; - if(convertView==null) - v = new TextView(getBaseContext()); - else - v = (TextView) convertView; - v.setText(myEntries.get(position)); - return v; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return myEntries.isEmpty(); - - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public void newLog(LogItem logmessage) { - Message msg = Message.obtain(); - msg.what=MESSAGE_NEWLOG; - Bundle mbundle=new Bundle(); - mbundle.putString("logmessage", logmessage.getString(getBaseContext())); - msg.setData(mbundle); - mHandler.sendMessage(msg); - } - - @Override - public boolean handleMessage(Message msg) { - // We have been called - if(msg.what==MESSAGE_NEWLOG) { - - String logmessage = msg.getData().getString("logmessage"); - myEntries.add(logmessage); - - for (DataSetObserver observer : observers) { - observer.onChanged(); - } - } else if (msg.what == MESSAGE_CLEARLOG) { - initLogBuffer(); - for (DataSetObserver observer : observers) { - observer.onInvalidated(); - } - } - - return true; - } - - void clearLog() { - // Actually is probably called from GUI Thread as result of the user - // pressing a button. But better safe than sorry - OpenVPN.clearLog(); - OpenVPN.logMessage(0,"","Log cleared."); - mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); - } - } - - - - private LogWindowListAdapter ladapter; - private TextView mSpeedView; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==R.id.clearlog) { - ladapter.clearLog(); - return true; - } else if(item.getItemId()==R.id.cancel){ - Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.title_cancel); - builder.setMessage(R.string.cancel_connection_query); - builder.setNegativeButton(android.R.string.no, null); - builder.setPositiveButton(android.R.string.yes, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ProfileManager.setConntectedVpnProfileDisconnected(getApplicationContext()); - OpenVpnManagementThread.stopOpenVPN(); - } - }); - - builder.show(); - return true; - } else if(item.getItemId()==R.id.info) { - if(mBconfig==null) - OpenVPN.triggerLogBuilderConfig(); - - } else if(item.getItemId()==R.id.send) { - ladapter.shareLog(); - } else if(item.getItemId()==R.id.edit_vpn) { - VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); - - if(lastConnectedprofile!=null) { - Intent vprefintent = new Intent(this,VPNPreferences.class) - .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); - startActivityForResult(vprefintent,START_VPN_CONFIG); - } else { - Toast.makeText(this, R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); - } - - - } - - return super.onOptionsItemSelected(item); - - } - - protected Context getContext() { - return this; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.logmenu, menu); - return true; - } - - - @Override - protected void onResume() { - super.onResume(); - OpenVPN.addStateListener(this); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == START_VPN_CONFIG && resultCode==RESULT_OK) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - - final VpnProfile profile = ProfileManager.get(configuredVPN); - ProfileManager.getInstance(this).saveProfile(this, profile); - // Name could be modified, reset List adapter - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.configuration_changed); - dialog.setMessage(R.string.restart_vpn_after_change); - - - dialog.setPositiveButton(R.string.restart, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(getBaseContext(), LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - } - - - }); - dialog.setNegativeButton(R.string.ignore, null); - dialog.create().show(); - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected void onStop() { - super.onStop(); - OpenVPN.removeStateListener(this); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.logwindow); - ListView lv = getListView(); - - lv.setOnItemLongClickListener(new OnItemLongClickListener() { - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - ClipboardManager clipboard = (ClipboardManager) - getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); - clipboard.setPrimaryClip(clip); - Toast.makeText(getBaseContext(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); - return true; - } - }); - - ladapter = new LogWindowListAdapter(); - lv.setAdapter(ladapter); - - mSpeedView = (TextView) findViewById(R.id.speed); - } - - @Override - public void updateState(final String status,final String logmessage, final int resid) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - String prefix=getString(resid) + ":"; - if (status.equals("BYTECOUNT") || status.equals("NOPROCESS") ) - prefix=""; - mSpeedView.setText(prefix + logmessage); - } - }); - - } - - @Override - protected void onDestroy() { - super.onDestroy(); - OpenVPN.removeLogListener(ladapter); - } - -} diff --git a/src/de/blinkt/openvpn/MainActivity.java b/src/de/blinkt/openvpn/MainActivity.java deleted file mode 100644 index 57506c4f..00000000 --- a/src/de/blinkt/openvpn/MainActivity.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.blinkt.openvpn; - -import java.util.List; - -import android.content.Intent; -import android.preference.PreferenceActivity; - -public class MainActivity extends PreferenceActivity { - - @Override - public void onBuildHeaders(List
target) { - loadHeadersFromResource(R.xml.main_headers, target); - - - String translatedby = getString(R.string.translationby); - if(!translatedby.equals("") && !translatedby.contains("Arne Schwabe")) { - Header translation = new Header(); - translation.title = getString(R.string.translation); - translation.summary = translatedby; - target.add(translation); - } - - if(SendDumpActivity.getLastestDump(this)!=null) { - Header sendDump = new Header(); - sendDump.titleRes = R.string.send_minidump; - sendDump.summaryRes = R.string.send_minidump_summary; - sendDump.intent = new Intent(this,SendDumpActivity.class); - target.add(sendDump); - } - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - System.out.println(data); - - - } - - -} diff --git a/src/de/blinkt/openvpn/NetworkSateReceiver.java b/src/de/blinkt/openvpn/NetworkSateReceiver.java deleted file mode 100644 index e20c8e52..00000000 --- a/src/de/blinkt/openvpn/NetworkSateReceiver.java +++ /dev/null @@ -1,85 +0,0 @@ -package de.blinkt.openvpn; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.NetworkInfo.State; -import android.preference.PreferenceManager; - -public class NetworkSateReceiver extends BroadcastReceiver { - private int lastNetwork=-1; - private OpenVpnManagementThread mManangement; - - private String lastStateMsg=null; - - public NetworkSateReceiver(OpenVpnManagementThread managementThread) { - super(); - mManangement = managementThread; - } - - @Override - public void onReceive(Context context, Intent intent) { - NetworkInfo networkInfo = getCurrentNetworkInfo(context); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); - - String netstatestring; - if(networkInfo==null) - netstatestring = "not connected"; - else { - String subtype = networkInfo.getSubtypeName(); - if(subtype==null) - subtype = ""; - String extrainfo = networkInfo.getExtraInfo(); - if(extrainfo==null) - extrainfo=""; - - /* - if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { - WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); - extrainfo+=wifiinfo.getBSSID(); - - subtype += wifiinfo.getNetworkId(); - }*/ - - - - netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(), - networkInfo.getDetailedState(),extrainfo,subtype ); - } - - - - if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) { - int newnet = networkInfo.getType(); - - if(sendusr1 && lastNetwork!=newnet) - mManangement.reconnect(); - - lastNetwork = newnet; - } else if (networkInfo==null) { - // Not connected, stop openvpn, set last connected network to no network - lastNetwork=-1; - if(sendusr1) - mManangement.signalusr1(); - } - - if(!netstatestring.equals(lastStateMsg)) - OpenVPN.logInfo(R.string.netstatus, netstatestring); - lastStateMsg=netstatestring; - - } - - private NetworkInfo getCurrentNetworkInfo(Context context) { - ConnectivityManager conn = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - - NetworkInfo networkInfo = conn.getActiveNetworkInfo(); - return networkInfo; - } - -} diff --git a/src/de/blinkt/openvpn/OnBootReceiver.java b/src/de/blinkt/openvpn/OnBootReceiver.java deleted file mode 100644 index 032501b6..00000000 --- a/src/de/blinkt/openvpn/OnBootReceiver.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.blinkt.openvpn; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -public class OnBootReceiver extends BroadcastReceiver { - - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED - @Override - public void onReceive(Context context, Intent intent) { - - final String action = intent.getAction(); - - if(Intent.ACTION_BOOT_COMPLETED.equals(action)) { - VpnProfile bootProfile = ProfileManager.getOnBootProfile(context); - if(bootProfile != null) { - lauchVPN(bootProfile, context); - } - } - } - - void lauchVPN(VpnProfile profile,Context context) { - Intent startVpnIntent = new Intent(Intent.ACTION_MAIN); - startVpnIntent.setClass(context, LaunchVPN.class); - startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString()); - startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - - context.startActivity(startVpnIntent); - } -} diff --git a/src/de/blinkt/openvpn/OpenVPN.java b/src/de/blinkt/openvpn/OpenVPN.java deleted file mode 100644 index 8d6cb4c8..00000000 --- a/src/de/blinkt/openvpn/OpenVPN.java +++ /dev/null @@ -1,243 +0,0 @@ -package de.blinkt.openvpn; - -import java.util.LinkedList; -import java.util.Locale; -import java.util.Vector; - - -import android.content.Context; -import android.os.Build; - -public class OpenVPN { - - - public static LinkedList logbuffer; - - private static Vector logListener; - private static Vector stateListener; - private static String[] mBconfig; - - private static String mLaststatemsg; - - private static String mLaststate; - - private static int mLastStateresid=R.string.state_noprocess; - - static { - logbuffer = new LinkedList(); - logListener = new Vector(); - stateListener = new Vector(); - logInformation(); - } - - public static class LogItem { - public static final int ERROR = 1; - public static final int INFO = 2; - public static final int VERBOSE = 3; - - private Object [] mArgs = null; - private String mMessage = null; - private int mRessourceId; - // Default log priority - int mLevel = INFO; - - public LogItem(int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - } - - - public LogItem(int loglevel,int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - mLevel = loglevel; - } - - - public LogItem(String message) { - mMessage = message; - } - - public LogItem(int loglevel, String msg) { - mLevel = loglevel; - mMessage = msg; - } - - - public LogItem(int loglevel, int ressourceId) { - mRessourceId =ressourceId; - mLevel = loglevel; - } - - - public String getString(Context c) { - if(mMessage !=null) { - return mMessage; - } else { - if(c!=null) { - if(mArgs == null) - return c.getString(mRessourceId); - else - return c.getString(mRessourceId,mArgs); - } else { - String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); - if(mArgs !=null) - for(Object o:mArgs) - str += "|" + o.toString(); - return str; - } - } - } - } - - private static final int MAXLOGENTRIES = 500; - - - public static final String MANAGMENT_PREFIX = "M:"; - - - - - - - public interface LogListener { - void newLog(LogItem logItem); - } - - public interface StateListener { - void updateState(String state, String logmessage, int localizedResId); - } - - synchronized static void logMessage(int level,String prefix, String message) - { - newlogItem(new LogItem(prefix + message)); - - } - - synchronized static void clearLog() { - logbuffer.clear(); - logInformation(); - } - - private static void logInformation() { - - logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); - } - - public synchronized static void addLogListener(LogListener ll){ - logListener.add(ll); - } - - public synchronized static void removeLogListener(LogListener ll) { - logListener.remove(ll); - } - - - public synchronized static void addStateListener(StateListener sl){ - stateListener.add(sl); - if(mLaststate!=null) - sl.updateState(mLaststate, mLaststatemsg, mLastStateresid); - } - - private static int getLocalizedState(String state){ - if (state.equals("CONNECTING")) - return R.string.state_connecting; - else if (state.equals("WAIT")) - return R.string.state_wait; - else if (state.equals("AUTH")) - return R.string.state_auth; - else if (state.equals("GET_CONFIG")) - return R.string.state_get_config; - else if (state.equals("ASSIGN_IP")) - return R.string.state_assign_ip; - else if (state.equals("ADD_ROUTES")) - return R.string.state_add_routes; - else if (state.equals("CONNECTED")) - return R.string.state_connected; - else if (state.equals("RECONNECTING")) - return R.string.state_reconnecting; - else if (state.equals("EXITING")) - return R.string.state_exiting; - else if (state.equals("RESOLVE")) - return R.string.state_resolve; - else if (state.equals("TCP_CONNECT")) - return R.string.state_tcp_connect; - else - return R.string.unknown_state; - - } - - public synchronized static void removeStateListener(StateListener sl) { - stateListener.remove(sl); - } - - - synchronized public static LogItem[] getlogbuffer() { - - // The stoned way of java to return an array from a vector - // brought to you by eclipse auto complete - return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]); - - } - public static void logBuilderConfig(String[] bconfig) { - mBconfig = bconfig; - } - public static void triggerLogBuilderConfig() { - if(mBconfig==null) { - logMessage(0, "", "No active interface"); - } else { - for (String item : mBconfig) { - logMessage(0, "", item); - } - } - - } - - public static void updateStateString (String state, String msg) { - int rid = getLocalizedState(state); - updateStateString(state, msg,rid); - } - - public synchronized static void updateStateString(String state, String msg, int resid) { - if (! "BYTECOUNT".equals(state)) { - mLaststate= state; - mLaststatemsg = msg; - mLastStateresid = resid; - } - - for (StateListener sl : stateListener) { - sl.updateState(state,msg,resid); - } - } - - public static void logInfo(String message) { - newlogItem(new LogItem(LogItem.INFO, message)); - } - - public static void logInfo(int ressourceId, Object... args) { - newlogItem(new LogItem(LogItem.INFO, ressourceId, args)); - } - - private static void newlogItem(LogItem logItem) { - logbuffer.addLast(logItem); - if(logbuffer.size()>MAXLOGENTRIES) - logbuffer.removeFirst(); - - for (LogListener ll : logListener) { - ll.newLog(logItem); - } - } - - public static void logError(String msg) { - newlogItem(new LogItem(LogItem.ERROR, msg)); - - } - - public static void logError(int ressourceId) { - newlogItem(new LogItem(LogItem.ERROR, ressourceId)); - } - public static void logError(int ressourceId, Object... args) { - newlogItem(new LogItem(LogItem.ERROR, ressourceId,args)); - } - -} diff --git a/src/de/blinkt/openvpn/OpenVPNThread.java b/src/de/blinkt/openvpn/OpenVPNThread.java deleted file mode 100644 index 1ef04f70..00000000 --- a/src/de/blinkt/openvpn/OpenVPNThread.java +++ /dev/null @@ -1,128 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.LinkedList; - -import android.util.Log; -import de.blinkt.openvpn.OpenVPN.LogItem; - -public class OpenVPNThread implements Runnable { - private static final String DUMP_PATH_STRING = "Dump path: "; - private static final String TAG = "OpenVPN"; - private String[] mArgv; - private Process mProcess; - private String mNativeDir; - private OpenVpnService mService; - private String mDumpPath; - - public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir) - { - mArgv = argv; - mNativeDir = nativelibdir; - mService = service; - } - - public void stopProcess() { - mProcess.destroy(); - } - - - - @Override - public void run() { - try { - Log.i(TAG, "Starting openvpn"); - startOpenVPNThreadArgs(mArgv); - Log.i(TAG, "Giving up"); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "OpenVPNThread Got " + e.toString()); - } finally { - int exitvalue = 0; - try { - exitvalue = mProcess.waitFor(); - } catch ( IllegalThreadStateException ite) { - OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage()); - } catch (InterruptedException ie) { - OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage()); - } - if( exitvalue != 0) - OpenVPN.logError("Process exited with exit value " + exitvalue); - - OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess); - if(mDumpPath!=null) { - try { - BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); - for(LogItem li :OpenVPN.getlogbuffer()){ - logout.write(li.getString(null) + "\n"); - } - logout.close(); - OpenVPN.logError(R.string.minidump_generated); - } catch (IOException e) { - OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage()); - } - } - - mService.processDied(); - Log.i(TAG, "Exiting"); - } - } - - private void startOpenVPNThreadArgs(String[] argv) { - LinkedList argvlist = new LinkedList(); - - for(String arg:argv) - argvlist.add(arg); - - ProcessBuilder pb = new ProcessBuilder(argvlist); - // Hack O rama - - // Hack until I find a good way to get the real library path - String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); - - String lbpath = pb.environment().get("LD_LIBRARY_PATH"); - if(lbpath==null) - lbpath = applibpath; - else - lbpath = lbpath + ":" + applibpath; - - if (!applibpath.equals(mNativeDir)) { - lbpath = lbpath + ":" + mNativeDir; - } - - pb.environment().put("LD_LIBRARY_PATH", lbpath); - pb.redirectErrorStream(true); - try { - mProcess = pb.start(); - // Close the output, since we don't need it - mProcess.getOutputStream().close(); - InputStream in = mProcess.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - while(true) { - String logline = br.readLine(); - if(logline==null) - return; - - if (logline.startsWith(DUMP_PATH_STRING)) - mDumpPath = logline.substring(DUMP_PATH_STRING.length()); - - - OpenVPN.logMessage(0, "P:", logline); - } - - - } catch (IOException e) { - OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage()); - e.printStackTrace(); - stopProcess(); - } - - - } -} diff --git a/src/de/blinkt/openvpn/OpenVpnManagementThread.java b/src/de/blinkt/openvpn/OpenVpnManagementThread.java deleted file mode 100644 index 3d68d943..00000000 --- a/src/de/blinkt/openvpn/OpenVpnManagementThread.java +++ /dev/null @@ -1,576 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.util.LinkedList; -import java.util.Vector; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - -import android.content.SharedPreferences; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.preference.PreferenceManager; -import android.util.Base64; -import android.util.Log; - -public class OpenVpnManagementThread implements Runnable { - - private static final String TAG = "openvpn"; - private LocalSocket mSocket; - private VpnProfile mProfile; - private OpenVpnService mOpenVPNService; - private LinkedList mFDList=new LinkedList(); - private int mBytecountinterval=2; - private long mLastIn=0; - private long mLastOut=0; - private LocalServerSocket mServerSocket; - private boolean mReleaseHold=true; - private boolean mWaitingForRelease=false; - private long mLastHoldRelease=0; - - private static Vector active=new Vector(); - - static private native void jniclose(int fdint); - static private native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; - - public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) { - mProfile = profile; - mServerSocket = mgmtsocket; - mOpenVPNService = openVpnService; - - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); - boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); - if(managemeNetworkState) - mReleaseHold=false; - - } - - static { - System.loadLibrary("opvpnutil"); - } - - public void managmentCommand(String cmd) { - if(mSocket!=null) { - try { - mSocket.getOutputStream().write(cmd.getBytes()); - mSocket.getOutputStream().flush(); - } catch (IOException e) { - // Ignore socket stack traces - } - } - } - - - @Override - public void run() { - Log.i(TAG, "Managment Socket Thread started"); - byte [] buffer =new byte[2048]; - // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad - - String pendingInput=""; - active.add(this); - - try { - // Wait for a client to connect - mSocket= mServerSocket.accept(); - InputStream instream = mSocket.getInputStream(); - - while(true) { - int numbytesread = instream.read(buffer); - if(numbytesread==-1) - return; - - FileDescriptor[] fds = null; - try { - fds = mSocket.getAncillaryFileDescriptors(); - } catch (IOException e) { - OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage()); - e.printStackTrace(); - } - if(fds!=null){ - - for (FileDescriptor fd : fds) { - - mFDList.add(fd); - } - } - - String input = new String(buffer,0,numbytesread,"UTF-8"); - - pendingInput += input; - - pendingInput=processInput(pendingInput); - - - - } - } catch (IOException e) { - e.printStackTrace(); - } - active.remove(this); - } - - //! Hack O Rama 2000! - private void protectFileDescriptor(FileDescriptor fd) { - Exception exp=null; - try { - Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); - int fdint = (Integer) getInt.invoke(fd); - - // You can even get more evil by parsing toString() and extract the int from that :) - - mOpenVPNService.protect(fdint); - - //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); - //pfd.close(); - jniclose(fdint); - return; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (NullPointerException e) { - exp =e; - } - if(exp!=null) { - exp.printStackTrace(); - Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); - OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage()); - } - } - - private String processInput(String pendingInput) { - - - while(pendingInput.contains("\n")) { - String[] tokens = pendingInput.split("\\r?\\n", 2); - processCommand(tokens[0]); - if(tokens.length == 1) - // No second part, newline was at the end - pendingInput=""; - else - pendingInput=tokens[1]; - } - return pendingInput; - } - - - private void processCommand(String command) { - if (command.startsWith(">") && command.contains(":")) { - String[] parts = command.split(":",2); - String cmd = parts[0].substring(1); - String argument = parts[1]; - - - if(cmd.equals("INFO")) { - // Ignore greeting from mgmt - //logStatusMessage(command); - }else if (cmd.equals("PASSWORD")) { - processPWCommand(argument); - } else if (cmd.equals("HOLD")) { - handleHold(); - } else if (cmd.equals("NEED-OK")) { - processNeedCommand(argument); - } else if (cmd.equals("BYTECOUNT")){ - processByteCount(argument); - } else if (cmd.equals("STATE")) { - processState(argument); - } else if (cmd.equals("PROXY")) { - processProxyCMD(argument); - } else if (cmd.equals("LOG")) { - String[] args = argument.split(",",3); - // 0 unix time stamp - // 1 log level N,I,E etc. - // 2 log message - OpenVPN.logMessage(0, "", args[2]); - } else if (cmd.equals("RSA_SIGN")) { - processSignCommand(argument); - } else { - OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command); - Log.i(TAG, "Got unrecognized command" + command); - } - } else if (command.startsWith("SUCCESS:")) { - // ignore - } else { - Log.i(TAG, "Got unrecognized line from managment" + command); - OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command); - } - } - private void handleHold() { - if(mReleaseHold) { - releaseHoldCmd(); - } else { - mWaitingForRelease=true; - OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork); - } - } - private void releaseHoldCmd() { - if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { - try { - Thread.sleep(3000); - } catch (InterruptedException e) {} - - } - mWaitingForRelease=false; - mLastHoldRelease = System.currentTimeMillis(); - managmentCommand("hold release\n"); - managmentCommand("bytecount " + mBytecountinterval + "\n"); - managmentCommand("state on\n"); - } - - public void releaseHold() { - mReleaseHold=true; - if(mWaitingForRelease) - releaseHoldCmd(); - - } - - private void processProxyCMD(String argument) { - String[] args = argument.split(",",3); - SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); - - - if(args.length >= 2) { - String proto = args[1]; - if(proto.equals("UDP")) { - proxyaddr=null; - } - } - - if(proxyaddr instanceof InetSocketAddress ){ - InetSocketAddress isa = (InetSocketAddress) proxyaddr; - - OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort()); - - String proxycmd = String.format("proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); - managmentCommand(proxycmd); - } else { - managmentCommand("proxy NONE\n"); - } - - } - private void processState(String argument) { - String[] args = argument.split(",",3); - String currentstate = args[1]; - if(args[2].equals(",,")) - OpenVPN.updateStateString(currentstate,""); - else - OpenVPN.updateStateString(currentstate,args[2]); - } - - - private void processByteCount(String argument) { - // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} - int comma = argument.indexOf(','); - long in = Long.parseLong(argument.substring(0, comma)); - long out = Long.parseLong(argument.substring(comma+1)); - - long diffin = in - mLastIn; - long diffout = out - mLastOut; - - mLastIn=in; - mLastOut=out; - - String netstat = String.format("In: %8s, %8s/s Out %8s, %8s/s", - humanReadableByteCount(in, false), - humanReadableByteCount(diffin, false), - humanReadableByteCount(out, false), - humanReadableByteCount(diffout, false)); - OpenVPN.updateStateString("BYTECOUNT",netstat); - - - } - - // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java - public static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - private void processNeedCommand(String argument) { - int p1 =argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - - String needed = argument.substring(p1+1, p2); - String extra = argument.split(":",2)[1]; - - String status = "ok"; - - - if (needed.equals("PROTECTFD")) { - FileDescriptor fdtoprotect = mFDList.pollFirst(); - protectFileDescriptor(fdtoprotect); - } else if (needed.equals("DNSSERVER")) { - mOpenVPNService.addDNS(extra); - }else if (needed.equals("DNSDOMAIN")){ - mOpenVPNService.setDomain(extra); - } else if (needed.equals("ROUTE")) { - String[] routeparts = extra.split(" "); - mOpenVPNService.addRoute(routeparts[0], routeparts[1]); - } else if (needed.equals("ROUTE6")) { - mOpenVPNService.addRoutev6(extra); - } else if (needed.equals("IFCONFIG")) { - String[] ifconfigparts = extra.split(" "); - int mtu = Integer.parseInt(ifconfigparts[2]); - mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); - } else if (needed.equals("IFCONFIG6")) { - mOpenVPNService.setLocalIPv6(extra); - - } else if (needed.equals("OPENTUN")) { - if(sendTunFD(needed,extra)) - return; - else - status="cancel"; - // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( - - } else { - Log.e(TAG,"Unkown needok command " + argument); - return; - } - - String cmd = String.format("needok '%s' %s\n", needed, status); - managmentCommand(cmd); - } - - private boolean sendTunFD (String needed, String extra) { - Exception exp = null; - if(!extra.equals("tun")) { - // We only support tun - String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra); - OpenVPN.logMessage(0, "", errmsg ); - - return false; - } - ParcelFileDescriptor pfd = mOpenVPNService.openTun(); - if(pfd==null) - return false; - - Method setInt; - int fdint = pfd.getFd(); - try { - setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); - FileDescriptor fdtosend = new FileDescriptor(); - - setInt.invoke(fdtosend,fdint); - - FileDescriptor[] fds = {fdtosend}; - mSocket.setFileDescriptorsForSend(fds); - - Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd); - // Trigger a send so we can close the fd on our side of the channel - // The API documentation fails to mention that it will not reset the file descriptor to - // be send and will happily send the file descriptor on every write ... - String cmd = String.format("needok '%s' %s\n", needed, "ok"); - managmentCommand(cmd); - - // Set the FileDescriptor to null to stop this mad behavior - mSocket.setFileDescriptorsForSend(null); - - pfd.close(); - - return true; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (IOException e) { - exp =e; - } - if(exp!=null) { - OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage()); - exp.printStackTrace(); - } - return false; - } - - private void processPWCommand(String argument) { - //argument has the form Need 'Private Key' password - // or ">PASSWORD:Verification Failed: '%s' ['%s']" - String needed; - - - - try{ - - int p1 = argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - needed = argument.substring(p1+1, p2); - if (argument.startsWith("Verification Failed")) { - proccessPWFailed(needed, argument.substring(p2+1)); - return; - } - } catch (StringIndexOutOfBoundsException sioob) { - OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument); - return; - } - - String pw=null; - - if(needed.equals("Private Key")) { - pw = mProfile.getPasswordPrivateKey(); - } else if (needed.equals("Auth")) { - String usercmd = String.format("username '%s' %s\n", - needed, VpnProfile.openVpnEscape(mProfile.mUsername)); - managmentCommand(usercmd); - pw = mProfile.getPasswordAuth(); - } - if(pw!=null) { - String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); - managmentCommand(cmd); - } else { - OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); - } - - } - - - - - private void proccessPWFailed(String needed, String args) { - OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed); - } - private void logStatusMessage(String command) { - OpenVPN.logMessage(0,"MGMT:", command); - } - - - public static boolean stopOpenVPN() { - boolean sendCMD=false; - for (OpenVpnManagementThread mt: active){ - mt.managmentCommand("signal SIGINT\n"); - sendCMD=true; - try { - if(mt.mSocket !=null) - mt.mSocket.close(); - } catch (IOException e) { - // Ignore close error on already closed socket - } - } - return sendCMD; - } - - public void signalusr1() { - mReleaseHold=false; - if(!mWaitingForRelease) - managmentCommand("signal SIGUSR1\n"); - } - - public void reconnect() { - signalusr1(); - releaseHold(); - } - - private void processSignCommand(String b64data) { - - PrivateKey privkey = mProfile.getKeystoreKey(); - Exception err =null; - - byte[] data = Base64.decode(b64data, Base64.DEFAULT); - - // The Jelly Bean *evil* Hack - // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider - if(Build.VERSION.SDK_INT==16){ - processSignJellyBeans(privkey,data); - return; - } - - - try{ - - - Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); - - rsasinger.init(Cipher.ENCRYPT_MODE, privkey); - - byte[] signed_bytes = rsasinger.doFinal(data); - String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); - managmentCommand("rsa-sig\n"); - managmentCommand(signed_string); - managmentCommand("\nEND\n"); - } catch (NoSuchAlgorithmException e){ - err =e; - } catch (InvalidKeyException e) { - err =e; - } catch (NoSuchPaddingException e) { - err =e; - } catch (IllegalBlockSizeException e) { - err =e; - } catch (BadPaddingException e) { - err =e; - } - if(err !=null) { - OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); - } - - } - - - private void processSignJellyBeans(PrivateKey privkey, byte[] data) { - Exception err =null; - try { - Method[] allm = privkey.getClass().getSuperclass().getDeclaredMethods(); - System.out.println(allm); - Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); - getKey.setAccessible(true); - - // Real object type is OpenSSLKey - Object opensslkey = getKey.invoke(privkey); - - getKey.setAccessible(false); - - Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); - - // integer pointer to EVP_pkey - getPkeyContext.setAccessible(true); - int pkey = (Integer) getPkeyContext.invoke(opensslkey); - getPkeyContext.setAccessible(false); - - byte[] signed_bytes = rsasign(data, pkey); - String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); - managmentCommand("rsa-sig\n"); - managmentCommand(signed_string); - managmentCommand("\nEND\n"); - - } catch (NoSuchMethodException e) { - err=e; - } catch (IllegalArgumentException e) { - err=e; - } catch (IllegalAccessException e) { - err=e; - } catch (InvocationTargetException e) { - err=e; - } catch (InvalidKeyException e) { - err=e; - } - if(err !=null) { - OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); - } - - } -} diff --git a/src/de/blinkt/openvpn/OpenVpnPreferencesFragment.java b/src/de/blinkt/openvpn/OpenVpnPreferencesFragment.java deleted file mode 100644 index 4cf3b10b..00000000 --- a/src/de/blinkt/openvpn/OpenVpnPreferencesFragment.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.blinkt.openvpn; - -import android.os.Bundle; -import android.preference.PreferenceFragment; - -public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { - - protected VpnProfile mProfile; - - protected abstract void loadSettings(); - protected abstract void saveSettings(); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile = ProfileManager.get(getActivity(),profileUUID); - getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - - } - - @Override - public void onPause() { - super.onPause(); - saveSettings(); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if(savedInstanceState!=null) { - String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); - mProfile = ProfileManager.get(getActivity(),profileUUID); - loadSettings(); - } - } - - @Override - public void onStop() { - // TODO Auto-generated method stub - super.onStop(); - } - - @Override - public void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); - saveSettings(); - outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); - } -} diff --git a/src/de/blinkt/openvpn/OpenVpnService.java b/src/de/blinkt/openvpn/OpenVpnService.java deleted file mode 100644 index 4dff943b..00000000 --- a/src/de/blinkt/openvpn/OpenVpnService.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.blinkt.openvpn; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Vector; - -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; -import android.net.VpnService; -import android.os.Binder; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Build; -import android.os.IBinder; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import de.blinkt.openvpn.OpenVPN.StateListener; - -public class OpenVpnService extends VpnService implements StateListener, Callback { - public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE"; - - private Thread mProcessThread=null; - - private Vector mDnslist=new Vector(); - - private VpnProfile mProfile; - - private String mDomain=null; - - private Vector mRoutes=new Vector(); - private Vector mRoutesv6=new Vector(); - - private CIDRIP mLocalIP=null; - - private OpenVpnManagementThread mSocketManager; - - private Thread mSocketManagerThread; - private int mMtu; - private String mLocalIPv6=null; - private NetworkSateReceiver mNetworkStateReceiver; - - private boolean mDisplayBytecount=false; - - private boolean mStarting=false; - - private long mConnecttime; - - - private static final int OPENVPN_STATUS = 1; - - public static final int PROTECT_FD = 0; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public OpenVpnService getService() { - // Return this instance of LocalService so clients can call public methods - return OpenVpnService.this; - } - } - - @Override - public IBinder onBind(Intent intent) { - String action = intent.getAction(); - if( action !=null && action.equals(START_SERVICE)) - return mBinder; - else - return super.onBind(intent); - } - - @Override - public void onRevoke() { - OpenVpnManagementThread.stopOpenVPN(); - endVpnService(); - } - - // Similar to revoke but do not try to stop process - public void processDied() { - endVpnService(); - } - - private void endVpnService() { - mProcessThread=null; - OpenVPN.logBuilderConfig(null); - ProfileManager.setConntectedVpnProfileDisconnected(this); - if(!mStarting) { - stopSelf(); - stopForeground(true); - } - } - - private void showNotification(String msg, String tickerText, boolean lowpriority, long when) { - String ns = Context.NOTIFICATION_SERVICE; - NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); - - - int icon = R.drawable.ic_stat_vpn; - android.app.Notification.Builder nbuilder = new Notification.Builder(this); - - nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mName)); - nbuilder.setContentText(msg); - nbuilder.setOnlyAlertOnce(true); - nbuilder.setOngoing(true); - nbuilder.setContentIntent(getLogPendingIntent()); - nbuilder.setSmallIcon(icon); - if(when !=0) - nbuilder.setWhen(when); - - - // Try to set the priority available since API 16 (Jellybean) - jbNotificationExtras(lowpriority, nbuilder); - if(tickerText!=null) - nbuilder.setTicker(tickerText); - - @SuppressWarnings("deprecation") - Notification notification = nbuilder.getNotification(); - - - mNotificationManager.notify(OPENVPN_STATUS, notification); - startForeground(OPENVPN_STATUS, notification); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void jbNotificationExtras(boolean lowpriority, - android.app.Notification.Builder nbuilder) { - try { - if(lowpriority) { - Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); - // PRIORITY_MIN == -2 - setpriority.invoke(nbuilder, -2 ); - - nbuilder.setUsesChronometer(true); - /* PendingIntent cancelconnet=null; - - nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, - getString(R.string.cancel_connection),cancelconnet); */ - } - - //ignore exception - } catch (NoSuchMethodException nsm) { - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } catch (InvocationTargetException e) { - } - - } - - PendingIntent getLogPendingIntent() { - // Let the configure Button show the Log - Intent intent = new Intent(getBaseContext(),LogWindow.class); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - return startLW; - - } - - - private LocalServerSocket openManagmentInterface(int tries) { - // Could take a while to open connection - String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); - LocalSocket sock = new LocalSocket(); - - while(tries > 0 && !sock.isConnected()) { - try { - sock.bind(new LocalSocketAddress(socketname, - LocalSocketAddress.Namespace.FILESYSTEM)); - } catch (IOException e) { - // wait 300 ms before retrying - try { Thread.sleep(300); - } catch (InterruptedException e1) {} - - } - tries--; - } - - try { - LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor()); - return lss; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - - - } - - void registerNetworkStateReceiver() { - // Registers BroadcastReceiver to track network connection changes. - IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); - mNetworkStateReceiver = new NetworkSateReceiver(mSocketManager); - this.registerReceiver(mNetworkStateReceiver, filter); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE)) - return START_NOT_STICKY; - - - // Extract information from the intent. - String prefix = getPackageName(); - String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); - String nativelibdir = intent.getStringExtra(prefix + ".nativelib"); - String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); - - mProfile = ProfileManager.get(profileUUID); - - showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0); - - - OpenVPN.addStateListener(this); - - // Set a flag that we are starting a new VPN - mStarting=true; - // Stop the previous session by interrupting the thread. - if(OpenVpnManagementThread.stopOpenVPN()){ - // an old was asked to exit, wait 2s - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - - if (mProcessThread!=null) { - mProcessThread.interrupt(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - // An old running VPN should now be exited - mStarting=false; - - - // Open the Management Interface - LocalServerSocket mgmtsocket = openManagmentInterface(8); - - if(mgmtsocket!=null) { - // start a Thread that handles incoming messages of the managment socket - mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket,this); - mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread"); - mSocketManagerThread.start(); - OpenVPN.logInfo("started Socket Thread"); - registerNetworkStateReceiver(); - } - - - // Start a new session by creating a new thread. - OpenVPNThread processThread = new OpenVPNThread(this, argv,nativelibdir); - - mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); - mProcessThread.start(); - - ProfileManager.setConnectedVpnProfile(this, mProfile); - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - if (mProcessThread != null) { - mSocketManager.managmentCommand("signal SIGINT\n"); - - mProcessThread.interrupt(); - } - if (mNetworkStateReceiver!= null) { - this.unregisterReceiver(mNetworkStateReceiver); - } - - } - - - - public ParcelFileDescriptor openTun() { - Builder builder = new Builder(); - - if(mLocalIP==null && mLocalIPv6==null) { - OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr)); - return null; - } - - if(mLocalIP!=null) { - builder.addAddress(mLocalIP.mIp, mLocalIP.len); - } - - if(mLocalIPv6!=null) { - String[] ipv6parts = mLocalIPv6.split("/"); - builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1])); - } - - - for (String dns : mDnslist ) { - try { - builder.addDnsServer(dns); - } catch (IllegalArgumentException iae) { - OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage()); - } - } - - - builder.setMtu(mMtu); - - - for (CIDRIP route:mRoutes) { - try { - builder.addRoute(route.mIp, route.len); - } catch (IllegalArgumentException ia) { - OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); - } - } - - for(String v6route:mRoutesv6) { - try { - String[] v6parts = v6route.split("/"); - builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1])); - } catch (IllegalArgumentException ia) { - OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage()); - } - } - - if(mDomain!=null) - builder.addSearchDomain(mDomain); - - String bconfig[] = new String[6]; - - bconfig[0]= getString(R.string.last_openvpn_tun_config); - bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu); - bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist)); - bconfig[3] = getString(R.string.dns_domain_info, mDomain); - bconfig[4] = getString(R.string.routes_info, joinString(mRoutes)); - bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6)); - - String session = mProfile.mName; - if(mLocalIP!=null && mLocalIPv6!=null) - session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6); - else if (mLocalIP !=null) - session= getString(R.string.session_ipv4string, session, mLocalIP); - - builder.setSession(session); - - - OpenVPN.logBuilderConfig(bconfig); - - // No DNS Server, log a warning - if(mDnslist.size()==0) - OpenVPN.logInfo(R.string.warn_no_dns); - - // Reset information - mDnslist.clear(); - mRoutes.clear(); - mRoutesv6.clear(); - mLocalIP=null; - mLocalIPv6=null; - mDomain=null; - - builder.setConfigureIntent(getLogPendingIntent()); - - try { - ParcelFileDescriptor pfd = builder.establish(); - return pfd; - } catch (Exception e) { - OpenVPN.logMessage(0, "", getString(R.string.tun_open_error)); - OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage()); - OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful)); - return null; - } - - } - - - // Ugly, but java has no such method - private String joinString(Vector vec) { - String ret = ""; - if(vec.size() > 0){ - ret = vec.get(0).toString(); - for(int i=1;i < vec.size();i++) { - ret = ret + ", " + vec.get(i).toString(); - } - } - return ret; - } - - - - - - - public void addDNS(String dns) { - mDnslist.add(dns); - } - - - public void setDomain(String domain) { - if(mDomain==null) { - mDomain=domain; - } - } - - - public void addRoute(String dest, String mask) { - CIDRIP route = new CIDRIP(dest, mask); - if(route.len == 32 && !mask.equals("255.255.255.255")) { - OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask)); - } - - if(route.normalise()) - OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp)); - - mRoutes.add(route); - } - - public void addRoutev6(String extra) { - mRoutesv6.add(extra); - } - - - public void setLocalIP(String local, String netmask,int mtu, String mode) { - mLocalIP = new CIDRIP(local, netmask); - mMtu = mtu; - - if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { - // get the netmask as IP - long netint = CIDRIP.getInt(netmask); - if(Math.abs(netint - mLocalIP.getInt()) ==1) { - if(mode.equals("net30")) - mLocalIP.len=30; - else - mLocalIP.len=31; - } else { - OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode)); - } - } - } - - public void setLocalIPv6(String ipv6addr) { - mLocalIPv6 = ipv6addr; - } - - @Override - public void updateState(String state,String logmessage, int resid) { - // If the process is not running, ignore any state, - // Notification should be invisible in this state - if(mProcessThread==null) - return; - - // Display byte count only after being connected - - if("BYTECOUNT".equals(state)) { - if(mDisplayBytecount) { - showNotification(logmessage,null,true,mConnecttime); - } - } else { - if("CONNECTED".equals(state)) { - mDisplayBytecount = true; - mConnecttime = System.currentTimeMillis(); - } else { - mDisplayBytecount = false; - } - - // Other notifications are shown, - // This also mean we are no longer connected, ignore bytecount messages until next - // CONNECTED - String ticker = getString(resid); - showNotification(getString(resid) +" " + logmessage,ticker,false,0); - - } - } - - @Override - public boolean handleMessage(Message msg) { - Runnable r = msg.getCallback(); - if(r!=null){ - r.run(); - return true; - } else { - return false; - } - } -} diff --git a/src/de/blinkt/openvpn/ProfileManager.java b/src/de/blinkt/openvpn/ProfileManager.java deleted file mode 100644 index 9f17a68e..00000000 --- a/src/de/blinkt/openvpn/ProfileManager.java +++ /dev/null @@ -1,221 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.StreamCorruptedException; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; - -public class ProfileManager { - private static final String PREFS_NAME = "VPNList"; - - - - private static final String ONBOOTPROFILE = "onBootProfile"; - - - - private static ProfileManager instance; - - - - private static VpnProfile mLastConnectedVpn=null; - private HashMap profiles=new HashMap(); - private static VpnProfile tmpprofile=null; - - - public static VpnProfile get(String key) { - if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) - return tmpprofile; - - if(instance==null) - return null; - return instance.profiles.get(key); - - } - - - - private ProfileManager() { } - - private static void checkInstance(Context context) { - if(instance == null) { - instance = new ProfileManager(); - instance.loadVPNList(context); - } - } - - synchronized public static ProfileManager getInstance(Context context) { - checkInstance(context); - return instance; - } - - public static void setConntectedVpnProfileDisconnected(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - prefsedit.putString(ONBOOTPROFILE, null); - prefsedit.apply(); - - } - - public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - - prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); - prefsedit.apply(); - mLastConnectedVpn=connectedrofile; - - } - - public static VpnProfile getOnBootProfile(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - - boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); - - - String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null); - if(useStartOnBoot && mBootProfileUUID!=null) - return get(c, mBootProfileUUID); - else - return null; - } - - - - - public Collection getProfiles() { - return profiles.values(); - } - - public VpnProfile getProfileByName(String name) { - for (VpnProfile vpnp : profiles.values()) { - if(vpnp.getName().equals(name)) { - return vpnp; - } - } - return null; - } - - public void saveProfileList(Context context) { - SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Editor editor = sharedprefs.edit(); - editor.putStringSet("vpnlist", profiles.keySet()); - - // For reasing I do not understand at all - // Android saves my prefs file only one time - // if I remove the debug code below :( - int counter = sharedprefs.getInt("counter", 0); - editor.putInt("counter", counter+1); - editor.apply(); - - } - - public void addProfile(VpnProfile profile) { - profiles.put(profile.getUUID().toString(),profile); - - } - - public static void setTemporaryProfile(VpnProfile tmp) { - ProfileManager.tmpprofile = tmp; - } - - - public void saveProfile(Context context,VpnProfile profile) { - // First let basic settings save its state - - ObjectOutputStream vpnfile; - try { - vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); - - vpnfile.writeObject(profile); - vpnfile.flush(); - vpnfile.close(); - } catch (FileNotFoundException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - } catch (IOException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - } - - - private void loadVPNList(Context context) { - profiles = new HashMap(); - SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Set vlist = listpref.getStringSet("vpnlist", null); - Exception exp =null; - if(vlist==null){ - vlist = new HashSet(); - } - - for (String vpnentry : vlist) { - try { - ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); - VpnProfile vp = ((VpnProfile) vpnfile.readObject()); - - // Sanity check - if(vp==null || vp.mName==null || vp.getUUID()==null) - continue; - - profiles.put(vp.getUUID().toString(), vp); - - } catch (StreamCorruptedException e) { - exp=e; - } catch (FileNotFoundException e) { - exp=e; - } catch (IOException e) { - exp=e; - } catch (ClassNotFoundException e) { - exp=e; - } - if(exp!=null) { - exp.printStackTrace(); - } - } - } - - public int getNumberOfProfiles() { - return profiles.size(); - } - - - - public void removeProfile(Context context,VpnProfile profile) { - String vpnentry = profile.getUUID().toString(); - profiles.remove(vpnentry); - saveProfileList(context); - context.deleteFile(vpnentry + ".vp"); - if(mLastConnectedVpn==profile) - mLastConnectedVpn=null; - - } - - - - public static VpnProfile get(Context context, String profileUUID) { - checkInstance(context); - return get(profileUUID); - } - - - - public static VpnProfile getLastConnectedVpn() { - return mLastConnectedVpn; - } - -} diff --git a/src/de/blinkt/openvpn/ProxyDetection.java b/src/de/blinkt/openvpn/ProxyDetection.java deleted file mode 100644 index dfcfbf19..00000000 --- a/src/de/blinkt/openvpn/ProxyDetection.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.blinkt.openvpn; - -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.List; - -public class ProxyDetection { - static SocketAddress detectProxy(VpnProfile vp) { - // Construct a new url with https as protocol - try { - URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); - Proxy proxy = getFirstProxy(url); - - if(proxy==null) - return null; - SocketAddress addr = proxy.address(); - if (addr instanceof InetSocketAddress) { - return addr; - } - - } catch (MalformedURLException e) { - OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); - } catch (URISyntaxException e) { - OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); - } - return null; - } - - static Proxy getFirstProxy(URL url) throws URISyntaxException { - System.setProperty("java.net.useSystemProxies", "true"); - - List proxylist = ProxySelector.getDefault().select(url.toURI()); - - - if (proxylist != null) { - for (Proxy proxy: proxylist) { - SocketAddress addr = proxy.address(); - - if (addr != null) { - return proxy; - } - } - - } - return null; - } -} \ No newline at end of file diff --git a/src/de/blinkt/openvpn/SendDumpActivity.java b/src/de/blinkt/openvpn/SendDumpActivity.java deleted file mode 100644 index 8a09b535..00000000 --- a/src/de/blinkt/openvpn/SendDumpActivity.java +++ /dev/null @@ -1,60 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.File; -import java.util.ArrayList; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -public class SendDumpActivity extends Activity { - - protected void onStart() { - super.onStart(); - emailMiniDumps(); - finish(); - }; - - public void emailMiniDumps() - { - //need to "send multiple" to get more than one attachment - final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); - emailIntent.setType("*/*"); - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, - new String[]{"Arne Schwabe "}); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "OpenVPN Minidump"); - - emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe the issue you have experienced"); - - ArrayList uris = new ArrayList(); - - File ldump = getLastestDump(this); - if(ldump==null) { - OpenVPN.logError("No Minidump found!"); - } - - uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName())); - uris.add(Uri.parse("content://de.blinkt.openvpn.FileProvider/" + ldump.getName() + ".log")); - - emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); - startActivity(emailIntent); - } - - static public File getLastestDump(Context c) { - long newestDumpTime=0; - File newestDumpFile=null; - - for(File f:c.getCacheDir().listFiles()) { - if(!f.getName().endsWith(".dmp")) - continue; - - if (newestDumpTime < f.lastModified()) { - newestDumpTime = f.lastModified(); - newestDumpFile=f; - } - } - return newestDumpFile; - } -} diff --git a/src/de/blinkt/openvpn/Settings_Authentication.java b/src/de/blinkt/openvpn/Settings_Authentication.java deleted file mode 100644 index 6733ed0c..00000000 --- a/src/de/blinkt/openvpn/Settings_Authentication.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.blinkt.openvpn; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.SwitchPreference; - - -public class Settings_Authentication extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener, OnPreferenceClickListener { - private static final int SELECT_TLS_FILE = 23223232; - private CheckBoxPreference mExpectTLSCert; - private CheckBoxPreference mCheckRemoteCN; - private EditTextPreference mRemoteCN; - private ListPreference mTLSAuthDirection; - private Preference mTLSAuthFile; - private SwitchPreference mUseTLSAuth; - private EditTextPreference mCipher; - private String mTlsAuthFileData; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_authentification); - - mExpectTLSCert = (CheckBoxPreference) findPreference("remoteServerTLS"); - mCheckRemoteCN = (CheckBoxPreference) findPreference("checkRemoteCN"); - mRemoteCN = (EditTextPreference) findPreference("remotecn"); - mRemoteCN.setOnPreferenceChangeListener(this); - - mUseTLSAuth = (SwitchPreference) findPreference("useTLSAuth" ); - mTLSAuthFile = findPreference("tlsAuthFile"); - mTLSAuthDirection = (ListPreference) findPreference("tls_direction"); - - - mTLSAuthFile.setOnPreferenceClickListener(this); - - mCipher =(EditTextPreference) findPreference("cipher"); - mCipher.setOnPreferenceChangeListener(this); - - loadSettings(); - - } - - @Override - protected void loadSettings() { - - mExpectTLSCert.setChecked(mProfile.mExpectTLSCert); - mCheckRemoteCN.setChecked(mProfile.mCheckRemoteCN); - mRemoteCN.setText(mProfile.mRemoteCN); - onPreferenceChange(mRemoteCN, mProfile.mRemoteCN); - - mUseTLSAuth.setChecked(mProfile.mUseTLSAuth); - mTlsAuthFileData= mProfile.mTLSAuthFilename; - setTlsAuthSummary(mTlsAuthFileData); - mTLSAuthDirection.setValue(mProfile.mTLSAuthDirection); - mCipher.setText(mProfile.mCipher); - onPreferenceChange(mCipher, mProfile.mCipher); - } - - @Override - protected void saveSettings() { - mProfile.mExpectTLSCert=mExpectTLSCert.isChecked(); - mProfile.mCheckRemoteCN=mCheckRemoteCN.isChecked(); - mProfile.mRemoteCN=mRemoteCN.getText(); - - mProfile.mUseTLSAuth = mUseTLSAuth.isChecked(); - mProfile.mTLSAuthFilename = mTlsAuthFileData; - - if(mTLSAuthDirection.getValue()==null) - mProfile.mTLSAuthDirection=null; - else - mProfile.mTLSAuthDirection = mTLSAuthDirection.getValue().toString(); - - if(mCipher.getText()==null) - mProfile.mCipher=null; - else - mProfile.mCipher = mCipher.getText(); - - } - - - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if(preference==mRemoteCN) { - if ("".equals(newValue)) - preference.setSummary(mProfile.mServerName); - else - preference.setSummary((String)newValue); - } else if (preference == mCipher) { - preference.setSummary((CharSequence) newValue); - } - return true; - } - void startFileDialog() { - Intent startFC = new Intent(getActivity(),FileSelect.class); - startFC.putExtra(FileSelect.START_DATA, Environment.getExternalStorageDirectory().getPath()); - - startActivityForResult(startFC,SELECT_TLS_FILE); - } - @Override - public boolean onPreferenceClick(Preference preference) { - startFileDialog(); - return true; - - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if(requestCode==SELECT_TLS_FILE && resultCode == Activity.RESULT_OK){ - String result = data.getStringExtra(FileSelect.RESULT_DATA); - mTlsAuthFileData=result; - setTlsAuthSummary(result); - - } - } - - private void setTlsAuthSummary(String result) { - if(result==null) result = getString(R.string.no_certificate); - if(result.startsWith(VpnProfile.INLINE_TAG)) - mTLSAuthFile.setSummary(R.string.inline_file_data); - else - mTLSAuthFile.setSummary(result); - } -} \ No newline at end of file diff --git a/src/de/blinkt/openvpn/Settings_Basic.java b/src/de/blinkt/openvpn/Settings_Basic.java deleted file mode 100644 index 9c85094a..00000000 --- a/src/de/blinkt/openvpn/Settings_Basic.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.blinkt.openvpn; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Fragment; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.ToggleButton; -import de.blinkt.openvpn.R.id; - -public class Settings_Basic extends Fragment implements View.OnClickListener, OnItemSelectedListener, Callback { - private static final int CHOOSE_FILE_OFFSET = 1000; - private static final int UPDATE_ALIAS = 20; - - - - private TextView mServerAddress; - private TextView mServerPort; - private FileSelectLayout mClientCert; - private FileSelectLayout mCaCert; - private FileSelectLayout mClientKey; - private TextView mAliasName; - private CheckBox mUseLzo; - private ToggleButton mTcpUdp; - private Spinner mType; - private FileSelectLayout mpkcs12; - private TextView mPKCS12Password; - - private Handler mHandler; - - - - - - private SparseArray fileselects = new SparseArray(); - - - private EditText mUserName; - - - private EditText mPassword; - - - private View mView; - - - private VpnProfile mProfile; - private EditText mProfileName; - private EditText mKeyPassword; - - - - private void addFileSelectLayout (FileSelectLayout fsl) { - int i = fileselects.size() + CHOOSE_FILE_OFFSET; - fileselects.put(i, fsl); - fsl.setFragment(this,i); - } - - - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(profileuuid); - getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - - mView = inflater.inflate(R.layout.basic_settings,container,false); - - mProfileName = (EditText) mView.findViewById(R.id.profilename); - mServerAddress = (TextView) mView.findViewById(R.id.address); - mServerPort = (TextView) mView.findViewById(R.id.port); - mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect); - mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect); - mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect); - mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select); - mUseLzo = (CheckBox) mView.findViewById(R.id.lzo); - mTcpUdp = (ToggleButton) mView.findViewById(id.tcpudp); - mType = (Spinner) mView.findViewById(R.id.type); - mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password); - mAliasName = (TextView) mView.findViewById(R.id.aliasname); - - mUserName = (EditText) mView.findViewById(R.id.auth_username); - mPassword = (EditText) mView.findViewById(R.id.auth_password); - mKeyPassword = (EditText) mView.findViewById(R.id.key_password); - - - - addFileSelectLayout(mCaCert); - addFileSelectLayout(mClientCert); - addFileSelectLayout(mClientKey); - addFileSelectLayout(mpkcs12); - mpkcs12.setBase64Encode(); - mCaCert.setShowClear(); - - - mType.setOnItemSelectedListener(this); - - mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); - - - if (mHandler == null) { - mHandler = new Handler(this); - } - - return mView; - } - - - @Override - public void onStart() { - super.onStart(); - String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(profileuuid); - loadPreferences(); - - } - - @Override - public void onActivityResult(int request, int result, Intent data) { - if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) { - String filedata = data.getStringExtra(FileSelect.RESULT_DATA); - FileSelectLayout fsl = fileselects.get(request); - fsl.setData(filedata); - - savePreferences(); - - // Private key files may result in showing/hiding the private key password dialog - if(fsl==mClientKey) { - changeType(mType.getSelectedItemPosition()); - } - } - - } - - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (parent == mType) { - changeType(position); - } - } - @Override - public void onPause() { - super.onPause(); - savePreferences(); - } - - - - private void changeType(int type){ - // hide everything - mView.findViewById(R.id.pkcs12).setVisibility(View.GONE); - mView.findViewById(R.id.certs).setVisibility(View.GONE); - mView.findViewById(R.id.statickeys).setVisibility(View.GONE); - mView.findViewById(R.id.keystore).setVisibility(View.GONE); - mView.findViewById(R.id.cacert).setVisibility(View.GONE); - mView.findViewById(R.id.userpassword).setVisibility(View.GONE); - mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE); - - // Fall through are by design - switch(type) { - case VpnProfile.TYPE_USERPASS_CERTIFICATES: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_CERTIFICATES: - mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - if(mProfile.requireTLSKeyPassword()) - mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS_PKCS12: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_PKCS12: - mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_STATICKEYS: - mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS_KEYSTORE: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - case VpnProfile.TYPE_KEYSTORE: - mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - break; - - case VpnProfile.TYPE_USERPASS: - mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - break; - } - - - } - - private void loadPreferences() { - mProfileName.setText(mProfile.mName); - mClientCert.setData(mProfile.mClientCertFilename); - mClientKey.setData(mProfile.mClientKeyFilename); - mCaCert.setData(mProfile.mCaFilename); - - mUseLzo.setChecked(mProfile.mUseLzo); - mServerPort.setText(mProfile.mServerPort); - mServerAddress.setText(mProfile.mServerName); - mTcpUdp.setChecked(mProfile.mUseUdp); - mType.setSelection(mProfile.mAuthenticationType); - mpkcs12.setData(mProfile.mPKCS12Filename); - mPKCS12Password.setText(mProfile.mPKCS12Password); - mUserName.setText(mProfile.mUsername); - mPassword.setText(mProfile.mPassword); - mKeyPassword.setText(mProfile.mKeyPassword); - - setAlias(); - - } - - void savePreferences() { - - mProfile.mName = mProfileName.getText().toString(); - mProfile.mCaFilename = mCaCert.getData(); - mProfile.mClientCertFilename = mClientCert.getData(); - mProfile.mClientKeyFilename = mClientKey.getData(); - - mProfile.mUseLzo = mUseLzo.isChecked(); - mProfile.mServerPort =mServerPort.getText().toString(); - mProfile.mServerName = mServerAddress.getText().toString(); - mProfile.mUseUdp = mTcpUdp.isChecked(); - - mProfile.mAuthenticationType = mType.getSelectedItemPosition(); - mProfile.mPKCS12Filename = mpkcs12.getData(); - mProfile.mPKCS12Password = mPKCS12Password.getText().toString(); - - mProfile.mPassword = mPassword.getText().toString(); - mProfile.mUsername = mUserName.getText().toString(); - mProfile.mKeyPassword = mKeyPassword.getText().toString(); - - } - - - private void setAlias() { - if(mProfile.mAlias == null) { - mAliasName.setText(R.string.client_no_certificate); - } else { - mAliasName.setText(mProfile.mAlias); - } - } - - public void showCertDialog () { - try { - KeyChain.choosePrivateKeyAlias(getActivity(), - new KeyChainAliasCallback() { - - public void alias(String alias) { - // Credential alias selected. Remember the alias selection for future use. - mProfile.mAlias=alias; - mHandler.sendEmptyMessage(UPDATE_ALIAS); - } - - - }, - new String[] {"RSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - mProfile.mServerName, // host name of server requesting the cert, null if unavailable - -1, // port of server requesting the cert, -1 if unavailable - mProfile.mAlias); // alias to preselect, null if unavailable - } catch (ActivityNotFoundException anf) { - Builder ab = new AlertDialog.Builder(getActivity()); - ab.setTitle(R.string.broken_image_cert_title); - ab.setMessage(R.string.broken_image_cert); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - @Override - public void onClick(View v) { - if (v == mView.findViewById(R.id.select_keystore_button)) { - showCertDialog(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - savePreferences(); - if(mProfile!=null) { - outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString()); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - - - @Override - public boolean handleMessage(Message msg) { - setAlias(); - return true; - } - - -} diff --git a/src/de/blinkt/openvpn/Settings_IP.java b/src/de/blinkt/openvpn/Settings_IP.java deleted file mode 100644 index d6fd19d4..00000000 --- a/src/de/blinkt/openvpn/Settings_IP.java +++ /dev/null @@ -1,128 +0,0 @@ -package de.blinkt.openvpn; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.PreferenceManager; -import android.preference.SwitchPreference; - -public class Settings_IP extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { - private EditTextPreference mIPv4; - private EditTextPreference mIPv6; - private SwitchPreference mUsePull; - private CheckBoxPreference mOverrideDNS; - private EditTextPreference mSearchdomain; - private EditTextPreference mDNS1; - private EditTextPreference mDNS2; - private CheckBoxPreference mNobind; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - // Make sure default values are applied. In a real app, you would - // want this in a shared function that is used to retrieve the - // SharedPreferences wherever they are needed. - PreferenceManager.setDefaultValues(getActivity(), - R.xml.vpn_ipsettings, false); - - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_ipsettings); - mIPv4 = (EditTextPreference) findPreference("ipv4_address"); - mIPv6 = (EditTextPreference) findPreference("ipv6_address"); - mUsePull = (SwitchPreference) findPreference("usePull"); - mOverrideDNS = (CheckBoxPreference) findPreference("overrideDNS"); - mSearchdomain =(EditTextPreference) findPreference("searchdomain"); - mDNS1 = (EditTextPreference) findPreference("dns1"); - mDNS2 = (EditTextPreference) findPreference("dns2"); - mNobind = (CheckBoxPreference) findPreference("nobind"); - - mIPv4.setOnPreferenceChangeListener(this); - mIPv6.setOnPreferenceChangeListener(this); - mDNS1.setOnPreferenceChangeListener(this); - mDNS2.setOnPreferenceChangeListener(this); - mUsePull.setOnPreferenceChangeListener(this); - mOverrideDNS.setOnPreferenceChangeListener(this); - mSearchdomain.setOnPreferenceChangeListener(this); - - loadSettings(); - } - - @Override - protected void loadSettings() { - - mUsePull.setChecked(mProfile.mUsePull); - mIPv4.setText(mProfile.mIPv4Address); - mIPv6.setText(mProfile.mIPv6Address); - mDNS1.setText(mProfile.mDNS1); - mDNS2.setText(mProfile.mDNS2); - mOverrideDNS.setChecked(mProfile.mOverrideDNS); - mSearchdomain.setText(mProfile.mSearchDomain); - mNobind.setChecked(mProfile.mNobind); - - // Sets Summary - onPreferenceChange(mIPv4, mIPv4.getText()); - onPreferenceChange(mIPv6, mIPv6.getText()); - onPreferenceChange(mDNS1, mDNS1.getText()); - onPreferenceChange(mDNS2, mDNS2.getText()); - onPreferenceChange(mSearchdomain, mSearchdomain.getText()); - - setDNSState(); - } - - - @Override - protected void saveSettings() { - mProfile.mUsePull = mUsePull.isChecked(); - mProfile.mIPv4Address = mIPv4.getText(); - mProfile.mIPv6Address = mIPv6.getText(); - mProfile.mDNS1 = mDNS1.getText(); - mProfile.mDNS2 = mDNS2.getText(); - mProfile.mOverrideDNS = mOverrideDNS.isChecked(); - mProfile.mSearchDomain = mSearchdomain.getText(); - mProfile.mNobind = mNobind.isChecked(); - - } - - @Override - public boolean onPreferenceChange(Preference preference, - Object newValue) { - if(preference==mIPv4 || preference == mIPv6 - || preference==mDNS1 || preference == mDNS2 - || preference == mSearchdomain - ) - - preference.setSummary((String)newValue); - - if(preference== mUsePull || preference == mOverrideDNS) - if(preference==mOverrideDNS) { - // Set so the function gets the right value - mOverrideDNS.setChecked((Boolean) newValue); - } - setDNSState(); - - saveSettings(); - return true; - } - - private void setDNSState() { - boolean enabled; - mOverrideDNS.setEnabled(mUsePull.isChecked()); - if(!mUsePull.isChecked()) - enabled =true; - else if (mOverrideDNS.isChecked()) - enabled = true; - else - enabled = false; - - mDNS1.setEnabled(enabled); - mDNS2.setEnabled(enabled); - mSearchdomain.setEnabled(enabled); - - - } - - - } \ No newline at end of file diff --git a/src/de/blinkt/openvpn/Settings_Obscure.java b/src/de/blinkt/openvpn/Settings_Obscure.java deleted file mode 100644 index 160dbe0c..00000000 --- a/src/de/blinkt/openvpn/Settings_Obscure.java +++ /dev/null @@ -1,115 +0,0 @@ -package de.blinkt.openvpn; - -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; - -public class Settings_Obscure extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { - private CheckBoxPreference mUseRandomHostName; - private CheckBoxPreference mUseFloat; - private CheckBoxPreference mUseCustomConfig; - private EditTextPreference mCustomConfig; - private ListPreference mLogverbosity; - private CheckBoxPreference mPersistent; - private ListPreference mConnectretrymax; - private EditTextPreference mConnectretry; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.vpn_obscure); - - - mUseRandomHostName = (CheckBoxPreference) findPreference("useRandomHostname"); - mUseFloat = (CheckBoxPreference) findPreference("useFloat"); - mUseCustomConfig = (CheckBoxPreference) findPreference("enableCustomOptions"); - mCustomConfig = (EditTextPreference) findPreference("customOptions"); - mLogverbosity = (ListPreference) findPreference("verblevel"); - mPersistent = (CheckBoxPreference) findPreference("usePersistTun"); - mConnectretrymax = (ListPreference) findPreference("connectretrymax"); - mConnectretry = (EditTextPreference) findPreference("connectretry"); - - mLogverbosity.setOnPreferenceChangeListener(this); - mLogverbosity.setSummary("%s"); - - mConnectretrymax.setOnPreferenceChangeListener(this); - mConnectretrymax.setSummary("%s"); - - mConnectretry.setOnPreferenceChangeListener(this); - - - loadSettings(); - - } - - protected void loadSettings() { - mUseRandomHostName.setChecked(mProfile.mUseRandomHostname); - mUseFloat.setChecked(mProfile.mUseFloat); - mUseCustomConfig.setChecked(mProfile.mUseCustomConfig); - mCustomConfig.setText(mProfile.mCustomConfigOptions); - mPersistent.setChecked(mProfile.mPersistTun); - - mLogverbosity.setValue(mProfile.mVerb); - onPreferenceChange(mLogverbosity, mProfile.mVerb); - - mConnectretrymax.setValue(mProfile.mConnectRetryMax); - onPreferenceChange(mConnectretrymax, mProfile.mConnectRetryMax); - - mConnectretry.setText(mProfile.mConnectRetry); - onPreferenceChange(mConnectretry, mProfile.mConnectRetry); - } - - - protected void saveSettings() { - mProfile.mUseRandomHostname = mUseRandomHostName.isChecked(); - mProfile.mUseFloat = mUseFloat.isChecked(); - mProfile.mUseCustomConfig = mUseCustomConfig.isChecked(); - mProfile.mCustomConfigOptions = mCustomConfig.getText(); - mProfile.mVerb = mLogverbosity.getValue(); - mProfile.mConnectRetryMax = mConnectretrymax.getValue(); - mProfile.mPersistTun = mPersistent.isChecked(); - mProfile.mConnectRetry = mConnectretry.getText(); - } - - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if(preference==mLogverbosity) { - // Catch old version problem - if(newValue==null){ - newValue="1"; - } - mLogverbosity.setDefaultValue(newValue); - //This is idiotic. - int i =Integer.parseInt((String) newValue); - - // verb >= 5 is not supported by the chooser - if(i < mLogverbosity.getEntries().length ) - mLogverbosity.setSummary(mLogverbosity.getEntries()[i]); - else - mLogverbosity.setSummary(String.format("debug verbosity: %d",i)); - } else if (preference == mConnectretrymax) { - if(newValue==null) { - newValue="5"; - } - mConnectretrymax.setDefaultValue(newValue); - - for(int i=0;i 0) { - fout.write(buf, 0, lenread); - lenread = mvpn.read(buf); - } - fout.close(); - - if(!mvpnout.setExecutable(true)) { - OpenVPN.logMessage(0, "","Failed to set minivpn executable"); - return false; - } - - - return true; - } catch (IOException e) { - if(e2!=null) - OpenVPN.logMessage(0, "",e2.getLocalizedMessage()); - OpenVPN.logMessage(0, "",e.getLocalizedMessage()); - e.printStackTrace(); - return false; - } - } - - - public static void startOpenVpn(VpnProfile startprofile, Context context) { - if(!writeMiniVPN(context)) { - OpenVPN.logMessage(0, "", "Error writing minivpn binary"); - return; - } - OpenVPN.logMessage(0, "", context.getString(R.string.building_configration)); - - Intent startVPN = startprofile.prepareIntent(context); - if(startVPN!=null) - context.startService(startVPN); - - } -} diff --git a/src/de/blinkt/openvpn/VPNPreferences.java b/src/de/blinkt/openvpn/VPNPreferences.java deleted file mode 100644 index c49d39de..00000000 --- a/src/de/blinkt/openvpn/VPNPreferences.java +++ /dev/null @@ -1,137 +0,0 @@ -package de.blinkt.openvpn; - -import java.util.List; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.preference.PreferenceActivity; -import android.view.Menu; -import android.view.MenuItem; - - -public class VPNPreferences extends PreferenceActivity { - - private String mProfileUUID; - private VpnProfile mProfile; - - public VPNPreferences() { - super(); - - } - - - - - @Override - protected void onStop() { - super.onStop(); - }; - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putString(getIntent().getStringExtra(getPackageName() + ".profileUUID"),mProfileUUID); - super.onSaveInstanceState(outState); - } - - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - - - if(intent!=null) { - String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); - if(profileUUID==null) { - Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); - profileUUID = initialArguments.getString(getPackageName() + ".profileUUID"); - } - if(profileUUID!=null){ - - mProfileUUID = profileUUID; - mProfile = ProfileManager.get(this,mProfileUUID); - - } - } - // When a profile is deleted from a category fragment in hadset mod we need to finish - // this activity as well when returning - if (mProfile==null || mProfile.profileDleted) { - setResult(VPNProfileList.RESULT_VPN_DELETED); - finish(); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - mProfileUUID = getIntent().getStringExtra(getPackageName() + ".profileUUID"); - if(savedInstanceState!=null){ - String savedUUID = savedInstanceState.getString(getPackageName() + ".profileUUID"); - if(savedUUID!=null) - mProfileUUID=savedUUID; - } - - mProfile = ProfileManager.get(this,mProfileUUID); - if(mProfile!=null) { - setTitle(getString(R.string.edit_profile_title, mProfile.getName())); - } - super.onCreate(savedInstanceState); - } - - - @Override - public void onBuildHeaders(List
target) { - loadHeadersFromResource(R.xml.vpn_headers, target); - for (Header header : target) { - if(header.fragmentArguments==null) - header.fragmentArguments = new Bundle(); - header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID); - } - } - - @Override - public void onBackPressed() { - setResult(RESULT_OK, getIntent()); - super.onBackPressed(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId() == R.id.remove_vpn) - askProfileRemoval(); - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - getMenuInflater().inflate(R.menu.vpnpreferences_menu, menu); - - return super.onCreateOptionsMenu(menu); - } - - private void askProfileRemoval() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Confirm deletion"); - dialog.setMessage(getString(R.string.remove_vpn_query, mProfile.mName)); - - dialog.setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - removeProfile(mProfile); - } - - }); - dialog.setNegativeButton(android.R.string.no,null); - dialog.create().show(); - } - - protected void removeProfile(VpnProfile profile) { - ProfileManager.getInstance(this).removeProfile(this,profile); - setResult(VPNProfileList.RESULT_VPN_DELETED); - finish(); - - } -} - diff --git a/src/de/blinkt/openvpn/VPNProfileList.java b/src/de/blinkt/openvpn/VPNProfileList.java deleted file mode 100644 index 1e24bfe7..00000000 --- a/src/de/blinkt/openvpn/VPNProfileList.java +++ /dev/null @@ -1,316 +0,0 @@ -package de.blinkt.openvpn; - -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ListFragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.text.Html.ImageGetter; -import android.view.LayoutInflater; -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.ArrayAdapter; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -public class VPNProfileList extends ListFragment { - - final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; - - private static final int MENU_ADD_PROFILE = Menu.FIRST; - - private static final int START_VPN_CONFIG = 92; - private static final int SELECT_PROFILE = 43; - private static final int IMPORT_PROFILE = 231; - - private static final int MENU_IMPORT_PROFILE = Menu.FIRST +1; - - class VPNArrayAdapter extends ArrayAdapter { - - public VPNArrayAdapter(Context context, int resource, - int textViewResourceId) { - super(context, resource, textViewResourceId); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - View titleview = v.findViewById(R.id.vpn_list_item_left); - titleview.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - VpnProfile profile =(VpnProfile) getListAdapter().getItem(position); - startVPN(profile); - } - }); - - View settingsview = v.findViewById(R.id.quickedit_settings); - settingsview.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - mEditProfile =(VpnProfile) getListAdapter().getItem(position); - editVPN(mEditProfile); - - } - }); - - return v; - } - } - - - - - - - - - private ArrayAdapter mArrayadapter; - - protected VpnProfile mEditProfile=null; - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - - // Debug load JNI - //OpenVPN.foo(); - } - - - class MiniImageGetter implements ImageGetter { - - - @Override - public Drawable getDrawable(String source) { - Drawable d=null; - if ("ic_menu_add".equals(source)) - d = getActivity().getResources().getDrawable(android.R.drawable.ic_menu_add); - else if("ic_menu_archive".equals(source)) - d = getActivity().getResources().getDrawable(R.drawable.ic_menu_archive); - - - - if(d!=null) { - d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); - return d; - }else{ - return null; - } - } - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.vpn_profile_list, container,false); - - TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); - TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); - - - - newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null)); - importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null)); - - - - return v; - - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setListAdapter(); - } - - class VpnProfileNameComperator implements Comparator { - - @Override - public int compare(VpnProfile lhs, VpnProfile rhs) { - return lhs.mName.compareTo(rhs.mName); - } - - } - - private void setListAdapter() { - mArrayadapter = new VPNArrayAdapter(getActivity(),R.layout.vpn_list_item,R.id.vpn_item_title); - Collection allvpn = getPM().getProfiles(); - - TreeSet sortedset = new TreeSet(new VpnProfileNameComperator()); - sortedset.addAll(allvpn); - mArrayadapter.addAll(sortedset); - - setListAdapter(mArrayadapter); - } - - - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_ADD_PROFILE, 0, R.string.menu_add_profile) - .setIcon(android.R.drawable.ic_menu_add) - .setAlphabeticShortcut('a') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) - .setIcon(R.drawable.ic_menu_archive) - .setAlphabeticShortcut('i') - .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT ); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == MENU_ADD_PROFILE) { - onAddProfileClicked(); - return true; - } else if (itemId == MENU_IMPORT_PROFILE) { - startImportConfig(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void startImportConfig() { - Intent intent = new Intent(getActivity(),FileSelect.class); - intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); - intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); - startActivityForResult(intent, SELECT_PROFILE); - } - - - - - - private void onAddProfileClicked() { - Context context = getActivity(); - if (context != null) { - final EditText entry = new EditText(context); - entry.setSingleLine(); - - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - dialog.setTitle(R.string.menu_add_profile); - dialog.setMessage(R.string.add_profile_name_prompt); - dialog.setView(entry); - - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String name = entry.getText().toString(); - if (getPM().getProfileByName(name)==null) { - VpnProfile profile = new VpnProfile(name); - addProfile(profile); - } else { - Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); - } - } - - - }); - dialog.setNegativeButton(android.R.string.cancel, null); - dialog.create().show(); - } - - } - - - private void addProfile(VpnProfile profile) { - getPM().addProfile(profile); - getPM().saveProfileList(getActivity()); - getPM().saveProfile(getActivity(),profile); - mArrayadapter.add(profile); - } - - - - - - private ProfileManager getPM() { - return ProfileManager.getInstance(getActivity()); - } - - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if(resultCode == RESULT_VPN_DELETED){ - if(mArrayadapter != null && mEditProfile !=null) - mArrayadapter.remove(mEditProfile); - } - - if(resultCode != Activity.RESULT_OK) - return; - - if (requestCode == START_VPN_CONFIG) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - - VpnProfile profile = ProfileManager.get(configuredVPN); - getPM().saveProfile(getActivity(), profile); - // Name could be modified, reset List adapter - setListAdapter(); - - } else if(requestCode== SELECT_PROFILE) { - String filedata = data.getStringExtra(FileSelect.RESULT_DATA); - Intent startImport = new Intent(getActivity(),ConfigConverter.class); - startImport.setAction(ConfigConverter.IMPORT_PROFILE); - Uri uri = new Uri.Builder().path(filedata).scheme("file").build(); - startImport.setData(uri); - startActivityForResult(startImport, IMPORT_PROFILE); - } else if(requestCode == IMPORT_PROFILE) { - String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - mArrayadapter.add(ProfileManager.get(profileUUID)); - } - - } - - - private void editVPN(VpnProfile profile) { - - Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) - .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); - - startActivityForResult(vprefintent,START_VPN_CONFIG); - } - - private void startVPN(VpnProfile profile) { - - getPM().saveProfile(getActivity(), profile); - - Intent intent = new Intent(getActivity(),LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - - getActivity().finish(); - } -} diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java deleted file mode 100644 index d8b22ae6..00000000 --- a/src/de/blinkt/openvpn/VpnProfile.java +++ /dev/null @@ -1,745 +0,0 @@ -package de.blinkt.openvpn; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.UUID; -import java.util.Vector; - -import org.spongycastle.util.io.pem.PemObject; -import org.spongycastle.util.io.pem.PemWriter; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.preference.PreferenceManager; -import android.security.KeyChain; -import android.security.KeyChainException; - -public class VpnProfile implements Serializable{ - // Parcable - /** - * - */ - private static final long serialVersionUID = 7085688938959334563L; - static final int TYPE_CERTIFICATES=0; - static final int TYPE_PKCS12=1; - static final int TYPE_KEYSTORE=2; - public static final int TYPE_USERPASS = 3; - public static final int TYPE_STATICKEYS = 4; - public static final int TYPE_USERPASS_CERTIFICATES = 5; - public static final int TYPE_USERPASS_PKCS12 = 6; - public static final int TYPE_USERPASS_KEYSTORE = 7; - - // Don't change this, not all parts of the program use this constant - public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID"; - public static final String INLINE_TAG = "[[INLINE]]"; - private static final String OVPNCONFIGFILE = "android.conf"; - - protected transient String mTransientPW=null; - protected transient String mTransientPCKS12PW=null; - private transient PrivateKey mPrivateKey; - protected boolean profileDleted=false; - - - public static String DEFAULT_DNS1="131.234.137.23"; - public static String DEFAULT_DNS2="131.234.137.24"; - - // Public attributes, since I got mad with getter/setter - // set members to default values - private UUID mUuid; - public int mAuthenticationType = TYPE_KEYSTORE ; - public String mName; - public String mAlias; - public String mClientCertFilename; - public String mTLSAuthDirection=""; - public String mTLSAuthFilename; - public String mClientKeyFilename; - public String mCaFilename; - public boolean mUseLzo=true; - public String mServerPort= "1194" ; - public boolean mUseUdp = true; - public String mPKCS12Filename; - public String mPKCS12Password; - public boolean mUseTLSAuth = false; - public String mServerName = "openvpn.blinkt.de" ; - public String mDNS1=DEFAULT_DNS1; - public String mDNS2=DEFAULT_DNS2; - public String mIPv4Address; - public String mIPv6Address; - public boolean mOverrideDNS=false; - public String mSearchDomain="blinkt.de"; - public boolean mUseDefaultRoute=true; - public boolean mUsePull=true; - public String mCustomRoutes; - public boolean mCheckRemoteCN=false; - public boolean mExpectTLSCert=true; - public String mRemoteCN=""; - public String mPassword=""; - public String mUsername=""; - public boolean mRoutenopull=false; - public boolean mUseRandomHostname=false; - public boolean mUseFloat=false; - public boolean mUseCustomConfig=false; - public String mCustomConfigOptions=""; - public String mVerb="1"; - public String mCipher=""; - public boolean mNobind=false; - public boolean mUseDefaultRoutev6=true; - public String mCustomRoutesv6=""; - public String mKeyPassword=""; - public boolean mPersistTun = false; - public String mConnectRetryMax="5"; - public String mConnectRetry="5"; - public boolean mUserEditable=true; - - static final String MINIVPN = "miniopenvpn"; - - - - - - - public void clearDefaults() { - mServerName="unkown"; - mUsePull=false; - mUseLzo=false; - mUseDefaultRoute=false; - mUseDefaultRoutev6=false; - mExpectTLSCert=false; - mPersistTun = false; - } - - - public static String openVpnEscape(String unescaped) { - if(unescaped==null) - return null; - String escapedString = unescaped.replace("\\", "\\\\"); - escapedString = escapedString.replace("\"","\\\""); - escapedString = escapedString.replace("\n","\\n"); - - if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#")) - return unescaped; - else - return '"' + escapedString + '"'; - } - - - static final String OVPNCONFIGCA = "android-ca.pem"; - static final String OVPNCONFIGUSERCERT = "android-user.pem"; - - - public VpnProfile(String name) { - mUuid = UUID.randomUUID(); - mName = name; - } - - public UUID getUUID() { - return mUuid; - - } - - public String getName() { - return mName; - } - - - public String getConfigFile(Context context) - { - - File cacheDir= context.getCacheDir(); - String cfg=""; - - // Enable managment interface - cfg += "# Enables connection to GUI\n"; - cfg += "management "; - - cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; - cfg += " unix\n"; - cfg += "management-client\n"; - // Not needed, see updated man page in 2.3 - //cfg += "management-signal\n"; - cfg += "management-query-passwords\n"; - cfg += "management-hold\n\n"; - - /* tmp-dir patched out :) - cfg+="# /tmp does not exist on Android\n"; - cfg+="tmp-dir "; - cfg+=cacheDir.getAbsolutePath(); - cfg+="\n\n"; */ - - cfg+="# Log window is better readable this way\n"; - cfg+="suppress-timestamps\n"; - - - - boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); - - if(useTLSClient && mUsePull) - cfg+="client\n"; - else if (mUsePull) - cfg+="pull\n"; - else if(useTLSClient) - cfg+="tls-client\n"; - - - cfg+="verb " + mVerb + "\n"; - - if(mConnectRetryMax ==null) { - mConnectRetryMax="5"; - } - - if(!mConnectRetryMax.equals("-1")) - cfg+="connect-retry-max " + mConnectRetryMax+ "\n"; - - if(mConnectRetry==null) - mConnectRetry="5"; - - - cfg+="connect-retry " + mConnectRetry + "\n"; - - cfg+="resolv-retry 60\n"; - - - - // We cannot use anything else than tun - cfg+="dev tun\n"; - - // Server Address - cfg+="remote "; - cfg+=mServerName; - cfg+=" "; - cfg+=mServerPort; - if(mUseUdp) - cfg+=" udp\n"; - else - cfg+=" tcp-client\n"; - - - - - switch(mAuthenticationType) { - case VpnProfile.TYPE_USERPASS_CERTIFICATES: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_CERTIFICATES: - // Ca - cfg+=insertFileData("ca",mCaFilename); - - // Client Cert + Key - cfg+=insertFileData("key",mClientKeyFilename); - cfg+=insertFileData("cert",mClientCertFilename); - - break; - case VpnProfile.TYPE_USERPASS_PKCS12: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_PKCS12: - cfg+=insertFileData("pkcs12",mPKCS12Filename); - break; - - case VpnProfile.TYPE_USERPASS_KEYSTORE: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_KEYSTORE: - cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n"; - cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n"; - cfg+="management-external-key\n"; - - break; - case VpnProfile.TYPE_USERPASS: - cfg+="auth-user-pass\n"; - cfg+=insertFileData("ca",mCaFilename); - } - - if(mUseLzo) { - cfg+="comp-lzo\n"; - } - - if(mUseTLSAuth) { - if(mAuthenticationType==TYPE_STATICKEYS) - cfg+=insertFileData("secret",mTLSAuthFilename); - else - cfg+=insertFileData("tls-auth",mTLSAuthFilename); - - if(nonNull(mTLSAuthDirection)) { - cfg+= "key-direction "; - cfg+= mTLSAuthDirection; - cfg+="\n"; - } - - } - - if(!mUsePull ) { - if(nonNull(mIPv4Address)) - cfg +="ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; - - if(nonNull(mIPv6Address)) - cfg +="ifconfig-ipv6 " + mIPv6Address + "\n"; - } - - if(mUsePull && mRoutenopull) - cfg += "route-nopull\n"; - - String routes = ""; - int numroutes=0; - if(mUseDefaultRoute) - routes += "route 0.0.0.0 0.0.0.0\n"; - else - for(String route:getCustomRoutes()) { - routes += "route " + route + "\n"; - numroutes++; - } - - - if(mUseDefaultRoutev6) - cfg += "route-ipv6 ::/0\n"; - else - for(String route:getCustomRoutesv6()) { - routes += "route-ipv6 " + route + "\n"; - numroutes++; - } - - // Round number to next 100 - if(numroutes> 90) { - numroutes = ((numroutes / 100)+1) * 100; - cfg+="# Alot of routes are set, increase max-routes\n"; - cfg+="max-routes " + numroutes + "\n"; - } - cfg+=routes; - - if(mOverrideDNS || !mUsePull) { - if(nonNull(mDNS1)) - cfg+="dhcp-option DNS " + mDNS1 + "\n"; - if(nonNull(mDNS2)) - cfg+="dhcp-option DNS " + mDNS2 + "\n"; - if(nonNull(mSearchDomain)) - cfg+="dhcp-option DOMAIN " + mSearchDomain + "\n"; - - } - - if(mNobind) - cfg+="nobind\n"; - - - - // Authentication - if(mCheckRemoteCN) { - if(mRemoteCN == null || mRemoteCN.equals("") ) - cfg+="tls-remote " + mServerName + "\n"; - else - cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; - } - if(mExpectTLSCert) - cfg += "remote-cert-tls server\n"; - - - if(nonNull(mCipher)){ - cfg += "cipher " + mCipher + "\n"; - } - - - // Obscure Settings dialog - if(mUseRandomHostname) - cfg += "#my favorite options :)\nremote-random-hostname\n"; - - if(mUseFloat) - cfg+= "float\n"; - - if(mPersistTun) { - cfg+= "persist-tun\n"; - cfg+= "# persist-tun also sets persist-remote-ip to avoid DNS resolve problem\n"; - cfg+= "persist-remote-ip\n"; - } - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); - if(usesystemproxy) { - cfg+= "# Use system proxy setting\n"; - cfg+= "management-query-proxy\n"; - } - - - if(mUseCustomConfig) { - cfg += "# Custom configuration options\n"; - cfg += "# You are on your on own here :)\n"; - cfg += mCustomConfigOptions; - cfg += "\n"; - - } - - - - return cfg; - } - - //! Put inline data inline and other data as normal escaped filename - private String insertFileData(String cfgentry, String filedata) { - if(filedata==null) { - return String.format("%s %s\n",cfgentry,"missing"); - }else if(filedata.startsWith(VpnProfile.INLINE_TAG)){ - String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length()); - return String.format("<%s>\n%s\n\n",cfgentry,datawoheader,cfgentry); - } else { - return String.format("%s %s\n",cfgentry,openVpnEscape(filedata)); - } - } - - private boolean nonNull(String val) { - if(val == null || val.equals("")) - return false; - else - return true; - } - - private Collection getCustomRoutes() { - Vector cidrRoutes=new Vector(); - if(mCustomRoutes==null) { - // No routes set, return empty vector - return cidrRoutes; - } - for(String route:mCustomRoutes.split("[\n \t]")) { - if(!route.equals("")) { - String cidrroute = cidrToIPAndNetmask(route); - if(cidrRoutes == null) - return null; - - cidrRoutes.add(cidrroute); - } - } - - return cidrRoutes; - } - - private Collection getCustomRoutesv6() { - Vector cidrRoutes=new Vector(); - if(mCustomRoutesv6==null) { - // No routes set, return empty vector - return cidrRoutes; - } - for(String route:mCustomRoutesv6.split("[\n \t]")) { - if(!route.equals("")) { - cidrRoutes.add(route); - } - } - - return cidrRoutes; - } - - - - private String cidrToIPAndNetmask(String route) { - String[] parts = route.split("/"); - - // No /xx, assume /32 as netmask - if (parts.length ==1) - parts = (route + "/32").split("/"); - - if (parts.length!=2) - return null; - int len; - try { - len = Integer.parseInt(parts[1]); - } catch(NumberFormatException ne) { - return null; - } - if (len <0 || len >32) - return null; - - - long nm = 0xffffffffl; - nm = (nm << (32-len)) & 0xffffffffl; - - String netmask =String.format("%d.%d.%d.%d", (nm & 0xff000000) >> 24,(nm & 0xff0000) >> 16, (nm & 0xff00) >> 8 ,nm & 0xff ); - return parts[0] + " " + netmask; - } - - - - private String[] buildOpenvpnArgv(File cacheDir) - { - Vector args = new Vector(); - - // Add fixed paramenters - //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); - args.add(cacheDir.getAbsolutePath() +"/" + VpnProfile.MINIVPN); - - args.add("--config"); - args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); - // Silences script security warning - - args.add("script-security"); - args.add("0"); - - - return (String[]) args.toArray(new String[args.size()]); - } - - public Intent prepareIntent(Context context) { - String prefix = context.getPackageName(); - - Intent intent = new Intent(context,OpenVpnService.class); - - if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - if(!saveCertificates(context)) - return null; - } - - intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir())); - intent.putExtra(prefix + ".profileUUID", mUuid.toString()); - - ApplicationInfo info = context.getApplicationInfo(); - intent.putExtra(prefix +".nativelib",info.nativeLibraryDir); - - try { - FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); - cfg.write(getConfigFile(context)); - cfg.flush(); - cfg.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - return intent; - } - - private boolean saveCertificates(Context context) { - PrivateKey privateKey = null; - X509Certificate[] cachain=null; - try { - privateKey = KeyChain.getPrivateKey(context,mAlias); - mPrivateKey = privateKey; - - cachain = KeyChain.getCertificateChain(context, mAlias); - if(cachain.length <= 1 && !nonNull(mCaFilename)) - OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert)); - - for(X509Certificate cert:cachain) { - OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN()); - } - - - - - if(nonNull(mCaFilename)) { - try { - Certificate cacert = getCacertFromFile(); - X509Certificate[] newcachain = new X509Certificate[cachain.length+1]; - for(int i=0;i= 1){ - X509Certificate usercert = cachain[0]; - - FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT); - - PemWriter upw = new PemWriter(userout); - upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); - upw.close(); - - } - - return true; - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (CertificateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (KeyChainException e) { - OpenVPN.logMessage(0,"",context.getString(R.string.keychain_access)); - } - return false; - } - private Certificate getCacertFromFile() throws FileNotFoundException, CertificateException { - CertificateFactory certFact = CertificateFactory.getInstance("X.509"); - - InputStream inStream; - - if(mCaFilename.startsWith(INLINE_TAG)) - inStream = new ByteArrayInputStream(mCaFilename.replace(INLINE_TAG,"").getBytes()); - else - inStream = new FileInputStream(mCaFilename); - - return certFact.generateCertificate(inStream); - } - - - //! Return an error if somethign is wrong - public int checkProfile(Context context) { - if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) { - if(mAlias==null) - return R.string.no_keystore_cert_selected; - } - - if(!mUsePull) { - if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) - return R.string.ipv4_format_error; - } - if(isUserPWAuth() && !nonNull(mUsername)) { - return R.string.error_empty_username; - } - if(!mUseDefaultRoute && getCustomRoutes()==null) - return R.string.custom_route_format_error; - - // Everything okay - return R.string.no_error_found; - - } - - //! Openvpn asks for a "Private Key", this should be pkcs12 key - // - public String getPasswordPrivateKey() { - if(mTransientPCKS12PW!=null) { - String pwcopy = mTransientPCKS12PW; - mTransientPCKS12PW=null; - return pwcopy; - } - switch (mAuthenticationType) { - case TYPE_PKCS12: - case TYPE_USERPASS_PKCS12: - return mPKCS12Password; - - case TYPE_CERTIFICATES: - case TYPE_USERPASS_CERTIFICATES: - return mKeyPassword; - - case TYPE_USERPASS: - case TYPE_STATICKEYS: - default: - return null; - } - } - private boolean isUserPWAuth() { - switch(mAuthenticationType) { - case TYPE_USERPASS: - case TYPE_USERPASS_CERTIFICATES: - case TYPE_USERPASS_KEYSTORE: - case TYPE_USERPASS_PKCS12: - return true; - default: - return false; - - } - } - - - public boolean requireTLSKeyPassword() { - if(!nonNull(mClientKeyFilename)) - return false; - - String data = ""; - if(mClientKeyFilename.startsWith(INLINE_TAG)) - data = mClientKeyFilename; - else { - char[] buf = new char[2048]; - FileReader fr; - try { - fr = new FileReader(mClientKeyFilename); - int len = fr.read(buf); - while(len > 0 ) { - data += new String(buf,0,len); - len = fr.read(buf); - } - fr.close(); - } catch (FileNotFoundException e) { - return false; - } catch (IOException e) { - return false; - } - - } - - if(data.contains("Proc-Type: 4,ENCRYPTED")) - return true; - else if(data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) - return true; - else - return false; - } - - public int needUserPWInput() { - if((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12)&& - (mPKCS12Password == null || mPKCS12Password.equals(""))) { - if(mTransientPCKS12PW==null) - return R.string.pkcs12_file_encryption_key; - } - - if(mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { - if(requireTLSKeyPassword() && !nonNull(mKeyPassword)) - if(mTransientPCKS12PW==null) { - return R.string.private_key_password; - } - } - - if(isUserPWAuth() && (mPassword.equals("") || mPassword == null)) { - if(mTransientPW==null) - return R.string.password; - - } - return 0; - } - - public String getPasswordAuth() { - if(mTransientPW!=null) { - String pwcopy = mTransientPW; - mTransientPW=null; - return pwcopy; - } else { - return mPassword; - } - } - - - // Used by the Array Adapter - @Override - public String toString() { - return mName; - } - - - public String getUUIDString() { - return mUuid.toString(); - } - - - public PrivateKey getKeystoreKey() { - return mPrivateKey; - } - - - -} - - - - diff --git a/src/se/leap/openvpn/AboutFragment.java b/src/se/leap/openvpn/AboutFragment.java new file mode 100644 index 00000000..3563528b --- /dev/null +++ b/src/se/leap/openvpn/AboutFragment.java @@ -0,0 +1,60 @@ +package se.leap.openvpn; + +import android.app.Fragment; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.text.Html; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import se.leap.leapclient.R; + +public class AboutFragment extends Fragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v= inflater.inflate(R.layout.about, container, false); + TextView ver = (TextView) v.findViewById(R.id.version); + + String version; + String name="Openvpn"; + try { + PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); + version = packageinfo.versionName; + name = getString(R.string.app); + } catch (NameNotFoundException e) { + version = "error fetching version"; + } + + + ver.setText(getString(R.string.version_info,name,version)); + + TextView paypal = (TextView) v.findViewById(R.id.donatestring); + + String donatetext = getActivity().getString(R.string.donatewithpaypal); + Spanned htmltext = Html.fromHtml(donatetext); + paypal.setText(htmltext); + paypal.setMovementMethod(LinkMovementMethod.getInstance()); + + TextView translation = (TextView) v.findViewById(R.id.translation); + + // Don't print a text for myself + if ( getString(R.string.translationby).contains("Arne Schwabe")) + translation.setText(""); + else + translation.setText(R.string.translationby); + return v; + } + +} diff --git a/src/se/leap/openvpn/CIDRIP.java b/src/se/leap/openvpn/CIDRIP.java new file mode 100644 index 00000000..8c4b6709 --- /dev/null +++ b/src/se/leap/openvpn/CIDRIP.java @@ -0,0 +1,58 @@ +package se.leap.openvpn; + +class CIDRIP{ + String mIp; + int len; + public CIDRIP(String ip, String mask){ + mIp=ip; + long netmask=getInt(mask); + + // Add 33. bit to ensure the loop terminates + netmask += 1l << 32; + + int lenZeros = 0; + while((netmask & 0x1) == 0) { + lenZeros++; + netmask = netmask >> 1; + } + // Check if rest of netmask is only 1s + if(netmask != (0x1ffffffffl >> lenZeros)) { + // Asume no CIDR, set /32 + len=32; + } else { + len =32 -lenZeros; + } + + } + @Override + public String toString() { + return String.format("%s/%d",mIp,len); + } + + public boolean normalise(){ + long ip=getInt(mIp); + + long newip = ip & (0xffffffffl << (32 -len)); + if (newip != ip){ + mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff); + return true; + } else { + return false; + } + } + static long getInt(String ipaddr) { + String[] ipt = ipaddr.split("\\."); + long ip=0; + + ip += Long.parseLong(ipt[0])<< 24; + ip += Integer.parseInt(ipt[1])<< 16; + ip += Integer.parseInt(ipt[2])<< 8; + ip += Integer.parseInt(ipt[3]); + + return ip; + } + public long getInt() { + return getInt(mIp); + } + +} \ No newline at end of file diff --git a/src/se/leap/openvpn/ConfigConverter.java b/src/se/leap/openvpn/ConfigConverter.java new file mode 100644 index 00000000..8b5f316d --- /dev/null +++ b/src/se/leap/openvpn/ConfigConverter.java @@ -0,0 +1,453 @@ + +package se.leap.openvpn; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Vector; +import se.leap.leapclient.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.ListActivity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.util.Base64; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import se.leap.openvpn.ConfigParser.ConfigParseError; + +public class ConfigConverter extends ListActivity { + + public static final String IMPORT_PROFILE = "se.leap.openvpn.IMPORT_PROFILE"; + + private VpnProfile mResult; + private ArrayAdapter mArrayAdapter; + + private List mPathsegments; + + private String mAliasName=null; + + private int RESULT_INSTALLPKCS12 = 7; + + private String mPossibleName=null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.config_converter); + } + + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==R.id.cancel){ + setResult(Activity.RESULT_CANCELED); + finish(); + } else if(item.getItemId()==R.id.ok) { + if(mResult==null) { + log("Importing the config had error, cannot save it"); + return true; + } + + Intent in = installPKCS12(); + + if(in != null) + startActivityForResult(in, RESULT_INSTALLPKCS12); + else + saveProfile(); + + return true; + } + + return super.onOptionsItemSelected(item); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode==RESULT_INSTALLPKCS12) { + if(resultCode==Activity.RESULT_OK) { + showCertDialog(); + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + private void saveProfile() { + Intent result = new Intent(); + ProfileManager vpl = ProfileManager.getInstance(this); + + if(((CheckBox)findViewById(R.id.correcttls)).isChecked() && isOldCNFormat()) { + convertTLSRemote(); + } + + setUniqueProfileName(vpl); + vpl.addProfile(mResult); + vpl.saveProfile(this, mResult); + vpl.saveProfileList(this); + result.putExtra(VpnProfile.EXTRA_PROFILEUUID,mResult.getUUID().toString()); + setResult(Activity.RESULT_OK, result); + finish(); + } + + + + private void convertTLSRemote() { + if(mResult.mRemoteCN.startsWith("/")) + mResult.mRemoteCN = mResult.mRemoteCN.substring(1); + mResult.mRemoteCN = mResult.mRemoteCN.replace("/", ", "); + } + + public void showCertDialog () { + try { + KeyChain.choosePrivateKeyAlias(this, + new KeyChainAliasCallback() { + + public void alias(String alias) { + // Credential alias selected. Remember the alias selection for future use. + mResult.mAlias=alias; + saveProfile(); + } + + + }, + new String[] {"RSA"}, // List of acceptable key types. null for any + null, // issuer, null for any + mResult.mServerName, // host name of server requesting the cert, null if unavailable + -1, // port of server requesting the cert, -1 if unavailable + mAliasName); // alias to preselect, null if unavailable + } catch (ActivityNotFoundException anf) { + Builder ab = new AlertDialog.Builder(this); + ab.setTitle(R.string.broken_image_cert_title); + ab.setMessage(R.string.broken_image_cert); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + + private Intent installPKCS12() { + + if(!((CheckBox)findViewById(R.id.importpkcs12)).isChecked()) { + setAuthTypeToEmbeddedPKCS12(); + return null; + + } + String pkcs12datastr = mResult.mPKCS12Filename; + if(pkcs12datastr!=null && pkcs12datastr.startsWith(VpnProfile.INLINE_TAG)) { + Intent inkeyintent = KeyChain.createInstallIntent(); + + pkcs12datastr= pkcs12datastr.substring(VpnProfile.INLINE_TAG.length()); + + + byte[] pkcs12data = Base64.decode(pkcs12datastr, Base64.DEFAULT); + + + inkeyintent.putExtra(KeyChain.EXTRA_PKCS12,pkcs12data ); + + if(mAliasName.equals("")) + mAliasName=null; + + if(mAliasName!=null){ + inkeyintent.putExtra(KeyChain.EXTRA_NAME, mAliasName); + } + return inkeyintent; + + } + return null; + } + + + + private void setAuthTypeToEmbeddedPKCS12() { + if(mResult.mPKCS12Filename!=null && mResult.mPKCS12Filename.startsWith(VpnProfile.INLINE_TAG)) { + if(mResult.mAuthenticationType==VpnProfile.TYPE_USERPASS_KEYSTORE) + mResult.mAuthenticationType=VpnProfile.TYPE_USERPASS_PKCS12; + + if(mResult.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) + mResult.mAuthenticationType=VpnProfile.TYPE_PKCS12; + + } + } + + + + + + private void setUniqueProfileName(ProfileManager vpl) { + int i=0; + + String newname = mPossibleName; + + while(vpl.getProfileByName(newname)!=null) { + i++; + if(i==1) + newname = getString(R.string.converted_profile); + else + newname = getString(R.string.converted_profile_i,i); + } + + mResult.mName=newname; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.import_menu, menu); + return true; + } + + private String embedFile(String filename) { + return embedFile(filename, false); + } + + private String embedFile(String filename, boolean base64encode) + { + if(filename==null) + return null; + + // Already embedded, nothing to do + if(filename.startsWith(VpnProfile.INLINE_TAG)) + return filename; + + File possibleFile = findFile(filename); + if(possibleFile==null) + return filename; + else + return readFileContent(possibleFile,base64encode); + + } + + private File findFile(String filename) { + File foundfile =findFileRaw(filename); + + if (foundfile==null && filename!=null && !filename.equals("")) + log(R.string.import_could_not_open,filename); + + return foundfile; + } + + + + private File findFileRaw(String filename) + { + if(filename == null || filename.equals("")) + return null; + + // Try diffent path relative to /mnt/sdcard + File sdcard = Environment.getExternalStorageDirectory(); + File root = new File("/"); + + Vector dirlist = new Vector(); + + for(int i=mPathsegments.size()-1;i >=0 ;i--){ + String path = ""; + for (int j = 0;j<=i;j++) { + path += "/" + mPathsegments.get(j); + } + dirlist.add(new File(path)); + } + dirlist.add(sdcard); + dirlist.add(root); + + + String[] fileparts = filename.split("/"); + for(File rootdir:dirlist){ + String suffix=""; + for(int i=fileparts.length-1; i >=0;i--) { + if(i==fileparts.length-1) + suffix = fileparts[i]; + else + suffix = fileparts[i] + "/" + suffix; + + File possibleFile = new File(rootdir,suffix); + if(!possibleFile.canRead()) + continue; + + // read the file inline + return possibleFile; + + } + } + return null; + } + + String readFileContent(File possibleFile, boolean base64encode) { + byte [] filedata; + try { + filedata = readBytesFromFile(possibleFile); + } catch (IOException e) { + log(e.getLocalizedMessage()); + return null; + } + + String data; + if(base64encode) { + data = Base64.encodeToString(filedata, Base64.DEFAULT); + } else { + data = new String(filedata); + + } + return VpnProfile.INLINE_TAG + data; + + } + + + private byte[] readBytesFromFile(File file) throws IOException { + InputStream input = new FileInputStream(file); + + long len= file.length(); + + + // Create the byte array to hold the data + byte[] bytes = new byte[(int) len]; + + // Read in the bytes + int offset = 0; + int bytesRead = 0; + while (offset < bytes.length + && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { + offset += bytesRead; + } + + input.close(); + return bytes; + } + + void embedFiles() { + // This where I would like to have a c++ style + // void embedFile(std::string & option) + + if (mResult.mPKCS12Filename!=null) { + File pkcs12file = findFileRaw(mResult.mPKCS12Filename); + if(pkcs12file!=null) { + mAliasName = pkcs12file.getName().replace(".p12", ""); + } else { + mAliasName = "Imported PKCS12"; + } + } + + + mResult.mCaFilename = embedFile(mResult.mCaFilename); + mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename); + mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename); + mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename); + mResult.mPKCS12Filename = embedFile(mResult.mPKCS12Filename,true); + + + if(mResult.mUsername == null && mResult.mPassword != null ){ + String data =embedFile(mResult.mPassword); + ConfigParser.useEmbbedUserAuth(mResult, data); + } + } + + + @Override + protected void onStart() { + super.onStart(); + + mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1); + getListView().setAdapter(mArrayAdapter); + final android.content.Intent intent = getIntent (); + + if (intent != null) + { + final android.net.Uri data = intent.getData (); + if (data != null) + { + //log(R.string.import_experimental); + log(R.string.importing_config,data.toString()); + try { + if(data.getScheme().equals("file")) { + mPossibleName = data.getLastPathSegment(); + if(mPossibleName!=null){ + mPossibleName =mPossibleName.replace(".ovpn", ""); + mPossibleName =mPossibleName.replace(".conf", ""); + } + } + InputStream is = getContentResolver().openInputStream(data); + mPathsegments = data.getPathSegments(); + + doImport(is); + } catch (FileNotFoundException e) { + log(R.string.import_content_resolve_error); + } + } + } + + return; + } + + private void log(String logmessage) { + mArrayAdapter.add(logmessage); + } + + private void doImport(InputStream is) { + ConfigParser cp = new ConfigParser(); + try { + InputStreamReader isr = new InputStreamReader(is); + + cp.parseConfig(isr); + VpnProfile vp = cp.convertProfile(); + mResult = vp; + embedFiles(); + displayWarnings(); + log(R.string.import_done); + return; + + } catch (IOException e) { + log(R.string.error_reading_config_file); + log(e.getLocalizedMessage()); + } catch (ConfigParseError e) { + log(R.string.error_reading_config_file); + log(e.getLocalizedMessage()); + } + mResult=null; + + } + + private void displayWarnings() { + if(mResult.mUseCustomConfig) { + log(R.string.import_warning_custom_options); + String copt = mResult.mCustomConfigOptions; + if(copt.startsWith("#")) { + int until = copt.indexOf('\n'); + copt = copt.substring(until+1); + } + + log(copt); + } + + if(mResult.mAuthenticationType==VpnProfile.TYPE_KEYSTORE || + mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { + findViewById(R.id.importpkcs12).setVisibility(View.VISIBLE); + } + + if (isOldCNFormat()) + findViewById(R.id.correcttls).setVisibility(View.VISIBLE); + } + + private boolean isOldCNFormat() { + return mResult.mCheckRemoteCN && mResult.mRemoteCN.contains("/") && ! mResult.mRemoteCN.contains("_"); + } + + private void log(int ressourceId, Object... formatArgs) { + log(getString(ressourceId,formatArgs)); + } +} diff --git a/src/se/leap/openvpn/ConfigParser.java b/src/se/leap/openvpn/ConfigParser.java new file mode 100644 index 00000000..f57bbae9 --- /dev/null +++ b/src/se/leap/openvpn/ConfigParser.java @@ -0,0 +1,559 @@ +package se.leap.openvpn; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Locale; +import java.util.Vector; + +//! Openvpn Config FIle Parser, probably not 100% accurate but close enough + +// And rember, this is valid :) +// -- +// bar +// +public class ConfigParser { + + + private HashMap>> options = new HashMap>>(); + public void parseConfig(Reader reader) throws IOException, ConfigParseError { + + + BufferedReader br =new BufferedReader(reader); + + @SuppressWarnings("unused") + int lineno=0; + + while (true){ + String line = br.readLine(); + if(line==null) + break; + lineno++; + Vector args = parseline(line); + if(args.size() ==0) + continue; + + + if(args.get(0).startsWith("--")) + args.set(0, args.get(0).substring(2)); + + checkinlinefile(args,br); + + String optionname = args.get(0); + if(!options.containsKey(optionname)) { + options.put(optionname, new Vector>()); + } + options.get(optionname).add(args); + } + } + + private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { + String arg0 = args.get(0); + // CHeck for + if(arg0.startsWith("<") && arg0.endsWith(">")) { + String argname = arg0.substring(1, arg0.length()-1); + String inlinefile = VpnProfile.INLINE_TAG; + + String endtag = String.format("",argname); + do { + String line = br.readLine(); + if(line==null){ + throw new ConfigParseError(String.format("No endtag for starttag <%s> found",argname,argname)); + } + if(line.equals(endtag)) + break; + else { + inlinefile+=line; + inlinefile+= "\n"; + } + } while(true); + + args.clear(); + args.add(argname); + args.add(inlinefile); + } + + } + + enum linestate { + initial, + readin_single_quote + , reading_quoted, reading_unquoted, done} + + private boolean space(char c) { + // I really hope nobody is using zero bytes inside his/her config file + // to sperate parameter but here we go: + return Character.isWhitespace(c) || c == '\0'; + + } + + public class ConfigParseError extends Exception { + private static final long serialVersionUID = -60L; + + public ConfigParseError(String msg) { + super(msg); + } + } + + + // adapted openvpn's parse function to java + private Vector parseline(String line) throws ConfigParseError { + Vector parameters = new Vector(); + + if (line.length()==0) + return parameters; + + + linestate state = linestate.initial; + boolean backslash = false; + char out=0; + + int pos=0; + String currentarg=""; + + do { + // Emulate the c parsing ... + char in; + if(pos < line.length()) + in = line.charAt(pos); + else + in = '\0'; + + if (!backslash && in == '\\' && state != linestate.readin_single_quote) + { + backslash = true; + } + else + { + if (state == linestate.initial) + { + if (!space (in)) + { + if (in == ';' || in == '#') /* comment */ + break; + if (!backslash && in == '\"') + state = linestate.reading_quoted; + else if (!backslash && in == '\'') + state = linestate.readin_single_quote; + else + { + out = in; + state = linestate.reading_unquoted; + } + } + } + else if (state == linestate.reading_unquoted) + { + if (!backslash && space (in)) + state = linestate.done; + else + out = in; + } + else if (state == linestate.reading_quoted) + { + if (!backslash && in == '\"') + state = linestate.done; + else + out = in; + } + else if (state == linestate.readin_single_quote) + { + if (in == '\'') + state = linestate.done; + else + out = in; + } + + if (state == linestate.done) + { + /* ASSERT (parm_len > 0); */ + state = linestate.initial; + parameters.add(currentarg); + currentarg = ""; + out =0; + } + + if (backslash && out!=0) + { + if (!(out == '\\' || out == '\"' || space (out))) + { + throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); + } + } + backslash = false; + } + + /* store parameter character */ + if (out!=0) + { + currentarg+=out; + } + } while (pos++ < line.length()); + + return parameters; + } + + + final String[] unsupportedOptions = { "config", + "connection", + "proto-force", + "remote-random", + "tls-server" + + }; + + // Ignore all scripts + // in most cases these won't work and user who wish to execute scripts will + // figure out themselves + final String[] ignoreOptions = { "tls-client", + "askpass", + "auth-nocache", + "up", + "down", + "route-up", + "ipchange", + "route-up", + "route-pre-down", + "auth-user-pass-verify", + "dhcp-release", + "dhcp-renew", + "dh", + "management-hold", + "management", + "management-query-passwords", + "pause-exit", + "persist-key", + "register-dns", + "route-delay", + "route-gateway", + "route-metric", + "route-method", + "status", + "script-security", + "show-net-up", + "suppress-timestamps", + "tmp-dir", + "tun-ipv6", + "topology", + "win-sys", + }; + + + // This method is far too long + public VpnProfile convertProfile() throws ConfigParseError{ + boolean noauthtypeset=true; + VpnProfile np = new VpnProfile("converted Profile"); + // Pull, client, tls-client + np.clearDefaults(); + + if(options.containsKey("client") || options.containsKey("pull")) { + np.mUsePull=true; + options.remove("pull"); + options.remove("client"); + } + + Vector secret = getOption("secret", 1, 2); + if(secret!=null) + { + np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; + noauthtypeset=false; + np.mUseTLSAuth=true; + np.mTLSAuthFilename=secret.get(1); + if(secret.size()==3) + np.mTLSAuthDirection=secret.get(2); + + } + + Vector> routes = getAllOption("route", 1, 4); + if(routes!=null) { + String routeopt = ""; + for(Vector route:routes){ + String netmask = "255.255.255.255"; + if(route.size() >= 3) + netmask = route.get(2); + String net = route.get(1); + try { + CIDRIP cidr = new CIDRIP(net, netmask); + routeopt+=cidr.toString() + " "; + } catch (ArrayIndexOutOfBoundsException aioob) { + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } catch (NumberFormatException ne) { + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } + + } + np.mCustomRoutes=routeopt; + } + + // Also recognize tls-auth [inline] direction ... + Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); + if(tlsauthoptions!=null) { + for(Vector tlsauth:tlsauthoptions) { + if(tlsauth!=null) + { + if(!tlsauth.get(1).equals("[inline]")) { + np.mTLSAuthFilename=tlsauth.get(1); + np.mUseTLSAuth=true; + } + if(tlsauth.size()==3) + np.mTLSAuthDirection=tlsauth.get(2); + } + } + } + + Vector direction = getOption("key-direction", 1, 1); + if(direction!=null) + np.mTLSAuthDirection=direction.get(1); + + + if(getAllOption("redirect-gateway", 0, 5) != null) + np.mUseDefaultRoute=true; + + Vector dev =getOption("dev",1,1); + Vector devtype =getOption("dev-type",1,1); + + if( (devtype !=null && devtype.get(1).equals("tun")) || + (dev!=null && dev.get(1).startsWith("tun")) || + (devtype ==null && dev == null) ) { + //everything okay + } else { + throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); + } + + + + Vector mode =getOption("mode",1,1); + if (mode != null){ + if(!mode.get(1).equals("p2p")) + throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); + } + + Vector port = getOption("port", 1,1); + if(port!=null){ + np.mServerPort = port.get(1); + } + + Vector proto = getOption("proto", 1,1); + if(proto!=null){ + np.mUseUdp=isUdpProto(proto.get(1));; + } + + // Parse remote config + Vector remote = getOption("remote",1,3); + if(remote != null){ + switch (remote.size()) { + case 4: + np.mUseUdp=isUdpProto(remote.get(3)); + case 3: + np.mServerPort = remote.get(2); + case 2: + np.mServerName = remote.get(1); + } + } + + Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); + if(dhcpoptions!=null) { + for(Vector dhcpoption:dhcpoptions) { + String type=dhcpoption.get(1); + String arg = dhcpoption.get(2); + if(type.equals("DOMAIN")) { + np.mSearchDomain=dhcpoption.get(2); + } else if(type.equals("DNS")) { + np.mOverrideDNS=true; + if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) + np.mDNS1=arg; + else + np.mDNS2=arg; + } + } + } + + Vector ifconfig = getOption("ifconfig", 2, 2); + if(ifconfig!=null) { + CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); + np.mIPv4Address=cidr.toString(); + } + + if(getOption("remote-random-hostname", 0, 0)!=null) + np.mUseRandomHostname=true; + + if(getOption("float", 0, 0)!=null) + np.mUseFloat=true; + + if(getOption("comp-lzo", 0, 1)!=null) + np.mUseLzo=true; + + Vector cipher = getOption("cipher", 1, 1); + if(cipher!=null) + np.mCipher= cipher.get(1); + + Vector ca = getOption("ca",1,1); + if(ca!=null){ + np.mCaFilename = ca.get(1); + } + + Vector cert = getOption("cert",1,1); + if(cert!=null){ + np.mClientCertFilename = cert.get(1); + np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; + noauthtypeset=false; + } + Vector key= getOption("key",1,1); + if(key!=null) + np.mClientKeyFilename=key.get(1); + + Vector pkcs12 = getOption("pkcs12",1,1); + if(pkcs12!=null) { + np.mPKCS12Filename = pkcs12.get(1); + np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; + noauthtypeset=false; + } + + Vector tlsremote = getOption("tls-remote",1,1); + if(tlsremote!=null){ + np.mRemoteCN = tlsremote.get(1); + np.mCheckRemoteCN=true; + } + + Vector verb = getOption("verb",1,1); + if(verb!=null){ + np.mVerb=verb.get(1); + } + + + if(getOption("nobind", 0, 0) != null) + np.mNobind=true; + + if(getOption("persist-tun", 0,0) != null) + np.mPersistTun=true; + + Vector connectretry = getOption("connect-retry", 1, 1); + if(connectretry!=null) + np.mConnectRetry =connectretry.get(1); + + Vector connectretrymax = getOption("connect-retry-max", 1, 1); + if(connectretrymax!=null) + np.mConnectRetryMax =connectretrymax.get(1); + + Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); + if(remotetls!=null) + if(remotetls.get(0).get(1).equals("server")) + np.mExpectTLSCert=true; + else + options.put("remotetls",remotetls); + + Vector authuser = getOption("auth-user-pass",0,1); + if(authuser !=null){ + if(noauthtypeset) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS; + } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES; + } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; + } + if(authuser.size()>1) { + // Set option value to password get to get canche to embed later. + np.mUsername=null; + np.mPassword=authuser.get(1); + useEmbbedUserAuth(np,authuser.get(1)); + } + + } + + + // Check the other options + + checkIgnoreAndInvalidOptions(np); + fixup(np); + + return np; + } + + private boolean isUdpProto(String proto) throws ConfigParseError { + boolean isudp; + if(proto.equals("udp") || proto.equals("udp6")) + isudp=true; + else if (proto.equals("tcp-client") || + proto.equals("tcp") || + proto.equals("tcp6") || + proto.endsWith("tcp6-client")) + isudp =false; + else + throw new ConfigParseError("Unsupported option to --proto " + proto); + return isudp; + } + + static public void useEmbbedUserAuth(VpnProfile np,String inlinedata) + { + String data = inlinedata.replace(VpnProfile.INLINE_TAG, ""); + String[] parts = data.split("\n"); + if(parts.length >= 2) { + np.mUsername=parts[0]; + np.mPassword=parts[1]; + } + } + + private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { + for(String option:unsupportedOptions) + if(options.containsKey(option)) + throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option)); + + for(String option:ignoreOptions) + // removing an item which is not in the map is no error + options.remove(option); + + if(options.size()> 0) { + String custom = "# These Options were found in the config file do not map to config settings:\n"; + + for(Vector> option:options.values()) { + for(Vector optionsline: option) { + for (String arg : optionsline) + custom+= VpnProfile.openVpnEscape(arg) + " "; + } + custom+="\n"; + + } + np.mCustomConfigOptions = custom; + np.mUseCustomConfig=true; + + } + } + + + private void fixup(VpnProfile np) { + if(np.mRemoteCN.equals(np.mServerName)) { + np.mRemoteCN=""; + } + } + + private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> alloptions = getAllOption(option, minarg, maxarg); + if(alloptions==null) + return null; + else + return alloptions.lastElement(); + } + + + private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> args = options.get(option); + if(args==null) + return null; + + for(Vector optionline:args) + + if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) { + String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d", + option,optionline.size()-1,minarg,maxarg ); + throw new ConfigParseError(err); + } + options.remove(option); + return args; + } + +} + + + + diff --git a/src/se/leap/openvpn/FaqFragment.java b/src/se/leap/openvpn/FaqFragment.java new file mode 100644 index 00000000..2b9f02eb --- /dev/null +++ b/src/se/leap/openvpn/FaqFragment.java @@ -0,0 +1,43 @@ +package se.leap.openvpn; + +import android.app.Fragment; +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import se.leap.leapclient.R; + +public class FaqFragment extends Fragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v= inflater.inflate(R.layout.faq, container, false); + + insertHtmlEntry(v,R.id.broken_images_faq,R.string.broken_images_faq); + insertHtmlEntry(v,R.id.faq_howto,R.string.faq_howto); + insertHtmlEntry(v, R.id.baterry_consumption, R.string.baterry_consumption); + insertHtmlEntry(v, R.id.faq_tethering, R.string.faq_tethering); + + return v; + + + + } + + private void insertHtmlEntry (View v, int viewId, int stringId) { + TextView faqitem = (TextView) v.findViewById(viewId); + faqitem.setText(Html.fromHtml(getActivity().getString(stringId))); + faqitem.setMovementMethod(LinkMovementMethod.getInstance()); + } + +} diff --git a/src/se/leap/openvpn/FileProvider.java b/src/se/leap/openvpn/FileProvider.java new file mode 100644 index 00000000..d2714b2c --- /dev/null +++ b/src/se/leap/openvpn/FileProvider.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package se.leap.openvpn; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import android.content.ContentProvider; +import android.content.ContentProvider.PipeDataWriter; +import android.content.ContentValues; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; +import android.util.Log; + +/** + * A very simple content provider that can serve arbitrary asset files from + * our .apk. + */ +public class FileProvider extends ContentProvider +implements PipeDataWriter { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + try { + File dumpfile = getFileFromURI(uri); + + + MatrixCursor c = new MatrixCursor(projection); + + Object[] row = new Object[projection.length]; + int i=0; + for (String r:projection) { + if(r.equals(OpenableColumns.SIZE)) + row[i] = dumpfile.length(); + if(r.equals(OpenableColumns.DISPLAY_NAME)) + row[i] = dumpfile.getName(); + i++; + } + c.addRow(row); + return c; + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // Don't support inserts. + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // Don't support deletes. + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // Don't support updates. + return 0; + } + + @Override + public String getType(Uri uri) { + // For this sample, assume all files are .apks. + return "application/octet-stream"; + } + + @Override + public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { + File dumpfile = getFileFromURI(uri); + + try { + + InputStream is = new FileInputStream(dumpfile); + // Start a new thread that pipes the stream data back to the caller. + return new AssetFileDescriptor( + openPipeHelper(uri, null, null, is, this), 0, + dumpfile.length()); + } catch (IOException e) { + FileNotFoundException fnf = new FileNotFoundException("Unable to open minidump " + uri); + throw fnf; + } + } + + private File getFileFromURI(Uri uri) throws FileNotFoundException { + // Try to open an asset with the given name. + String path = uri.getPath(); + if(path.startsWith("/")) + path = path.replaceFirst("/", ""); + + // I think this already random enough, no need for magic secure cookies + // 1f9563a4-a1f5-2165-255f2219-111823ef.dmp + if (!path.matches("^[0-9a-z-.]*(dmp|dmp.log)$")) + throw new FileNotFoundException("url not in expect format " + uri); + File cachedir = getContext().getCacheDir(); + File dumpfile = new File(cachedir,path); + return dumpfile; + } + + @Override + public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, + Bundle opts, InputStream args) { + // Transfer data from the asset to the pipe the client is reading. + byte[] buffer = new byte[8192]; + int n; + FileOutputStream fout = new FileOutputStream(output.getFileDescriptor()); + try { + while ((n=args.read(buffer)) >= 0) { + fout.write(buffer, 0, n); + } + } catch (IOException e) { + Log.i("OpenVPNFileProvider", "Failed transferring", e); + } finally { + try { + args.close(); + } catch (IOException e) { + } + try { + fout.close(); + } catch (IOException e) { + } + } + } +} diff --git a/src/se/leap/openvpn/FileSelect.java b/src/se/leap/openvpn/FileSelect.java new file mode 100644 index 00000000..4abf6c31 --- /dev/null +++ b/src/se/leap/openvpn/FileSelect.java @@ -0,0 +1,208 @@ +package se.leap.openvpn; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import se.leap.leapclient.R; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.util.Base64; + +public class FileSelect extends Activity { + public static final String RESULT_DATA = "RESULT_PATH"; + public static final String START_DATA = "START_DATA"; + public static final String WINDOW_TITLE = "WINDOW_TILE"; + public static final String NO_INLINE_SELECTION = "se.leap.openvpn.NO_INLINE_SELECTION"; + public static final String SHOW_CLEAR_BUTTON = "se.leap.openvpn.SHOW_CLEAR_BUTTON"; + public static final String DO_BASE64_ENCODE = "se.leap.openvpn.BASE64ENCODE"; + + private FileSelectionFragment mFSFragment; + private InlineFileTab mInlineFragment; + private String mData; + private Tab inlineFileTab; + private Tab fileExplorerTab; + private boolean mNoInline; + private boolean mShowClear; + private boolean mBase64Encode; + + + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.file_dialog); + + mData = getIntent().getStringExtra(START_DATA); + if(mData==null) + mData=Environment.getExternalStorageDirectory().getPath(); + + String title = getIntent().getStringExtra(WINDOW_TITLE); + int titleId = getIntent().getIntExtra(WINDOW_TITLE, 0); + if(titleId!=0) + title =getString(titleId); + if(title!=null) + setTitle(title); + + mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false); + mShowClear = getIntent().getBooleanExtra(SHOW_CLEAR_BUTTON, false); + mBase64Encode = getIntent().getBooleanExtra(DO_BASE64_ENCODE, false); + + ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + fileExplorerTab = bar.newTab().setText(R.string.file_explorer_tab); + inlineFileTab = bar.newTab().setText(R.string.inline_file_tab); + + mFSFragment = new FileSelectionFragment(); + fileExplorerTab.setTabListener(new MyTabsListener(this, mFSFragment)); + bar.addTab(fileExplorerTab); + + if(!mNoInline) { + mInlineFragment = new InlineFileTab(); + inlineFileTab.setTabListener(new MyTabsListener(this, mInlineFragment)); + bar.addTab(inlineFileTab); + } else { + mFSFragment.setNoInLine(); + } + + + } + + protected boolean showClear() { + if(mData == null || mData.equals("")) + return false; + else + return mShowClear; + } + + protected class MyTabsListener implements ActionBar.TabListener + { + private Fragment mFragment; + private boolean mAdded=false; + + public MyTabsListener( Activity activity, Fragment fragment){ + this.mFragment = fragment; + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + // Check if the fragment is already initialized + if (!mAdded) { + // If not, instantiate and add it to the activity + ft.add(android.R.id.content, mFragment); + mAdded =true; + } else { + // If it exists, simply attach it in order to show it + ft.attach(mFragment); + } + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + ft.detach(mFragment); + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + + } + } + + public void importFile(String path) { + File ifile = new File(path); + Exception fe = null; + try { + + String data =VpnProfile.INLINE_TAG; + + byte[] filedata = readBytesFromFile(ifile) ; + if(mBase64Encode) + data += Base64.encodeToString(filedata, Base64.DEFAULT); + else + data += new String(filedata); + + mData =data; + mInlineFragment.setData(data); + getActionBar().selectTab(inlineFileTab); + } catch (FileNotFoundException e) { + fe = e; + } catch (IOException e) { + fe =e; + } + if(fe!=null) { + Builder ab = new AlertDialog.Builder(this); + ab.setTitle(R.string.error_importing_file); + ab.setMessage(getString(R.string.import_error_message) + "\n" + fe.getLocalizedMessage()); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + private byte[] readBytesFromFile(File file) throws IOException { + InputStream input = new FileInputStream(file); + + long len= file.length(); + + + // Create the byte array to hold the data + byte[] bytes = new byte[(int) len]; + + // Read in the bytes + int offset = 0; + int bytesRead = 0; + while (offset < bytes.length + && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) { + offset += bytesRead; + } + + input.close(); + return bytes; + } + + + public void setFile(String path) { + Intent intent = new Intent(); + intent.putExtra(RESULT_DATA, path); + setResult(Activity.RESULT_OK,intent); + finish(); + } + + public String getSelectPath() { + if(!mData.startsWith(VpnProfile.INLINE_TAG)) + return mData; + else + return Environment.getExternalStorageDirectory().getPath(); + } + + public CharSequence getInlineData() { + if(mData.startsWith(VpnProfile.INLINE_TAG)) + return mData.substring(VpnProfile.INLINE_TAG.length()); + else + return ""; + } + + public void clearData() { + Intent intent = new Intent(); + intent.putExtra(RESULT_DATA, (String)null); + setResult(Activity.RESULT_OK,intent); + finish(); + + } + + public void saveInlineData(String string) { + Intent intent = new Intent(); + intent.putExtra(RESULT_DATA, string); + setResult(Activity.RESULT_OK,intent); + finish(); + + } +} diff --git a/src/se/leap/openvpn/FileSelectLayout.java b/src/se/leap/openvpn/FileSelectLayout.java new file mode 100644 index 00000000..2ca4490d --- /dev/null +++ b/src/se/leap/openvpn/FileSelectLayout.java @@ -0,0 +1,92 @@ +package se.leap.openvpn; + +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import se.leap.leapclient.R; + + +public class FileSelectLayout extends LinearLayout implements OnClickListener { + + private TextView mDataView; + private String mData; + private Fragment mFragment; + private int mTaskId; + private Button mSelectButton; + private boolean mBase64Encode; + private String mTitle; + private boolean mShowClear; + + public FileSelectLayout( Context context,AttributeSet attrset) { + super(context,attrset); + inflate(getContext(), R.layout.file_select, this); + + TypedArray ta = context.obtainStyledAttributes(attrset,R.styleable.FileSelectLayout); + + mTitle = ta.getString(R.styleable.FileSelectLayout_title); + + TextView tview = (TextView) findViewById(R.id.file_title); + tview.setText(mTitle); + + mDataView = (TextView) findViewById(R.id.file_selected_item); + mSelectButton = (Button) findViewById(R.id.file_select_button); + mSelectButton.setOnClickListener(this); + + } + + public void setFragment(Fragment fragment, int i) + { + mTaskId = i; + mFragment = fragment; + } + + public void getCertificateFileDialog() { + Intent startFC = new Intent(getContext(),FileSelect.class); + startFC.putExtra(FileSelect.START_DATA, mData); + startFC.putExtra(FileSelect.WINDOW_TITLE,mTitle); + if(mBase64Encode) + startFC.putExtra(FileSelect.DO_BASE64_ENCODE, true); + if(mShowClear) + startFC.putExtra(FileSelect.SHOW_CLEAR_BUTTON, true); + mFragment.startActivityForResult(startFC,mTaskId); + } + + + public String getData() { + return mData; + } + + public void setData(String data) { + mData = data; + if(data==null) + mDataView.setText(mFragment.getString(R.string.no_data)); + else if(mData.startsWith(VpnProfile.INLINE_TAG)) + mDataView.setText(R.string.inline_file_data); + else + mDataView.setText(data); + + } + + @Override + public void onClick(View v) { + if(v == mSelectButton) { + getCertificateFileDialog(); + } + } + + public void setBase64Encode() { + mBase64Encode =true; + } + + public void setShowClear() { + mShowClear=true; + } + +} diff --git a/src/se/leap/openvpn/FileSelectionFragment.java b/src/se/leap/openvpn/FileSelectionFragment.java new file mode 100644 index 00000000..5bc981fe --- /dev/null +++ b/src/se/leap/openvpn/FileSelectionFragment.java @@ -0,0 +1,265 @@ +package se.leap.openvpn; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.TreeMap; +import se.leap.leapclient.R; + +import android.app.AlertDialog; +import android.app.ListFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; + +/** + * Activity para escolha de arquivos/diretorios. + * + * @author android + * + */ +public class FileSelectionFragment extends ListFragment { + + private static final String ITEM_KEY = "key"; + private static final String ITEM_IMAGE = "image"; + private static final String ROOT = "/"; + + + private List path = null; + private TextView myPath; + private ArrayList> mList; + + private Button selectButton; + + + private String parentPath; + private String currentPath = ROOT; + + + private String[] formatFilter = null; + + private File selectedFile; + private HashMap lastPositions = new HashMap(); + private String mStartPath; + private Button mImportFile; + private Button mClearButton; + private boolean mHideImport=false; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.file_dialog_main, container,false); + + myPath = (TextView) v.findViewById(R.id.path); + + selectButton = (Button) v.findViewById(R.id.fdButtonSelect); + selectButton.setEnabled(false); + selectButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (selectedFile != null) { + ((FileSelect) getActivity()).setFile(selectedFile.getPath()); + + } + } + }); + + mClearButton = (Button) v.findViewById(R.id.fdClear); + mClearButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + ((FileSelect) getActivity()).clearData(); + } + }); + if(!((FileSelect) getActivity()).showClear()) { + mClearButton.setVisibility(View.GONE); + mClearButton.setEnabled(false); + } + + + mImportFile = (Button) v.findViewById(R.id.importfile); + mImportFile.setEnabled(false); + mImportFile.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + ((FileSelect) getActivity()).importFile(selectedFile.getPath()); + } + }); + + if(mHideImport== true) { + mImportFile.setVisibility(View.GONE); + } + + + + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mStartPath = ((FileSelect) getActivity()).getSelectPath(); + getDir(mStartPath); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + private void getDir(String dirPath) { + + boolean useAutoSelection = dirPath.length() < currentPath.length(); + + Integer position = lastPositions.get(parentPath); + + getDirImpl(dirPath); + + if (position != null && useAutoSelection) { + getListView().setSelection(position); + } + + } + + /** + * Monta a estrutura de arquivos e diretorios filhos do diretorio fornecido. + * + * @param dirPath + * Diretorio pai. + */ + private void getDirImpl(final String dirPath) { + + currentPath = dirPath; + + final List item = new ArrayList(); + path = new ArrayList(); + mList = new ArrayList>(); + + File f = new File(currentPath); + File[] files = f.listFiles(); + if (files == null) { + currentPath = ROOT; + f = new File(currentPath); + files = f.listFiles(); + } + + myPath.setText(getText(R.string.location) + ": " + currentPath); + + if (!currentPath.equals(ROOT)) { + + item.add(ROOT); + addItem(ROOT, R.drawable.folder); + path.add(ROOT); + + item.add("../"); + addItem("../", R.drawable.folder); + path.add(f.getParent()); + parentPath = f.getParent(); + + } + + TreeMap dirsMap = new TreeMap(); + TreeMap dirsPathMap = new TreeMap(); + TreeMap filesMap = new TreeMap(); + TreeMap filesPathMap = new TreeMap(); + for (File file : files) { + if (file.isDirectory()) { + String dirName = file.getName(); + dirsMap.put(dirName, dirName); + dirsPathMap.put(dirName, file.getPath()); + } else { + final String fileName = file.getName(); + final String fileNameLwr = fileName.toLowerCase(); + // se ha um filtro de formatos, utiliza-o + if (formatFilter != null) { + boolean contains = false; + for (int i = 0; i < formatFilter.length; i++) { + final String formatLwr = formatFilter[i].toLowerCase(); + if (fileNameLwr.endsWith(formatLwr)) { + contains = true; + break; + } + } + if (contains) { + filesMap.put(fileName, fileName); + filesPathMap.put(fileName, file.getPath()); + } + // senao, adiciona todos os arquivos + } else { + filesMap.put(fileName, fileName); + filesPathMap.put(fileName, file.getPath()); + } + } + } + item.addAll(dirsMap.tailMap("").values()); + item.addAll(filesMap.tailMap("").values()); + path.addAll(dirsPathMap.tailMap("").values()); + path.addAll(filesPathMap.tailMap("").values()); + + SimpleAdapter fileList = new SimpleAdapter(getActivity(), mList, R.layout.file_dialog_row, new String[] { + ITEM_KEY, ITEM_IMAGE }, new int[] { R.id.fdrowtext, R.id.fdrowimage }); + + for (String dir : dirsMap.tailMap("").values()) { + addItem(dir, R.drawable.folder); + } + + for (String file : filesMap.tailMap("").values()) { + addItem(file, R.drawable.file); + } + + fileList.notifyDataSetChanged(); + + setListAdapter(fileList); + + } + + private void addItem(String fileName, int imageId) { + HashMap item = new HashMap(); + item.put(ITEM_KEY, fileName); + item.put(ITEM_IMAGE, imageId); + mList.add(item); + } + + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + + File file = new File(path.get(position)); + + if (file.isDirectory()) { + selectButton.setEnabled(false); + mImportFile.setEnabled(false); + + if (file.canRead()) { + lastPositions.put(currentPath, position); + getDir(path.get(position)); + } else { + new AlertDialog.Builder(getActivity()).setIcon(R.drawable.icon) + .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder)) + .setPositiveButton("OK", null).show(); + } + } else { + selectedFile = file; + v.setSelected(true); + selectButton.setEnabled(true); + mImportFile.setEnabled(true); + } + } + + public void setNoInLine() { + mHideImport=true; + } + +} diff --git a/src/se/leap/openvpn/GeneralSettings.java b/src/se/leap/openvpn/GeneralSettings.java new file mode 100644 index 00000000..34657914 --- /dev/null +++ b/src/se/leap/openvpn/GeneralSettings.java @@ -0,0 +1,31 @@ +package se.leap.openvpn; +import java.io.File; +import se.leap.leapclient.R; + +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; + +public class GeneralSettings extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.general_settings); + Preference loadtun = findPreference("loadTunModule"); + if(!isTunModuleAvailable()) + loadtun.setEnabled(false); + } + + private boolean isTunModuleAvailable() { + // Check if the tun module exists on the file system + if(new File("/system/lib/modules/tun.ko").length() > 10) + return true; + return false; + } + + + } \ No newline at end of file diff --git a/src/se/leap/openvpn/InlineFileTab.java b/src/se/leap/openvpn/InlineFileTab.java new file mode 100644 index 00000000..40995ceb --- /dev/null +++ b/src/se/leap/openvpn/InlineFileTab.java @@ -0,0 +1,66 @@ +package se.leap.openvpn; + +import android.app.Fragment; +import android.os.Bundle; +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.EditText; +import se.leap.leapclient.R; + +public class InlineFileTab extends Fragment +{ + + private static final int MENU_SAVE = 0; + private EditText mInlineData; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mInlineData.setText(((FileSelect)getActivity()).getInlineData()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + + View v = inflater.inflate(R.layout.file_dialog_inline, container, false); + mInlineData =(EditText) v.findViewById(R.id.inlineFileData); + return v; + } + + public void setData(String data) { + if(mInlineData!=null) + mInlineData.setText(data); + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_SAVE, 0, "Use inline data") + .setIcon(android.R.drawable.ic_menu_save) + .setAlphabeticShortcut('u') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==MENU_SAVE){ + ((FileSelect)getActivity()).saveInlineData(mInlineData.getText().toString()); + return true; + } + return super.onOptionsItemSelected(item); + } + +} \ No newline at end of file diff --git a/src/se/leap/openvpn/LaunchVPN.java b/src/se/leap/openvpn/LaunchVPN.java new file mode 100644 index 00000000..3df2aacb --- /dev/null +++ b/src/se/leap/openvpn/LaunchVPN.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package se.leap.openvpn; + +import java.io.IOException; +import java.util.Collection; +import java.util.Vector; +import se.leap.leapclient.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Bundle; +import android.os.Parcelable; +import android.preference.PreferenceManager; +import android.text.InputType; +import android.text.method.PasswordTransformationMethod; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +/** + * This Activity actually handles two stages of a launcher shortcut's life cycle. + * + * 1. Your application offers to provide shortcuts to the launcher. When + * the user installs a shortcut, an activity within your application + * generates the actual shortcut and returns it to the launcher, where it + * is shown to the user as an icon. + * + * 2. Any time the user clicks on an installed shortcut, an intent is sent. + * Typically this would then be handled as necessary by an activity within + * your application. + * + * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form + * of an {@link android.content.Intent} that the launcher will use to create the shortcut. + * + * You can also implement this in an interactive way, by having your activity actually present + * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, + * media item, or action. + * + * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents + * of the incoming {@link android.content.Intent}. + * + * In a real application, you would probably use the shortcut intent to display specific content + * or start a particular operation. + */ +public class LaunchVPN extends ListActivity implements OnItemClickListener { + + public static final String EXTRA_KEY = "se.leap.openvpn.shortcutProfileUUID"; + public static final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; + public static final String EXTRA_HIDELOG = "se.leap.openvpn.showNoLogWindow";; + + private static final int START_VPN_PROFILE= 70; + + + private ProfileManager mPM; + private VpnProfile mSelectedProfile; + private boolean mhideLog=false; + + private boolean mCmfixed=false; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mPM =ProfileManager.getInstance(this); + + } + + @Override + protected void onStart() { + super.onStart(); + // Resolve the intent + + final Intent intent = getIntent(); + final String action = intent.getAction(); + + // If the intent is a request to create a shortcut, we'll do that and exit + + + if(Intent.ACTION_MAIN.equals(action)) { + // we got called to be the starting point, most likely a shortcut + String shortcutUUID = intent.getStringExtra( EXTRA_KEY); + String shortcutName = intent.getStringExtra( EXTRA_NAME); + mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); + + VpnProfile profileToConnect = ProfileManager.get(shortcutUUID); + if(shortcutName != null && profileToConnect ==null) + profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); + + if(profileToConnect ==null) { + OpenVPN.logError(R.string.shortcut_profile_notfound); + // show Log window to display error + showLogWindow(); + finish(); + return; + } + + mSelectedProfile = profileToConnect; + launchVPN(); + + } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { + createListView(); + } + } + + private void createListView() { + ListView lv = getListView(); + //lv.setTextFilterEnabled(true); + + Collection vpnlist = mPM.getProfiles(); + + Vector vpnnames=new Vector(); + for (VpnProfile vpnProfile : vpnlist) { + vpnnames.add(vpnProfile.mName); + } + + + + ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnnames); + lv.setAdapter(adapter); + + lv.setOnItemClickListener(this); + } + + /** + * This function creates a shortcut and returns it to the caller. There are actually two + * intents that you will send back. + * + * The first intent serves as a container for the shortcut and is returned to the launcher by + * setResult(). This intent must contain three fields: + * + *
    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ * + * If you use a simple drawable resource, note that you must wrapper it using + * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so + * that the launcher can access resources that are stored in your application's .apk file. If + * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras + * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. + * + * The shortcut intent can be any intent that you wish the launcher to send, when the user + * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} + * with an appropriate Uri for your content, but any Intent will work here as long as it + * triggers the desired action within your Activity. + * @param profile + */ + private void setupShortcut(VpnProfile profile) { + // First, set up the shortcut intent. For this example, we simply create an intent that + // will bring us directly back to this activity. A more typical implementation would use a + // data Uri in order to display a more specific result, or a custom action in order to + // launch a specific operation. + + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClass(this, LaunchVPN.class); + shortcutIntent.putExtra(EXTRA_KEY,profile.getUUID().toString()); + + // Then, set up the container intent (the response to the caller) + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); + Parcelable iconResource = Intent.ShortcutIconResource.fromContext( + this, R.drawable.icon); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + + // Now, return the result to the launcher + + setResult(RESULT_OK, intent); + } + + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + String profilename = ((TextView) view).getText().toString(); + + VpnProfile profile = mPM.getProfileByName(profilename); + + // if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { + setupShortcut(profile); + finish(); + return; + // } + + } + + + + private void askForPW(final int type) { + + final EditText entry = new EditText(this); + entry.setSingleLine(); + entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + entry.setTransformationMethod(new PasswordTransformationMethod()); + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Need " + getString(type)); + dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); + dialog.setView(entry); + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String pw = entry.getText().toString(); + if(type == R.string.password) { + mSelectedProfile.mTransientPW = pw; + } else { + mSelectedProfile.mTransientPCKS12PW = pw; + } + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + + } + + }); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + + dialog.create().show(); + + } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if(requestCode==START_VPN_PROFILE) { + if(resultCode == Activity.RESULT_OK) { + int needpw = mSelectedProfile.needUserPWInput(); + if(needpw !=0) { + askForPW(needpw); + } else { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean showlogwindow = prefs.getBoolean("showlogwindow", true); + + if(!mhideLog && showlogwindow) + showLogWindow(); + new startOpenVpnThread().start(); + } + } else if (resultCode == Activity.RESULT_CANCELED) { + // User does not want us to start, so we just vanish + finish(); + } + } + } + void showLogWindow() { + + Intent startLW = new Intent(getBaseContext(),LogWindow.class); + startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(startLW); + + } + + void showConfigErrorDialog(int vpnok) { + AlertDialog.Builder d = new AlertDialog.Builder(this); + d.setTitle(R.string.config_error_found); + d.setMessage(vpnok); + d.setPositiveButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + + } + }); + d.show(); + } + + void launchVPN () { + int vpnok = mSelectedProfile.checkProfile(this); + if(vpnok!= R.string.no_error_found) { + showConfigErrorDialog(vpnok); + return; + } + + Intent intent = VpnService.prepare(this); + // Check if we want to fix /dev/tun + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); + boolean loadTunModule = prefs.getBoolean("loadTunModule", false); + + if(loadTunModule) + execeuteSUcmd("insmod /system/lib/modules/tun.ko"); + + if(usecm9fix && !mCmfixed ) { + execeuteSUcmd("chown system /dev/tun"); + } + + + if (intent != null) { + // Start the query + try { + startActivityForResult(intent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + OpenVPN.logError(R.string.no_vpn_support_image); + showLogWindow(); + } + } else { + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + } + + } + + private void execeuteSUcmd(String command) { + ProcessBuilder pb = new ProcessBuilder(new String[] {"su","-c",command}); + try { + Process p = pb.start(); + int ret = p.waitFor(); + if(ret ==0) + mCmfixed=true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private class startOpenVpnThread extends Thread { + + @Override + public void run() { + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + finish(); + + } + + } + + +} diff --git a/src/se/leap/openvpn/LogWindow.java b/src/se/leap/openvpn/LogWindow.java new file mode 100644 index 00000000..abd1acd6 --- /dev/null +++ b/src/se/leap/openvpn/LogWindow.java @@ -0,0 +1,351 @@ +package se.leap.openvpn; + +import java.util.Vector; +import se.leap.leapclient.R; + +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.ListActivity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import se.leap.openvpn.OpenVPN.LogItem; +import se.leap.openvpn.OpenVPN.LogListener; +import se.leap.openvpn.OpenVPN.StateListener; + +public class LogWindow extends ListActivity implements StateListener { + private static final int START_VPN_CONFIG = 0; + private String[] mBconfig=null; + + + class LogWindowListAdapter implements ListAdapter, LogListener, Callback { + + private static final int MESSAGE_NEWLOG = 0; + + private static final int MESSAGE_CLEARLOG = 1; + + private Vector myEntries=new Vector(); + + private Handler mHandler; + + private Vector observers=new Vector(); + + + public LogWindowListAdapter() { + initLogBuffer(); + + if (mHandler == null) { + mHandler = new Handler(this); + } + + OpenVPN.addLogListener(this); + } + + + + private void initLogBuffer() { + myEntries.clear(); + for (LogItem litem : OpenVPN.getlogbuffer()) { + myEntries.add(litem.getString(getContext())); + } + } + + String getLogStr() { + String str = ""; + for(String entry:myEntries) { + str+=entry + '\n'; + } + return str; + } + + + private void shareLog() { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file)); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Send Logfile")); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + + @Override + public int getCount() { + return myEntries.size(); + } + + @Override + public Object getItem(int position) { + return myEntries.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v; + if(convertView==null) + v = new TextView(getBaseContext()); + else + v = (TextView) convertView; + v.setText(myEntries.get(position)); + return v; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return myEntries.isEmpty(); + + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void newLog(LogItem logmessage) { + Message msg = Message.obtain(); + msg.what=MESSAGE_NEWLOG; + Bundle mbundle=new Bundle(); + mbundle.putString("logmessage", logmessage.getString(getBaseContext())); + msg.setData(mbundle); + mHandler.sendMessage(msg); + } + + @Override + public boolean handleMessage(Message msg) { + // We have been called + if(msg.what==MESSAGE_NEWLOG) { + + String logmessage = msg.getData().getString("logmessage"); + myEntries.add(logmessage); + + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } else if (msg.what == MESSAGE_CLEARLOG) { + initLogBuffer(); + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } + + return true; + } + + void clearLog() { + // Actually is probably called from GUI Thread as result of the user + // pressing a button. But better safe than sorry + OpenVPN.clearLog(); + OpenVPN.logMessage(0,"","Log cleared."); + mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); + } + } + + + + private LogWindowListAdapter ladapter; + private TextView mSpeedView; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==R.id.clearlog) { + ladapter.clearLog(); + return true; + } else if(item.getItemId()==R.id.cancel){ + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.title_cancel); + builder.setMessage(R.string.cancel_connection_query); + builder.setNegativeButton(android.R.string.no, null); + builder.setPositiveButton(android.R.string.yes, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ProfileManager.setConntectedVpnProfileDisconnected(getApplicationContext()); + OpenVpnManagementThread.stopOpenVPN(); + } + }); + + builder.show(); + return true; + } else if(item.getItemId()==R.id.info) { + if(mBconfig==null) + OpenVPN.triggerLogBuilderConfig(); + + } else if(item.getItemId()==R.id.send) { + ladapter.shareLog(); + } else if(item.getItemId()==R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); + + if(lastConnectedprofile!=null) { + Intent vprefintent = new Intent(this,VPNPreferences.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent,START_VPN_CONFIG); + } else { + Toast.makeText(this, R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } + + + } + + return super.onOptionsItemSelected(item); + + } + + protected Context getContext() { + return this; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.logmenu, menu); + return true; + } + + + @Override + protected void onResume() { + super.onResume(); + OpenVPN.addStateListener(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == START_VPN_CONFIG && resultCode==RESULT_OK) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + final VpnProfile profile = ProfileManager.get(configuredVPN); + ProfileManager.getInstance(this).saveProfile(this, profile); + // Name could be modified, reset List adapter + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.configuration_changed); + dialog.setMessage(R.string.restart_vpn_after_change); + + + dialog.setPositiveButton(R.string.restart, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(getBaseContext(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } + + + }); + dialog.setNegativeButton(R.string.ignore, null); + dialog.create().show(); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onStop() { + super.onStop(); + OpenVPN.removeStateListener(this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.logwindow); + ListView lv = getListView(); + + lv.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView parent, View view, + int position, long id) { + ClipboardManager clipboard = (ClipboardManager) + getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); + clipboard.setPrimaryClip(clip); + Toast.makeText(getBaseContext(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); + return true; + } + }); + + ladapter = new LogWindowListAdapter(); + lv.setAdapter(ladapter); + + mSpeedView = (TextView) findViewById(R.id.speed); + } + + @Override + public void updateState(final String status,final String logmessage, final int resid) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + String prefix=getString(resid) + ":"; + if (status.equals("BYTECOUNT") || status.equals("NOPROCESS") ) + prefix=""; + mSpeedView.setText(prefix + logmessage); + } + }); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + OpenVPN.removeLogListener(ladapter); + } + +} diff --git a/src/se/leap/openvpn/MainActivity.java b/src/se/leap/openvpn/MainActivity.java new file mode 100644 index 00000000..d33fd832 --- /dev/null +++ b/src/se/leap/openvpn/MainActivity.java @@ -0,0 +1,44 @@ +package se.leap.openvpn; + +import java.util.List; +import se.leap.leapclient.R; + +import android.content.Intent; +import android.preference.PreferenceActivity; + +public class MainActivity extends PreferenceActivity { + + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.main_headers, target); + + + String translatedby = getString(R.string.translationby); + if(!translatedby.equals("") && !translatedby.contains("Arne Schwabe")) { + Header translation = new Header(); + translation.title = getString(R.string.translation); + translation.summary = translatedby; + target.add(translation); + } + + if(SendDumpActivity.getLastestDump(this)!=null) { + Header sendDump = new Header(); + sendDump.titleRes = R.string.send_minidump; + sendDump.summaryRes = R.string.send_minidump_summary; + sendDump.intent = new Intent(this,SendDumpActivity.class); + target.add(sendDump); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + System.out.println(data); + + + } + + +} diff --git a/src/se/leap/openvpn/NetworkSateReceiver.java b/src/se/leap/openvpn/NetworkSateReceiver.java new file mode 100644 index 00000000..301803b6 --- /dev/null +++ b/src/se/leap/openvpn/NetworkSateReceiver.java @@ -0,0 +1,86 @@ +package se.leap.openvpn; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.preference.PreferenceManager; +import se.leap.leapclient.R; + +public class NetworkSateReceiver extends BroadcastReceiver { + private int lastNetwork=-1; + private OpenVpnManagementThread mManangement; + + private String lastStateMsg=null; + + public NetworkSateReceiver(OpenVpnManagementThread managementThread) { + super(); + mManangement = managementThread; + } + + @Override + public void onReceive(Context context, Intent intent) { + NetworkInfo networkInfo = getCurrentNetworkInfo(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); + + String netstatestring; + if(networkInfo==null) + netstatestring = "not connected"; + else { + String subtype = networkInfo.getSubtypeName(); + if(subtype==null) + subtype = ""; + String extrainfo = networkInfo.getExtraInfo(); + if(extrainfo==null) + extrainfo=""; + + /* + if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); + extrainfo+=wifiinfo.getBSSID(); + + subtype += wifiinfo.getNetworkId(); + }*/ + + + + netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(), + networkInfo.getDetailedState(),extrainfo,subtype ); + } + + + + if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) { + int newnet = networkInfo.getType(); + + if(sendusr1 && lastNetwork!=newnet) + mManangement.reconnect(); + + lastNetwork = newnet; + } else if (networkInfo==null) { + // Not connected, stop openvpn, set last connected network to no network + lastNetwork=-1; + if(sendusr1) + mManangement.signalusr1(); + } + + if(!netstatestring.equals(lastStateMsg)) + OpenVPN.logInfo(R.string.netstatus, netstatestring); + lastStateMsg=netstatestring; + + } + + private NetworkInfo getCurrentNetworkInfo(Context context) { + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo networkInfo = conn.getActiveNetworkInfo(); + return networkInfo; + } + +} diff --git a/src/se/leap/openvpn/OnBootReceiver.java b/src/se/leap/openvpn/OnBootReceiver.java new file mode 100644 index 00000000..d97097c3 --- /dev/null +++ b/src/se/leap/openvpn/OnBootReceiver.java @@ -0,0 +1,33 @@ +package se.leap.openvpn; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + + +public class OnBootReceiver extends BroadcastReceiver { + + // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED + @Override + public void onReceive(Context context, Intent intent) { + + final String action = intent.getAction(); + + if(Intent.ACTION_BOOT_COMPLETED.equals(action)) { + VpnProfile bootProfile = ProfileManager.getOnBootProfile(context); + if(bootProfile != null) { + lauchVPN(bootProfile, context); + } + } + } + + void lauchVPN(VpnProfile profile,Context context) { + Intent startVpnIntent = new Intent(Intent.ACTION_MAIN); + startVpnIntent.setClass(context, LaunchVPN.class); + startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString()); + startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + + context.startActivity(startVpnIntent); + } +} diff --git a/src/se/leap/openvpn/OpenVPN.java b/src/se/leap/openvpn/OpenVPN.java new file mode 100644 index 00000000..152cf2d8 --- /dev/null +++ b/src/se/leap/openvpn/OpenVPN.java @@ -0,0 +1,244 @@ +package se.leap.openvpn; + +import java.util.LinkedList; +import java.util.Locale; +import java.util.Vector; +import se.leap.leapclient.R; + + +import android.content.Context; +import android.os.Build; + +public class OpenVPN { + + + public static LinkedList logbuffer; + + private static Vector logListener; + private static Vector stateListener; + private static String[] mBconfig; + + private static String mLaststatemsg; + + private static String mLaststate; + + private static int mLastStateresid=R.string.state_noprocess; + + static { + logbuffer = new LinkedList(); + logListener = new Vector(); + stateListener = new Vector(); + logInformation(); + } + + public static class LogItem { + public static final int ERROR = 1; + public static final int INFO = 2; + public static final int VERBOSE = 3; + + private Object [] mArgs = null; + private String mMessage = null; + private int mRessourceId; + // Default log priority + int mLevel = INFO; + + public LogItem(int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + } + + + public LogItem(int loglevel,int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + mLevel = loglevel; + } + + + public LogItem(String message) { + mMessage = message; + } + + public LogItem(int loglevel, String msg) { + mLevel = loglevel; + mMessage = msg; + } + + + public LogItem(int loglevel, int ressourceId) { + mRessourceId =ressourceId; + mLevel = loglevel; + } + + + public String getString(Context c) { + if(mMessage !=null) { + return mMessage; + } else { + if(c!=null) { + if(mArgs == null) + return c.getString(mRessourceId); + else + return c.getString(mRessourceId,mArgs); + } else { + String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); + if(mArgs !=null) + for(Object o:mArgs) + str += "|" + o.toString(); + return str; + } + } + } + } + + private static final int MAXLOGENTRIES = 500; + + + public static final String MANAGMENT_PREFIX = "M:"; + + + + + + + public interface LogListener { + void newLog(LogItem logItem); + } + + public interface StateListener { + void updateState(String state, String logmessage, int localizedResId); + } + + synchronized static void logMessage(int level,String prefix, String message) + { + newlogItem(new LogItem(prefix + message)); + + } + + synchronized static void clearLog() { + logbuffer.clear(); + logInformation(); + } + + private static void logInformation() { + + logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); + } + + public synchronized static void addLogListener(LogListener ll){ + logListener.add(ll); + } + + public synchronized static void removeLogListener(LogListener ll) { + logListener.remove(ll); + } + + + public synchronized static void addStateListener(StateListener sl){ + stateListener.add(sl); + if(mLaststate!=null) + sl.updateState(mLaststate, mLaststatemsg, mLastStateresid); + } + + private static int getLocalizedState(String state){ + if (state.equals("CONNECTING")) + return R.string.state_connecting; + else if (state.equals("WAIT")) + return R.string.state_wait; + else if (state.equals("AUTH")) + return R.string.state_auth; + else if (state.equals("GET_CONFIG")) + return R.string.state_get_config; + else if (state.equals("ASSIGN_IP")) + return R.string.state_assign_ip; + else if (state.equals("ADD_ROUTES")) + return R.string.state_add_routes; + else if (state.equals("CONNECTED")) + return R.string.state_connected; + else if (state.equals("RECONNECTING")) + return R.string.state_reconnecting; + else if (state.equals("EXITING")) + return R.string.state_exiting; + else if (state.equals("RESOLVE")) + return R.string.state_resolve; + else if (state.equals("TCP_CONNECT")) + return R.string.state_tcp_connect; + else + return R.string.unknown_state; + + } + + public synchronized static void removeStateListener(StateListener sl) { + stateListener.remove(sl); + } + + + synchronized public static LogItem[] getlogbuffer() { + + // The stoned way of java to return an array from a vector + // brought to you by eclipse auto complete + return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]); + + } + public static void logBuilderConfig(String[] bconfig) { + mBconfig = bconfig; + } + public static void triggerLogBuilderConfig() { + if(mBconfig==null) { + logMessage(0, "", "No active interface"); + } else { + for (String item : mBconfig) { + logMessage(0, "", item); + } + } + + } + + public static void updateStateString (String state, String msg) { + int rid = getLocalizedState(state); + updateStateString(state, msg,rid); + } + + public synchronized static void updateStateString(String state, String msg, int resid) { + if (! "BYTECOUNT".equals(state)) { + mLaststate= state; + mLaststatemsg = msg; + mLastStateresid = resid; + } + + for (StateListener sl : stateListener) { + sl.updateState(state,msg,resid); + } + } + + public static void logInfo(String message) { + newlogItem(new LogItem(LogItem.INFO, message)); + } + + public static void logInfo(int ressourceId, Object... args) { + newlogItem(new LogItem(LogItem.INFO, ressourceId, args)); + } + + private static void newlogItem(LogItem logItem) { + logbuffer.addLast(logItem); + if(logbuffer.size()>MAXLOGENTRIES) + logbuffer.removeFirst(); + + for (LogListener ll : logListener) { + ll.newLog(logItem); + } + } + + public static void logError(String msg) { + newlogItem(new LogItem(LogItem.ERROR, msg)); + + } + + public static void logError(int ressourceId) { + newlogItem(new LogItem(LogItem.ERROR, ressourceId)); + } + public static void logError(int ressourceId, Object... args) { + newlogItem(new LogItem(LogItem.ERROR, ressourceId,args)); + } + +} diff --git a/src/se/leap/openvpn/OpenVPNThread.java b/src/se/leap/openvpn/OpenVPNThread.java new file mode 100644 index 00000000..3f9a0d91 --- /dev/null +++ b/src/se/leap/openvpn/OpenVPNThread.java @@ -0,0 +1,129 @@ +package se.leap.openvpn; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedList; +import se.leap.leapclient.R; + +import android.util.Log; +import se.leap.openvpn.OpenVPN.LogItem; + +public class OpenVPNThread implements Runnable { + private static final String DUMP_PATH_STRING = "Dump path: "; + private static final String TAG = "OpenVPN"; + private String[] mArgv; + private Process mProcess; + private String mNativeDir; + private OpenVpnService mService; + private String mDumpPath; + + public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir) + { + mArgv = argv; + mNativeDir = nativelibdir; + mService = service; + } + + public void stopProcess() { + mProcess.destroy(); + } + + + + @Override + public void run() { + try { + Log.i(TAG, "Starting openvpn"); + startOpenVPNThreadArgs(mArgv); + Log.i(TAG, "Giving up"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "OpenVPNThread Got " + e.toString()); + } finally { + int exitvalue = 0; + try { + exitvalue = mProcess.waitFor(); + } catch ( IllegalThreadStateException ite) { + OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage()); + } catch (InterruptedException ie) { + OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage()); + } + if( exitvalue != 0) + OpenVPN.logError("Process exited with exit value " + exitvalue); + + OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess); + if(mDumpPath!=null) { + try { + BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); + for(LogItem li :OpenVPN.getlogbuffer()){ + logout.write(li.getString(null) + "\n"); + } + logout.close(); + OpenVPN.logError(R.string.minidump_generated); + } catch (IOException e) { + OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage()); + } + } + + mService.processDied(); + Log.i(TAG, "Exiting"); + } + } + + private void startOpenVPNThreadArgs(String[] argv) { + LinkedList argvlist = new LinkedList(); + + for(String arg:argv) + argvlist.add(arg); + + ProcessBuilder pb = new ProcessBuilder(argvlist); + // Hack O rama + + // Hack until I find a good way to get the real library path + String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); + + String lbpath = pb.environment().get("LD_LIBRARY_PATH"); + if(lbpath==null) + lbpath = applibpath; + else + lbpath = lbpath + ":" + applibpath; + + if (!applibpath.equals(mNativeDir)) { + lbpath = lbpath + ":" + mNativeDir; + } + + pb.environment().put("LD_LIBRARY_PATH", lbpath); + pb.redirectErrorStream(true); + try { + mProcess = pb.start(); + // Close the output, since we don't need it + mProcess.getOutputStream().close(); + InputStream in = mProcess.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + while(true) { + String logline = br.readLine(); + if(logline==null) + return; + + if (logline.startsWith(DUMP_PATH_STRING)) + mDumpPath = logline.substring(DUMP_PATH_STRING.length()); + + + OpenVPN.logMessage(0, "P:", logline); + } + + + } catch (IOException e) { + OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage()); + e.printStackTrace(); + stopProcess(); + } + + + } +} diff --git a/src/se/leap/openvpn/OpenVpnManagementThread.java b/src/se/leap/openvpn/OpenVpnManagementThread.java new file mode 100644 index 00000000..43d4f701 --- /dev/null +++ b/src/se/leap/openvpn/OpenVpnManagementThread.java @@ -0,0 +1,577 @@ +package se.leap.openvpn; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.util.LinkedList; +import java.util.Vector; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import se.leap.leapclient.R; + +import android.content.SharedPreferences; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; + +public class OpenVpnManagementThread implements Runnable { + + private static final String TAG = "openvpn"; + private LocalSocket mSocket; + private VpnProfile mProfile; + private OpenVpnService mOpenVPNService; + private LinkedList mFDList=new LinkedList(); + private int mBytecountinterval=2; + private long mLastIn=0; + private long mLastOut=0; + private LocalServerSocket mServerSocket; + private boolean mReleaseHold=true; + private boolean mWaitingForRelease=false; + private long mLastHoldRelease=0; + + private static Vector active=new Vector(); + + static private native void jniclose(int fdint); + static private native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; + + public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) { + mProfile = profile; + mServerSocket = mgmtsocket; + mOpenVPNService = openVpnService; + + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); + boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); + if(managemeNetworkState) + mReleaseHold=false; + + } + + static { + System.loadLibrary("opvpnutil"); + } + + public void managmentCommand(String cmd) { + if(mSocket!=null) { + try { + mSocket.getOutputStream().write(cmd.getBytes()); + mSocket.getOutputStream().flush(); + } catch (IOException e) { + // Ignore socket stack traces + } + } + } + + + @Override + public void run() { + Log.i(TAG, "Managment Socket Thread started"); + byte [] buffer =new byte[2048]; + // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad + + String pendingInput=""; + active.add(this); + + try { + // Wait for a client to connect + mSocket= mServerSocket.accept(); + InputStream instream = mSocket.getInputStream(); + + while(true) { + int numbytesread = instream.read(buffer); + if(numbytesread==-1) + return; + + FileDescriptor[] fds = null; + try { + fds = mSocket.getAncillaryFileDescriptors(); + } catch (IOException e) { + OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage()); + e.printStackTrace(); + } + if(fds!=null){ + + for (FileDescriptor fd : fds) { + + mFDList.add(fd); + } + } + + String input = new String(buffer,0,numbytesread,"UTF-8"); + + pendingInput += input; + + pendingInput=processInput(pendingInput); + + + + } + } catch (IOException e) { + e.printStackTrace(); + } + active.remove(this); + } + + //! Hack O Rama 2000! + private void protectFileDescriptor(FileDescriptor fd) { + Exception exp=null; + try { + Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); + int fdint = (Integer) getInt.invoke(fd); + + // You can even get more evil by parsing toString() and extract the int from that :) + + mOpenVPNService.protect(fdint); + + //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); + //pfd.close(); + jniclose(fdint); + return; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (NullPointerException e) { + exp =e; + } + if(exp!=null) { + exp.printStackTrace(); + Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); + OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage()); + } + } + + private String processInput(String pendingInput) { + + + while(pendingInput.contains("\n")) { + String[] tokens = pendingInput.split("\\r?\\n", 2); + processCommand(tokens[0]); + if(tokens.length == 1) + // No second part, newline was at the end + pendingInput=""; + else + pendingInput=tokens[1]; + } + return pendingInput; + } + + + private void processCommand(String command) { + if (command.startsWith(">") && command.contains(":")) { + String[] parts = command.split(":",2); + String cmd = parts[0].substring(1); + String argument = parts[1]; + + + if(cmd.equals("INFO")) { + // Ignore greeting from mgmt + //logStatusMessage(command); + }else if (cmd.equals("PASSWORD")) { + processPWCommand(argument); + } else if (cmd.equals("HOLD")) { + handleHold(); + } else if (cmd.equals("NEED-OK")) { + processNeedCommand(argument); + } else if (cmd.equals("BYTECOUNT")){ + processByteCount(argument); + } else if (cmd.equals("STATE")) { + processState(argument); + } else if (cmd.equals("PROXY")) { + processProxyCMD(argument); + } else if (cmd.equals("LOG")) { + String[] args = argument.split(",",3); + // 0 unix time stamp + // 1 log level N,I,E etc. + // 2 log message + OpenVPN.logMessage(0, "", args[2]); + } else if (cmd.equals("RSA_SIGN")) { + processSignCommand(argument); + } else { + OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command); + Log.i(TAG, "Got unrecognized command" + command); + } + } else if (command.startsWith("SUCCESS:")) { + // ignore + } else { + Log.i(TAG, "Got unrecognized line from managment" + command); + OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command); + } + } + private void handleHold() { + if(mReleaseHold) { + releaseHoldCmd(); + } else { + mWaitingForRelease=true; + OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork); + } + } + private void releaseHoldCmd() { + if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) {} + + } + mWaitingForRelease=false; + mLastHoldRelease = System.currentTimeMillis(); + managmentCommand("hold release\n"); + managmentCommand("bytecount " + mBytecountinterval + "\n"); + managmentCommand("state on\n"); + } + + public void releaseHold() { + mReleaseHold=true; + if(mWaitingForRelease) + releaseHoldCmd(); + + } + + private void processProxyCMD(String argument) { + String[] args = argument.split(",",3); + SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + + + if(args.length >= 2) { + String proto = args[1]; + if(proto.equals("UDP")) { + proxyaddr=null; + } + } + + if(proxyaddr instanceof InetSocketAddress ){ + InetSocketAddress isa = (InetSocketAddress) proxyaddr; + + OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort()); + + String proxycmd = String.format("proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); + managmentCommand(proxycmd); + } else { + managmentCommand("proxy NONE\n"); + } + + } + private void processState(String argument) { + String[] args = argument.split(",",3); + String currentstate = args[1]; + if(args[2].equals(",,")) + OpenVPN.updateStateString(currentstate,""); + else + OpenVPN.updateStateString(currentstate,args[2]); + } + + + private void processByteCount(String argument) { + // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} + int comma = argument.indexOf(','); + long in = Long.parseLong(argument.substring(0, comma)); + long out = Long.parseLong(argument.substring(comma+1)); + + long diffin = in - mLastIn; + long diffout = out - mLastOut; + + mLastIn=in; + mLastOut=out; + + String netstat = String.format("In: %8s, %8s/s Out %8s, %8s/s", + humanReadableByteCount(in, false), + humanReadableByteCount(diffin, false), + humanReadableByteCount(out, false), + humanReadableByteCount(diffout, false)); + OpenVPN.updateStateString("BYTECOUNT",netstat); + + + } + + // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + private void processNeedCommand(String argument) { + int p1 =argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + + String needed = argument.substring(p1+1, p2); + String extra = argument.split(":",2)[1]; + + String status = "ok"; + + + if (needed.equals("PROTECTFD")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + protectFileDescriptor(fdtoprotect); + } else if (needed.equals("DNSSERVER")) { + mOpenVPNService.addDNS(extra); + }else if (needed.equals("DNSDOMAIN")){ + mOpenVPNService.setDomain(extra); + } else if (needed.equals("ROUTE")) { + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoute(routeparts[0], routeparts[1]); + } else if (needed.equals("ROUTE6")) { + mOpenVPNService.addRoutev6(extra); + } else if (needed.equals("IFCONFIG")) { + String[] ifconfigparts = extra.split(" "); + int mtu = Integer.parseInt(ifconfigparts[2]); + mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); + } else if (needed.equals("IFCONFIG6")) { + mOpenVPNService.setLocalIPv6(extra); + + } else if (needed.equals("OPENTUN")) { + if(sendTunFD(needed,extra)) + return; + else + status="cancel"; + // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( + + } else { + Log.e(TAG,"Unkown needok command " + argument); + return; + } + + String cmd = String.format("needok '%s' %s\n", needed, status); + managmentCommand(cmd); + } + + private boolean sendTunFD (String needed, String extra) { + Exception exp = null; + if(!extra.equals("tun")) { + // We only support tun + String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra); + OpenVPN.logMessage(0, "", errmsg ); + + return false; + } + ParcelFileDescriptor pfd = mOpenVPNService.openTun(); + if(pfd==null) + return false; + + Method setInt; + int fdint = pfd.getFd(); + try { + setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); + FileDescriptor fdtosend = new FileDescriptor(); + + setInt.invoke(fdtosend,fdint); + + FileDescriptor[] fds = {fdtosend}; + mSocket.setFileDescriptorsForSend(fds); + + Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd); + // Trigger a send so we can close the fd on our side of the channel + // The API documentation fails to mention that it will not reset the file descriptor to + // be send and will happily send the file descriptor on every write ... + String cmd = String.format("needok '%s' %s\n", needed, "ok"); + managmentCommand(cmd); + + // Set the FileDescriptor to null to stop this mad behavior + mSocket.setFileDescriptorsForSend(null); + + pfd.close(); + + return true; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (IOException e) { + exp =e; + } + if(exp!=null) { + OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage()); + exp.printStackTrace(); + } + return false; + } + + private void processPWCommand(String argument) { + //argument has the form Need 'Private Key' password + // or ">PASSWORD:Verification Failed: '%s' ['%s']" + String needed; + + + + try{ + + int p1 = argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + needed = argument.substring(p1+1, p2); + if (argument.startsWith("Verification Failed")) { + proccessPWFailed(needed, argument.substring(p2+1)); + return; + } + } catch (StringIndexOutOfBoundsException sioob) { + OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument); + return; + } + + String pw=null; + + if(needed.equals("Private Key")) { + pw = mProfile.getPasswordPrivateKey(); + } else if (needed.equals("Auth")) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(mProfile.mUsername)); + managmentCommand(usercmd); + pw = mProfile.getPasswordAuth(); + } + if(pw!=null) { + String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); + managmentCommand(cmd); + } else { + OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); + } + + } + + + + + private void proccessPWFailed(String needed, String args) { + OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed); + } + private void logStatusMessage(String command) { + OpenVPN.logMessage(0,"MGMT:", command); + } + + + public static boolean stopOpenVPN() { + boolean sendCMD=false; + for (OpenVpnManagementThread mt: active){ + mt.managmentCommand("signal SIGINT\n"); + sendCMD=true; + try { + if(mt.mSocket !=null) + mt.mSocket.close(); + } catch (IOException e) { + // Ignore close error on already closed socket + } + } + return sendCMD; + } + + public void signalusr1() { + mReleaseHold=false; + if(!mWaitingForRelease) + managmentCommand("signal SIGUSR1\n"); + } + + public void reconnect() { + signalusr1(); + releaseHold(); + } + + private void processSignCommand(String b64data) { + + PrivateKey privkey = mProfile.getKeystoreKey(); + Exception err =null; + + byte[] data = Base64.decode(b64data, Base64.DEFAULT); + + // The Jelly Bean *evil* Hack + // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider + if(Build.VERSION.SDK_INT==16){ + processSignJellyBeans(privkey,data); + return; + } + + + try{ + + + Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); + + rsasinger.init(Cipher.ENCRYPT_MODE, privkey); + + byte[] signed_bytes = rsasinger.doFinal(data); + String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + } catch (NoSuchAlgorithmException e){ + err =e; + } catch (InvalidKeyException e) { + err =e; + } catch (NoSuchPaddingException e) { + err =e; + } catch (IllegalBlockSizeException e) { + err =e; + } catch (BadPaddingException e) { + err =e; + } + if(err !=null) { + OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); + } + + } + + + private void processSignJellyBeans(PrivateKey privkey, byte[] data) { + Exception err =null; + try { + Method[] allm = privkey.getClass().getSuperclass().getDeclaredMethods(); + System.out.println(allm); + Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); + getKey.setAccessible(true); + + // Real object type is OpenSSLKey + Object opensslkey = getKey.invoke(privkey); + + getKey.setAccessible(false); + + Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); + + // integer pointer to EVP_pkey + getPkeyContext.setAccessible(true); + int pkey = (Integer) getPkeyContext.invoke(opensslkey); + getPkeyContext.setAccessible(false); + + byte[] signed_bytes = rsasign(data, pkey); + String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + + } catch (NoSuchMethodException e) { + err=e; + } catch (IllegalArgumentException e) { + err=e; + } catch (IllegalAccessException e) { + err=e; + } catch (InvocationTargetException e) { + err=e; + } catch (InvalidKeyException e) { + err=e; + } + if(err !=null) { + OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); + } + + } +} diff --git a/src/se/leap/openvpn/OpenVpnPreferencesFragment.java b/src/se/leap/openvpn/OpenVpnPreferencesFragment.java new file mode 100644 index 00000000..31746332 --- /dev/null +++ b/src/se/leap/openvpn/OpenVpnPreferencesFragment.java @@ -0,0 +1,52 @@ +package se.leap.openvpn; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import se.leap.leapclient.R; + +public abstract class OpenVpnPreferencesFragment extends PreferenceFragment { + + protected VpnProfile mProfile; + + protected abstract void loadSettings(); + protected abstract void saveSettings(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String profileUUID = getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile = ProfileManager.get(getActivity(),profileUUID); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + + } + + @Override + public void onPause() { + super.onPause(); + saveSettings(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if(savedInstanceState!=null) { + String profileUUID=savedInstanceState.getString(VpnProfile.EXTRA_PROFILEUUID); + mProfile = ProfileManager.get(getActivity(),profileUUID); + loadSettings(); + } + } + + @Override + public void onStop() { + // TODO Auto-generated method stub + super.onStop(); + } + + @Override + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + saveSettings(); + outState.putString(VpnProfile.EXTRA_PROFILEUUID, mProfile.getUUIDString()); + } +} diff --git a/src/se/leap/openvpn/OpenVpnService.java b/src/se/leap/openvpn/OpenVpnService.java new file mode 100644 index 00000000..c745ee3b --- /dev/null +++ b/src/se/leap/openvpn/OpenVpnService.java @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package se.leap.openvpn; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import se.leap.leapclient.R; + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.net.VpnService; +import android.os.Binder; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Build; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import se.leap.openvpn.OpenVPN.StateListener; + +public class OpenVpnService extends VpnService implements StateListener, Callback { + public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE"; + + private Thread mProcessThread=null; + + private Vector mDnslist=new Vector(); + + private VpnProfile mProfile; + + private String mDomain=null; + + private Vector mRoutes=new Vector(); + private Vector mRoutesv6=new Vector(); + + private CIDRIP mLocalIP=null; + + private OpenVpnManagementThread mSocketManager; + + private Thread mSocketManagerThread; + private int mMtu; + private String mLocalIPv6=null; + private NetworkSateReceiver mNetworkStateReceiver; + + private boolean mDisplayBytecount=false; + + private boolean mStarting=false; + + private long mConnecttime; + + + private static final int OPENVPN_STATUS = 1; + + public static final int PROTECT_FD = 0; + + private final IBinder mBinder = new LocalBinder(); + + public class LocalBinder extends Binder { + public OpenVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return OpenVpnService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + String action = intent.getAction(); + if( action !=null && action.equals(START_SERVICE)) + return mBinder; + else + return super.onBind(intent); + } + + @Override + public void onRevoke() { + OpenVpnManagementThread.stopOpenVPN(); + endVpnService(); + } + + // Similar to revoke but do not try to stop process + public void processDied() { + endVpnService(); + } + + private void endVpnService() { + mProcessThread=null; + OpenVPN.logBuilderConfig(null); + ProfileManager.setConntectedVpnProfileDisconnected(this); + if(!mStarting) { + stopSelf(); + stopForeground(true); + } + } + + private void showNotification(String msg, String tickerText, boolean lowpriority, long when) { + String ns = Context.NOTIFICATION_SERVICE; + NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); + + + int icon = R.drawable.ic_stat_vpn; + android.app.Notification.Builder nbuilder = new Notification.Builder(this); + + nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mName)); + nbuilder.setContentText(msg); + nbuilder.setOnlyAlertOnce(true); + nbuilder.setOngoing(true); + nbuilder.setContentIntent(getLogPendingIntent()); + nbuilder.setSmallIcon(icon); + if(when !=0) + nbuilder.setWhen(when); + + + // Try to set the priority available since API 16 (Jellybean) + jbNotificationExtras(lowpriority, nbuilder); + if(tickerText!=null) + nbuilder.setTicker(tickerText); + + @SuppressWarnings("deprecation") + Notification notification = nbuilder.getNotification(); + + + mNotificationManager.notify(OPENVPN_STATUS, notification); + startForeground(OPENVPN_STATUS, notification); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void jbNotificationExtras(boolean lowpriority, + android.app.Notification.Builder nbuilder) { + try { + if(lowpriority) { + Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); + // PRIORITY_MIN == -2 + setpriority.invoke(nbuilder, -2 ); + + nbuilder.setUsesChronometer(true); + /* PendingIntent cancelconnet=null; + + nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, + getString(R.string.cancel_connection),cancelconnet); */ + } + + //ignore exception + } catch (NoSuchMethodException nsm) { + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + + } + + PendingIntent getLogPendingIntent() { + // Let the configure Button show the Log + Intent intent = new Intent(getBaseContext(),LogWindow.class); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + return startLW; + + } + + + private LocalServerSocket openManagmentInterface(int tries) { + // Could take a while to open connection + String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); + LocalSocket sock = new LocalSocket(); + + while(tries > 0 && !sock.isConnected()) { + try { + sock.bind(new LocalSocketAddress(socketname, + LocalSocketAddress.Namespace.FILESYSTEM)); + } catch (IOException e) { + // wait 300 ms before retrying + try { Thread.sleep(300); + } catch (InterruptedException e1) {} + + } + tries--; + } + + try { + LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor()); + return lss; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + + + } + + void registerNetworkStateReceiver() { + // Registers BroadcastReceiver to track network connection changes. + IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + mNetworkStateReceiver = new NetworkSateReceiver(mSocketManager); + this.registerReceiver(mNetworkStateReceiver, filter); + } + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + if(intent != null && intent.getAction() !=null &&intent.getAction().equals(START_SERVICE)) + return START_NOT_STICKY; + + + // Extract information from the intent. + String prefix = getPackageName(); + String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); + String nativelibdir = intent.getStringExtra(prefix + ".nativelib"); + String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); + + mProfile = ProfileManager.get(profileUUID); + + showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0); + + + OpenVPN.addStateListener(this); + + // Set a flag that we are starting a new VPN + mStarting=true; + // Stop the previous session by interrupting the thread. + if(OpenVpnManagementThread.stopOpenVPN()){ + // an old was asked to exit, wait 2s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + + if (mProcessThread!=null) { + mProcessThread.interrupt(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + // An old running VPN should now be exited + mStarting=false; + + + // Open the Management Interface + LocalServerSocket mgmtsocket = openManagmentInterface(8); + + if(mgmtsocket!=null) { + // start a Thread that handles incoming messages of the managment socket + mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket,this); + mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread"); + mSocketManagerThread.start(); + OpenVPN.logInfo("started Socket Thread"); + registerNetworkStateReceiver(); + } + + + // Start a new session by creating a new thread. + OpenVPNThread processThread = new OpenVPNThread(this, argv,nativelibdir); + + mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); + mProcessThread.start(); + + ProfileManager.setConnectedVpnProfile(this, mProfile); + + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + if (mProcessThread != null) { + mSocketManager.managmentCommand("signal SIGINT\n"); + + mProcessThread.interrupt(); + } + if (mNetworkStateReceiver!= null) { + this.unregisterReceiver(mNetworkStateReceiver); + } + + } + + + + public ParcelFileDescriptor openTun() { + Builder builder = new Builder(); + + if(mLocalIP==null && mLocalIPv6==null) { + OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr)); + return null; + } + + if(mLocalIP!=null) { + builder.addAddress(mLocalIP.mIp, mLocalIP.len); + } + + if(mLocalIPv6!=null) { + String[] ipv6parts = mLocalIPv6.split("/"); + builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1])); + } + + + for (String dns : mDnslist ) { + try { + builder.addDnsServer(dns); + } catch (IllegalArgumentException iae) { + OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage()); + } + } + + + builder.setMtu(mMtu); + + + for (CIDRIP route:mRoutes) { + try { + builder.addRoute(route.mIp, route.len); + } catch (IllegalArgumentException ia) { + OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); + } + } + + for(String v6route:mRoutesv6) { + try { + String[] v6parts = v6route.split("/"); + builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1])); + } catch (IllegalArgumentException ia) { + OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage()); + } + } + + if(mDomain!=null) + builder.addSearchDomain(mDomain); + + String bconfig[] = new String[6]; + + bconfig[0]= getString(R.string.last_openvpn_tun_config); + bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu); + bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist)); + bconfig[3] = getString(R.string.dns_domain_info, mDomain); + bconfig[4] = getString(R.string.routes_info, joinString(mRoutes)); + bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6)); + + String session = mProfile.mName; + if(mLocalIP!=null && mLocalIPv6!=null) + session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6); + else if (mLocalIP !=null) + session= getString(R.string.session_ipv4string, session, mLocalIP); + + builder.setSession(session); + + + OpenVPN.logBuilderConfig(bconfig); + + // No DNS Server, log a warning + if(mDnslist.size()==0) + OpenVPN.logInfo(R.string.warn_no_dns); + + // Reset information + mDnslist.clear(); + mRoutes.clear(); + mRoutesv6.clear(); + mLocalIP=null; + mLocalIPv6=null; + mDomain=null; + + builder.setConfigureIntent(getLogPendingIntent()); + + try { + ParcelFileDescriptor pfd = builder.establish(); + return pfd; + } catch (Exception e) { + OpenVPN.logMessage(0, "", getString(R.string.tun_open_error)); + OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage()); + OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful)); + return null; + } + + } + + + // Ugly, but java has no such method + private String joinString(Vector vec) { + String ret = ""; + if(vec.size() > 0){ + ret = vec.get(0).toString(); + for(int i=1;i < vec.size();i++) { + ret = ret + ", " + vec.get(i).toString(); + } + } + return ret; + } + + + + + + + public void addDNS(String dns) { + mDnslist.add(dns); + } + + + public void setDomain(String domain) { + if(mDomain==null) { + mDomain=domain; + } + } + + + public void addRoute(String dest, String mask) { + CIDRIP route = new CIDRIP(dest, mask); + if(route.len == 32 && !mask.equals("255.255.255.255")) { + OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask)); + } + + if(route.normalise()) + OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp)); + + mRoutes.add(route); + } + + public void addRoutev6(String extra) { + mRoutesv6.add(extra); + } + + + public void setLocalIP(String local, String netmask,int mtu, String mode) { + mLocalIP = new CIDRIP(local, netmask); + mMtu = mtu; + + if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { + // get the netmask as IP + long netint = CIDRIP.getInt(netmask); + if(Math.abs(netint - mLocalIP.getInt()) ==1) { + if(mode.equals("net30")) + mLocalIP.len=30; + else + mLocalIP.len=31; + } else { + OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode)); + } + } + } + + public void setLocalIPv6(String ipv6addr) { + mLocalIPv6 = ipv6addr; + } + + @Override + public void updateState(String state,String logmessage, int resid) { + // If the process is not running, ignore any state, + // Notification should be invisible in this state + if(mProcessThread==null) + return; + + // Display byte count only after being connected + + if("BYTECOUNT".equals(state)) { + if(mDisplayBytecount) { + showNotification(logmessage,null,true,mConnecttime); + } + } else { + if("CONNECTED".equals(state)) { + mDisplayBytecount = true; + mConnecttime = System.currentTimeMillis(); + } else { + mDisplayBytecount = false; + } + + // Other notifications are shown, + // This also mean we are no longer connected, ignore bytecount messages until next + // CONNECTED + String ticker = getString(resid); + showNotification(getString(resid) +" " + logmessage,ticker,false,0); + + } + } + + @Override + public boolean handleMessage(Message msg) { + Runnable r = msg.getCallback(); + if(r!=null){ + r.run(); + return true; + } else { + return false; + } + } +} diff --git a/src/se/leap/openvpn/ProfileManager.java b/src/se/leap/openvpn/ProfileManager.java new file mode 100644 index 00000000..b9eb3ab6 --- /dev/null +++ b/src/se/leap/openvpn/ProfileManager.java @@ -0,0 +1,220 @@ +package se.leap.openvpn; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; + +public class ProfileManager { + private static final String PREFS_NAME = "VPNList"; + + + + private static final String ONBOOTPROFILE = "onBootProfile"; + + + + private static ProfileManager instance; + + + + private static VpnProfile mLastConnectedVpn=null; + private HashMap profiles=new HashMap(); + private static VpnProfile tmpprofile=null; + + + public static VpnProfile get(String key) { + if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) + return tmpprofile; + + if(instance==null) + return null; + return instance.profiles.get(key); + + } + + + + private ProfileManager() { } + + private static void checkInstance(Context context) { + if(instance == null) { + instance = new ProfileManager(); + instance.loadVPNList(context); + } + } + + synchronized public static ProfileManager getInstance(Context context) { + checkInstance(context); + return instance; + } + + public static void setConntectedVpnProfileDisconnected(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + prefsedit.putString(ONBOOTPROFILE, null); + prefsedit.apply(); + + } + + public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + + prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); + prefsedit.apply(); + mLastConnectedVpn=connectedrofile; + + } + + public static VpnProfile getOnBootProfile(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + + boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); + + + String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null); + if(useStartOnBoot && mBootProfileUUID!=null) + return get(c, mBootProfileUUID); + else + return null; + } + + + + + public Collection getProfiles() { + return profiles.values(); + } + + public VpnProfile getProfileByName(String name) { + for (VpnProfile vpnp : profiles.values()) { + if(vpnp.getName().equals(name)) { + return vpnp; + } + } + return null; + } + + public void saveProfileList(Context context) { + SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Editor editor = sharedprefs.edit(); + editor.putStringSet("vpnlist", profiles.keySet()); + + // For reasing I do not understand at all + // Android saves my prefs file only one time + // if I remove the debug code below :( + int counter = sharedprefs.getInt("counter", 0); + editor.putInt("counter", counter+1); + editor.apply(); + + } + + public void addProfile(VpnProfile profile) { + profiles.put(profile.getUUID().toString(),profile); + + } + + public static void setTemporaryProfile(VpnProfile tmp) { + ProfileManager.tmpprofile = tmp; + } + + + public void saveProfile(Context context,VpnProfile profile) { + // First let basic settings save its state + + ObjectOutputStream vpnfile; + try { + vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); + + vpnfile.writeObject(profile); + vpnfile.flush(); + vpnfile.close(); + } catch (FileNotFoundException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } catch (IOException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + + } + } + + + private void loadVPNList(Context context) { + profiles = new HashMap(); + SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Set vlist = listpref.getStringSet("vpnlist", null); + Exception exp =null; + if(vlist==null){ + vlist = new HashSet(); + } + + for (String vpnentry : vlist) { + try { + ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); + VpnProfile vp = ((VpnProfile) vpnfile.readObject()); + + // Sanity check + if(vp==null || vp.mName==null || vp.getUUID()==null) + continue; + + profiles.put(vp.getUUID().toString(), vp); + + } catch (StreamCorruptedException e) { + exp=e; + } catch (FileNotFoundException e) { + exp=e; + } catch (IOException e) { + exp=e; + } catch (ClassNotFoundException e) { + exp=e; + } + if(exp!=null) { + exp.printStackTrace(); + } + } + } + + public int getNumberOfProfiles() { + return profiles.size(); + } + + + + public void removeProfile(Context context,VpnProfile profile) { + String vpnentry = profile.getUUID().toString(); + profiles.remove(vpnentry); + saveProfileList(context); + context.deleteFile(vpnentry + ".vp"); + if(mLastConnectedVpn==profile) + mLastConnectedVpn=null; + + } + + + + public static VpnProfile get(Context context, String profileUUID) { + checkInstance(context); + return get(profileUUID); + } + + + + public static VpnProfile getLastConnectedVpn() { + return mLastConnectedVpn; + } + +} diff --git a/src/se/leap/openvpn/ProxyDetection.java b/src/se/leap/openvpn/ProxyDetection.java new file mode 100644 index 00000000..7114857d --- /dev/null +++ b/src/se/leap/openvpn/ProxyDetection.java @@ -0,0 +1,53 @@ +package se.leap.openvpn; + +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import se.leap.leapclient.R; + +public class ProxyDetection { + static SocketAddress detectProxy(VpnProfile vp) { + // Construct a new url with https as protocol + try { + URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); + Proxy proxy = getFirstProxy(url); + + if(proxy==null) + return null; + SocketAddress addr = proxy.address(); + if (addr instanceof InetSocketAddress) { + return addr; + } + + } catch (MalformedURLException e) { + OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); + } catch (URISyntaxException e) { + OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); + } + return null; + } + + static Proxy getFirstProxy(URL url) throws URISyntaxException { + System.setProperty("java.net.useSystemProxies", "true"); + + List proxylist = ProxySelector.getDefault().select(url.toURI()); + + + if (proxylist != null) { + for (Proxy proxy: proxylist) { + SocketAddress addr = proxy.address(); + + if (addr != null) { + return proxy; + } + } + + } + return null; + } +} \ No newline at end of file diff --git a/src/se/leap/openvpn/SendDumpActivity.java b/src/se/leap/openvpn/SendDumpActivity.java new file mode 100644 index 00000000..20194db0 --- /dev/null +++ b/src/se/leap/openvpn/SendDumpActivity.java @@ -0,0 +1,59 @@ +package se.leap.openvpn; + +import java.io.File; +import java.util.ArrayList; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +public class SendDumpActivity extends Activity { + + protected void onStart() { + super.onStart(); + emailMiniDumps(); + finish(); + }; + + public void emailMiniDumps() + { + //need to "send multiple" to get more than one attachment + final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); + emailIntent.setType("*/*"); + emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, + new String[]{"Arne Schwabe "}); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "OpenVPN Minidump"); + + emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe the issue you have experienced"); + + ArrayList uris = new ArrayList(); + + File ldump = getLastestDump(this); + if(ldump==null) { + OpenVPN.logError("No Minidump found!"); + } + + uris.add(Uri.parse("content://se.leap.openvpn.FileProvider/" + ldump.getName())); + uris.add(Uri.parse("content://se.leap.openvpn.FileProvider/" + ldump.getName() + ".log")); + + emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + startActivity(emailIntent); + } + + static public File getLastestDump(Context c) { + long newestDumpTime=0; + File newestDumpFile=null; + + for(File f:c.getCacheDir().listFiles()) { + if(!f.getName().endsWith(".dmp")) + continue; + + if (newestDumpTime < f.lastModified()) { + newestDumpTime = f.lastModified(); + newestDumpFile=f; + } + } + return newestDumpFile; + } +} diff --git a/src/se/leap/openvpn/Settings_Authentication.java b/src/se/leap/openvpn/Settings_Authentication.java new file mode 100644 index 00000000..00f4c08a --- /dev/null +++ b/src/se/leap/openvpn/Settings_Authentication.java @@ -0,0 +1,136 @@ +package se.leap.openvpn; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.SwitchPreference; +import se.leap.leapclient.R; + + +public class Settings_Authentication extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener, OnPreferenceClickListener { + private static final int SELECT_TLS_FILE = 23223232; + private CheckBoxPreference mExpectTLSCert; + private CheckBoxPreference mCheckRemoteCN; + private EditTextPreference mRemoteCN; + private ListPreference mTLSAuthDirection; + private Preference mTLSAuthFile; + private SwitchPreference mUseTLSAuth; + private EditTextPreference mCipher; + private String mTlsAuthFileData; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_authentification); + + mExpectTLSCert = (CheckBoxPreference) findPreference("remoteServerTLS"); + mCheckRemoteCN = (CheckBoxPreference) findPreference("checkRemoteCN"); + mRemoteCN = (EditTextPreference) findPreference("remotecn"); + mRemoteCN.setOnPreferenceChangeListener(this); + + mUseTLSAuth = (SwitchPreference) findPreference("useTLSAuth" ); + mTLSAuthFile = findPreference("tlsAuthFile"); + mTLSAuthDirection = (ListPreference) findPreference("tls_direction"); + + + mTLSAuthFile.setOnPreferenceClickListener(this); + + mCipher =(EditTextPreference) findPreference("cipher"); + mCipher.setOnPreferenceChangeListener(this); + + loadSettings(); + + } + + @Override + protected void loadSettings() { + + mExpectTLSCert.setChecked(mProfile.mExpectTLSCert); + mCheckRemoteCN.setChecked(mProfile.mCheckRemoteCN); + mRemoteCN.setText(mProfile.mRemoteCN); + onPreferenceChange(mRemoteCN, mProfile.mRemoteCN); + + mUseTLSAuth.setChecked(mProfile.mUseTLSAuth); + mTlsAuthFileData= mProfile.mTLSAuthFilename; + setTlsAuthSummary(mTlsAuthFileData); + mTLSAuthDirection.setValue(mProfile.mTLSAuthDirection); + mCipher.setText(mProfile.mCipher); + onPreferenceChange(mCipher, mProfile.mCipher); + } + + @Override + protected void saveSettings() { + mProfile.mExpectTLSCert=mExpectTLSCert.isChecked(); + mProfile.mCheckRemoteCN=mCheckRemoteCN.isChecked(); + mProfile.mRemoteCN=mRemoteCN.getText(); + + mProfile.mUseTLSAuth = mUseTLSAuth.isChecked(); + mProfile.mTLSAuthFilename = mTlsAuthFileData; + + if(mTLSAuthDirection.getValue()==null) + mProfile.mTLSAuthDirection=null; + else + mProfile.mTLSAuthDirection = mTLSAuthDirection.getValue().toString(); + + if(mCipher.getText()==null) + mProfile.mCipher=null; + else + mProfile.mCipher = mCipher.getText(); + + } + + + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if(preference==mRemoteCN) { + if ("".equals(newValue)) + preference.setSummary(mProfile.mServerName); + else + preference.setSummary((String)newValue); + } else if (preference == mCipher) { + preference.setSummary((CharSequence) newValue); + } + return true; + } + void startFileDialog() { + Intent startFC = new Intent(getActivity(),FileSelect.class); + startFC.putExtra(FileSelect.START_DATA, Environment.getExternalStorageDirectory().getPath()); + + startActivityForResult(startFC,SELECT_TLS_FILE); + } + @Override + public boolean onPreferenceClick(Preference preference) { + startFileDialog(); + return true; + + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(requestCode==SELECT_TLS_FILE && resultCode == Activity.RESULT_OK){ + String result = data.getStringExtra(FileSelect.RESULT_DATA); + mTlsAuthFileData=result; + setTlsAuthSummary(result); + + } + } + + private void setTlsAuthSummary(String result) { + if(result==null) result = getString(R.string.no_certificate); + if(result.startsWith(VpnProfile.INLINE_TAG)) + mTLSAuthFile.setSummary(R.string.inline_file_data); + else + mTLSAuthFile.setSummary(result); + } +} \ No newline at end of file diff --git a/src/se/leap/openvpn/Settings_Basic.java b/src/se/leap/openvpn/Settings_Basic.java new file mode 100644 index 00000000..f3510da9 --- /dev/null +++ b/src/se/leap/openvpn/Settings_Basic.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package se.leap.openvpn; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.ToggleButton; +import se.leap.leapclient.R.id; +import se.leap.leapclient.R; + +public class Settings_Basic extends Fragment implements View.OnClickListener, OnItemSelectedListener, Callback { + private static final int CHOOSE_FILE_OFFSET = 1000; + private static final int UPDATE_ALIAS = 20; + + + + private TextView mServerAddress; + private TextView mServerPort; + private FileSelectLayout mClientCert; + private FileSelectLayout mCaCert; + private FileSelectLayout mClientKey; + private TextView mAliasName; + private CheckBox mUseLzo; + private ToggleButton mTcpUdp; + private Spinner mType; + private FileSelectLayout mpkcs12; + private TextView mPKCS12Password; + + private Handler mHandler; + + + + + + private SparseArray fileselects = new SparseArray(); + + + private EditText mUserName; + + + private EditText mPassword; + + + private View mView; + + + private VpnProfile mProfile; + private EditText mProfileName; + private EditText mKeyPassword; + + + + private void addFileSelectLayout (FileSelectLayout fsl) { + int i = fileselects.size() + CHOOSE_FILE_OFFSET; + fileselects.put(i, fsl); + fsl.setFragment(this,i); + } + + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile=ProfileManager.get(profileuuid); + getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + + mView = inflater.inflate(R.layout.basic_settings,container,false); + + mProfileName = (EditText) mView.findViewById(R.id.profilename); + mServerAddress = (TextView) mView.findViewById(R.id.address); + mServerPort = (TextView) mView.findViewById(R.id.port); + mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect); + mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect); + mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect); + mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select); + mUseLzo = (CheckBox) mView.findViewById(R.id.lzo); + mTcpUdp = (ToggleButton) mView.findViewById(id.tcpudp); + mType = (Spinner) mView.findViewById(R.id.type); + mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password); + mAliasName = (TextView) mView.findViewById(R.id.aliasname); + + mUserName = (EditText) mView.findViewById(R.id.auth_username); + mPassword = (EditText) mView.findViewById(R.id.auth_password); + mKeyPassword = (EditText) mView.findViewById(R.id.key_password); + + + + addFileSelectLayout(mCaCert); + addFileSelectLayout(mClientCert); + addFileSelectLayout(mClientKey); + addFileSelectLayout(mpkcs12); + mpkcs12.setBase64Encode(); + mCaCert.setShowClear(); + + + mType.setOnItemSelectedListener(this); + + mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); + + + if (mHandler == null) { + mHandler = new Handler(this); + } + + return mView; + } + + + @Override + public void onStart() { + super.onStart(); + String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile=ProfileManager.get(profileuuid); + loadPreferences(); + + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) { + String filedata = data.getStringExtra(FileSelect.RESULT_DATA); + FileSelectLayout fsl = fileselects.get(request); + fsl.setData(filedata); + + savePreferences(); + + // Private key files may result in showing/hiding the private key password dialog + if(fsl==mClientKey) { + changeType(mType.getSelectedItemPosition()); + } + } + + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (parent == mType) { + changeType(position); + } + } + @Override + public void onPause() { + super.onPause(); + savePreferences(); + } + + + + private void changeType(int type){ + // hide everything + mView.findViewById(R.id.pkcs12).setVisibility(View.GONE); + mView.findViewById(R.id.certs).setVisibility(View.GONE); + mView.findViewById(R.id.statickeys).setVisibility(View.GONE); + mView.findViewById(R.id.keystore).setVisibility(View.GONE); + mView.findViewById(R.id.cacert).setVisibility(View.GONE); + mView.findViewById(R.id.userpassword).setVisibility(View.GONE); + mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE); + + // Fall through are by design + switch(type) { + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_CERTIFICATES: + mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + if(mProfile.requireTLSKeyPassword()) + mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS_PKCS12: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_PKCS12: + mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_STATICKEYS: + mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS_KEYSTORE: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + case VpnProfile.TYPE_KEYSTORE: + mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + break; + + case VpnProfile.TYPE_USERPASS: + mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + break; + } + + + } + + private void loadPreferences() { + mProfileName.setText(mProfile.mName); + mClientCert.setData(mProfile.mClientCertFilename); + mClientKey.setData(mProfile.mClientKeyFilename); + mCaCert.setData(mProfile.mCaFilename); + + mUseLzo.setChecked(mProfile.mUseLzo); + mServerPort.setText(mProfile.mServerPort); + mServerAddress.setText(mProfile.mServerName); + mTcpUdp.setChecked(mProfile.mUseUdp); + mType.setSelection(mProfile.mAuthenticationType); + mpkcs12.setData(mProfile.mPKCS12Filename); + mPKCS12Password.setText(mProfile.mPKCS12Password); + mUserName.setText(mProfile.mUsername); + mPassword.setText(mProfile.mPassword); + mKeyPassword.setText(mProfile.mKeyPassword); + + setAlias(); + + } + + void savePreferences() { + + mProfile.mName = mProfileName.getText().toString(); + mProfile.mCaFilename = mCaCert.getData(); + mProfile.mClientCertFilename = mClientCert.getData(); + mProfile.mClientKeyFilename = mClientKey.getData(); + + mProfile.mUseLzo = mUseLzo.isChecked(); + mProfile.mServerPort =mServerPort.getText().toString(); + mProfile.mServerName = mServerAddress.getText().toString(); + mProfile.mUseUdp = mTcpUdp.isChecked(); + + mProfile.mAuthenticationType = mType.getSelectedItemPosition(); + mProfile.mPKCS12Filename = mpkcs12.getData(); + mProfile.mPKCS12Password = mPKCS12Password.getText().toString(); + + mProfile.mPassword = mPassword.getText().toString(); + mProfile.mUsername = mUserName.getText().toString(); + mProfile.mKeyPassword = mKeyPassword.getText().toString(); + + } + + + private void setAlias() { + if(mProfile.mAlias == null) { + mAliasName.setText(R.string.client_no_certificate); + } else { + mAliasName.setText(mProfile.mAlias); + } + } + + public void showCertDialog () { + try { + KeyChain.choosePrivateKeyAlias(getActivity(), + new KeyChainAliasCallback() { + + public void alias(String alias) { + // Credential alias selected. Remember the alias selection for future use. + mProfile.mAlias=alias; + mHandler.sendEmptyMessage(UPDATE_ALIAS); + } + + + }, + new String[] {"RSA"}, // List of acceptable key types. null for any + null, // issuer, null for any + mProfile.mServerName, // host name of server requesting the cert, null if unavailable + -1, // port of server requesting the cert, -1 if unavailable + mProfile.mAlias); // alias to preselect, null if unavailable + } catch (ActivityNotFoundException anf) { + Builder ab = new AlertDialog.Builder(getActivity()); + ab.setTitle(R.string.broken_image_cert_title); + ab.setMessage(R.string.broken_image_cert); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + + @Override + public void onClick(View v) { + if (v == mView.findViewById(R.id.select_keystore_button)) { + showCertDialog(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + savePreferences(); + if(mProfile!=null) { + outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString()); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + + + @Override + public boolean handleMessage(Message msg) { + setAlias(); + return true; + } + + +} diff --git a/src/se/leap/openvpn/Settings_IP.java b/src/se/leap/openvpn/Settings_IP.java new file mode 100644 index 00000000..df2efb40 --- /dev/null +++ b/src/se/leap/openvpn/Settings_IP.java @@ -0,0 +1,129 @@ +package se.leap.openvpn; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceManager; +import android.preference.SwitchPreference; +import se.leap.leapclient.R; + +public class Settings_IP extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { + private EditTextPreference mIPv4; + private EditTextPreference mIPv6; + private SwitchPreference mUsePull; + private CheckBoxPreference mOverrideDNS; + private EditTextPreference mSearchdomain; + private EditTextPreference mDNS1; + private EditTextPreference mDNS2; + private CheckBoxPreference mNobind; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + // Make sure default values are applied. In a real app, you would + // want this in a shared function that is used to retrieve the + // SharedPreferences wherever they are needed. + PreferenceManager.setDefaultValues(getActivity(), + R.xml.vpn_ipsettings, false); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_ipsettings); + mIPv4 = (EditTextPreference) findPreference("ipv4_address"); + mIPv6 = (EditTextPreference) findPreference("ipv6_address"); + mUsePull = (SwitchPreference) findPreference("usePull"); + mOverrideDNS = (CheckBoxPreference) findPreference("overrideDNS"); + mSearchdomain =(EditTextPreference) findPreference("searchdomain"); + mDNS1 = (EditTextPreference) findPreference("dns1"); + mDNS2 = (EditTextPreference) findPreference("dns2"); + mNobind = (CheckBoxPreference) findPreference("nobind"); + + mIPv4.setOnPreferenceChangeListener(this); + mIPv6.setOnPreferenceChangeListener(this); + mDNS1.setOnPreferenceChangeListener(this); + mDNS2.setOnPreferenceChangeListener(this); + mUsePull.setOnPreferenceChangeListener(this); + mOverrideDNS.setOnPreferenceChangeListener(this); + mSearchdomain.setOnPreferenceChangeListener(this); + + loadSettings(); + } + + @Override + protected void loadSettings() { + + mUsePull.setChecked(mProfile.mUsePull); + mIPv4.setText(mProfile.mIPv4Address); + mIPv6.setText(mProfile.mIPv6Address); + mDNS1.setText(mProfile.mDNS1); + mDNS2.setText(mProfile.mDNS2); + mOverrideDNS.setChecked(mProfile.mOverrideDNS); + mSearchdomain.setText(mProfile.mSearchDomain); + mNobind.setChecked(mProfile.mNobind); + + // Sets Summary + onPreferenceChange(mIPv4, mIPv4.getText()); + onPreferenceChange(mIPv6, mIPv6.getText()); + onPreferenceChange(mDNS1, mDNS1.getText()); + onPreferenceChange(mDNS2, mDNS2.getText()); + onPreferenceChange(mSearchdomain, mSearchdomain.getText()); + + setDNSState(); + } + + + @Override + protected void saveSettings() { + mProfile.mUsePull = mUsePull.isChecked(); + mProfile.mIPv4Address = mIPv4.getText(); + mProfile.mIPv6Address = mIPv6.getText(); + mProfile.mDNS1 = mDNS1.getText(); + mProfile.mDNS2 = mDNS2.getText(); + mProfile.mOverrideDNS = mOverrideDNS.isChecked(); + mProfile.mSearchDomain = mSearchdomain.getText(); + mProfile.mNobind = mNobind.isChecked(); + + } + + @Override + public boolean onPreferenceChange(Preference preference, + Object newValue) { + if(preference==mIPv4 || preference == mIPv6 + || preference==mDNS1 || preference == mDNS2 + || preference == mSearchdomain + ) + + preference.setSummary((String)newValue); + + if(preference== mUsePull || preference == mOverrideDNS) + if(preference==mOverrideDNS) { + // Set so the function gets the right value + mOverrideDNS.setChecked((Boolean) newValue); + } + setDNSState(); + + saveSettings(); + return true; + } + + private void setDNSState() { + boolean enabled; + mOverrideDNS.setEnabled(mUsePull.isChecked()); + if(!mUsePull.isChecked()) + enabled =true; + else if (mOverrideDNS.isChecked()) + enabled = true; + else + enabled = false; + + mDNS1.setEnabled(enabled); + mDNS2.setEnabled(enabled); + mSearchdomain.setEnabled(enabled); + + + } + + + } \ No newline at end of file diff --git a/src/se/leap/openvpn/Settings_Obscure.java b/src/se/leap/openvpn/Settings_Obscure.java new file mode 100644 index 00000000..ffbce971 --- /dev/null +++ b/src/se/leap/openvpn/Settings_Obscure.java @@ -0,0 +1,116 @@ +package se.leap.openvpn; + +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import se.leap.leapclient.R; + +public class Settings_Obscure extends OpenVpnPreferencesFragment implements OnPreferenceChangeListener { + private CheckBoxPreference mUseRandomHostName; + private CheckBoxPreference mUseFloat; + private CheckBoxPreference mUseCustomConfig; + private EditTextPreference mCustomConfig; + private ListPreference mLogverbosity; + private CheckBoxPreference mPersistent; + private ListPreference mConnectretrymax; + private EditTextPreference mConnectretry; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.vpn_obscure); + + + mUseRandomHostName = (CheckBoxPreference) findPreference("useRandomHostname"); + mUseFloat = (CheckBoxPreference) findPreference("useFloat"); + mUseCustomConfig = (CheckBoxPreference) findPreference("enableCustomOptions"); + mCustomConfig = (EditTextPreference) findPreference("customOptions"); + mLogverbosity = (ListPreference) findPreference("verblevel"); + mPersistent = (CheckBoxPreference) findPreference("usePersistTun"); + mConnectretrymax = (ListPreference) findPreference("connectretrymax"); + mConnectretry = (EditTextPreference) findPreference("connectretry"); + + mLogverbosity.setOnPreferenceChangeListener(this); + mLogverbosity.setSummary("%s"); + + mConnectretrymax.setOnPreferenceChangeListener(this); + mConnectretrymax.setSummary("%s"); + + mConnectretry.setOnPreferenceChangeListener(this); + + + loadSettings(); + + } + + protected void loadSettings() { + mUseRandomHostName.setChecked(mProfile.mUseRandomHostname); + mUseFloat.setChecked(mProfile.mUseFloat); + mUseCustomConfig.setChecked(mProfile.mUseCustomConfig); + mCustomConfig.setText(mProfile.mCustomConfigOptions); + mPersistent.setChecked(mProfile.mPersistTun); + + mLogverbosity.setValue(mProfile.mVerb); + onPreferenceChange(mLogverbosity, mProfile.mVerb); + + mConnectretrymax.setValue(mProfile.mConnectRetryMax); + onPreferenceChange(mConnectretrymax, mProfile.mConnectRetryMax); + + mConnectretry.setText(mProfile.mConnectRetry); + onPreferenceChange(mConnectretry, mProfile.mConnectRetry); + } + + + protected void saveSettings() { + mProfile.mUseRandomHostname = mUseRandomHostName.isChecked(); + mProfile.mUseFloat = mUseFloat.isChecked(); + mProfile.mUseCustomConfig = mUseCustomConfig.isChecked(); + mProfile.mCustomConfigOptions = mCustomConfig.getText(); + mProfile.mVerb = mLogverbosity.getValue(); + mProfile.mConnectRetryMax = mConnectretrymax.getValue(); + mProfile.mPersistTun = mPersistent.isChecked(); + mProfile.mConnectRetry = mConnectretry.getText(); + } + + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if(preference==mLogverbosity) { + // Catch old version problem + if(newValue==null){ + newValue="1"; + } + mLogverbosity.setDefaultValue(newValue); + //This is idiotic. + int i =Integer.parseInt((String) newValue); + + // verb >= 5 is not supported by the chooser + if(i < mLogverbosity.getEntries().length ) + mLogverbosity.setSummary(mLogverbosity.getEntries()[i]); + else + mLogverbosity.setSummary(String.format("debug verbosity: %d",i)); + } else if (preference == mConnectretrymax) { + if(newValue==null) { + newValue="5"; + } + mConnectretrymax.setDefaultValue(newValue); + + for(int i=0;i 0) { + fout.write(buf, 0, lenread); + lenread = mvpn.read(buf); + } + fout.close(); + + if(!mvpnout.setExecutable(true)) { + OpenVPN.logMessage(0, "","Failed to set minivpn executable"); + return false; + } + + + return true; + } catch (IOException e) { + if(e2!=null) + OpenVPN.logMessage(0, "",e2.getLocalizedMessage()); + OpenVPN.logMessage(0, "",e.getLocalizedMessage()); + e.printStackTrace(); + return false; + } + } + + + public static void startOpenVpn(VpnProfile startprofile, Context context) { + if(!writeMiniVPN(context)) { + OpenVPN.logMessage(0, "", "Error writing minivpn binary"); + return; + } + OpenVPN.logMessage(0, "", context.getString(R.string.building_configration)); + + Intent startVPN = startprofile.prepareIntent(context); + if(startVPN!=null) + context.startService(startVPN); + + } +} diff --git a/src/se/leap/openvpn/VPNPreferences.java b/src/se/leap/openvpn/VPNPreferences.java new file mode 100644 index 00000000..81d854ce --- /dev/null +++ b/src/se/leap/openvpn/VPNPreferences.java @@ -0,0 +1,138 @@ +package se.leap.openvpn; + +import java.util.List; +import se.leap.leapclient.R; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.Menu; +import android.view.MenuItem; + + +public class VPNPreferences extends PreferenceActivity { + + private String mProfileUUID; + private VpnProfile mProfile; + + public VPNPreferences() { + super(); + + } + + + + + @Override + protected void onStop() { + super.onStop(); + }; + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putString(getIntent().getStringExtra(getPackageName() + ".profileUUID"),mProfileUUID); + super.onSaveInstanceState(outState); + } + + @Override + protected void onResume() { + super.onResume(); + Intent intent = getIntent(); + + + if(intent!=null) { + String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); + if(profileUUID==null) { + Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); + profileUUID = initialArguments.getString(getPackageName() + ".profileUUID"); + } + if(profileUUID!=null){ + + mProfileUUID = profileUUID; + mProfile = ProfileManager.get(this,mProfileUUID); + + } + } + // When a profile is deleted from a category fragment in hadset mod we need to finish + // this activity as well when returning + if (mProfile==null || mProfile.profileDleted) { + setResult(VPNProfileList.RESULT_VPN_DELETED); + finish(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + mProfileUUID = getIntent().getStringExtra(getPackageName() + ".profileUUID"); + if(savedInstanceState!=null){ + String savedUUID = savedInstanceState.getString(getPackageName() + ".profileUUID"); + if(savedUUID!=null) + mProfileUUID=savedUUID; + } + + mProfile = ProfileManager.get(this,mProfileUUID); + if(mProfile!=null) { + setTitle(getString(R.string.edit_profile_title, mProfile.getName())); + } + super.onCreate(savedInstanceState); + } + + + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.vpn_headers, target); + for (Header header : target) { + if(header.fragmentArguments==null) + header.fragmentArguments = new Bundle(); + header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID); + } + } + + @Override + public void onBackPressed() { + setResult(RESULT_OK, getIntent()); + super.onBackPressed(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.remove_vpn) + askProfileRemoval(); + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + getMenuInflater().inflate(R.menu.vpnpreferences_menu, menu); + + return super.onCreateOptionsMenu(menu); + } + + private void askProfileRemoval() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Confirm deletion"); + dialog.setMessage(getString(R.string.remove_vpn_query, mProfile.mName)); + + dialog.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + removeProfile(mProfile); + } + + }); + dialog.setNegativeButton(android.R.string.no,null); + dialog.create().show(); + } + + protected void removeProfile(VpnProfile profile) { + ProfileManager.getInstance(this).removeProfile(this,profile); + setResult(VPNProfileList.RESULT_VPN_DELETED); + finish(); + + } +} + diff --git a/src/se/leap/openvpn/VPNProfileList.java b/src/se/leap/openvpn/VPNProfileList.java new file mode 100644 index 00000000..a7132c9e --- /dev/null +++ b/src/se/leap/openvpn/VPNProfileList.java @@ -0,0 +1,317 @@ +package se.leap.openvpn; + +import java.util.Collection; +import java.util.Comparator; +import java.util.TreeSet; +import se.leap.leapclient.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; +import android.text.Html.ImageGetter; +import android.view.LayoutInflater; +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.ArrayAdapter; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class VPNProfileList extends ListFragment { + + final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER; + + private static final int MENU_ADD_PROFILE = Menu.FIRST; + + private static final int START_VPN_CONFIG = 92; + private static final int SELECT_PROFILE = 43; + private static final int IMPORT_PROFILE = 231; + + private static final int MENU_IMPORT_PROFILE = Menu.FIRST +1; + + class VPNArrayAdapter extends ArrayAdapter { + + public VPNArrayAdapter(Context context, int resource, + int textViewResourceId) { + super(context, resource, textViewResourceId); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + View titleview = v.findViewById(R.id.vpn_list_item_left); + titleview.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + VpnProfile profile =(VpnProfile) getListAdapter().getItem(position); + startVPN(profile); + } + }); + + View settingsview = v.findViewById(R.id.quickedit_settings); + settingsview.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + mEditProfile =(VpnProfile) getListAdapter().getItem(position); + editVPN(mEditProfile); + + } + }); + + return v; + } + } + + + + + + + + + private ArrayAdapter mArrayadapter; + + protected VpnProfile mEditProfile=null; + + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + // Debug load JNI + //OpenVPN.foo(); + } + + + class MiniImageGetter implements ImageGetter { + + + @Override + public Drawable getDrawable(String source) { + Drawable d=null; + if ("ic_menu_add".equals(source)) + d = getActivity().getResources().getDrawable(android.R.drawable.ic_menu_add); + else if("ic_menu_archive".equals(source)) + d = getActivity().getResources().getDrawable(R.drawable.ic_menu_archive); + + + + if(d!=null) { + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + return d; + }else{ + return null; + } + } + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.vpn_profile_list, container,false); + + TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint); + TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint); + + + + newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null)); + importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null)); + + + + return v; + + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(); + } + + class VpnProfileNameComperator implements Comparator { + + @Override + public int compare(VpnProfile lhs, VpnProfile rhs) { + return lhs.mName.compareTo(rhs.mName); + } + + } + + private void setListAdapter() { + mArrayadapter = new VPNArrayAdapter(getActivity(),R.layout.vpn_list_item,R.id.vpn_item_title); + Collection allvpn = getPM().getProfiles(); + + TreeSet sortedset = new TreeSet(new VpnProfileNameComperator()); + sortedset.addAll(allvpn); + mArrayadapter.addAll(sortedset); + + setListAdapter(mArrayadapter); + } + + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_ADD_PROFILE, 0, R.string.menu_add_profile) + .setIcon(android.R.drawable.ic_menu_add) + .setAlphabeticShortcut('a') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + menu.add(0, MENU_IMPORT_PROFILE, 0, R.string.menu_import) + .setIcon(R.drawable.ic_menu_archive) + .setAlphabeticShortcut('i') + .setTitleCondensed(getActivity().getString(R.string.menu_import_short)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT ); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == MENU_ADD_PROFILE) { + onAddProfileClicked(); + return true; + } else if (itemId == MENU_IMPORT_PROFILE) { + startImportConfig(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void startImportConfig() { + Intent intent = new Intent(getActivity(),FileSelect.class); + intent.putExtra(FileSelect.NO_INLINE_SELECTION, true); + intent.putExtra(FileSelect.WINDOW_TITLE, R.string.import_configuration_file); + startActivityForResult(intent, SELECT_PROFILE); + } + + + + + + private void onAddProfileClicked() { + Context context = getActivity(); + if (context != null) { + final EditText entry = new EditText(context); + entry.setSingleLine(); + + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setTitle(R.string.menu_add_profile); + dialog.setMessage(R.string.add_profile_name_prompt); + dialog.setView(entry); + + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = entry.getText().toString(); + if (getPM().getProfileByName(name)==null) { + VpnProfile profile = new VpnProfile(name); + addProfile(profile); + } else { + Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); + } + } + + + }); + dialog.setNegativeButton(android.R.string.cancel, null); + dialog.create().show(); + } + + } + + + private void addProfile(VpnProfile profile) { + getPM().addProfile(profile); + getPM().saveProfileList(getActivity()); + getPM().saveProfile(getActivity(),profile); + mArrayadapter.add(profile); + } + + + + + + private ProfileManager getPM() { + return ProfileManager.getInstance(getActivity()); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if(resultCode == RESULT_VPN_DELETED){ + if(mArrayadapter != null && mEditProfile !=null) + mArrayadapter.remove(mEditProfile); + } + + if(resultCode != Activity.RESULT_OK) + return; + + if (requestCode == START_VPN_CONFIG) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + VpnProfile profile = ProfileManager.get(configuredVPN); + getPM().saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + setListAdapter(); + + } else if(requestCode== SELECT_PROFILE) { + String filedata = data.getStringExtra(FileSelect.RESULT_DATA); + Intent startImport = new Intent(getActivity(),ConfigConverter.class); + startImport.setAction(ConfigConverter.IMPORT_PROFILE); + Uri uri = new Uri.Builder().path(filedata).scheme("file").build(); + startImport.setData(uri); + startActivityForResult(startImport, IMPORT_PROFILE); + } else if(requestCode == IMPORT_PROFILE) { + String profileUUID = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + mArrayadapter.add(ProfileManager.get(profileUUID)); + } + + } + + + private void editVPN(VpnProfile profile) { + + Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) + .putExtra(getActivity().getPackageName() + ".profileUUID", profile.getUUID().toString()); + + startActivityForResult(vprefintent,START_VPN_CONFIG); + } + + private void startVPN(VpnProfile profile) { + + getPM().saveProfile(getActivity(), profile); + + Intent intent = new Intent(getActivity(),LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + + getActivity().finish(); + } +} diff --git a/src/se/leap/openvpn/VpnProfile.java b/src/se/leap/openvpn/VpnProfile.java new file mode 100644 index 00000000..293a3d06 --- /dev/null +++ b/src/se/leap/openvpn/VpnProfile.java @@ -0,0 +1,746 @@ +package se.leap.openvpn; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.UUID; +import java.util.Vector; + +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemWriter; +import se.leap.leapclient.R; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.preference.PreferenceManager; +import android.security.KeyChain; +import android.security.KeyChainException; + +public class VpnProfile implements Serializable{ + // Parcable + /** + * + */ + private static final long serialVersionUID = 7085688938959334563L; + static final int TYPE_CERTIFICATES=0; + static final int TYPE_PKCS12=1; + static final int TYPE_KEYSTORE=2; + public static final int TYPE_USERPASS = 3; + public static final int TYPE_STATICKEYS = 4; + public static final int TYPE_USERPASS_CERTIFICATES = 5; + public static final int TYPE_USERPASS_PKCS12 = 6; + public static final int TYPE_USERPASS_KEYSTORE = 7; + + // Don't change this, not all parts of the program use this constant + public static final String EXTRA_PROFILEUUID = "se.leap.openvpn.profileUUID"; + public static final String INLINE_TAG = "[[INLINE]]"; + private static final String OVPNCONFIGFILE = "android.conf"; + + protected transient String mTransientPW=null; + protected transient String mTransientPCKS12PW=null; + private transient PrivateKey mPrivateKey; + protected boolean profileDleted=false; + + + public static String DEFAULT_DNS1="131.234.137.23"; + public static String DEFAULT_DNS2="131.234.137.24"; + + // Public attributes, since I got mad with getter/setter + // set members to default values + private UUID mUuid; + public int mAuthenticationType = TYPE_KEYSTORE ; + public String mName; + public String mAlias; + public String mClientCertFilename; + public String mTLSAuthDirection=""; + public String mTLSAuthFilename; + public String mClientKeyFilename; + public String mCaFilename; + public boolean mUseLzo=true; + public String mServerPort= "1194" ; + public boolean mUseUdp = true; + public String mPKCS12Filename; + public String mPKCS12Password; + public boolean mUseTLSAuth = false; + public String mServerName = "openvpn.blinkt.de" ; + public String mDNS1=DEFAULT_DNS1; + public String mDNS2=DEFAULT_DNS2; + public String mIPv4Address; + public String mIPv6Address; + public boolean mOverrideDNS=false; + public String mSearchDomain="blinkt.de"; + public boolean mUseDefaultRoute=true; + public boolean mUsePull=true; + public String mCustomRoutes; + public boolean mCheckRemoteCN=false; + public boolean mExpectTLSCert=true; + public String mRemoteCN=""; + public String mPassword=""; + public String mUsername=""; + public boolean mRoutenopull=false; + public boolean mUseRandomHostname=false; + public boolean mUseFloat=false; + public boolean mUseCustomConfig=false; + public String mCustomConfigOptions=""; + public String mVerb="1"; + public String mCipher=""; + public boolean mNobind=false; + public boolean mUseDefaultRoutev6=true; + public String mCustomRoutesv6=""; + public String mKeyPassword=""; + public boolean mPersistTun = false; + public String mConnectRetryMax="5"; + public String mConnectRetry="5"; + public boolean mUserEditable=true; + + static final String MINIVPN = "miniopenvpn"; + + + + + + + public void clearDefaults() { + mServerName="unkown"; + mUsePull=false; + mUseLzo=false; + mUseDefaultRoute=false; + mUseDefaultRoutev6=false; + mExpectTLSCert=false; + mPersistTun = false; + } + + + public static String openVpnEscape(String unescaped) { + if(unescaped==null) + return null; + String escapedString = unescaped.replace("\\", "\\\\"); + escapedString = escapedString.replace("\"","\\\""); + escapedString = escapedString.replace("\n","\\n"); + + if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#")) + return unescaped; + else + return '"' + escapedString + '"'; + } + + + static final String OVPNCONFIGCA = "android-ca.pem"; + static final String OVPNCONFIGUSERCERT = "android-user.pem"; + + + public VpnProfile(String name) { + mUuid = UUID.randomUUID(); + mName = name; + } + + public UUID getUUID() { + return mUuid; + + } + + public String getName() { + return mName; + } + + + public String getConfigFile(Context context) + { + + File cacheDir= context.getCacheDir(); + String cfg=""; + + // Enable managment interface + cfg += "# Enables connection to GUI\n"; + cfg += "management "; + + cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; + cfg += " unix\n"; + cfg += "management-client\n"; + // Not needed, see updated man page in 2.3 + //cfg += "management-signal\n"; + cfg += "management-query-passwords\n"; + cfg += "management-hold\n\n"; + + /* tmp-dir patched out :) + cfg+="# /tmp does not exist on Android\n"; + cfg+="tmp-dir "; + cfg+=cacheDir.getAbsolutePath(); + cfg+="\n\n"; */ + + cfg+="# Log window is better readable this way\n"; + cfg+="suppress-timestamps\n"; + + + + boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); + + if(useTLSClient && mUsePull) + cfg+="client\n"; + else if (mUsePull) + cfg+="pull\n"; + else if(useTLSClient) + cfg+="tls-client\n"; + + + cfg+="verb " + mVerb + "\n"; + + if(mConnectRetryMax ==null) { + mConnectRetryMax="5"; + } + + if(!mConnectRetryMax.equals("-1")) + cfg+="connect-retry-max " + mConnectRetryMax+ "\n"; + + if(mConnectRetry==null) + mConnectRetry="5"; + + + cfg+="connect-retry " + mConnectRetry + "\n"; + + cfg+="resolv-retry 60\n"; + + + + // We cannot use anything else than tun + cfg+="dev tun\n"; + + // Server Address + cfg+="remote "; + cfg+=mServerName; + cfg+=" "; + cfg+=mServerPort; + if(mUseUdp) + cfg+=" udp\n"; + else + cfg+=" tcp-client\n"; + + + + + switch(mAuthenticationType) { + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_CERTIFICATES: + // Ca + cfg+=insertFileData("ca",mCaFilename); + + // Client Cert + Key + cfg+=insertFileData("key",mClientKeyFilename); + cfg+=insertFileData("cert",mClientCertFilename); + + break; + case VpnProfile.TYPE_USERPASS_PKCS12: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_PKCS12: + cfg+=insertFileData("pkcs12",mPKCS12Filename); + break; + + case VpnProfile.TYPE_USERPASS_KEYSTORE: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_KEYSTORE: + cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n"; + cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n"; + cfg+="management-external-key\n"; + + break; + case VpnProfile.TYPE_USERPASS: + cfg+="auth-user-pass\n"; + cfg+=insertFileData("ca",mCaFilename); + } + + if(mUseLzo) { + cfg+="comp-lzo\n"; + } + + if(mUseTLSAuth) { + if(mAuthenticationType==TYPE_STATICKEYS) + cfg+=insertFileData("secret",mTLSAuthFilename); + else + cfg+=insertFileData("tls-auth",mTLSAuthFilename); + + if(nonNull(mTLSAuthDirection)) { + cfg+= "key-direction "; + cfg+= mTLSAuthDirection; + cfg+="\n"; + } + + } + + if(!mUsePull ) { + if(nonNull(mIPv4Address)) + cfg +="ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; + + if(nonNull(mIPv6Address)) + cfg +="ifconfig-ipv6 " + mIPv6Address + "\n"; + } + + if(mUsePull && mRoutenopull) + cfg += "route-nopull\n"; + + String routes = ""; + int numroutes=0; + if(mUseDefaultRoute) + routes += "route 0.0.0.0 0.0.0.0\n"; + else + for(String route:getCustomRoutes()) { + routes += "route " + route + "\n"; + numroutes++; + } + + + if(mUseDefaultRoutev6) + cfg += "route-ipv6 ::/0\n"; + else + for(String route:getCustomRoutesv6()) { + routes += "route-ipv6 " + route + "\n"; + numroutes++; + } + + // Round number to next 100 + if(numroutes> 90) { + numroutes = ((numroutes / 100)+1) * 100; + cfg+="# Alot of routes are set, increase max-routes\n"; + cfg+="max-routes " + numroutes + "\n"; + } + cfg+=routes; + + if(mOverrideDNS || !mUsePull) { + if(nonNull(mDNS1)) + cfg+="dhcp-option DNS " + mDNS1 + "\n"; + if(nonNull(mDNS2)) + cfg+="dhcp-option DNS " + mDNS2 + "\n"; + if(nonNull(mSearchDomain)) + cfg+="dhcp-option DOMAIN " + mSearchDomain + "\n"; + + } + + if(mNobind) + cfg+="nobind\n"; + + + + // Authentication + if(mCheckRemoteCN) { + if(mRemoteCN == null || mRemoteCN.equals("") ) + cfg+="tls-remote " + mServerName + "\n"; + else + cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; + } + if(mExpectTLSCert) + cfg += "remote-cert-tls server\n"; + + + if(nonNull(mCipher)){ + cfg += "cipher " + mCipher + "\n"; + } + + + // Obscure Settings dialog + if(mUseRandomHostname) + cfg += "#my favorite options :)\nremote-random-hostname\n"; + + if(mUseFloat) + cfg+= "float\n"; + + if(mPersistTun) { + cfg+= "persist-tun\n"; + cfg+= "# persist-tun also sets persist-remote-ip to avoid DNS resolve problem\n"; + cfg+= "persist-remote-ip\n"; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); + if(usesystemproxy) { + cfg+= "# Use system proxy setting\n"; + cfg+= "management-query-proxy\n"; + } + + + if(mUseCustomConfig) { + cfg += "# Custom configuration options\n"; + cfg += "# You are on your on own here :)\n"; + cfg += mCustomConfigOptions; + cfg += "\n"; + + } + + + + return cfg; + } + + //! Put inline data inline and other data as normal escaped filename + private String insertFileData(String cfgentry, String filedata) { + if(filedata==null) { + return String.format("%s %s\n",cfgentry,"missing"); + }else if(filedata.startsWith(VpnProfile.INLINE_TAG)){ + String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length()); + return String.format("<%s>\n%s\n\n",cfgentry,datawoheader,cfgentry); + } else { + return String.format("%s %s\n",cfgentry,openVpnEscape(filedata)); + } + } + + private boolean nonNull(String val) { + if(val == null || val.equals("")) + return false; + else + return true; + } + + private Collection getCustomRoutes() { + Vector cidrRoutes=new Vector(); + if(mCustomRoutes==null) { + // No routes set, return empty vector + return cidrRoutes; + } + for(String route:mCustomRoutes.split("[\n \t]")) { + if(!route.equals("")) { + String cidrroute = cidrToIPAndNetmask(route); + if(cidrRoutes == null) + return null; + + cidrRoutes.add(cidrroute); + } + } + + return cidrRoutes; + } + + private Collection getCustomRoutesv6() { + Vector cidrRoutes=new Vector(); + if(mCustomRoutesv6==null) { + // No routes set, return empty vector + return cidrRoutes; + } + for(String route:mCustomRoutesv6.split("[\n \t]")) { + if(!route.equals("")) { + cidrRoutes.add(route); + } + } + + return cidrRoutes; + } + + + + private String cidrToIPAndNetmask(String route) { + String[] parts = route.split("/"); + + // No /xx, assume /32 as netmask + if (parts.length ==1) + parts = (route + "/32").split("/"); + + if (parts.length!=2) + return null; + int len; + try { + len = Integer.parseInt(parts[1]); + } catch(NumberFormatException ne) { + return null; + } + if (len <0 || len >32) + return null; + + + long nm = 0xffffffffl; + nm = (nm << (32-len)) & 0xffffffffl; + + String netmask =String.format("%d.%d.%d.%d", (nm & 0xff000000) >> 24,(nm & 0xff0000) >> 16, (nm & 0xff00) >> 8 ,nm & 0xff ); + return parts[0] + " " + netmask; + } + + + + private String[] buildOpenvpnArgv(File cacheDir) + { + Vector args = new Vector(); + + // Add fixed paramenters + //args.add("/data/data/se.leap.openvpn/lib/openvpn"); + args.add(cacheDir.getAbsolutePath() +"/" + VpnProfile.MINIVPN); + + args.add("--config"); + args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); + // Silences script security warning + + args.add("script-security"); + args.add("0"); + + + return (String[]) args.toArray(new String[args.size()]); + } + + public Intent prepareIntent(Context context) { + String prefix = context.getPackageName(); + + Intent intent = new Intent(context,OpenVpnService.class); + + if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { + if(!saveCertificates(context)) + return null; + } + + intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir())); + intent.putExtra(prefix + ".profileUUID", mUuid.toString()); + + ApplicationInfo info = context.getApplicationInfo(); + intent.putExtra(prefix +".nativelib",info.nativeLibraryDir); + + try { + FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); + cfg.write(getConfigFile(context)); + cfg.flush(); + cfg.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return intent; + } + + private boolean saveCertificates(Context context) { + PrivateKey privateKey = null; + X509Certificate[] cachain=null; + try { + privateKey = KeyChain.getPrivateKey(context,mAlias); + mPrivateKey = privateKey; + + cachain = KeyChain.getCertificateChain(context, mAlias); + if(cachain.length <= 1 && !nonNull(mCaFilename)) + OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert)); + + for(X509Certificate cert:cachain) { + OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN()); + } + + + + + if(nonNull(mCaFilename)) { + try { + Certificate cacert = getCacertFromFile(); + X509Certificate[] newcachain = new X509Certificate[cachain.length+1]; + for(int i=0;i= 1){ + X509Certificate usercert = cachain[0]; + + FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT); + + PemWriter upw = new PemWriter(userout); + upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); + upw.close(); + + } + + return true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (KeyChainException e) { + OpenVPN.logMessage(0,"",context.getString(R.string.keychain_access)); + } + return false; + } + private Certificate getCacertFromFile() throws FileNotFoundException, CertificateException { + CertificateFactory certFact = CertificateFactory.getInstance("X.509"); + + InputStream inStream; + + if(mCaFilename.startsWith(INLINE_TAG)) + inStream = new ByteArrayInputStream(mCaFilename.replace(INLINE_TAG,"").getBytes()); + else + inStream = new FileInputStream(mCaFilename); + + return certFact.generateCertificate(inStream); + } + + + //! Return an error if somethign is wrong + public int checkProfile(Context context) { + if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) { + if(mAlias==null) + return R.string.no_keystore_cert_selected; + } + + if(!mUsePull) { + if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) + return R.string.ipv4_format_error; + } + if(isUserPWAuth() && !nonNull(mUsername)) { + return R.string.error_empty_username; + } + if(!mUseDefaultRoute && getCustomRoutes()==null) + return R.string.custom_route_format_error; + + // Everything okay + return R.string.no_error_found; + + } + + //! Openvpn asks for a "Private Key", this should be pkcs12 key + // + public String getPasswordPrivateKey() { + if(mTransientPCKS12PW!=null) { + String pwcopy = mTransientPCKS12PW; + mTransientPCKS12PW=null; + return pwcopy; + } + switch (mAuthenticationType) { + case TYPE_PKCS12: + case TYPE_USERPASS_PKCS12: + return mPKCS12Password; + + case TYPE_CERTIFICATES: + case TYPE_USERPASS_CERTIFICATES: + return mKeyPassword; + + case TYPE_USERPASS: + case TYPE_STATICKEYS: + default: + return null; + } + } + private boolean isUserPWAuth() { + switch(mAuthenticationType) { + case TYPE_USERPASS: + case TYPE_USERPASS_CERTIFICATES: + case TYPE_USERPASS_KEYSTORE: + case TYPE_USERPASS_PKCS12: + return true; + default: + return false; + + } + } + + + public boolean requireTLSKeyPassword() { + if(!nonNull(mClientKeyFilename)) + return false; + + String data = ""; + if(mClientKeyFilename.startsWith(INLINE_TAG)) + data = mClientKeyFilename; + else { + char[] buf = new char[2048]; + FileReader fr; + try { + fr = new FileReader(mClientKeyFilename); + int len = fr.read(buf); + while(len > 0 ) { + data += new String(buf,0,len); + len = fr.read(buf); + } + fr.close(); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } + + } + + if(data.contains("Proc-Type: 4,ENCRYPTED")) + return true; + else if(data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) + return true; + else + return false; + } + + public int needUserPWInput() { + if((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12)&& + (mPKCS12Password == null || mPKCS12Password.equals(""))) { + if(mTransientPCKS12PW==null) + return R.string.pkcs12_file_encryption_key; + } + + if(mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { + if(requireTLSKeyPassword() && !nonNull(mKeyPassword)) + if(mTransientPCKS12PW==null) { + return R.string.private_key_password; + } + } + + if(isUserPWAuth() && (mPassword.equals("") || mPassword == null)) { + if(mTransientPW==null) + return R.string.password; + + } + return 0; + } + + public String getPasswordAuth() { + if(mTransientPW!=null) { + String pwcopy = mTransientPW; + mTransientPW=null; + return pwcopy; + } else { + return mPassword; + } + } + + + // Used by the Array Adapter + @Override + public String toString() { + return mName; + } + + + public String getUUIDString() { + return mUuid.toString(); + } + + + public PrivateKey getKeystoreKey() { + return mPrivateKey; + } + + + +} + + + + -- cgit v1.2.3