Home | History | Annotate | Download | only in vpn2
      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 android.app.AlertDialog;
     20 import android.content.Context;
     21 import android.content.DialogInterface;
     22 import android.os.Bundle;
     23 import android.os.SystemProperties;
     24 import android.security.Credentials;
     25 import android.security.KeyStore;
     26 import android.text.Editable;
     27 import android.text.TextWatcher;
     28 import android.view.View;
     29 import android.view.WindowManager;
     30 import android.widget.AdapterView;
     31 import android.widget.ArrayAdapter;
     32 import android.widget.CheckBox;
     33 import android.widget.CompoundButton;
     34 import android.widget.Spinner;
     35 import android.widget.TextView;
     36 
     37 import com.android.internal.net.VpnProfile;
     38 import com.android.settings.R;
     39 
     40 import java.net.InetAddress;
     41 
     42 /**
     43  * Dialog showing information about a VPN configuration. The dialog
     44  * can be launched to either edit or prompt for credentials to connect
     45  * to a user-added VPN.
     46  *
     47  * {@see AppDialog}
     48  */
     49 class ConfigDialog extends AlertDialog implements TextWatcher,
     50         View.OnClickListener, AdapterView.OnItemSelectedListener,
     51         CompoundButton.OnCheckedChangeListener {
     52     private final KeyStore mKeyStore = KeyStore.getInstance();
     53     private final DialogInterface.OnClickListener mListener;
     54     private final VpnProfile mProfile;
     55 
     56     private boolean mEditing;
     57     private boolean mExists;
     58 
     59     private View mView;
     60 
     61     private TextView mName;
     62     private Spinner mType;
     63     private TextView mServer;
     64     private TextView mUsername;
     65     private TextView mPassword;
     66     private TextView mSearchDomains;
     67     private TextView mDnsServers;
     68     private TextView mRoutes;
     69     private CheckBox mMppe;
     70     private TextView mL2tpSecret;
     71     private TextView mIpsecIdentifier;
     72     private TextView mIpsecSecret;
     73     private Spinner mIpsecUserCert;
     74     private Spinner mIpsecCaCert;
     75     private Spinner mIpsecServerCert;
     76     private CheckBox mSaveLogin;
     77     private CheckBox mShowOptions;
     78     private CheckBox mAlwaysOnVpn;
     79     private TextView mAlwaysOnInvalidReason;
     80 
     81     ConfigDialog(Context context, DialogInterface.OnClickListener listener,
     82             VpnProfile profile, boolean editing, boolean exists) {
     83         super(context);
     84 
     85         mListener = listener;
     86         mProfile = profile;
     87         mEditing = editing;
     88         mExists = exists;
     89     }
     90 
     91     @Override
     92     protected void onCreate(Bundle savedState) {
     93         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
     94         setView(mView);
     95 
     96         Context context = getContext();
     97 
     98         // First, find out all the fields.
     99         mName = (TextView) mView.findViewById(R.id.name);
    100         mType = (Spinner) mView.findViewById(R.id.type);
    101         mServer = (TextView) mView.findViewById(R.id.server);
    102         mUsername = (TextView) mView.findViewById(R.id.username);
    103         mPassword = (TextView) mView.findViewById(R.id.password);
    104         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
    105         mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
    106         mRoutes = (TextView) mView.findViewById(R.id.routes);
    107         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
    108         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
    109         mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
    110         mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
    111         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
    112         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
    113         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
    114         mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
    115         mShowOptions = (CheckBox) mView.findViewById(R.id.show_options);
    116         mAlwaysOnVpn = (CheckBox) mView.findViewById(R.id.always_on_vpn);
    117         mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
    118 
    119         // Second, copy values from the profile.
    120         mName.setText(mProfile.name);
    121         mType.setSelection(mProfile.type);
    122         mServer.setText(mProfile.server);
    123         if (mProfile.saveLogin) {
    124             mUsername.setText(mProfile.username);
    125             mPassword.setText(mProfile.password);
    126         }
    127         mSearchDomains.setText(mProfile.searchDomains);
    128         mDnsServers.setText(mProfile.dnsServers);
    129         mRoutes.setText(mProfile.routes);
    130         mMppe.setChecked(mProfile.mppe);
    131         mL2tpSecret.setText(mProfile.l2tpSecret);
    132         mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
    133         mIpsecSecret.setText(mProfile.ipsecSecret);
    134         loadCertificates(mIpsecUserCert, Credentials.USER_PRIVATE_KEY, 0, mProfile.ipsecUserCert);
    135         loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
    136                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
    137         loadCertificates(mIpsecServerCert, Credentials.USER_CERTIFICATE,
    138                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
    139         mSaveLogin.setChecked(mProfile.saveLogin);
    140         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
    141 
    142         // Hide lockdown VPN on devices that require IMS authentication
    143         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
    144             mAlwaysOnVpn.setVisibility(View.GONE);
    145         }
    146 
    147         // Third, add listeners to required fields.
    148         mName.addTextChangedListener(this);
    149         mType.setOnItemSelectedListener(this);
    150         mServer.addTextChangedListener(this);
    151         mUsername.addTextChangedListener(this);
    152         mPassword.addTextChangedListener(this);
    153         mDnsServers.addTextChangedListener(this);
    154         mRoutes.addTextChangedListener(this);
    155         mIpsecSecret.addTextChangedListener(this);
    156         mIpsecUserCert.setOnItemSelectedListener(this);
    157         mShowOptions.setOnClickListener(this);
    158         mAlwaysOnVpn.setOnCheckedChangeListener(this);
    159 
    160         // Fourth, determine whether to do editing or connecting.
    161         mEditing = mEditing || !validate(true /*editing*/);
    162 
    163         if (mEditing) {
    164             setTitle(R.string.vpn_edit);
    165 
    166             // Show common fields.
    167             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
    168 
    169             // Show type-specific fields.
    170             changeType(mProfile.type);
    171 
    172             // Hide 'save login' when we are editing.
    173             mSaveLogin.setVisibility(View.GONE);
    174 
    175             // Switch to advanced view immediately if any advanced options are on
    176             if (!mProfile.searchDomains.isEmpty() || !mProfile.dnsServers.isEmpty() ||
    177                     !mProfile.routes.isEmpty()) {
    178                 showAdvancedOptions();
    179             }
    180 
    181             // Create a button to forget the profile if it has already been saved..
    182             if (mExists) {
    183                 setButton(DialogInterface.BUTTON_NEUTRAL,
    184                         context.getString(R.string.vpn_forget), mListener);
    185             }
    186 
    187             // Create a button to save the profile.
    188             setButton(DialogInterface.BUTTON_POSITIVE,
    189                     context.getString(R.string.vpn_save), mListener);
    190         } else {
    191             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
    192 
    193             // Create a button to connect the network.
    194             setButton(DialogInterface.BUTTON_POSITIVE,
    195                     context.getString(R.string.vpn_connect), mListener);
    196         }
    197 
    198         // Always provide a cancel button.
    199         setButton(DialogInterface.BUTTON_NEGATIVE,
    200                 context.getString(R.string.vpn_cancel), mListener);
    201 
    202         // Let AlertDialog create everything.
    203         super.onCreate(savedState);
    204 
    205         // Update UI controls according to the current configuration.
    206         updateUiControls();
    207 
    208         // Workaround to resize the dialog for the input method.
    209         getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
    210                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
    211     }
    212 
    213     @Override
    214     public void onRestoreInstanceState(Bundle savedState) {
    215         super.onRestoreInstanceState(savedState);
    216 
    217         // Visibility isn't restored by super.onRestoreInstanceState, so re-show the advanced
    218         // options here if they were already revealed or set.
    219         if (mShowOptions.isChecked()) {
    220             showAdvancedOptions();
    221         }
    222     }
    223 
    224     @Override
    225     public void afterTextChanged(Editable field) {
    226         updateUiControls();
    227     }
    228 
    229     @Override
    230     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    231     }
    232 
    233     @Override
    234     public void onTextChanged(CharSequence s, int start, int before, int count) {
    235     }
    236 
    237     @Override
    238     public void onClick(View view) {
    239         if (view == mShowOptions) {
    240             showAdvancedOptions();
    241         }
    242     }
    243 
    244     @Override
    245     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    246         if (parent == mType) {
    247             changeType(position);
    248         }
    249         updateUiControls();
    250     }
    251 
    252     @Override
    253     public void onNothingSelected(AdapterView<?> parent) {
    254     }
    255 
    256     @Override
    257     public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
    258         if (compoundButton == mAlwaysOnVpn) {
    259             updateUiControls();
    260         }
    261     }
    262 
    263     public boolean isVpnAlwaysOn() {
    264         return mAlwaysOnVpn.isChecked();
    265     }
    266 
    267     /**
    268      * Updates the UI according to the current configuration entered by the user.
    269      *
    270      * These include:
    271      * "Always-on VPN" checkbox
    272      * Reason for "Always-on VPN" being disabled, when necessary
    273      * "Save account information" checkbox
    274      * "Save" and "Connect" buttons
    275      */
    276     private void updateUiControls() {
    277         VpnProfile profile = getProfile();
    278 
    279         // Always-on VPN
    280         if (profile.isValidLockdownProfile()) {
    281             mAlwaysOnVpn.setEnabled(true);
    282             mAlwaysOnInvalidReason.setVisibility(View.GONE);
    283         } else {
    284             mAlwaysOnVpn.setChecked(false);
    285             mAlwaysOnVpn.setEnabled(false);
    286             if (!profile.isTypeValidForLockdown()) {
    287                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_type);
    288             } else if (!profile.isServerAddressNumeric()) {
    289                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_server);
    290             } else if (!profile.hasDns()) {
    291                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_no_dns);
    292             } else if (!profile.areDnsAddressesNumeric()) {
    293                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_dns);
    294             } else {
    295                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
    296             }
    297             mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
    298         }
    299 
    300         // Save account information
    301         if (mAlwaysOnVpn.isChecked()) {
    302             mSaveLogin.setChecked(true);
    303             mSaveLogin.setEnabled(false);
    304         } else {
    305             mSaveLogin.setChecked(mProfile.saveLogin);
    306             mSaveLogin.setEnabled(true);
    307         }
    308 
    309         // Save or Connect button
    310         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
    311     }
    312 
    313     private void showAdvancedOptions() {
    314         mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
    315         mShowOptions.setVisibility(View.GONE);
    316     }
    317 
    318     private void changeType(int type) {
    319         // First, hide everything.
    320         mMppe.setVisibility(View.GONE);
    321         mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
    322         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
    323         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
    324         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
    325 
    326         // Then, unhide type-specific fields.
    327         switch (type) {
    328             case VpnProfile.TYPE_PPTP:
    329                 mMppe.setVisibility(View.VISIBLE);
    330                 break;
    331 
    332             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    333                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
    334                 // fall through
    335             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    336                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
    337                 break;
    338 
    339             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    340                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
    341                 // fall through
    342             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    343                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
    344                 // fall through
    345             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    346                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
    347                 break;
    348         }
    349     }
    350 
    351     private boolean validate(boolean editing) {
    352         if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
    353             return false;
    354         }
    355         if (!editing) {
    356             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
    357         }
    358         if (mName.getText().length() == 0 || mServer.getText().length() == 0 ||
    359                 !validateAddresses(mDnsServers.getText().toString(), false) ||
    360                 !validateAddresses(mRoutes.getText().toString(), true)) {
    361             return false;
    362         }
    363         switch (mType.getSelectedItemPosition()) {
    364             case VpnProfile.TYPE_PPTP:
    365             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    366                 return true;
    367 
    368             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    369             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    370                 return mIpsecSecret.getText().length() != 0;
    371 
    372             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    373             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    374                 return mIpsecUserCert.getSelectedItemPosition() != 0;
    375         }
    376         return false;
    377     }
    378 
    379     private boolean validateAddresses(String addresses, boolean cidr) {
    380         try {
    381             for (String address : addresses.split(" ")) {
    382                 if (address.isEmpty()) {
    383                     continue;
    384                 }
    385                 // Legacy VPN currently only supports IPv4.
    386                 int prefixLength = 32;
    387                 if (cidr) {
    388                     String[] parts = address.split("/", 2);
    389                     address = parts[0];
    390                     prefixLength = Integer.parseInt(parts[1]);
    391                 }
    392                 byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
    393                 int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
    394                         (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
    395                 if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
    396                         (prefixLength < 32 && (integer << prefixLength) != 0)) {
    397                     return false;
    398                 }
    399             }
    400         } catch (Exception e) {
    401             return false;
    402         }
    403         return true;
    404     }
    405 
    406     private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
    407         Context context = getContext();
    408         String first = (firstId == 0) ? "" : context.getString(firstId);
    409         String[] certificates = mKeyStore.list(prefix);
    410 
    411         if (certificates == null || certificates.length == 0) {
    412             certificates = new String[] {first};
    413         } else {
    414             String[] array = new String[certificates.length + 1];
    415             array[0] = first;
    416             System.arraycopy(certificates, 0, array, 1, certificates.length);
    417             certificates = array;
    418         }
    419 
    420         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    421                 context, android.R.layout.simple_spinner_item, certificates);
    422         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    423         spinner.setAdapter(adapter);
    424 
    425         for (int i = 1; i < certificates.length; ++i) {
    426             if (certificates[i].equals(selected)) {
    427                 spinner.setSelection(i);
    428                 break;
    429             }
    430         }
    431     }
    432 
    433     boolean isEditing() {
    434         return mEditing;
    435     }
    436 
    437     VpnProfile getProfile() {
    438         // First, save common fields.
    439         VpnProfile profile = new VpnProfile(mProfile.key);
    440         profile.name = mName.getText().toString();
    441         profile.type = mType.getSelectedItemPosition();
    442         profile.server = mServer.getText().toString().trim();
    443         profile.username = mUsername.getText().toString();
    444         profile.password = mPassword.getText().toString();
    445         profile.searchDomains = mSearchDomains.getText().toString().trim();
    446         profile.dnsServers = mDnsServers.getText().toString().trim();
    447         profile.routes = mRoutes.getText().toString().trim();
    448 
    449         // Then, save type-specific fields.
    450         switch (profile.type) {
    451             case VpnProfile.TYPE_PPTP:
    452                 profile.mppe = mMppe.isChecked();
    453                 break;
    454 
    455             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    456                 profile.l2tpSecret = mL2tpSecret.getText().toString();
    457                 // fall through
    458             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    459                 profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
    460                 profile.ipsecSecret = mIpsecSecret.getText().toString();
    461                 break;
    462 
    463             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    464                 profile.l2tpSecret = mL2tpSecret.getText().toString();
    465                 // fall through
    466             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    467                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
    468                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
    469                 }
    470                 // fall through
    471             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    472                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
    473                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
    474                 }
    475                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
    476                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
    477                 }
    478                 break;
    479         }
    480 
    481         final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
    482         profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
    483         return profile;
    484     }
    485 }
    486