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