• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.vpn2;
18 
19 import com.android.settings.R;
20 
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.net.IConnectivityManager;
24 import android.net.LinkProperties;
25 import android.net.RouteInfo;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.ServiceManager;
30 import android.preference.Preference;
31 import android.preference.PreferenceGroup;
32 import android.security.Credentials;
33 import android.security.KeyStore;
34 import android.util.Log;
35 import android.view.ContextMenu;
36 import android.view.ContextMenu.ContextMenuInfo;
37 import android.view.Menu;
38 import android.view.MenuItem;
39 import android.view.View;
40 import android.widget.AdapterView.AdapterContextMenuInfo;
41 
42 import com.android.internal.net.LegacyVpnInfo;
43 import com.android.internal.net.VpnConfig;
44 import com.android.settings.SettingsPreferenceFragment;
45 
46 import java.net.Inet4Address;
47 import java.nio.charset.Charsets;
48 import java.util.Arrays;
49 import java.util.HashMap;
50 
51 public class VpnSettings extends SettingsPreferenceFragment implements
52         Handler.Callback, Preference.OnPreferenceClickListener,
53         DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
54 
55     private static final String TAG = "VpnSettings";
56 
57     private final IConnectivityManager mService = IConnectivityManager.Stub
58             .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
59     private final KeyStore mKeyStore = KeyStore.getInstance();
60     private boolean mUnlocking = false;
61 
62     private HashMap<String, VpnPreference> mPreferences;
63     private VpnDialog mDialog;
64 
65     private Handler mUpdater;
66     private LegacyVpnInfo mInfo;
67 
68     // The key of the profile for the current ContextMenu.
69     private String mSelectedKey;
70 
71     @Override
onCreate(Bundle savedState)72     public void onCreate(Bundle savedState) {
73         super.onCreate(savedState);
74         addPreferencesFromResource(R.xml.vpn_settings2);
75         getPreferenceScreen().setOrderingAsAdded(false);
76 
77         if (savedState != null) {
78             VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
79                     savedState.getByteArray("VpnProfile"));
80             if (profile != null) {
81                 mDialog = new VpnDialog(getActivity(), this, profile,
82                         savedState.getBoolean("VpnEditing"));
83             }
84         }
85     }
86 
87     @Override
onSaveInstanceState(Bundle savedState)88     public void onSaveInstanceState(Bundle savedState) {
89         // We do not save view hierarchy, as they are just profiles.
90         if (mDialog != null) {
91             VpnProfile profile = mDialog.getProfile();
92             savedState.putString("VpnKey", profile.key);
93             savedState.putByteArray("VpnProfile", profile.encode());
94             savedState.putBoolean("VpnEditing", mDialog.isEditing());
95         }
96         // else?
97     }
98 
99     @Override
onResume()100     public void onResume() {
101         super.onResume();
102 
103         // Check KeyStore here, so others do not need to deal with it.
104         if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
105             if (!mUnlocking) {
106                 // Let us unlock KeyStore. See you later!
107                 Credentials.getInstance().unlock(getActivity());
108             } else {
109                 // We already tried, but it is still not working!
110                 finishFragment();
111             }
112             mUnlocking = !mUnlocking;
113             return;
114         }
115 
116         // Now KeyStore is always unlocked. Reset the flag.
117         mUnlocking = false;
118 
119         // Currently we are the only user of profiles in KeyStore.
120         // Assuming KeyStore and KeyGuard do the right thing, we can
121         // safely cache profiles in the memory.
122         if (mPreferences == null) {
123             mPreferences = new HashMap<String, VpnPreference>();
124             PreferenceGroup group = getPreferenceScreen();
125 
126             String[] keys = mKeyStore.saw(Credentials.VPN);
127             if (keys != null && keys.length > 0) {
128                 Context context = getActivity();
129 
130                 for (String key : keys) {
131                     VpnProfile profile = VpnProfile.decode(key,
132                             mKeyStore.get(Credentials.VPN + key));
133                     if (profile == null) {
134                         Log.w(TAG, "bad profile: key = " + key);
135                         mKeyStore.delete(Credentials.VPN + key);
136                     } else {
137                         VpnPreference preference = new VpnPreference(context, profile);
138                         mPreferences.put(key, preference);
139                         group.addPreference(preference);
140                     }
141                 }
142             }
143             group.findPreference("add_network").setOnPreferenceClickListener(this);
144         }
145 
146         // Show the dialog if there is one.
147         if (mDialog != null) {
148             mDialog.setOnDismissListener(this);
149             mDialog.show();
150         }
151 
152         // Start monitoring.
153         if (mUpdater == null) {
154             mUpdater = new Handler(this);
155         }
156         mUpdater.sendEmptyMessage(0);
157 
158         // Register for context menu. Hmmm, getListView() is hidden?
159         registerForContextMenu(getListView());
160     }
161 
162     @Override
onPause()163     public void onPause() {
164         super.onPause();
165 
166         // Hide the dialog if there is one.
167         if (mDialog != null) {
168             mDialog.setOnDismissListener(null);
169             mDialog.dismiss();
170         }
171 
172         // Unregister for context menu.
173         if (getView() != null) {
174             unregisterForContextMenu(getListView());
175         }
176     }
177 
178     @Override
onDismiss(DialogInterface dialog)179     public void onDismiss(DialogInterface dialog) {
180         // Here is the exit of a dialog.
181         mDialog = null;
182     }
183 
184     @Override
onClick(DialogInterface dialog, int button)185     public void onClick(DialogInterface dialog, int button) {
186         if (button == DialogInterface.BUTTON_POSITIVE) {
187             // Always save the profile.
188             VpnProfile profile = mDialog.getProfile();
189             mKeyStore.put(Credentials.VPN + profile.key, profile.encode());
190 
191             // Update the preference.
192             VpnPreference preference = mPreferences.get(profile.key);
193             if (preference != null) {
194                 disconnect(profile.key);
195                 preference.update(profile);
196             } else {
197                 preference = new VpnPreference(getActivity(), profile);
198                 mPreferences.put(profile.key, preference);
199                 getPreferenceScreen().addPreference(preference);
200             }
201 
202             // If we are not editing, connect!
203             if (!mDialog.isEditing()) {
204                 try {
205                     connect(profile);
206                 } catch (Exception e) {
207                     Log.e(TAG, "connect", e);
208                 }
209             }
210         }
211     }
212 
213     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)214     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
215         if (mDialog != null) {
216             Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
217             return;
218         }
219 
220         if (info instanceof AdapterContextMenuInfo) {
221             Preference preference = (Preference) getListView().getItemAtPosition(
222                     ((AdapterContextMenuInfo) info).position);
223             if (preference instanceof VpnPreference) {
224                 VpnProfile profile = ((VpnPreference) preference).getProfile();
225                 mSelectedKey = profile.key;
226                 menu.setHeaderTitle(profile.name);
227                 menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
228                 menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
229             }
230         }
231     }
232 
233     @Override
onContextItemSelected(MenuItem item)234     public boolean onContextItemSelected(MenuItem item) {
235         if (mDialog != null) {
236             Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
237             return false;
238         }
239 
240         VpnPreference preference = mPreferences.get(mSelectedKey);
241         if (preference == null) {
242             Log.v(TAG, "onContextItemSelected() is called but no preference is found");
243             return false;
244         }
245 
246         switch (item.getItemId()) {
247             case R.string.vpn_menu_edit:
248                 mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
249                 mDialog.setOnDismissListener(this);
250                 mDialog.show();
251                 return true;
252             case R.string.vpn_menu_delete:
253                 disconnect(mSelectedKey);
254                 getPreferenceScreen().removePreference(preference);
255                 mPreferences.remove(mSelectedKey);
256                 mKeyStore.delete(Credentials.VPN + mSelectedKey);
257                 return true;
258         }
259         return false;
260     }
261 
262     @Override
onPreferenceClick(Preference preference)263     public boolean onPreferenceClick(Preference preference) {
264         if (mDialog != null) {
265             Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
266             return true;
267         }
268 
269         if (preference instanceof VpnPreference) {
270             VpnProfile profile = ((VpnPreference) preference).getProfile();
271             if (mInfo != null && profile.key.equals(mInfo.key) &&
272                     mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
273                 try {
274                     mInfo.intent.send();
275                     return true;
276                 } catch (Exception e) {
277                     // ignore
278                 }
279             }
280             mDialog = new VpnDialog(getActivity(), this, profile, false);
281         } else {
282             // Generate a new key. Here we just use the current time.
283             long millis = System.currentTimeMillis();
284             while (mPreferences.containsKey(Long.toHexString(millis))) {
285                 ++millis;
286             }
287             mDialog = new VpnDialog(getActivity(), this,
288                     new VpnProfile(Long.toHexString(millis)), true);
289         }
290         mDialog.setOnDismissListener(this);
291         mDialog.show();
292         return true;
293     }
294 
295     @Override
handleMessage(Message message)296     public boolean handleMessage(Message message) {
297         mUpdater.removeMessages(0);
298 
299         if (isResumed()) {
300             try {
301                 LegacyVpnInfo info = mService.getLegacyVpnInfo();
302                 if (mInfo != null) {
303                     VpnPreference preference = mPreferences.get(mInfo.key);
304                     if (preference != null) {
305                         preference.update(-1);
306                     }
307                     mInfo = null;
308                 }
309                 if (info != null) {
310                     VpnPreference preference = mPreferences.get(info.key);
311                     if (preference != null) {
312                         preference.update(info.state);
313                         mInfo = info;
314                     }
315                 }
316             } catch (Exception e) {
317                 // ignore
318             }
319             mUpdater.sendEmptyMessageDelayed(0, 1000);
320         }
321         return true;
322     }
323 
getDefaultNetwork()324     private String[] getDefaultNetwork() throws Exception {
325         LinkProperties network = mService.getActiveLinkProperties();
326         if (network == null) {
327             throw new IllegalStateException("Network is not available");
328         }
329         String interfaze = network.getInterfaceName();
330         if (interfaze == null) {
331             throw new IllegalStateException("Cannot get the default interface");
332         }
333         String gateway = null;
334         for (RouteInfo route : network.getRoutes()) {
335             // Currently legacy VPN only works on IPv4.
336             if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
337                 gateway = route.getGateway().getHostAddress();
338                 break;
339             }
340         }
341         if (gateway == null) {
342             throw new IllegalStateException("Cannot get the default gateway");
343         }
344         return new String[] {interfaze, gateway};
345     }
346 
connect(VpnProfile profile)347     private void connect(VpnProfile profile) throws Exception {
348         // Get the default interface and the default gateway.
349         String[] network = getDefaultNetwork();
350         String interfaze = network[0];
351         String gateway = network[1];
352 
353         // Load certificates.
354         String privateKey = "";
355         String userCert = "";
356         String caCert = "";
357         if (!profile.ipsecUserCert.isEmpty()) {
358             byte[] value = mKeyStore.get(Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert);
359             privateKey = (value == null) ? null : new String(value, Charsets.UTF_8);
360             value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
361             userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
362         }
363         if (!profile.ipsecCaCert.isEmpty()) {
364             byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
365             caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
366         }
367         if (privateKey == null || userCert == null || caCert == null) {
368             // TODO: find out a proper way to handle this. Delete these keys?
369             throw new IllegalStateException("Cannot load credentials");
370         }
371 
372         // Prepare arguments for racoon.
373         String[] racoon = null;
374         switch (profile.type) {
375             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
376                 racoon = new String[] {
377                     interfaze, profile.server, "udppsk", profile.ipsecIdentifier,
378                     profile.ipsecSecret, "1701",
379                 };
380                 break;
381             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
382                 racoon = new String[] {
383                     interfaze, profile.server, "udprsa", privateKey, userCert, caCert, "1701",
384                 };
385                 break;
386             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
387                 racoon = new String[] {
388                     interfaze, profile.server, "xauthpsk", profile.ipsecIdentifier,
389                     profile.ipsecSecret, profile.username, profile.password, "", gateway,
390                 };
391                 break;
392             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
393                 racoon = new String[] {
394                     interfaze, profile.server, "xauthrsa", privateKey, userCert, caCert,
395                     profile.username, profile.password, "", gateway,
396                 };
397                 break;
398             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
399                 racoon = new String[] {
400                     interfaze, profile.server, "hybridrsa", caCert,
401                     profile.username, profile.password, "", gateway,
402                 };
403                 break;
404         }
405 
406         // Prepare arguments for mtpd.
407         String[] mtpd = null;
408         switch (profile.type) {
409             case VpnProfile.TYPE_PPTP:
410                 mtpd = new String[] {
411                     interfaze, "pptp", profile.server, "1723",
412                     "name", profile.username, "password", profile.password,
413                     "linkname", "vpn", "refuse-eap", "nodefaultroute",
414                     "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
415                     (profile.mppe ? "+mppe" : "nomppe"),
416                 };
417                 break;
418             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
419             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
420                 mtpd = new String[] {
421                     interfaze, "l2tp", profile.server, "1701", profile.l2tpSecret,
422                     "name", profile.username, "password", profile.password,
423                     "linkname", "vpn", "refuse-eap", "nodefaultroute",
424                     "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
425                 };
426                 break;
427         }
428 
429         VpnConfig config = new VpnConfig();
430         config.user = profile.key;
431         config.interfaze = interfaze;
432         config.session = profile.name;
433         config.routes = profile.routes;
434         if (!profile.dnsServers.isEmpty()) {
435             config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
436         }
437         if (!profile.searchDomains.isEmpty()) {
438             config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
439         }
440 
441         mService.startLegacyVpn(config, racoon, mtpd);
442     }
443 
disconnect(String key)444     private void disconnect(String key) {
445         if (mInfo != null && key.equals(mInfo.key)) {
446             try {
447                 mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
448             } catch (Exception e) {
449                 // ignore
450             }
451         }
452     }
453 
454     private class VpnPreference extends Preference {
455         private VpnProfile mProfile;
456         private int mState = -1;
457 
VpnPreference(Context context, VpnProfile profile)458         VpnPreference(Context context, VpnProfile profile) {
459             super(context);
460             setPersistent(false);
461             setOrder(0);
462             setOnPreferenceClickListener(VpnSettings.this);
463 
464             mProfile = profile;
465             update();
466         }
467 
getProfile()468         VpnProfile getProfile() {
469             return mProfile;
470         }
471 
update(VpnProfile profile)472         void update(VpnProfile profile) {
473             mProfile = profile;
474             update();
475         }
476 
update(int state)477         void update(int state) {
478             mState = state;
479             update();
480         }
481 
update()482         void update() {
483             if (mState < 0) {
484                 String[] types = getContext().getResources()
485                         .getStringArray(R.array.vpn_types_long);
486                 setSummary(types[mProfile.type]);
487             } else {
488                 String[] states = getContext().getResources()
489                         .getStringArray(R.array.vpn_states);
490                 setSummary(states[mState]);
491             }
492             setTitle(mProfile.name);
493             notifyHierarchyChanged();
494         }
495 
496         @Override
compareTo(Preference preference)497         public int compareTo(Preference preference) {
498             int result = -1;
499             if (preference instanceof VpnPreference) {
500                 VpnPreference another = (VpnPreference) preference;
501                 if ((result = another.mState - mState) == 0 &&
502                         (result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
503                         (result = mProfile.type - another.mProfile.type) == 0) {
504                     result = mProfile.key.compareTo(another.mProfile.key);
505                 }
506             }
507             return result;
508         }
509     }
510 }
511