Added LDAP id to structure
[public/netxms.git] / src / server / core / ldap.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Raden Solutions
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: ldap.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 #ifndef _WIN32
26
27 #define ldap_first_attributeA ldap_first_attribute
28 #define ldap_next_attributeA ldap_next_attribute
29 #define ldap_memfreeA ldap_memfree
30 #define ldap_get_values_lenA ldap_get_values_len
31
32 #endif
33
34 #define LDAP_DEFAULT 0
35 #define LDAP_USER 1
36 #define LDAP_GROUP 2
37
38 #ifndef LDAP_SSL_PORT
39 #define LDAP_SSL_PORT 636
40 #endif
41
42 #define LDAP_PAGE_OID "1.2.840.113556.1.4.319"
43
44 /**
45 * LDAP entry constructor
46 */
47 Entry::Entry()
48 {
49 m_type = LDAP_DEFAULT;
50 m_loginName = NULL;
51 m_fullName = NULL;
52 m_description = NULL;
53 m_id = NULL;
54 m_memberList = new StringSet();
55 }
56
57 /**
58 * LDAP entry destructor
59 */
60 Entry::~Entry()
61 {
62 free(m_loginName);
63 free(m_fullName);
64 free(m_description);
65 free(m_id);
66 delete m_memberList;
67 }
68
69 #if WITH_LDAP
70
71 #if !defined(_WIN32) && !HAVE_LDAP_CONTROL_CREATE && !HAVE_LDAP_CREATE_CONTROL && HAVE_NSLDAPI_BUILD_CONTROL
72
73 #if !HAVE_DECL_NSLDAPI_BUILD_CONTROL
74 extern "C" int nsldapi_build_control(const char *oid, BerElement *ber, int freeber, char iscritical, LDAPControl **ctrlp);
75 #endif
76
77 static int ldap_create_control(const char *oid, BerElement *ber, int iscritical, LDAPControl **ctrlp)
78 {
79 return nsldapi_build_control(oid, ber, 0, iscritical, ctrlp);
80 }
81
82 #endif
83
84 #if !HAVE_LDAP_CREATE_PAGE_CONTROL && !defined(_WIN32)
85 int ldap_create_page_control(LDAP *ldap, ber_int_t pagesize,
86 struct berval *cookie, char isCritical,
87 LDAPControl **output)
88 {
89 BerElement *ber;
90 int rc;
91
92 if (!ldap || !output)
93 return LDAP_PARAM_ERROR;
94
95 ber = ber_alloc_t(LBER_USE_DER);
96 if (!ber)
97 return LDAP_NO_MEMORY;
98
99 if (ber_printf(ber, "{io}", pagesize,
100 (cookie && cookie->bv_val) ? cookie->bv_val : "",
101 (cookie && cookie->bv_val) ? cookie->bv_len : 0)
102 == -1)
103 {
104 ber_free(ber, 1);
105 return LDAP_ENCODING_ERROR;
106 }
107
108 #if HAVE_LDAP_CONTROL_CREATE
109 rc = ldap_control_create(LDAP_PAGE_OID, isCritical, cookie, 0, output);
110 #else
111 rc = ldap_create_control(LDAP_PAGE_OID, ber, isCritical, output);
112 #endif
113
114 return rc;
115 }
116 #endif /* HAVE_LDAP_CREATE_PAGE_CONTROL */
117
118 #if !HAVE_LDAP_PARSE_PAGE_CONTROL && !defined(_WIN32)
119 int ldap_parse_page_control(LDAP *ldap, LDAPControl **controls,
120 ber_int_t *totalcount, struct berval **cookie)
121 {
122 BerElement *theBer;
123 LDAPControl *listCtrlp;
124
125 for(int i = 0; controls[i] != NULL; i++) {
126 if (strcmp(controls[i]->ldctl_oid, LDAP_PAGE_OID) == 0) {
127 listCtrlp = controls[i];
128
129 theBer = ber_init(&listCtrlp->ldctl_value);
130 if (!theBer)
131 return LDAP_NO_MEMORY;
132
133 if (ber_scanf(theBer, "{iO}", totalcount, cookie) == LBER_ERROR)
134 {
135 ber_free(theBer, 1);
136 return LDAP_DECODING_ERROR;
137 }
138
139 ber_free(theBer, 1);
140 return LDAP_SUCCESS;
141 }
142 }
143
144 return LDAP_CONTROL_NOT_FOUND;
145 }
146 #endif /* HAVE_LDAP_PARSE_PAGE_CONTROL */
147
148 /**
149 * Lists
150 */
151 StringObjectMap<Entry> *userIdEntryList;
152 StringObjectMap<Entry> *userDnEntryList;
153 StringObjectMap<Entry> *groupIdEntryList;
154 StringObjectMap<Entry> *groupDnEntryList;
155
156 /**
157 * Correctly formats ldap connection string (according to OS)
158 */
159 void LDAPConnection::prepareStringForInit(LDAP_CHAR *connectionLine)
160 {
161 LDAP_CHAR *comma;
162 LDAP_CHAR *lastSlash;
163 LDAP_CHAR *nearestSpace;
164
165 comma=ldap_strchr(connectionLine,_TLDAP(','));
166 while(comma != NULL)
167 {
168 *comma = _TLDAP(' ');
169 comma=ldap_strchr(connectionLine,_TLDAP(','));
170 }
171
172 if(ldap_strstr(connectionLine,_TLDAP("ldaps://")))
173 {
174 m_secure = 1;
175 }
176
177 lastSlash=ldap_strchr(connectionLine, _TLDAP('/'));
178 while(lastSlash != NULL)
179 {
180 *lastSlash = 0;
181 lastSlash++;
182
183 nearestSpace=ldap_strchr(connectionLine,_TLDAP(' '));
184 if(nearestSpace == NULL)
185 {
186 nearestSpace = connectionLine;
187 }
188 else
189 {
190 nearestSpace++;
191 }
192 *nearestSpace = 0;
193 ldap_strcat(connectionLine, lastSlash);
194
195 lastSlash=ldap_strchr(connectionLine, _TLDAP('/'));
196 }
197 }
198
199 /**
200 * Init connection with LDAP(init search line, start checking thread, init check interval)
201 */
202 void LDAPConnection::initLDAP()
203 {
204 DbgPrintf(4, _T("LDAPConnection::initLDAP(): Connecting to LDAP server"));
205 #if HAVE_LDAP_INITIALIZE
206 int errorCode = ldap_initialize(&m_ldapConn, m_connList);
207 if (errorCode != LDAP_SUCCESS)
208 #else
209 prepareStringForInit(m_connList);
210 int port = m_secure ? LDAP_SSL_PORT : LDAP_PORT;
211 #ifdef _WIN32
212 if (m_connList[0] == 0)
213 {
214 m_ldapConn = ldap_sslinit(NULL, port, m_secure);
215 }
216 else
217 {
218 DbgPrintf(4, _T("LDAPConnection::initLDAP(): servers=\"%s\" port=%d secure=%s"), m_connList, port, m_secure ? _T("yes") : _T("no"));
219 m_ldapConn = ldap_sslinit(m_connList, port, m_secure);
220 }
221 #else
222 if(m_secure)
223 {
224 #if HAVE_LDAPSSL_INIT
225 ldapssl_init(m_connList, port, m_secure);
226 #else
227 DbgPrintf(4, _T("LDAPConnection::initLDAP(): Your LDAP library does not support secure connection."));
228 #endif //HAVE_LDAPSSL_INIT
229 }
230 else
231 {
232 m_ldapConn = ldap_init(m_connList, LDAP_PORT);
233 }
234 #endif // _WIN32
235
236 #ifdef _WIN32
237 ULONG errorCode = LdapGetLastError();
238 #else
239 int errorCode = errno;
240 #endif // _WIN32
241 if (m_ldapConn == NULL)
242 #endif // HAVE_LDAP_INITIALIZE
243 {
244 TCHAR *error = getErrorString(errorCode);
245 DbgPrintf(4, _T("LDAPConnection::initLDAP(): LDAP session initialization failed (%s)"), error);
246 safe_free(error);
247 return;
248 }
249 //set all LDAP options
250 int version = LDAP_VERSION3;
251 ldap_set_option(m_ldapConn, LDAP_OPT_PROTOCOL_VERSION, &version); //default verion 2, it's recomended to use version 3
252 ldap_set_option(m_ldapConn, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
253 }
254
255 /**
256 * Reads required synchronization parameters
257 */
258 void LDAPConnection::getAllSyncParameters()
259 {
260 TCHAR tmpPwd[MAX_PASSWORD];
261 TCHAR tmpLogin[MAX_CONFIG_VALUE];
262 ConfigReadStr(_T("LdapSyncUserPassword"), tmpPwd, MAX_PASSWORD, _T(""));
263 ConfigReadStr(_T("LdapSyncUser"), tmpLogin, MAX_CONFIG_VALUE, _T(""));
264 DecryptPassword(tmpLogin, tmpPwd, tmpPwd, MAX_PASSWORD);
265 LdapConfigRead(_T("LdapConnectionString"), m_connList, MAX_CONFIG_VALUE, _TLDAP(""));
266 LdapConfigRead(_T("LdapSyncUser"), m_userDN, MAX_CONFIG_VALUE, _TLDAP(""));
267 LdapConfigRead(_T("LdapSearchBase"), m_searchBase, MAX_CONFIG_VALUE, _TLDAP(""));
268 LdapConfigRead(_T("LdapSearchFilter"), m_searchFilter, MAX_CONFIG_VALUE, _TLDAP("(objectClass=*)"));
269 if (m_searchFilter[0] == 0)
270 ldap_strcpy(m_searchFilter, _TLDAP("(objectClass=*)"));
271 #ifdef _WIN32
272 _tcsncpy(m_userPassword, tmpPwd, MAX_PASSWORD);
273 #else
274
275 #ifdef UNICODE
276 char *utf8Password = UTF8StringFromWideString(tmpPwd);
277 strcpy(m_userPassword, utf8Password);
278 safe_free(utf8Password);
279 #else
280 char *utf8Password = UTF8StringFromMBString(tmpPwd);
281 strcpy(m_userPassword, utf8Password);
282 safe_free(utf8Password);
283 #endif // UNICODE
284
285 #endif //win32
286 ConfigReadStrUTF8(_T("LdapMappingName"), m_ldapLoginNameAttr, MAX_CONFIG_VALUE, "");
287 ConfigReadStrUTF8(_T("LdapMappingFullName"), m_ldapFullNameAttr, MAX_CONFIG_VALUE, "");
288 ConfigReadStrUTF8(_T("LdapMappingDescription"), m_ldapDescriptionAttr, MAX_CONFIG_VALUE, "");
289 ConfigReadStrUTF8(_T("LdapUserUniqueId"), m_ldapUsreIdAttr, MAX_CONFIG_VALUE, "");
290 ConfigReadStrUTF8(_T("LdapGroupUniqueId"), m_ldapGroupIdAttr, MAX_CONFIG_VALUE, "");
291 ConfigReadStr(_T("LdapGroupClass"), m_groupClass, MAX_CONFIG_VALUE, _T(""));
292 ConfigReadStr(_T("LdapUserClass"), m_userClass, MAX_CONFIG_VALUE, _T(""));
293 m_action = ConfigReadInt(_T("LdapUserDeleteAction"), 1); //default value - to disable user(value=1)
294 m_pageSize = ConfigReadInt(_T("LdapPageSize"), 1000); //default value - 1000
295 }
296
297
298
299 /**
300 * Get users, according to search line & insetr in netxms DB missing
301 */
302 void LDAPConnection::syncUsers()
303 {
304 getAllSyncParameters();
305 initLDAP();
306 if (loginLDAP() != RCC_SUCCESS)
307 {
308 DbgPrintf(6, _T("LDAPConnection::syncUsers(): Could not login."));
309 return;
310 }
311
312 struct ldap_timeval timeOut = { 10, 0 }; // 10 second connecion/search timeout
313 LDAPMessage *searchResult;
314 userDnEntryList = new StringObjectMap<Entry>(true); //as unique string ID is used dn
315 userIdEntryList = new StringObjectMap<Entry>(false); //as unique string ID is used id
316 groupDnEntryList= new StringObjectMap<Entry>(true); //as unique string ID is used dn
317 groupIdEntryList= new StringObjectMap<Entry>(false); //as unique string ID is used id
318
319 //Parse search base string. As separater is used ';' if this symbol is not escaped
320 LDAP_CHAR *tmp = ldap_strdup(m_searchBase);
321 LDAP_CHAR *separator = tmp;
322 LDAP_CHAR *base = tmp;
323 size_t size = ldap_strlen(tmp);
324 int rc = LDAP_SUCCESS;
325
326 while (separator != NULL)
327 {
328 while (true)
329 {
330 separator = ldap_strchr(separator, ';');
331 if(separator != NULL)
332 {
333 if ((separator - tmp) > 0)
334 {
335 if(separator[-1] != '\\')
336 {
337 separator[0] = 0;
338 }
339 else
340 {
341 separator++;
342 continue;
343 }
344 }
345 else
346 {
347 base++;
348 separator++;
349 continue;
350 }
351 }
352 break;
353 }
354
355 DbgPrintf(6, _T("LDAPConnection::syncUsers(): Search Base DN: ") LDAP_TFMT, base);
356 rc = ldap_search_ext_s(
357 m_ldapConn, // LDAP session handle
358 base, // Search Base
359 LDAP_SCOPE_SUBTREE, // Search Scope – everything below o=Acme
360 m_searchFilter, // Search Filter – only inetOrgPerson objects
361 NULL, // returnAllAttributes – NULL means Yes
362 0, // attributesOnly – False means we want values
363 NULL, // Server controls – There are none
364 NULL, // Client controls – There are none
365 &timeOut, // search Timeout
366 LDAP_NO_LIMIT, // no size limit
367 &searchResult );
368
369 if (rc != LDAP_SUCCESS)
370 {
371 if (rc == LDAP_SIZELIMIT_EXCEEDED)
372 {
373 rc = readInPages(base);
374 }
375 else
376 {
377 TCHAR* error = getErrorString(rc);
378 DbgPrintf(1, _T("LDAPConnection::syncUsers(): LDAP could not get search results. Error code: %s"), error);
379 safe_free(error);
380 }
381 }
382 else
383 {
384 fillLists(searchResult);
385 }
386
387 ldap_msgfree(searchResult);
388 if ((separator != NULL) && ((size_t)(separator - tmp) < size))
389 {
390 separator++;
391 base = separator;
392 }
393 else
394 {
395 separator = NULL;
396 }
397 }
398
399 safe_free(tmp);
400 closeLDAPConnection();
401 if(rc == LDAP_SUCCESS)
402 {
403 //compare new LDAP list with old users
404 compareUserLists();
405 compareGroupList();
406 }
407
408 delete userDnEntryList;
409 delete userIdEntryList;
410 delete groupDnEntryList;
411 delete groupIdEntryList;
412 }
413
414 /**
415 * Reads and process each page
416 */
417 int LDAPConnection::readInPages(LDAP_CHAR *base)
418 {
419 DbgPrintf(7, _T("LDAPConnection::readInPages(): Getting LDAP results as a pages."));
420 LDAPControl *pageControl=NULL, *controls[2] = { NULL, NULL };
421 LDAPControl **returnedControls = NULL;
422 #if defined(__sun)
423 unsigned int pageSize = m_pageSize;
424 unsigned int totalCount = 0;
425 #elif defined(_WIN32)
426 ULONG pageSize = m_pageSize;
427 ULONG totalCount = 0;
428 #else
429 ber_int_t pageSize = m_pageSize;
430 ber_int_t totalCount = 0;
431 #endif
432 char pagingCriticality = 'T';
433 struct berval *cookie = NULL;
434
435 LDAPMessage *searchResult;
436 struct ldap_timeval timeOut = { 10, 0 }; // 10 second connecion/search timeout
437 int rc;
438
439 do {
440 rc = ldap_create_page_control(m_ldapConn, pageSize, cookie, pagingCriticality, &pageControl);
441 if (rc != LDAP_SUCCESS)
442 {
443 TCHAR* error = getErrorString(rc);
444 DbgPrintf(1, _T("LDAPConnection::readInPages(): LDAP could not create page control. Error code: %s"), error);
445 break;
446 }
447
448 /* Insert the control into a list to be passed to the search. */
449 controls[0] = pageControl;
450
451 DbgPrintf(6, _T("LDAPConnection::syncUsers(): Search Base DN: ") LDAP_TFMT, base);
452 /* Search for entries in the directory using the parmeters. */
453 rc = ldap_search_ext_s(
454 m_ldapConn, // LDAP session handle
455 base, // Search Base
456 LDAP_SCOPE_SUBTREE, // Search Scope – everything below o=Acme
457 m_searchFilter, // Search Filter – only inetOrgPerson objects
458 NULL, // returnAllAttributes – NULL means Yes
459 0, // attributesOnly – False means we want values
460 controls, // Server controls – Page controls
461 NULL, // Client controls – There are none
462 &timeOut, // search Timeout
463 LDAP_NO_LIMIT, // no size limit
464 &searchResult );
465
466 if ((rc != LDAP_SUCCESS) && (rc != LDAP_PARTIAL_RESULTS)) {
467 TCHAR* error = getErrorString(rc);
468 DbgPrintf(1, _T("LDAPConnection::readInPages(): Not possible to get result page. Error code: %s"), error);
469 ldap_control_free(pageControl);
470 break;
471 }
472
473 /* Parse the results to retrieve the contols being returned. */
474 rc = ldap_parse_result(m_ldapConn, searchResult, NULL, NULL, NULL, NULL, &returnedControls, FALSE);
475 fillLists(searchResult);
476 ldap_msgfree(searchResult);
477
478 /* Clear cookie */
479 if (cookie != NULL) {
480 ber_bvfree(cookie);
481 cookie = NULL;
482 }
483
484 /*
485 * Parse the page control returned to get the cookie and
486 * determine whether there are more pages.
487 */
488 rc = ldap_parse_page_control(m_ldapConn, returnedControls, &totalCount, &cookie);
489
490 /* Cleanup the controls used. */
491 if (returnedControls)
492 ldap_controls_free(returnedControls);
493
494 ldap_control_free(pageControl);
495
496 } while(cookie && cookie->bv_val && strlen(cookie->bv_val));
497 if (cookie != NULL) {
498 ber_bvfree(cookie);
499 }
500 return rc;
501 }
502
503 TCHAR *LDAPConnection::ldap_internal_get_dn(LDAP *conn, LDAPMessage *entry)
504 {
505 #ifdef _WIN32
506 TCHAR *_dn = ldap_get_dn(m_ldapConn, entry);
507 TCHAR *dn = _tcsdup(_dn);
508 #else
509 char *_dn = ldap_get_dn(m_ldapConn, entry);
510 #ifdef UNICODE
511 WCHAR *dn = WideStringFromUTF8String(_dn);
512 #else
513 char *dn = MBStringFromUTF8String(_dn);
514 #endif
515 #endif
516 ldap_memfree(_dn);
517 return dn;
518 }
519
520 /**
521 * Fills lists of users and groups from search results
522 */
523 void LDAPConnection::fillLists(LDAPMessage *searchResult)
524 {
525 LDAPMessage *entry;
526 char *attribute;
527 BerElement *ber;
528 int i;
529 DbgPrintf(4, _T("LDAPConnection::fillLists(): Found entry count: %d"), ldap_count_entries(m_ldapConn, searchResult));
530 for (entry = ldap_first_entry(m_ldapConn, searchResult); entry != NULL; entry = ldap_next_entry(m_ldapConn, entry))
531 {
532 Entry *newObj = new Entry();
533 TCHAR *dn = ldap_internal_get_dn(m_ldapConn, entry);
534 DbgPrintf(4, _T("LDAPConnection::fillLists(): Found dn: %s"), dn);
535 TCHAR *value;
536 for(i = 0, value = getAttrValue(entry, "objectClass", i); value != NULL; value = getAttrValue(entry, "objectClass", ++i))
537 {
538 if(_tcscmp(value, m_userClass) == 0)
539 {
540 newObj->m_type = LDAP_USER;
541 safe_free(value);
542 break;
543 }
544 if(_tcscmp(value, m_groupClass) == 0)
545 {
546 newObj->m_type = LDAP_GROUP;
547 safe_free(value);
548 break;
549 }
550 safe_free(value);
551 }
552
553 if (newObj->m_type == LDAP_DEFAULT)
554 {
555 DbgPrintf(4, _T("LDAPConnection::fillLists(): %s is not a user nor a group"), dn);
556 free(dn);
557 delete newObj;
558 continue;
559 }
560
561 for(attribute = ldap_first_attributeA(m_ldapConn, entry, &ber); attribute != NULL; attribute = ldap_next_attributeA(m_ldapConn, entry, ber))
562 {
563 // We get values only for those attributes that are used for user/group creation
564 if (!strcmp(attribute, m_ldapFullNameAttr))
565 {
566 newObj->m_fullName = getAttrValue(entry, attribute);
567 }
568 if (!strcmp(attribute, m_ldapLoginNameAttr))
569 {
570 newObj->m_loginName = getAttrValue(entry, attribute);
571 }
572 if (!strcmp(attribute, m_ldapDescriptionAttr))
573 {
574 newObj->m_description = getAttrValue(entry, attribute);
575 }
576 if (m_ldapUsreIdAttr[0] != 0 && !strcmp(attribute, m_ldapUsreIdAttr) && newObj->m_type == LDAP_USER)
577 {
578 //newObj->m_id = getIdAttrValue(entry, attribute);
579 }
580 if (m_ldapGroupIdAttr[0] != 0 && !strcmp(attribute, m_ldapGroupIdAttr) && newObj->m_type == LDAP_GROUP)
581 {
582 newObj->m_id = getIdAttrValue(entry, attribute);
583 }
584 if (!strcmp(attribute, "member"))
585 {
586 i = 0;
587 TCHAR *value = getAttrValue(entry, attribute, i);
588 while(value != NULL)
589 {
590 DbgPrintf(4, _T("LDAPConnection::fillLists(): member: %s"), value);
591 newObj->m_memberList->addPreallocated(value);
592 value = getAttrValue(entry, attribute, ++i);
593 }
594 }
595 if (!strncmp(attribute, "member;range=", 13))
596 {
597 DbgPrintf(4, _T("LDAPConnection::fillLists(): found member attr: %hs"), attribute);
598 DbgPrintf(4, _T("LDAPConnection::fillLists(): there are more members, than can be provided in one request"));
599 //There are more members than can be provided in one request
600 #if !defined(_WIN32) && defined(UNICODE)
601 char *tmpDn = UTF8StringFromWideString(dn);
602 updateMembers(newObj->m_memberList, attribute, entry, tmpDn);
603 free(tmpDn);
604 #else
605 updateMembers(newObj->m_memberList, attribute, entry, dn);
606 #endif
607 }
608 ldap_memfreeA(attribute);
609 }
610 ber_free(ber, 0);
611
612 // entry is added only if it was of a correct type
613 if (newObj->m_type == LDAP_USER && newObj->m_loginName != NULL)
614 {
615 DbgPrintf(4, _T("LDAPConnection::fillLists(): User added: dn: %s, login name: %s, full name: %s, description: %s"), dn, newObj->m_loginName, CHECK_NULL(newObj->m_fullName), CHECK_NULL(newObj->m_description));
616 userDnEntryList->set(dn, newObj);
617 if(m_ldapUsreIdAttr[0] != 0 && newObj->m_id != NULL)
618 userIdEntryList->set(newObj->m_id, newObj);
619 }
620 else if (newObj->m_type == LDAP_GROUP && newObj->m_loginName != NULL)
621 {
622 DbgPrintf(4, _T("LDAPConnection::fillLists(): Group added: dn: %s, login name: %s, full name: %s, description: %s"), dn, newObj->m_loginName, CHECK_NULL(newObj->m_fullName), CHECK_NULL(newObj->m_description));
623 groupDnEntryList->set(dn, newObj);
624 if(m_ldapGroupIdAttr[0] != 0 && newObj->m_id != NULL)
625 groupIdEntryList->set(newObj->m_id, newObj);
626 }
627 else
628 {
629 DbgPrintf(4, _T("LDAPConnection::fillLists(): Unknown object is not added: dn: %s, login name: %s, full name: %s, description: %s"), dn, CHECK_NULL(newObj->m_loginName), CHECK_NULL(newObj->m_fullName), CHECK_NULL(newObj->m_description));
630 delete newObj;
631 }
632 free(dn);
633 }
634 }
635
636 /**
637 * Get attribute's value
638 */
639 TCHAR *LDAPConnection::getAttrValue(LDAPMessage *entry, const char *attr, UINT32 i)
640 {
641 TCHAR *result = NULL;
642 berval **values = ldap_get_values_lenA(m_ldapConn, entry, (char *)attr); // cast needed for Windows LDAP library
643 if (ldap_count_values_len(values) > i)
644 {
645 #ifdef UNICODE
646 result = WideStringFromUTF8String(values[i]->bv_val);
647 #else
648 result = MBStringFromUTF8String(values[i]->bv_val);
649 #endif /* UNICODE */
650 }
651 ldap_value_free_len(values);
652 return result;
653 }
654
655 /**
656 * Get attribute's value
657 */
658 TCHAR *LDAPConnection::getIdAttrValue(LDAPMessage *entry, const char *attr)
659 {
660 BYTE hash[SHA256_DIGEST_SIZE];
661 BYTE tmp[1024];
662 memset(tmp, 0, 1024);
663 berval **values = ldap_get_values_lenA(m_ldapConn, entry, (char *)attr); // cast needed for Windows LDAP library
664 int i,pos;
665 for(i = 0, pos = 0; i < ldap_count_values_len(values); i++)
666 {
667 if(pos+values[i]->bv_len > 1024)
668 break;
669 memcpy(tmp+pos,values[i]->bv_val,values[i]->bv_len);
670 pos += values[i]->bv_len;
671 }
672 ldap_value_free_len(values);
673 if(i == 0)
674 return _tcsdup(_T(""));
675
676 CalculateSHA256Hash(tmp, pos, hash);
677 TCHAR *result = (TCHAR *)malloc(SHA256_DIGEST_SIZE * 2 + 1);
678 BinToStr(hash, SHA256_DIGEST_SIZE, result);
679 return result;
680 }
681
682 /**
683 * Parse range information from attribute
684 */
685 static void ParseRange(const char *attr, int *start, int *end)
686 {
687 *end = -1;
688 *start = -1;
689
690 const char *tmpS = strchr(attr, '=');
691 if (tmpS == NULL)
692 return;
693
694 char *tmpAttr = strdup(tmpS + 1);
695 char *tmpE = strchr(tmpAttr, '-');
696 if (tmpE == NULL)
697 {
698 free(tmpAttr);
699 return;
700 }
701 *tmpE = 0;
702 tmpE++;
703 *start = atoi(tmpAttr);
704 if (tmpE[0] != '*')
705 {
706 *end = atoi(tmpE);
707 }
708 free(tmpAttr);
709 }
710
711 /**
712 * Update group members
713 */
714 void LDAPConnection::updateMembers(StringSet *memberList, const char *firstAttr, LDAPMessage *firstEntry, const LDAP_CHAR *dn)
715 {
716 int start, end;
717
718 // get start, end member count
719 ParseRange(firstAttr, &start, &end);
720
721 // add received members
722 int i = 0;
723 TCHAR *value = getAttrValue(firstEntry, firstAttr, i);
724 while(value != NULL)
725 {
726 DbgPrintf(4, _T("LDAPConnection::updateMembers(): member: %s"), value);
727 memberList->addPreallocated(value);
728 value = getAttrValue(firstEntry, firstAttr, ++i);
729 }
730
731 LDAPMessage *searchResult;
732 LDAPMessage *entry;
733 char *attribute;
734 BerElement *ber;
735 LDAP_CHAR *requiredAttr[2];
736 LDAP_CHAR memberAttr[32];
737 requiredAttr[1] = NULL;
738
739 const LDAP_CHAR *filter = _TLDAP("(objectClass=*)");
740
741 while(true)
742 {
743 // request next members
744 struct ldap_timeval timeOut = { 10, 0 }; // 10 second connecion/search timeout
745 ldap_snprintf(memberAttr, 32, _TLDAP("member;range=%d-*"), end + 1);
746 requiredAttr[0] = memberAttr;
747 DbgPrintf(4, _T("LDAPConnection::updateMembers(): request members id ") LDAP_TFMT _T(" group: ") LDAP_TFMT, dn, memberAttr);
748
749 int rc = ldap_search_ext_s(
750 m_ldapConn, // LDAP session handle
751 (LDAP_CHAR *)dn, // Search Base
752 LDAP_SCOPE_SUBTREE, // Search Scope – everything below o=Acme
753 (LDAP_CHAR *)filter, // Search Filter – only inetOrgPerson objects
754 requiredAttr, // returnAllAttributes – NULL means Yes
755 0, // attributesOnly – False means we want values
756 NULL, // Server controls – There are none
757 NULL, // Client controls – There are none
758 &timeOut, // search Timeout
759 LDAP_NO_LIMIT, // no size limit
760 &searchResult);
761
762 if(rc != LDAP_SUCCESS)
763 {
764 TCHAR* error = getErrorString(rc);
765 DbgPrintf(1, _T("LDAPConnection::syncUsers(): LDAP could not get search results. Error code: %s"), error);
766 safe_free(error);
767 break;
768 }
769
770 bool found = false;
771 DbgPrintf(4, _T("LDAPConnection::fillLists(): Found entry count: %d"), ldap_count_entries(m_ldapConn, searchResult));
772 for(entry = ldap_first_entry(m_ldapConn, searchResult); entry != NULL; entry = ldap_next_entry(m_ldapConn, entry))
773 {
774 for(attribute = ldap_first_attributeA(m_ldapConn, entry, &ber); attribute != NULL; attribute = ldap_next_attributeA(m_ldapConn, entry, ber))
775 {
776 if (!strncmp(attribute, "member;range=", 13))
777 {
778 // add received members
779 i = 0;
780 TCHAR *value = getAttrValue(entry, attribute, i);
781 if(value != NULL)
782 {
783 ParseRange(attribute, &start, &end);
784 found = true;
785 }
786
787 while(value != NULL)
788 {
789 DbgPrintf(4, _T("LDAPConnection::updateMembers(): member: %s"), value);
790 memberList->addPreallocated(value);
791 value = getAttrValue(entry, attribute, ++i);
792 }
793 }
794 ldap_memfreeA(attribute);
795 }
796 ber_free(ber, 0);
797 }
798 ldap_msgfree(searchResult);
799
800 if (end == -1 || !found)
801 break;
802
803 if (start == -1)
804 {
805 DbgPrintf(4, _T("LDAPConnection::updateMembers(): member start interval returned as: %d"), start);
806 break;
807 }
808 }
809 }
810
811 /**
812 * Update user callback
813 */
814 static EnumerationCallbackResult UpdateUserCallback(const TCHAR *key, const void *value, void *data)
815 {
816 UpdateLDAPUser(key, (Entry *)value);
817 return _CONTINUE;
818 }
819
820 /**
821 * Updates user list according to newly recievd user list
822 */
823 void LDAPConnection::compareUserLists()
824 {
825 userDnEntryList->forEach(UpdateUserCallback, NULL);
826 RemoveDeletedLDAPEntries(userDnEntryList, userIdEntryList, m_action, true);
827 }
828
829 /**
830 * Update group callback
831 */
832 static EnumerationCallbackResult UpdateGroupCallback(const TCHAR *key, const void *value, void *data)
833 {
834 UpdateLDAPGroup(key, (Entry *)value);
835 return _CONTINUE;
836 }
837
838 /**
839 * Updates group list according to newly recievd user list
840 */
841 void LDAPConnection::compareGroupList()
842 {
843 groupDnEntryList->forEach(UpdateGroupCallback, NULL);
844 RemoveDeletedLDAPEntries(groupDnEntryList, groupIdEntryList, m_action, false);
845 }
846
847 /**
848 * Coverts given parameters to correct encoding, login to ldap and close connection.
849 * This function should be used to check connection. As name should be given user dn.
850 */
851 UINT32 LDAPConnection::ldapUserLogin(const TCHAR *name, const TCHAR *password)
852 {
853 getAllSyncParameters();
854 initLDAP();
855 UINT32 result;
856 #ifdef UNICODE
857 #ifdef _WIN32
858 nx_strncpy(m_userDN, name, MAX_CONFIG_VALUE);
859 nx_strncpy(m_userPassword, password, MAX_CONFIG_VALUE);
860 #else
861 char *utf8Name = UTF8StringFromWideString(name);
862 strcpy(m_userDN, utf8Name);
863 safe_free(utf8Name);
864 char *utf8Password = UTF8StringFromWideString(password);
865 strcpy(m_userPassword, utf8Password);
866 safe_free(utf8Password);
867 #endif
868 #else
869 strcpy(m_userDN, name);
870 strcpy(m_userPassword, password);
871 #endif // UNICODE
872 result = loginLDAP();
873 closeLDAPConnection();
874 return result;
875 }
876
877 /**
878 * Autentificate LDAP user
879 */
880 UINT32 LDAPConnection::loginLDAP()
881 {
882 int ldap_error;
883
884 // Prevent empty password, bind against ADS will succeed with
885 // empty password by default.
886 if(ldap_strlen(m_userPassword) == 0)
887 {
888 return RCC_ACCESS_DENIED;
889 }
890
891 if (m_ldapConn != NULL)
892 {
893 #ifdef _WIN32
894 ldap_error = ldap_simple_bind_s(m_ldapConn, m_userDN, m_userPassword);
895 #else
896 struct berval cred;
897 cred.bv_val = m_userPassword;
898 cred.bv_len = (int)strlen(m_userPassword);
899
900 ldap_error = ldap_sasl_bind_s(m_ldapConn, m_userDN, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
901 #endif
902 if (ldap_error == LDAP_SUCCESS)
903 {
904 return RCC_SUCCESS;
905 }
906 else
907 {
908 TCHAR *error = getErrorString(ldap_error);
909 DbgPrintf(4, _T("LDAPConnection::loginLDAP(): cannot login to LDAP server (%s)"), error);
910 free(error);
911 }
912 }
913 else
914 {
915 return RCC_NO_LDAP_CONNECTION;
916 }
917 return RCC_ACCESS_DENIED;
918 }
919
920 /**
921 * Converts error string and returns in right format.
922 * Memory should be released by caller.
923 */
924 TCHAR *LDAPConnection::getErrorString(int ldap_error)
925 {
926 #ifdef _WIN32
927 return _tcsdup(ldap_err2string(ldap_error));
928 #else
929 #ifdef UNICODE
930 return WideStringFromUTF8String(ldap_err2string(ldap_error));
931 #else
932 return MBStringFromUTF8String(ldap_err2string(ldap_error));
933 #endif
934 #endif
935 }
936
937 /**
938 * Close LDAP connection
939 */
940 void LDAPConnection::closeLDAPConnection()
941 {
942 DbgPrintf(4, _T("LDAPConnection::closeLDAPConnection(): Disconnect form LDAP server"));
943 if(m_ldapConn != NULL)
944 {
945 #ifdef _WIN32
946 ldap_unbind_s(m_ldapConn);
947 #else
948 ldap_unbind_ext(m_ldapConn, NULL, NULL);
949 #endif
950 m_ldapConn = NULL;
951 }
952 }
953
954 /**
955 * Defeult LDAPConnection class construtor
956 */
957 LDAPConnection::LDAPConnection()
958 {
959 m_ldapConn = NULL;
960 m_action = 1;
961 m_secure = 0;
962 m_pageSize = 1000;
963 }
964
965 /**
966 * Destructor
967 */
968 LDAPConnection::~LDAPConnection()
969 {
970 }
971
972 #else /* WITH_LDAP */
973
974 /**
975 * Synchronize users - stub for server without LDAP support
976 */
977 void LDAPConnection::syncUsers()
978 {
979 DbgPrintf(4, _T("LDAPConnection::syncUsers(): FAILED - server was compiled without LDAP support"));
980 }
981
982 /**
983 * Login via LDAP - stub for server without LDAP support
984 */
985 UINT32 LDAPConnection::ldapUserLogin(const TCHAR *name, const TCHAR *password)
986 {
987 DbgPrintf(4, _T("LDAPConnection::ldapUserLogin(): FAILED - server was compiled without LDAP support"));
988 return RCC_INTERNAL_ERROR;
989 }
990
991 #endif
992
993 /**
994 * Get users, according to search line & insetr in netxms DB missing
995 */
996 THREAD_RESULT THREAD_CALL SyncLDAPUsers(void *arg)
997 {
998 UINT32 syncInterval = ConfigReadInt(_T("LdapSyncInterval"), 0);
999 if (syncInterval == 0)
1000 {
1001 DbgPrintf(1, _T("SyncLDAPUsers: sync thread will not start because LDAP sync is disabled"));
1002 return THREAD_OK;
1003 }
1004
1005 DbgPrintf(1, _T("SyncLDAPUsers: sync thread started, interval %d minutes"), syncInterval);
1006 syncInterval *= 60;
1007 while(!SleepAndCheckForShutdown(syncInterval))
1008 {
1009 LDAPConnection conn;
1010 conn.syncUsers();
1011 }
1012 DbgPrintf(1, _T("SyncLDAPUsers: sync thread stopped"));
1013 return THREAD_OK;
1014 }