diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java index abfa7f88..68d0ba0f 100644 --- a/src/java/davmail/exchange/dav/DavExchangeSession.java +++ b/src/java/davmail/exchange/dav/DavExchangeSession.java @@ -186,16 +186,13 @@ public class DavExchangeSession extends ExchangeSession { GALFIND_CRITERIA_MAP.put("uid", "AN"); // assume mail starts with firstname GALFIND_CRITERIA_MAP.put("smtpemail1", "FN"); - GALFIND_CRITERIA_MAP.put("displayname", "DN"); GALFIND_CRITERIA_MAP.put("cn", "DN"); - GALFIND_CRITERIA_MAP.put("givenname", "FN"); + GALFIND_CRITERIA_MAP.put("givenName", "FN"); GALFIND_CRITERIA_MAP.put("sn", "LN"); GALFIND_CRITERIA_MAP.put("title", "TL"); - GALFIND_CRITERIA_MAP.put("company", "CP"); GALFIND_CRITERIA_MAP.put("o", "CP"); GALFIND_CRITERIA_MAP.put("l", "OF"); GALFIND_CRITERIA_MAP.put("department", "DP"); - GALFIND_CRITERIA_MAP.put("apple-group-realname", "DP"); } static final HashSet GALLOOKUP_ATTRIBUTES = new HashSet(); @@ -262,10 +259,10 @@ public class DavExchangeSession extends ExchangeSession { contact.setName(result.get("AN")); contact.put("uid", result.get("AN")); buildGalfindContact(contact, result); + if (needGalLookup(returningAttributes)) { + galLookup(contact); + } if (condition.isMatch(contact)) { - if (needGalLookup(returningAttributes)) { - galLookup(contact); - } contacts.put(contact.getName().toLowerCase(), contact); } } diff --git a/src/java/davmail/ldap/LdapConnection.java b/src/java/davmail/ldap/LdapConnection.java index a3a1d43a..b8b90ac1 100644 --- a/src/java/davmail/ldap/LdapConnection.java +++ b/src/java/davmail/ldap/LdapConnection.java @@ -47,10 +47,16 @@ public class LdapConnection extends AbstractConnection { * Davmail base context */ static final String BASE_CONTEXT = "ou=people"; + /** + * OSX server (OpenDirectory) base context + */ static final String OD_BASE_CONTEXT = "o=od"; static final String OD_USER_CONTEXT = "cn=users, o=od"; static final String COMPUTER_CONTEXT = "cn=computers, o=od"; + /** + * Root DSE naming contexts (default and OpenDirectory) + */ static final List NAMING_CONTEXTS = new ArrayList(); static { @@ -70,32 +76,9 @@ public class LdapConnection extends AbstractConnection { } /** - * Exchange to LDAP attribute map + * Map Exchange contact attribute names to LDAP attributes. + * Used only when returningAttributes is empty in LDAP request (return all available attributes) */ - static final HashMap ATTRIBUTE_MAP = new HashMap(); - - static { - ATTRIBUTE_MAP.put("uid", "AN"); - ATTRIBUTE_MAP.put("mail", "EM"); - ATTRIBUTE_MAP.put("cn", "DN"); - ATTRIBUTE_MAP.put("displayName", "DN"); - ATTRIBUTE_MAP.put("telephoneNumber", "PH"); - ATTRIBUTE_MAP.put("l", "OFFICE"); - ATTRIBUTE_MAP.put("company", "CP"); - ATTRIBUTE_MAP.put("title", "TL"); - - ATTRIBUTE_MAP.put("givenName", "first"); - ATTRIBUTE_MAP.put("initials", "initials"); - ATTRIBUTE_MAP.put("sn", "last"); - ATTRIBUTE_MAP.put("street", "street"); - ATTRIBUTE_MAP.put("st", "state"); - ATTRIBUTE_MAP.put("postalCode", "zip"); - ATTRIBUTE_MAP.put("c", "country"); - ATTRIBUTE_MAP.put("departement", "department"); - ATTRIBUTE_MAP.put("mobile", "mobile"); - } - - static final HashMap CONTACT_TO_LDAP_ATTRIBUTE_MAP = new HashMap(); static { @@ -113,74 +96,22 @@ public class LdapConnection extends AbstractConnection { CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeState", "mozillahomestate"); CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeStreet", "mozillahomestreet"); CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("businesshomepage", "mozillaworkurl"); - CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("description", "description"); CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("nickname", "mozillanickname"); } - static final HashMap STATIC_ATTRIBUTE_MAP = new HashMap(); - + /** + * OSX constant computer guid (used by iCal attendee completion) + */ static final String COMPUTER_GUID = "52486C30-F0AB-48E3-9C37-37E9B28CDD7B"; + /** + * OSX constant virtual host guid (used by iCal attendee completion) + */ static final String VIRTUALHOST_GUID = "D6DD8A10-1098-11DE-8C30-0800200C9A66"; - static final String SERVICEINFO = - "" + - "" + - "" + - "" + - "com.apple.macosxserver.host" + - "" + - "localhost" + // NOTE: Will be replaced by real hostname - "" + - "com.apple.macosxserver.virtualhosts" + - "" + - "" + VIRTUALHOST_GUID + "" + - "" + - "hostDetails" + - "" + - "http" + - "" + - "enabled" + - "" + - "port" + - "9999" + // NOTE: Will be replaced by real port number - "" + - "https" + - "" + - "disabled" + - "" + - "port" + - "0" + - "" + - "" + - "hostname" + - "localhost" + // NOTE: Will be replaced by real hostname - "serviceInfo" + - "" + - "calendar" + - "" + - "enabled" + - "" + - "templates" + - "" + - "calendarUserAddresses" + - "" + - "%(principaluri)s" + - "mailto:%(email)s" + - "urn:uuid:%(guid)s" + - "" + - "principalPath" + - "/principals/__uuids__/%(guid)s/" + - "" + - "" + - "" + - "serviceType" + - "" + - "calendar" + - "" + - "" + - "" + - "" + - ""; + /** + * OSX constant value for attribute apple-serviceslocator + */ + static final HashMap STATIC_ATTRIBUTE_MAP = new HashMap(); static { STATIC_ATTRIBUTE_MAP.put("apple-serviceslocator", COMPUTER_GUID + ':' + VIRTUALHOST_GUID + ":calendar"); @@ -189,6 +120,7 @@ public class LdapConnection extends AbstractConnection { /** * LDAP to Exchange Criteria Map */ + // TODO: remove static final HashMap CRITERIA_MAP = new HashMap(); static { @@ -207,6 +139,9 @@ public class LdapConnection extends AbstractConnection { CRITERIA_MAP.put("apple-group-realname", "DP"); } + /** + * LDAP to Exchange contact attribute map. + */ static final HashMap LDAP_TO_CONTACT_ATTRIBUTE_MAP = new HashMap(); static { @@ -299,6 +234,7 @@ public class LdapConnection extends AbstractConnection { /** * LDAP filter attributes ignore map */ + // TODO remove static final HashSet IGNORE_MAP = new HashSet(); static { @@ -334,10 +270,11 @@ public class LdapConnection extends AbstractConnection { static final int LDAP_SIZE_LIMIT_EXCEEDED = 4; static final int LDAP_INVALID_CREDENTIALS = 49; + // LDAP filter code static final int LDAP_FILTER_AND = 0xa0; static final int LDAP_FILTER_OR = 0xa1; - // LDAP filter operators (only LDAP_FILTER_SUBSTRINGS is supported) + // LDAP filter operators static final int LDAP_FILTER_SUBSTRINGS = 0xa4; //static final int LDAP_FILTER_GE = 0xa5; //static final int LDAP_FILTER_LE = 0xa6; @@ -345,7 +282,7 @@ public class LdapConnection extends AbstractConnection { //static final int LDAP_FILTER_APPROX = 0xa8; static final int LDAP_FILTER_EQUALITY = 0xa3; - // LDAP filter mode (only startsWith supported by galfind) + // LDAP filter mode static final int LDAP_SUBSTRING_INITIAL = 0x80; static final int LDAP_SUBSTRING_ANY = 0x81; static final int LDAP_SUBSTRING_FINAL = 0x82; @@ -361,7 +298,7 @@ public class LdapConnection extends AbstractConnection { //static final int SCOPE_SUBTREE = 2; /** - * For some unknow reaseon parseIntWithTag is private ! + * For some unknown reason parseIntWithTag is private ! */ static final Method PARSE_INT_WITH_TAG_METHOD; @@ -715,7 +652,7 @@ public class LdapConnection extends AbstractConnection { } } - protected String hostName() throws UnknownHostException { + protected String getCurrentHostName() throws UnknownHostException { if (client.getInetAddress().isLoopbackAddress()) { // local address, probably using localhost in iCal URL return "localhost"; @@ -725,6 +662,81 @@ public class LdapConnection extends AbstractConnection { } } + /** + * Cache serviceInfo string value + */ + protected String serviceInfo; + + protected String getServiceInfo() throws UnknownHostException { + if (serviceInfo == null) { + StringBuilder buffer = new StringBuilder(); + buffer.append("" + + "" + + "" + + "" + + "com.apple.macosxserver.host" + + "" + + "localhost" + // NOTE: Will be replaced by real hostname + "" + + "com.apple.macosxserver.virtualhosts" + + "" + + "" + VIRTUALHOST_GUID + "" + + "" + + "hostDetails" + + "" + + "http" + + "" + + "enabled" + + "" + + "port" + + ""); + buffer.append(Settings.getProperty("davmail.caldavPort")); + buffer.append("" + + "" + + "https" + + "" + + "disabled" + + "" + + "port" + + "0" + + "" + + "" + + "hostname" + + ""); + buffer.append(getCurrentHostName()); + buffer.append("" + + "serviceInfo" + + "" + + "calendar" + + "" + + "enabled" + + "" + + "templates" + + "" + + "calendarUserAddresses" + + "" + + "%(principaluri)s" + + "mailto:%(email)s" + + "urn:uuid:%(guid)s" + + "" + + "principalPath" + + "/principals/__uuids__/%(guid)s/" + + "" + + "" + + "" + + "serviceType" + + "" + + "calendar" + + "" + + "" + + "" + + "" + + ""); + serviceInfo = buffer.toString(); + } + return serviceInfo; + } + /** * Send ComputerContext * @@ -733,20 +745,17 @@ public class LdapConnection extends AbstractConnection { * @throws IOException on error */ protected void sendComputerContext(int currentMessageId, Set returningAttributes) throws IOException { - String customServiceInfo = SERVICEINFO.replaceAll("localhost", hostName()); - customServiceInfo = customServiceInfo.replaceAll("9999", Settings.getProperty("davmail.caldavPort")); - List objectClasses = new ArrayList(); objectClasses.add("top"); objectClasses.add("apple-computer"); Map attributes = new HashMap(); addIf(attributes, returningAttributes, "objectClass", objectClasses); addIf(attributes, returningAttributes, "apple-generateduid", COMPUTER_GUID); - addIf(attributes, returningAttributes, "apple-serviceinfo", customServiceInfo); + addIf(attributes, returningAttributes, "apple-serviceinfo", getServiceInfo()); addIf(attributes, returningAttributes, "apple-serviceslocator", "::anyService"); - addIf(attributes, returningAttributes, "cn", hostName()); + addIf(attributes, returningAttributes, "cn", getCurrentHostName()); - String dn = "cn=" + hostName() + ", " + COMPUTER_CONTEXT; + String dn = "cn=" + getCurrentHostName() + ", " + COMPUTER_CONTEXT; DavGatewayTray.debug(new BundleMessage("LOG_LDAP_SEND_COMPUTER_CONTEXT", dn, attributes)); sendEntry(currentMessageId, dn, attributes); @@ -1128,11 +1137,11 @@ public class LdapConnection extends AbstractConnection { return null; } - String galFindAttributeName = getGalFindAttributeName(); + String contactAttributeName = getContactAttributeName(attributeName); - if (galFindAttributeName != null) { + if (contactAttributeName != null) { // quick fix for cn=* filter - Map galPersons = session.galFind(session.startsWith(attributeName, "*".equals(value) ? "A" : value), returningAttributes, sizeLimit); + Map galPersons = session.galFind(session.startsWith(contactAttributeName, "*".equals(value) ? "A" : value), returningAttributes, sizeLimit); if (operator == LDAP_FILTER_EQUALITY) { // Make sure only exact matches are returned @@ -1437,75 +1446,44 @@ public class LdapConnection extends AbstractConnection { Map ldapPerson = new HashMap(); - // convert GAL entries - /*if (person.get("uid") != null) { - // TODO: move to galFind - // add detailed information, only for GAL entries - if (needDetails) { - session.galLookup(person); - } - // Process all attributes that are mapped from exchange - for (Map.Entry entry : ATTRIBUTE_MAP.entrySet()) { - String ldapAttribute = entry.getKey(); - String exchangeAttribute = entry.getValue(); - String value = person.get(exchangeAttribute); - // contactFind return ldap attributes directly - if (value == null) { - value = person.get(ldapAttribute); - } - if (value != null - && (returnAllAttributes || returningAttributes.contains(ldapAttribute.toLowerCase()))) { + // convert Contact entries + if (returnAllAttributes) { + // just convert contact attributes to default ldap names + for (Map.Entry entry : person.entrySet()) { + String ldapAttribute = getLdapAttributeName(entry.getKey()); + String value = entry.getValue(); + if (value != null) { ldapPerson.put(ldapAttribute, value); } } - // TODO - // iCal fix to suit both iCal 3 and 4: move cn to sn, remove cn - if (iCalSearch && ldapPerson.get("cn") != null && returningAttributes.contains("sn")) { - ldapPerson.put("sn", ldapPerson.get("cn")); - ldapPerson.remove("cn"); - } - - } else {*/ - // convert Contact entries - if (returnAllAttributes) { - // just convert contact attributes to default ldap names - for (Map.Entry entry : person.entrySet()) { - String ldapAttribute = getLdapAttributeName(entry.getKey()); - String value = entry.getValue(); - if (value != null) { - ldapPerson.put(ldapAttribute, value); - } - } - } else { - // always map uid - ldapPerson.put("uid", person.get("uid")); - // iterate over requested attributes - for (String ldapAttribute : returningAttributes) { - String contactAttribute = getContactAttributeName(ldapAttribute); - String value = person.get(contactAttribute); - if (value != null) { - if (ldapAttribute.startsWith("birth")) { - SimpleDateFormat parser = ExchangeSession.getZuluDateFormat(); - Calendar calendar = Calendar.getInstance(); - try { - calendar.setTime(parser.parse(value)); - } catch (ParseException e) { - throw new IOException(e); - } - if ("birthday".equals(ldapAttribute)) { - value = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)); - } else if ("birthmonth".equals(ldapAttribute)) { - value = String.valueOf(calendar.get(Calendar.MONTH) + 1); - } else if ("birthyear".equals(ldapAttribute)) { - value = String.valueOf(calendar.get(Calendar.YEAR)); - } + } else { + // always map uid + ldapPerson.put("uid", person.get("uid")); + // iterate over requested attributes + for (String ldapAttribute : returningAttributes) { + String contactAttribute = getContactAttributeName(ldapAttribute); + String value = person.get(contactAttribute); + if (value != null) { + if (ldapAttribute.startsWith("birth")) { + SimpleDateFormat parser = ExchangeSession.getZuluDateFormat(); + Calendar calendar = Calendar.getInstance(); + try { + calendar.setTime(parser.parse(value)); + } catch (ParseException e) { + throw new IOException(e); + } + if ("birthday".equals(ldapAttribute)) { + value = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)); + } else if ("birthmonth".equals(ldapAttribute)) { + value = String.valueOf(calendar.get(Calendar.MONTH) + 1); + } else if ("birthyear".equals(ldapAttribute)) { + value = String.valueOf(calendar.get(Calendar.YEAR)); } - ldapPerson.put(ldapAttribute, value); } + ldapPerson.put(ldapAttribute, value); } } - - //} + } // Process all attributes which have static mappings for (Map.Entry entry : STATIC_ATTRIBUTE_MAP.entrySet()) { @@ -1513,7 +1491,7 @@ public class LdapConnection extends AbstractConnection { String value = entry.getValue(); if (value != null - && (returnAllAttributes || returningAttributes.contains(ldapAttribute.toLowerCase()))) { + && (returnAllAttributes || returningAttributes.contains(ldapAttribute))) { ldapPerson.put(ldapAttribute, value); } } diff --git a/src/test/davmail/exchange/TestExchangeSessionSearchContact.java b/src/test/davmail/exchange/TestExchangeSessionSearchContact.java index b1bdae55..23c1b512 100644 --- a/src/test/davmail/exchange/TestExchangeSessionSearchContact.java +++ b/src/test/davmail/exchange/TestExchangeSessionSearchContact.java @@ -23,6 +23,7 @@ import davmail.Settings; import java.io.IOException; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -80,4 +81,24 @@ public class TestExchangeSessionSearchContact extends AbstractExchangeSessionTes } } + public void testGalFind() throws IOException { + // find a set of contacts + Map contacts = session.galFind(session.startsWith("cn", "a"), null, 100); + for (ExchangeSession.Contact contact : contacts.values()) { + System.out.println(contact); + } + if (!contacts.isEmpty()) { + ExchangeSession.Contact testContact = contacts.values().iterator().next(); + contacts = session.galFind(session.isEqualTo("cn", testContact.get("cn")), null, 100); + assertEquals(1, contacts.size()); + contacts = session.galFind(session.isEqualTo("smtpemail1", testContact.get("smtpemail1")), null, 100); + assertEquals(1, contacts.size()); + contacts = session.galFind(session.startsWith("smtpemail1", testContact.get("smtpemail1")), null, 100); + assertEquals(1, contacts.size()); + contacts = session.galFind(session.and(session.isEqualTo("cn", testContact.get("cn")), + session.startsWith("smtpemail1", testContact.get("smtpemail1"))), null, 100); + assertEquals(1, contacts.size()); + } + } + } diff --git a/src/test/davmail/ldap/TestLdap.java b/src/test/davmail/ldap/TestLdap.java index e17ff807..77e2e1f0 100644 --- a/src/test/davmail/ldap/TestLdap.java +++ b/src/test/davmail/ldap/TestLdap.java @@ -89,7 +89,6 @@ public class TestLdap extends AbstractExchangeSessionTestCase { public void testGalfind() throws NamingException { SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); -// searchControls.setReturningAttributes(new String[]{"uid"}); NamingEnumeration searchResults = ldapContext.search("ou=people", "(uid="+session.getAlias()+ ')', searchControls); assertTrue(searchResults.hasMore()); SearchResult searchResult = searchResults.next(); @@ -114,4 +113,16 @@ public class TestLdap extends AbstractExchangeSessionTestCase { NamingEnumeration searchResults = ldapContext.search("cn=users, o=od", "(&(objectclass=inetOrgPerson)(objectclass=extensibleObject)(objectclass=apple-user)(|(|(uid=fair*)(cn=fair*))(givenname=fair*)(sn=fair*)(cn=fair*)(mail=fair*))(objectclass=posixAccount)(objectclass=shadowAccount))", searchControls); } + + public void testSearchByGivenName() throws NamingException { + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + NamingEnumeration searchResults = ldapContext.search("ou=people", "(givenName=mic*)", searchControls); + } + + public void testSearchByGalfindUnsupportedAttribute() throws NamingException { + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + NamingEnumeration searchResults = ldapContext.search("ou=people", "(postalcode=N18 1ZF)", searchControls); + } }