From 5a65e0c5e80d147909acffa14b04d3c99d48de1a Mon Sep 17 00:00:00 2001
From: Arne Schwabe <arne@rfc2549.org>
Date: Sat, 12 May 2012 21:18:37 +0200
Subject: Almost working configuration import

---
 src/de/blinkt/openvpn/ConfigConverter.java         | 195 ++++++++++++++++++
 src/de/blinkt/openvpn/ConfigParser.java            | 227 ++++++++++++++++++---
 src/de/blinkt/openvpn/FileSelect.java              |  28 ++-
 src/de/blinkt/openvpn/FileSelectLayout.java        |   4 +-
 src/de/blinkt/openvpn/FileSelectionFragment.java   |  10 +-
 src/de/blinkt/openvpn/Settings_Authentication.java |   2 +-
 src/de/blinkt/openvpn/VPNProfileList.java          |  41 +++-
 src/de/blinkt/openvpn/VpnProfile.java              |  19 +-
 8 files changed, 481 insertions(+), 45 deletions(-)
 create mode 100644 src/de/blinkt/openvpn/ConfigConverter.java

(limited to 'src/de/blinkt')

diff --git a/src/de/blinkt/openvpn/ConfigConverter.java b/src/de/blinkt/openvpn/ConfigConverter.java
new file mode 100644
index 00000000..1973e0ac
--- /dev/null
+++ b/src/de/blinkt/openvpn/ConfigConverter.java
@@ -0,0 +1,195 @@
+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.Activity;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.ArrayAdapter;
+import android.widget.Toast;
+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<String> mArrayAdapter;
+	
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		Toast.makeText(this, "Got called!", Toast.LENGTH_LONG).show();
+	}
+
+	
+	@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");
+			}
+			Intent result = new Intent();
+			ProfileManager vpl = ProfileManager.getInstance(this);
+			vpl.addProfile(mResult);
+			result.putExtra(VpnProfile.EXTRA_PROFILEUUID,mResult.getUUID().toString());
+			setResult(Activity.RESULT_OK, result);
+			finish();
+		}
+
+		return super.onOptionsItemSelected(item);
+
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		MenuInflater inflater = getMenuInflater();
+		inflater.inflate(R.menu.import_menu, menu);
+		return true;
+	}
+
+	
+	private String embedFile(String filename)
+	{
+		if(filename == null || filename.equals(""))
+			return null;
+		// Already embedded, nothing to do
+		if(filename.startsWith(VpnProfile.INLINE_TAG))
+			return filename;
+		
+		// Try diffent path relative to /mnt/sdcard
+		File sdcard = Environment.getExternalStorageDirectory();
+		File root = new File("/");
+		File[] dirlist = {root, sdcard};
+		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
+				String filedata = VpnProfile.INLINE_TAG;
+				byte[] buf =new byte[2048];
+				
+				log(R.string.trying_to_read, possibleFile.getAbsolutePath());
+				try {
+					FileInputStream fis = new FileInputStream(possibleFile);
+					int len = fis.read(buf);
+					while( len > 0){
+						filedata += new String(buf,0,len);
+						len = fis.read(buf);
+					}
+					return filedata;
+				} catch (FileNotFoundException e) {
+					log(e.getLocalizedMessage());
+				} catch (IOException e) {
+					log(e.getLocalizedMessage());
+				}	
+						
+			
+			}
+		}
+		log(R.string.import_could_not_open,filename);
+		return null;
+	}
+	
+	void embedFiles() {
+		// This where I would like to have a c++ style
+		// void embedFile(std::string & option)
+		
+		mResult.mCaFilename = embedFile(mResult.mCaFilename);
+		mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename);
+		mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename);
+		mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename);
+	}
+	
+	
+	@Override
+	protected void onStart() {
+		super.onStart();
+
+		mArrayAdapter = new ArrayAdapter<String>(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.importing_config,data.toString());
+				try {
+					InputStream is = getContentResolver().openInputStream(data);
+					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 {
+			cp.parseConfig(is);
+			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);
+			log(mResult.mCustomConfigOptions);
+		}
+		
+		if(mResult.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) {
+			log(R.string.import_pkcs12_to_keystore);
+		}
+		
+	}
+
+
+	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
index 97ca6396..8fde12b8 100644
--- a/src/de/blinkt/openvpn/ConfigParser.java
+++ b/src/de/blinkt/openvpn/ConfigParser.java
@@ -1,9 +1,11 @@
 package de.blinkt.openvpn;
 
 import java.io.BufferedReader;
