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