• 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.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 
VpnDialog(Context context, DialogInterface.OnClickListener listener, VpnProfile profile, boolean editing)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
onCreate(Bundle savedState)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
afterTextChanged(Editable field)189     public void afterTextChanged(Editable field) {
190         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
191     }
192 
193     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)194     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
195     }
196 
197     @Override
onTextChanged(CharSequence s, int start, int before, int count)198     public void onTextChanged(CharSequence s, int start, int before, int count) {
199     }
200 
201     @Override
onClick(View showOptions)202     public void onClick(View showOptions) {
203         showOptions.setVisibility(View.GONE);
204         mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
205     }
206 
207     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)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
onNothingSelected(AdapterView<?> parent)216     public void onNothingSelected(AdapterView<?> parent) {
217     }
218 
changeType(int type)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 
validate(boolean editing)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 
validateAddresses(String addresses, boolean cidr)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 
loadCertificates(Spinner spinner, String prefix, int firstId, String selected)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 
isEditing()331     boolean isEditing() {
332         return mEditing;
333     }
334 
getProfile()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