/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package io.milton.ldap; import com.sun.jndi.ldap.Ber; import com.sun.jndi.ldap.BerEncoder; import io.milton.common.LogUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author brad */ public class LdapResponseHandler { private static final Logger log = LoggerFactory.getLogger(LdapResponseHandler.class); /** * reusable BER encoder */ final BerEncoder responseBer = new BerEncoder(); private final Socket client; private final OutputStream os; /** * Current LDAP version (used for String encoding) */ int ldapVersion = Ldap.LDAP_VERSION3; private String currentHostName; public LdapResponseHandler(Socket client, OutputStream os) { this.client = client; this.os = os; } public boolean isLdapV3() { return ldapVersion == Ldap.LDAP_VERSION3; } /** * Send Root DSE * * @param currentMessageId current message id * @throws IOException on error */ public void sendRootDSE(int currentMessageId) throws IOException { log.debug("LOG_LDAP_SEND_ROOT_DSE"); Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("objectClass", "top"); attributes.put("namingContexts", Ldap.NAMING_CONTEXTS); //attributes.put("supportedsaslmechanisms", "PLAIN"); sendEntry(currentMessageId, "Root DSE", attributes); } public void sendEntry(int currentMessageId, String dn, Map<String, Object> attributes) throws IOException { LogUtils.trace(log, "sendEntry", currentMessageId, dn, attributes.size()); // synchronize on responseBer synchronized (responseBer) { responseBer.reset(); responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); responseBer.encodeInt(currentMessageId); responseBer.beginSeq(Ldap.LDAP_REP_SEARCH); responseBer.encodeString(dn, isLdapV3()); responseBer.beginSeq(Ldap.LBER_SEQUENCE); for (Map.Entry<String, Object> entry : attributes.entrySet()) { responseBer.beginSeq(Ldap.LBER_SEQUENCE); responseBer.encodeString(entry.getKey(), isLdapV3()); responseBer.beginSeq(Ldap.LBER_SET); Object values = entry.getValue(); if (values instanceof String) { responseBer.encodeString((String) values, isLdapV3()); } else if (values instanceof List) { for (Object value : (List) values) { responseBer.encodeString((String) value, isLdapV3()); } } else { throw new RuntimeException("EXCEPTION_UNSUPPORTED_VALUE: " + values); } responseBer.endSeq(); responseBer.endSeq(); } responseBer.endSeq(); responseBer.endSeq(); responseBer.endSeq(); sendResponse(); } } public void sendErr(int currentMessageId, int responseOperation, Exception e) throws IOException { String message = e.getMessage(); if (message == null) { message = e.toString(); } sendClient(currentMessageId, responseOperation, Ldap.LDAP_OTHER, message); } public void sendClient(int currentMessageId, int responseOperation, int status, String message) throws IOException { responseBer.reset(); responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); responseBer.encodeInt(currentMessageId); responseBer.beginSeq(responseOperation); responseBer.encodeInt(status, Ldap.LBER_ENUMERATED); // dn responseBer.encodeString("", isLdapV3()); // error message responseBer.encodeString(message, isLdapV3()); responseBer.endSeq(); responseBer.endSeq(); sendResponse(); } public void sendResponse() throws IOException { //Ber.dumpBER(System.out, ">\n", responseBer.getBuf(), 0, responseBer.getDataLen()); os.write(responseBer.getBuf(), 0, responseBer.getDataLen()); os.flush(); } void setVersion(int v) { ldapVersion = v; } /** * Send Base Context * * @param currentMessageId current message id * @throws IOException on error */ public void sendBaseContext(int currentMessageId) throws IOException { List<String> objectClasses = new ArrayList<String>(); objectClasses.add("top"); objectClasses.add("organizationalUnit"); Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("objectClass", objectClasses); attributes.put("description", "Milton LDAP Gateway"); sendEntry(currentMessageId, Ldap.BASE_CONTEXT, attributes); } /** * Send ComputerContext * * @param currentMessageId current message id * @param returningAttributes attributes to return * @throws IOException on error */ public void sendComputerContext(int currentMessageId, Set<String> returningAttributes) throws IOException { List<String> objectClasses = new ArrayList<String>(); objectClasses.add("top"); objectClasses.add("apple-computer"); Map<String, Object> attributes = new HashMap<String, Object>(); addIf(attributes, returningAttributes, "objectClass", objectClasses); addIf(attributes, returningAttributes, "apple-generateduid", Ldap.COMPUTER_GUID); addIf(attributes, returningAttributes, "apple-serviceinfo", getServiceInfo()); // TODO: remove ? addIf(attributes, returningAttributes, "apple-xmlplist", getServiceInfo()); addIf(attributes, returningAttributes, "apple-serviceslocator", "::anyService"); addIf(attributes, returningAttributes, "cn", getCurrentHostName()); String dn = "cn=" + getCurrentHostName() + ", " + Ldap.COMPUTER_CONTEXT; log.debug("LOG_LDAP_SEND_COMPUTER_CONTEXT", dn, attributes); sendEntry(currentMessageId, dn, attributes); } protected String getServiceInfo() { StringBuilder buffer = new StringBuilder(); buffer.append("<?xml version='1.0' encoding='UTF-8'?>" + "<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>" + "<plist version='1.0'>" + "<dict>" + "<key>com.apple.macosxserver.host</key>" + "<array>" + "<string>localhost</string>" + // NOTE: Will be replaced by real hostname "</array>" + "<key>com.apple.macosxserver.virtualhosts</key>" + "<dict>" + "<key>" + Ldap.VIRTUALHOST_GUID + "</key>" + "<dict>" + "<key>hostDetails</key>" + "<dict>" + "<key>http</key>" + "<dict>" + "<key>enabled</key>" + "<true/>" + "</dict>" + "<key>https</key>" + "<dict>" + "<key>disabled</key>" + "<false/>" + "<key>port</key>" + "<integer>0</integer>" + "</dict>" + "</dict>" + "<key>hostname</key>" + "<string>"); try { buffer.append(getCurrentHostName()); } catch (UnknownHostException ex) { buffer.append("Unknown host"); } buffer.append("</string>" + "<key>serviceInfo</key>" + "<dict>" + "<key>calendar</key>" + "<dict>" + "<key>enabled</key>" + "<true/>" + "<key>templates</key>" + "<dict>" + "<key>calendarUserAddresses</key>" + "<array>" + "<string>%(principaluri)s</string>" + "<string>mailto:%(email)s</string>" + "<string>urn:uuid:%(guid)s</string>" + "</array>" + "<key>principalPath</key>" + "<string>/principals/__uuids__/%(guid)s/</string>" + "</dict>" + "</dict>" + "</dict>" + "<key>serviceType</key>" + "<array>" + "<string>calendar</string>" + "</array>" + "</dict>" + "</dict>" + "</dict>" + "</plist>"); return buffer.toString(); } protected String getCurrentHostName() throws UnknownHostException { if (currentHostName == null) { if (client.getInetAddress().isLoopbackAddress()) { // local address, probably using localhost in iCal URL currentHostName = "localhost"; } else { // remote address, send fully qualified domain name currentHostName = InetAddress.getLocalHost().getCanonicalHostName(); } } return currentHostName; } public void dumpBer(byte[] inbuf, int offset) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Ber.dumpBER(baos, "LDAP request buffer\n", inbuf, 0, offset); try { log.debug(new String(baos.toByteArray(), "UTF-8")); } catch (UnsupportedEncodingException e) { // should not happen log.error("", e); } } protected void addIf(Map<String, Object> attributes, Set<String> returningAttributes, String name, Object value) { if ((returningAttributes.isEmpty()) || returningAttributes.contains(name)) { attributes.put(name, value); } } public void sendBindResponse(int currentMessageId, int status, byte[] serverResponse) throws IOException { responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); responseBer.encodeInt(currentMessageId); responseBer.beginSeq(Ldap.LDAP_REP_BIND); responseBer.encodeInt(status, Ldap.LBER_ENUMERATED); // server credentials responseBer.encodeString("", isLdapV3()); responseBer.encodeString("", isLdapV3()); // challenge or response if (serverResponse != null) { responseBer.encodeOctetString(serverResponse, 0x87); } responseBer.endSeq(); responseBer.endSeq(); sendResponse(); } }