components/visual-panels/core/src/java/vpanels/client/com/oracle/solaris/vp/client/common/RadLoginManager.java
changeset 827 0944d8c0158b
child 1410 ca9946e5736c
equal deleted inserted replaced
826:c6aad84d2493 827:0944d8c0158b
       
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.client.common;
       
    27 
       
    28 import java.io.*;
       
    29 import java.net.*;
       
    30 import java.security.*;
       
    31 import java.security.cert.*;
       
    32 import java.security.cert.Certificate;
       
    33 import java.util.*;
       
    34 import java.util.logging.*;
       
    35 import javax.management.*;
       
    36 import javax.management.remote.*;
       
    37 import javax.swing.JOptionPane;
       
    38 import com.oracle.solaris.adr.Stability;
       
    39 import com.oracle.solaris.rad.*;
       
    40 import com.oracle.solaris.rad.jmx.*;
       
    41 import com.oracle.solaris.rad.pam.*;
       
    42 import com.oracle.solaris.rad.zonesbridge.*;
       
    43 import com.oracle.solaris.vp.panel.common.*;
       
    44 import com.oracle.solaris.vp.panel.common.action.*;
       
    45 import com.oracle.solaris.vp.panel.common.api.panel.MBeanUtil;
       
    46 import com.oracle.solaris.vp.util.misc.*;
       
    47 import com.oracle.solaris.vp.util.misc.finder.Finder;
       
    48 
       
    49 public abstract class RadLoginManager {
       
    50     public static final String RAD_PATH_AFUNIX_AUTH =
       
    51 	"/system/volatile/rad/radsocket";
       
    52     public static final String RAD_PATH_AFUNIX_UNAUTH =
       
    53 	"/system/volatile/rad/radsocket-unauth";
       
    54 
       
    55     //
       
    56     // Inner classes
       
    57     //
       
    58 
       
    59     private static abstract class AuthPrompter {
       
    60 	//
       
    61 	// Instance data
       
    62 	//
       
    63 
       
    64 	private boolean acknowledged;
       
    65 
       
    66 	//
       
    67 	// AuthPrompter methods
       
    68 	//
       
    69 
       
    70 	/**
       
    71 	 * _,__/|
       
    72 	 *  `O.o'
       
    73 	 * =(_x_)=
       
    74 	 *    U
       
    75 	 */
       
    76 	protected void ack() {
       
    77 	    acknowledged = true;
       
    78 	}
       
    79 
       
    80 	public boolean isAcknowledged() {
       
    81 	    return acknowledged;
       
    82 	}
       
    83 
       
    84 	public abstract Block initiate(LoginRequest request,
       
    85 	    AuthenticationMXBean auth) throws ActionAbortedException,
       
    86 	    ObjectException;
       
    87 
       
    88 	public abstract void prompt(LoginRequest request,
       
    89 	    List<LoginProperty> properties, boolean isFirst)
       
    90 	    throws ActionAbortedException, ActionRegressedException;
       
    91     }
       
    92 
       
    93     private class UserPrompter extends AuthPrompter {
       
    94 	@Override
       
    95 	public Block initiate(LoginRequest request,
       
    96 	    AuthenticationMXBean auth) throws ActionAbortedException,
       
    97 	    ObjectException {
       
    98 
       
    99 	    setLoginStatus(request, Finder.getString("login.status.login",
       
   100 		request.getUser().getValue()));
       
   101 
       
   102 	    return auth.login(Locale.getDefault().getLanguage(),
       
   103 		request.getUser().getValue());
       
   104 	}
       
   105 
       
   106 	@Override
       
   107 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   108 	    boolean isFirst) throws ActionAbortedException,
       
   109 	    ActionRegressedException {
       
   110 
       
   111 	    try {
       
   112 		promptForAuth(request, properties, false, true, isFirst);
       
   113 	    } finally {
       
   114 		ack();
       
   115 		request.getHost().setErrored(false);
       
   116 		request.getUser().setErrored(false);
       
   117 	    }
       
   118 	}
       
   119     }
       
   120 
       
   121     private class RolePrompter extends AuthPrompter {
       
   122 	@Override
       
   123 	public Block initiate(LoginRequest request,
       
   124 	    AuthenticationMXBean auth) throws ActionAbortedException,
       
   125 	    ObjectException {
       
   126 
       
   127 	    setLoginStatus(request, Finder.getString("login.status.assume",
       
   128 		request.getRole().getValue()));
       
   129 
       
   130 	    return auth.assume(Locale.getDefault().getLanguage(),
       
   131 		request.getRole().getValue());
       
   132 	}
       
   133 
       
   134 	@Override
       
   135 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   136 	    boolean isFirst) throws ActionAbortedException,
       
   137 	    ActionRegressedException {
       
   138 
       
   139 	    try {
       
   140 		promptForAuth(request, properties, false, false, isFirst);
       
   141 	    } finally {
       
   142 		ack();
       
   143 		request.getRole().setErrored(false);
       
   144 	    }
       
   145 	}
       
   146     }
       
   147 
       
   148     private class ZoneUserPrompter extends AuthPrompter {
       
   149 	@Override
       
   150 	public Block initiate(LoginRequest request,
       
   151 	    AuthenticationMXBean auth) throws ActionAbortedException,
       
   152 	    ObjectException {
       
   153 
       
   154 	    return auth.login(Locale.getDefault().getLanguage(),
       
   155 		request.getZoneUser().getValue());
       
   156 	}
       
   157 
       
   158 	@Override
       
   159 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   160 	    boolean isFirst) throws ActionAbortedException,
       
   161 	    ActionRegressedException {
       
   162 
       
   163 	    try {
       
   164 		promptForAuth(request, properties, true, true, isFirst);
       
   165 	    } finally {
       
   166 		ack();
       
   167 		request.getZone().setErrored(false);
       
   168 		request.getZoneUser().setErrored(false);
       
   169 	    }
       
   170 	}
       
   171     }
       
   172 
       
   173     private class ZoneRolePrompter extends AuthPrompter {
       
   174 	@Override
       
   175 	public Block initiate(LoginRequest request,
       
   176 	    AuthenticationMXBean auth) throws ActionAbortedException,
       
   177 	    ObjectException {
       
   178 
       
   179 	    return auth.assume(Locale.getDefault().getLanguage(),
       
   180 		request.getZoneRole().getValue());
       
   181 	}
       
   182 
       
   183 	@Override
       
   184 	public void prompt(LoginRequest request, List<LoginProperty> properties,
       
   185 	    boolean isFirst) throws ActionAbortedException,
       
   186 	    ActionRegressedException {
       
   187 
       
   188 	    try {
       
   189 		promptForAuth(request, properties, true, false, isFirst);
       
   190 	    } finally {
       
   191 		ack();
       
   192 		request.getZoneRole().setErrored(false);
       
   193 	    }
       
   194 	}
       
   195     }
       
   196 
       
   197     private static class LoginData {
       
   198 	//
       
   199 	// Instance data
       
   200 	//
       
   201 
       
   202 	private LinkedList<ConnectionInfo> depChain =
       
   203 	    new LinkedList<ConnectionInfo>();
       
   204 
       
   205 	private LinkedList<Boolean> acks = new LinkedList<Boolean>();
       
   206 
       
   207 	//
       
   208 	// LoginData methods
       
   209 	//
       
   210 
       
   211 	public boolean isAcknowledged() {
       
   212 	    return acks.peek();
       
   213 	}
       
   214 
       
   215 	public List<ConnectionInfo> getDepChain() {
       
   216 	    return depChain;
       
   217 	}
       
   218 
       
   219 	public ConnectionInfo peek(int offset) {
       
   220 	    return depChain.get(offset);
       
   221 	}
       
   222 
       
   223 	public void pop() {
       
   224 	    depChain.pop();
       
   225 	    acks.pop();
       
   226 	}
       
   227 
       
   228 	public void push(ConnectionInfo info, boolean acknowledged) {
       
   229 	    depChain.push(info);
       
   230 	    acks.push(acknowledged);
       
   231 	}
       
   232 
       
   233 	public void setDepChain(List<ConnectionInfo> depChain,
       
   234 	    boolean acknowledged) {
       
   235 
       
   236 	    assert compatible(depChain);
       
   237 
       
   238 	    this.depChain.clear();
       
   239 	    acks.clear();
       
   240 
       
   241 	    for (int i = depChain.size() - 1; i >= 0; i--) {
       
   242 		push(depChain.get(i), acknowledged);
       
   243 	    }
       
   244 	}
       
   245 
       
   246 	//
       
   247 	// Object methods
       
   248 	//
       
   249 
       
   250 	@Override
       
   251 	public String toString() {
       
   252 	    StringBuilder buffer = new StringBuilder();
       
   253 	    for (int i = depChain.size() - 1; i >= 0; i--) {
       
   254 		buffer.append(String.format("%d. %s (%s)\n", i,
       
   255 		    depChain.get(i), acks.get(i) ? "acknowledged" :
       
   256 		    "not acknowledged"));
       
   257 	    }
       
   258 	    return buffer.toString();
       
   259 	}
       
   260 
       
   261 	//
       
   262 	// Private methods
       
   263 	//
       
   264 
       
   265 	private void clearConnectionsTo(int level) {
       
   266 	    int size = depChain.size();
       
   267 	    for (int i = size - 1; i >= level; i--) {
       
   268 		depChain.remove(i);
       
   269 	    }
       
   270 	}
       
   271 
       
   272 	private boolean compatible(List<ConnectionInfo> depChain) {
       
   273 	    boolean compatible = false;
       
   274 	    if (depChain.size() == this.depChain.size() + 1) {
       
   275 		compatible = true;
       
   276 		for (int i = 0, n = this.depChain.size(); i < n; i++) {
       
   277 		    if (!this.depChain.get(i).equals(depChain.get(i + 1))) {
       
   278 			compatible = false;
       
   279 			break;
       
   280 		    }
       
   281 		}
       
   282 	    }
       
   283 	    return compatible;
       
   284 	}
       
   285     }
       
   286 
       
   287     //
       
   288     // Static data
       
   289     //
       
   290 
       
   291     public static final String TRUSTSTORE_PASSWORD = "trustpass";
       
   292     public static final String LOCAL_USER = System.getProperty("user.name");
       
   293     public static final String LOCAL_HOST = "localhost";
       
   294 
       
   295     //
       
   296     // Instance data
       
   297     //
       
   298 
       
   299     private ConnectionManager connManager;
       
   300 
       
   301     //
       
   302     // Constructors
       
   303     //
       
   304 
       
   305     public RadLoginManager(ConnectionManager connManager) {
       
   306 	this.connManager = connManager;
       
   307     }
       
   308 
       
   309     //
       
   310     // RadLoginManager methods
       
   311     //
       
   312 
       
   313     /**
       
   314      * Creates an empty truststore file.
       
   315      */
       
   316     protected void createTrustStore(File truststore) throws KeyStoreException,
       
   317 	IOException, NoSuchAlgorithmException, CertificateException {
       
   318 
       
   319 	File truststoreDir = truststore.getParentFile();
       
   320 
       
   321 	if (!truststoreDir.exists()) {
       
   322 	    if (!truststoreDir.mkdirs()) {
       
   323 		throw new IOException(
       
   324 		    "could not create truststore directory: " +
       
   325 		    truststoreDir.getAbsolutePath());
       
   326 	    }
       
   327 	}
       
   328 
       
   329 	KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
       
   330 	char[] password = getTrustStorePassword().toCharArray();
       
   331 
       
   332 	// Create empty keystore
       
   333 	keyStore.load(null, password);
       
   334 
       
   335 	FileOutputStream fos = new FileOutputStream(truststore);
       
   336 	keyStore.store(fos, password);
       
   337 	fos.close();
       
   338     }
       
   339 
       
   340     /**
       
   341      * Guides the user through the login process, then returns a dependency
       
   342      * chain of {@link ConnectionInfo}s.  The first element of the chain is the
       
   343      * {@code ConnectionInfo} that satisfies the given request.  Each additional
       
   344      * {@code ConnectionInfo} is a dependency of the previous {@code
       
   345      * ConnectionInfo}.
       
   346      * <p/>
       
   347      * For example, a role-based connection ("root@nerd (via talley)", say)
       
   348      * would have an dependency on the user-based connection ("talley@nerd")
       
   349      * used to create it.  The returned chain would contain the role-based
       
   350      * connection just before the user-based connection.
       
   351      * <p/>
       
   352      * This chain of {@code ConnectionInfo} dependencies can be {@link
       
   353      * ConnectionManager#add added} to the {@code ConnectionManager} for
       
   354      * automatic dependency-based management.
       
   355      *
       
   356      * @param	    request
       
   357      *		    the {@link LoginRequest} encapsulating the preset values and
       
   358      *		    editability of each core {@link LoginProperty}
       
   359      *
       
   360      * @param	    current
       
   361      *		    if non-{@code null}, ensures that the user is aware of any
       
   362      *		    change in login, preventing the use of cached connections
       
   363      *		    without the user's knowledge
       
   364      *
       
   365      * @exception   ActionAbortedException
       
   366      *		    if the user cancels the operation
       
   367      *
       
   368      * @exception   ActionFailedException
       
   369      *		    if the given request fails
       
   370      */
       
   371     @SuppressWarnings({"fallthrough"})
       
   372     public List<ConnectionInfo> getConnectionInfo(LoginRequest request,
       
   373 	LoginInfo current) throws ActionAbortedException, ActionFailedException
       
   374     {
       
   375 	LoginData data = new LoginData();
       
   376 	Boolean doZone;
       
   377 	for (int step = 0; ; ) {
       
   378 	    try {
       
   379 		switch (step) {
       
   380 		case 0:
       
   381 		    gatherHostAndUserData(request, data);
       
   382 		    step++;
       
   383 
       
   384 		case 1:
       
   385 		    gatherRoleData(request, data);
       
   386 		    step++;
       
   387 
       
   388 		case 2:
       
   389 		    doZone = request.getZonePrompt().getValue();
       
   390 		    if (doZone != null && doZone) {
       
   391 			gatherZoneHostAndUserData(request, data);
       
   392 		    }
       
   393 		    step++;
       
   394 
       
   395 		case 3:
       
   396 		    doZone = request.getZonePrompt().getValue();
       
   397 		    if (doZone != null && doZone) {
       
   398 			gatherZoneRoleData(request, data);
       
   399 		    }
       
   400 		    step++;
       
   401 		}
       
   402 		break;
       
   403 	    } catch (ActionRegressedException e) {
       
   404 		step--;
       
   405 	    }
       
   406 	}
       
   407 
       
   408 	List<ConnectionInfo> depChain = data.getDepChain();
       
   409 
       
   410 	// To prevent rogue connections, if the chosen connection differs from
       
   411 	// the current one, ensure that the user has acknowledged it at some
       
   412 	// point in the authentication process.  If not, prompt for
       
   413 	// acknowledgement now.
       
   414 	if (!data.isAcknowledged() && !depChain.get(0).matches(current)) {
       
   415 	    promptForAck(request);
       
   416 	}
       
   417 
       
   418 	return depChain;
       
   419     }
       
   420 
       
   421     public ConnectionManager getConnectionManager() {
       
   422 	return connManager;
       
   423     }
       
   424 
       
   425     /**
       
   426      * Gets the truststore file.
       
   427      */
       
   428     public abstract File getTrustStoreFile();
       
   429 
       
   430     /**
       
   431      * Gets the truststore password.  This default implementation returns
       
   432      * "{@code trustpass}".
       
   433      */
       
   434     public String getTrustStorePassword() {
       
   435 	return TRUSTSTORE_PASSWORD;
       
   436     }
       
   437 
       
   438     protected boolean handleCertFailure(String host, File truststore,
       
   439 	Certificate certificate) throws ActionAbortedException,
       
   440 	KeyStoreException, IOException, NoSuchAlgorithmException,
       
   441 	CertificateException {
       
   442 
       
   443 	KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
       
   444 	char[] password = getTrustStorePassword().toCharArray();
       
   445 
       
   446 	// Load truststore
       
   447 	FileInputStream fis = new FileInputStream(truststore);
       
   448 	keyStore.load(fis, password);
       
   449 	fis.close();
       
   450 
       
   451 	// Does the truststore already contain the certificate?
       
   452 	String alias = keyStore.getCertificateAlias(certificate);
       
   453 	if (alias != null) {
       
   454 	    return false;
       
   455 	}
       
   456 
       
   457 	boolean acceptNeeded = true;
       
   458 
       
   459 	if (NetUtil.isLocalAddress(host)) {
       
   460 	    FileInputStream certFileIn = null;
       
   461 	    try {
       
   462 		File certFile = new File("/etc/rad/cert.pem");
       
   463 		certFileIn = new FileInputStream(certFile);
       
   464 		Certificate localCert = CertificateFactory.
       
   465 		    getInstance("X.509").generateCertificate(certFileIn);
       
   466 
       
   467 		if (localCert.equals(certificate)) {
       
   468 		    acceptNeeded = false;
       
   469 		}
       
   470 	    } catch (Throwable ignore) {
       
   471 	    } finally {
       
   472 		IOUtil.closeIgnore(certFileIn);
       
   473 	    }
       
   474 	}
       
   475 
       
   476 	if (acceptNeeded) {
       
   477 	    // Display the certificate, prompt user to accept
       
   478 	    promptForCertificate(host, certificate);
       
   479 	}
       
   480 
       
   481 	// Add certificate
       
   482 	alias = ((X509Certificate)certificate).getIssuerDN().toString();
       
   483 	KeyStore.Entry entry =
       
   484 	    new KeyStore.TrustedCertificateEntry(certificate);
       
   485 	keyStore.setEntry(alias, entry, null);
       
   486 
       
   487 	FileOutputStream fos = new FileOutputStream(truststore);
       
   488 	keyStore.store(fos, password);
       
   489 	fos.close();
       
   490 
       
   491 	return true;
       
   492     }
       
   493 
       
   494     /**
       
   495      * Prompt the user to acknowledge or reject the imminent completion of the
       
   496      * given request.
       
   497      *
       
   498      * @param	    request
       
   499      *		    the {@link LoginRequest} encapsulating the preset values of
       
   500      *		    each core {@link LoginProperty}
       
   501      *
       
   502      * @exception   ActionAbortedException
       
   503      *		    if the user cancels the operation
       
   504      */
       
   505     protected abstract void promptForAck(LoginRequest request)
       
   506 	throws ActionAbortedException;
       
   507 
       
   508     /**
       
   509      * Prompts the user to enter data for each of the given {@link
       
   510      * LoginProperty}s required for user or role authentication by the server.
       
   511      *
       
   512      * @param	    request
       
   513      *		    the {@link LoginRequest} encapsulating the preset values and
       
   514      *		    editability of each core {@link LoginProperty}
       
   515      *
       
   516      * @param	    properties
       
   517      *		    a {@code List} of {@link LoginProperty}s from the server
       
   518      *		    ({@code LoginProperty<String>} or {@code
       
   519      *		    PasswordLoginProperty} only)
       
   520      *
       
   521      * @exception   ActionAbortedException
       
   522      *		    if the user cancels the operation
       
   523      *
       
   524      * @exception   ActionRegressedException
       
   525      *		    if the chooses to re-edit the host or user fields
       
   526      *
       
   527      * @param	    isZone
       
   528      *		    {@code true} if prompting for a zone user or role
       
   529      *		    authentication
       
   530      *
       
   531      * @param	    isUserAuth
       
   532      *		    {@code true} if for user authentication, {@code false} if
       
   533      *		    for role authentication
       
   534      *
       
   535      * @param	    isFirst
       
   536      *		    {@code true} if this is the first prompt in the
       
   537      *		    authentication conversation
       
   538      */
       
   539     protected abstract void promptForAuth(LoginRequest request,
       
   540 	List<LoginProperty> properties, boolean isZone, boolean isUserAuth,
       
   541 	boolean isFirst) throws ActionAbortedException,
       
   542 	ActionRegressedException;
       
   543 
       
   544     /**
       
   545      * Display the given {@code Certificate} details and prompt for user
       
   546      * confirmation to add it to the truststore.
       
   547      *
       
   548      * @param	    host
       
   549      *		    the owner of the {@code Certificate}
       
   550      *
       
   551      * @param	    certificate
       
   552      *		    the {@code Certificate} to verify
       
   553      *
       
   554      * @exception   ActionAbortedException
       
   555      *		    if the user cancels the operation
       
   556      */
       
   557     protected abstract void promptForCertificate(String host,
       
   558 	Certificate certificate) throws ActionAbortedException;
       
   559 
       
   560     /**
       
   561      * Prompts the user to acknowledge failure of the given request.
       
   562      *
       
   563      * @param	    request
       
   564      *		    the {@link LoginRequest} encapsulating the values and
       
   565      *		    error status of each core {@link LoginProperty}
       
   566      */
       
   567     protected abstract void promptForFailedRequest(LoginRequest request);
       
   568 
       
   569     /**
       
   570      * Prompt the user to enter host/user data, subject to the editability and
       
   571      * preset values of the host and user {@link LoginProperty}s of the given
       
   572      * request.
       
   573      *
       
   574      * @param	    request
       
   575      *		    the {@link LoginRequest} encapsulating the preset values and
       
   576      *		    editability of each core {@link LoginProperty}
       
   577      *
       
   578      * @exception   ActionAbortedException
       
   579      *		    if the user cancels the operation
       
   580      */
       
   581     protected abstract void promptForHostAndUser(LoginRequest request)
       
   582 	throws ActionAbortedException;
       
   583 
       
   584     /**
       
   585      * Prompt the user to select a role, subject to the editability and preset
       
   586      * value of the role {@link LoginProperty} of the given request.
       
   587      *
       
   588      * @param	    request
       
   589      *		    the {@link LoginRequest} encapsulating the preset values and
       
   590      *		    editability of each core {@link LoginProperty}
       
   591      *
       
   592      * @param	    roles
       
   593      *		    a list of valid roles for the selected user
       
   594      *
       
   595      * @param	    isZone
       
   596      *		    {@code true} if prompting for a zone role
       
   597      *
       
   598      * @exception   ActionAbortedException
       
   599      *		    if the user cancels the operation
       
   600      *
       
   601      * @exception   ActionRegressedException
       
   602      *		    if the chooses to re-edit the host or user fields
       
   603      */
       
   604     protected abstract void promptForRole(LoginRequest request,
       
   605 	List<String> roles, boolean isZone) throws ActionAbortedException,
       
   606 	ActionRegressedException;
       
   607 
       
   608     /**
       
   609      * Prompt the user to enter zone/user data, subject to the editability and
       
   610      * preset values of the zone and user {@link LoginProperty}s of the given
       
   611      * request.
       
   612      *
       
   613      * @param	    request
       
   614      *		    the {@link LoginRequest} encapsulating the preset values and
       
   615      *		    editability of each core {@link LoginProperty}
       
   616      *
       
   617      * @param	    zones
       
   618      *		    a list of valid zones for the selected host
       
   619      *
       
   620      * @exception   ActionAbortedException
       
   621      *		    if the user cancels the operation
       
   622      *
       
   623      * @exception   ActionRegressedException
       
   624      *		    if the chooses to re-edit the host or user fields
       
   625      */
       
   626     protected abstract void promptForZoneAndUser(LoginRequest request,
       
   627 	List<String> zones) throws ActionAbortedException,
       
   628 	ActionRegressedException;
       
   629 
       
   630     /**
       
   631      * Set login status.
       
   632      *
       
   633      * @param	    request
       
   634      *		    the {@link LoginRequest} encapsulating the preset values of
       
   635      *		    each core {@link LoginProperty}
       
   636      *
       
   637      * @param	    status
       
   638      *		    the login status
       
   639      */
       
   640     protected abstract void setLoginStatus(LoginRequest request, String status);
       
   641 
       
   642     //
       
   643     // Private methods
       
   644     //
       
   645 
       
   646     @SuppressWarnings({"fallthrough"})
       
   647     private boolean authConverse(LoginRequest request,
       
   648 	AuthenticationMXBean auth, AuthPrompter prompter)
       
   649 	throws ActionAbortedException, ActionRegressedException {
       
   650 
       
   651 	List<DialogMessage> messages = request.getMessages();
       
   652 	boolean isFirst = true;
       
   653 
       
   654 	try {
       
   655 	    Block answer = prompter.initiate(request, auth);
       
   656 
       
   657 	    List<LoginProperty> properties =
       
   658 		new LinkedList<LoginProperty>();
       
   659 
       
   660 	    BlockType type;
       
   661 	    while (true) {
       
   662 		properties.clear();
       
   663 
       
   664 		switch (type = answer.getType()) {
       
   665 		case SUCCESS:
       
   666 		    // Display any lingering messages from the server
       
   667 		    if (!messages.isEmpty()) {
       
   668 			prompter.prompt(request, properties, isFirst);
       
   669 			isFirst = false;
       
   670 			messages.clear();
       
   671 		    }
       
   672 
       
   673 		    auth.complete();
       
   674 		    return true;
       
   675 
       
   676 		case ERROR:
       
   677 		    return false;
       
   678 
       
   679 		default:
       
   680 		    assert type == BlockType.CONV;
       
   681 		case CONV:
       
   682 		    for (Message m : answer.getMessages()) {
       
   683 			String text = m.getMessage();
       
   684 			switch (m.getStyle()) {
       
   685 			case PROMPT_ECHO_OFF:
       
   686 			    properties.add(new PasswordLoginProperty(
       
   687 				text, new char[0], true));
       
   688 			    break;
       
   689 
       
   690 			case PROMPT_ECHO_ON:
       
   691 			    properties.add(new LoginProperty<String>(
       
   692 				text, "", true));
       
   693 			    break;
       
   694 
       
   695 			case ERROR_MSG:
       
   696 			    messages.add(new DialogMessage(text,
       
   697 				JOptionPane.ERROR_MESSAGE));
       
   698 			    break;
       
   699 
       
   700 			case TEXT_INFO:
       
   701 			    messages.add(new DialogMessage(text,
       
   702 				JOptionPane.INFORMATION_MESSAGE));
       
   703 			    break;
       
   704 			}
       
   705 		    }
       
   706 
       
   707 		    if (!properties.isEmpty()) {
       
   708 			prompter.prompt(request, properties, isFirst);
       
   709 			isFirst = false;
       
   710 			messages.clear();
       
   711 		    }
       
   712 
       
   713 		    List<char[]> response = new LinkedList<char[]>();
       
   714 		    for (LoginProperty property : properties) {
       
   715 			Object value = property.getValue();
       
   716 			if (value != null) {
       
   717 			    response.add(value instanceof char[] ?
       
   718 				(char[])value : ((String)value).toCharArray());
       
   719 			}
       
   720 		    }
       
   721 
       
   722 		    answer = auth.submit(response);
       
   723 		    // clear out passwords
       
   724 		    for (char[] res : response) {
       
   725 			Arrays.fill(res, (char)0);
       
   726 			res = null;
       
   727 		    }
       
   728 		}
       
   729 	    }
       
   730 	} catch (ObjectException e) {
       
   731 	    messages.add(new DialogMessage(
       
   732 		Finder.getString("login.err.io",
       
   733 		request.getHost().getValue()),
       
   734 		JOptionPane.ERROR_MESSAGE));
       
   735 	    return false;
       
   736 	}
       
   737     }
       
   738 
       
   739     private <T> T createMXBeanProxy(LoginRequest request, ConnectionInfo info,
       
   740 	Class<T> ifaceClass, Stability s, String domain, String name)
       
   741 	throws ActionFailedException {
       
   742 
       
   743 	MBeanServerConnection mbsc = getMBeanServerConnection(request, info);
       
   744 	if (mbsc == null) {
       
   745 	    return null;
       
   746 	}
       
   747 
       
   748 	ObjectName oName = MBeanUtil.makeObjectName(domain, name);
       
   749 
       
   750 	try {
       
   751 	    return ifaceClass.cast(RadJMX.newMXBeanProxy(mbsc, oName,
       
   752 		ifaceClass, s));
       
   753 	} catch (IncompatibleVersionException e) {
       
   754 	    List<DialogMessage> messages = request.getMessages();
       
   755 	    messages.add(new DialogMessage(Finder.getString(
       
   756 		"proxy.error.version", e.getClientVersion(),
       
   757 		e.getServerVersion(), ifaceClass.getSimpleName()),
       
   758 		JOptionPane.ERROR_MESSAGE));
       
   759 	    requestFailed(request);
       
   760 	} catch (JMException e) {
       
   761 	    List<DialogMessage> messages = request.getMessages();
       
   762 	    messages.add(new DialogMessage(Finder.getString(
       
   763 		"proxy.error.general", oName),
       
   764 		JOptionPane.ERROR_MESSAGE));
       
   765 	    requestFailed(request);
       
   766 	} catch (IOException e) {
       
   767 	    List<DialogMessage> messages = request.getMessages();
       
   768 	    messages.add(new DialogMessage(Finder.getString(
       
   769 		"proxy.error.io", ifaceClass.getSimpleName()),
       
   770 		JOptionPane.ERROR_MESSAGE));
       
   771 	    requestFailed(request);
       
   772 	}
       
   773 	return null;
       
   774     }
       
   775 
       
   776     private AuthenticationMXBean createAuthBean(LoginRequest request,
       
   777 	ConnectionInfo info) throws ActionFailedException {
       
   778 	return createMXBeanProxy(request, info, AuthenticationMXBean.class,
       
   779 	    Stability.PRIVATE, "com.oracle.solaris.rad.pam", "Authentication");
       
   780     }
       
   781 
       
   782     private JMXConnector createConnector(String host)
       
   783 	throws KeyStoreException, NoSuchAlgorithmException,
       
   784 	CertificateException, MalformedURLException, IOException,
       
   785 	ActionAbortedException {
       
   786 
       
   787 	if (NetUtil.isLocalAddress(host)) {
       
   788 	    String[] paths = {
       
   789 		RAD_PATH_AFUNIX_AUTH,
       
   790 		RAD_PATH_AFUNIX_UNAUTH
       
   791 	    };
       
   792 
       
   793 	    for (String path : paths) {
       
   794 		JMXServiceURL url = null;
       
   795 		try {
       
   796 		    url = new JMXServiceURL(RadConnector.PROTOCOL_UNIX, "", 0,
       
   797 			path);
       
   798 		    return JMXConnectorFactory.connect(url);
       
   799 		} catch (IOException e) {
       
   800 		    // Not necessarily an error
       
   801 		    Logger.getLogger(getClass().getName()).log(Level.CONFIG,
       
   802 			"unable to utilize local AF_UNIX connector: " +
       
   803 			(url == null ? path : url), e);
       
   804 		}
       
   805 	    }
       
   806 	}
       
   807 
       
   808 	File truststore = getTrustStoreFile();
       
   809 	if (!truststore.exists()) {
       
   810 	    createTrustStore(truststore);
       
   811 	}
       
   812 
       
   813 	Map<String, Object> env = new HashMap<String, Object>();
       
   814 	env.put(RadConnector.KEY_TLS_TRUSTSTORE,
       
   815 	    truststore.getAbsolutePath());
       
   816 	env.put(RadConnector.KEY_TLS_TRUSTPASS,
       
   817 	    getTrustStorePassword());
       
   818 
       
   819 	JMXServiceURL url = new JMXServiceURL(
       
   820 	    RadConnector.PROTOCOL_TLS, host, 0);
       
   821 
       
   822 	// Throws MalformedURLException
       
   823 	JMXConnector connector = JMXConnectorFactory.newJMXConnector(url, null);
       
   824 
       
   825 	for (;;) {
       
   826 	    RadTrustManager mtm = new RadTrustManager();
       
   827 	    env.put(RadConnector.KEY_TLS_RADMANAGER, mtm);
       
   828 
       
   829 	    try {
       
   830 		connector.connect(env);
       
   831 		break;
       
   832 	    } catch (IOException e) {
       
   833 		X509Certificate[] chain = mtm.getBadChain();
       
   834 		if (chain == null) {
       
   835 		    throw e;
       
   836 		}
       
   837 
       
   838 		if (!handleCertFailure(host, truststore, chain[0])) {
       
   839 		    throw e;
       
   840 		}
       
   841 	    }
       
   842 	}
       
   843 
       
   844 	return connector;
       
   845     }
       
   846 
       
   847     private JMXConnector createConnector(LoginRequest request)
       
   848 	throws ActionAbortedException {
       
   849 
       
   850 	JMXConnector connector = null;
       
   851 	LoginProperty<String> host = request.getHost();
       
   852 	String hostVal = host.getValue();
       
   853 	List<DialogMessage> messages = request.getMessages();
       
   854 	boolean success = false;
       
   855 
       
   856 	try {
       
   857 	    setLoginStatus(request, Finder.getString("login.status.host",
       
   858 		hostVal));
       
   859 
       
   860 	    connector = createConnector(hostVal);
       
   861 	    success = true;
       
   862 
       
   863 	// Thrown by JMXConnector.connect
       
   864 	} catch (UnknownHostException e) {
       
   865 	    messages.add(new DialogMessage(Finder.getString(
       
   866 		"login.err.host.unknown", hostVal), JOptionPane.ERROR_MESSAGE));
       
   867 
       
   868 	// Thrown by JMXConnector.connect
       
   869 	} catch (ConnectException e) {
       
   870 	    messages.add(new DialogMessage(Finder.getString(
       
   871 		"login.err.host.refused", hostVal), JOptionPane.ERROR_MESSAGE));
       
   872 
       
   873 	// Thrown by JMXConnector.connect
       
   874 	} catch (SecurityException e) {
       
   875 	    messages.add(new DialogMessage(Finder.getString(
       
   876 		"login.err.security", hostVal),
       
   877 		JOptionPane.ERROR_MESSAGE));
       
   878 
       
   879 	// Thrown by createTrustStore
       
   880 	} catch (KeyStoreException e) {
       
   881 	    messages.add(new DialogMessage(Finder.getString(
       
   882 		"login.err.keystore", e.getMessage()),
       
   883 		JOptionPane.ERROR_MESSAGE));
       
   884 
       
   885 	// Thrown by createTrustStore
       
   886 	} catch (NoSuchAlgorithmException e) {
       
   887 	    messages.add(new DialogMessage(Finder.getString(
       
   888 		"login.err.keystore", e.getMessage()),
       
   889 		JOptionPane.ERROR_MESSAGE));
       
   890 
       
   891 	// Thrown by getDaemonCertificateChain
       
   892 	} catch (CertificateException e) {
       
   893 	    messages.add(new DialogMessage(Finder.getString(
       
   894 		"login.err.nocerts", hostVal),
       
   895 		JOptionPane.ERROR_MESSAGE));
       
   896 
       
   897 	// Thrown by new JMXServiceURL
       
   898 	} catch (MalformedURLException e) {
       
   899 	    messages.add(new DialogMessage(Finder.getString(
       
   900 		"login.err.url.invalid"),
       
   901 		JOptionPane.ERROR_MESSAGE));
       
   902 
       
   903 	// Thrown by JMXConnector.connect et al
       
   904 	} catch (IOException e) {
       
   905 	    messages.add(new DialogMessage(Finder.getString(
       
   906 		"login.err.io", hostVal),
       
   907 		JOptionPane.ERROR_MESSAGE));
       
   908 
       
   909 	} finally {
       
   910 	    if (!success) {
       
   911 		host.setErrored(true);
       
   912 	    }
       
   913 	}
       
   914 
       
   915 	return connector;
       
   916     }
       
   917 
       
   918     private IOMXBean createZonesBridgeBean(LoginRequest request,
       
   919 	ConnectionInfo info) throws ActionFailedException {
       
   920 	return createMXBeanProxy(request, info, IOMXBean.class,
       
   921 	    Stability.PRIVATE, "com.oracle.solaris.rad.zonesbridge", "IO");
       
   922     }
       
   923 
       
   924     private UtilMXBean createZonesUtilBean(LoginRequest request,
       
   925 	ConnectionInfo info) throws ActionFailedException {
       
   926 	return createMXBeanProxy(request, info, UtilMXBean.class,
       
   927 	    Stability.PRIVATE, "com.oracle.solaris.rad.zonesbridge", "Util");
       
   928     }
       
   929 
       
   930     private JMXConnector createZoneConnector(LoginRequest request,
       
   931 	IOMXBean bean) {
       
   932 
       
   933 	JMXConnector connector = null;
       
   934 	LoginProperty<String> zone = request.getZone();
       
   935 	String zoneVal = zone.getValue();
       
   936 	String zoneUserVal = request.getZoneUser().getValue();
       
   937 	List<DialogMessage> messages = request.getMessages();
       
   938 	boolean success = false;
       
   939 
       
   940 	try {
       
   941 	    JMXServiceURL url = new JMXServiceURL(
       
   942 		RadConnector.PROTOCOL_ZONESBRIDGE, zoneVal, 0,
       
   943 		"/" + zoneUserVal);
       
   944 
       
   945 	    Map<String, Object> env = new HashMap<String, Object>();
       
   946 	    env.put(RadConnector.KEY_ZONESBRIDGE_MXBEAN, bean);
       
   947 
       
   948 	    connector = JMXConnectorFactory.connect(url, env);
       
   949 	    success = true;
       
   950 
       
   951 	// Thrown by JMXConnector.connect
       
   952 	} catch (SecurityException e) {
       
   953 	    messages.add(new DialogMessage(Finder.getString(
       
   954 		"login.err.zone.security", zoneVal, zoneUserVal),
       
   955 		JOptionPane.ERROR_MESSAGE));
       
   956 
       
   957 	// Thrown by new JMXServiceURL
       
   958 	} catch (MalformedURLException e) {
       
   959 	    messages.add(new DialogMessage(Finder.getString(
       
   960 		"login.err.url.invalid"), JOptionPane.ERROR_MESSAGE));
       
   961 
       
   962 	// Thrown by JMXConnector.connect et al
       
   963 	} catch (IOException e) {
       
   964 	    messages.add(new DialogMessage(Finder.getString(
       
   965 		"login.err.io", zoneVal), JOptionPane.ERROR_MESSAGE));
       
   966 
       
   967 	} finally {
       
   968 	    if (!success) {
       
   969 		zone.setErrored(true);
       
   970 	    }
       
   971 	}
       
   972 
       
   973 	return connector;
       
   974     }
       
   975 
       
   976     private void gatherHostAndUserData(LoginRequest request, LoginData data)
       
   977 	throws ActionAbortedException, ActionFailedException {
       
   978 
       
   979 	LoginProperty<String> host = request.getHost();
       
   980 	LoginProperty<String> user = request.getUser();
       
   981 	LoginProperty<Boolean> zonePrompt = request.getZonePrompt();
       
   982 	List<DialogMessage> messages = request.getMessages();
       
   983 
       
   984 	// Validate any preset values prior to prompting user
       
   985 	if (host.getValue() != null || !host.isEditable()) {
       
   986 	    isHostValid(request);
       
   987 	}
       
   988 	if (user.getValue() != null || !user.isEditable()) {
       
   989 	    isUserValid(request);
       
   990 	}
       
   991 	if (zonePrompt.getValue() != null || !zonePrompt.isEditable()) {
       
   992 	    isPropertyValid(zonePrompt, request);
       
   993 	}
       
   994 
       
   995 	// Loop until connected to host and authenticated as user
       
   996 	while (true) {
       
   997 	    boolean acknowledged = false;
       
   998 
       
   999 	    // Refresh each iteration in case a prop isEditableOnError()
       
  1000 	    boolean hostEditable = host.isEditable();
       
  1001 	    boolean userEditable = user.isEditable();
       
  1002 	    boolean zonePromptEditable = zonePrompt.isEditable();
       
  1003 
       
  1004 	    // If an error cannot be fixed by the user...
       
  1005 	    if ((!zonePromptEditable && zonePrompt.isErrored()) ||
       
  1006 		(!hostEditable && (host.isErrored() ||
       
  1007 		(!userEditable && user.isErrored())))) {
       
  1008 
       
  1009 		requestFailed(request);
       
  1010 	    }
       
  1011 
       
  1012 	    if (hostEditable || userEditable || zonePromptEditable ||
       
  1013 		!messages.isEmpty()) {
       
  1014 
       
  1015 		try {
       
  1016 		    promptForHostAndUser(request);
       
  1017 		} finally {
       
  1018 		    acknowledged = true;
       
  1019 
       
  1020 		    // User only needs to see any message once, presumably
       
  1021 		    messages.clear();
       
  1022 
       
  1023 		    host.setErrored(false);
       
  1024 		    user.setErrored(false);
       
  1025 		    zonePrompt.setErrored(false);
       
  1026 		}
       
  1027 
       
  1028 		if (!isHostValid(request) || !isUserValid(request) ||
       
  1029 		    !isPropertyValid(zonePrompt, request)) {
       
  1030 		    continue;
       
  1031 		}
       
  1032 	    }
       
  1033 
       
  1034 	    String hostVal = host.getValue();
       
  1035 	    String userVal = user.getValue();
       
  1036 
       
  1037 	    // Search for existing connection
       
  1038 	    List<ConnectionInfo> depChain =
       
  1039 		getConnectionManager().getDepChain(hostVal, userVal, null, null,
       
  1040 		null, null);
       
  1041 	    if (depChain != null) {
       
  1042 		data.setDepChain(depChain, acknowledged);
       
  1043 		return;
       
  1044 	    }
       
  1045 
       
  1046 	    // Create connection, append to messages on error
       
  1047 	    JMXConnector connector = createConnector(request);
       
  1048 	    if (connector != null) {
       
  1049 		ConnectionInfo info = new ConnectionInfo(hostVal, userVal, null,
       
  1050 		    connector);
       
  1051 
       
  1052 		// Get/create auth bean, append to messages on error
       
  1053 		AuthenticationMXBean auth = createAuthBean(request, info);
       
  1054 		if (auth != null) {
       
  1055 		    setLoginStatus(request,
       
  1056 			Finder.getString("login.status.user"));
       
  1057 
       
  1058 		    if (userVal.equals(auth.getuser())) {
       
  1059 			data.push(info, acknowledged);
       
  1060 			return;
       
  1061 		    }
       
  1062 
       
  1063 		    try {
       
  1064 			AuthPrompter prompter = new UserPrompter();
       
  1065 			do {
       
  1066 			    if (authConverse(request, auth, prompter)) {
       
  1067 				acknowledged |= prompter.isAcknowledged();
       
  1068 				data.push(info, acknowledged);
       
  1069 				return;
       
  1070 			    }
       
  1071 
       
  1072 			    // Authentication failed
       
  1073 			    user.setErrored(true);
       
  1074 
       
  1075 			    // Add generic auth failure message if not already
       
  1076 			    // provided by server
       
  1077 			    if (messages.isEmpty()) {
       
  1078 				messages.add(new DialogMessage(
       
  1079 				    Finder.getString("login.err.user.auth",
       
  1080 				    hostVal, userVal),
       
  1081 				    JOptionPane.ERROR_MESSAGE));
       
  1082 			    }
       
  1083 
       
  1084 			// No chance to edit, so keep iterating here
       
  1085 			} while (!host.isEditable() && !user.isEditable());
       
  1086 
       
  1087 		    // Thrown by authConverse
       
  1088 		    } catch (ActionRegressedException e) {
       
  1089 		    }
       
  1090 		}
       
  1091 	    }
       
  1092 	}
       
  1093     }
       
  1094 
       
  1095     private void gatherRoleData(LoginRequest request, LoginData data)
       
  1096 	throws ActionAbortedException, ActionFailedException,
       
  1097 	ActionRegressedException {
       
  1098 
       
  1099 	LoginProperty<String> host = request.getHost();
       
  1100 	String hostVal = host.getValue();
       
  1101 
       
  1102 	LoginProperty<String> user = request.getUser();
       
  1103 	String userVal = user.getValue();
       
  1104 
       
  1105 	LoginProperty<String> zone = request.getZone();
       
  1106 	String zoneVal = zone.getValue();
       
  1107 
       
  1108 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1109 	String zoneUserVal = zoneUser.getValue();
       
  1110 
       
  1111 	// Get/create auth bean, append to messages on error
       
  1112 	AuthenticationMXBean userAuth = createAuthBean(request, data.peek(0));
       
  1113 	if (userAuth == null) {
       
  1114 	    // Not likely, but handle it anyway
       
  1115 	    requestFailed(request);
       
  1116 	}
       
  1117 
       
  1118 	LoginProperty<String> role = request.getRole();
       
  1119 	List<DialogMessage> messages = request.getMessages();
       
  1120 
       
  1121 	setLoginStatus(request, Finder.getString("login.status.roles",
       
  1122 	    request.getUser().getValue()));
       
  1123 	List<String> roles = userAuth.getroles();
       
  1124 
       
  1125 	// Validate any preset value prior to prompting user
       
  1126 	if (role.getValue() != null || !role.isEditable()) {
       
  1127 	    isRoleValid(request, roles, false);
       
  1128 	}
       
  1129 
       
  1130 	// Loop until no role is chosen, or chosen role is authenticated
       
  1131 	while (true) {
       
  1132 	    boolean acknowledged = false;
       
  1133 
       
  1134 	    // Refresh each iteration in case role.isEditableOnError()
       
  1135 	    boolean roleEditable = role.isEditable();
       
  1136 
       
  1137 	    // If an error cannot be fixed by the user...
       
  1138 	    if (!roleEditable && role.isErrored()) {
       
  1139 		requestFailed(request);
       
  1140 	    }
       
  1141 
       
  1142 	    if ((roleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
       
  1143 		try {
       
  1144 		    promptForRole(request, roles, false);
       
  1145 		} finally {
       
  1146 		    acknowledged = true;
       
  1147 
       
  1148 		    // User only needs to see any message once, presumably
       
  1149 		    messages.clear();
       
  1150 
       
  1151 		    role.setErrored(false);
       
  1152 		}
       
  1153 
       
  1154 		if (!isRoleValid(request, roles, false)) {
       
  1155 		    continue;
       
  1156 		}
       
  1157 	    }
       
  1158 
       
  1159 	    String roleVal = role.getValue();
       
  1160 	    if (roleVal == null) {
       
  1161 		// No need to keep going
       
  1162 		return;
       
  1163 	    }
       
  1164 
       
  1165 	    // Search for existing connection
       
  1166 	    List<ConnectionInfo> depChain = getConnectionManager().getDepChain(
       
  1167 		hostVal, userVal, roleVal, null, null, null);
       
  1168 	    if (depChain != null) {
       
  1169 		data.setDepChain(depChain, acknowledged);
       
  1170 		return;
       
  1171 	    }
       
  1172 
       
  1173 	    try {
       
  1174 		byte[] token = userAuth.createToken();
       
  1175 
       
  1176 		// Create connection, append to messages on error
       
  1177 		JMXConnector connector = createConnector(request);
       
  1178 		if (connector != null) {
       
  1179 		    ConnectionInfo info = new ConnectionInfo(hostVal, userVal,
       
  1180 			roleVal, connector);
       
  1181 
       
  1182 		    // Create auth bean, append to messages on error
       
  1183 		    AuthenticationMXBean roleAuth = createAuthBean(request,
       
  1184 			info);
       
  1185 		    if (roleAuth != null) {
       
  1186 			roleAuth.redeemToken(userVal, token);
       
  1187 
       
  1188 			AuthPrompter prompter = new RolePrompter();
       
  1189 			do {
       
  1190 			    if (authConverse(request, roleAuth, prompter)) {
       
  1191 				acknowledged |= prompter.isAcknowledged();
       
  1192 				data.push(info, acknowledged);
       
  1193 				return;
       
  1194 			    }
       
  1195 
       
  1196 			    // Authentication failed
       
  1197 			    role.setErrored(true);
       
  1198 
       
  1199 			    // Add generic auth failure message if not already
       
  1200 			    // provided by server
       
  1201 			    if (messages.isEmpty()) {
       
  1202 				messages.add(new DialogMessage(
       
  1203 				    Finder.getString("login.err.role.auth",
       
  1204 				    hostVal, userVal, roleVal),
       
  1205 				    JOptionPane.ERROR_MESSAGE));
       
  1206 			    }
       
  1207 
       
  1208 			// No chance to edit role, so keep iterating here
       
  1209 			} while (!role.isEditable());
       
  1210 		    }
       
  1211 		}
       
  1212 
       
  1213 	    // Thrown by createToken/redeemToken
       
  1214 	    } catch (ObjectException e) {
       
  1215 		messages.add(new DialogMessage(Finder.getString(
       
  1216 		    "login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
       
  1217 
       
  1218 	    // Thrown by authConverse
       
  1219 	    } catch (ActionRegressedException e) {
       
  1220 	    }
       
  1221 	}
       
  1222     }
       
  1223 
       
  1224     private void gatherZoneHostAndUserData(LoginRequest request, LoginData data)
       
  1225 	throws ActionAbortedException, ActionFailedException,
       
  1226 	ActionRegressedException {
       
  1227 
       
  1228 	IOMXBean zcon = createZonesBridgeBean(request, data.peek(0));
       
  1229 	UtilMXBean zutil = createZonesUtilBean(request, data.peek(0));
       
  1230 	if (zcon == null || zutil == null) {
       
  1231 	    requestFailed(request);
       
  1232 	}
       
  1233 
       
  1234 	LoginProperty<String> zone = request.getZone();
       
  1235 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1236 	List<DialogMessage> messages = request.getMessages();
       
  1237 
       
  1238 	List<String> zones = null;
       
  1239 	try {
       
  1240 	    zones = zutil.getZones(ZoneState.RUNNING);
       
  1241 	} catch (ObjectException e) {
       
  1242 	    messages.add(new DialogMessage(Finder.getString(
       
  1243                 "login.err.io", request.getHost().getValue()),
       
  1244                 JOptionPane.ERROR_MESSAGE));
       
  1245 	    requestFailed(request);
       
  1246 	}
       
  1247 
       
  1248 	// Validate any preset value prior to prompting user
       
  1249 	if (zone.getValue() != null || !zone.isEditable()) {
       
  1250 	    isZoneValid(request, zones);
       
  1251 	}
       
  1252 	if (zoneUser.getValue() != null || !zoneUser.isEditable()) {
       
  1253 	    isZoneUserValid(request);
       
  1254 	}
       
  1255 
       
  1256 	// Loop until connected to zone and authenticated as zoneUser
       
  1257 	while (true) {
       
  1258 	    boolean acknowledged = false;
       
  1259 
       
  1260 	    // Refresh each iteration in case zone/zoneUser.isEditableOnError()
       
  1261 	    boolean zoneEditable = zone.isEditable();
       
  1262 	    boolean zoneUserEditable = zoneUser.isEditable();
       
  1263 
       
  1264 	    // If an error cannot be fixed by the user...
       
  1265 	    if (!zoneEditable && (zone.isErrored() ||
       
  1266 		(!zoneUserEditable && zoneUser.isErrored()))) {
       
  1267 
       
  1268 		requestFailed(request);
       
  1269 	    }
       
  1270 
       
  1271 	    if (zoneEditable || zoneUserEditable || !messages.isEmpty()) {
       
  1272 		try {
       
  1273 		    promptForZoneAndUser(request, zones);
       
  1274 		} finally {
       
  1275 		    acknowledged = true;
       
  1276 
       
  1277 		    // User only needs to see any message once, presumably
       
  1278 		    messages.clear();
       
  1279 
       
  1280 		    zone.setErrored(false);
       
  1281 		    zoneUser.setErrored(false);
       
  1282 		}
       
  1283 
       
  1284 		if (!isZoneValid(request, zones) || !isZoneUserValid(request)) {
       
  1285 		    continue;
       
  1286 		}
       
  1287 	    }
       
  1288 
       
  1289 	    String zoneVal = zone.getValue();
       
  1290 	    if (zoneVal == null) {
       
  1291 		// No need to keep going
       
  1292 		return;
       
  1293 	    }
       
  1294 
       
  1295 	    String zoneUserVal = zoneUser.getValue();
       
  1296 
       
  1297 	    // Search for existing connection
       
  1298 	    List<ConnectionInfo> depChain =
       
  1299 		getConnectionManager().getDepChain(
       
  1300 		request.getHost().getValue(), request.getUser().getValue(),
       
  1301 		request.getRole().getValue(), zoneVal, zoneUserVal, null);
       
  1302 	    if (depChain != null) {
       
  1303 		data.setDepChain(depChain, acknowledged);
       
  1304 		return;
       
  1305 	    }
       
  1306 
       
  1307 	    JMXConnector connector = createZoneConnector(request, zcon);
       
  1308 	    if (connector != null) {
       
  1309 		ConnectionInfo info = new ConnectionInfo(
       
  1310 		    request.getHost().getValue(), request.getUser().getValue(),
       
  1311 		    request.getRole().getValue(), zoneVal, zoneUserVal, null,
       
  1312 		    connector);
       
  1313 
       
  1314 		// Get/create auth bean, append to messages on error
       
  1315 		AuthenticationMXBean auth = createAuthBean(request, info);
       
  1316 		if (auth != null) {
       
  1317 		    if (zoneUserVal.equals(auth.getuser())) {
       
  1318 			data.push(info, acknowledged);
       
  1319 			return;
       
  1320 		    }
       
  1321 
       
  1322 		    try {
       
  1323 			AuthPrompter prompter = new ZoneUserPrompter();
       
  1324 			do {
       
  1325 			    if (authConverse(request, auth, prompter)) {
       
  1326 				acknowledged |= prompter.isAcknowledged();
       
  1327 				data.push(info, acknowledged);
       
  1328 				return;
       
  1329 			    }
       
  1330 
       
  1331 			    // Authentication failed
       
  1332 			    zoneUser.setErrored(true);
       
  1333 
       
  1334 			    // Add generic auth failure message if not already
       
  1335 			    // provided by server
       
  1336 			    if (messages.isEmpty()) {
       
  1337 				messages.add(new DialogMessage(
       
  1338 				    Finder.getString("login.err.user.auth",
       
  1339 				    zoneVal, zoneUserVal),
       
  1340 				    JOptionPane.ERROR_MESSAGE));
       
  1341 			    }
       
  1342 
       
  1343 			// No chance to edit, so keep iterating here
       
  1344 			} while (!zone.isEditable() && !zoneUser.isEditable());
       
  1345 
       
  1346 		    // Thrown by authConverse
       
  1347 		    } catch (ActionRegressedException e) {
       
  1348 		    }
       
  1349 		}
       
  1350 	    }
       
  1351 	}
       
  1352     }
       
  1353 
       
  1354     private void gatherZoneRoleData(LoginRequest request, LoginData data)
       
  1355 	throws ActionAbortedException, ActionFailedException,
       
  1356 	ActionRegressedException {
       
  1357 
       
  1358 	LoginProperty<String> host = request.getHost();
       
  1359 	String hostVal = host.getValue();
       
  1360 
       
  1361 	LoginProperty<String> user = request.getUser();
       
  1362 	String userVal = user.getValue();
       
  1363 
       
  1364 	LoginProperty<String> role = request.getRole();
       
  1365 	String roleVal = role.getValue();
       
  1366 
       
  1367 	LoginProperty<String> zone = request.getZone();
       
  1368 	String zoneVal = zone.getValue();
       
  1369 
       
  1370 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1371 	String zoneUserVal = zoneUser.getValue();
       
  1372 
       
  1373 	// Get/create auth bean, append to messages on error
       
  1374 	AuthenticationMXBean userAuth = createAuthBean(request, data.peek(0));
       
  1375 	if (userAuth == null) {
       
  1376 	    // Not likely, but handle it anyway
       
  1377 	    requestFailed(request);
       
  1378 	}
       
  1379 
       
  1380 	LoginProperty<String> zoneRole = request.getZoneRole();
       
  1381 	List<DialogMessage> messages = request.getMessages();
       
  1382 
       
  1383 	setLoginStatus(request, Finder.getString("login.status.roles",
       
  1384 	    request.getZoneUser().getValue()));
       
  1385 	List<String> roles = userAuth.getroles();
       
  1386 
       
  1387 	// Validate any preset value prior to prompting user
       
  1388 	if (zoneRole.getValue() != null || !zoneRole.isEditable()) {
       
  1389 	    isRoleValid(request, roles, true);
       
  1390 	}
       
  1391 
       
  1392 	IOMXBean zcon = null;
       
  1393 
       
  1394 	// Loop until no role is chosen, or chosen role is authenticated
       
  1395 	while (true) {
       
  1396 	    boolean acknowledged = false;
       
  1397 
       
  1398 	    // Refresh each iteration in case zoneRole.isEditableOnError()
       
  1399 	    boolean zoneRoleEditable = zoneRole.isEditable();
       
  1400 
       
  1401 	    // If an error cannot be fixed by the user...
       
  1402 	    if (!zoneRoleEditable && zoneRole.isErrored()) {
       
  1403 		requestFailed(request);
       
  1404 	    }
       
  1405 
       
  1406 	    if ((zoneRoleEditable && !roles.isEmpty()) || !messages.isEmpty()) {
       
  1407 		try {
       
  1408 		    promptForRole(request, roles, true);
       
  1409 		} finally {
       
  1410 		    acknowledged = true;
       
  1411 
       
  1412 		    // User only needs to see any message once, presumably
       
  1413 		    messages.clear();
       
  1414 
       
  1415 		    zoneRole.setErrored(false);
       
  1416 		}
       
  1417 
       
  1418 		if (!isRoleValid(request, roles, true)) {
       
  1419 		    continue;
       
  1420 		}
       
  1421 	    }
       
  1422 
       
  1423 	    String zoneRoleVal = zoneRole.getValue();
       
  1424 	    if (zoneRoleVal == null) {
       
  1425 		// No need to keep going
       
  1426 		return;
       
  1427 	    }
       
  1428 
       
  1429 	    // Search for existing connection
       
  1430 	    List<ConnectionInfo> depChain = getConnectionManager().getDepChain(
       
  1431 		hostVal, userVal, roleVal, zoneVal, zoneUserVal, zoneRoleVal);
       
  1432 	    if (depChain != null) {
       
  1433 		data.setDepChain(depChain, acknowledged);
       
  1434 		return;
       
  1435 	    }
       
  1436 
       
  1437 	    try {
       
  1438 		byte[] token = userAuth.createToken();
       
  1439 
       
  1440 		if (zcon == null) {
       
  1441 		    // Peek back to the host-based connection
       
  1442 		    zcon = createZonesBridgeBean(request, data.peek(1));
       
  1443 		    if (zcon == null) {
       
  1444 			requestFailed(request);
       
  1445 		    }
       
  1446 		}
       
  1447 
       
  1448 		// Create connection, append to messages on error
       
  1449 		JMXConnector connector = createZoneConnector(request, zcon);
       
  1450 		if (connector != null) {
       
  1451 		    ConnectionInfo info = new ConnectionInfo(hostVal, userVal,
       
  1452 			roleVal, zoneVal, zoneUserVal, zoneRoleVal, connector);
       
  1453 
       
  1454 		    // Create auth bean, append to messages on error
       
  1455 		    AuthenticationMXBean roleAuth = createAuthBean(request,
       
  1456 			info);
       
  1457 		    if (roleAuth != null) {
       
  1458 			roleAuth.redeemToken(userVal, token);
       
  1459 
       
  1460 			AuthPrompter prompter = new ZoneRolePrompter();
       
  1461 			do {
       
  1462 			    if (authConverse(request, roleAuth, prompter)) {
       
  1463 				acknowledged |= prompter.isAcknowledged();
       
  1464 				data.push(info, acknowledged);
       
  1465 				return;
       
  1466 			    }
       
  1467 
       
  1468 			    // Authentication failed
       
  1469 			    zoneRole.setErrored(true);
       
  1470 
       
  1471 			    // Add generic auth failure message if not already
       
  1472 			    // provided by server
       
  1473 			    if (messages.isEmpty()) {
       
  1474 				messages.add(new DialogMessage(
       
  1475 				    Finder.getString("login.err.zonerole.auth",
       
  1476 				    hostVal, userVal, roleVal, zoneVal,
       
  1477 				    zoneUserVal, zoneRoleVal),
       
  1478 				    JOptionPane.ERROR_MESSAGE));
       
  1479 			    }
       
  1480 
       
  1481 			// No chance to edit role, so keep iterating here
       
  1482 			} while (!zoneRole.isEditable());
       
  1483 		    }
       
  1484 		}
       
  1485 
       
  1486 	    // Thrown by createToken/redeemToken
       
  1487 	    } catch (ObjectException e) {
       
  1488 		messages.add(new DialogMessage(Finder.getString(
       
  1489 		    "login.err.io", hostVal), JOptionPane.ERROR_MESSAGE));
       
  1490 
       
  1491 	    // Thrown by authConverse
       
  1492 	    } catch (ActionRegressedException e) {
       
  1493 	    }
       
  1494 	}
       
  1495     }
       
  1496 
       
  1497     private MBeanServerConnection getMBeanServerConnection(LoginRequest request,
       
  1498 	ConnectionInfo info) {
       
  1499 
       
  1500 	try {
       
  1501 	    return info.getConnector().getMBeanServerConnection();
       
  1502 	} catch (IOException e) {
       
  1503 	    request.getMessages().add(new DialogMessage(
       
  1504 		Finder.getString("login.err.io",
       
  1505 		request.getHost().getValue()),
       
  1506 		JOptionPane.ERROR_MESSAGE));
       
  1507 	}
       
  1508 	return null;
       
  1509     }
       
  1510 
       
  1511     private <T> boolean inSet(LoginProperty<T> property, List<T> valid,
       
  1512 	String resource, LoginRequest request) {
       
  1513 
       
  1514 	if (!valid.contains(property.getValue())) {
       
  1515 	    String message = Finder.getString(resource,
       
  1516 		request.getHost().getValue(),
       
  1517 		request.getUser().getValue(),
       
  1518 		request.getRole().getValue(),
       
  1519 		request.getZone().getValue(),
       
  1520 		request.getZoneUser().getValue(),
       
  1521 		request.getZoneRole().getValue());
       
  1522 
       
  1523 	    request.getMessages().add(new DialogMessage(message,
       
  1524 		JOptionPane.ERROR_MESSAGE));
       
  1525 
       
  1526 	    property.setErrored(true);
       
  1527 
       
  1528 	    if (property.isEditable()) {
       
  1529 		property.setValue(null);
       
  1530 	    }
       
  1531 
       
  1532 	    return false;
       
  1533 	}
       
  1534 
       
  1535 	return true;
       
  1536     }
       
  1537 
       
  1538     private boolean isHostValid(LoginRequest request)
       
  1539 	throws ActionFailedException {
       
  1540 
       
  1541 	LoginProperty<String> host = request.getHost();
       
  1542 	return isPropertyNonEmpty(host, request, "login.err.host.empty") &&
       
  1543 	    isPropertyValid(host, request);
       
  1544     }
       
  1545 
       
  1546     private boolean isPropertyNonEmpty(LoginProperty<String> property,
       
  1547 	LoginRequest request, String resource) {
       
  1548 
       
  1549 	String value = property.getValue();
       
  1550 	if (value == null || value.isEmpty()) {
       
  1551 	    request.getMessages().add(new DialogMessage(Finder.getString(
       
  1552 		resource), JOptionPane.ERROR_MESSAGE));
       
  1553 	    property.setErrored(true);
       
  1554 	    return false;
       
  1555 	}
       
  1556 
       
  1557 	return true;
       
  1558     }
       
  1559 
       
  1560     private <T> boolean isPropertyValid(LoginProperty<T> property,
       
  1561 	LoginRequest request, T... valid) throws ActionFailedException {
       
  1562 
       
  1563 	try {
       
  1564 	    property.validate(request, valid);
       
  1565 	    return true;
       
  1566 	} catch (LoginPropertyException e) {
       
  1567 	    property.setErrored(true);
       
  1568 	    request.getMessages().add(e.getDialogMessage());
       
  1569 	    if (e.isFatal()) {
       
  1570 		requestFailed(request);
       
  1571 	    }
       
  1572 	    return false;
       
  1573 	}
       
  1574     }
       
  1575 
       
  1576     private boolean isRoleValid(LoginRequest request, List<String> roles,
       
  1577 	boolean isZone) throws ActionFailedException {
       
  1578 
       
  1579 	LoginProperty<String> role = isZone ? request.getZoneRole() :
       
  1580 	    request.getRole();
       
  1581 
       
  1582 	return (role.getValue() == null ||
       
  1583 	    inSet(role, roles, "login.err.role.invalid", request)) &&
       
  1584 	    isPropertyValid(role, request,
       
  1585 	    roles.toArray(new String[roles.size()]));
       
  1586     }
       
  1587 
       
  1588     private boolean isUserValid(LoginRequest request)
       
  1589 	throws ActionFailedException {
       
  1590 
       
  1591 	LoginProperty<String> user = request.getUser();
       
  1592 	return isPropertyNonEmpty(user, request, "login.err.user.empty") &&
       
  1593 	    isPropertyValid(user, request);
       
  1594     }
       
  1595 
       
  1596     private boolean isZoneValid(LoginRequest request, List<String> zones)
       
  1597 	throws ActionFailedException {
       
  1598 
       
  1599 	LoginProperty<String> zone = request.getZone();
       
  1600 	if (zone.getValue() == null) {
       
  1601 	    request.getMessages().add(new DialogMessage(Finder.getString(
       
  1602 		"login.err.zone.empty"), JOptionPane.ERROR_MESSAGE));
       
  1603 	    zone.setErrored(true);
       
  1604 	    return false;
       
  1605 	}
       
  1606 
       
  1607 	String resource = "login.err.zone.invalid";
       
  1608 	if (request.getRole().getValue() != null) {
       
  1609 	    resource += ".role";
       
  1610 	}
       
  1611 
       
  1612 	return inSet(zone, zones, resource, request) &&
       
  1613 	    isPropertyValid(zone, request,
       
  1614 	    zones.toArray(new String[zones.size()]));
       
  1615     }
       
  1616 
       
  1617     private boolean isZoneUserValid(LoginRequest request)
       
  1618 	throws ActionFailedException {
       
  1619 
       
  1620 	LoginProperty<String> zoneUser = request.getZoneUser();
       
  1621 	return isPropertyNonEmpty(zoneUser, request,
       
  1622 	    "login.err.zoneuser.empty") && isPropertyValid(zoneUser, request);
       
  1623     }
       
  1624 
       
  1625     private void requestFailed(LoginRequest request)
       
  1626 	throws ActionFailedException {
       
  1627 
       
  1628 	promptForFailedRequest(request);
       
  1629 
       
  1630 	List<DialogMessage> messages = request.getMessages();
       
  1631 	throw new ActionFailedException(messages.isEmpty() ? null :
       
  1632 	    messages.get(0).getText());
       
  1633     }
       
  1634 }