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-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.schema;
028import static org.opends.messages.SchemaMessages.*;
029import static org.opends.server.schema.SchemaConstants.*;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.opendj.ldap.ByteSequence;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.Schema;
035import org.forgerock.opendj.ldap.schema.Syntax;
036import org.opends.server.admin.std.server.AttributeSyntaxCfg;
037import org.opends.server.api.AttributeSyntax;
038import org.opends.server.types.DirectoryException;
039
040
041/**
042 * This class defines the auth password attribute syntax, which is defined in
043 * RFC 3112 and is used to hold authentication information.  Only equality
044 * matching will be allowed by default.
045 */
046public class AuthPasswordSyntax
047       extends AttributeSyntax<AttributeSyntaxCfg>
048{
049
050  /**
051   * Creates a new instance of this syntax.  Note that the only thing that
052   * should be done here is to invoke the default constructor for the
053   * superclass.  All initialization should be performed in the
054   * <CODE>initializeSyntax</CODE> method.
055   */
056  public AuthPasswordSyntax()
057  {
058    super();
059  }
060
061  /** {@inheritDoc} */
062  @Override
063  public Syntax getSDKSyntax(Schema schema)
064  {
065    return schema.getSyntax(SchemaConstants.SYNTAX_AUTH_PASSWORD_OID);
066  }
067
068  /**
069   * Retrieves the common name for this attribute syntax.
070   *
071   * @return  The common name for this attribute syntax.
072   */
073  @Override
074  public String getName()
075  {
076    return SYNTAX_AUTH_PASSWORD_NAME;
077  }
078
079  /**
080   * Retrieves the OID for this attribute syntax.
081   *
082   * @return  The OID for this attribute syntax.
083   */
084  @Override
085  public String getOID()
086  {
087    return SYNTAX_AUTH_PASSWORD_OID;
088  }
089
090  /**
091   * Retrieves a description for this attribute syntax.
092   *
093   * @return  A description for this attribute syntax.
094   */
095  @Override
096  public String getDescription()
097  {
098    return SYNTAX_AUTH_PASSWORD_DESCRIPTION;
099  }
100
101  /**
102   * Decodes the provided authentication password value into its component
103   * parts.
104   *
105   * @param  authPasswordValue  The authentication password value to be decoded.
106   *
107   * @return  A three-element array, containing the scheme, authInfo, and
108   *          authValue components of the given string, in that order.
109   *
110   * @throws  DirectoryException  If a problem is encountered while attempting
111   *                              to decode the value.
112   */
113  public static StringBuilder[] decodeAuthPassword(String authPasswordValue)
114         throws DirectoryException
115  {
116    // Create placeholders for the values to return.
117    StringBuilder scheme    = new StringBuilder();
118    StringBuilder authInfo  = new StringBuilder();
119    StringBuilder authValue = new StringBuilder();
120
121
122    // First, ignore any leading whitespace.
123    int length = authPasswordValue.length();
124    int  pos   = 0;
125    while (pos < length && authPasswordValue.charAt(pos) == ' ')
126    {
127      pos++;
128    }
129
130
131    // The next set of characters will be the scheme, which must consist only
132    // of digits, uppercase alphabetic characters, dash, period, slash, and
133    // underscore characters.  It must be immediately followed by one or more
134    // spaces or a dollar sign.
135readScheme:
136    while (pos < length)
137    {
138      char c = authPasswordValue.charAt(pos);
139
140      switch (c)
141      {
142        case '0':
143        case '1':
144        case '2':
145        case '3':
146        case '4':
147        case '5':
148        case '6':
149        case '7':
150        case '8':
151        case '9':
152        case 'A':
153        case 'B':
154        case 'C':
155        case 'D':
156        case 'E':
157        case 'F':
158        case 'G':
159        case 'H':
160        case 'I':
161        case 'J':
162        case 'K':
163        case 'L':
164        case 'M':
165        case 'N':
166        case 'O':
167        case 'P':
168        case 'Q':
169        case 'R':
170        case 'S':
171        case 'T':
172        case 'U':
173        case 'V':
174        case 'W':
175        case 'X':
176        case 'Y':
177        case 'Z':
178        case '-':
179        case '.':
180        case '/':
181        case '_':
182          scheme.append(c);
183          pos++;
184          break;
185        case ' ':
186        case '$':
187          break readScheme;
188        default:
189          LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
190          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
191                                       message);
192      }
193    }
194
195
196    // The scheme must consist of at least one character.
197    if (scheme.length() == 0)
198    {
199      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
200      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
201              message);
202    }
203
204
205    // Ignore any spaces before the dollar sign separator.  Then read the dollar
206    // sign and ignore any trailing spaces.
207    while (pos < length && authPasswordValue.charAt(pos) == ' ')
208    {
209      pos++;
210    }
211
212    if (pos < length && authPasswordValue.charAt(pos) == '$')
213    {
214      pos++;
215    }
216    else
217    {
218      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
219      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
220              message);
221    }
222
223    while (pos < length && authPasswordValue.charAt(pos) == ' ')
224    {
225      pos++;
226    }
227
228
229    // The next component must be the authInfo element, containing only
230    // printable characters other than the dollar sign and space character.
231readAuthInfo:
232    while (pos < length)
233    {
234      char c = authPasswordValue.charAt(pos);
235      if (c == ' ' || c == '$')
236      {
237        break readAuthInfo;
238      }
239      else if (PrintableString.isPrintableCharacter(c))
240      {
241        authInfo.append(c);
242        pos++;
243      }
244      else
245      {
246        LocalizableMessage message =
247            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
248        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
249                                     message);
250      }
251    }
252
253
254    // The authInfo element must consist of at least one character.
255    if (scheme.length() == 0)
256    {
257      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
258      throw new DirectoryException(
259              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
260    }
261
262
263    // Ignore any spaces before the dollar sign separator.  Then read the dollar
264    // sign and ignore any trailing spaces.
265    while (pos < length && authPasswordValue.charAt(pos) == ' ')
266    {
267      pos++;
268    }
269
270    if (pos < length && authPasswordValue.charAt(pos) == '$')
271    {
272      pos++;
273    }
274    else
275    {
276      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
277      throw new DirectoryException(
278              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
279    }
280
281    while (pos < length && authPasswordValue.charAt(pos) == ' ')
282    {
283      pos++;
284    }
285
286
287    // The final component must be the authValue element, containing only
288    // printable characters other than the dollar sign and space character.
289    while (pos < length)
290    {
291      char c = authPasswordValue.charAt(pos);
292      if (c == ' ' || c == '$')
293      {
294        break ;
295      }
296      else if (PrintableString.isPrintableCharacter(c))
297      {
298        authValue.append(c);
299        pos++;
300      }
301      else
302      {
303        LocalizableMessage message =
304            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
305        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
306                                     message);
307      }
308    }
309
310
311    // The authValue element must consist of at least one character.
312    if (scheme.length() == 0)
313    {
314      LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
315      throw new DirectoryException(
316              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
317    }
318
319
320    // The only characters remaining must be whitespace.
321    while (pos < length)
322    {
323      char c = authPasswordValue.charAt(pos);
324      if (c == ' ')
325      {
326        pos++;
327      }
328      else
329      {
330        LocalizableMessage message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
331        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
332                                     message);
333      }
334    }
335
336
337    // If we've gotten here, then everything must be OK.
338    return new StringBuilder[]
339    {
340      scheme,
341      authInfo,
342      authValue
343    };
344  }
345
346  /**
347   * Indicates whether the provided value is encoded using the auth password
348   * syntax.
349   *
350   * @param  value  The value for which to make the determination.
351   *
352   * @return  <CODE>true</CODE> if the value appears to be encoded using the
353   *          auth password syntax, or <CODE>false</CODE> if not.
354   */
355  public static boolean isEncoded(ByteSequence value)
356  {
357    // FIXME -- Make this more efficient, and don't use exceptions for flow
358    // control.
359
360
361    try
362    {
363      decodeAuthPassword(value.toString());
364      return true;
365    }
366    catch (Exception e)
367    {
368      return false;
369    }
370  }
371}
372