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