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 com.android.settings.R;
     20 
     21 import android.app.AlertDialog;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.os.Bundle;
     25 import android.security.Credentials;
     26 import android.security.KeyStore;
     27 import android.text.Editable;
     28 import android.text.TextWatcher;
     29 import android.view.View;
     30 import android.view.WindowManager;
     31 import android.widget.AdapterView;
     32 import android.widget.ArrayAdapter;
     33 import android.widget.Button;
     34 import android.widget.CheckBox;
     35 import android.widget.Spinner;
     36 import android.widget.TextView;
     37 
     38 import java.net.InetAddress;
     39 
     40 class VpnDialog extends AlertDialog implements TextWatcher,
     41         View.OnClickListener, AdapterView.OnItemSelectedListener {
     42     private final KeyStore mKeyStore = KeyStore.getInstance();
     43     private final DialogInterface.OnClickListener mListener;
     44     private final VpnProfile mProfile;
     45 
     46     private boolean mEditing;
     47 
     48     private View mView;
     49 
     50     private TextView mName;
     51     private Spinner mType;
     52     private TextView mServer;
     53     private TextView mUsername;
     54     private TextView mPassword;
     55     private TextView mSearchDomains;
     56     private TextView mDnsServers;
     57     private TextView mRoutes;
     58     private CheckBox mMppe;
     59     private TextView mL2tpSecret;
     60     private TextView mIpsecIdentifier;
     61     private TextView mIpsecSecret;
     62     private Spinner mIpsecUserCert;
     63     private Spinner mIpsecCaCert;
     64     private Spinner mIpsecServerCert;
     65     private CheckBox mSaveLogin;
     66 
     67     VpnDialog(Context context, DialogInterface.OnClickListener listener,
     68             VpnProfile profile, boolean editing) {
     69         super(context);
     70         mListener = listener;
     71         mProfile = profile;
     72         mEditing = editing;
     73     }
     74 
     75     @Override
     76     protected void onCreate(Bundle savedState) {
     77         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
     78         setView(mView);
     79         setInverseBackgroundForced(true);
     80 
     81         Context context = getContext();
     82 
     83         // First, find out all the fields.
     84         mName = (TextView) mView.findViewById(R.id.name);
     85         mType = (Spinner) mView.findViewById(R.id.type);
     86         mServer = (TextView) mView.findViewById(R.id.server);
     87         mUsername = (TextView) mView.findViewById(R.id.username);
     88         mPassword = (TextView) mView.findViewById(R.id.password);
     89         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
     90         mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
     91         mRoutes = (TextView) mView.findViewById(R.id.routes);
     92         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
     93         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
     94         mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
     95         mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
     96         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
     97         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
     98         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
     99         mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
    100 
    101         // Second, copy values from the profile.
    102         mName.setText(mProfile.name);
    103         mType.setSelection(mProfile.type);
    104         mServer.setText(mProfile.server);
    105         mUsername.setText(mProfile.username);
    106         mPassword.setText(mProfile.password);
    107         mSearchDomains.setText(mProfile.searchDomains);
    108         mDnsServers.setText(mProfile.dnsServers);
    109         mRoutes.setText(mProfile.routes);
    110         mMppe.setChecked(mProfile.mppe);
    111         mL2tpSecret.setText(mProfile.l2tpSecret);
    112         mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
    113         mIpsecSecret.setText(mProfile.ipsecSecret);
    114         loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE,
    115                 0, mProfile.ipsecUserCert);
    116         loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
    117                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
    118         loadCertificates(mIpsecServerCert, Credentials.USER_CERTIFICATE,
    119                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
    120         mSaveLogin.setChecked(mProfile.saveLogin);
    121 
    122         // Third, add listeners to required fields.
    123         mName.addTextChangedListener(this);
    124         mType.setOnItemSelectedListener(this);
    125         mServer.addTextChangedListener(this);
    126         mUsername.addTextChangedListener(this);
    127         mPassword.addTextChangedListener(this);
    128         mDnsServers.addTextChangedListener(this);
    129         mRoutes.addTextChangedListener(this);
    130         mIpsecSecret.addTextChangedListener(this);
    131         mIpsecUserCert.setOnItemSelectedListener(this);
    132 
    133         // Forth, determine to do editing or connecting.
    134         boolean valid = validate(true);
    135         mEditing = mEditing || !valid;
    136 
    137         if (mEditing) {
    138             setTitle(R.string.vpn_edit);
    139 
    140             // Show common fields.
    141             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
    142 
    143             // Show type-specific fields.
    144             changeType(mProfile.type);
    145 
    146             // Show advanced options directly if any of them is set.
    147             View showOptions = mView.findViewById(R.id.show_options);
    148             if (mProfile.searchDomains.isEmpty() && mProfile.dnsServers.isEmpty() &&
    149                     mProfile.routes.isEmpty()) {
    150                 showOptions.setOnClickListener(this);
    151             } else {
    152                 onClick(showOptions);
    153             }
    154 
    155             // Create a button to save the profile.
    156             setButton(DialogInterface.BUTTON_POSITIVE,
    157                     context.getString(R.string.vpn_save), mListener);
    158         } else {
    159             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
    160 
    161             // Not editing, just show username and password.
    162             mView.findViewById(R.id.login).setVisibility(View.VISIBLE);
    163 
    164             // Create a button to connect the network.
    165             setButton(DialogInterface.BUTTON_POSITIVE,
    166                     context.getString(R.string.vpn_connect), mListener);
    167         }
    168 
    169         // Always provide a cancel button.
    170         setButton(DialogInterface.BUTTON_NEGATIVE,
    171                 context.getString(R.string.vpn_cancel), mListener);
    172 
    173         // Let AlertDialog create everything.
    174         super.onCreate(null);
    175 
    176         // Disable the action button if necessary.
    177         getButton(DialogInterface.BUTTON_POSITIVE)
    178                 .setEnabled(mEditing ? valid : validate(false));
    179 
    180         // Workaround to resize the dialog for the input method.
    181         getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
    182                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
    183     }
    184 
    185     @Override
    186     public void afterTextChanged(Editable field) {
    187         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
    188     }
    189 
    190     @Override
    191     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    192     }
    193 
    194     @Override
    195     public void onTextChanged(CharSequence s, int start, int before, int count) {
    196     }
    197 
    198     @Override
    199     public void onClick(View showOptions) {
    200         showOptions.setVisibility(View.GONE);
    201         mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
    202     }
    203 
    204     @Override
    205     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    206         if (parent == mType) {
    207             changeType(position);
    208         }
    209         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
    210     }
    211 
    212     @Override
    213     public void onNothingSelected(AdapterView<?> parent) {
    214     }
    215 
    216     private void changeType(int type) {
    217         // First, hide everything.
    218         mMppe.setVisibility(View.GONE);
    219         mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
    220         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
    221         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
    222         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
    223 
    224         // Then, unhide type-specific fields.
    225         switch (type) {
    226             case VpnProfile.TYPE_PPTP:
    227                 mMppe.setVisibility(View.VISIBLE);
    228                 break;
    229 
    230             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    231                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
    232                 // fall through
    233             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    234                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
    235                 break;
    236 
    237             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    238                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
    239                 // fall through
    240             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    241                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
    242                 // fall through
    243             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    244                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
    245                 break;
    246         }
    247     }
    248 
    249     private boolean validate(boolean editing) {
    250         if (!editing) {
    251             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
    252         }
    253         if (mName.getText().length() == 0 || mServer.getText().length() == 0 ||
    254                 !validateAddresses(mDnsServers.getText().toString(), false) ||
    255                 !validateAddresses(mRoutes.getText().toString(), true)) {
    256             return false;
    257         }
    258         switch (mType.getSelectedItemPosition()) {
    259             case VpnProfile.TYPE_PPTP:
    260             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    261                 return true;
    262 
    263             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    264             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    265                 return mIpsecSecret.getText().length() != 0;
    266 
    267             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    268             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    269                 return mIpsecUserCert.getSelectedItemPosition() != 0;
    270         }
    271         return false;
    272     }
    273 
    274     private boolean validateAddresses(String addresses, boolean cidr) {
    275         try {
    276             for (String address : addresses.split(" ")) {
    277                 if (address.isEmpty()) {
    278                     continue;
    279                 }
    280                 // Legacy VPN currently only supports IPv4.
    281                 int prefixLength = 32;
    282                 if (cidr) {
    283                     String[] parts = address.split("/", 2);
    284                     address = parts[0];
    285                     prefixLength = Integer.parseInt(parts[1]);
    286                 }
    287                 byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
    288                 int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
    289                         (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
    290                 if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
    291                         (prefixLength < 32 && (integer << prefixLength) != 0)) {
    292                     return false;
    293                 }
    294             }
    295         } catch (Exception e) {
    296             return false;
    297         }
    298         return true;
    299     }
    300 
    301     private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
    302         Context context = getContext();
    303         String first = (firstId == 0) ? "" : context.getString(firstId);
    304         String[] certificates = mKeyStore.saw(prefix);
    305 
    306         if (certificates == null || certificates.length == 0) {
    307             certificates = new String[] {first};
    308         } else {
    309             String[] array = new String[certificates.length + 1];
    310             array[0] = first;
    311             System.arraycopy(certificates, 0, array, 1, certificates.length);
    312             certificates = array;
    313         }
    314 
    315         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
    316                 context, android.R.layout.simple_spinner_item, certificates);
    317         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    318         spinner.setAdapter(adapter);
    319 
    320         for (int i = 1; i < certificates.length; ++i) {
    321             if (certificates[i].equals(selected)) {
    322                 spinner.setSelection(i);
    323                 break;
    324             }
    325         }
    326     }
    327 
    328     boolean isEditing() {
    329         return mEditing;
    330     }
    331 
    332     VpnProfile getProfile() {
    333         // First, save common fields.
    334         VpnProfile profile = new VpnProfile(mProfile.key);
    335         profile.name = mName.getText().toString();
    336         profile.type = mType.getSelectedItemPosition();
    337         profile.server = mServer.getText().toString().trim();
    338         profile.username = mUsername.getText().toString();
    339         profile.password = mPassword.getText().toString();
    340         profile.searchDomains = mSearchDomains.getText().toString().trim();
    341         profile.dnsServers = mDnsServers.getText().toString().trim();
    342         profile.routes = mRoutes.getText().toString().trim();
    343 
    344         // Then, save type-specific fields.
    345         switch (profile.type) {
    346             case VpnProfile.TYPE_PPTP:
    347                 profile.mppe = mMppe.isChecked();
    348                 break;
    349 
    350             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
    351                 profile.l2tpSecret = mL2tpSecret.getText().toString();
    352                 // fall through
    353             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
    354                 profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
    355                 profile.ipsecSecret = mIpsecSecret.getText().toString();
    356                 break;
    357 
    358             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
    359                 profile.l2tpSecret = mL2tpSecret.getText().toString();
    360                 // fall through
    361             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
    362                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
    363                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
    364                 }
    365                 // fall through
    366             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
    367                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
    368                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
    369                 }
    370                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
    371                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
    372                 }
    373                 break;
    374         }
    375 
    376         profile.saveLogin = mSaveLogin.isChecked();
    377         return profile;
    378     }
    379 }
    380