コード例 #1
1
      /** {@inheritDoc} */
      @Override
      public ByteString normalizeValue(ByteSequence value) throws DirectoryException {
        StringBuilder buffer = new StringBuilder();
        prepareUnicode(buffer, value, TRIM, CASE_FOLD);

        int bufferLength = buffer.length();
        if (bufferLength == 0) {
          if (value.length() > 0) {
            // This should only happen if the value is composed entirely
            // of spaces. In that case, the normalized value is a single space.
            return SINGLE_SPACE_VALUE;
          } else {
            // The value is empty, so it is already normalized.
            return ByteString.empty();
          }
        }

        // Replace any consecutive spaces with a single space.
        for (int pos = bufferLength - 1; pos > 0; pos--) {
          if (buffer.charAt(pos) == ' ') {
            if (buffer.charAt(pos - 1) == ' ') {
              buffer.delete(pos, pos + 1);
            }
          }
        }

        return ByteString.valueOf(buffer.toString());
      }
コード例 #2
0
  /**
   * Tests whether the Who Am I? extended operation with an internal authenticated connection
   * succeeds with default setting of "ds-cfg-reject-unauthenticated-requests".
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testAuthWAIDefCfg() throws Exception {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    Socket s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
    LDAPReader reader = new LDAPReader(s);
    LDAPWriter writer = new LDAPWriter(s);

    AtomicInteger nextMessageID = new AtomicInteger(1);
    LDAPAuthenticationHandler authHandler =
        new LDAPAuthenticationHandler(reader, writer, "localhost", nextMessageID);
    authHandler.doSimpleBind(
        3,
        ByteString.valueOf("cn=Directory Manager"),
        ByteString.valueOf("password"),
        new ArrayList<Control>(),
        new ArrayList<Control>());
    ByteString authzID = authHandler.requestAuthorizationIdentity();
    assertNotNull(authzID);

    LDAPMessage unbindMessage =
        new LDAPMessage(nextMessageID.getAndIncrement(), new UnbindRequestProtocolOp());
    writer.writeMessage(unbindMessage);
    s.close();
  }
コード例 #3
0
  /**
   * Tests the {@code getRawAuthorizationDN} and {@code setRawAuthorizationDN} methods.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testGetAndSetRawAuthorizationDN() throws Exception {
    ProxiedAuthV1Control proxyControl = new ProxiedAuthV1Control(ByteString.valueOf(""));
    assertEquals(proxyControl.getRawAuthorizationDN(), ByteString.valueOf(""));

    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    assertEquals(proxyControl.getRawAuthorizationDN(), ByteString.valueOf("uid=test,o=test"));
  }
コード例 #4
0
  /**
   * Tests whether an authenticated BIND request will be allowed with the default configuration
   * settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test()
  public void testAuthBindDefCfg() {
    DirectoryServer.setRejectUnauthenticatedRequests(false);

    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    ByteString user = ByteString.valueOf("cn=Directory Manager");
    ByteString password = ByteString.valueOf("password");
    BindOperation bindOperation = conn.processSimpleBind(user, password);
    assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
  }
コード例 #5
0
  /**
   * Tests the fourth constructor for the request control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor4NonNullContextID() throws Exception {
    VLVRequestControl vlvRequest =
        new VLVRequestControl(true, 0, 9, ByteString.valueOf("a"), ByteString.valueOf("foo"));

    assertEquals(vlvRequest.isCritical(), true);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getGreaterThanOrEqualAssertion().toString(), "a");
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(), VLVRequestControl.TYPE_TARGET_GREATERTHANOREQUAL);
    assertNotNull(vlvRequest.toString());
  }
コード例 #6
0
  /**
   * Tests the ASN.1 encoding for the response control.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testASN1ValueEncoding() throws Exception {
    ByteStringBuilder builder = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(builder);
    VLVResponseControl vlvResponse =
        new VLVResponseControl(true, 0, 15, 0, ByteString.valueOf("foo"));
    vlvResponse.writeValue(writer);

    ASN1Reader reader = ASN1.getReader(builder.toByteString());
    // Should start as an octet string with a nested sequence
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
    reader.readStartSequence();
    // Should be an sequence start
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
    reader.readStartSequence();
    // Should be an integer with targetPosition
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_INTEGER_TYPE);
    assertEquals(reader.readInteger(), 0);
    // Should be an integer with contentCount
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_INTEGER_TYPE);
    assertEquals(reader.readInteger(), 15);
    // Should be an enumerated with virtualListViewResult
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_ENUMERATED_TYPE);
    assertEquals(reader.readEnumerated(), 0);
    // Should be an octet string with contextID
    assertEquals(reader.peekType(), ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
    assertEquals(reader.readOctetStringAsString(), "foo");
  }
コード例 #7
0
 /**
  * Verifies that the server will reject a CRAM-MD5 bind in which the first message contains SASL
  * credentials (which isn't allowed).
  *
  * @throws Exception If an unexpected problem occurs.
  */
 @Test()
 public void testOutOfSequenceBind() throws Exception {
   InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
   BindOperation bindOperation =
       conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, ByteString.valueOf("invalid"));
   assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
 }
