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