org.gcaldaemon.core.ldap.LDAPListener.java Source code

Java tutorial

Introduction

Here is the source code for org.gcaldaemon.core.ldap.LDAPListener.java

Source

//
// GCALDaemon is an OS-independent Java program that offers two-way
// synchronization between Google Calendar and various iCalalendar (RFC 2445)
// compatible calendar applications (Sunbird, Rainlendar, iCal, Lightning, etc).
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
// 
// Project home:
// http://gcaldaemon.sourceforge.net
//
package org.gcaldaemon.core.ldap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.shared.asn1.codec.DecoderException;
import org.apache.directory.shared.ldap.codec.LdapConstants;
import org.apache.directory.shared.ldap.codec.LdapDecoder;
import org.apache.directory.shared.ldap.codec.LdapMessage;
import org.apache.directory.shared.ldap.codec.LdapMessageContainer;
import org.apache.directory.shared.ldap.codec.LdapResponse;
import org.apache.directory.shared.ldap.codec.LdapResult;
import org.apache.directory.shared.ldap.codec.bind.BindResponse;
import org.apache.directory.shared.ldap.codec.search.Filter;
import org.apache.directory.shared.ldap.codec.search.SearchRequest;
import org.apache.directory.shared.ldap.codec.search.SearchResultDone;
import org.apache.directory.shared.ldap.codec.search.SearchResultEntry;
import org.apache.directory.shared.ldap.codec.search.SubstringFilter;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.gcaldaemon.core.FilterMask;
import org.gcaldaemon.core.GmailContact;
import org.gcaldaemon.core.StringUtils;

/**
 * LDAP server thread.
 * 
 * Created: Jan 03, 2007 12:50:56 PM
 * 
 * @author Andras Berkes
 */
final class LDAPListener extends Thread {

    // --- CONSTANTS ---

    private static final String PLATFORM_ENCODING = Charset.defaultCharset().name();

    // --- LOGGER ---

    private static final Log log = LogFactory.getLog(LDAPListener.class);

    // --- READ BUFFER ---

    private static final ByteBuffer requestBuffer = ByteBuffer.allocateDirect(1024);

    // --- VARIABLES ---

    private final ContactLoader loader;
    private final ServerSocketChannel serverChannel;
    private final Selector selector;
    private final FilterMask[] hosts;
    private final FilterMask[] addresses;

    // --- CONSTRUCTOR ---

    LDAPListener(ContactLoader loader, FilterMask[] hosts, FilterMask[] addresses, int port) throws Exception {

        // Starting server
        log.info("LDAP server starting on port " + port + "...");

        // Store pointers
        this.loader = loader;
        this.hosts = hosts;
        this.addresses = addresses;

        // Allocate an unbound server socket channel
        serverChannel = ServerSocketChannel.open();

        // Get the associated ServerSocket to bind it with
        ServerSocket serverSocket = serverChannel.socket();

        // Set the port the server channel will listen to
        serverSocket.bind(new InetSocketAddress(port));

        // Set non-blocking mode for the listening socket
        serverChannel.configureBlocking(false);

        // Create a new Selector for use below
        selector = Selector.open();

        // Register the ServerSocketChannel with the Selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // Start thread
        start();
    }

    // --- REQUEST LISTENER LOOP ---

