001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.tools; 028import static org.opends.messages.ToolMessages.*; 029import static org.opends.server.util.ServerConstants.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import static com.forgerock.opendj.cli.Utils.*; 033 034import java.io.File; 035import java.io.FileInputStream; 036import java.io.IOException; 037import java.io.PrintStream; 038import java.net.SocketTimeoutException; 039 040import org.forgerock.i18n.LocalizableMessage; 041import org.forgerock.opendj.ldap.ByteString; 042import org.forgerock.opendj.ldap.DecodeException; 043import org.opends.server.protocols.ldap.LDAPControl; 044import org.opends.server.protocols.ldap.LDAPResultCode; 045import org.opends.server.types.DN; 046 047 048 049/** 050 * This class provides utility functions for all the client side tools. 051 */ 052public class LDAPToolUtils 053{ 054 055 056 /** 057 * Parse the specified command line argument to create the 058 * appropriate LDAPControl. The argument string should be in the format 059 * controloid[:criticality[:value|::b64value|:<fileurl]] 060 * 061 * @param argString The argument string containing the encoded control 062 * information. 063 * @param err A print stream to which error messages should be 064 * written if a problem occurs. 065 * 066 * @return The control decoded from the provided string, or <CODE>null</CODE> 067 * if an error occurs while parsing the argument value. 068 */ 069 public static LDAPControl getControl(String argString, PrintStream err) 070 { 071 LDAPControl control = null; 072 String controlOID = null; 073 boolean controlCriticality = false; 074 ByteString controlValue = null; 075 076 int idx = argString.indexOf(":"); 077 078 if(idx < 0) 079 { 080 controlOID = argString; 081 } 082 else 083 { 084 controlOID = argString.substring(0, idx); 085 } 086 087 String lowerOID = toLowerCase(controlOID); 088 if (lowerOID.equals("accountusable") || lowerOID.equals("accountusability")) 089 { 090 controlOID = OID_ACCOUNT_USABLE_CONTROL; 091 } 092 else if (lowerOID.equals("authzid") || 093 lowerOID.equals("authorizationidentity")) 094 { 095 controlOID = OID_AUTHZID_REQUEST; 096 } 097 else if (lowerOID.equals("noop") || lowerOID.equals("no-op")) 098 { 099 controlOID = OID_LDAP_NOOP_OPENLDAP_ASSIGNED; 100 } 101 else if (lowerOID.equals("managedsait")) 102 { 103 controlOID = OID_MANAGE_DSAIT_CONTROL; 104 } 105 else if (lowerOID.equals("pwpolicy") || lowerOID.equals("passwordpolicy")) 106 { 107 controlOID = OID_PASSWORD_POLICY_CONTROL; 108 } 109 else if (lowerOID.equals("subtreedelete") || lowerOID.equals("treedelete")) 110 { 111 controlOID = OID_SUBTREE_DELETE_CONTROL; 112 } 113 else if (lowerOID.equals("realattrsonly") || 114 lowerOID.equals("realattributesonly")) 115 { 116 controlOID = OID_REAL_ATTRS_ONLY; 117 } 118 else if (lowerOID.equals("virtualattrsonly") || 119 lowerOID.equals("virtualattributesonly")) 120 { 121 controlOID = OID_VIRTUAL_ATTRS_ONLY; 122 } 123 else if(lowerOID.equals("effectiverights") || 124 lowerOID.equals("geteffectiverights")) 125 { 126 controlOID = OID_GET_EFFECTIVE_RIGHTS; 127 } 128 129 if (idx < 0) 130 { 131 return new LDAPControl(controlOID); 132 } 133 134 String remainder = argString.substring(idx+1, argString.length()); 135 136 idx = remainder.indexOf(":"); 137 if(idx == -1) 138 { 139 if(remainder.equalsIgnoreCase("true")) 140 { 141 controlCriticality = true; 142 } else if(remainder.equalsIgnoreCase("false")) 143 { 144 controlCriticality = false; 145 } else 146 { 147 printWrappedText(err, "Invalid format for criticality value:" + remainder); 148 return null; 149 } 150 return new LDAPControl(controlOID, controlCriticality); 151 } 152 153 String critical = remainder.substring(0, idx); 154 if(critical.equalsIgnoreCase("true")) 155 { 156 controlCriticality = true; 157 } else if(critical.equalsIgnoreCase("false")) 158 { 159 controlCriticality = false; 160 } else 161 { 162 printWrappedText(err, "Invalid format for criticality value:" + critical); 163 return null; 164 } 165 166 String valString = remainder.substring(idx+1, remainder.length()); 167 if (valString.length() == 0) 168 { 169 return new LDAPControl(controlOID, controlCriticality); 170 } 171 if(valString.charAt(0) == ':') 172 { 173 controlValue = 174 ByteString.valueOfUtf8(valString.substring(1, valString.length())); 175 } else if(valString.charAt(0) == '<') 176 { 177 // Read data from the file. 178 String filePath = valString.substring(1, valString.length()); 179 try 180 { 181 byte[] val = readBytesFromFile(filePath, err); 182 controlValue = ByteString.wrap(val); 183 } 184 catch (Exception e) 185 { 186 return null; 187 } 188 } else 189 { 190 controlValue = ByteString.valueOfUtf8(valString); 191 } 192 193 return new LDAPControl(controlOID, controlCriticality, controlValue); 194 } 195 196 /** 197 * Read the data from the specified file and return it in a byte array. 198 * 199 * @param filePath The path to the file that should be read. 200 * @param err A print stream to which error messages should be 201 * written if a problem occurs. 202 * 203 * @return A byte array containing the contents of the requested file. 204 * 205 * @throws IOException If a problem occurs while trying to read the 206 * specified file. 207 */ 208 public static byte[] readBytesFromFile(String filePath, PrintStream err) 209 throws IOException 210 { 211 byte[] val = null; 212 FileInputStream fis = null; 213 try 214 { 215 File file = new File(filePath); 216 fis = new FileInputStream (file); 217 long length = file.length(); 218 val = new byte[(int)length]; 219 // Read in the bytes 220 int offset = 0; 221 int numRead = 0; 222 while (offset < val.length && 223 (numRead=fis.read(val, offset, val.length-offset)) >= 0) { 224 offset += numRead; 225 } 226 227 // Ensure all the bytes have been read in 228 if (offset < val.length) 229 { 230 printWrappedText(err, ERR_FILE_NOT_FULLY_READABLE.get(filePath)); 231 return null; 232 } 233 234 return val; 235 } finally 236 { 237 if (fis != null) 238 { 239 fis.close(); 240 } 241 } 242 } 243 244 /** 245 * Prints a multi-line error message with the provided information to the 246 * given print stream. 247 * 248 * @param err The print stream to use to write the error message. 249 * @param explanation The general explanation to provide to the user, or 250 * {@code null} if there is none. 251 * @param resultCode The result code returned from the server, or -1 if 252 * there is none. 253 * @param errorMessage The additional information / error message returned 254 * from the server, or {@code null} if there was none. 255 * @param matchedDN The matched DN returned from the server, or 256 * {@code null} if there was none. 257 */ 258 public static void printErrorMessage(PrintStream err, LocalizableMessage explanation, 259 int resultCode, LocalizableMessage errorMessage, 260 DN matchedDN) 261 { 262 if (explanation != null && explanation.length() > 0) 263 { 264 err.println(explanation); 265 } 266 267 if (resultCode >= 0) 268 { 269 err.println(ERR_TOOL_RESULT_CODE.get(resultCode, 270 LDAPResultCode.toString(resultCode))); 271 } 272 273 if (errorMessage != null && errorMessage.length() > 0) 274 { 275 err.println(ERR_TOOL_ERROR_MESSAGE.get(errorMessage)); 276 } 277 278 if (matchedDN != null) 279 { 280 err.println(ERR_TOOL_MATCHED_DN.get(matchedDN)); 281 } 282 } 283 284 /** 285 * Returns the message to be displayed to the user when an exception occurs. 286 * <br> 287 * The code simply checks that the exception corresponds to a client side 288 * time out. 289 * @param ae the DecodeException that occurred connecting to the server or 290 * handling the response from the server. 291 * @return the message to be displayed to the user when an exception occurs. 292 */ 293 public static String getMessageForConnectionException(DecodeException ae) 294 { 295 Throwable cause = ae.getCause(); 296 if (cause != null) 297 { 298 boolean isTimeout = false; 299 while (cause != null && !isTimeout) 300 { 301 isTimeout = cause instanceof SocketTimeoutException; 302 cause = cause.getCause(); 303 } 304 if (isTimeout) 305 { 306 return ERR_CLIENT_SIDE_TIMEOUT.get(ae.getMessageObject()).toString(); 307 } 308 } 309 return ae.getMessageObject().toString(); 310 } 311} 312