summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2012-04-16 19:21:14 +0200
committerArne Schwabe <arne@rfc2549.org>2012-04-16 19:21:14 +0200
commit3e4d8f433239c40311037616b1b8833a06651ae0 (patch)
tree98ab7fce0d011d34677b0beb762d389cb5c39199 /src
Initial import
Diffstat (limited to 'src')
-rw-r--r--src/com/lamerman/FileDialog.java389
-rw-r--r--src/com/lamerman/SelectionMode.java7
-rw-r--r--src/de/blinkt/openvpn/AboutActivity.java22
-rw-r--r--src/de/blinkt/openvpn/FileSelectLayout.java75
-rw-r--r--src/de/blinkt/openvpn/LogWindow.java37
-rw-r--r--src/de/blinkt/openvpn/OpenVPN.java78
-rw-r--r--src/de/blinkt/openvpn/OpenVPNClient.java567
-rw-r--r--src/de/blinkt/openvpn/OpenVpnService.java203
-rw-r--r--src/de/blinkt/openvpn/VpnProfile.java8
9 files changed, 1386 insertions, 0 deletions
diff --git a/src/com/lamerman/FileDialog.java b/src/com/lamerman/FileDialog.java
new file mode 100644
index 00000000..821b1fbf
--- /dev/null
+++ b/src/com/lamerman/FileDialog.java
@@ -0,0 +1,389 @@
+package com.lamerman;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.TreeMap;
+
+import de.blinkt.openvpn.R;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+
+/**
+ * Activity para escolha de arquivos/diretorios.
+ *
+ * @author android
+ *
+ */
+public class FileDialog extends ListActivity {
+
+ /**
+ * Chave de um item da lista de paths.
+ */
+ private static final String ITEM_KEY = "key";
+
+ /**
+ * Imagem de um item da lista de paths (diretorio ou arquivo).
+ */
+ private static final String ITEM_IMAGE = "image";
+
+ /**
+ * Diretorio raiz.
+ */
+ private static final String ROOT = "/";
+
+ /**
+ * Parametro de entrada da Activity: path inicial. Padrao: ROOT.
+ */
+ public static final String START_PATH = "START_PATH";
+
+ /**
+ * Parametro de entrada da Activity: filtro de formatos de arquivos. Padrao:
+ * null.
+ */
+ public static final String FORMAT_FILTER = "FORMAT_FILTER";
+
+ /**
+ * Parametro de saida da Activity: path escolhido. Padrao: null.
+ */
+ public static final String RESULT_PATH = "RESULT_PATH";
+
+ /**
+ * Parametro de entrada da Activity: tipo de selecao: pode criar novos paths
+ * ou nao. Padrao: nao permite.
+ *
+ * @see {@link SelectionMode}
+ */
+ public static final String SELECTION_MODE = "SELECTION_MODE";
+
+ /**
+ * Parametro de entrada da Activity: se e permitido escolher diretorios.
+ * Padrao: falso.
+ */
+ public static final String CAN_SELECT_DIR = "CAN_SELECT_DIR";
+
+ private List<String> path = null;
+ private TextView myPath;
+ private EditText mFileName;
+ private ArrayList<HashMap<String, Object>> mList;
+
+ private Button selectButton;
+
+ private LinearLayout layoutSelect;
+ private LinearLayout layoutCreate;
+ private InputMethodManager inputManager;
+ private String parentPath;
+ private String currentPath = ROOT;
+
+ private int selectionMode = SelectionMode.MODE_CREATE;
+
+ private String[] formatFilter = null;
+
+ private boolean canSelectDir = false;
+
+ private File selectedFile;
+ private HashMap<String, Integer> lastPositions = new HashMap<String, Integer>();
+
+ /**
+ * Called when the activity is first created. Configura todos os parametros
+ * de entrada e das VIEWS..
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setResult(RESULT_CANCELED, getIntent());
+
+ setContentView(R.layout.file_dialog_main);
+ myPath = (TextView) findViewById(R.id.path);
+ mFileName = (EditText) findViewById(R.id.fdEditTextFile);
+
+ inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+
+ selectButton = (Button) findViewById(R.id.fdButtonSelect);
+ selectButton.setEnabled(false);
+ selectButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (selectedFile != null) {
+ getIntent().putExtra(RESULT_PATH, selectedFile.getPath());
+ setResult(RESULT_OK, getIntent());
+ finish();
+ }
+ }
+ });
+
+ final Button newButton = (Button) findViewById(R.id.fdButtonNew);
+ newButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ setCreateVisible(v);
+
+ mFileName.setText("");
+ mFileName.requestFocus();
+ }
+ });
+
+ selectionMode = getIntent().getIntExtra(SELECTION_MODE, SelectionMode.MODE_CREATE);
+
+ formatFilter = getIntent().getStringArrayExtra(FORMAT_FILTER);
+
+ canSelectDir = getIntent().getBooleanExtra(CAN_SELECT_DIR, false);
+
+ if (selectionMode == SelectionMode.MODE_OPEN) {
+ newButton.setEnabled(false);
+ newButton.setVisibility(View.GONE);
+ }
+
+ layoutSelect = (LinearLayout) findViewById(R.id.fdLinearLayoutSelect);
+ layoutCreate = (LinearLayout) findViewById(R.id.fdLinearLayoutCreate);
+ layoutCreate.setVisibility(View.GONE);
+
+ final Button cancelButton = (Button) findViewById(R.id.fdButtonCancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ setSelectVisible(v);
+ }
+
+ });
+ final Button createButton = (Button) findViewById(R.id.fdButtonCreate);
+ createButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mFileName.getText().length() > 0) {
+ getIntent().putExtra(RESULT_PATH, currentPath + "/" + mFileName.getText());
+ setResult(RESULT_OK, getIntent());
+ finish();
+ }
+ }
+ });
+
+ String startPath = getIntent().getStringExtra(START_PATH);
+ startPath = startPath != null ? startPath : ROOT;
+ if (canSelectDir) {
+ File file = new File(startPath);
+ selectedFile = file;
+ selectButton.setEnabled(true);
+ }
+ getDir(startPath);
+ }
+
+ 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<String> item = new ArrayList<String>();
+ path = new ArrayList<String>();
+ mList = new ArrayList<HashMap<String, Object>>();
+
+ 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<String, String> dirsMap = new TreeMap<String, String>();
+ TreeMap<String, String> dirsPathMap = new TreeMap<String, String>();
+ TreeMap<String, String> filesMap = new TreeMap<String, String>();
+ TreeMap<String, String> filesPathMap = new TreeMap<String, String>();
+ 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(this, 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<String, Object> item = new HashMap<String, Object>();
+ item.put(ITEM_KEY, fileName);
+ item.put(ITEM_IMAGE, imageId);
+ mList.add(item);
+ }
+
+ /**
+ * Quando clica no item da lista, deve-se: 1) Se for diretorio, abre seus
+ * arquivos filhos; 2) Se puder escolher diretorio, define-o como sendo o
+ * path escolhido. 3) Se for arquivo, define-o como path escolhido. 4) Ativa
+ * botao de selecao.
+ */
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+
+ File file = new File(path.get(position));
+
+ setSelectVisible(v);
+
+ if (file.isDirectory()) {
+ selectButton.setEnabled(false);
+ if (file.canRead()) {
+ lastPositions.put(currentPath, position);
+ getDir(path.get(position));
+ if (canSelectDir) {
+ selectedFile = file;
+ v.setSelected(true);
+ selectButton.setEnabled(true);
+ }
+ } else {
+ new AlertDialog.Builder(this).setIcon(R.drawable.icon)
+ .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder))
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ }
+ }).show();
+ }
+ } else {
+ selectedFile = file;
+ v.setSelected(true);
+ selectButton.setEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if ((keyCode == KeyEvent.KEYCODE_BACK)) {
+ selectButton.setEnabled(false);
+
+ if (layoutCreate.getVisibility() == View.VISIBLE) {
+ layoutCreate.setVisibility(View.GONE);
+ layoutSelect.setVisibility(View.VISIBLE);
+ } else {
+ if (!currentPath.equals(ROOT)) {
+ getDir(parentPath);
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+ /**
+ * Define se o botao de CREATE e visivel.
+ *
+ * @param v
+ */
+ private void setCreateVisible(View v) {
+ layoutCreate.setVisibility(View.VISIBLE);
+ layoutSelect.setVisibility(View.GONE);
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ selectButton.setEnabled(false);
+ }
+
+ /**
+ * Define se o botao de SELECT e visivel.
+ *
+ * @param v
+ */
+ private void setSelectVisible(View v) {
+ layoutCreate.setVisibility(View.GONE);
+ layoutSelect.setVisibility(View.VISIBLE);
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ selectButton.setEnabled(false);
+ }
+}
diff --git a/src/com/lamerman/SelectionMode.java b/src/com/lamerman/SelectionMode.java
new file mode 100644
index 00000000..3c05dfa8
--- /dev/null
+++ b/src/com/lamerman/SelectionMode.java
@@ -0,0 +1,7 @@
+package com.lamerman;
+
+public class SelectionMode {
+ public static final int MODE_CREATE = 0;
+
+ public static final int MODE_OPEN = 1;
+}
diff --git a/src/de/blinkt/openvpn/AboutActivity.java b/src/de/blinkt/openvpn/AboutActivity.java
new file mode 100644
index 00000000..0361d67c
--- /dev/null
+++ b/src/de/blinkt/openvpn/AboutActivity.java
@@ -0,0 +1,22 @@
+package de.blinkt.openvpn;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class AboutActivity extends Activity implements OnClickListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.about);
+
+ //findViewById(R.layout.about).setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+}
diff --git a/src/de/blinkt/openvpn/FileSelectLayout.java b/src/de/blinkt/openvpn/FileSelectLayout.java
new file mode 100644
index 00000000..0615b2ab
--- /dev/null
+++ b/src/de/blinkt/openvpn/FileSelectLayout.java
@@ -0,0 +1,75 @@
+package de.blinkt.openvpn;
+
+import com.lamerman.FileDialog;
+import com.lamerman.SelectionMode;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class FileSelectLayout extends LinearLayout implements OnClickListener {
+
+ private TextView mData;
+ private Activity mActivity;
+ private int mTaskId;
+ private Button mSelectButton;
+
+ public FileSelectLayout( Context context,AttributeSet attrset) {
+ super(context,attrset);
+ inflate(getContext(), R.layout.file_select, this);
+
+ TypedArray ta = context.obtainStyledAttributes(attrset,R.styleable.FileSelectLayout);
+
+ String title = ta.getString(R.styleable.FileSelectLayout_title);
+
+ TextView tview = (TextView) findViewById(R.id.file_title);
+ tview.setText(title);
+
+ mData = (TextView) findViewById(R.id.file_selected_item);
+ mSelectButton = (Button) findViewById(R.id.file_select_button);
+ mSelectButton.setOnClickListener(this);
+
+ }
+
+ public void setActivity(Activity a, int i)
+ {
+ mTaskId = i;
+ mActivity = a;
+ }
+
+ public void getCertificateFileDialog() {
+ Intent startFC = new Intent(getContext(),FileDialog.class);
+ startFC.putExtra(FileDialog.START_PATH, "/sdcard");
+ startFC.putExtra(FileDialog.SELECTION_MODE, SelectionMode.MODE_OPEN);
+
+ mActivity.startActivityForResult(startFC,mTaskId);
+ }
+
+
+ public String getData() {
+ return mData.getText().toString();
+ }
+
+ public void setData(String data) {
+ mData.setText(data);
+
+ }
+
+ @Override
+ public void onClick(View v) {
+ if(v == mSelectButton) {
+ getCertificateFileDialog();
+ }
+ }
+
+
+
+}
diff --git a/src/de/blinkt/openvpn/LogWindow.java b/src/de/blinkt/openvpn/LogWindow.java
new file mode 100644
index 00000000..a26da2ba
--- /dev/null
+++ b/src/de/blinkt/openvpn/LogWindow.java
@@ -0,0 +1,37 @@
+package de.blinkt.openvpn;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+
+
+public class LogWindow extends ListActivity implements OnItemClickListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //setListAdapter(new ArrayAdapter<String>(this, R.layout.log_entry, COUNTRIES));
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.log_entry, OpenVPN.getlogbuffer()));
+ //setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, OpenVPN.logbuffer));
+
+ ListView lv = getListView();
+ lv.setTextFilterEnabled(true);
+
+ lv.setOnItemClickListener(this);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ // When clicked, show a toast with the TextView text
+ //Toast.makeText(getApplicationContext(), ((TextView) view).getText(),
+ //Toast.LENGTH_SHORT).show();
+
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.log_entry, OpenVPN.getlogbuffer()));
+ }
+}
diff --git a/src/de/blinkt/openvpn/OpenVPN.java b/src/de/blinkt/openvpn/OpenVPN.java
new file mode 100644
index 00000000..7385726b
--- /dev/null
+++ b/src/de/blinkt/openvpn/OpenVPN.java
@@ -0,0 +1,78 @@
+package de.blinkt.openvpn;
+
+import java.util.LinkedList;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+public class OpenVPN {
+ private static OpenVpnService mOpenVpnService;
+ private static String localip;
+ private static final int MAXLOGENTRIES = 500;
+ public static native int startOpenVPNThread();
+ public static native int startOpenVPNThreadArgs(String argv[]);
+ private static final String TAG = "OpenVpn";
+
+
+ public static LinkedList<String> logbuffer = new LinkedList<String>();
+ private static int counter=0;
+
+
+ static {
+ System.loadLibrary("crypto");
+ System.loadLibrary("ssl");
+ System.loadLibrary("lzo");
+ System.loadLibrary("openvpn");
+ }
+
+ static void addRoute(String dest,String mask, String gw) {
+ Log.i("openvpn" ,"Got Routing information " + dest + " " + mask + " " + gw );
+ }
+
+ synchronized static void logMessage(int level,String prefix, String message)
+ {
+ logbuffer.addFirst(prefix + " " + message);
+ if(logbuffer.size()>MAXLOGENTRIES)
+ logbuffer.removeLast();
+
+ // The garbage collector does not collect the String from native
+ // but kills me for logging 100 messages with too many references :(
+ // Force GC how and then to kill loose ends
+ if(counter++ % 50==0)
+ System.gc();
+ }
+
+
+ static void addInterfaceInfo(int mtu, String local, String remote)
+ {
+ Log.i("openvpn","Got interface info M" + mtu + " L: " + local + "R: " + remote);
+ localip=local;
+ }
+
+ public static void setCallback(OpenVpnService openVpnService) {
+ mOpenVpnService = openVpnService;
+ }
+
+ public static boolean protectSocket (int sockfd)
+ {
+ boolean p = mOpenVpnService.protect(sockfd);
+ if(p)
+ Log.d("openvpn","Protected socket "+ sockfd);
+ else
+ Log.e("openvpn","Error protecting socket "+ sockfd);
+ return p;
+ }
+
+ public static int openTunDevice() {
+ Log.d(TAG,"Opening tun device");
+ ParcelFileDescriptor pfd = mOpenVpnService.openTun(localip);
+ return pfd.detachFd();
+ }
+ //! Dummy method being called to force loading of JNI Libraries
+ public static void foo() { }
+
+ synchronized public static String[] getlogbuffer() {
+ return (String[]) logbuffer.toArray(new String[logbuffer.size()]);
+
+ }
+}
diff --git a/src/de/blinkt/openvpn/OpenVPNClient.java b/src/de/blinkt/openvpn/OpenVPNClient.java
new file mode 100644
index 00000000..af949c2d
--- /dev/null
+++ b/src/de/blinkt/openvpn/OpenVPNClient.java
@@ -0,0 +1,567 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.VpnService;
+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.security.KeyChainException;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.lamerman.FileDialog;
+
+import de.blinkt.openvpn.R.id;
+
+public class OpenVPNClient extends Activity implements View.OnClickListener, OnItemSelectedListener, Callback, OnCheckedChangeListener {
+ private static final String TAG = "OpenVpnClient";
+
+
+ private static final int START_OPENVPN = 0;
+ private static final int CHOOSE_FILE_OFFSET = 1000;
+ private static final int UPDATE_ALIAS = 20;
+
+ private static final String PREFS_NAME = "OVPN_SERVER";
+
+ private static final String OVPNCONFIGFILE = "android.conf";
+ private static final String OVPNCONFIGPKCS12 = "android.pkcs12";
+
+
+ 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 String certalias;
+ private FileSelectLayout mpkcs12;
+ private TextView mPKCS12Password;
+
+ private Handler mHandler;
+
+
+ private CheckBox mUseTlsAuth;
+
+
+ private CheckBox mShowAdvanced;
+
+
+ private FileSelectLayout mTlsFile;
+
+ private HashMap<Integer, FileSelectLayout> fileselects = new HashMap<Integer, FileSelectLayout>();
+
+
+ private Spinner mTLSDirection;
+
+ @Override
+ protected void onStop(){
+ super.onStop();
+ savePreferences();
+ }
+
+
+ public void writeConfigFile()
+ {
+
+ try {
+ FileWriter cfg = new FileWriter(getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
+
+
+
+ // TODO "--remote-cert-eku", "TLS Web Server Authentication"
+
+
+ // The stoned way of java to return an array from a vector
+ // brought to you by eclipse auto complete
+
+ cfg.write("client\n");
+ cfg.write("verb 2\n");
+
+
+ // /tmp does not exist on Android
+ cfg.write("tmp-dir ");
+ cfg.write(getCacheDir().getAbsolutePath());
+ cfg.write("\n");
+
+ // quit after 5 tries
+ cfg.write("--connect-retry-max 5\n");
+ cfg.write("--resolv-retry 5\n");
+
+
+
+ // We cannot use anything else than tun
+ cfg.write("dev tun\n");
+
+ // Server Address
+ cfg.write("remote ");
+ cfg.write(mServerAddress.getText().toString());
+ cfg.write(" ");
+ cfg.write(mServerPort.getText().toString());
+ if(mTcpUdp.isChecked())
+ cfg.write(" udp\n");
+ else
+ cfg.write(" tcp\n");
+
+
+
+ switch(mType.getSelectedItemPosition()) {
+ case VpnProfile.TYPE_CERTIFICATES:
+ // Ca
+ cfg.write("ca ");
+ cfg.write(mCaCert.getData());
+ cfg.write("\n");
+
+ // Client Cert + Key
+ cfg.write("key ");
+ cfg.write(mClientKey.getData());
+ cfg.write("\n");
+ cfg.write("cert ");
+ cfg.write(mClientCert.getData());
+ cfg.write("\n");
+ break;
+ case VpnProfile.TYPE_PKCS12:
+ cfg.write("pkcs12 ");
+ cfg.write(mpkcs12.getData());
+ cfg.write("\n");
+ cfg.write("management-query-passwords\n");
+ break;
+
+ case VpnProfile.TYPE_KEYSTORE:
+ cfg.write("pkcs12 ");
+ cfg.write(getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGPKCS12);
+ cfg.write("\n");
+ cfg.write("management-query-passwords\n");
+ break;
+
+ }
+
+ if(mUseLzo.isChecked()) {
+ cfg.write("comp-lzo\n");
+ }
+
+ if(mUseTlsAuth.isChecked()) {
+ cfg.write("tls-auth ");
+ cfg.write(mTlsFile.getData());
+ int tlsdir= mTLSDirection.getSelectedItemPosition();
+ // 2 is unspecified
+ if(tlsdir == 1 || tlsdir==2) {
+ cfg.write(" ");
+ cfg.write(new Integer(tlsdir).toString());
+ }
+ cfg.write("\n");
+ }
+ cfg.flush();
+ cfg.close();
+
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ private void addFileSelectLayout (FileSelectLayout fsl) {
+ int i = fileselects.size() + CHOOSE_FILE_OFFSET;
+ fileselects.put(i, fsl);
+ fsl.setActivity(this,i);
+ }
+
+ public String[] buildOpenvpnArgv()
+ {
+ Vector<String> args = new Vector<String>();
+
+ // Add fixed paramenters
+ args.add("openvpn");
+
+ // Enable managment interface to
+ // stop openvpn
+ args.add("--management");
+
+ args.add(getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
+ args.add("unix");
+ //args.add("--management-hold");
+
+ args.add("--config");
+ args.add(getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE);
+
+
+ return (String[]) args.toArray(new String[args.size()]);
+ }
+
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.config);
+
+ // Forces early JNI Load
+ OpenVPN.foo();
+
+ mServerAddress = (TextView) findViewById(R.id.address);
+ mServerPort = (TextView) findViewById(R.id.port);
+ mClientCert = (FileSelectLayout) findViewById(R.id.certselect);
+ mClientKey = (FileSelectLayout) findViewById(R.id.keyselect);
+ mCaCert = (FileSelectLayout) findViewById(R.id.certselect);
+ mpkcs12 = (FileSelectLayout) findViewById(R.id.pkcs12select);
+ mUseLzo = (CheckBox) findViewById(R.id.lzo);
+ mTcpUdp = (ToggleButton) findViewById(id.tcpudp);
+ mType = (Spinner) findViewById(R.id.type);
+ mPKCS12Password = (TextView) findViewById(R.id.pkcs12password);
+ mAliasName = (TextView) findViewById(R.id.aliasname);
+ mUseTlsAuth = (CheckBox) findViewById(R.id.useTLSAuth);
+ mTLSDirection = (Spinner) findViewById(R.id.tls_direction);
+
+ mShowAdvanced = (CheckBox) findViewById(R.id.show_advanced);
+ mTlsFile = (FileSelectLayout) findViewById(R.id.tlsAuth);
+
+
+
+ addFileSelectLayout(mCaCert);
+ addFileSelectLayout(mClientCert);
+ addFileSelectLayout(mClientKey);
+ addFileSelectLayout(mTlsFile);
+ addFileSelectLayout(mpkcs12);
+
+ loadPreferences();
+
+ mType.setOnItemSelectedListener(this);
+
+ mShowAdvanced.setOnCheckedChangeListener(this);
+ mUseTlsAuth.setOnCheckedChangeListener(this);
+
+
+ findViewById(R.id.select_keystore_button).setOnClickListener(this);
+ findViewById(R.id.about).setOnClickListener(this);
+ findViewById(R.id.connect).setOnClickListener(this);
+
+ if (mHandler == null) {
+ mHandler = new Handler(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
+ findViewById(R.id.pkcs12).setVisibility(View.GONE);
+ findViewById(R.id.certs).setVisibility(View.GONE);
+ findViewById(R.id.commonsecret).setVisibility(View.GONE);
+ findViewById(R.id.keystore).setVisibility(View.GONE);
+
+ switch(type) {
+ case VpnProfile.TYPE_CERTIFICATES:
+ findViewById(R.id.certs).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_PKCS12:
+ findViewById(R.id.pkcs12).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.COMMON_SECRET:
+ findViewById(R.id.commonsecret).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_KEYSTORE:
+ findViewById(R.id.keystore).setVisibility(View.VISIBLE);
+ break;
+ }
+
+
+ }
+
+ private void loadPreferences() {
+ SharedPreferences settings = getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE);
+
+ mClientCert.setData(settings.getString("clientcert", ""));
+ mClientKey.setData(settings.getString("clientkey", ""));
+ mCaCert.setData(settings.getString("ca", ""));
+
+ mUseLzo.setChecked(settings.getBoolean("useLzo", true));
+ mServerPort.setText(settings.getString("port", "1194"));
+ mServerAddress.setText(settings.getString("server", "openvpn.blinkt.de"));
+ mTcpUdp.setChecked(settings.getBoolean("udp", true));
+ mType.setSelection(settings.getInt("type", VpnProfile.TYPE_PKCS12));
+ mpkcs12.setData(settings.getString("pkcs12file", ""));
+ mPKCS12Password.setText(settings.getString("pkcs12password", ""));
+ certalias = settings.getString("alias", null);
+ mUseTlsAuth.setChecked(settings.getBoolean("tlsauth", false));
+ onCheckedChanged(mUseTlsAuth,mUseTlsAuth.isChecked());
+
+ mTlsFile.setData(settings.getString("tlsfile",""));
+ mTLSDirection.setSelection(settings.getInt("tls-direction", 2)); // Unspecified
+ setAlias();
+
+ }
+
+ private void savePreferences() {
+ // We need an Editor object to make preference changes.
+ // All objects are from android.context.Context
+ SharedPreferences settings = getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE);
+ SharedPreferences.Editor editor = settings.edit();
+
+
+ editor.putString("ca" , mCaCert.getData());
+ editor.putString("clientcert", mClientCert.getData());
+ editor.putString("clientkey", mClientKey.getData());
+
+ editor.putBoolean("useLzo",mUseLzo.isChecked());
+ editor.putString("port", mServerPort.getText().toString());
+ editor.putString("server", mServerAddress.getText().toString());
+ editor.putBoolean("udp", mTcpUdp.isChecked());
+
+ editor.putInt("type",mType.getSelectedItemPosition());
+ editor.putString("pkcs12file", mpkcs12.getData());
+ editor.putString("pkcs12password", mPKCS12Password.getText().toString());
+ editor.putString("alias", certalias);
+ editor.putBoolean("tlsauth", mUseTlsAuth.isChecked());
+ editor.putString("tlsfile", mTlsFile.getData());
+ editor.putInt("tls-direction", mTLSDirection.getSelectedItemPosition());
+ // Commit the edits!
+ editor.commit();
+ }
+
+
+ private void setAlias() {
+ if(certalias == null) {
+ mAliasName.setText(R.string.client_no_certificate);
+ } else {
+ mAliasName.setText(certalias);
+ }
+ }
+
+ public void showCertDialog () {
+ KeyChain.choosePrivateKeyAlias(this,
+ new KeyChainAliasCallback() {
+
+ public void alias(String alias) {
+ // Credential alias selected. Remember the alias selection for future use.
+ certalias=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
+ }
+
+ private String getRandomPW() {
+ String pw= "";
+ // Put enough digits togher to make a password :)
+ Random r = new Random();
+ for(int i=0;i < 4;i++) {
+ pw += new Integer(r.nextInt(1000)).toString();
+ }
+
+ return pw;
+
+ }
+
+ private String savePKCS12() {
+ Context context = getBaseContext();
+ PrivateKey privateKey = null;
+ X509Certificate[] cachain=null;
+ try {
+ privateKey = KeyChain.getPrivateKey(context,certalias);
+ cachain = KeyChain.getCertificateChain(context, certalias);
+
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(null, null);
+ ks.setKeyEntry("usercert", privateKey, null, cachain);
+ String mypw = getRandomPW();
+ FileOutputStream fout = new FileOutputStream(getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGPKCS12);
+ ks.store(fout,mypw.toCharArray());
+ fout.flush(); fout.close();
+ return mypw;
+ } catch (KeyChainException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (CertificateException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return "ERROR";
+
+ }
+
+ public void testGetallCerts() throws NoSuchAlgorithmException, KeyStoreException {
+ TrustManagerFactory tmf = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+
+ tmf.init((KeyStore) null);
+ X509TrustManager xtm = (X509TrustManager) tmf.getTrustManagers()[0];
+
+ X509Certificate[] foo = xtm.getAcceptedIssuers();
+ for (X509Certificate cert : xtm.getAcceptedIssuers()) {
+ String certStr = "S:" + cert.getSubjectDN().getName() + "\nI:"
+ + cert.getIssuerDN().getName();
+ Log.d(TAG, certStr);
+ }
+ System.out.println(foo);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if(v == findViewById(R.id.connect)) {
+ Intent intent = VpnService.prepare(this);
+ if (intent != null) {
+ startActivityForResult(intent, 0);
+ } else {
+ onActivityResult(START_OPENVPN, RESULT_OK, null);
+ }
+ } else if (v == findViewById(R.id.about)) {
+ Intent intent = new Intent(getBaseContext(),AboutActivity.class);
+ startActivity(intent);
+ } else if (v == findViewById(R.id.select_keystore_button)) {
+ showCertDialog();
+ }
+ }
+
+ void startOpenVpn() {
+ String prefix = getPackageName();
+ writeConfigFile();
+
+ Intent intent = new Intent(this, OpenVpnService.class)
+ .putExtra(prefix + ".ARGV" , buildOpenvpnArgv());
+
+ if(mType.getSelectedItemPosition()== VpnProfile.TYPE_PKCS12){
+ intent.putExtra(prefix + ".PKCS12PASS",
+ mPKCS12Password.getText().toString());
+ }
+
+ if(mType.getSelectedItemPosition() == VpnProfile.TYPE_KEYSTORE) {
+ String pkcs12pw = savePKCS12();
+ intent.putExtra(prefix + ".PKCS12PASS", pkcs12pw);
+ }
+
+
+ startService(intent);
+ Intent startLW = new Intent(getBaseContext(),LogWindow.class);
+ startActivity(startLW);
+ }
+
+ /* (non-Javadoc)
+ * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
+ */
+ @Override
+ protected void onActivityResult(int request, int result, Intent data) {
+ if (request== START_OPENVPN) {
+ if (result == RESULT_OK) {
+ // It always crashes and never saves ;)
+ savePreferences();
+ new startOpenVpnThread().start();
+ }
+
+ } else if (request >= CHOOSE_FILE_OFFSET) {
+ String filepath = data.getStringExtra(FileDialog.RESULT_PATH);
+ FileSelectLayout fsl = fileselects.get(request);
+ fsl.setData(filepath);
+ }
+ savePreferences();
+ }
+
+
+
+ private class startOpenVpnThread extends Thread {
+
+ @Override
+ public void run() {
+ startOpenVpn();
+ }
+
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ setAlias();
+ return true;
+ }
+
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ int visibility;
+ if(isChecked)
+ visibility =View.VISIBLE;
+ else
+ visibility =View.GONE;
+
+ if(buttonView==mShowAdvanced) {
+ findViewById(R.id.advanced_options).setVisibility(visibility);
+ } else if (buttonView == mUseTlsAuth) {
+ findViewById(R.id.tlsauth_options).setVisibility(visibility);
+ }
+ }
+}
diff --git a/src/de/blinkt/openvpn/OpenVpnService.java b/src/de/blinkt/openvpn/OpenVpnService.java
new file mode 100644
index 00000000..e8174bcb
--- /dev/null
+++ b/src/de/blinkt/openvpn/OpenVpnService.java
@@ -0,0 +1,203 @@
+/*
+ * 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.net.UnknownHostException;
+import java.util.Arrays;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+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;
+
+ private Handler mHandler;
+ private Thread mThread;
+
+ private ParcelFileDescriptor mInterface;
+
+ @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 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();
+
+ if(intent.hasExtra(prefix +".PKCS12PASS"))
+ {
+ try {
+ String pkcs12password = intent.getStringExtra(prefix +".PKCS12PASS");
+ Thread.sleep(3000);
+
+ managmentCommand("password 'Private Key' " + pkcs12password + "\n");
+ } catch (InterruptedException e) {
+ }
+
+ }
+
+
+
+ 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 ParcelFileDescriptor openTun(String localip) {
+ // FIXME: hardcoded assumptions
+ Builder builder = new Builder();
+ builder.addRoute("0.0.0.0", 0);
+ builder.addAddress(localip, 24 );
+ builder.addDnsServer("131.234.137.23");
+ builder.addSearchDomain("blinkt.de");
+ builder.setSession("OpenVPN - " + localip);
+ Intent intent = new Intent(getBaseContext(),LogWindow.class);
+ PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+ builder.setConfigureIntent(startLW);
+ mInterface = builder.establish();
+ return mInterface;
+
+ }
+}
diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java
new file mode 100644
index 00000000..d01b1104
--- /dev/null
+++ b/src/de/blinkt/openvpn/VpnProfile.java
@@ -0,0 +1,8 @@
+package de.blinkt.openvpn;
+
+public class VpnProfile {
+ static final int TYPE_CERTIFICATES=0;
+ static final int TYPE_PKCS12=1;
+ static final int COMMON_SECRET=3;
+ static final int TYPE_KEYSTORE=2;
+}