/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.jobs; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.Random; import java.util.StringTokenizer; import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPConnection; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; import netscape.ldap.LDAPSearchResults; import netscape.ldap.LDAPSocketFactory; import netscape.ldap.factory.JSSESocketFactory; import com.unboundid.util.FixedRateBarrier; import com.unboundid.util.ValuePattern; import com.slamd.common.SLAMDException; import com.slamd.job.JobClass; import com.slamd.job.UnableToRunException; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.IntegerParameter; import com.slamd.parameter.InvalidValueException; import com.slamd.parameter.MultiChoiceParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.parameter.PasswordParameter; import com.slamd.parameter.PlaceholderParameter; import com.slamd.parameter.StringParameter; import com.slamd.stat.IncrementalTracker; import com.slamd.stat.RealTimeStatReporter; import com.slamd.stat.StatTracker; import com.slamd.stat.TimeTracker; /** * This class defines a SLAMD job that simulates the load that a Solaris 9 * client places on the directory when authenticating telnet users through * pam_ldap. * * * @author Neil A. Wilson */ public class SolarisLDAPAuthRateJobClass extends JobClass { /** * The flag indicating that the "anonymous" credential level should be used. */ public static final int CREDENTIAL_LEVEL_ANONYMOUS = 0; /** * The flag indicating that the "proxy" credential level should be used. */ public static final int CREDENTIAL_LEVEL_PROXY = 1; /** * The human-readable strings corresponding to the credential level constants. */ public static final String[] CREDENTIAL_LEVEL_STRINGS = { "Anonymous", "Proxy" }; /** * The flag indicating that the authentication method should be "simple". */ public static final int AUTH_METHOD_SIMPLE = 0; /** * The flag indicating that the authentication method should be "DIGEST-MD5". */ public static final int AUTH_METHOD_DIGEST_MD5 = 1; /** * The flag indicating that the authentication method should be "simple" * over an SSL connection. */ public static final int AUTH_METHOD_TLS_SIMPLE = 2; /** * The flag indicating that the authentication method should be "DIGEST-MD5" * over an SSL connection. */ public static final int AUTH_METHOD_TLS_DIGEST_MD5 = 3; /** * The human-readable strings corresponding to the authentication method * constants. */ public static final String[] AUTH_METHOD_STRINGS = { "Simple Authentication", "DIGEST-MD5 Authentication", "Simple Authentication over SSL", "DIGEST-MD5 Authentication over SSL" }; /** * The system property used to specify the location of the JSSE key store. */ public static final String SSL_KEY_STORE_PROPERTY = "javax.net.ssl.keyStore"; /** * The system property used to specify the password for the JSSE key store. */ public static final String SSL_KEY_PASSWORD_PROPERTY = "javax.net.ssl.keyStorePassword"; /** * The system property used to specify the location of the JSSE trust store. */ public static final String SSL_TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; /** * The system property used to specify the password for the JSSE trust store. */ public static final String SSL_TRUST_PASSWORD_PROPERTY = "javax.net.ssl.trustStorePassword"; /** * The name of the stat tracker that will be used to count the number of * authentication attempts. */ public static final String STAT_TRACKER_AUTHENTICATION_ATTEMPTS = "Authentication Attempts"; /** * The name of the stat tracker that will be used to keep track of the time * required to perform each authentication. */ public static final String STAT_TRACKER_AUTHENTICATION_TIME = "Authentication Time"; /** * The name of the stat tracker that will be used to count the number of * failed authentications. */ public static final String STAT_TRACKER_FAILED_AUTHENTICATIONS = "Failed Authentications"; /** * The name of the stat tracker that will be used to count the number of * successful authentications. */ public static final String STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS = "Successful Authentications"; // The parameter that indicates whether the client should trust any SSL cert. private BooleanParameter blindTrustParameter = new BooleanParameter("blind_trust", "Blindly Trust Any Certificate", "Indicates whether the client should blindly trust " + "any certificate presented by the server, or " + "whether the key and trust stores should be used.", true); // The parameter that specifies the maximum authentication rate. private IntegerParameter maxRateParameter = new IntegerParameter("maxRate", "Max Authentication Rate (Auths/Second/Client)", "Specifies the maximum authentication rate (in auths per second per " + "client) to attempt to maintain. If multiple clients are used, " + "then each client will attempt to maintain this rate. A value " + "less than or equal to zero indicates that the client should " + "attempt to perform authentications as quickly as possible.", true, -1); // The parameter that specifies the directory server port number. private IntegerParameter portParameter = new IntegerParameter("ldap_port", "Directory Server Port", "The port number of the directory server to " + "use for authenticating clients.", true, 389, true, 1, true, 65535); // The parameter that specifies the interval over which to enforce the maximum // request rate. private IntegerParameter rateLimitDurationParameter = new IntegerParameter( "maxRateDuration", "Max Rate Enforcement Interval (Seconds)", "Specifies the duration in seconds of the interval over which to " + "attempt to maintain the configured maximum rate. A value of " + "zero indicates that it should be equal to the statistics " + "collection interval. Large values may allow more variation but " + "may be more accurate over time. Small values can better " + "ensure that the rate doesn't exceed the requested level but may " + "be less able to achieve the desired rate.", true, 0, true,0, false, 0); // The parameter that specifies the authentication method to use when binding // to the server. private MultiChoiceParameter authMethodParameter = new MultiChoiceParameter("auth_method", "Authentication Method", "The method that should be used when " + "authenticating to the directory server as " + "the end user. If the proxy credential " + "level is chosen, then this method will " + "also be used to authenticate the proxy user.", AUTH_METHOD_STRINGS, null); // The parameter that specifies the credential level to use when finding // the user's account. private MultiChoiceParameter credentialParameter = new MultiChoiceParameter("cred_level", "Credential Level", "The credential level that indicates how " + "the client should bind to the directory " + "server in order to find the user's account " + "to perform the authentication. It may be " + "either anonymous (no authentication) or " + "proxy (authenticate as some proxy user to " + "use when performing the queries)", CREDENTIAL_LEVEL_STRINGS, null); // The parameter that specifies the password for the SSL key store private PasswordParameter keyPWParameter = new PasswordParameter("sslkeypw", "SSL Key Store Password", "The password for the JSSE key store. This is " + "not needed unless an SSL-based connection is " + "to be used that requires access to a private " + "key store", false, ""); // The parameter that specifies the password for the SSL key store private PasswordParameter trustPWParameter = new PasswordParameter("ssltrustpw", "SSL Trust Store Password", "The password for the JSSE trust store. This " + "is not needed unless an SSL-based connection " + "is to be used that requires access to a trust " + "store.", false, ""); // The parameter that specifies the password for the proxy user. private PasswordParameter proxyPasswordParameter = new PasswordParameter("proxy_password", "Proxy User Password", "The password that should be used for the " + "proxy DN. This must be provided if the " + "proxy credential level is selected, but it is " + "not needed otherwise.", false, ""); // The parameter that specifies the password to use when authenticating. private PasswordParameter userPasswordParameter = new PasswordParameter("user_password", "User Password", "The password that should be used when " + "authenticating as the end user. If the user " + "ID is actually a range of users, then this " + "password should be the same for all users.", true, ""); // A placeholder used only for cosmetic purposes. private PlaceholderParameter placeholder = new PlaceholderParameter(); // The parameter that specifies the base DN for information in the directory. private StringParameter baseParameter = new StringParameter("base_dn", "Directory Base DN", "The DN that should be used as the search base " + "all Solaris authentication information in the " + "directory server", true, ""); // The parameter that specifies the directory server address. private StringParameter hostParameter = new StringParameter("ldap_host", "Directory Server Address", "The IP address or fully-qualified domain name of " + "the directory server to use for authenticating " + "clients.", true, ""); // The parameter that specifies the IP addresses of hosts from which to // simulate telnet connections. private StringParameter ipAddressParameter = new StringParameter("ip_addr", "Simulated Client Address Range", "The CIDR-style IP address range of the client " + "systems that will be used when simulating a " + "telnet connection to the Solaris system. The " + "address of the client is resolved using a call " + "to gethostbyaddr, which generates a query of the " + "directory server.", true, "192.168.1.0/24"); // The parameter that specifies the location of the SSL key store private StringParameter keyStoreParameter = new StringParameter("sslkeystore", "SSL Key Store Location", "The location of the JSSE key store. This is not " + "needed unless an SSL-based connection is to be " + "used that requires access to a private key store", false, ""); // The parameter that specifies the location of the SSL key store private StringParameter trustStoreParameter = new StringParameter("ssltruststore", "SSL Trust Store Location", "The location of the JSSE trust store. This is " + "not needed unless an SSL-based connection is to " + "be used that requires access to a trust store.", false, ""); // The parameter that specifies the DN for the proxy user. private StringParameter proxyDNParameter = new StringParameter("proxy_dn", "Proxy User DN", "The DN of the proxy user that should be used " + "when binding to the directory server in order to " + "locate the end user's account. This must be " + "provided if the proxy credential level is " + "selected, but it is not required if the " + "anonymous credential level is to be used.", false, ""); // The parameter that specifies the attribute to use for the user ID. private StringParameter userIDAttributeParameter = new StringParameter("user_id_attr", "User ID Attribute", "The name of the LDAP attribute that contains the " + "user ID that will be used for authentication.", true, "uid"); // The parameter that specifies the user ID(s) to use when authenticating. private StringParameter userIDParameter = new StringParameter("user_id", "User ID", "The user ID to use when authenticating to the " + "directory server. It may include a numeric " + "range of values in brackets separated by a " + "dash (e.g., [1-1000]) to choose a value at " + "random, or by a colon (e.g., [1:1000]) to " + "iterate through them sequentially.", true, ""); // Indicates whether to blindly trust any SSL certificate. private static boolean blindTrust; // The rate limiter for this job. private static FixedRateBarrier rateLimiter; // The method that should be used to authenticate to the directory server // when the user is performing a bind. private static int authMethod; // The method that should be used for binding to the directory server to // perform searches to retrieve user information. private static int credentialLevel; // The base value for the IP address range of client systems, encoded as a // 32-bit value with the mask bits all set to zero. private static int ipAddressBase; // The mask value for the IP address range of client systems, encoded as a // 32-bit value. The mask bits will all be set to one, and the remaining // bits will all be zero. private static int ipAddressMask; // The port number for the directory server. private static int ldapPort; // The scope to use when searching the directory server. This will not be // configurable by the end user because we don't want to deal with custom // service search descriptors. private static int searchScope = LDAPConnection.SCOPE_SUB; // The search base to use for the directory server. private static String baseDN; // The address of the directory server. private static String ldapHost; // The DN to use when binding to the directory server as the proxy user. private static String proxyBindDN; // The password to use when binding to the directory server as the proxy user. private static String proxyBindPW; // The attribute that will hold the user ID value. private static String userIDAttribute; // The password that should be used when binding to the directory server as // the end user. private static String userPassword; // The value pattern to use to construct the user IDs. private static ValuePattern userIDPattern; // The connection to the directory server that is currently in use. private LDAPConnection conn; // The socket factory that should be used for creating the connections // to the directory used for bind and search operations. private LDAPSocketFactory bindSocketFactory; private LDAPSocketFactory proxySocketFactory; // The stat trackers used for this job. private IncrementalTracker authenticationAttempts; private IncrementalTracker failedAuthentications; private IncrementalTracker successfulAuthentications; private TimeTracker authenticationTimer; // The random number generators used for this job. private static Random parentRandom; private Random random; /** * Creates a new instance of this job thread. This constructor does not need * to do anything other than invoke the constructor for the superclass. */ public SolarisLDAPAuthRateJobClass() { super(); } /** * {@inheritDoc} */ @Override() public String getJobName() { return "LDAP Solaris Authentication Load Generator"; } /** * {@inheritDoc} */ @Override() public String getShortDescription() { return "Simulate the load generated by Solaris 9 Native LDAP clients"; } /** * {@inheritDoc} */ @Override() public String[] getLongDescription() { return new String[] { "This job can be used to simulate the load observed by Solaris 9 " + "Native LDAP clients when attempting to authenticate users through a " + "service like telnet." }; } /** * {@inheritDoc} */ @Override() public String getJobCategoryName() { return "LDAP"; } /** * {@inheritDoc} */ @Override() public ParameterList getParameterStubs() { Parameter[] parameterArray = new Parameter[] { placeholder, hostParameter, portParameter, baseParameter, placeholder, credentialParameter, proxyDNParameter, proxyPasswordParameter, placeholder, authMethodParameter, userIDAttributeParameter, userIDParameter, userPasswordParameter, placeholder, ipAddressParameter, placeholder, maxRateParameter, rateLimitDurationParameter, blindTrustParameter, keyStoreParameter, keyPWParameter, trustStoreParameter, trustPWParameter }; return new ParameterList(parameterArray); } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackerStubs(String clientID, String threadID, int collectionInterval) { return new StatTracker[] { new IncrementalTracker(clientID, threadID, STAT_TRACKER_AUTHENTICATION_ATTEMPTS, collectionInterval), new TimeTracker(clientID, threadID, STAT_TRACKER_AUTHENTICATION_TIME, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_TRACKER_FAILED_AUTHENTICATIONS, collectionInterval) }; } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackers() { return new StatTracker[] { authenticationAttempts, authenticationTimer, successfulAuthentications, failedAuthentications }; } /** * {@inheritDoc} */ @Override() public void validateJobInfo(int numClients, int threadsPerClient, int threadStartupDelay, Date startTime, Date stopTime, int duration, int collectionInterval, ParameterList parameters) throws InvalidValueException { // The user ID parameter must be parseable as a value pattern. StringParameter p = parameters.getStringParameter(userIDParameter.getName()); if ((p != null) && p.hasValue()) { try { new ValuePattern(p.getValue()); } catch (ParseException pe) { throw new InvalidValueException("The value provided for the '" + p.getDisplayName() + "' parameter is not a valid value " + "pattern: " + pe.getMessage(), pe); } } // Make sure that a bind DN and password were provided if the proxy // credential level was chosen. MultiChoiceParameter credLevelParam = parameters.getMultiChoiceParameter(credentialParameter.getName()); if (credLevelParam == null) { throw new InvalidValueException("Unable to determine the credential " + "level that should be used."); } String credLevel = credLevelParam.getStringValue(); if (credLevel.equals(CREDENTIAL_LEVEL_STRINGS[CREDENTIAL_LEVEL_PROXY])) { StringParameter proxyDNParam = parameters.getStringParameter(proxyDNParameter.getName()); PasswordParameter proxyPWParam = parameters.getPasswordParameter(proxyPasswordParameter.getName()); if ((proxyDNParam == null) || (proxyDNParam.getStringValue() == null) || (proxyDNParam.getStringValue().length() == 0) || (proxyPWParam == null) || (proxyPWParam.getStringValue() == null) || (proxyPWParam.getStringValue().length() == 0)) { throw new InvalidValueException("Both a proxy bind DN and password " + "must be specified if the proxy " + "credential level is chosen."); } } // Make sure that the IP address range provided was either a single IP // address or a valid CIDR address. StringParameter ipRangeParam = parameters.getStringParameter(ipAddressParameter.getName()); if (ipRangeParam == null) { throw new InvalidValueException("Unable to determine the IP address " + "range that should be used for " + "simulating Solaris clients."); } String ipAddrStr = ipRangeParam.getStringValue(); int slashPos = ipAddrStr.indexOf('/'); if (slashPos > 0) { try { int maskBits = Integer.parseInt(ipAddrStr.substring(slashPos+1)); if ((maskBits < 0) || (maskBits > 32)) { throw new InvalidValueException("Invalid IP address range -- " + "number of mask bits in a CIDR " + "address must be between 0 and 32"); } ipAddrStr = ipAddrStr.substring(0, slashPos); } catch (InvalidValueException ive) { throw ive; } catch (Exception e) { throw new InvalidValueException("Invalid IP address range -- unable " + "to interpret number of mask bits " + "-- " + e); } } try { StringTokenizer tokenizer = new StringTokenizer(ipAddrStr, "."); int octet = Integer.parseInt(tokenizer.nextToken()); if ((octet < 0) || (octet > 255)) { throw new InvalidValueException("Invalid IP address range -- octet " + "1 value is not between 0 and 255"); } octet = Integer.parseInt(tokenizer.nextToken()); if ((octet < 0) || (octet > 255)) { throw new InvalidValueException("Invalid IP address range -- octet " + "2 value is not between 0 and 255"); } octet = Integer.parseInt(tokenizer.nextToken()); if ((octet < 0) || (octet > 255)) { throw new InvalidValueException("Invalid IP address range -- octet " + "3 value is not between 0 and 255"); } octet = Integer.parseInt(tokenizer.nextToken()); if ((octet < 0) || (octet > 255)) { throw new InvalidValueException("Invalid IP address range -- octet " + "4 value is not between 0 and 255"); } if (tokenizer.hasMoreTokens()) { throw new InvalidValueException("Invalid IP address range -- too " + "many octets."); } } catch (InvalidValueException ive) { throw ive; } catch (Exception e) { throw new InvalidValueException("Invalid IP address range -- unable to " + "interpret \"" + ipAddrStr + "\" as an IP address"); } } /** * {@inheritDoc} */ @Override() public boolean providesParameterTest() { return true; } /** * {@inheritDoc} */ @Override() public boolean testJobParameters(ParameterList parameters, ArrayList<String> outputMessages) { // Get the parameters that should be tested. boolean bind = false; boolean useSSL = false; int port = -1; String baseDN = null; String bindDN = null; String bindPW = null; String credLevel = CREDENTIAL_LEVEL_STRINGS[0]; String host = null; StringParameter hostParam = parameters.getStringParameter(hostParameter.getName()); if ((hostParam == null) || (! hostParam.hasValue())) { outputMessages.add("ERROR: No directory server address was provided."); return false; } else { host = hostParam.getStringValue(); } IntegerParameter portParam = parameters.getIntegerParameter(portParameter.getName()); if ((portParam == null) || (! hostParam.hasValue())) { outputMessages.add("ERROR: No directory server port was provided."); return false; } else { port = portParam.getIntValue(); } StringParameter baseParam = parameters.getStringParameter(baseParameter.getName()); if ((baseParam == null) || (! baseParam.hasValue())) { outputMessages.add("ERROR: No directory base DN was provided."); return false; } else { baseDN = baseParam.getStringValue(); } MultiChoiceParameter credLevelParam = parameters.getMultiChoiceParameter(credentialParameter.getName()); if ((credLevelParam == null) || (! credLevelParam.hasValue())) { outputMessages.add("ERROR: No credential level was provided."); return false; } else { credLevel = credLevelParam.getStringValue(); } if (credLevel.equals(CREDENTIAL_LEVEL_STRINGS[CREDENTIAL_LEVEL_PROXY])) { bind = true; StringParameter bindDNParam = parameters.getStringParameter(proxyDNParameter.getName()); if ((bindDNParam == null) || (! bindDNParam.hasValue())) { outputMessages.add("ERROR: No proxy user DN was provided for the " + "proxy credential level."); return false; } else { bindDN = bindDNParam.getStringValue(); } PasswordParameter bindPWParam = parameters.getPasswordParameter(proxyPasswordParameter.getName()); if ((bindPWParam == null) || (! bindPWParam.hasValue())) { outputMessages.add("ERROR: No proxy user password was provided for " + "the proxy credential level."); return false; } else { bindPW = bindPWParam.getStringValue(); } } MultiChoiceParameter authMethodParam = parameters.getMultiChoiceParameter(authMethodParameter.getName()); if ((authMethodParam == null) || (! authMethodParam.hasValue())) { outputMessages.add("ERROR: No authentication method was provided."); return false; } else { String methodStr = authMethodParam.getStringValue(); useSSL = (methodStr.equals(AUTH_METHOD_STRINGS[AUTH_METHOD_TLS_SIMPLE]) || methodStr.equals(AUTH_METHOD_STRINGS[AUTH_METHOD_TLS_DIGEST_MD5])); } // Verify that we can establish a connection to the directory server. LDAPConnection conn; String message = "Attempting to connect to directory server " + host + ':' + port; if (useSSL) { outputMessages.add(message + " overSSL"); try { conn = new LDAPConnection(new JSSEBlindTrustSocketFactory()); } catch (Exception e) { outputMessages.add("ERROR: Unable to complete the SSL " + "initialization: " + e); return false; } } else { outputMessages.add(message); conn = new LDAPConnection(new SLAMDLDAPSocketFactory()); } if (bind) { try { conn.connect(3, host, port, bindDN, bindPW); } catch (Exception e) { outputMessages.add("ERROR: Unable to connect to " + host + ':' + port + " as " + bindDN + " -- " + e); return false; } } else { try { conn.connect(host, port); } catch (Exception e) { outputMessages.add("ERROR: Unable to connect to " + host + ':' + port + " -- " + e); return false; } } // Make sure that the base DN exists. outputMessages.add("Connection established successfully."); outputMessages.add(""); outputMessages.add("Attempting to retrieve the base entry " + baseDN); try { LDAPEntry entry = conn.read(baseDN); if (entry == null) { conn.disconnect(); outputMessages.add("ERROR: Unable to retrieve the base entry."); return false; } outputMessages.add("Successfully read the base entry."); } catch (Exception e) { outputMessages.add("ERROR: Unable to retrieve the base entry: " + e); try { } catch (Exception e2) {} return false; } // Close the connection to the server. outputMessages.add(""); outputMessages.add("All tests completed successfully."); try { conn.disconnect(); } catch (Exception e) {} return true; } /** * {@inheritDoc} */ @Override() public void initializeClient(String clientID, ParameterList parameters) throws UnableToRunException { // Seed the parent random number generator. parentRandom = new Random(); // Get the directory server address hostParameter = parameters.getStringParameter(hostParameter.getName()); if (hostParameter == null) { throw new UnableToRunException("No directory server host provided."); } else { ldapHost = hostParameter.getStringValue(); } // Get the directory server port portParameter = parameters.getIntegerParameter(portParameter.getName()); if (portParameter != null) { ldapPort = portParameter.getIntValue(); } // Get the directory base DN baseParameter = parameters.getStringParameter(baseParameter.getName()); if (baseParameter != null) { baseDN = baseParameter.getStringValue(); } // Get the credential level. credentialParameter = parameters.getMultiChoiceParameter(credentialParameter.getName()); if (credentialParameter != null) { String credentialParamStr = credentialParameter.getStringValue(); for (int i=0; i < CREDENTIAL_LEVEL_STRINGS.length; i++) { if (credentialParamStr.equals(CREDENTIAL_LEVEL_STRINGS[i])) { credentialLevel = i; } } } // Get the proxy DN. proxyDNParameter = parameters.getStringParameter(proxyDNParameter.getName()); if (proxyDNParameter != null) { proxyBindDN = proxyDNParameter.getStringValue(); } if (proxyBindDN == null) { proxyBindDN = ""; } // Get the proxy password. proxyPasswordParameter = parameters.getPasswordParameter(proxyPasswordParameter.getName()); if (proxyPasswordParameter != null) { proxyBindPW = proxyPasswordParameter.getStringValue(); } if (proxyBindPW == null) { proxyBindPW = ""; } // Get the authentication method. authMethodParameter = parameters.getMultiChoiceParameter(authMethodParameter.getName()); if (authMethodParameter != null) { String authMethodStr = authMethodParameter.getStringValue(); for (int i=0; i < AUTH_METHOD_STRINGS.length; i++) { if (authMethodStr.equals(AUTH_METHOD_STRINGS[i])) { authMethod = i; } } } // Get the user ID attribute. userIDAttributeParameter = parameters.getStringParameter(userIDAttributeParameter.getName()); if (userIDAttributeParameter != null) { userIDAttribute = userIDAttributeParameter.getStringValue(); } // Get the user ID. It could be a range of values. userIDParameter = parameters.getStringParameter(userIDParameter.getName()); if (userIDParameter != null) { try { userIDPattern = new ValuePattern(userIDParameter.getStringValue()); } catch (Exception e) { throw new UnableToRunException( "Unable to parse the user ID pattern: " + stackTraceToString(e), e); } } // Get the password for the user. userPasswordParameter = parameters.getPasswordParameter(userPasswordParameter.getName()); if (userPasswordParameter != null) { userPassword = userPasswordParameter.getStringValue(); } // Get the IP address for the simulated clients. ipAddressParameter = parameters.getStringParameter(ipAddressParameter.getName()); if (ipAddressParameter != null) { String ipAddressStr = ipAddressParameter.getStringValue(); int maskBits = 32; int slashPos = ipAddressStr.indexOf('/'); if (slashPos >= 0) { maskBits = Integer.parseInt(ipAddressStr.substring(slashPos+1)); ipAddressStr = ipAddressStr.substring(0, slashPos); } StringTokenizer tokenizer = new StringTokenizer(ipAddressStr, "."); int a = Integer.parseInt(tokenizer.nextToken()); int b = Integer.parseInt(tokenizer.nextToken()); int c = Integer.parseInt(tokenizer.nextToken()); int d = Integer.parseInt(tokenizer.nextToken()); int ipAddrInt = ((0x000000FF & a) << 24) | ((0x000000FF & b) << 16) | ((0x000000FF & c) << 8) | (0x000000FF & d); int baseMask = 0x00000000; ipAddressMask = 0x00000000; for (int i=0; i < 32; i++) { baseMask <<= 1; ipAddressMask <<= 1; if (i < maskBits) { baseMask |= 0x0000001; } else { ipAddressMask |= 0x00000001; } } ipAddressBase = ipAddrInt & baseMask; } rateLimiter = null; maxRateParameter = parameters.getIntegerParameter(maxRateParameter.getName()); if ((maxRateParameter != null) && maxRateParameter.hasValue()) { int maxRate = maxRateParameter.getIntValue(); if (maxRate > 0) { int rateIntervalSeconds = 0; rateLimitDurationParameter = parameters.getIntegerParameter( rateLimitDurationParameter.getName()); if ((rateLimitDurationParameter != null) && rateLimitDurationParameter.hasValue()) { rateIntervalSeconds = rateLimitDurationParameter.getIntValue(); } if (rateIntervalSeconds <= 0) { rateIntervalSeconds = getClientSideJob().getCollectionInterval(); } rateLimiter = new FixedRateBarrier(rateIntervalSeconds * 1000L, maxRate * rateIntervalSeconds); } } blindTrustParameter = parameters.getBooleanParameter(blindTrustParameter.getName()); if (blindTrustParameter != null) { blindTrust = blindTrustParameter.getBooleanValue(); } keyStoreParameter = parameters.getStringParameter(keyStoreParameter.getName()); if (keyStoreParameter != null) { String keyStoreLocation = keyStoreParameter.getStringValue(); System.setProperty(SSL_KEY_STORE_PROPERTY, keyStoreLocation); } keyPWParameter = parameters.getPasswordParameter(keyPWParameter.getName()); if (keyPWParameter != null) { String keyPW = keyPWParameter.getStringValue(); System.setProperty(SSL_KEY_PASSWORD_PROPERTY, keyPW); } trustStoreParameter = parameters.getStringParameter(trustStoreParameter.getName()); if (trustStoreParameter != null) { String trustStoreLocation = trustStoreParameter.getStringValue(); System.setProperty(SSL_TRUST_STORE_PROPERTY, trustStoreLocation); } trustPWParameter = parameters.getPasswordParameter(trustPWParameter.getName()); if (trustPWParameter != null) { String trustPW = trustPWParameter.getStringValue(); System.setProperty(SSL_TRUST_PASSWORD_PROPERTY, trustPW); } // If the connection to the directory server should be over SSL, then create // an SSL-based connection now. The reason for this is that the first time // an SSL-based connection is created, it can be a relatively expensive // process, taking up to a few seconds, and we want to get that out of the // way before the timer starts. if ((authMethod == AUTH_METHOD_TLS_SIMPLE) || (authMethod == AUTH_METHOD_TLS_DIGEST_MD5)) { try { LDAPConnection conn; if (blindTrust) { conn = new LDAPConnection(new JSSEBlindTrustSocketFactory()); } else { conn = new LDAPConnection(new JSSESocketFactory(null)); } conn.setConnectTimeout(10); conn.connect(ldapHost, ldapPort); conn.disconnect(); } catch (Exception e) {} } } /** * {@inheritDoc} */ @Override() public void initializeThread(String clientID, String threadID, int collectionInterval, ParameterList parameters) throws UnableToRunException { // Create the stat trackers. authenticationAttempts = new IncrementalTracker(clientID, threadID, STAT_TRACKER_AUTHENTICATION_ATTEMPTS, collectionInterval); authenticationTimer = new TimeTracker(clientID, threadID, STAT_TRACKER_AUTHENTICATION_TIME, collectionInterval); successfulAuthentications = new IncrementalTracker(clientID, threadID, STAT_TRACKER_SUCCESSFUL_AUTHENTICATIONS, collectionInterval); failedAuthentications = new IncrementalTracker(clientID, threadID, STAT_TRACKER_FAILED_AUTHENTICATIONS, collectionInterval); // Enable real-time reporting of the data for these stat trackers. RealTimeStatReporter statReporter = getStatReporter(); if (statReporter != null) { String jobID = getJobID(); authenticationAttempts.enableRealTimeStats(statReporter, jobID); authenticationTimer.enableRealTimeStats(statReporter, jobID); successfulAuthentications.enableRealTimeStats(statReporter, jobID); failedAuthentications.enableRealTimeStats(statReporter, jobID); } // Seed the random number generator for this thread. random = new Random(parentRandom.nextLong()); // If necessary, create the socket factories to use for the proxy and bind // users. switch (authMethod) { case AUTH_METHOD_SIMPLE: bindSocketFactory = null; proxySocketFactory = null; break; case AUTH_METHOD_DIGEST_MD5: try { bindSocketFactory = new LDAPDigestMD5SocketFactory(); proxySocketFactory = new LDAPDigestMD5SocketFactory(); } catch (SLAMDException se) { throw new UnableToRunException("Unable to create the DIGEST-MD5 " + "socket factory -- " + se, se); } if (credentialLevel == CREDENTIAL_LEVEL_PROXY) { ((LDAPDigestMD5SocketFactory) proxySocketFactory).setAuthenticationInfo("dn:" + proxyBindDN, proxyBindPW); } break; case AUTH_METHOD_TLS_SIMPLE: if (blindTrust) { try { bindSocketFactory = new JSSEBlindTrustSocketFactory(); proxySocketFactory = new JSSEBlindTrustSocketFactory(); } catch (LDAPException le) { throw new UnableToRunException(le.getMessage(), le); } } else { bindSocketFactory = new JSSESocketFactory(null); proxySocketFactory = new JSSESocketFactory(null); } break; case AUTH_METHOD_TLS_DIGEST_MD5: try { LDAPDigestMD5SocketFactory bindFactory = new LDAPDigestMD5SocketFactory(); LDAPDigestMD5SocketFactory proxyFactory = new LDAPDigestMD5SocketFactory(); if (blindTrust) { try { bindFactory.setAdditionalSocketFactory( new JSSEBlindTrustSocketFactory()); proxyFactory.setAdditionalSocketFactory( new JSSEBlindTrustSocketFactory()); } catch (LDAPException le) { throw new UnableToRunException(le.getMessage(), le); } } else { bindFactory.setAdditionalSocketFactory(new JSSESocketFactory(null)); proxyFactory.setAdditionalSocketFactory( new JSSESocketFactory(null)); } if (credentialLevel == CREDENTIAL_LEVEL_PROXY) { proxyFactory.setAuthenticationInfo("dn:" + proxyBindDN, proxyBindPW); } bindSocketFactory = bindFactory; proxySocketFactory = proxyFactory; } catch (SLAMDException se) { throw new UnableToRunException("Unable to create the DIGEST-MD5 " + "socket factory -- " + se, se); } break; } } /** * {@inheritDoc} */ @Override() public void runJob() { authenticationAttempts.startTracker(); successfulAuthentications.startTracker(); failedAuthentications.startTracker(); authenticationTimer.startTracker(); while (! shouldStop()) { if (rateLimiter != null) { if (rateLimiter.await()) { continue; } } try { // We will collect these later. int uidNumber; String userDN; // Get the information to use for this authentication attempt. String ipAddress = getIPAddress(); String userID = userIDPattern.nextValue(); // Start the timer and increment the attempt counter. authenticationAttempts.increment(); authenticationTimer.startTimer(); // First, issue a query to get the hostname of the system from which the // connection is being established. We don't care about any return // value from this. getHostByAddress(ipAddress); // Next, get the shadow account for the user. If it is successful, it // will return the DN of the user's entry. userDN = getShadowAccount(userID); if (userDN == null) { failedAuthentications.increment(); authenticationTimer.stopTimer(); } // Next, get the POSIX account for the user. If it is successful, it // will return the uid number for the user. uidNumber = getPosixAccount(userID); if (uidNumber < 0) { failedAuthentications.increment(); authenticationTimer.stopTimer(); } // Issue a sequence of queries that are quite unnecessary but // unfortunately are performed nonetheless. getShadowAccount(userID); getPosixAccount(userID); getShadowAccount(userID); // Now get the user's entry and request all attributes (i.e., don't // specify an attribute list). getUserEntry(userID); // Bind as the user. bindAsUser(userDN, userPassword); // Get the extended attributes for the user. getUserAttr(userID); // Get project information for the user. getProjectByName("user." + userID); getProjectByName("group.other"); // Get the shadow account again. getShadowAccount(userID); // Get group information for the user. getGroupsForUser(userID); // Still more retrievals of the POSIX and shadow information. getPosixAccount(userID); getShadowAccount(userID); // Get the NIS key information for the user. getNISKey(uidNumber); // Get the host information for the system we are authenticating. // We'll use the directory server's address for that. getHostByName(ldapHost); // If we've gotten here, then the authentication was successful. successfulAuthentications.increment(); authenticationTimer.stopTimer(); } catch (LDAPException le) { failedAuthentications.increment(); authenticationTimer.stopTimer(); try { conn.disconnect(); } catch (LDAPException e) {} } } authenticationAttempts.stopTracker(); successfulAuthentications.stopTracker(); failedAuthentications.stopTracker(); authenticationTimer.stopTracker(); } /** * Retrieves a randomly-chosen IP address to use as the client's source * address. * * @return A randomly-chosen IP address to use as the client's source * address. */ private String getIPAddress() { int ipAddressInt = ipAddressBase | (random.nextInt() & ipAddressMask); StringBuilder addrBuffer = new StringBuilder(); addrBuffer.append((ipAddressInt >>> 24) & 0x000000FF); addrBuffer.append('.'); addrBuffer.append((ipAddressInt >>> 16) & 0x000000FF); addrBuffer.append('.'); addrBuffer.append((ipAddressInt >>> 8) & 0x000000FF); addrBuffer.append('.'); addrBuffer.append(ipAddressInt & 0x000000FF); return addrBuffer.toString(); } /** * Simulates the query issued to the directory server whenever the Solaris * gethostbyaddr function is called. * * @param ipAddress The dotted-quad (e.g., 1.2.3.4) IP address for which to * retrieve the value. * * @throws LDAPException If a problem occurs while processing the query. */ private void getHostByAddress(String ipAddress) throws LDAPException { String filter = "(&(objectClass=ipHost)(ipHostNumber=" + ipAddress + "))"; String[] attrs = { "cn", "ipHostNumber" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server whenever the Solaris * gethostbyname function is called. * * @param hostname The hostname for which to retrieve the address. * * @throws LDAPException If a problem occurs while processing the query. */ private void getHostByName(String hostname) throws LDAPException { String filter = "(&(objectClass=ipHost)(cn=" + hostname + "))"; String[] attrs = { "cn", "ipHostNumber" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server whenever the Solaris * getpwnam function is called. * * @param userID The user ID for which to retrieve the entry. * * @return The uid number for the user whose account was returned, or -1 * if no user could be found. * * @throws LDAPException If a problem occurs while processing the query, or * if multiple entries matched the given query. */ private int getPosixAccount(String userID) throws LDAPException { String filter = "(&(objectClass=posixAccount)(" + userIDAttribute + '=' + userID + "))"; String[] attrs = { "cn", userIDAttribute, "uidNumber", "gidNumber", "gecos", "description", "homeDirectory", "loginShell" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); int uidNumber = -1; while (results.hasMoreElements()) { Object element = results.nextElement(); if (element instanceof LDAPEntry) { if (uidNumber >= 0) { conn.disconnect(); throw new LDAPException("Multiple entries returned for getpwnam", LDAPException.OTHER); } LDAPEntry entry = (LDAPEntry) element; LDAPAttribute attr = entry.getAttribute("uidNumber"); if (attr == null) { conn.disconnect(); throw new LDAPException("Unable to obtain uid number for entry " + entry.getDN()); } String[] values = attr.getStringValueArray(); if ((values == null) || (values.length == 0)) { conn.disconnect(); throw new LDAPException("Unable to obtain uid number for entry " + entry.getDN()); } try { uidNumber = Integer.parseInt(values[0]); } catch (NumberFormatException nfe) { conn.disconnect(); throw new LDAPException("Unable to parse uid number an an integer " + "for entry " + entry.getDN()); } } } conn.disconnect(); return uidNumber; } /** * Simulates the query issued to the directory server whenever the Solaris * getspnam function is called. * * @param userID The user ID for which to retrieve the entry. * * @return The DN of the entry matching the provided criteria, or * <CODE>null</CODE> if no entry was found. * * @throws LDAPException If a problem occurs while processing the query, or * if multiple entries matched the given query. */ private String getShadowAccount(String userID) throws LDAPException { String filter = "(&(objectClass=shadowAccount)(" + userIDAttribute + '=' + userID + "))"; String[] attrs = { userIDAttribute, "userPassword", "shadowFlag" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); String userDN = null; while (results.hasMoreElements()) { Object element = results.nextElement(); if (element instanceof LDAPEntry) { if (userDN != null) { conn.disconnect(); throw new LDAPException("Multiple entries returned for getspnam", LDAPException.OTHER); } LDAPEntry entry = (LDAPEntry) element; userDN = entry.getDN(); } } conn.disconnect(); return userDN; } /** * Retrieves the entry for the user with all attributes. I'm not sure which * system call this corresponds to, but it does happen during the login * process. * * @param userID The user ID for which to retrieve the entry. * * @return <CODE>true</CODE> if a match was found, or <CODE>false</CODE> if * there were no matches. * * @throws LDAPException If a problem occurs while processing the query, or * if multiple entries matched the given query. */ private boolean getUserEntry(String userID) throws LDAPException { String filter = "(&(objectClass=posixAccount)(" + userIDAttribute + '=' + userID + "))"; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, null, false); boolean matchFound = false; while (results.hasMoreElements()) { Object element = results.nextElement(); if (element instanceof LDAPEntry) { if (matchFound) { conn.disconnect(); throw new LDAPException("Multiple entries returned when getting " + "user entry", LDAPException.OTHER); } matchFound = true; } } conn.disconnect(); return matchFound; } /** * Performs a bind as the user with the specified DN, using the configured * authentication method. * * @param userDN The DN for the user. * @param userPassword The password for the user. * * @throws LDAPException If a problem occurs while performing the bind. */ private void bindAsUser(String userDN, String userPassword) throws LDAPException { if (bindSocketFactory == null) { conn = new LDAPConnection(new SLAMDLDAPSocketFactory()); } else { if (bindSocketFactory instanceof LDAPDigestMD5SocketFactory) { LDAPDigestMD5SocketFactory md5SocketFactory = (LDAPDigestMD5SocketFactory) bindSocketFactory; md5SocketFactory.setAuthenticationInfo("dn:" + userDN, userPassword); } conn = new LDAPConnection(bindSocketFactory); } conn.setConnectTimeout(10); switch (authMethod) { case AUTH_METHOD_SIMPLE: case AUTH_METHOD_TLS_SIMPLE: conn.connect(3, ldapHost, ldapPort, userDN, userPassword); break; case AUTH_METHOD_DIGEST_MD5: case AUTH_METHOD_TLS_DIGEST_MD5: conn.connect(ldapHost, ldapPort); break; } conn.disconnect(); } /** * Simulates the query issued to the directory server when Solaris is looking * for the automount records for a specified key. * * @param keyName The name of the key for which to retrieve the automount * records. * * @throws LDAPException If a problem occurs while processing the query. */ private void getAutomountEntry(String keyName) throws LDAPException { String filter = "(&(objectClass=automount)(automountKey=" + keyName + "))"; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, null, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server when Solaris is trying * to retrieve extended attributes for a user entry. * * @param userID The user ID for which to retrieve the information. * * @throws LDAPException If a problem occurs while processing the query. */ private void getUserAttr(String userID) throws LDAPException { String filter = "(&(objectClass=SolarisUserAttr)(" + userIDAttribute + '=' + userID + "))"; String[] attrs = { userIDAttribute, "SolarisUserQualifier", "SolarisAttrReserved1", "SolarisAttrReserved2", "SolarisAttrKeyValue" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server when Solaris is trying * to retrieve the project with the given name. * * @param projectName The name of the project to retrieve. * * @throws LDAPException If a problem occurs while processing the query. */ private void getProjectByName(String projectName) throws LDAPException { String filter = "(&(objectClass=SolarisProject)(SolarisProjectName=" + projectName + "))"; String[] attrs = { "SolarisProjectName", "SolarisProjectID", "description", "memberGid", "SolarisProjectAttr" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server when Solaris is trying * to determine the groups for the specified user. * * @param userID The user ID for which to retrieve the groups. * * @throws LDAPException If a problem occurs while processing the query. */ private void getGroupsForUser(String userID) throws LDAPException { String filter = "(&(objectClass=posixGroup)(memberUID=" + userID + "))"; String[] attrs = { "cn", "gidNumber", "userPassword", "memberUID" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Simulates the query issued to the directory server when Solaris is trying * to get the NIS key information for the specified user. * * @param uidNumber The UID number for the user. * * @throws LDAPException If a problem occurs while processing the query. */ private void getNISKey(int uidNumber) throws LDAPException { String filter = "(&(objectClass=nisKeyObject)(uidNumber=" + uidNumber + "))"; String[] attrs = { "nisPublicKey", "nisSecretKey" }; getLDAPConnection(); LDAPSearchResults results = conn.search(baseDN, searchScope, filter, attrs, false); while (results.hasMoreElements()) { results.nextElement(); } conn.disconnect(); } /** * Establishes a connection to the directory server that is bound as an * appropriate user using the correct mechanism. * * @return A connection to the directory server that is bound as an * appropriate user using the correct mechanism. * * @throws LDAPException If a problem occurs while establishing the * connection. */ private LDAPConnection getLDAPConnection() throws LDAPException { if (proxySocketFactory == null) { conn = new LDAPConnection(new SLAMDLDAPSocketFactory()); } else { conn = new LDAPConnection(proxySocketFactory); } conn.setConnectTimeout(10); switch (credentialLevel) { case CREDENTIAL_LEVEL_ANONYMOUS: conn.connect(ldapHost, ldapPort); break; case CREDENTIAL_LEVEL_PROXY: if ((authMethod == AUTH_METHOD_SIMPLE) || (authMethod == AUTH_METHOD_TLS_SIMPLE)) { conn.connect(3, ldapHost, ldapPort, proxyBindDN, proxyBindPW); } else { conn.connect(ldapHost, ldapPort); } break; } return conn; } }