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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.schema;
028    import org.opends.messages.Message;
029    
030    
031    
032    import org.opends.server.admin.std.server.AttributeSyntaxCfg;
033    import org.opends.server.api.ApproximateMatchingRule;
034    import org.opends.server.api.AttributeSyntax;
035    import org.opends.server.api.EqualityMatchingRule;
036    import org.opends.server.api.OrderingMatchingRule;
037    import org.opends.server.api.SubstringMatchingRule;
038    import org.opends.server.config.ConfigException;
039    import org.opends.server.core.DirectoryServer;
040    import org.opends.server.types.ByteString;
041    import org.opends.server.types.DirectoryException;
042    
043    
044    import org.opends.server.types.ResultCode;
045    
046    import static org.opends.server.loggers.ErrorLogger.*;
047    import static org.opends.messages.SchemaMessages.*;
048    import org.opends.messages.MessageBuilder;
049    import static org.opends.server.schema.SchemaConstants.*;
050    
051    
052    /**
053     * This class defines the auth password attribute syntax, which is defined in
054     * RFC 3112 and is used to hold authentication information.  Only equality
055     * matching will be allowed by default.
056     */
057    public class AuthPasswordSyntax
058           extends AttributeSyntax<AttributeSyntaxCfg>
059    {
060      // The default equality matching rule for this syntax.
061      private EqualityMatchingRule defaultEqualityMatchingRule;
062    
063    
064    
065      /**
066       * Creates a new instance of this syntax.  Note that the only thing that
067       * should be done here is to invoke the default constructor for the
068       * superclass.  All initialization should be performed in the
069       * <CODE>initializeSyntax</CODE> method.
070       */
071      public AuthPasswordSyntax()
072      {
073        super();
074      }
075    
076    
077    
078      /**
079       * {@inheritDoc}
080       */
081      public void initializeSyntax(AttributeSyntaxCfg configuration)
082             throws ConfigException
083      {
084        defaultEqualityMatchingRule =
085             DirectoryServer.getEqualityMatchingRule(EMR_AUTH_PASSWORD_EXACT_OID);
086        if (defaultEqualityMatchingRule == null)
087        {
088          logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
089              EMR_AUTH_PASSWORD_EXACT_NAME, SYNTAX_AUTH_PASSWORD_NAME));
090        }
091      }
092    
093    
094    
095      /**
096       * Retrieves the common name for this attribute syntax.
097       *
098       * @return  The common name for this attribute syntax.
099       */
100      public String getSyntaxName()
101      {
102        return SYNTAX_AUTH_PASSWORD_NAME;
103      }
104    
105    
106    
107      /**
108       * Retrieves the OID for this attribute syntax.
109       *
110       * @return  The OID for this attribute syntax.
111       */
112      public String getOID()
113      {
114        return SYNTAX_AUTH_PASSWORD_OID;
115      }
116    
117    
118    
119      /**
120       * Retrieves a description for this attribute syntax.
121       *
122       * @return  A description for this attribute syntax.
123       */
124      public String getDescription()
125      {
126        return SYNTAX_AUTH_PASSWORD_DESCRIPTION;
127      }
128    
129    
130    
131      /**
132       * Retrieves the default equality matching rule that will be used for
133       * attributes with this syntax.
134       *
135       * @return  The default equality matching rule that will be used for
136       *          attributes with this syntax, or <CODE>null</CODE> if equality
137       *          matches will not be allowed for this type by default.
138       */
139      public EqualityMatchingRule getEqualityMatchingRule()
140      {
141        return defaultEqualityMatchingRule;
142      }
143    
144    
145    
146      /**
147       * Retrieves the default ordering matching rule that will be used for
148       * attributes with this syntax.
149       *
150       * @return  The default ordering matching rule that will be used for
151       *          attributes with this syntax, or <CODE>null</CODE> if ordering
152       *          matches will not be allowed for this type by default.
153       */
154      public OrderingMatchingRule getOrderingMatchingRule()
155      {
156        // There is no ordering matching rule by default.
157        return null;
158      }
159    
160    
161    
162      /**
163       * Retrieves the default substring matching rule that will be used for
164       * attributes with this syntax.
165       *
166       * @return  The default substring matching rule that will be used for
167       *          attributes with this syntax, or <CODE>null</CODE> if substring
168       *          matches will not be allowed for this type by default.
169       */
170      public SubstringMatchingRule getSubstringMatchingRule()
171      {
172        // There is no substring matching rule by default.
173        return null;
174      }
175    
176    
177    
178      /**
179       * Retrieves the default approximate matching rule that will be used for
180       * attributes with this syntax.
181       *
182       * @return  The default approximate matching rule that will be used for
183       *          attributes with this syntax, or <CODE>null</CODE> if approximate
184       *          matches will not be allowed for this type by default.
185       */
186      public ApproximateMatchingRule getApproximateMatchingRule()
187      {
188        // There is no approximate matching rule by default.
189        return null;
190      }
191    
192    
193    
194      /**
195       * Indicates whether the provided value is acceptable for use in an attribute
196       * with this syntax.  If it is not, then the reason may be appended to the
197       * provided buffer.
198       *
199       * @param  value          The value for which to make the determination.
200       * @param  invalidReason  The buffer to which the invalid reason should be
201       *                        appended.
202       *
203       * @return  <CODE>true</CODE> if the provided value is acceptable for use with
204       *          this syntax, or <CODE>false</CODE> if not.
205       */
206      public boolean valueIsAcceptable(ByteString value,
207                                       MessageBuilder invalidReason)
208      {
209        try
210        {
211          decodeAuthPassword(value.stringValue());
212          return true;
213        }
214        catch (DirectoryException de)
215        {
216          invalidReason.append(de.getMessageObject());
217          return false;
218        }
219      }
220    
221    
222    
223      /**
224       * Decodes the provided authentication password value into its component
225       * parts.
226       *
227       * @param  authPasswordValue  The authentication password value to be decoded.
228       *
229       * @return  A three-element array, containing the scheme, authInfo, and
230       *          authValue components of the given string, in that order.
231       *
232       * @throws  DirectoryException  If a problem is encountered while attempting
233       *                              to decode the value.
234       */
235      public static StringBuilder[] decodeAuthPassword(String authPasswordValue)
236             throws DirectoryException
237      {
238        // Create placeholders for the values to return.
239        StringBuilder scheme    = new StringBuilder();
240        StringBuilder authInfo  = new StringBuilder();
241        StringBuilder authValue = new StringBuilder();
242    
243    
244        // First, ignore any leading whitespace.
245        int length = authPasswordValue.length();
246        int  pos   = 0;
247        while ((pos < length) && (authPasswordValue.charAt(pos) == ' '))
248        {
249          pos++;
250        }
251    
252    
253        // The next set of characters will be the scheme, which must consist only
254        // of digits, uppercase alphabetic characters, dash, period, slash, and
255        // underscore characters.  It must be immediately followed by one or more
256        // spaces or a dollar sign.
257    readScheme:
258        while (pos < length)
259        {
260          char c = authPasswordValue.charAt(pos);
261    
262          switch (c)
263          {
264            case '0':
265            case '1':
266            case '2':
267            case '3':
268            case '4':
269            case '5':
270            case '6':
271            case '7':
272            case '8':
273            case '9':
274            case 'A':
275            case 'B':
276            case 'C':
277            case 'D':
278            case 'E':
279            case 'F':
280            case 'G':
281            case 'H':
282            case 'I':
283            case 'J':
284            case 'K':
285            case 'L':
286            case 'M':
287            case 'N':
288            case 'O':
289            case 'P':
290            case 'Q':
291            case 'R':
292            case 'S':
293            case 'T':
294            case 'U':
295            case 'V':
296            case 'W':
297            case 'X':
298            case 'Y':
299            case 'Z':
300            case '-':
301            case '.':
302            case '/':
303            case '_':
304              scheme.append(c);
305              pos++;
306              break;
307            case ' ':
308            case '$':
309              break readScheme;
310            default:
311              Message message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
312              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
313                                           message);
314          }
315        }
316    
317    
318        // The scheme must consist of at least one character.
319        if (scheme.length() == 0)
320        {
321          Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
322          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
323                  message);
324        }
325    
326    
327        // Ignore any spaces before the dollar sign separator.  Then read the dollar
328        // sign and ignore any trailing spaces.
329        while ((pos < length) && (authPasswordValue.charAt(pos) == ' '))
330        {
331          pos++;
332        }
333    
334        if ((pos < length) && (authPasswordValue.charAt(pos) == '$'))
335        {
336          pos++;
337        }
338        else
339        {
340          Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
341          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
342                  message);
343        }
344    
345        while ((pos < length) && (authPasswordValue.charAt(pos) == ' '))
346        {
347          pos++;
348        }
349    
350    
351        // The next component must be the authInfo element, containing only
352        // printable characters other than the dollar sign and space character.
353    readAuthInfo:
354        while (pos < length)
355        {
356          char c = authPasswordValue.charAt(pos);
357          if ((c == ' ') || (c == '$'))
358          {
359            break readAuthInfo;
360          }
361          else if (PrintableString.isPrintableCharacter(c))
362          {
363            authInfo.append(c);
364            pos++;
365          }
366          else
367          {
368            Message message =
369                ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
370            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
371                                         message);
372          }
373        }
374    
375    
376        // The authInfo element must consist of at least one character.
377        if (scheme.length() == 0)
378        {
379          Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
380          throw new DirectoryException(
381                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
382        }
383    
384    
385        // Ignore any spaces before the dollar sign separator.  Then read the dollar
386        // sign and ignore any trailing spaces.
387        while ((pos < length) && (authPasswordValue.charAt(pos) == ' '))
388        {
389          pos++;
390        }
391    
392        if ((pos < length) && (authPasswordValue.charAt(pos) == '$'))
393        {
394          pos++;
395        }
396        else
397        {
398          Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
399          throw new DirectoryException(
400                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
401        }
402    
403        while ((pos < length) && (authPasswordValue.charAt(pos) == ' '))
404        {
405          pos++;
406        }
407    
408    
409        // The final component must be the authValue element, containing only
410        // printable characters other than the dollar sign and space character.
411    readAuthValue:
412        while (pos < length)
413        {
414          char c = authPasswordValue.charAt(pos);
415          if ((c == ' ') || (c == '$'))
416          {
417            break ;
418          }
419          else if (PrintableString.isPrintableCharacter(c))
420          {
421            authValue.append(c);
422            pos++;
423          }
424          else
425          {
426            Message message =
427                ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
428            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
429                                         message);
430          }
431        }
432    
433    
434        // The authValue element must consist of at least one character.
435        if (scheme.length() == 0)
436        {
437          Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
438          throw new DirectoryException(
439                  ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
440        }
441    
442    
443        // The only characters remaining must be whitespace.
444        while (pos < length)
445        {
446          char c = authPasswordValue.charAt(pos);
447          if (c == ' ')
448          {
449            pos++;
450          }
451          else
452          {
453            Message message = ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
454            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
455                                         message);
456          }
457        }
458    
459    
460        // If we've gotten here, then everything must be OK.
461        return new StringBuilder[]
462        {
463          scheme,
464          authInfo,
465          authValue
466        };
467      }
468    
469    
470    
471      /**
472       * Indicates whether the provided value is encoded using the auth password
473       * syntax.
474       *
475       * @param  value  The value for which to make the determination.
476       *
477       * @return  <CODE>true</CODE> if the value appears to be encoded using the
478       *          auth password syntax, or <CODE>false</CODE> if not.
479       */
480      public static boolean isEncoded(ByteString value)
481      {
482        // FIXME -- Make this more efficient, and don't use exceptions for flow
483        // control.
484    
485    
486        try
487        {
488          decodeAuthPassword(value.stringValue());
489          return true;
490        }
491        catch (Exception e)
492        {
493          return false;
494        }
495      }
496    }
497