| /* |
| * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import java.math.BigInteger; |
| import java.util.Arrays; |
| import java.util.Optional; |
| import java.util.stream.Stream; |
| |
| /** |
| * An LDAP message. |
| */ |
| public class LdapMessage { |
| |
| private final byte[] message; |
| private int messageID; |
| private Operation operation; |
| |
| public enum Operation { |
| BIND_REQUEST(0x60, "BindRequest"), // [APPLICATION 0] |
| BIND_RESPONSE(0x61, "BindResponse"), // [APPLICATION 1] |
| UNBIND_REQUEST(0x42, "UnbindRequest"), // [APPLICATION 2] |
| SEARCH_REQUEST(0x63, "SearchRequest"), // [APPLICATION 3] |
| SEARCH_RESULT_ENTRY(0x64, "SearchResultEntry"), // [APPLICATION 4] |
| SEARCH_RESULT_DONE(0x65, "SearchResultDone"), // [APPLICATION 5] |
| MODIFY_REQUEST(0x66, "ModifyRequest"), // [APPLICATION 6] |
| MODIFY_RESPONSE(0x67, "ModifyResponse"), // [APPLICATION 7] |
| ADD_REQUEST(0x68, "AddRequest"), // [APPLICATION 8] |
| ADD_RESPONSE(0x69, "AddResponse"), // [APPLICATION 9] |
| DELETE_REQUEST(0x4A, "DeleteRequest"), // [APPLICATION 10] |
| DELETE_RESPONSE(0x6B, "DeleteResponse"), // [APPLICATION 11] |
| MODIFY_DN_REQUEST(0x6C, "ModifyDNRequest"), // [APPLICATION 12] |
| MODIFY_DN_RESPONSE(0x6D, "ModifyDNResponse"), // [APPLICATION 13] |
| COMPARE_REQUEST(0x6E, "CompareRequest"), // [APPLICATION 14] |
| COMPARE_RESPONSE(0x6F, "CompareResponse"), // [APPLICATION 15] |
| ABANDON_REQUEST(0x50, "AbandonRequest"), // [APPLICATION 16] |
| SEARCH_RESULT_REFERENCE(0x73, "SearchResultReference"), // [APPLICATION 19] |
| EXTENDED_REQUEST(0x77, "ExtendedRequest"), // [APPLICATION 23] |
| EXTENDED_RESPONSE(0x78, "ExtendedResponse"), // [APPLICATION 24] |
| INTERMEDIATE_RESPONSE(0x79, "IntermediateResponse"); // [APPLICATION 25] |
| |
| private final int id; |
| private final String name; |
| |
| Operation(int id, String name) { |
| this.id = id; |
| this.name = name; |
| } |
| |
| public int getId() { |
| return id; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| |
| private static Operation fromId(int id) { |
| Optional<Operation> optional = Stream.of(Operation.values()) |
| .filter(o -> o.id == id).findFirst(); |
| if (optional.isPresent()) { |
| return optional.get(); |
| } else { |
| throw new RuntimeException( |
| "Unknown id " + id + " for enum Operation."); |
| } |
| } |
| } |
| |
| public LdapMessage(byte[] message) { |
| this.message = message; |
| parse(); |
| } |
| |
| public LdapMessage(String hexString) { |
| this(parseHexBinary(hexString)); |
| } |
| |
| // Extracts the message ID and operation ID from an LDAP protocol encoding |
| private void parse() { |
| if (message == null || message.length < 2) { |
| throw new RuntimeException( |
| "Invalid ldap message: " + Arrays.toString(message)); |
| } |
| |
| if (message[0] != 0x30) { |
| throw new RuntimeException("Bad LDAP encoding in message, " |
| + "expected ASN.1 SEQUENCE tag (0x30), encountered " |
| + message[0]); |
| } |
| |
| int index = 2; |
| if ((message[1] & 0x80) == 0x80) { |
| index += (message[1] & 0x0F); |
| } |
| |
| if (message[index] != 0x02) { |
| throw new RuntimeException("Bad LDAP encoding in message, " |
| + "expected ASN.1 INTEGER tag (0x02), encountered " |
| + message[index]); |
| } |
| int length = message[index + 1]; |
| index += 2; |
| messageID = new BigInteger(1, |
| Arrays.copyOfRange(message, index, index + length)).intValue(); |
| index += length; |
| int operationID = message[index]; |
| operation = Operation.fromId(operationID); |
| } |
| |
| /** |
| * Return original ldap message in byte array. |
| * |
| * @return original ldap message |
| */ |
| public byte[] getMessage() { |
| return Arrays.copyOf(message, message.length); |
| } |
| |
| /** |
| * Return ldap message id. |
| * |
| * @return ldap message id. |
| */ |
| public int getMessageID() { |
| return messageID; |
| } |
| |
| /** |
| * Return ldap message's operation. |
| * |
| * @return ldap message's operation. |
| */ |
| public Operation getOperation() { |
| return operation; |
| } |
| |
| private static byte[] parseHexBinary(String s) { |
| |
| final int len = s.length(); |
| |
| // "111" is not a valid hex encoding. |
| if (len % 2 != 0) { |
| throw new IllegalArgumentException( |
| "hexBinary needs to be even-length: " + s); |
| } |
| |
| byte[] out = new byte[len / 2]; |
| |
| for (int i = 0; i < len; i += 2) { |
| int h = Character.digit(s.charAt(i), 16); |
| int l = Character.digit(s.charAt(i + 1), 16); |
| if (h == -1 || l == -1) { |
| throw new IllegalArgumentException( |
| "contains illegal character for hexBinary: " + s); |
| } |
| |
| out[i / 2] = (byte) (h * 16 + l); |
| } |
| |
| return out; |
| } |
| |
| public static int getMessageLength(byte[] encoding) { |
| if (encoding.length < 2) { |
| // not enough data to extract msg len, just return -1 |
| return -1; |
| } |
| |
| if (encoding[0] != 0x30) { |
| throw new RuntimeException("Error: bad LDAP encoding message: " |
| + "expected ASN.1 SEQUENCE tag (0x30), encountered " |
| + encoding[0]); |
| } |
| |
| int len; |
| int index = 1; |
| int payloadLen = 0; |
| |
| if ((encoding[1] & 0x80) == 0x80) { |
| len = (encoding[1] & 0x0F); |
| index++; |
| } else { |
| len = 1; |
| } |
| |
| if (len > 4) { |
| throw new RuntimeException( |
| "Error: LDAP encoding message payload too large"); |
| } |
| |
| if (encoding.length < index + len) { |
| // additional data required to extract payload len, return -1 |
| return -1; |
| } |
| |
| for (byte b : Arrays.copyOfRange(encoding, index, index + len)) { |
| payloadLen = payloadLen << 8 | (b & 0xFF); |
| } |
| |
| if (payloadLen <= 0) { |
| throw new RuntimeException( |
| "Error: invalid LDAP encoding message length or payload too large"); |
| } |
| |
| return index + len + payloadLen; |
| } |
| } |