    public final void run() {
        log.info("LDAP server started successfully.");

        // Create variables
        SelectionKey key, newKey;
        SocketChannel channel;
        Socket socket = null;
        Iterator keys;
        int n;

        // Server loop
        for (;;) {
            try {

                // Select sockets
                try {
                    socket = null;
                    n = selector.select();
                } catch (NullPointerException closedError) {

                    // Ignore Selector bug - client socket closed
                    if (log.isDebugEnabled()) {
                        log.debug("Socket closed.", closedError);
                    }
                    continue;
                } catch (ClosedSelectorException interrupt) {
                    break;
                } catch (Exception selectError) {

                    // Unknown exception - stop server
                    log.warn("Unable to select sockets!", selectError);
                    break;
                }

                if (n != 0) {

                    // Get an iterator over the set of selected keys
                    keys = selector.selectedKeys().iterator();
                    if (keys == null) {
                        continue;
                    }

                    // Look at each key in the selected set
                    while (keys.hasNext()) {
                        key = (SelectionKey) keys.next();
                        keys.remove();

                        // Nothing to do
                        if (key == null) {
                            continue;
                        }

                        // Check key status
                        if (key.isValid()) {

                            // Accept new incoming connection
                            if (key.isAcceptable()) {
                                channel = serverChannel.accept();
                                if (channel != null) {

                                    // Register new socket connection
                                    socket = channel.socket();
                                    channel.configureBlocking(false);
                                    newKey = channel.register(selector, SelectionKey.OP_READ);
                                    processAccept(newKey);
                                }
                            } else {
                                if (key.isReadable()) {

                                    // Read from socket connection
                                    socket = ((SocketChannel) key.channel()).socket();
                                    processRead(key);
                                } else {

                                    // Write to socket connection
                                    if (key.isWritable()) {
                                        socket = ((SocketChannel) key.channel()).socket();
                                        processWrite(key);
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (InterruptedException interrupt) {
                closeSocket(socket);
                break;
            } catch (IOException socketClosed) {
                closeSocket(socket);
                continue;
            } catch (Exception processingException) {
                closeSocket(socket);
                log.warn(processingException.getMessage(), processingException);
            }
        }
        log.info("LDAP server stopped.");
    }

    private static final void closeSocket(Socket socket) {
        if (socket != null) {
            try {
                if (!socket.isClosed()) {
                    socket.close();
                }
            } catch (Exception ignored) {
            }
        }
    }

    // --- TCP/IP ACCESS CONTROL ---

    private final void processAccept(SelectionKey key) throws Exception {

        // Check TCP/IP access
        if (hosts != null || addresses != null) {
            Socket socket = ((SocketChannel) key.channel()).socket();
            InetAddress inetAddress = socket.getInetAddress();
            if (hosts != null) {
                String host = inetAddress.getHostName();
                if (host == null || host.length() == 0 || host.equals("127.0.0.1")) {
                    host = "localhost";
                } else {
                    host = host.toLowerCase();
                    if (host.equals("localhost.localdomain")) {
                        host = "localhost";
                    }
                }
                if (!isHostMatch(host)) {
                    throw new Exception("Connection refused, forbidden hostname (" + host + ")!");
                }
            }
            if (addresses != null) {
                String address = inetAddress.getHostAddress();
                if (address == null || address.length() == 0) {
                    address = "127.0.0.1";
                }
                if (!isAddressMatch(address)) {
                    throw new Exception("Connection refused, forbidden IP-address (" + address + ")!");
                }
            }
        }
    }

    private final boolean isAddressMatch(String string) {
        for (int i = 0; i < addresses.length; i++) {
            if (addresses[i].match(string)) {
                return true;
            }
        }
        return false;
    }

    private final boolean isHostMatch(String string) {
        for (int i = 0; i < hosts.length; i++) {
            if (hosts[i].match(string)) {
                return true;
            }
        }
        return false;
    }

    // --- LDAP REQUEST PROCESSOR ---

    private final void processRead(SelectionKey key) throws Exception {

        // Read packet from socket channel
        sleep(100);
        byte[] bytes = null;
        Object att = key.attachment();
        if (att != null && att instanceof byte[]) {
            bytes = (byte[]) att;
        }
        SocketChannel channel = (SocketChannel) key.channel();
        requestBuffer.clear();
        int len = channel.read(requestBuffer);
        if (len == -1) {
            throw new IOException();
        }
        if (len != 0) {
            requestBuffer.flip();
            byte[] packet = new byte[len];
            requestBuffer.get(packet, 0, len);
            if (bytes == null || bytes.length == 0) {
                bytes = packet;
                key.attach(bytes);
            } else {
                byte[] swap = new byte[bytes.length + packet.length];
                System.arraycopy(bytes, 0, swap, 0, bytes.length);
                System.arraycopy(packet, 0, swap, bytes.length, packet.length);
                bytes = swap;
                key.attach(bytes);
            }

            // Try to process packet
            LdapMessageContainer container = new LdapMessageContainer();
            try {
                ByteBuffer buffer = ByteBuffer.wrap(bytes);
                LdapDecoder decoder = new LdapDecoder();
                decoder.decode(buffer, container);
            } catch (DecoderException emptyStringException) {
                String msg = emptyStringException.getMessage();
                if (msg != null && (msg.indexOf("empty") != -1 || msg.indexOf("transition") != -1)) {

                    // All contacts requested
                    int id = container.getMessageId();
                    SearchRequest search = new SearchRequest();
                    search.setMessageId(id);
                    LdapMessage ldap = new LdapMessage();
                    ldap.setMessageId(id);
                    ldap.setProtocolOP(search);
                    container.setLdapMessage(ldap);
                } else {
                    throw emptyStringException;
                }
            }

            // Process LDAP request
            ByteBuffer response = processRequest(container.getLdapMessage(), !container.isGrammarEndAllowed());
            key.attach(response);
            key.interestOps(SelectionKey.OP_WRITE);
        }
    }

    private boolean nativeCharsetLocked = false;

    private final ByteBuffer processRequest(LdapMessage request, boolean utf8) throws Exception {
        if (log.isDebugEnabled()) {
            try {
                String command = request.getMessageTypeName();
                if (command != null) {
                    command = command.toLowerCase().replace('_', ' ');
                }
                log.debug("Processing " + command + "...");
            } catch (Exception ignored) {
                log.warn("Processing unknown LDAP request...");
            }
        }
        LinkedList list = new LinkedList();
        switch (request.getMessageType()) {
        case LdapConstants.BIND_REQUEST:

            // Bind response
            BindResponse bind = new BindResponse();
            bind.setMessageId(request.getMessageId());
            LdapResult result = new LdapResult();
            result.setResultCode(0);
            bind.setLdapResult(result);
            list.addLast(bind);
            break;

        case LdapConstants.UNBIND_REQUEST:

            // Unbind response
            LdapResponse unbind = new LdapResponse();
            unbind.setMessageId(request.getMessageId());
            result = new LdapResult();
            result.setResultCode(0);
            unbind.setLdapResult(result);
            list.addLast(unbind);
            break;

        case LdapConstants.SEARCH_REQUEST:

            // Switch back encoding
            if (nativeCharsetLocked) {
                utf8 = false;
            }

            // Get search string
            SearchRequest search = request.getSearchRequest();
            Filter filter = search.getTerminalFilter();
            String key = null;
            if (filter == null) {
                filter = search.getFilter();
                if (filter == null) {
                    filter = search.getCurrentFilter();
                }
            }
            if (filter != null) {
                if (filter instanceof SubstringFilter) {
                    SubstringFilter substringFilter = (SubstringFilter) filter;
                    ArrayList substrings = substringFilter.getAnySubstrings();
                    if (substrings != null && substrings.size() != 0) {
                        key = (String) substrings.get(0);
                    }
                }
                if (key == null) {
                    key = filter.toString();
                    if (key != null) {
                        if (key.charAt(0) == '*') {
                            key = key.substring(1);
                        }
                        if (key.charAt(key.length() - 1) == '*') {
                            key = key.substring(0, key.length() - 1);
                        }
                        if (key.indexOf('=') != -1) {
                            key = key.substring(key.indexOf('=') + 1);
                        }
                    }
                }
                if (key != null) {
                    if (key.length() == 0) {
                        key = null;
                    } else {

                        // Decode UTF8 chars
                        try {
                            byte[] bytes = key.getBytes(PLATFORM_ENCODING);
                            key = StringUtils.decodeToString(bytes, StringUtils.UTF_8);
                            if (utf8) {
                                bytes = key.getBytes(PLATFORM_ENCODING);
                                key = StringUtils.decodeToString(bytes, StringUtils.UTF_8);
                            }
                        } catch (Exception ignored) {
                        }

                        if (log.isDebugEnabled()) {
                            log.debug("LDAP search filter (" + key + ") readed.");
                        }
                        key = key.toLowerCase();

                        // All contacts requested
                        if (key.equals("@")) {
                            key = null;
                        }
                    }
                }
            }

            // Handle native charset lock
            if (key != null && !utf8) {
                nativeCharsetLocked = true;
            }

            // Find entry
            GmailContact[] contacts = loader.getContacts();
            if (contacts != null) {
                GmailContact contact;
                for (int n = 0; n < contacts.length; n++) {
                    contact = contacts[n];
                    if (key != null && contact.name.toLowerCase().indexOf(key) == -1) {
                        continue;
                    }

                    // Add search entry
                    SearchResultEntry entry = new SearchResultEntry();
                    entry.setMessageId(request.getMessageId());
                    LdapDN name;
                    try {
                        name = new LdapDN("CN=" + encode(contact.name, utf8));
                    } catch (Exception badDN) {
                        log.debug(badDN);
                        continue;
                    }
                    entry.setObjectName(name);

                    BasicAttributes partialAttributeList = new BasicAttributes(true);
                    partialAttributeList.put(new BasicAttribute("cn", encode(contact.name, utf8)));
                    if (contact.email.length() != 0) {

                        // first email
                        partialAttributeList.put(new BasicAttribute("mail", encode(contact.email, utf8)));
                    }
                    if (contact.notes.length() != 0) {

                        // notes
                        partialAttributeList.put(new BasicAttribute("comment", encode(contact.notes, utf8)));
                        partialAttributeList.put(new BasicAttribute("description", encode(contact.notes, utf8)));
                    }
                    String mobile = contact.mobile;
                    if (mobile.length() == 0) {
                        mobile = contact.phone;
                    }
                    if (mobile.length() != 0) {

                        // mobile phone
                        partialAttributeList.put(new BasicAttribute("telephonenumber", encode(mobile, utf8)));
                    }
                    if (contact.phone.length() != 0) {

                        // homePhone
                        partialAttributeList.put(new BasicAttribute("homePhone", encode(contact.phone, utf8)));
                    }
                    if (contact.mail.length() != 0) {

                        // second email
                        partialAttributeList
                                .put(new BasicAttribute("mozillaSecondEmail", encode(contact.mail, utf8)));
                        partialAttributeList
                                .put(new BasicAttribute("mailAlternateAddress", encode(contact.mail, utf8)));
                    }
                    if (contact.address.length() != 0) {

                        // postal address
                        partialAttributeList
                                .put(new BasicAttribute("postalAddress", encode(contact.address, utf8)));
                        partialAttributeList
                                .put(new BasicAttribute("homePostalAddress", encode(contact.address, utf8)));
                        partialAttributeList.put(new BasicAttribute("homeStreet", encode(contact.address, utf8)));
                    }
                    if (contact.pager.length() != 0) {

                        // pager
                        partialAttributeList.put(new BasicAttribute("pager", encode(contact.pager, utf8)));
                    }
                    if (contact.fax.length() != 0) {

                        // fax
                        partialAttributeList
                                .put(new BasicAttribute("facsimileTelephoneNumber", encode(contact.fax, utf8)));
                        if (contact.pager.length() == 0) {
                            partialAttributeList.put(new BasicAttribute("pager", encode(contact.fax, utf8)));
                        }
                    }
                    if (contact.title.length() != 0) {

                        // title
                        partialAttributeList.put(new BasicAttribute("title", encode(contact.title, utf8)));
                    }
                    if (contact.company.length() != 0) {

                        // company
                        partialAttributeList.put(new BasicAttribute("company", encode(contact.company, utf8)));
                        partialAttributeList.put(new BasicAttribute("o", encode(contact.company, utf8)));
                    }
                    entry.setPartialAttributeList(partialAttributeList);
                    list.addLast(entry);
                }
            }

            // Search done
            if (log.isDebugEnabled()) {
                log.debug("Found " + list.size() + " contacts.");
            }
            SearchResultDone done = new SearchResultDone();
            done.setMessageId(request.getMessageId());
            result = new LdapResult();
            result.setResultCode(0);
            done.setLdapResult(result);
            list.addLast(done);
            break;

        case LdapConstants.ABANDON_REQUEST:

            // Abandon command
            result = new LdapResult();
            result.setResultCode(0);
            LdapResponse response = new LdapResponse();
            response.setLdapResult(result);
            list.addLast(response);
            break;

        default:

            // Unsupported command
            log.debug("Unsupported LDAP command!");
            result = new LdapResult();
            result.setErrorMessage("Unsupported LDAP command!");
            response = new LdapResponse();
            response.setLdapResult(result);
            list.addLast(response);
        }
        log.debug("LDAP request processed.");
        if (!list.isEmpty()) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Iterator responses = list.iterator();
            while (responses.hasNext()) {
                LdapMessage response = (LdapMessage) responses.next();
                response.setMessageId(request.getMessageId());

                // Append LDAP response
                LdapMessage message = new LdapMessage();
                message.setProtocolOP(response);
                message.setMessageId(request.getMessageId());
                ByteBuffer bb = message.encode(null);
                byte[] a = bb.array();
                out.write(a);
            }
            byte[] bytes = out.toByteArray();
            return ByteBuffer.wrap(bytes);
        }
        return null;
    }

    private static final String encode(String text, boolean utf8) throws Exception {
        if (utf8) {
            return new String(text.getBytes("UTF8"), PLATFORM_ENCODING);
        }
        return text;
    }

    private static final void processWrite(SelectionKey key) throws Exception {
        Object att = key.attachment();
        if (att == null) {
            Thread.sleep(100);
            return;
        }
        if (att instanceof ByteBuffer) {
            ByteBuffer buffer = (ByteBuffer) att;
            if (!buffer.hasRemaining()) {
                key.attach(new byte[0]);
                key.interestOps(SelectionKey.OP_READ);
                return;
            }
            SocketChannel channel = (SocketChannel) key.channel();
            channel.write(buffer);
        }
    }

    // --- STOP SERVICE ---

    public final void interrupt() {

        // Interrupt thread
        super.interrupt();

        // Close resources
        try {
            if (serverChannel.isOpen()) {
                serverChannel.close();
            }
        } catch (Exception ignored) {
        }
        try {
            if (selector.isOpen()) {
                selector.close();
            }
        } catch (Exception ignored) {
        }
    }

}