-import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.HashMap;
+import java.util.Map.Entry;
 import java.util.Vector;
 
 //! Openvpn Config FIle Parser, probably not 100% accurate but close enough
@@ -16,12 +18,13 @@ public class ConfigParser {
 
 
 	private HashMap<String,Vector<String>> options = new HashMap<String, Vector<String>>();
-	private void parseConfig(String filename) throws IOException, ConfigParseError {
+	public void parseConfig(InputStream inputStream) throws IOException, ConfigParseError {
 
 
-		FileReader fr = new FileReader(filename);
+		InputStreamReader fr = new InputStreamReader(inputStream);
 		BufferedReader br =new BufferedReader(fr);
 
+		@SuppressWarnings("unused")
 		int lineno=0;
 
 		while (true){
@@ -29,8 +32,6 @@ public class ConfigParser {
 			if(line==null)
 				break;
 			lineno++;
-			System.out.print("LINE:");
-			System.out.println(line);
 			Vector<String> args = parseline(line);
 			if(args.size() ==0)
 				continue;
@@ -51,7 +52,7 @@ public class ConfigParser {
 		// CHeck for <foo>
 		if(arg0.startsWith("<") && arg0.endsWith(">")) {
 			String argname = arg0.substring(1, arg0.length()-1);
-			String inlinefile = "";
+			String inlinefile = VpnProfile.INLINE_TAG;
 
 			String endtag = String.format("</%s>",argname);
 			do {
@@ -192,37 +193,217 @@ public class ConfigParser {
 		return parameters;
 	}
 
-	void convertProfile() throws ConfigParseError{
-		VpnProfile newprofile = new VpnProfile("converted Profile");
+
+	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",
+			"auth-user-pass-verify"
+	};
+	// Missing
+	// proto tcp-client|udp
+
+	VpnProfile convertProfile() throws ConfigParseError{
+		VpnProfile np = new VpnProfile("converted Profile");
 		// Pull, client, tls-client
-		
+		np.clearDefaults();
+
 		if(options.containsKey("client") || options.containsKey("pull")) {
-			newprofile.mUsePull=true;
+			np.mUsePull=true;
 			options.remove("pull");
 			options.remove("client");
 		}
-		
-		if(options.containsKey("secret")){
-			newprofile.mAuthenticationType=VpnProfile.TYPE_STATICKEYS;
-			options.remove("secret");
+
+		Vector<String> secret = getOption("secret", 1, 2);
+		if(secret!=null) 
+		{
+			np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS;
+			np.mUseTLSAuth=true;
+			np.mTLSAuthFilename=secret.get(1);
+			if(secret.size()==3)
+				np.mTLSAuthDirection=secret.get(2);
 		}
-		
+
+		Vector<String> tlsauth = getOption("tls-auth", 1, 2);
+		if(tlsauth!=null) 
+		{
+			np.mUseTLSAuth=true;
+			np.mTLSAuthFilename=tlsauth.get(1);
+			if(tlsauth.size()==3)
+				np.mTLSAuthDirection=tlsauth.get(2);
+		}
+
+		Vector<String> direction = getOption("key-direction", 1, 1);
+		if(direction!=null)
+			np.mTLSAuthDirection=direction.get(1);
+
 		if(options.containsKey("redirect-gateway")) {
 			options.remove("redirect-gateway");
-			newprofile.mUseDefaultRoute=true;
+			np.mUseDefaultRoute=true;
 		} else {
-			newprofile.mUseDefaultRoute=true;
+			np.mUseDefaultRoute=true;
 		}
-		
-		Vector<String> mode = options.get("mode");
+
+		Vector<String> dev =getOption("dev",1,1);
+		Vector<String> 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<String> mode =getOption("mode",1,1);
 		if (mode != null){
-			options.remove("mode");
-			if(mode.size() != 2) 
-				throw new ConfigParseError("--mode has more than one parameter");
 			if(!mode.get(1).equals("p2p"))
-				throw new ConfigParseError("Invalid mode for --mode specified");
+				throw new ConfigParseError("Invalid mode for --mode specified, need p2p");
+		}
+
+		// Parse remote config
+		Vector<String> remote = getOption("remote",1,3);
+		if(remote != null){
+			switch (remote.size()) {
+			case 4:
+				String proto = remote.get(3);
+				if(proto.equals("udp"))
+					np.mUseUdp=true;
+				else if (proto.equals("tcp"))
+					np.mUseUdp=false;
+				else
+					throw new ConfigParseError("remote protocol must be tcp or udp");
+			case 3:
+				np.mServerPort = remote.get(2);
+			case 2:
+				np.mServerName = remote.get(1);
+			}
 		}
 		
+		Vector<String> proto = getOption("proto, ", 1,1);
+		if(proto!=null){
+			if(proto.get(1).equals("udp"))
+				np.mUseUdp=true;
+			else if (proto.get(1).equals("tcp-client"))
+				np.mUseUdp=false;
+			else 
+				throw new ConfigParseError("Unsupported option to --proto " + proto.get(1));
+					
+		}
+		
+		Vector<String> dhcpoption = getOption("dhcp-options", 1, 3);
+		if(dhcpoption!=null) {
+			String type=dhcpoption.get(1);
+
+		}
+		
+		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<String> cipher = getOption("cipher", 1, 1);
+		if(cipher!=null)
+			np.mCipher= cipher.get(1);
+
+		Vector<String> ca = getOption("ca",1,1);
+		if(ca!=null){
+			np.mCaFilename = ca.get(1);
+		}
+
+		Vector<String> cert = getOption("cert",1,1);
+		if(cert!=null){
+			np.mClientCertFilename = cert.get(1);
+			np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES;
+		}
+		Vector<String> key= getOption("key",1,1);
+		if(key!=null)
+			np.mClientKeyFilename=key.get(1);
+
+		Vector<String> pkcs12 = getOption("pkcs12",1,1);
+		if(pkcs12!=null) {
+			np.mPKCS12Filename = pkcs12.get(1);
+			np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE;
+		}
+
+		Vector<String> tlsremote = getOption("tls-remote",1,1);
+		if(tlsremote!=null){
+			np.mRemoteCN = tlsremote.get(1);
+			np.mCheckRemoteCN=true;
+		} 
+		
+		Vector<String> verb = getOption("verb",1,1);
+		if(verb!=null){
+			np.mVerb=verb.get(1);
+		}
+
+		// Check the other options
+
+		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 but not parsed:\n";
+			for(Entry<String, Vector<String>> option:options.entrySet()) {
+				for (String arg : option.getValue()) {
+					custom+= arg + " ";
+				}
+				custom+="\n";
+			}
+			np.mCustomConfigOptions = custom;
+			np.mUseCustomConfig=true;
+
+		}
+
+
+		fixup(np);
+
+		return np;
+	}
+
+	private void fixup(VpnProfile np) {
+		if(np.mRemoteCN.equals(np.mServerName)) {
+			np.mRemoteCN="";
+		}
+	}
+
+	private Vector<String> getOption(String option, int minarg, int maxarg) throws ConfigParseError {
+		Vector<String> args = options.get(option);
+		if(args==null)
+			return null;
+		if(args.size()< (minarg+1) || args.size() > maxarg+1) {
+			String err = String.format("Option %s has %d parameters, expected between %d and %d",
+					option,args.size()-1,minarg,maxarg );
+			throw new ConfigParseError(err);
+		}
+		options.remove(option);
+		return args;
 	}
 
 }
diff --git a/src/de/blinkt/openvpn/FileSelect.java b/src/de/blinkt/openvpn/FileSelect.java
index 3cc060c6..12a3ae01 100644
--- a/src/de/blinkt/openvpn/FileSelect.java
+++ b/src/de/blinkt/openvpn/FileSelect.java
@@ -13,19 +13,19 @@ import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 
 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 INLINE_TAG = "[[INLINE]]";
+	public static final String NO_INLINE_SELECTION = "de.blinkt.openvpn.NO_INLINE_SELECTION";
 	private FileSelectionFragment mFSFragment;
 	private InlineFileTab mInlineFragment;
 	private String mData;
 	private Tab inlineFileTab;
 	private Tab fileExplorerTab;
+	private boolean mNoInline;
 
 	public void onCreate(Bundle savedInstanceState)
 	{
@@ -33,6 +33,10 @@ public class FileSelect extends Activity {
 		setContentView(R.layout.file_dialog);
 
 		mData = getIntent().getStringExtra(START_DATA);
+		if(mData==null)
+			mData="/sdcard";
+		
+		mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false);
 		
 		ActionBar bar = getActionBar();
 		bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
@@ -40,13 +44,15 @@ public class FileSelect extends Activity {
 		inlineFileTab = bar.newTab().setText(R.string.inline_file_tab); 
 
 		mFSFragment = new FileSelectionFragment();
-		mInlineFragment = new InlineFileTab();
+		mFSFragment.setNoInLine();
 		fileExplorerTab.setTabListener(new MyTabsListener<FileSelectionFragment>(this, mFSFragment));
-		inlineFileTab.setTabListener(new MyTabsListener<InlineFileTab>(this, mInlineFragment));
-
 		bar.addTab(fileExplorerTab);
-		bar.addTab(inlineFileTab);
 		
+		if(!mNoInline) {
+			mInlineFragment = new InlineFileTab();
+			inlineFileTab.setTabListener(new MyTabsListener<InlineFileTab>(this, mInlineFragment));
+			bar.addTab(inlineFileTab);
+			}
 
 		
 	}
@@ -88,7 +94,7 @@ public class FileSelect extends Activity {
 		Exception fe = null;
 		try {
 			FileInputStream fis = new FileInputStream(ifile);
-			String data =INLINE_TAG;
+			String data =VpnProfile.INLINE_TAG;
 
 			byte buf[] =new byte[16384];
 			int len=fis.read(buf);
@@ -116,21 +122,21 @@ public class FileSelect extends Activity {
 
 	public void setFile(String path) {
 		Intent intent = new Intent();
-		intent.putExtra(RESULT_DATA, mData);
+		intent.putExtra(RESULT_DATA, path);
 		setResult(Activity.RESULT_OK,intent);
 		finish();		
 	}
 
 	public String getSelectPath() {
-		if(mData.startsWith(INLINE_TAG))
+		if(mData.startsWith(VpnProfile.INLINE_TAG))
 			return mData;
 		else
 			return "/mnt/sdcard";
 	}
 
 	public CharSequence getInlineData() {
-		if(mData.startsWith(INLINE_TAG))
-			return mData.substring(INLINE_TAG.length());
+		if(mData.startsWith(VpnProfile.INLINE_TAG))
+			return mData.substring(VpnProfile.INLINE_TAG.length());
 		else
 			return "";
 	}
diff --git a/src/de/blinkt/openvpn/FileSelectLayout.java b/src/de/blinkt/openvpn/FileSelectLayout.java
index bbaf7778..0be099af 100644
--- a/src/de/blinkt/openvpn/FileSelectLayout.java
+++ b/src/de/blinkt/openvpn/FileSelectLayout.java
@@ -57,7 +57,9 @@ public class FileSelectLayout extends LinearLayout implements OnClickListener {
 
 	public void setData(String data) {
 		mData = data;
-		if(mData.startsWith(FileSelect.INLINE_TAG))
+		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);
diff --git a/src/de/blinkt/openvpn/FileSelectionFragment.java b/src/de/blinkt/openvpn/FileSelectionFragment.java
index 31390280..41c7a1eb 100644
--- a/src/de/blinkt/openvpn/FileSelectionFragment.java
+++ b/src/de/blinkt/openvpn/FileSelectionFragment.java
@@ -51,6 +51,7 @@ public class FileSelectionFragment extends ListFragment {
 	private HashMap<String, Integer> lastPositions = new HashMap<String, Integer>();
 	private String mStartPath;
 	private Button importFile;
+	private boolean mHideImport=false;
 
 	
 	@Override
@@ -73,6 +74,7 @@ public class FileSelectionFragment extends ListFragment {
 			}
 		});
 
+		
 		importFile = (Button) v.findViewById(R.id.importfile);
 		importFile.setEnabled(false);
 		importFile.setOnClickListener(new OnClickListener() {
@@ -83,7 +85,9 @@ public class FileSelectionFragment extends ListFragment {
 			}
 		});
 
-
+		if(mHideImport== true) {
+			importFile.setVisibility(View.GONE);
+		}
 
 		
 		return v;
@@ -241,4 +245,8 @@ public class FileSelectionFragment extends ListFragment {
 		}
 	}
 
+	public void setNoInLine() {
+		mHideImport=true;
+	}
+
 }
diff --git a/src/de/blinkt/openvpn/Settings_Authentication.java b/src/de/blinkt/openvpn/Settings_Authentication.java
index 5849923d..e8740b5d 100644
--- a/src/de/blinkt/openvpn/Settings_Authentication.java
+++ b/src/de/blinkt/openvpn/Settings_Authentication.java
@@ -131,7 +131,7 @@ public class Settings_Authentication extends PreferenceFragment implements OnPre
 
 	private void setTlsAuthSummary(String result) {
 		if(result==null) result = getString(R.string.no_certificate);
-		if(result.startsWith(FileSelect.INLINE_TAG))
+		if(result.startsWith(VpnProfile.INLINE_TAG))
 			   mTLSAuthFile.setSummary(R.string.inline_file_data);
 		   else
 			   mTLSAuthFile.setSummary(result);
diff --git a/src/de/blinkt/openvpn/VPNProfileList.java b/src/de/blinkt/openvpn/VPNProfileList.java
index a578251c..a14c835e 100644
--- a/src/de/blinkt/openvpn/VPNProfileList.java
+++ b/src/de/blinkt/openvpn/VPNProfileList.java
@@ -1,11 +1,13 @@
 package de.blinkt.openvpn;
 
+import de.blinkt.openvpn.FileSelect.MyTabsListener;
 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.net.Uri;
 import android.os.Bundle;
 import android.view.ActionMode;
 import android.view.Menu;
@@ -56,11 +58,8 @@ public class VPNProfileList extends ListFragment {
 				}
 			});
 				
-			
 			return v;
 		}
-		
-		
 	}
 	
 	
@@ -68,6 +67,13 @@ public class VPNProfileList extends ListFragment {
 	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;
+
+	
+
 
 	private ArrayAdapter<VpnProfile> mArrayadapter;
 
@@ -104,6 +110,13 @@ public class VPNProfileList extends ListFragment {
 		.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(android.R.drawable.ic_menu_myplaces)
+		.setAlphabeticShortcut('a')
+		.setTitleCondensed(getActivity().getString(R.string.menu_import_short))
+		.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
+				| MenuItem.SHOW_AS_ACTION_WITH_TEXT );
 	}
 
 
@@ -113,6 +126,11 @@ public class VPNProfileList extends ListFragment {
 		if (itemId == MENU_ADD_PROFILE) {
 			onAddProfileClicked();
 			return true;
+		} else if (itemId == MENU_IMPORT_PROFILE) {
+			Intent intent = new Intent(getActivity(),FileSelect.class);
+			intent.putExtra(FileSelect.NO_INLINE_SELECTION, true);
+			startActivityForResult(intent, SELECT_PROFILE);
+			return true;
 		} else {
 			return super.onOptionsItemSelected(item);
 		}
@@ -198,13 +216,26 @@ public class VPNProfileList extends ListFragment {
 	@Override
 	public void onActivityResult(int requestCode, int resultCode, Intent data) {
 		super.onActivityResult(requestCode, resultCode, data);
-		if (requestCode == START_VPN_CONFIG && resultCode == Activity.RESULT_OK) {
-			String configuredVPN = data.getStringExtra(getActivity().getPackageName() + ".profileUUID");
+		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
 
+		} 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));
 		}
 
 	}
diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java
index 639619ff..b7297e89 100644
--- a/src/de/blinkt/openvpn/VpnProfile.java
+++ b/src/de/blinkt/openvpn/VpnProfile.java
@@ -38,6 +38,8 @@ public class VpnProfile implements  Serializable{
 	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";
 
 
 
@@ -90,6 +92,17 @@ public class VpnProfile implements  Serializable{
 	public String mCustomConfigOptions="";
 	public String mVerb="1";
 	public String mCipher="";
+	public static final String INLINE_TAG = "[[INLINE]]";
+
+	
+
+	public void clearDefaults() {
+		mServerName="unkown";
+		mUsePull=false;
+		mUseLzo=false;
+		mUseDefaultRoute=false;
+		mExpectTLSCert=false;
+	}
 
 
 	public static String openVpnEscape(String unescaped) {
@@ -294,11 +307,11 @@ public class VpnProfile implements  Serializable{
 
 	//! Put inline data inline and other data as normal escaped filename
 	private String insertFileData(String cfgentry, String filedata) {
-		if(filedata.startsWith(FileSelect.INLINE_TAG)){
-			String datawoheader = filedata.substring(FileSelect.INLINE_TAG.length());
+		if(filedata.startsWith(VpnProfile.INLINE_TAG)){
+			String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length());
 			return String.format("<%s>\n%s\n</%s>\n",cfgentry,datawoheader,cfgentry);
 		} else {
-			return String.format("%s %s",cfgentry,openVpnEscape(filedata));
+			return String.format("%s %s\n",cfgentry,openVpnEscape(filedata));
 		}
 	}
 
-- 
cgit v1.2.3