コード例 #8
0
  /**
   * Tests the {@code decodeControl} method when the control value is not a sequence.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test(expectedExceptions = {DirectoryException.class})
  public void testDecodeControlValueNotSequence() throws Exception {
    LDAPControl c =
        new LDAPControl(OID_PROXIED_AUTH_V1, true, ByteString.valueOf("uid=test,o=test"));

    ProxiedAuthV1Control.DECODER.decode(c.isCritical(), c.getValue());
  }
コード例 #9
0
  /**
   * Tests whether authenticated and unauthenticated BIND requests will be allowed with the new
   * configuration settings for "ds-cfg-reject-unauthenticated-requests" .
   */
  @Test
  public void testBindNewCfg() {
    try {
      DirectoryServer.setRejectUnauthenticatedRequests(true);

      InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
      ByteString user = ByteString.valueOf("cn=Directory Manager");
      ByteString password = ByteString.valueOf("password");
      // Unauthenticated BIND request.
      BindOperation bindOperation = conn.processSimpleBind(DN.nullDN(), null);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
      // Authenticated BIND request.
      bindOperation = conn.processSimpleBind(user, password);
      assertEquals(bindOperation.getResultCode(), ResultCode.SUCCESS);
    } finally {
      DirectoryServer.setRejectUnauthenticatedRequests(false);
    }
  }
コード例 #10
0
  /**
   * Verifies that the server will reject a CRAM-MD5 bind with credentials containing a malformed
   * digest.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testMalformedDigest() throws Exception {
    InternalClientConnection conn = new InternalClientConnection(new AuthenticationInfo());
    BindOperation bindOperation = conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, null);
    assertEquals(bindOperation.getResultCode(), ResultCode.SASL_BIND_IN_PROGRESS);

    ByteString creds = ByteString.valueOf("dn:cn=Directory Manager malformeddigest");
    bindOperation = conn.processSASLBind(DN.nullDN(), SASL_MECHANISM_CRAM_MD5, creds);
    assertFalse(bindOperation.getResultCode() == ResultCode.SUCCESS);
  }
コード例 #11
0
  /**
   * Tests the {@code toString} methods.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testToString() throws Exception {
    // The default toString() calls the version that takes a string builder
    // argument, so we only need to use the default version to cover both cases.
    ProxiedAuthV1Control proxyControl =
        new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    proxyControl.toString();

    proxyControl = new ProxiedAuthV1Control(DN.decode("uid=test,o=test"));
    proxyControl.toString();
  }
  /** {@inheritDoc} */
  @Override()
  public Set<AttributeValue> getValues(Entry entry, VirtualAttributeRule rule) {
    Backend backend = DirectoryServer.getBackend(entry.getDN());

    try {
      ConditionResult ret = backend.hasSubordinates(entry.getDN());
      if (ret != null && ret != ConditionResult.UNDEFINED) {
        AttributeValue value =
            AttributeValues.create(
                ByteString.valueOf(ret.toString()), ByteString.valueOf(ret.toString()));
        return Collections.singleton(value);
      }
    } catch (DirectoryException de) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
    }

    return Collections.emptySet();
  }
