From 2f73ef1dda56ba7ae588091e0c0e8f71c02bbf32 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Mon, 30 Apr 2012 02:53:12 +0200 Subject: version 0.4.2 --- src/de/blinkt/openvpn/BasicSettings.java | 292 ----------------- src/de/blinkt/openvpn/LogWindow.java | 13 +- src/de/blinkt/openvpn/OpenVPN.java | 4 + src/de/blinkt/openvpn/OpenVPNThread.java | 61 ++++ src/de/blinkt/openvpn/OpenVpnManagementThread.java | 140 ++++++++ src/de/blinkt/openvpn/OpenVpnService.java | 354 +++++++++------------ src/de/blinkt/openvpn/Settings_Authentication.java | 6 +- src/de/blinkt/openvpn/Settings_Basic.java | 291 +++++++++++++++++ src/de/blinkt/openvpn/VPNPreferences.java | 5 +- src/de/blinkt/openvpn/VPNProfileList.java | 71 ++++- src/de/blinkt/openvpn/VpnProfile.java | 109 ++++--- 11 files changed, 788 insertions(+), 558 deletions(-) delete mode 100644 src/de/blinkt/openvpn/BasicSettings.java create mode 100644 src/de/blinkt/openvpn/OpenVPNThread.java create mode 100644 src/de/blinkt/openvpn/OpenVpnManagementThread.java create mode 100644 src/de/blinkt/openvpn/Settings_Basic.java (limited to 'src/de/blinkt/openvpn') diff --git a/src/de/blinkt/openvpn/BasicSettings.java b/src/de/blinkt/openvpn/BasicSettings.java deleted file mode 100644 index 51a4c2bc..00000000 --- a/src/de/blinkt/openvpn/BasicSettings.java +++ /dev/null @@ -1,292 +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.util.HashMap; - -import android.app.Fragment; -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.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 com.lamerman.FileDialog; - -import de.blinkt.openvpn.R.id; - -public class BasicSettings 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 HashMap fileselects = new HashMap(); - - - private EditText mUserName; - - - private EditText mPassword; - - - private View mView; - - - private VpnProfile mProfile; - private EditText mProfileName; - - - - 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) { - Bundle foo = getArguments(); - String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(profileuuid); - super.onCreate(savedInstanceState); - } - - @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); - - - - - addFileSelectLayout(mCaCert); - addFileSelectLayout(mClientCert); - addFileSelectLayout(mClientKey); - addFileSelectLayout(mpkcs12); - - loadPreferences(); - - mType.setOnItemSelectedListener(this); - - mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); - - - if (mHandler == null) { - mHandler = new Handler(this); - } - - return mView; - } - - - @Override - public void onActivityResult(int request, int result, Intent data) { - if (request >= CHOOSE_FILE_OFFSET) { - String filepath = data.getStringExtra(FileDialog.RESULT_PATH); - FileSelectLayout fsl = fileselects.get(request); - fsl.setData(filepath); - } - savePreferences(); - } - - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - ((VPNPreferences) getActivity()).setmBS(this); - } - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (parent == mType) { - changeType(position); - } - } - - - - 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); - - switch(type) { - case VpnProfile.TYPE_CERTIFICATES: - mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); - mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); - break; - 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_KEYSTORE: - mView.findViewById(R.id.keystore).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); - - 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(); - - } - - - private void setAlias() { - if(mProfile.mAlias == null) { - mAliasName.setText(R.string.client_no_certificate); - } else { - mAliasName.setText(mProfile.mAlias); - } - } - - public void showCertDialog () { - 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", "DSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - "internal.example.com", // host name of server requesting the cert, null if unavailable - 443, // port of server requesting the cert, -1 if unavailable - null); // alias to preselect, null if unavailable - } - - @Override - public void onClick(View v) { - if (v == mView.findViewById(R.id.select_keystore_button)) { - showCertDialog(); - } - } - - - - @Override - public void onNothingSelected(AdapterView parent) { - } - - - @Override - public boolean handleMessage(Message msg) { - setAlias(); - return true; - } - - -} diff --git a/src/de/blinkt/openvpn/LogWindow.java b/src/de/blinkt/openvpn/LogWindow.java index 6c3e1ff7..b5e7008f 100644 --- a/src/de/blinkt/openvpn/LogWindow.java +++ b/src/de/blinkt/openvpn/LogWindow.java @@ -8,11 +8,12 @@ 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.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ListView; -import android.widget.SimpleAdapter; import android.widget.TextView; import de.blinkt.openvpn.OpenVPN.LogListener; @@ -45,7 +46,7 @@ public class LogWindow extends ListActivity { observers.add(observer); } - + @Override public void unregisterDataSetObserver(DataSetObserver observer) { observers.remove(observer); @@ -133,6 +134,14 @@ public class LogWindow extends ListActivity { } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.logmenu, menu); + return true; + } + + @Override public void onCreate(Bundle savedInstanceState) { diff --git a/src/de/blinkt/openvpn/OpenVPN.java b/src/de/blinkt/openvpn/OpenVPN.java index c3d92a4c..ea6c4926 100644 --- a/src/de/blinkt/openvpn/OpenVPN.java +++ b/src/de/blinkt/openvpn/OpenVPN.java @@ -53,6 +53,10 @@ public class OpenVPN { } + synchronized static void clearLog() { + logbuffer.clear(); + } + synchronized static void addLogListener(LogListener ll){ logListener.add(ll); } diff --git a/src/de/blinkt/openvpn/OpenVPNThread.java b/src/de/blinkt/openvpn/OpenVPNThread.java new file mode 100644 index 00000000..124179e5 --- /dev/null +++ b/src/de/blinkt/openvpn/OpenVPNThread.java @@ -0,0 +1,61 @@ +package de.blinkt.openvpn; + +import java.util.Arrays; + +import android.util.Log; + +public class OpenVPNThread implements Runnable { + private static final String TAG = "OpenVPN"; + private OpenVpnService mService; + private String[] mArgv; + + public OpenVPNThread(OpenVpnService service,String[] argv) + { + mService = service; + mArgv = argv; + } + + @Override + public void run() { + try { + Log.i(TAG, "Starting o"); + + + OpenVPN.setCallback(mService); + + + // We try to create the tunnel for several times. The better way + // is to work with ConnectivityManager, such as trying only when + // the network is avaiable. Here we just use a counter to keep + // things simple. + //for (int attempt = 0; attempt < 10; ++attempt) { + mService.getHandler().sendEmptyMessage(R.string.connecting); + + // Log argv + + OpenVPN.logMessage(0, "argv:" , Arrays.toString(mArgv)); + + OpenVPN.startOpenVPNThreadArgs(mArgv); + + + + // Sleep for a while. This also checks if we got interrupted. + Thread.sleep(3000); + //} + Log.i(TAG, "Giving up"); + } catch (Exception e) { + Log.e(TAG, "Got " + e.toString()); + } finally { + try { + /// mInterface.close(); + } catch (Exception e) { + // ignore + } + //mInterface = null; + + + mService.getHandler().sendEmptyMessage(R.string.disconnected); + Log.i(TAG, "Exiting"); + } + } +} diff --git a/src/de/blinkt/openvpn/OpenVpnManagementThread.java b/src/de/blinkt/openvpn/OpenVpnManagementThread.java new file mode 100644 index 00000000..00d8fe9b --- /dev/null +++ b/src/de/blinkt/openvpn/OpenVpnManagementThread.java @@ -0,0 +1,140 @@ +package de.blinkt.openvpn; + +import java.io.IOException; +import java.io.InputStream; + +import android.net.LocalSocket; +import android.util.Log; + +public class OpenVpnManagementThread implements Runnable { + + private static final String TAG = "openvpn"; + private LocalSocket mSocket; + private VpnProfile mProfile; + + public OpenVpnManagementThread(VpnProfile profile, LocalSocket mgmtsocket) { + mProfile = profile; + mSocket = mgmtsocket; + } + + + private String managmentEscape(String unescape) { + String escapedString = unescape.replace("\\", "\\\\"); + escapedString = escapedString.replace("\"","\\\""); + escapedString = escapedString.replace("\n","\\n"); + return '"' + escapedString + '"'; + } + + + public void managmentCommand(String cmd) { + try { + mSocket.getOutputStream().write(cmd.getBytes()); + mSocket.getOutputStream().flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + @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 + InputStream instream = null; + try { + instream = mSocket.getInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + String pendingInput=""; + + try { + + while(true) { + int numbytesread = instream.read(buffer); + if(numbytesread==-1) + return; + + String input = new String(buffer,0,numbytesread,"UTF-8"); + + pendingInput += input; + + pendingInput=processInput(pendingInput); + + + + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + + 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")) + logStatusMessage(command); + else if (cmd.equals("PASSWORD")) { + processPWCommand(argument); + } else if (cmd.equals("HOLD")) { + managmentCommand("hold release\n"); + } + } + Log.i(TAG, "Got unrecognized command" + command); + + } + + + private void processPWCommand(String argument) { + //argument has the form Need 'Private Key' password + int p1 =argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + //String needed = argument.replace("Need '", "").replace("' password", ""); + String needed = argument.substring(p1+1, p2); + + 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, managmentEscape(mProfile.mUsername)); + managmentCommand(usercmd); + pw = mProfile.getPasswordAuth(); + } + if(pw!=null) { + String cmd = String.format("password '%s' %s\n", needed, managmentEscape(pw)); + managmentCommand(cmd); + } + + } + + + + + private void logStatusMessage(String command) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/de/blinkt/openvpn/OpenVpnService.java b/src/de/blinkt/openvpn/OpenVpnService.java index 39d73adb..031f2a14 100644 --- a/src/de/blinkt/openvpn/OpenVpnService.java +++ b/src/de/blinkt/openvpn/OpenVpnService.java @@ -17,13 +17,11 @@ package de.blinkt.openvpn; import java.io.IOException; -import java.net.UnknownHostException; -import java.util.Arrays; import java.util.Vector; -import de.blinkt.openvpn.OpenVpnService.CIDRIP; - +import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -31,19 +29,15 @@ import android.net.VpnService; import android.os.Handler; import android.os.Message; import android.os.ParcelFileDescriptor; -import android.util.Log; import android.widget.Toast; -public class OpenVpnService extends VpnService implements Handler.Callback, Runnable { - private static final String TAG = "OpenVpnService"; - - private String[] mArgv; +public class OpenVpnService extends VpnService implements Handler.Callback { + private static final String TAG = "OpenVpnService"; - private Handler mHandler; - // Only one VPN, make this thread shared between all instances - private static Thread mThread; + Handler mHandler; + private Thread mServiceThread; - private ParcelFileDescriptor mInterface; + private ParcelFileDescriptor mInterface; private Vector mDnslist=new Vector(); @@ -55,7 +49,14 @@ public class OpenVpnService extends VpnService implements Handler.Callback, Runn private CIDRIP mLocalIP; - + private OpenVpnManagementThread mSocketManager; + + private Thread mSocketManagerThread; + + private NotificationManager mNotificationManager; + + + class CIDRIP{ String mIp; int len; @@ -63,12 +64,12 @@ public class OpenVpnService extends VpnService implements Handler.Callback, Runn mIp=ip; String[] ipt = mask.split("\\."); long netmask=0; - + netmask += Integer.parseInt(ipt[0]); netmask += Integer.parseInt(ipt[1])<< 8; netmask += Integer.parseInt(ipt[2])<< 16; netmask += Integer.parseInt(ipt[3])<< 24; - + len =0; while((netmask & 0x1) == 1) { len++; @@ -80,209 +81,147 @@ public class OpenVpnService extends VpnService implements Handler.Callback, Runn return String.format("%s/%d",mIp,len); } } - - @Override - public void onRevoke() { - managmentCommand("signal SIGINT\n"); - mThread=null; - stopSelf(); - }; - - - public void managmentCommand(String cmd) { - LocalSocket mgmtsocket; - try { - byte[] buffer = new byte[400]; - mgmtsocket = new LocalSocket(); - - mgmtsocket.connect(new LocalSocketAddress(getCacheDir().getAbsolutePath() + "/" + "mgmtsocket", - LocalSocketAddress.Namespace.FILESYSTEM)); - //mgmtsocket = new Dat("127.0.0.1",OpenVPNClient.MANAGMENTPORT)); - - //OutputStreamWriter outw = new OutputStreamWriter(mgmtsocket.getOutputStream()); - mgmtsocket.getInputStream().read(buffer); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - //outw.write(cmd); - mgmtsocket.getOutputStream().write(cmd.getBytes()); - //outw.flush(); - try { - Thread.sleep(400); - } catch (InterruptedException e) { - } - mgmtsocket.close(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + @Override + public void onRevoke() { + mSocketManager.managmentCommand("signal SIGINT\n"); + mServiceThread=null; + stopSelf(); + }; + + + + + + + private LocalSocket openManagmentInterface() { + // Could take a while to open connection + String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); + LocalSocket sock = new LocalSocket(); + int tries = 8; + + while(tries > 0 && !sock.isConnected()) { + try { + sock.connect(new LocalSocketAddress(socketname, + LocalSocketAddress.Namespace.FILESYSTEM)); + } catch (IOException e) { + // wait 300 ms before retrying + try { Thread.sleep(300); + } catch (InterruptedException e1) {} + + } + tries--; } + return sock; + } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // The handler is only used to show messages. - if (mHandler == null) { - mHandler = new Handler(this); - } - - // Stop the previous session by interrupting the thread. - if (mThread != null) { - managmentCommand("signal SIGINT\n"); - mThread.interrupt(); - } - - // Thread already running, reuse existing, - - // Extract information from the intent. - String prefix = getPackageName(); - mArgv = intent.getStringArrayExtra(prefix + ".ARGV"); - - - // Start a new session by creating a new thread. - mThread = new Thread(this, "OpenVPNThread"); - mThread.start(); - - String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); - mProfile = ProfileManager.get(profileUUID); - - if(intent.hasExtra(prefix +".PKCS12PASS")) - { - try { - String pkcs12password = intent.getStringExtra(prefix +".PKCS12PASS"); - Thread.sleep(3000); - - managmentCommand("password 'Private Key' " + pkcs12password + "\n"); - } catch (InterruptedException e) { - } - - } - if(intent.hasExtra(prefix +".USERNAME")) - { - try { - String user = managmentEscape(intent.getStringExtra(prefix +".USERNAME")); - String pw = managmentEscape(intent.getStringExtra(prefix +".PASSWORD")); - Thread.sleep(3000); - - - managmentCommand("username 'Auth' " + user+ "\n" + - "password 'Auth' " + pw + "\n"); - } catch (InterruptedException e) { - } - - } - - - return START_STICKY; - } - - private String managmentEscape(String unescape) { - String escapedString = unescape.replace("\\", "\\\\"); - escapedString = escapedString.replace("\"","\\\""); - escapedString = escapedString.replace("\n","\\n"); - return '"' + escapedString + '"'; + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // The handler is only used to show messages. + if (mHandler == null) { + mHandler = new Handler(this); + } + + mNotificationManager=(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE); + + + // Stop the previous session by interrupting the thread. + if (mSocketManager != null) { + mSocketManager.managmentCommand("signal SIGINT\n"); + } + + if (mServiceThread!=null) { + mServiceThread.interrupt(); + } + + + // Extract information from the intent. + String prefix = getPackageName(); + String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); + + String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); + mProfile = ProfileManager.get(profileUUID); + + // Start a new session by creating a new thread. + + OpenVPNThread serviceThread = new OpenVPNThread(this, argv); + + mServiceThread = new Thread(serviceThread, "OpenVPNServiceThread"); + mServiceThread.start(); + + + // Open the Management Interface + LocalSocket mgmtsocket = openManagmentInterface(); + + if(mgmtsocket!=null) { + // start a Thread that handles incoming messages of the managment socket + mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket); + mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread"); + mSocketManagerThread.start(); + } + + return START_STICKY; } + + + @Override - public void onDestroy() { - if (mThread != null) { - managmentCommand("signal SIGINT\n"); - - mThread.interrupt(); - } - } - - @Override - public boolean handleMessage(Message message) { - if (message != null) { - Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show(); - } - return true; - } - - @Override - public synchronized void run() { - try { - Log.i(TAG, "Starting o"); - - - OpenVPN.setCallback(this); - - - // We try to create the tunnel for several times. The better way - // is to work with ConnectivityManager, such as trying only when - // the network is avaiable. Here we just use a counter to keep - // things simple. - //for (int attempt = 0; attempt < 10; ++attempt) { - mHandler.sendEmptyMessage(R.string.connecting); - - // Log argv - - OpenVPN.logMessage(0, "argv:" , Arrays.toString(mArgv)); - - OpenVPN.startOpenVPNThreadArgs(mArgv); - - - - // Sleep for a while. This also checks if we got interrupted. - Thread.sleep(3000); - //} - Log.i(TAG, "Giving up"); - } catch (Exception e) { - Log.e(TAG, "Got " + e.toString()); - } finally { - try { - mInterface.close(); - } catch (Exception e) { - // ignore - } - mInterface = null; - - - mHandler.sendEmptyMessage(R.string.disconnected); - Log.i(TAG, "Exiting"); - } - } + public void onDestroy() { + if (mServiceThread != null) { + mSocketManager.managmentCommand("signal SIGINT\n"); + + mServiceThread.interrupt(); + } + } + + @Override + public boolean handleMessage(Message message) { + if (message != null) { + Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show(); + } + return true; + } + + public ParcelFileDescriptor openTun() { - Builder builder = new Builder(); - - builder.addAddress(mLocalIP.mIp, mLocalIP.len); - - for (String dns : mDnslist ) { - builder.addDnsServer(dns); + Builder builder = new Builder(); + + builder.addAddress(mLocalIP.mIp, mLocalIP.len); + + for (String dns : mDnslist ) { + builder.addDnsServer(dns); } - - - for (CIDRIP route:mRoutes) { - builder.addRoute(route.mIp, route.len); - } - - if(mDomain!=null) - builder.addSearchDomain(mDomain); - - - mDnslist.clear(); - mRoutes.clear(); - - - builder.setSession(mProfile.mName + " - " + mLocalIP); - - // Let the configure Button show the Log - Intent intent = new Intent(getBaseContext(),LogWindow.class); - PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); - builder.setConfigureIntent(startLW); - mInterface = builder.establish(); - return mInterface; - } + + for (CIDRIP route:mRoutes) { + builder.addRoute(route.mIp, route.len); + } + + if(mDomain!=null) + builder.addSearchDomain(mDomain); + mDnslist.clear(); + mRoutes.clear(); + + + builder.setSession(mProfile.mName + " - " + mLocalIP); + + // Let the configure Button show the Log + Intent intent = new Intent(getBaseContext(),LogWindow.class); + PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); + builder.setConfigureIntent(startLW); + mInterface = builder.establish(); + return mInterface; + + } + public void addDNS(String dns) { mDnslist.add(dns); } @@ -303,4 +242,9 @@ public class OpenVpnService extends VpnService implements Handler.Callback, Runn public void setLocalIP(String local, String netmask) { mLocalIP = new CIDRIP(local, netmask); } + + + public Handler getHandler() { + return mHandler; + } } diff --git a/src/de/blinkt/openvpn/Settings_Authentication.java b/src/de/blinkt/openvpn/Settings_Authentication.java index 4c70344d..57d99417 100644 --- a/src/de/blinkt/openvpn/Settings_Authentication.java +++ b/src/de/blinkt/openvpn/Settings_Authentication.java @@ -1,8 +1,5 @@ package de.blinkt.openvpn; -import com.lamerman.FileDialog; -import com.lamerman.SelectionMode; - import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -15,6 +12,9 @@ import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceFragment; import android.preference.SwitchPreference; +import com.lamerman.FileDialog; +import com.lamerman.SelectionMode; + public class Settings_Authentication extends PreferenceFragment implements OnPreferenceChangeListener, OnPreferenceClickListener { private static final int SELECT_TLS_FILE = 23223232; private CheckBoxPreference mExpectTLSCert; diff --git a/src/de/blinkt/openvpn/Settings_Basic.java b/src/de/blinkt/openvpn/Settings_Basic.java new file mode 100644 index 00000000..35e86792 --- /dev/null +++ b/src/de/blinkt/openvpn/Settings_Basic.java @@ -0,0 +1,291 @@ +/* + * 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.util.HashMap; + +import android.app.Fragment; +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.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 com.lamerman.FileDialog; + +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 HashMap fileselects = new HashMap(); + + + private EditText mUserName; + + + private EditText mPassword; + + + private View mView; + + + private VpnProfile mProfile; + private EditText mProfileName; + + + + 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) { + String profileuuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); + mProfile=ProfileManager.get(profileuuid); + super.onCreate(savedInstanceState); + } + + @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); + + + + + addFileSelectLayout(mCaCert); + addFileSelectLayout(mClientCert); + addFileSelectLayout(mClientKey); + addFileSelectLayout(mpkcs12); + + loadPreferences(); + + mType.setOnItemSelectedListener(this); + + mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); + + + if (mHandler == null) { + mHandler = new Handler(this); + } + + return mView; + } + + + @Override + public void onActivityResult(int request, int result, Intent data) { + if (request >= CHOOSE_FILE_OFFSET) { + String filepath = data.getStringExtra(FileDialog.RESULT_PATH); + FileSelectLayout fsl = fileselects.get(request); + fsl.setData(filepath); + } + savePreferences(); + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ((VPNPreferences) getActivity()).setmBS(this); + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (parent == mType) { + changeType(position); + } + } + + + + 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); + + switch(type) { + case VpnProfile.TYPE_CERTIFICATES: + mView.findViewById(R.id.certs).setVisibility(View.VISIBLE); + mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE); + break; + 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_KEYSTORE: + mView.findViewById(R.id.keystore).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); + + 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(); + + } + + + private void setAlias() { + if(mProfile.mAlias == null) { + mAliasName.setText(R.string.client_no_certificate); + } else { + mAliasName.setText(mProfile.mAlias); + } + } + + public void showCertDialog () { + 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", "DSA"}, // List of acceptable key types. null for any + null, // issuer, null for any + "internal.example.com", // host name of server requesting the cert, null if unavailable + 443, // port of server requesting the cert, -1 if unavailable + null); // alias to preselect, null if unavailable + } + + @Override + public void onClick(View v) { + if (v == mView.findViewById(R.id.select_keystore_button)) { + showCertDialog(); + } + } + + + + @Override + public void onNothingSelected(AdapterView parent) { + } + + + @Override + public boolean handleMessage(Message msg) { + setAlias(); + return true; + } + + +} diff --git a/src/de/blinkt/openvpn/VPNPreferences.java b/src/de/blinkt/openvpn/VPNPreferences.java index 771cd902..38de9d3e 100644 --- a/src/de/blinkt/openvpn/VPNPreferences.java +++ b/src/de/blinkt/openvpn/VPNPreferences.java @@ -4,14 +4,13 @@ import java.util.List; import android.os.Bundle; import android.preference.PreferenceActivity; -import android.widget.Button; public class VPNPreferences extends PreferenceActivity { private String mProfileUUID; - private BasicSettings mBS; - public void setmBS(BasicSettings mBS) { + private Settings_Basic mBS; + public void setmBS(Settings_Basic mBS) { this.mBS = mBS; } diff --git a/src/de/blinkt/openvpn/VPNProfileList.java b/src/de/blinkt/openvpn/VPNProfileList.java index 8f4304dc..bc61dddb 100644 --- a/src/de/blinkt/openvpn/VPNProfileList.java +++ b/src/de/blinkt/openvpn/VPNProfileList.java @@ -13,6 +13,7 @@ import android.preference.PreferenceScreen; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.Toast; import de.blinkt.openvpn.VPNConfigPreference.VpnPreferencesClickListener; @@ -58,6 +59,39 @@ public class VPNProfileList extends PreferenceFragment implements VpnPreference } } + private void askForPW(String type) { + + final EditText entry = new EditText(getActivity()); + entry.setSingleLine(); + entry.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); + dialog.setTitle("Need " + type); + dialog.setMessage("Enter the password for profile " + mSelectedVPN.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(); + mSelectedVPN.mTransientPW = 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) { + } + }); + + dialog.create().show(); + + } + private void onAddProfileClicked() { Context context = getActivity(); if (context != null) { @@ -97,7 +131,7 @@ public class VPNProfileList extends PreferenceFragment implements VpnPreference } - + private void addProfile(VpnProfile profile) { getPM().addProfile(profile); getPM().saveProfileList(getActivity()); @@ -107,7 +141,7 @@ public class VPNProfileList extends PreferenceFragment implements VpnPreference - + public void refreshList() { PreferenceScreen plist = getPreferenceScreen(); @@ -159,7 +193,12 @@ public class VPNProfileList extends PreferenceFragment implements VpnPreference public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode==START_VPN_PROFILE && resultCode == Activity.RESULT_OK) { - new startOpenVpnThread().start(); + + if(mSelectedVPN.needUserPWInput()!=null) { + askForPW(mSelectedVPN.needUserPWInput()); + } else { + new startOpenVpnThread().start(); + } } else if (requestCode == START_VPN_CONFIG && resultCode == Activity.RESULT_OK) { String configuredVPN = data.getStringExtra(getActivity().getPackageName() + ".profileUUID"); @@ -183,44 +222,44 @@ public class VPNProfileList extends PreferenceFragment implements VpnPreference Intent startLW = new Intent(getActivity().getBaseContext(),LogWindow.class); startActivity(startLW); - + OpenVPN.logMessage(0, "", "Building configration..."); - + Intent startVPN = mSelectedVPN.prepareIntent(getActivity()); - + getActivity().startService(startVPN); getActivity().finish(); - + } } void showConfigErrorDialog(int vpnok) { - + AlertDialog.Builder d = new AlertDialog.Builder(getActivity()); d.setTitle(R.string.config_error_found); - + d.setMessage(vpnok); - - + + d.setPositiveButton("Ok", null); - + d.show(); } - + @Override public void onStartVPNClick(VPNConfigPreference preference) { getPM(); // Query the System for permission mSelectedVPN = ProfileManager.get(preference.getKey()); - + int vpnok = mSelectedVPN.checkProfile(); if(vpnok!= R.string.no_error_found) { showConfigErrorDialog(vpnok); return; } - - + + getPM().saveProfile(getActivity(), mSelectedVPN); Intent intent = VpnService.prepare(getActivity()); diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java index 2db89395..4028f3d0 100644 --- a/src/de/blinkt/openvpn/VpnProfile.java +++ b/src/de/blinkt/openvpn/VpnProfile.java @@ -38,6 +38,7 @@ public class VpnProfile implements Serializable{ public static final int TYPE_STATICKEYS = 4; private static final String OVPNCONFIGFILE = "android.conf"; + // Keep in order of parceling // Public attributes, since I got mad with getter/setter @@ -74,6 +75,10 @@ public class VpnProfile implements Serializable{ public String mUsername=""; public boolean mRoutenopull=false; + + protected transient String mTransientPW=null; + private static transient String mTempPKCS12Password; + public int describeContents() { return 0; @@ -153,10 +158,17 @@ public class VpnProfile implements Serializable{ String cfg=""; + // Enable managment interface + cfg += "management "; - // TODO "--remote-cert-eku", "TLS Web Server Authentication" - - + cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; + cfg += " unix\n"; + cfg += "management-hold\n\n"; + + cfg+="# tmp does not exist on Android\n"; + cfg+="tmp-dir "; + cfg+=cacheDir.getAbsolutePath(); + cfg+="\n\n"; boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); @@ -171,10 +183,7 @@ public class VpnProfile implements Serializable{ cfg+="verb 2\n"; - // /tmp does not exist on Android - cfg+="tmp-dir "; - cfg+=cacheDir.getAbsolutePath(); - cfg+="\n"; + // quit after 5 tries cfg+="connect-retry-max 5\n"; @@ -339,14 +348,6 @@ public class VpnProfile implements Serializable{ // Add fixed paramenters args.add("openvpn"); - // Enable managment interface to - // stop openvpn - args.add("--management"); - - args.add(cacheDir.getAbsolutePath() + "/" + "mgmtsocket"); - args.add("unix"); - //args.add("--management-hold"); - args.add("--config"); args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); @@ -358,24 +359,12 @@ public class VpnProfile implements Serializable{ String prefix = activity.getPackageName(); Intent intent = new Intent(activity,OpenVpnService.class); + + if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE) { + savePKCS12(activity); + } intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(activity.getCacheDir())); - - if(mAuthenticationType == TYPE_PKCS12){ - intent.putExtra(prefix + ".PKCS12PASS", - mPKCS12Password); - } - - if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE) { - String pkcs12pw = savePKCS12(activity); - intent.putExtra(prefix + ".PKCS12PASS", pkcs12pw); - } - - if(mAuthenticationType == VpnProfile.TYPE_USERPASS) { - intent.putExtra(prefix + ".USERNAME", mUsername); - intent.putExtra(prefix + ".PASSWORD", mPassword); - } - intent.putExtra(prefix + ".profileUUID", mUuid.toString()); try { @@ -390,7 +379,10 @@ public class VpnProfile implements Serializable{ return intent; } - private String getRandomPW() { + public String getTemporaryPKCS12Password() { + if(mTempPKCS12Password!=null) + return mTempPKCS12Password; + String pw= ""; // Put enough digits togher to make a password :) Random r = new Random(); @@ -398,11 +390,12 @@ public class VpnProfile implements Serializable{ pw += new Integer(r.nextInt(1000)).toString(); } - return pw; + mTempPKCS12Password=pw; + return mTempPKCS12Password; } - private String savePKCS12(Context context) { + private void savePKCS12(Context context) { PrivateKey privateKey = null; X509Certificate[] cachain=null; try { @@ -412,11 +405,11 @@ public class VpnProfile implements Serializable{ KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(null, null); ks.setKeyEntry("usercert", privateKey, null, cachain); - String mypw = getRandomPW(); + String mypw = getTemporaryPKCS12Password(); FileOutputStream fout = new FileOutputStream(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGPKCS12); ks.store(fout,mypw.toCharArray()); fout.flush(); fout.close(); - return mypw; + return; } catch (KeyChainException e) { e.printStackTrace(); } catch (InterruptedException e) { @@ -432,7 +425,6 @@ public class VpnProfile implements Serializable{ } catch (IOException e) { e.printStackTrace(); } - return "ERROR"; } //! Return an error if somethign is wrong @@ -453,6 +445,49 @@ public class VpnProfile implements Serializable{ } + //! Openvpn asks for a "Private Key", this can be pkcs12 pw or private key pw + // + public String getPasswordPrivateKey() { + if(mTransientPW!=null) { + return mTransientPW; + } + switch (mAuthenticationType) { + case TYPE_KEYSTORE: + return getTemporaryPKCS12Password(); + + case TYPE_PKCS12: + return mPKCS12Password; + + case TYPE_USERPASS: + case TYPE_STATICKEYS: + case TYPE_CERTIFICATES: + default: + return null; + } + } + + public String needUserPWInput() { + if(mTransientPW!=null) + return null; + if(mAuthenticationType == TYPE_PKCS12 && + (mPKCS12Password.equals("") || mPKCS12Password == null)) { + return "PKCS12 File Password"; + } + if(mAuthenticationType == TYPE_USERPASS && + (mPassword.equals("") || mPassword == null)) { + return "Password"; + } + return null; + } + + public String getPasswordAuth() { + if(mTransientPW!=null) + return mTransientPW; + else + return mPassword; + } + + } -- cgit v1.2.3