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