コード例 #13
0
  /**
   * Tests the second constructor for the response control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testResponseConstructor2NonNullContextID() throws Exception {
    VLVResponseControl vlvResponse =
        new VLVResponseControl(true, 0, 15, 0, ByteString.valueOf("foo"));

    assertEquals(vlvResponse.isCritical(), true);
    assertEquals(vlvResponse.getTargetPosition(), 0);
    assertEquals(vlvResponse.getContentCount(), 15);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertNotNull(vlvResponse.getContextID());
    assertNotNull(vlvResponse.toString());
  }
コード例 #14
0
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value that is after all values in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueAfterAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("sn"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("zz")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();

    // It will be successful because the control isn't critical.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);

    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      }
    }

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
コード例 #15
0
  /**
   * Tests the first constructor, which creates an instance of the control using a raw, unprocessed
   * DN.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testConstructor1() throws Exception {
    // Try a DN of "null", which is not valid and will fail on the attempt to
    // create the control
    ProxiedAuthV1Control proxyControl;
    try {
      proxyControl = new ProxiedAuthV1Control((ByteString) null);
      throw new AssertionError(
          "Expected a failure when creating a proxied "
              + "auth V1 control with a null octet string.");
    } catch (Throwable t) {
    }

    // Try an empty DN, which is acceptable.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf(""));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    assertTrue(proxyControl.getAuthorizationDN().isNullDN());

    // Try a valid DN, which is acceptable.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("uid=test,o=test"));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    assertEquals(proxyControl.getAuthorizationDN(), DN.decode("uid=test,o=test"));

    // Try an invalid DN, which will be initally accepted but will fail when
    // attempting to get the authorization DN.
    proxyControl = new ProxiedAuthV1Control(ByteString.valueOf("invalid"));
    assertTrue(proxyControl.getOID().equals(OID_PROXIED_AUTH_V1));
    assertTrue(proxyControl.isCritical());
    try {
      proxyControl.getAuthorizationDN();
      throw new AssertionError(
          "Expected a failure when creating a proxied "
              + "auth V1 control with an invalid DN string.");
    } catch (Exception e) {
    }
  }
コード例 #16
0
  /**
   * Tests the {@code decodeControl} method when the control value is a sequence with multiple
   * elements.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test
  public void testDecodeControlValueMultiElementSequence() throws Exception {
    ByteStringBuilder bsb = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(bsb);
    writer.writeStartSequence();
    writer.writeOctetString("uid=element1,o=test");
    writer.writeOctetString("uid=element2,o=test");
    writer.writeEndSequence();
    LDAPControl c = new LDAPControl(OID_PROXIED_AUTH_V1, true, bsb.toByteString());

    assertEquals(
        ByteString.valueOf("uid=element1,o=test"),
        ProxiedAuthV1Control.DECODER.decode(c.isCritical(), c.getValue()).getRawAuthorizationDN());
  }
コード例 #17
0
  /**
   * Tests the second constructor for the request control with a non-null context ID.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testRequestConstructor2NonNullContextID() throws Exception {
    VLVRequestControl vlvRequest =
        new VLVRequestControl(true, 0, 9, 1, 0, ByteString.valueOf("foo"));

    assertEquals(vlvRequest.isCritical(), true);
    assertEquals(vlvRequest.getBeforeCount(), 0);
    assertEquals(vlvRequest.getAfterCount(), 9);
    assertEquals(vlvRequest.getOffset(), 1);
    assertEquals(vlvRequest.getContentCount(), 0);
    assertNotNull(vlvRequest.getContextID());
    assertEquals(vlvRequest.getTargetType(), VLVRequestControl.TYPE_TARGET_BYOFFSET);
    assertNull(vlvRequest.getGreaterThanOrEqualAssertion());
    assertNotNull(vlvRequest.toString());
  }
  private Object[] generateValues(String password) throws Exception {
    ByteString bytePassword = ByteString.valueOf(password);
    SaltedMD5PasswordStorageScheme scheme = new SaltedMD5PasswordStorageScheme();

    ConfigEntry configEntry =
        DirectoryServer.getConfigEntry(
            DN.decode("cn=Salted MD5,cn=Password Storage Schemes,cn=config"));

    SaltedMD5PasswordStorageSchemeCfg configuration =
        AdminTestCaseUtils.getConfiguration(
            SaltedMD5PasswordStorageSchemeCfgDefn.getInstance(), configEntry.getEntry());

    scheme.initializePasswordStorageScheme(configuration);

    ByteString encodedAuthPassword = scheme.encodePasswordWithScheme(bytePassword);

    return new Object[] {encodedAuthPassword.toString(), password, true};
  }
コード例 #19
0
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value before any actual value in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueBeforeAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("a")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN); // Aaccf
    expectedDNOrder.add(aaronZimmermanDN); // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN); // Albert, higher entry ID

    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries()) {
      returnedDNOrder.add(e.getDN());
    }

    assertEquals(returnedDNOrder, expectedDNOrder);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);

    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          sortResponse =
              ServerSideSortResponseControl.DECODER.decode(
                  c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          sortResponse = (ServerSideSortResponseControl) c;
        }
      } else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      } else {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }

    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
コード例 #20
0
  /**
   * Decodes the contents of the provided byte sequence as an ldap syntax definition according to
   * the rules of this syntax. Note that the provided byte sequence value does not need to be
   * normalized (and in fact, it should not be in order to allow the desired capitalization to be
   * preserved).
   *
   * @param value The byte sequence containing the value to decode (it does not need to be
   *     normalized).
   * @param schema The schema to use to resolve references to other schema elements.
   * @param allowUnknownElements Indicates whether to allow values that are not defined in the
   *     server schema. This should only be true when called by {@code valueIsAcceptable}. Not used
   *     for LDAP Syntaxes
   * @return The decoded ldapsyntax definition.
   * @throws DirectoryException If the provided value cannot be decoded as an ldapsyntax definition.
   */
  public static LDAPSyntaxDescription decodeLDAPSyntax(
      ByteSequence value, Schema schema, boolean allowUnknownElements) throws DirectoryException {
    // Get string representations of the provided value using the provided form.
    String valueStr = value.toString();

    // We'll do this a character at a time.  First, skip over any leading
    // whitespace.
    int pos = 0;
    int length = valueStr.length();
    while ((pos < length) && (valueStr.charAt(pos) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the value was empty or contained only whitespace.  That
      // is illegal.

      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE.get();
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // The next character must be an open parenthesis.  If it is not, then that
    // is an error.
    char c = valueStr.charAt(pos++);
    if (c != '(') {

      Message message =
          ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
              valueStr, (pos - 1), String.valueOf(c));
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // Skip over any spaces immediately following the opening parenthesis.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    int oidStartPos = pos;
    if (isDigit(c)) {
      // This must be a numeric OID.  In that case, we will accept only digits
      // and periods, but not consecutive periods.
      boolean lastWasPeriod = false;
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (c == '.') {
          if (lastWasPeriod) {
            Message message =
                ERR_ATTR_SYNTAX_LDAPSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(valueStr, (pos - 1));
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          } else {
            lastWasPeriod = true;
          }
        } else if (!isDigit(c)) {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        } else {
          lastWasPeriod = false;
        }
        pos++;
      }
    } else {
      // This must be a "fake" OID.  In this case, we will only accept
      // alphabetic characters, numeric digits, and the hyphen.
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (isAlpha(c)
            || isDigit(c)
            || (c == '-')
            || ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) {
          // This is fine.  It is an acceptable character.
          pos++;
        } else {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      }
    }

    // If we're at the end of the value, then it isn't a valid attribute type
    // description.  Otherwise, parse out the OID.
    String oid;
    if (pos >= length) {
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    } else {
      oid = toLowerCase(valueStr.substring(oidStartPos, pos));
    }

    // Skip over the space(s) after the OID.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // At this point, we should have a pretty specific syntax that describes
    // what may come next, but some of the components are optional and it would
    // be pretty easy to put something in the wrong order, so we will be very
    // flexible about what we can accept.  Just look at the next token, figure
    // out what it is and how to treat what comes after it, then repeat until
    // we get to the end of the value.  But before we start, set default values
    // for everything else we might need to know.
    String description = null;
    LDAPSyntaxDescriptionSyntax syntax = null;
    HashMap<String, List<String>> extraProperties = new LinkedHashMap<String, List<String>>();
    boolean hasXSyntaxToken = false;

    while (true) {
      StringBuilder tokenNameBuffer = new StringBuilder();
      pos = readTokenName(valueStr, tokenNameBuffer, pos);
      String tokenName = tokenNameBuffer.toString();
      String lowerTokenName = toLowerCase(tokenName);
      if (tokenName.equals(")")) {
        // We must be at the end of the value.  If not, then that's a problem.
        if (pos < length) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(valueStr, (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        break;
      } else if (lowerTokenName.equals("desc")) {
        // This specifies the description for the attribute type.  It is an
        // arbitrary string of characters enclosed in single quotes.
        StringBuilder descriptionBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, descriptionBuffer, pos);
        description = descriptionBuffer.toString();
      } else if (lowerTokenName.equals("x-subst")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder woidBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, woidBuffer, pos);
        String syntaxOID = toLowerCase(woidBuffer.toString());
        AttributeSyntax<?> subSyntax = schema.getSyntax(syntaxOID);
        if (subSyntax == null) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_SYNTAX.get(String.valueOf(oid), syntaxOID);
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
        }
        syntax = new SubstitutionSyntax(subSyntax, valueStr, description, oid);
      } else if (lowerTokenName.equals("x-pattern")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder regexBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, regexBuffer, pos);
        String regex = regexBuffer.toString().trim();
        if (regex.length() == 0) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        try {
          Pattern pattern = Pattern.compile(regex);
          syntax = new RegexSyntax(pattern, valueStr, description, oid);
        } catch (Exception e) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get(valueStr, regex);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      } else if (lowerTokenName.equals("x-enum")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        LinkedList<String> values = new LinkedList<String>();
        pos = readExtraParameterValues(valueStr, values, pos);

        if (values.isEmpty()) {
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_ENUM_NO_VALUES.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        // Parse all enum values, check for uniqueness
        LinkedList<ByteSequence> entries = new LinkedList<ByteSequence>();
        for (String v : values) {
          ByteString entry = ByteString.valueOf(v);
          if (entries.contains(entry)) {
            Message message =
                WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get(
                    valueStr, entry.toString(), pos);
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          }
          entries.add(entry);
        }
        syntax = new EnumSyntax(entries, valueStr, description, oid);
      } else if (tokenName.matches("X\\-[_\\p{Alpha}-]+")) {
        // This must be a non-standard property and it must be followed by
        // either a single value in single quotes or an open parenthesis
        // followed by one or more values in single quotes separated by spaces
        // followed by a close parenthesis.
        List<String> valueList = new ArrayList<String>();
        pos = readExtraParameterValues(valueStr, valueList, pos);
        extraProperties.put(tokenName, valueList);
      } else {
        // Unknown Token
        Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get(valueStr, tokenName, pos);
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
    }
    if (syntax == null) {
      // Create a plain Syntax. That seems to be required by export/import
      // Schema backend.
      syntax = new LDAPSyntaxDescriptionSyntax();
    }

    CommonSchemaElements.checkSafeProperties(extraProperties);

    // Since we reached here it means everything is OK.
    return new LDAPSyntaxDescription(
        valueStr, syntax,
        description, extraProperties);
  }