/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. */ package org.opends.sdk.tools; import static org.opends.messages.ToolMessages.*; import static org.opends.messages.UtilityMessages.INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS; import static org.opends.messages.UtilityMessages.INFO_TIME_IN_HOURS_MINUTES_SECONDS; import static org.opends.messages.UtilityMessages.INFO_TIME_IN_MINUTES_SECONDS; import static org.opends.messages.UtilityMessages.INFO_TIME_IN_SECONDS; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import org.opends.messages.Message; import org.opends.sdk.Connection; import org.opends.sdk.DecodeException; import org.opends.sdk.ErrorResultException; import org.opends.sdk.AuthenticatedConnectionFactory.AuthenticatedConnection; import org.opends.sdk.controls.*; import org.opends.sdk.responses.BindResult; import org.opends.sdk.util.ByteString; import org.opends.sdk.util.StaticUtils; import org.opends.server.util.cli.ConsoleApplication; /** * This class provides utility functions for all the client side tools. */ final class Utils { // Prevent instantiation. private Utils() { // Do nothing. } /** * Parse the specified command line argument to create the appropriate * LDAPControl. The argument string should be in the format * controloid[:criticality[:value|::b64value|:<fileurl]] * * @param argString * The argument string containing the encoded control * information. * @return The control decoded from the provided string, or * null if an error occurs while parsing the * argument value. * @throws org.opends.sdk.DecodeException * If an error occurs. */ public static GenericControl getControl(String argString) throws DecodeException { String controlOID = null; boolean controlCriticality = false; ByteString controlValue = null; int idx = argString.indexOf(":"); if (idx < 0) { controlOID = argString; } else { controlOID = argString.substring(0, idx); } String lowerOID = StaticUtils.toLowerCase(controlOID); if (lowerOID.equals("accountusable") || lowerOID.equals("accountusability")) { controlOID = AccountUsabilityControl.OID_ACCOUNT_USABLE_CONTROL; } else if (lowerOID.equals("authzid") || lowerOID.equals("authorizationidentity")) { controlOID = AuthorizationIdentityControl.OID_AUTHZID_REQUEST; } else if (lowerOID.equals("noop") || lowerOID.equals("no-op")) { // controlOID = OID_LDAP_NOOP_OPENLDAP_ASSIGNED; } else if (lowerOID.equals("subentries")) { // controlOID = OID_LDAP_SUBENTRIES; } else if (lowerOID.equals("managedsait")) { // controlOID = OID_MANAGE_DSAIT_CONTROL; } else if (lowerOID.equals("pwpolicy") || lowerOID.equals("passwordpolicy")) { controlOID = PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL; } else if (lowerOID.equals("subtreedelete") || lowerOID.equals("treedelete")) { controlOID = SubtreeDeleteControl.OID_SUBTREE_DELETE_CONTROL; } else if (lowerOID.equals("realattrsonly") || lowerOID.equals("realattributesonly")) { // controlOID = OID_REAL_ATTRS_ONLY; } else if (lowerOID.equals("virtualattrsonly") || lowerOID.equals("virtualattributesonly")) { // controlOID = OID_VIRTUAL_ATTRS_ONLY; } else if (lowerOID.equals("effectiverights") || lowerOID.equals("geteffectiverights")) { controlOID = GetEffectiveRightsRequestControl.OID_GET_EFFECTIVE_RIGHTS; } if (idx < 0) { return new GenericControl(controlOID); } String remainder = argString.substring(idx + 1, argString.length()); idx = remainder.indexOf(":"); if (idx == -1) { if (remainder.equalsIgnoreCase("true")) { controlCriticality = true; } else if (remainder.equalsIgnoreCase("false")) { controlCriticality = false; } else { // TODO: I18N throw DecodeException.error(Message .raw("Invalid format for criticality value:" + remainder)); } return new GenericControl(controlOID, controlCriticality); } String critical = remainder.substring(0, idx); if (critical.equalsIgnoreCase("true")) { controlCriticality = true; } else if (critical.equalsIgnoreCase("false")) { controlCriticality = false; } else { // TODO: I18N throw DecodeException.error(Message .raw("Invalid format for criticality value:" + critical)); } String valString = remainder.substring(idx + 1, remainder.length()); if (valString.charAt(0) == ':') { controlValue = ByteString.valueOf(valString.substring(1, valString.length())); } else if (valString.charAt(0) == '<') { // Read data from the file. String filePath = valString.substring(1, valString.length()); try { byte[] val = readBytesFromFile(filePath); controlValue = ByteString.wrap(val); } catch (Exception e) { return null; } } else { controlValue = ByteString.valueOf(valString); } return new GenericControl(controlOID, controlCriticality, controlValue); } /** * Read the data from the specified file and return it in a byte * array. * * @param filePath * The path to the file that should be read. * @return A byte array containing the contents of the requested file. * @throws IOException * If a problem occurs while trying to read the specified * file. */ public static byte[] readBytesFromFile(String filePath) throws IOException { byte[] val = null; FileInputStream fis = null; try { File file = new File(filePath); fis = new FileInputStream(file); long length = file.length(); val = new byte[(int) length]; // Read in the bytes int offset = 0; int numRead = 0; while (offset < val.length && (numRead = fis.read(val, offset, val.length - offset)) >= 0) { offset += numRead; } // Ensure all the bytes have been read in if (offset < val.length) { throw new IOException("Could not completely read file " + filePath); } return val; } finally { if (fis != null) { fis.close(); } } } /** * Prints a multi-line error message with the provided information to * the given print stream. * * @param app * The console app to use to write the error message. * @param ere * The error result. * @return The error code. */ public static int printErrorMessage(ConsoleApplication app, ErrorResultException ere) { // if ((ere.getMessage() != null) && (ere.getMessage().length() > // 0)) // { // app.println(Message.raw(ere.getMessage())); // } if (ere.getResult().getResultCode().intValue() >= 0) { app.println(ERR_TOOL_RESULT_CODE.get(ere.getResult() .getResultCode().intValue(), ere.getResult().getResultCode() .toString())); } if ((ere.getResult().getDiagnosticMessage() != null) && (ere.getResult().getDiagnosticMessage().length() > 0)) { app.println(ERR_TOOL_ERROR_MESSAGE.get(ere.getResult() .getDiagnosticMessage())); } if (ere.getResult().getMatchedDN() != null && ere.getResult().getMatchedDN().length() > 0) { app.println(ERR_TOOL_MATCHED_DN.get(ere.getResult() .getMatchedDN())); } if (app.isVerbose() && ere.getResult().getCause() != null) { ere.getResult().getCause().printStackTrace(app.getErrorStream()); } return ere.getResult().getResultCode().intValue(); } /** * Retrieves a user-friendly string that indicates the length of time * (in days, hours, minutes, and seconds) in the specified number of * seconds. * * @param numSeconds * The number of seconds to be converted to a more * user-friendly value. * @return The user-friendly representation of the specified number of * seconds. */ public static Message secondsToTimeString(int numSeconds) { if (numSeconds < 60) { // We can express it in seconds. return INFO_TIME_IN_SECONDS.get(numSeconds); } else if (numSeconds < 3600) { // We can express it in minutes and seconds. int m = numSeconds / 60; int s = numSeconds % 60; return INFO_TIME_IN_MINUTES_SECONDS.get(m, s); } else if (numSeconds < 86400) { // We can express it in hours, minutes, and seconds. int h = numSeconds / 3600; int m = (numSeconds % 3600) / 60; int s = numSeconds % 3600 % 60; return INFO_TIME_IN_HOURS_MINUTES_SECONDS.get(h, m, s); } else { // We can express it in days, hours, minutes, and seconds. int d = numSeconds / 86400; int h = (numSeconds % 86400) / 3600; int m = (numSeconds % 86400 % 3600) / 60; int s = numSeconds % 86400 % 3600 % 60; return INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS.get(d, h, m, s); } } public static void printPasswordPolicyResults(ConsoleApplication app, Connection connection) { if (connection instanceof AuthenticatedConnection) { AuthenticatedConnection conn = (AuthenticatedConnection) connection; BindResult result = conn.getAuthenticatedBindResult(); Control control = result .getControl(AuthorizationIdentityControl.OID_AUTHZID_RESPONSE); if (control != null) { AuthorizationIdentityControl.Response dc = (AuthorizationIdentityControl.Response) control; Message message = INFO_BIND_AUTHZID_RETURNED.get(dc .getAuthorizationID()); app.println(message); } control = result .getControl(PasswordExpiredControl.OID_NS_PASSWORD_EXPIRED); if (control != null) { Message message = INFO_BIND_PASSWORD_EXPIRED.get(); app.println(message); } control = result .getControl(PasswordExpiringControl.OID_NS_PASSWORD_EXPIRING); if (control != null) { PasswordExpiringControl dc = (PasswordExpiringControl) control; Message timeString = Utils.secondsToTimeString(dc .getSecondsUntilExpiration()); Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); app.println(message); } control = result .getControl(PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL); if (control != null) { PasswordPolicyControl.Response dc = (PasswordPolicyControl.Response) control; PasswordPolicyErrorType errorType = dc.getErrorType(); if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED) { Message message = INFO_BIND_PASSWORD_EXPIRED.get(); app.println(message); } else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED) { Message message = INFO_BIND_ACCOUNT_LOCKED.get(); app.println(message); } else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET) { Message message = INFO_BIND_MUST_CHANGE_PASSWORD.get(); app.println(message); } PasswordPolicyWarningType warningType = dc.getWarningType(); if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION) { Message timeString = Utils.secondsToTimeString(dc .getWarningValue()); Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); app.println(message); } else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING) { Message message = INFO_BIND_GRACE_LOGINS_REMAINING.get(dc .getWarningValue()); app.println(message); } } } } /** * Filters the provided value to ensure that it is appropriate for use * as an exit code. Exit code values are generally only allowed to be * between 0 and 255, so any value outside of this range will be * converted to 255, which is the typical exit code used to indicate * an overflow value. * * @param exitCode * The exit code value to be processed. * @return An integer value between 0 and 255, inclusive. If the * provided exit code was already between 0 and 255, then the * original value will be returned. If the provided value was * out of this range, then 255 will be returned. */ public static int filterExitCode(int exitCode) { if (exitCode < 0) { return 255; } else if (exitCode > 255) { return 255; } else { return exitCode; } } }