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