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