1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.ldap;
20
21 import davmail.AbstractConnection;
22 import davmail.BundleMessage;
23 import davmail.Settings;
24 import davmail.exception.DavMailException;
25 import davmail.exchange.ExchangeSession;
26 import davmail.exchange.ExchangeSessionFactory;
27 import davmail.exchange.dav.DavExchangeSession;
28 import davmail.ui.tray.DavGatewayTray;
29 import org.apache.log4j.Logger;
30
31 import javax.naming.InvalidNameException;
32 import javax.naming.ldap.Rdn;
33 import javax.security.auth.callback.Callback;
34 import javax.security.auth.callback.CallbackHandler;
35 import javax.security.auth.callback.NameCallback;
36 import javax.security.auth.callback.PasswordCallback;
37 import javax.security.sasl.AuthorizeCallback;
38 import javax.security.sasl.Sasl;
39 import javax.security.sasl.SaslServer;
40 import java.io.BufferedInputStream;
41 import java.io.BufferedOutputStream;
42 import java.io.ByteArrayOutputStream;
43 import java.io.IOException;
44 import java.net.InetAddress;
45 import java.net.Socket;
46 import java.net.SocketException;
47 import java.net.SocketTimeoutException;
48 import java.net.UnknownHostException;
49 import java.nio.charset.StandardCharsets;
50 import java.text.ParseException;
51 import java.text.SimpleDateFormat;
52 import java.util.ArrayList;
53 import java.util.Calendar;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59
60
61
62
63 public class LdapConnection extends AbstractConnection {
64 private static final Logger LOGGER = Logger.getLogger(LdapConnection.class);
65
66
67
68 static final String BASE_CONTEXT = "ou=people";
69
70
71
72 static final String OD_BASE_CONTEXT = "o=od";
73 static final String OD_USER_CONTEXT = "cn=users, o=od";
74 static final String OD_CONFIG_CONTEXT = "cn=config, o=od";
75 static final String COMPUTER_CONTEXT = "cn=computers, o=od";
76 static final String OD_GROUP_CONTEXT = "cn=groups, o=od";
77
78
79 static final String COMPUTER_CONTEXT_LION = "cn=computers,o=od";
80 static final String OD_USER_CONTEXT_LION = "cn=users, ou=people";
81
82
83
84
85 static final List<String> NAMING_CONTEXTS = new ArrayList<>();
86
87 static {
88 NAMING_CONTEXTS.add(BASE_CONTEXT);
89 NAMING_CONTEXTS.add(OD_BASE_CONTEXT);
90 }
91
92 static final List<String> PERSON_OBJECT_CLASSES = new ArrayList<>();
93
94 static {
95 PERSON_OBJECT_CLASSES.add("top");
96 PERSON_OBJECT_CLASSES.add("person");
97 PERSON_OBJECT_CLASSES.add("organizationalPerson");
98 PERSON_OBJECT_CLASSES.add("inetOrgPerson");
99
100 PERSON_OBJECT_CLASSES.add("apple-user");
101 }
102
103
104
105
106
107 static final HashMap<String, String> CONTACT_TO_LDAP_ATTRIBUTE_MAP = new HashMap<>();
108
109 static {
110 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("imapUid", "uid");
111 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("co", "countryname");
112 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute1", "custom1");
113 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute2", "custom2");
114 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute3", "custom3");
115 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("extensionattribute4", "custom4");
116 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("smtpemail1", "mail");
117 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("smtpemail2", "xmozillasecondemail");
118 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeCountry", "mozillahomecountryname");
119 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeCity", "mozillahomelocalityname");
120 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homePostalCode", "mozillahomepostalcode");
121 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeState", "mozillahomestate");
122 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("homeStreet", "mozillahomestreet");
123 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("businesshomepage", "mozillaworkurl");
124 CONTACT_TO_LDAP_ATTRIBUTE_MAP.put("nickname", "mozillanickname");
125 }
126
127
128
129
130 static final String COMPUTER_GUID = "52486C30-F0AB-48E3-9C37-37E9B28CDD7B";
131
132
133
134 static final String VIRTUALHOST_GUID = "D6DD8A10-1098-11DE-8C30-0800200C9A66";
135
136
137
138
139 static final HashMap<String, String> STATIC_ATTRIBUTE_MAP = new HashMap<>();
140
141 static {
142 STATIC_ATTRIBUTE_MAP.put("apple-serviceslocator", COMPUTER_GUID + ':' + VIRTUALHOST_GUID + ":calendar");
143 }
144
145
146
147
148
149 static final HashMap<String, String> CRITERIA_MAP = new HashMap<>();
150
151 static {
152
153 CRITERIA_MAP.put("uid", "AN");
154 CRITERIA_MAP.put("mail", "FN");
155 CRITERIA_MAP.put("displayname", "DN");
156 CRITERIA_MAP.put("cn", "DN");
157 CRITERIA_MAP.put("givenname", "FN");
158 CRITERIA_MAP.put("sn", "LN");
159 CRITERIA_MAP.put("title", "TL");
160 CRITERIA_MAP.put("company", "CP");
161 CRITERIA_MAP.put("o", "CP");
162 CRITERIA_MAP.put("l", "OF");
163 CRITERIA_MAP.put("department", "DP");
164 CRITERIA_MAP.put("apple-group-realname", "DP");
165 }
166
167
168
169
170 static final HashMap<String, String> LDAP_TO_CONTACT_ATTRIBUTE_MAP = new HashMap<>();
171
172 static {
173 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("uid", "imapUid");
174 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mail", "smtpemail1");
175
176 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("displayname", "cn");
177 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("commonname", "cn");
178
179 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("givenname", "givenName");
180
181 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("surname", "sn");
182
183 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("company", "o");
184
185 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-group-realname", "department");
186 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomelocalityname", "homeCity");
187
188 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("c", "co");
189 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("countryname", "co");
190
191 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom1", "extensionattribute1");
192 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom2", "extensionattribute2");
193 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom3", "extensionattribute3");
194 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("custom4", "extensionattribute4");
195
196 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom1", "extensionattribute1");
197 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom2", "extensionattribute2");
198 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom3", "extensionattribute3");
199 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillacustom4", "extensionattribute4");
200
201 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("telephonenumber", "telephoneNumber");
202 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("orgunit", "department");
203 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("departmentnumber", "department");
204 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("ou", "department");
205
206 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillaworkstreet2", null);
207 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestreet", "homeStreet");
208
209 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillanickname", "nickname");
210 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillanickname", "nickname");
211
212 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("cellphone", "mobile");
213 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homeurl", "personalHomePage");
214 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomeurl", "personalHomePage");
215 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-homeurl", "personalHomePage");
216 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomepostalcode", "homePostalCode");
217 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("fax", "facsimiletelephonenumber");
218 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomecountryname", "homeCountry");
219 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("streetaddress", "street");
220 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillaworkurl", "businesshomepage");
221 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("workurl", "businesshomepage");
222
223 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("region", "st");
224
225 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthmonth", "bday");
226 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthday", "bday");
227 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("birthyear", "bday");
228
229 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("carphone", "othermobile");
230 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("nsaimid", "im");
231 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("nscpaimscreenname", "im");
232 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-imhandle", "im");
233 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("imhandle", "im");
234
235 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillasecondemail", "smtpemail2");
236
237 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("notes", "description");
238 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("pagerphone", "pager");
239 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("pager", "pager");
240
241 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("locality", "l");
242 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homephone", "homePhone");
243 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillasecondemail", "smtpemail2");
244
245 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("zip", "postalcode");
246 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestate", "homeState");
247
248 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("modifytimestamp", "lastmodified");
249
250
251 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("objectclass", null);
252 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillausehtmlmail", null);
253 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("xmozillausehtmlmail", null);
254
255 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("mozillahomestreet2", null);
256
257 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("labeleduri", null);
258 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-generateduid", null);
259 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("uidnumber", null);
260 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("gidnumber", null);
261 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("jpegphoto", null);
262 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-emailcontacts", null);
263 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-picture", null);
264
265 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_usercertificate", null);
266 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_realname", null);
267 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_jpegphoto", null);
268 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_guest", null);
269 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_linkedidentity", null);
270 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_defaultlanguage", null);
271 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_hint", null);
272 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers__defaultlanguage", null);
273 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("_writers_picture", null);
274
275 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-user-authenticationhint", null);
276 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("external", null);
277 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("userpassword", null);
278 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("linkedidentity", null);
279 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homedirectory", null);
280 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("authauthority", null);
281
282 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("applefloor", null);
283 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("buildingname", null);
284 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("destinationindicator", null);
285 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("postaladdress", null);
286 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("homepostaladdress", null);
287
288
289 LDAP_TO_CONTACT_ATTRIBUTE_MAP.put("apple-serviceslocator", "apple-serviceslocator");
290 }
291
292
293
294
295
296 static final HashSet<String> IGNORE_MAP = new HashSet<>();
297
298 static {
299 IGNORE_MAP.add("objectclass");
300 IGNORE_MAP.add("apple-generateduid");
301 IGNORE_MAP.add("augmentconfiguration");
302 IGNORE_MAP.add("ou");
303 IGNORE_MAP.add("apple-realname");
304 IGNORE_MAP.add("apple-group-nestedgroup");
305 IGNORE_MAP.add("apple-group-memberguid");
306 IGNORE_MAP.add("macaddress");
307 IGNORE_MAP.add("memberuid");
308 }
309
310
311
312 static final int LDAP_VERSION3 = 0x03;
313
314
315 static final int LDAP_REQ_BIND = 0x60;
316 static final int LDAP_REQ_SEARCH = 0x63;
317 static final int LDAP_REQ_UNBIND = 0x42;
318 static final int LDAP_REQ_ABANDON = 0x50;
319
320
321 static final int LDAP_REP_BIND = 0x61;
322 static final int LDAP_REP_SEARCH = 0x64;
323 static final int LDAP_REP_RESULT = 0x65;
324
325 static final int LDAP_SASL_BIND_IN_PROGRESS = 0x0E;
326
327
328 static final int LDAP_OTHER = 80;
329 static final int LDAP_SUCCESS = 0;
330 static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;
331 static final int LDAP_INVALID_CREDENTIALS = 49;
332
333
334 static final int LDAP_FILTER_AND = 0xa0;
335 static final int LDAP_FILTER_OR = 0xa1;
336 static final int LDAP_FILTER_NOT = 0xa2;
337
338
339 static final int LDAP_FILTER_SUBSTRINGS = 0xa4;
340
341
342 static final int LDAP_FILTER_PRESENT = 0x87;
343
344 static final int LDAP_FILTER_EQUALITY = 0xa3;
345
346
347 static final int LDAP_SUBSTRING_INITIAL = 0x80;
348 static final int LDAP_SUBSTRING_ANY = 0x81;
349 static final int LDAP_SUBSTRING_FINAL = 0x82;
350
351
352 static final int LBER_ENUMERATED = 0x0a;
353 static final int LBER_SET = 0x31;
354 static final int LBER_SEQUENCE = 0x30;
355
356
357 static final int SCOPE_BASE_OBJECT = 0;
358
359
360
361
362
363
364 protected SaslServer saslServer;
365
366
367
368
369 protected BufferedInputStream is;
370
371
372
373
374 protected final BerEncoder responseBer = new BerEncoder();
375
376
377
378
379 int ldapVersion = LDAP_VERSION3;
380
381
382
383
384 protected final HashMap<Integer, SearchRunnable> searchThreadMap = new HashMap<>();
385
386
387
388
389
390
391 public LdapConnection(Socket clientSocket) {
392 super(LdapConnection.class.getSimpleName(), clientSocket);
393 try {
394 is = new BufferedInputStream(client.getInputStream());
395 os = new BufferedOutputStream(client.getOutputStream());
396 } catch (IOException e) {
397 close();
398 DavGatewayTray.error(new BundleMessage("LOG_EXCEPTION_GETTING_SOCKET_STREAMS"), e);
399 }
400 }
401
402 protected boolean isLdapV3() {
403 return ldapVersion == LDAP_VERSION3;
404 }
405
406 @Override
407 public void run() {
408 byte[] inbuf = new byte[2048];
409 int bytesread;
410 int bytesleft;
411 int br;
412 int offset;
413 boolean eos;
414
415 try {
416 ExchangeSessionFactory.checkConfig();
417 while (true) {
418 offset = 0;
419
420
421 bytesread = is.read(inbuf, offset, 1);
422 if (bytesread < 0) {
423 break;
424 }
425
426 if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
427 continue;
428 }
429
430
431 bytesread = is.read(inbuf, offset, 1);
432 if (bytesread < 0) {
433 break;
434 }
435 int seqlen = inbuf[offset++];
436
437
438
439
440 if ((seqlen & 0x80) == 0x80) {
441 int seqlenlen = seqlen & 0x7f;
442
443 bytesread = 0;
444 eos = false;
445
446
447 while (bytesread < seqlenlen) {
448 br = is.read(inbuf, offset + bytesread,
449 seqlenlen - bytesread);
450 if (br < 0) {
451 eos = true;
452 break;
453 }
454 bytesread += br;
455 }
456
457
458 if (eos) {
459 break;
460 }
461
462
463 seqlen = 0;
464 for (int i = 0; i < seqlenlen; i++) {
465 seqlen = (seqlen << 8) + (inbuf[offset + i] & 0xff);
466 }
467 offset += bytesread;
468 }
469
470
471 bytesleft = seqlen;
472 if ((offset + bytesleft) > inbuf.length) {
473 byte[] nbuf = new byte[offset + bytesleft];
474 System.arraycopy(inbuf, 0, nbuf, 0, offset);
475 inbuf = nbuf;
476 }
477 while (bytesleft > 0) {
478 bytesread = is.read(inbuf, offset, bytesleft);
479 if (bytesread < 0) {
480 break;
481 }
482 offset += bytesread;
483 bytesleft -= bytesread;
484 }
485
486 DavGatewayTray.switchIcon();
487
488 handleRequest(inbuf, offset);
489 }
490
491 } catch (SocketException e) {
492 DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
493 } catch (SocketTimeoutException e) {
494 DavGatewayTray.debug(new BundleMessage("LOG_CLOSE_CONNECTION_ON_TIMEOUT"));
495 } catch (Exception e) {
496 DavGatewayTray.log(e);
497 try {
498 sendErr(0, LDAP_REP_BIND, e);
499 } catch (IOException e2) {
500 DavGatewayTray.warn(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
501 }
502 } finally {
503
504 synchronized (searchThreadMap) {
505 for (SearchRunnable searchRunnable : searchThreadMap.values()) {
506 searchRunnable.abandon();
507 }
508 }
509 close();
510 }
511 DavGatewayTray.resetIcon();
512 }
513
514 protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
515
516 protected void handleRequest(byte[] inbuf, int offset) throws IOException {
517
518 BerDecoder reqBer = new BerDecoder(inbuf, 0, offset);
519 int currentMessageId = 0;
520 try {
521 reqBer.parseSeq(null);
522 currentMessageId = reqBer.parseInt();
523 int requestOperation = reqBer.peekByte();
524
525 if (requestOperation == LDAP_REQ_BIND) {
526 reqBer.parseSeq(null);
527 ldapVersion = reqBer.parseInt();
528
529 userName = extractRdnValue(reqBer.parseString(isLdapV3()));
530 if (reqBer.peekByte() == (Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3)) {
531
532 reqBer.parseSeq(null);
533
534 String mechanism = reqBer.parseString(isLdapV3());
535
536 byte[] serverResponse;
537 CallbackHandler callbackHandler = callbacks -> {
538
539 for (Callback callback : callbacks) {
540 if (callback instanceof NameCallback) {
541 userName = extractRdnValue(((NameCallback) callback).getDefaultName());
542
543 password = ExchangeSessionFactory.getUserPassword(userName);
544 }
545 }
546
547 for (Callback callback : callbacks) {
548 if (callback instanceof AuthorizeCallback) {
549 ((AuthorizeCallback) callback).setAuthorized(true);
550 } else if (callback instanceof PasswordCallback) {
551 if (password != null) {
552 ((PasswordCallback) callback).setPassword(password.toCharArray());
553 }
554 }
555 }
556 };
557 int status;
558 if (reqBer.bytesLeft() > 0 && saslServer != null) {
559 byte[] clientResponse = reqBer.parseOctetString(Ber.ASN_OCTET_STR, null);
560 serverResponse = saslServer.evaluateResponse(clientResponse);
561 status = LDAP_SUCCESS;
562
563 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_USER", currentMessageId, userName));
564 try {
565 session = ExchangeSessionFactory.getInstance(userName, password);
566 logConnection("LOGON", userName);
567 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_SUCCESS"));
568 } catch (IOException e) {
569 logConnection("FAILED", userName);
570 serverResponse = EMPTY_BYTE_ARRAY;
571 status = LDAP_INVALID_CREDENTIALS;
572 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS"));
573 }
574
575 } else {
576 Map<String, String> properties = new HashMap<>();
577 properties.put("javax.security.sasl.qop", "auth,auth-int");
578 saslServer = Sasl.createSaslServer(mechanism, "ldap", client.getLocalAddress().getHostAddress(), properties, callbackHandler);
579 if (saslServer == null) {
580 throw new IOException("Unable to create SASL server for mechanism " + mechanism);
581 }
582 serverResponse = saslServer.evaluateResponse(EMPTY_BYTE_ARRAY);
583 status = LDAP_SASL_BIND_IN_PROGRESS;
584 }
585
586 responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
587 responseBer.encodeInt(currentMessageId);
588 responseBer.beginSeq(LDAP_REP_BIND);
589 responseBer.encodeInt(status, LBER_ENUMERATED);
590
591 responseBer.encodeString("", isLdapV3());
592 responseBer.encodeString("", isLdapV3());
593
594 if (serverResponse != null) {
595 responseBer.encodeOctetString(serverResponse, 0x87);
596 }
597 responseBer.endSeq();
598 responseBer.endSeq();
599 sendResponse();
600
601 } else {
602 password = reqBer.parseStringWithTag(Ber.ASN_CONTEXT, isLdapV3(), null);
603
604 if (userName.length() > 0 && password.length() > 0) {
605 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_USER", currentMessageId, userName));
606 try {
607 session = ExchangeSessionFactory.getInstance(userName, password);
608 logConnection("LOGON", userName);
609 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_SUCCESS"));
610 sendClient(currentMessageId, LDAP_REP_BIND, LDAP_SUCCESS, "");
611 } catch (IOException e) {
612 logConnection("FAILED", userName);
613 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_INVALID_CREDENTIALS"));
614 sendClient(currentMessageId, LDAP_REP_BIND, LDAP_INVALID_CREDENTIALS, "");
615 }
616 } else {
617 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_BIND_ANONYMOUS", currentMessageId));
618
619 sendClient(currentMessageId, LDAP_REP_BIND, LDAP_SUCCESS, "");
620 }
621 }
622
623 } else if (requestOperation == LDAP_REQ_UNBIND) {
624 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_UNBIND", currentMessageId));
625 if (session != null) {
626 session = null;
627 }
628 } else if (requestOperation == LDAP_REQ_SEARCH) {
629 reqBer.parseSeq(null);
630 String dn = reqBer.parseString(isLdapV3());
631 int scope = reqBer.parseEnumeration();
632
633 reqBer.parseEnumeration();
634 int sizeLimit = reqBer.parseInt();
635 if (sizeLimit > 100 || sizeLimit == 0) {
636 sizeLimit = 100;
637 }
638 int timelimit = reqBer.parseInt();
639
640 reqBer.parseBoolean();
641 LdapFilter ldapFilter = parseFilter(reqBer);
642 Set<String> returningAttributes = parseReturningAttributes(reqBer);
643 SearchRunnable searchRunnable = new SearchRunnable(currentMessageId, dn, scope, sizeLimit, timelimit, ldapFilter, returningAttributes);
644 if (BASE_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT_LION.equalsIgnoreCase(dn)) {
645
646 synchronized (searchThreadMap) {
647 searchThreadMap.put(currentMessageId, searchRunnable);
648 }
649 Thread searchThread = new Thread(searchRunnable);
650 searchThread.setName(getName() + "-Search-" + currentMessageId);
651 searchThread.start();
652 } else {
653
654 searchRunnable.run();
655 }
656
657 } else if (requestOperation == LDAP_REQ_ABANDON) {
658 int abandonMessageId;
659 abandonMessageId = reqBer.parseIntWithTag(LDAP_REQ_ABANDON);
660 synchronized (searchThreadMap) {
661 SearchRunnable searchRunnable = searchThreadMap.get(abandonMessageId);
662 if (searchRunnable != null) {
663 searchRunnable.abandon();
664 searchThreadMap.remove(currentMessageId);
665 }
666 }
667 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_ABANDON_SEARCH", currentMessageId, abandonMessageId));
668 } else {
669 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_OPERATION", requestOperation));
670 sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_OTHER, "Unsupported operation");
671 }
672 } catch (IOException e) {
673 dumpBer(inbuf, offset);
674 try {
675 sendErr(currentMessageId, LDAP_REP_RESULT, e);
676 } catch (IOException e2) {
677 DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
678 }
679 throw e;
680 }
681 }
682
683
684
685
686
687
688 private String extractRdnValue(String dn) throws IOException {
689 if (dn.startsWith("uid=")) {
690 String rdn = dn;
691 if (rdn.indexOf(',') > 0) {
692 rdn = rdn.substring(0, rdn.indexOf(','));
693 }
694 try {
695 return (String) new Rdn(rdn).getValue();
696 } catch (InvalidNameException e) {
697 throw new IOException(e);
698 }
699 } else {
700 return dn;
701 }
702 }
703
704 protected void dumpBer(byte[] inbuf, int offset) {
705 ByteArrayOutputStream baos = new ByteArrayOutputStream();
706 Ber.dumpBER(baos, "LDAP request buffer\n", inbuf, 0, offset);
707 LOGGER.debug(new String(baos.toByteArray(), StandardCharsets.UTF_8));
708 }
709
710 protected LdapFilter parseFilter(BerDecoder reqBer) throws IOException {
711 LdapFilter ldapFilter;
712 if (reqBer.peekByte() == LDAP_FILTER_PRESENT) {
713 String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase();
714 ldapFilter = new SimpleFilter(attributeName);
715 } else {
716 int[] seqSize = new int[1];
717 int ldapFilterType = reqBer.parseSeq(seqSize);
718 int end = reqBer.getParsePosition() + seqSize[0];
719
720 ldapFilter = parseNestedFilter(reqBer, ldapFilterType, end);
721 }
722
723 return ldapFilter;
724 }
725
726 protected LdapFilter parseNestedFilter(BerDecoder reqBer, int ldapFilterType, int end) throws IOException {
727 LdapFilter nestedFilter;
728
729 if ((ldapFilterType == LDAP_FILTER_OR) || (ldapFilterType == LDAP_FILTER_AND)
730 || ldapFilterType == LDAP_FILTER_NOT) {
731 nestedFilter = new CompoundFilter(ldapFilterType);
732
733 while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
734 if (reqBer.peekByte() == LDAP_FILTER_PRESENT) {
735 String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase();
736 nestedFilter.add(new SimpleFilter(attributeName));
737 } else {
738 int[] seqSize = new int[1];
739 int ldapFilterOperator = reqBer.parseSeq(seqSize);
740 int subEnd = reqBer.getParsePosition() + seqSize[0];
741 nestedFilter.add(parseNestedFilter(reqBer, ldapFilterOperator, subEnd));
742 }
743 }
744 } else {
745
746 nestedFilter = parseSimpleFilter(reqBer, ldapFilterType);
747 }
748
749 return nestedFilter;
750 }
751
752 protected LdapFilter parseSimpleFilter(BerDecoder reqBer, int ldapFilterOperator) throws IOException {
753 String attributeName = reqBer.parseString(isLdapV3()).toLowerCase();
754 int ldapFilterMode = 0;
755
756 StringBuilder value = new StringBuilder();
757 if (ldapFilterOperator == LDAP_FILTER_SUBSTRINGS) {
758
759 int[] seqSize = new int[1];
760
761 reqBer.parseSeq(seqSize);
762 int end = reqBer.getParsePosition() + seqSize[0];
763 while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
764 ldapFilterMode = reqBer.peekByte();
765 if (value.length() > 0) {
766 value.append(' ');
767 }
768 value.append(reqBer.parseStringWithTag(ldapFilterMode, isLdapV3(), null));
769 }
770 } else if (ldapFilterOperator == LDAP_FILTER_EQUALITY) {
771 value.append(reqBer.parseString(isLdapV3()));
772 } else {
773 DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_VALUE"));
774 }
775
776 String sValue = value.toString();
777
778 if ("uid".equalsIgnoreCase(attributeName) && sValue.equals(userName)) {
779
780 if (session instanceof DavExchangeSession) {
781 sValue = session.getAlias();
782 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REPLACED_UID_FILTER", userName, sValue));
783 }
784 }
785
786 return new SimpleFilter(attributeName, sValue, ldapFilterOperator, ldapFilterMode);
787 }
788
789 protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException {
790 Set<String> returningAttributes = new HashSet<>();
791 int[] seqSize = new int[1];
792 reqBer.parseSeq(seqSize);
793 int end = reqBer.getParsePosition() + seqSize[0];
794 while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
795 returningAttributes.add(reqBer.parseString(isLdapV3()).toLowerCase());
796 }
797 return returningAttributes;
798 }
799
800
801
802
803
804
805
806 protected void sendRootDSE(int currentMessageId) throws IOException {
807 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_SEND_ROOT_DSE"));
808
809 Map<String, Object> attributes = new HashMap<>();
810 attributes.put("objectClass", "top");
811 attributes.put("namingContexts", NAMING_CONTEXTS);
812
813
814 sendEntry(currentMessageId, "Root DSE", attributes);
815 }
816
817 protected void addIf(Map<String, Object> attributes, Set<String> returningAttributes, String name, Object value) {
818 if ((returningAttributes.isEmpty()) || returningAttributes.contains(name)) {
819 attributes.put(name, value);
820 }
821 }
822
823 protected String currentHostName;
824
825 protected String getCurrentHostName() throws UnknownHostException {
826 if (currentHostName == null) {
827 InetAddress clientInetAddress = client.getInetAddress();
828 if (clientInetAddress != null && clientInetAddress.isLoopbackAddress()) {
829
830 currentHostName = "localhost";
831 } else {
832
833 currentHostName = InetAddress.getLocalHost().getCanonicalHostName();
834 }
835 }
836 return currentHostName;
837 }
838
839
840
841
842 protected String serviceInfo;
843
844 protected String getServiceInfo() throws UnknownHostException {
845 if (serviceInfo == null) {
846 serviceInfo = ("<?xml version='1.0' encoding='UTF-8'?>" +
847 "<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>" +
848 "<plist version='1.0'>" +
849 "<dict>" +
850 "<key>com.apple.macosxserver.host</key>" +
851 "<array>" +
852 "<string>localhost</string>" +
853 "</array>" +
854 "<key>com.apple.macosxserver.virtualhosts</key>" +
855 "<dict>" +
856 "<key>" + VIRTUALHOST_GUID + "</key>" +
857 "<dict>" +
858 "<key>hostDetails</key>" +
859 "<dict>" +
860 "<key>http</key>" +
861 "<dict>" +
862 "<key>enabled</key>" +
863 "<true/>" +
864 "<key>port</key>" +
865 "<integer>") + Settings.getProperty("davmail.caldavPort") + "</integer>" +
866 "</dict>" +
867 "<key>https</key>" +
868 "<dict>" +
869 "<key>disabled</key>" +
870 "<false/>" +
871 "<key>port</key>" +
872 "<integer>0</integer>" +
873 "</dict>" +
874 "</dict>" +
875 "<key>hostname</key>" +
876 "<string>" + getCurrentHostName() + "</string>" +
877 "<key>serviceInfo</key>" +
878 "<dict>" +
879 "<key>calendar</key>" +
880 "<dict>" +
881 "<key>enabled</key>" +
882 "<true/>" +
883 "<key>templates</key>" +
884 "<dict>" +
885 "<key>calendarUserAddresses</key>" +
886 "<array>" +
887 "<string>%(principaluri)s</string>" +
888 "<string>mailto:%(email)s</string>" +
889 "<string>urn:uuid:%(guid)s</string>" +
890 "</array>" +
891 "<key>principalPath</key>" +
892 "<string>/principals/__uuids__/%(guid)s/</string>" +
893 "</dict>" +
894 "</dict>" +
895 "</dict>" +
896 "<key>serviceType</key>" +
897 "<array>" +
898 "<string>calendar</string>" +
899 "</array>" +
900 "</dict>" +
901 "</dict>" +
902 "</dict>" +
903 "</plist>";
904 }
905 return serviceInfo;
906 }
907
908
909
910
911
912
913
914
915 protected void sendComputerContext(int currentMessageId, Set<String> returningAttributes) throws IOException {
916 List<String> objectClasses = new ArrayList<>();
917 objectClasses.add("top");
918 objectClasses.add("apple-computer");
919 Map<String, Object> attributes = new HashMap<>();
920 addIf(attributes, returningAttributes, "objectClass", objectClasses);
921 addIf(attributes, returningAttributes, "apple-generateduid", COMPUTER_GUID);
922 addIf(attributes, returningAttributes, "apple-serviceinfo", getServiceInfo());
923
924 addIf(attributes, returningAttributes, "apple-xmlplist", getServiceInfo());
925 addIf(attributes, returningAttributes, "apple-serviceslocator", "::anyService");
926 addIf(attributes, returningAttributes, "cn", getCurrentHostName());
927
928 String dn = "cn=" + getCurrentHostName() + ", " + COMPUTER_CONTEXT;
929 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_SEND_COMPUTER_CONTEXT", dn, attributes));
930
931 sendEntry(currentMessageId, dn, attributes);
932 }
933
934
935
936
937
938
939
940 protected void sendBaseContext(int currentMessageId) throws IOException {
941 List<String> objectClasses = new ArrayList<>();
942 objectClasses.add("top");
943 objectClasses.add("organizationalUnit");
944 Map<String, Object> attributes = new HashMap<>();
945 attributes.put("objectClass", objectClasses);
946 attributes.put("description", "DavMail Gateway LDAP for " + Settings.getProperty("davmail.url"));
947 sendEntry(currentMessageId, BASE_CONTEXT, attributes);
948 }
949
950 protected void sendEntry(int currentMessageId, String dn, Map<String, Object> attributes) throws IOException {
951
952 synchronized (responseBer) {
953 responseBer.reset();
954 responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
955 responseBer.encodeInt(currentMessageId);
956 responseBer.beginSeq(LDAP_REP_SEARCH);
957 responseBer.encodeString(dn, isLdapV3());
958 responseBer.beginSeq(LBER_SEQUENCE);
959 for (Map.Entry<String, Object> entry : attributes.entrySet()) {
960 responseBer.beginSeq(LBER_SEQUENCE);
961 responseBer.encodeString(entry.getKey(), isLdapV3());
962 responseBer.beginSeq(LBER_SET);
963 Object values = entry.getValue();
964 if (values instanceof String) {
965 responseBer.encodeString((String) values, isLdapV3());
966 } else if (values instanceof List) {
967 for (Object value : (Iterable) values) {
968 responseBer.encodeString((String) value, isLdapV3());
969 }
970 } else {
971 throw new DavMailException("EXCEPTION_UNSUPPORTED_VALUE", values);
972 }
973 responseBer.endSeq();
974 responseBer.endSeq();
975 }
976 responseBer.endSeq();
977 responseBer.endSeq();
978 responseBer.endSeq();
979 sendResponse();
980 }
981 }
982
983 protected void sendErr(int currentMessageId, int responseOperation, Exception e) throws IOException {
984 String message = e.getMessage();
985 if (message == null) {
986 message = e.toString();
987 }
988 sendClient(currentMessageId, responseOperation, LDAP_OTHER, message);
989 }
990
991 protected void sendClient(int currentMessageId, int responseOperation, int status, String message) throws IOException {
992 responseBer.reset();
993
994 responseBer.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
995 responseBer.encodeInt(currentMessageId);
996 responseBer.beginSeq(responseOperation);
997 responseBer.encodeInt(status, LBER_ENUMERATED);
998
999 responseBer.encodeString("", isLdapV3());
1000
1001 responseBer.encodeString(message, isLdapV3());
1002 responseBer.endSeq();
1003 responseBer.endSeq();
1004 sendResponse();
1005 }
1006
1007 protected void sendResponse() throws IOException {
1008
1009 os.write(responseBer.getBuf(), 0, responseBer.getDataLen());
1010 os.flush();
1011 }
1012
1013 interface LdapFilter {
1014 ExchangeSession.Condition getContactSearchFilter();
1015
1016 Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException;
1017
1018 void add(LdapFilter filter);
1019
1020 boolean isFullSearch();
1021
1022 boolean isMatch(Map<String, String> person);
1023 }
1024
1025 class CompoundFilter implements LdapFilter {
1026 final Set<LdapFilter> criteria = new HashSet<>();
1027 final int type;
1028
1029 CompoundFilter(int filterType) {
1030 type = filterType;
1031 }
1032
1033 @Override
1034 public String toString() {
1035 StringBuilder buffer = new StringBuilder();
1036
1037 if (type == LDAP_FILTER_OR) {
1038 buffer.append("(|");
1039 } else if (type == LDAP_FILTER_AND) {
1040 buffer.append("(&");
1041 } else {
1042 buffer.append("(!");
1043 }
1044
1045 for (LdapFilter child : criteria) {
1046 buffer.append(child.toString());
1047 }
1048
1049 buffer.append(')');
1050
1051 return buffer.toString();
1052 }
1053
1054
1055
1056
1057
1058
1059 public void add(LdapFilter filter) {
1060 criteria.add(filter);
1061 }
1062
1063
1064
1065
1066
1067
1068
1069 public boolean isFullSearch() {
1070 for (LdapFilter child : criteria) {
1071 if (!child.isFullSearch()) {
1072 return false;
1073 }
1074 }
1075
1076 return true;
1077 }
1078
1079
1080
1081
1082
1083
1084
1085 public ExchangeSession.Condition getContactSearchFilter() {
1086 ExchangeSession.MultiCondition condition;
1087
1088 if (type == LDAP_FILTER_OR) {
1089 condition = session.or();
1090 } else {
1091 condition = session.and();
1092 }
1093
1094 for (LdapFilter child : criteria) {
1095 condition.add(child.getContactSearchFilter());
1096 }
1097
1098 return condition;
1099 }
1100
1101
1102
1103
1104
1105
1106
1107 public boolean isMatch(Map<String, String> person) {
1108 if (type == LDAP_FILTER_OR) {
1109 for (LdapFilter child : criteria) {
1110 if (!child.isFullSearch()) {
1111 if (child.isMatch(person)) {
1112
1113 return true;
1114 }
1115 }
1116 }
1117
1118
1119 return false;
1120 } else if (type == LDAP_FILTER_AND) {
1121 for (LdapFilter child : criteria) {
1122 if (!child.isFullSearch()) {
1123 if (!child.isMatch(person)) {
1124
1125 return false;
1126 }
1127 }
1128 }
1129
1130
1131 return true;
1132 }
1133
1134 return false;
1135 }
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145 public Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException {
1146 Map<String, ExchangeSession.Contact> persons = null;
1147
1148 for (LdapFilter child : criteria) {
1149 int currentSizeLimit = sizeLimit;
1150 if (persons != null) {
1151 currentSizeLimit -= persons.size();
1152 }
1153 Map<String, ExchangeSession.Contact> childFind = child.findInGAL(session, returningAttributes, currentSizeLimit);
1154
1155 if (childFind != null) {
1156 if (persons == null) {
1157 persons = childFind;
1158 } else if (type == LDAP_FILTER_OR) {
1159
1160 persons.putAll(childFind);
1161 } else if (type == LDAP_FILTER_AND) {
1162
1163
1164
1165
1166
1167
1168 for (ExchangeSession.Contact result : childFind.values()) {
1169 if (isMatch(result)) {
1170
1171 persons.put(result.get("uid"), result);
1172 }
1173 }
1174 }
1175 }
1176 }
1177
1178 if ((persons == null) && !isFullSearch()) {
1179
1180 return new HashMap<>();
1181 }
1182
1183 return persons;
1184 }
1185 }
1186
1187 class SimpleFilter implements LdapFilter {
1188 static final String STAR = "*";
1189 final String attributeName;
1190 final String value;
1191 final int mode;
1192 final int operator;
1193 final boolean canIgnore;
1194
1195 SimpleFilter(String attributeName) {
1196 this.attributeName = attributeName;
1197 this.value = SimpleFilter.STAR;
1198 this.operator = LDAP_FILTER_SUBSTRINGS;
1199 this.mode = 0;
1200 this.canIgnore = checkIgnore();
1201 }
1202
1203 SimpleFilter(String attributeName, String value, int ldapFilterOperator, int ldapFilterMode) {
1204 this.attributeName = attributeName;
1205 this.value = value;
1206 this.operator = ldapFilterOperator;
1207 this.mode = ldapFilterMode;
1208 this.canIgnore = checkIgnore();
1209 }
1210
1211 private boolean checkIgnore() {
1212 if ("objectclass".equals(attributeName) && STAR.equals(value)) {
1213
1214 return true;
1215 } else if (IGNORE_MAP.contains(attributeName)) {
1216
1217 return true;
1218 } else if (CRITERIA_MAP.get(attributeName) == null && getContactAttributeName(attributeName) == null) {
1219 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE",
1220 attributeName, value));
1221
1222 return true;
1223 }
1224
1225 return false;
1226 }
1227
1228 public boolean isFullSearch() {
1229
1230 return "objectclass".equals(attributeName) && STAR.equals(value);
1231 }
1232
1233 @Override
1234 public String toString() {
1235 StringBuilder buffer = new StringBuilder();
1236 buffer.append('(');
1237 buffer.append(attributeName);
1238 buffer.append('=');
1239 if (SimpleFilter.STAR.equals(value)) {
1240 buffer.append(SimpleFilter.STAR);
1241 } else if (operator == LDAP_FILTER_SUBSTRINGS) {
1242 if (mode == LDAP_SUBSTRING_FINAL || mode == LDAP_SUBSTRING_ANY) {
1243 buffer.append(SimpleFilter.STAR);
1244 }
1245 buffer.append(value);
1246 if (mode == LDAP_SUBSTRING_INITIAL || mode == LDAP_SUBSTRING_ANY) {
1247 buffer.append(SimpleFilter.STAR);
1248 }
1249 } else {
1250 buffer.append(value);
1251 }
1252
1253 buffer.append(')');
1254 return buffer.toString();
1255 }
1256
1257 public ExchangeSession.Condition getContactSearchFilter() {
1258 String contactAttributeName = getContactAttributeName(attributeName);
1259
1260 if (canIgnore || (contactAttributeName == null)) {
1261 return null;
1262 }
1263
1264 ExchangeSession.Condition condition = null;
1265
1266 if (operator == LDAP_FILTER_EQUALITY) {
1267 condition = session.isEqualTo(contactAttributeName, value);
1268 } else if ("*".equals(value)) {
1269 condition = session.not(session.isNull(contactAttributeName));
1270
1271 } else if (!"imapUid".equals(contactAttributeName)) {
1272
1273 if (mode == LDAP_SUBSTRING_FINAL || mode == LDAP_SUBSTRING_ANY) {
1274 condition = session.contains(contactAttributeName, value);
1275 } else {
1276 condition = session.startsWith(contactAttributeName, value);
1277 }
1278 }
1279 return condition;
1280 }
1281
1282 public boolean isMatch(Map<String, String> person) {
1283 if (canIgnore) {
1284
1285 return true;
1286 }
1287
1288 String personAttributeValue = person.get(attributeName);
1289
1290 if (personAttributeValue == null) {
1291
1292 return false;
1293 } else if (value == null) {
1294
1295 return true;
1296 } else if ((operator == LDAP_FILTER_EQUALITY) && personAttributeValue.equalsIgnoreCase(value)) {
1297
1298 return true;
1299 } else
1300 if ((operator == LDAP_FILTER_SUBSTRINGS) && (personAttributeValue.toLowerCase().contains(value.toLowerCase()))) {
1301
1302 return true;
1303 }
1304
1305 return false;
1306 }
1307
1308 public Map<String, ExchangeSession.Contact> findInGAL(ExchangeSession session, Set<String> returningAttributes, int sizeLimit) throws IOException {
1309 if (canIgnore) {
1310 return null;
1311 }
1312
1313 String contactAttributeName = getContactAttributeName(attributeName);
1314
1315 if (contactAttributeName != null) {
1316
1317 Map<String, ExchangeSession.Contact> galPersons = session.galFind(session.startsWith(contactAttributeName, "*".equals(value) ? "A" : value),
1318 convertLdapToContactReturningAttributes(returningAttributes), sizeLimit);
1319
1320 if (operator == LDAP_FILTER_EQUALITY) {
1321
1322
1323 Map<String, ExchangeSession.Contact> results = new HashMap<>();
1324
1325 for (ExchangeSession.Contact person : galPersons.values()) {
1326 if (isMatch(person)) {
1327
1328 results.put(person.get("uid"), person);
1329 }
1330 }
1331
1332 return results;
1333 } else {
1334 return galPersons;
1335 }
1336 }
1337
1338 return null;
1339 }
1340
1341 public void add(LdapFilter filter) {
1342
1343 DavGatewayTray.error(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER", "nested simple filters"));
1344 }
1345 }
1346
1347
1348
1349
1350
1351
1352
1353 protected static String getContactAttributeName(String ldapAttributeName) {
1354 String contactAttributeName = null;
1355
1356 if (ExchangeSession.CONTACT_ATTRIBUTES.contains(ldapAttributeName)) {
1357 contactAttributeName = ldapAttributeName;
1358 } else if (LDAP_TO_CONTACT_ATTRIBUTE_MAP.containsKey(ldapAttributeName)) {
1359 String mappedAttribute = LDAP_TO_CONTACT_ATTRIBUTE_MAP.get(ldapAttributeName);
1360 if (mappedAttribute != null) {
1361 contactAttributeName = mappedAttribute;
1362 }
1363 } else if (!"hassubordinates".equals(ldapAttributeName)){
1364 DavGatewayTray.debug(new BundleMessage("UNKNOWN_ATTRIBUTE", ldapAttributeName));
1365 }
1366 return contactAttributeName;
1367 }
1368
1369
1370
1371
1372
1373
1374
1375 protected static String getLdapAttributeName(String contactAttributeName) {
1376 String mappedAttributeName = CONTACT_TO_LDAP_ATTRIBUTE_MAP.get(contactAttributeName);
1377 if (mappedAttributeName != null) {
1378 return mappedAttributeName;
1379 } else {
1380 return contactAttributeName;
1381 }
1382 }
1383
1384 protected Set<String> convertLdapToContactReturningAttributes(Set<String> returningAttributes) {
1385 Set<String> contactReturningAttributes;
1386 if (returningAttributes != null && !returningAttributes.isEmpty()) {
1387 contactReturningAttributes = new HashSet<>();
1388
1389 contactReturningAttributes.add("imapUid");
1390 for (String attribute : returningAttributes) {
1391 String contactAttributeName = getContactAttributeName(attribute);
1392 if (contactAttributeName != null) {
1393 contactReturningAttributes.add(contactAttributeName);
1394 }
1395 }
1396 } else {
1397 contactReturningAttributes = ExchangeSession.CONTACT_ATTRIBUTES;
1398 }
1399 return contactReturningAttributes;
1400 }
1401
1402 protected class SearchRunnable implements Runnable {
1403 private final int currentMessageId;
1404 private final String dn;
1405 private final int scope;
1406 private final int sizeLimit;
1407 private final int timelimit;
1408 private final LdapFilter ldapFilter;
1409 private final Set<String> returningAttributes;
1410 private boolean abandon;
1411
1412 protected SearchRunnable(int currentMessageId, String dn, int scope, int sizeLimit, int timelimit, LdapFilter ldapFilter, Set<String> returningAttributes) {
1413 this.currentMessageId = currentMessageId;
1414 this.dn = dn;
1415 this.scope = scope;
1416 this.sizeLimit = sizeLimit;
1417 this.timelimit = timelimit;
1418 this.ldapFilter = ldapFilter;
1419 this.returningAttributes = returningAttributes;
1420 }
1421
1422
1423
1424
1425 protected void abandon() {
1426 abandon = true;
1427 }
1428
1429 public void run() {
1430 try {
1431 int size = 0;
1432 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH", currentMessageId, dn, scope, sizeLimit, timelimit, ldapFilter.toString(), returningAttributes));
1433
1434 if (scope == SCOPE_BASE_OBJECT) {
1435 if (dn != null && dn.length() == 0) {
1436 size = 1;
1437 sendRootDSE(currentMessageId);
1438 } else if (BASE_CONTEXT.equals(dn)) {
1439 size = 1;
1440
1441 sendBaseContext(currentMessageId);
1442 } else if (dn != null && dn.startsWith("uid=") && dn.indexOf(',') > 0) {
1443 if (session != null) {
1444
1445 String uid = dn.substring("uid=".length(), dn.indexOf(','));
1446 Map<String, ExchangeSession.Contact> persons = null;
1447
1448
1449 try {
1450
1451 Integer.parseInt(uid);
1452 persons = contactFind(session.isEqualTo("imapUid", uid), returningAttributes, sizeLimit);
1453 } catch (NumberFormatException e) {
1454
1455 }
1456
1457
1458 if (persons == null || persons.isEmpty()) {
1459 persons = session.galFind(session.isEqualTo("imapUid", uid),
1460 convertLdapToContactReturningAttributes(returningAttributes), sizeLimit);
1461
1462 ExchangeSession.Contact person = persons.get(uid.toLowerCase());
1463
1464 if (persons.size() > 1 || person == null) {
1465 persons = new HashMap<>();
1466 if (person != null) {
1467 persons.put(uid.toLowerCase(), person);
1468 }
1469 }
1470 }
1471 size = persons.size();
1472 sendPersons(currentMessageId, dn.substring(dn.indexOf(',')), persons, returningAttributes);
1473 } else {
1474 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN", currentMessageId, dn));
1475 }
1476 } else {
1477 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_INVALID_DN", currentMessageId, dn));
1478 }
1479 } else if (COMPUTER_CONTEXT.equals(dn) || COMPUTER_CONTEXT_LION.equals(dn)) {
1480 size = 1;
1481
1482 sendComputerContext(currentMessageId, returningAttributes);
1483 } else if ((BASE_CONTEXT.equalsIgnoreCase(dn) || OD_USER_CONTEXT.equalsIgnoreCase(dn)) || OD_USER_CONTEXT_LION.equalsIgnoreCase(dn)) {
1484 if (session != null) {
1485 Map<String, ExchangeSession.Contact> persons = new HashMap<>();
1486 if (ldapFilter.isFullSearch()) {
1487
1488 for (ExchangeSession.Contact person : contactFind(null, returningAttributes, sizeLimit).values()) {
1489 persons.put(person.get("imapUid"), person);
1490 if (persons.size() == sizeLimit) {
1491 break;
1492 }
1493 }
1494
1495 for (char c = 'A'; c <= 'Z'; c++) {
1496 if (!abandon && persons.size() < sizeLimit) {
1497 for (ExchangeSession.Contact person : session.galFind(session.startsWith("cn", String.valueOf(c)),
1498 convertLdapToContactReturningAttributes(returningAttributes), sizeLimit).values()) {
1499 persons.put(person.get("uid"), person);
1500 if (persons.size() == sizeLimit) {
1501 break;
1502 }
1503 }
1504 }
1505 if (persons.size() == sizeLimit) {
1506 break;
1507 }
1508 }
1509 } else {
1510
1511 ExchangeSession.Condition filter = ldapFilter.getContactSearchFilter();
1512
1513
1514
1515 if (ldapFilter.isFullSearch() || filter != null) {
1516 for (ExchangeSession.Contact person : contactFind(filter, returningAttributes, sizeLimit).values()) {
1517 persons.put(person.get("imapUid"), person);
1518
1519 if (persons.size() == sizeLimit) {
1520 break;
1521 }
1522 }
1523 if (!abandon && persons.size() < sizeLimit) {
1524 for (ExchangeSession.Contact person : ldapFilter.findInGAL(session, returningAttributes, sizeLimit - persons.size()).values()) {
1525 if (persons.size() == sizeLimit) {
1526 break;
1527 }
1528
1529 persons.put(person.get("uid"), person);
1530 }
1531 }
1532 }
1533 }
1534
1535 size = persons.size();
1536 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_FOUND_RESULTS", currentMessageId, size));
1537 sendPersons(currentMessageId, ", " + dn, persons, returningAttributes);
1538 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_END", currentMessageId));
1539 } else {
1540 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_ANONYMOUS_ACCESS_FORBIDDEN", currentMessageId, dn));
1541 }
1542 } else if (dn != null && dn.length() > 0 && !OD_CONFIG_CONTEXT.equals(dn) && !OD_GROUP_CONTEXT.equals(dn)) {
1543 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_INVALID_DN", currentMessageId, dn));
1544 }
1545
1546
1547 if (size > 1 && size == sizeLimit) {
1548 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SIZE_LIMIT_EXCEEDED", currentMessageId));
1549 sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_SIZE_LIMIT_EXCEEDED, "");
1550 } else {
1551 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SUCCESS", currentMessageId));
1552 sendClient(currentMessageId, LDAP_REP_RESULT, LDAP_SUCCESS, "");
1553 }
1554 } catch (SocketException e) {
1555
1556 LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
1557 } catch (IOException e) {
1558 DavGatewayTray.log(e);
1559 try {
1560 sendErr(currentMessageId, LDAP_REP_RESULT, e);
1561 } catch (IOException e2) {
1562 DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
1563 }
1564 } finally {
1565 synchronized (searchThreadMap) {
1566 searchThreadMap.remove(currentMessageId);
1567 }
1568 }
1569
1570 DavGatewayTray.resetIcon();
1571 }
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582 public Map<String, ExchangeSession.Contact> contactFind(ExchangeSession.Condition condition, Set<String> returningAttributes, int maxCount) throws IOException {
1583 Map<String, ExchangeSession.Contact> results = new HashMap<>();
1584
1585 Set<String> contactReturningAttributes = convertLdapToContactReturningAttributes(returningAttributes);
1586 contactReturningAttributes.remove("apple-serviceslocator");
1587 List<ExchangeSession.Contact> contacts = session.searchContacts(ExchangeSession.CONTACTS, contactReturningAttributes, condition, maxCount);
1588
1589 for (ExchangeSession.Contact contact : contacts) {
1590
1591 String imapUid = contact.get("imapUid");
1592 if (imapUid != null) {
1593 results.put(imapUid, contact);
1594 }
1595 }
1596
1597 return results;
1598 }
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610 protected void sendPersons(int currentMessageId, String baseContext, Map<String, ExchangeSession.Contact> persons, Set<String> returningAttributes) throws IOException {
1611 boolean needObjectClasses = returningAttributes.contains("objectclass") || returningAttributes.isEmpty();
1612 boolean returnAllAttributes = returningAttributes.isEmpty();
1613
1614 for (ExchangeSession.Contact person : persons.values()) {
1615 if (abandon) {
1616 break;
1617 }
1618
1619 Map<String, Object> ldapPerson = new HashMap<>();
1620
1621
1622 if (returnAllAttributes) {
1623
1624 for (Map.Entry<String, String> entry : person.entrySet()) {
1625 String ldapAttribute = getLdapAttributeName(entry.getKey());
1626 String value = entry.getValue();
1627 if (value != null) {
1628 ldapPerson.put(ldapAttribute, value);
1629 }
1630 }
1631 } else {
1632
1633 ldapPerson.put("uid", person.get("imapUid"));
1634
1635 for (String ldapAttribute : returningAttributes) {
1636 String contactAttribute = getContactAttributeName(ldapAttribute);
1637 String value = person.get(contactAttribute);
1638 if (value != null) {
1639 if (ldapAttribute.startsWith("birth")) {
1640 SimpleDateFormat parser = ExchangeSession.getZuluDateFormat();
1641 Calendar calendar = Calendar.getInstance();
1642 try {
1643 calendar.setTime(parser.parse(value));
1644 } catch (ParseException e) {
1645 throw new IOException(e + " " + e.getMessage());
1646 }
1647 switch (ldapAttribute) {
1648 case "birthday":
1649 value = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));
1650 break;
1651 case "birthmonth":
1652 value = String.valueOf(calendar.get(Calendar.MONTH) + 1);
1653 break;
1654 case "birthyear":
1655 value = String.valueOf(calendar.get(Calendar.YEAR));
1656 break;
1657 }
1658 }
1659 ldapPerson.put(ldapAttribute, value);
1660 } else if ("hassubordinates".equals(ldapAttribute)) {
1661 ldapPerson.put(ldapAttribute, "false");
1662 }
1663 }
1664 }
1665
1666
1667 for (Map.Entry<String, String> entry : STATIC_ATTRIBUTE_MAP.entrySet()) {
1668 String ldapAttribute = entry.getKey();
1669 String value = entry.getValue();
1670
1671 if (value != null
1672 && (returnAllAttributes || returningAttributes.contains(ldapAttribute))) {
1673 ldapPerson.put(ldapAttribute, value);
1674 }
1675 }
1676
1677 if (needObjectClasses) {
1678 ldapPerson.put("objectClass", PERSON_OBJECT_CLASSES);
1679 }
1680
1681
1682 if (returnAllAttributes || returningAttributes.contains("apple-generateduid")) {
1683 String mail = (String) ldapPerson.get("mail");
1684 if (mail != null) {
1685 ldapPerson.put("apple-generateduid", mail.replaceAll("@", "__AT__"));
1686 } else {
1687
1688 ldapPerson.put("apple-generateduid", ldapPerson.get("uid"));
1689 }
1690 }
1691
1692
1693 if (session.getAlias().equals(ldapPerson.get("uid"))) {
1694 if (returningAttributes.contains("uidnumber")) {
1695 ldapPerson.put("uidnumber", userName);
1696 }
1697 }
1698 DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH_SEND_PERSON", currentMessageId, ldapPerson.get("uid"), baseContext, ldapPerson));
1699
1700 try {
1701 sendEntry(currentMessageId, new Rdn("uid", ldapPerson.get("uid")) + baseContext, ldapPerson);
1702 } catch (InvalidNameException e) {
1703 throw new IOException(e);
1704 }
1705 }
1706
1707 }
1708
1709 }
1710 }