Added a parameter to semaphore constructor to avoid ambiguity
[pwlib.git] / src / ptclib / pldap.cxx
blob733c614912ccb0778c9e3275a2c88fd1f1974c49
1 /*
2 * pldap.cxx
4 * Lightweight Directory Access Protocol interface class.
6 * Portable Windows Library
8 * Copyright (c) 1993-2003 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Contributor(s): ______________________________________.
26 * $Log$
27 * Revision 1.14 2004/02/04 09:37:00 rjongbloed
28 * Fixed memory leak and race condition, thanks Rossano Ravelli
30 * Revision 1.13 2004/01/17 17:45:29 csoutheren
31 * Changed to use PString::MakeEmpty
33 * Revision 1.12 2003/07/15 12:12:11 csoutheren
34 * Added support for multiple values in a single attribute string
35 * Thanks to Ravelli Rossano
37 * Revision 1.11 2003/07/12 00:10:40 csoutheren
38 * Fixed problem where Modify routines were calling Add, thanks to Ravelli Rossano
40 * Revision 1.10 2003/06/06 09:14:01 dsandras
42 * Test that a search result has been returned before calling ldapresult2error.
44 * Revision 1.9 2003/06/05 23:17:52 rjongbloed
45 * Changed default operation timeout to 30 seconds.
47 * Revision 1.8 2003/06/05 05:29:30 rjongbloed
48 * Fixed LDAP bind authentication methods, thanks Ravelli Rossano
50 * Revision 1.7 2003/04/17 08:34:48 robertj
51 * Changed LDAP structure output so if field is empty it leaves it out
52 * altogether rather then encoding an empty string, some servers barf.
54 * Revision 1.6 2003/04/16 08:00:19 robertj
55 * Windoes psuedo autoconf support
57 * Revision 1.5 2003/04/07 11:59:52 robertj
58 * Fixed search function returning an error if can't find anything for filter.
60 * Revision 1.4 2003/04/01 07:05:16 robertj
61 * Added ability to specify host:port in opening an LDAP server
63 * Revision 1.3 2003/03/31 03:32:53 robertj
64 * Major addition of functionality.
68 #ifdef __GNUC__
69 #pragma implementation "pldap.h"
70 #endif
72 #include <ptlib.h>
74 #include <ptlib/sockets.h>
75 #include <ptclib/pldap.h>
77 #if P_LDAP
79 #include <ldap.h>
82 #if defined(_WIN32)
83 #pragma comment(lib, P_LDAP_LIBRARY)
84 #endif
87 ///////////////////////////////////////////////////////////////////////////////
89 PLDAPSession::PLDAPSession(const PString & baseDN)
90 : ldapContext(NULL),
91 errorNumber(LDAP_SUCCESS),
92 protocolVersion(LDAP_VERSION3),
93 defaultBaseDN(baseDN),
94 searchLimit(UINT_MAX),
95 timeout(0, 30),
96 multipleValueSeparator('\n')
101 PLDAPSession::~PLDAPSession()
103 Close();
107 BOOL PLDAPSession::Open(const PString & server, WORD port)
109 Close();
111 PString host = server;
112 PINDEX colon = server.Find(':');
113 if (colon != P_MAX_INDEX) {
114 host = server.Left(colon);
115 port = PIPSocket::GetPortByService(server.Mid(colon+1), "tcp");
118 ldapContext = ldap_init(server, port);
119 if (!IsOpen())
120 return FALSE;
122 SetOption(LDAP_OPT_PROTOCOL_VERSION, protocolVersion);
123 return TRUE;
127 BOOL PLDAPSession::Close()
129 if (!IsOpen())
130 return FALSE;
132 ldap_unbind(ldapContext);
133 ldapContext = NULL;
134 return TRUE;
138 BOOL PLDAPSession::SetOption(int optcode, int value)
140 if (!IsOpen())
141 return FALSE;
143 return ldap_set_option(ldapContext, optcode, &value);
147 BOOL PLDAPSession::SetOption(int optcode, void * value)
149 if (!IsOpen())
150 return FALSE;
152 return ldap_set_option(ldapContext, optcode, value);
156 BOOL PLDAPSession::Bind(const PString & who,
157 const PString & passwd,
158 AuthenticationMethod authMethod)
160 if (!IsOpen())
161 return FALSE;
163 const char * whoPtr;
164 if (who.IsEmpty())
165 whoPtr = NULL;
166 else
167 whoPtr = who;
169 static const int AuthMethodCode[NumAuthenticationMethod] = {
170 LDAP_AUTH_SIMPLE, LDAP_AUTH_SASL, LDAP_AUTH_KRBV4
172 errorNumber = ldap_bind_s(ldapContext, whoPtr, passwd, AuthMethodCode[authMethod]);
173 return errorNumber == LDAP_SUCCESS;
177 PLDAPSession::ModAttrib::ModAttrib(const PString & n, Operation o)
178 : name(n),
179 op(o)
184 void PLDAPSession::ModAttrib::SetLDAPMod(struct ldapmod & mod, Operation defaultOp)
186 mod.mod_type = (char *)(const char *)name;
188 Operation realOp = op == NumOperations ? defaultOp : op;
189 static const int OpCode[NumOperations] = {
190 LDAP_MOD_ADD, LDAP_MOD_REPLACE, LDAP_MOD_DELETE
192 mod.mod_op = OpCode[realOp];
194 if (IsBinary())
195 mod.mod_op |= LDAP_MOD_BVALUES;
197 SetLDAPModVars(mod);
201 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
202 Operation op)
203 : ModAttrib(name, op)
208 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
209 const PString & value,
210 Operation op)
211 : ModAttrib(name, op)
213 AddValue(value);
217 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
218 const PStringList & vals,
219 Operation op)
220 : ModAttrib(name, op),
221 values(vals)
226 void PLDAPSession::StringModAttrib::SetValue(const PString & value)
228 values.RemoveAll();
229 values.AppendString(value);
233 void PLDAPSession::StringModAttrib::AddValue(const PString & value)
235 values.AppendString(value);
239 BOOL PLDAPSession::StringModAttrib::IsBinary() const
241 return FALSE;
245 void PLDAPSession::StringModAttrib::SetLDAPModVars(struct ldapmod & mod)
247 pointers.SetSize(values.GetSize()+1);
248 PINDEX i;
249 for (i = 0; i < values.GetSize(); i++)
250 pointers[i] = values[i].GetPointer();
251 pointers[i] = NULL;
252 mod.mod_values = pointers.GetPointer();
256 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
257 Operation op)
258 : ModAttrib(name, op)
263 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
264 const PBYTEArray & value,
265 Operation op)
266 : ModAttrib(name, op)
268 AddValue(value);
272 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
273 const PList<PBYTEArray> & vals,
274 Operation op)
275 : ModAttrib(name, op),
276 values(vals)
281 void PLDAPSession::BinaryModAttrib::SetValue(const PBYTEArray & value)
283 values.RemoveAll();
284 values.Append(new PBYTEArray(value));
288 void PLDAPSession::BinaryModAttrib::AddValue(const PBYTEArray & value)
290 values.Append(new PBYTEArray(value));
294 BOOL PLDAPSession::BinaryModAttrib::IsBinary() const
296 return TRUE;
300 void PLDAPSession::BinaryModAttrib::SetLDAPModVars(struct ldapmod & mod)
302 pointers.SetSize(values.GetSize()+1);
303 bervals.SetSize(values.GetSize()*sizeof(berval));
304 berval * ber = (berval *)bervals.GetPointer();
305 PINDEX i;
306 for (i = 0; i < values.GetSize(); i++) {
307 ber[i].bv_val = (char *)values[i].GetPointer();
308 ber[i].bv_len = values[i].GetSize();
309 pointers[i] = &ber[i];
311 pointers[i] = NULL;
312 mod.mod_bvalues = pointers.GetPointer();
316 static LDAPMod ** CreateLDAPModArray(const PList<PLDAPSession::ModAttrib> & attributes,
317 PLDAPSession::ModAttrib::Operation defaultOp,
318 PBYTEArray & storage)
320 PINDEX count = attributes.GetSize();
321 storage.SetSize(count*sizeof(LDAPMod) + (count+1)*sizeof(LDAPMod *));
323 LDAPMod ** attrs = (LDAPMod **)storage.GetPointer();
324 LDAPMod * attr = (LDAPMod * )&attrs[count+1];
325 for (PINDEX i = 0; i < count; i++) {
326 attrs[i] = &attr[i];
327 attributes[i].SetLDAPMod(attr[i], defaultOp);
330 return attrs;
334 static PList<PLDAPSession::ModAttrib> AttribsFromDict(const PStringToString & attributes)
336 PList<PLDAPSession::ModAttrib> attrs;
338 for (PINDEX i = 0; i < attributes.GetSize(); i++)
339 attrs.Append(new PLDAPSession::StringModAttrib(attributes.GetKeyAt(i),
340 attributes.GetDataAt(i).Lines()));
342 return attrs;
346 static PList<PLDAPSession::ModAttrib> AttribsFromArray(const PStringArray & attributes)
348 PList<PLDAPSession::ModAttrib> attrs;
350 for (PINDEX i = 0; i < attributes.GetSize(); i++) {
351 PString attr = attributes[i];
352 PINDEX equal = attr.Find('=');
353 if (equal != P_MAX_INDEX)
354 attrs.Append(new PLDAPSession::StringModAttrib(attr.Left(equal),
355 attr.Mid(equal+1).Lines()));
358 return attrs;
362 static PList<PLDAPSession::ModAttrib> AttribsFromStruct(const PLDAPStructBase & attributes)
364 PList<PLDAPSession::ModAttrib> attrs;
366 for (PINDEX i = 0; i < attributes.GetNumAttributes(); i++) {
367 PLDAPAttributeBase & attr = attributes.GetAttribute(i);
368 if (attr.IsBinary())
369 attrs.Append(new PLDAPSession::BinaryModAttrib(attr.GetName(), attr.ToBinary()));
370 else {
371 PString str = attr.ToString();
372 if (!str)
373 attrs.Append(new PLDAPSession::StringModAttrib(attr.GetName(), str));
377 return attrs;
381 BOOL PLDAPSession::Add(const PString & dn, const PList<ModAttrib> & attributes)
383 if (!IsOpen())
384 return FALSE;
386 PBYTEArray storage;
387 int msgid;
388 errorNumber = ldap_add_ext(ldapContext,
390 CreateLDAPModArray(attributes, ModAttrib::Add, storage),
391 NULL,
392 NULL,
393 &msgid);
394 if (errorNumber != LDAP_SUCCESS)
395 return FALSE;
397 P_timeval tval = timeout;
398 LDAPMessage * result = NULL;
399 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
400 if (result)
401 errorNumber = ldap_result2error(ldapContext, result, TRUE);
403 return errorNumber == LDAP_SUCCESS;
407 BOOL PLDAPSession::Add(const PString & dn, const PStringToString & attributes)
409 return Add(dn, AttribsFromDict(attributes));
413 BOOL PLDAPSession::Add(const PString & dn, const PStringArray & attributes)
415 return Add(dn, AttribsFromArray(attributes));
419 BOOL PLDAPSession::Add(const PString & dn, const PLDAPStructBase & attributes)
421 return Add(dn, AttribsFromStruct(attributes));
425 BOOL PLDAPSession::Modify(const PString & dn, const PList<ModAttrib> & attributes)
427 if (!IsOpen())
428 return FALSE;
430 PBYTEArray storage;
431 int msgid;
432 errorNumber = ldap_modify_ext(ldapContext,
434 CreateLDAPModArray(attributes, ModAttrib::Replace, storage),
435 NULL,
436 NULL,
437 &msgid);
438 if (errorNumber != LDAP_SUCCESS)
439 return FALSE;
441 P_timeval tval = timeout;
442 LDAPMessage * result = NULL;
443 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
444 if (result)
445 errorNumber = ldap_result2error(ldapContext, result, TRUE);
447 return errorNumber == LDAP_SUCCESS;
451 BOOL PLDAPSession::Modify(const PString & dn, const PStringToString & attributes)
453 return Modify(dn, AttribsFromDict(attributes));
457 BOOL PLDAPSession::Modify(const PString & dn, const PStringArray & attributes)
459 return Modify(dn, AttribsFromArray(attributes));
463 BOOL PLDAPSession::Modify(const PString & dn, const PLDAPStructBase & attributes)
465 return Modify(dn, AttribsFromStruct(attributes));
469 BOOL PLDAPSession::Delete(const PString & dn)
471 if (!IsOpen())
472 return FALSE;
474 int msgid;
475 errorNumber = ldap_delete_ext(ldapContext, dn, NULL, NULL, &msgid);
476 if (errorNumber != LDAP_SUCCESS)
477 return FALSE;
479 P_timeval tval = timeout;
480 LDAPMessage * result = NULL;
481 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
482 if (result)
483 errorNumber = ldap_result2error(ldapContext, result, TRUE);
485 return errorNumber == LDAP_SUCCESS;
489 PLDAPSession::SearchContext::SearchContext()
491 result = NULL;
492 message = NULL;
493 found = FALSE;
494 completed = FALSE;
498 PLDAPSession::SearchContext::~SearchContext()
500 if (message != NULL)
501 ldap_msgfree(message);
503 if (result != NULL && result != message)
504 ldap_msgfree(result);
508 BOOL PLDAPSession::Search(SearchContext & context,
509 const PString & filter,
510 const PStringArray & attributes,
511 const PString & baseDN,
512 SearchScope scope)
514 if (!IsOpen())
515 return FALSE;
517 PCharArray storage;
518 char ** attribs = attributes.ToCharArray(&storage);
520 PString base = baseDN;
521 if (base.IsEmpty())
522 base = defaultBaseDN;
524 static const int ScopeCode[NumSearchScope] = {
525 LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
528 P_timeval tval = timeout;
530 errorNumber = ldap_search_ext(ldapContext,
531 base,
532 ScopeCode[scope],
533 filter,
534 attribs,
535 FALSE,
536 NULL,
537 NULL,
538 tval,
539 searchLimit,
540 &context.msgid);
542 if (errorNumber != LDAP_SUCCESS)
543 return FALSE;
545 if (ldap_result(ldapContext, context.msgid, LDAP_MSG_ONE, tval, &context.result) > 0)
546 return GetNextSearchResult(context);
548 if (context.result)
549 errorNumber = ldap_result2error(ldapContext, context.result, TRUE);
550 if (errorNumber == 0)
551 errorNumber = LDAP_OTHER;
552 return FALSE;
556 BOOL PLDAPSession::GetSearchResult(SearchContext & context, PStringToString & data)
558 data.RemoveAll();
560 if (!IsOpen())
561 return FALSE;
563 if (context.result == NULL || context.message == NULL || context.completed)
564 return FALSE;
566 // Extract the resulting data
568 data.SetAt("dn", GetSearchResultDN(context));
570 BerElement * ber = NULL;
571 char * attrib = ldap_first_attribute(ldapContext, context.message, &ber);
572 while (attrib != NULL) {
574 struct berval ** bvals = ldap_get_values_len(ldapContext, context.message, attrib);
575 if (bvals != NULL) {
576 PString value = data(attrib);
578 for (PINDEX i = 0; bvals[i] != NULL; i++ ) {
579 if (!value)
580 value += multipleValueSeparator;
581 value += PString(bvals[i]->bv_val, bvals[i]->bv_len);
583 ber_bvecfree(bvals);
585 data.SetAt(attrib, value);
588 ldap_memfree(attrib);
589 attrib = ldap_next_attribute(ldapContext, context.message, ber);
592 if (ber != NULL)
593 ber_free (ber, 0);
595 return TRUE;
599 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
600 const PString & attribute,
601 PString & data)
603 data.MakeEmpty();
605 if (!IsOpen())
606 return FALSE;
608 if (context.result == NULL || context.message == NULL || context.completed)
609 return FALSE;
611 if (attribute == "dn") {
612 data = GetSearchResultDN(context);
613 return TRUE;
616 char ** values = ldap_get_values(ldapContext, context.message, attribute);
617 if (values == NULL)
618 return FALSE;
620 PINDEX count = ldap_count_values(values);
621 for (PINDEX i = 0; i < count; i++) {
622 if (!data)
623 data += multipleValueSeparator;
624 data += values[i];
627 ldap_value_free(values);
628 return TRUE;
632 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
633 const PString & attribute,
634 PStringArray & data)
636 data.RemoveAll();
638 if (!IsOpen())
639 return FALSE;
641 if (context.result == NULL || context.message == NULL || context.completed)
642 return FALSE;
644 if (attribute == "dn") {
645 data.SetSize(1);
646 data[0] = GetSearchResultDN(context);
647 return TRUE;
650 char ** values = ldap_get_values(ldapContext, context.message, attribute);
651 if (values == NULL)
652 return FALSE;
654 PINDEX count = ldap_count_values(values);
655 data.SetSize(count);
656 for (PINDEX i = 0; i < count; i++)
657 data[i] = values[i];
659 ldap_value_free(values);
660 return TRUE;
664 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
665 const PString & attribute,
666 PArray<PBYTEArray> & data)
668 data.RemoveAll();
670 if (!IsOpen())
671 return FALSE;
673 if (attribute == "dn") {
674 char * dn = ldap_get_dn(ldapContext, context.message);
675 data.Append(new PBYTEArray((const BYTE *)dn, ::strlen(dn)));
676 ldap_memfree(dn);
677 return TRUE;
680 struct berval ** values = ldap_get_values_len(ldapContext, context.message, attribute);
681 if (values == NULL)
682 return FALSE;
684 PINDEX count = ldap_count_values_len(values);
685 data.SetSize(count);
686 for (PINDEX i = 0; i < count; i++)
687 data[i] = PBYTEArray((const BYTE *)values[i]->bv_val, values[i]->bv_len);
689 ldap_value_free_len(values);
690 return TRUE;
694 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
695 PLDAPStructBase & data)
697 if (!IsOpen())
698 return FALSE;
700 BOOL atLeastOne = FALSE;
702 for (PINDEX i = 0; i < data.GetNumAttributes(); i++) {
703 PLDAPAttributeBase & attr = data.GetAttribute(i);
704 if (attr.IsBinary()) {
705 PArray<PBYTEArray> bin;
706 if (GetSearchResult(context, attr.GetName(), bin)) {
707 attr.FromBinary(bin);
708 atLeastOne = TRUE;
711 else {
712 PString str;
713 if (GetSearchResult(context, attr.GetName(), str)) {
714 attr.FromString(str);
715 atLeastOne = TRUE;
720 return atLeastOne;
724 PString PLDAPSession::GetSearchResultDN(SearchContext & context)
726 PString str;
728 if (context.message != NULL) {
729 char * dn = ldap_get_dn(ldapContext, context.message);
730 if (dn != NULL) {
731 str = dn;
732 ldap_memfree(dn);
736 return str;
740 BOOL PLDAPSession::GetNextSearchResult(SearchContext & context)
742 if (!IsOpen())
743 return FALSE;
745 if (context.result == NULL || context.completed)
746 return FALSE;
748 P_timeval tval = timeout;
749 do {
750 if (context.message == NULL)
751 context.message = ldap_first_message(ldapContext, context.result);
752 else
753 context.message = ldap_next_message(ldapContext, context.message);
755 if (context.message != NULL) {
756 switch (ldap_msgtype(context.message)) {
757 case LDAP_RES_SEARCH_ENTRY :
758 context.found = TRUE;
759 errorNumber = LDAP_SUCCESS;
760 return TRUE;
762 case LDAP_RES_SEARCH_RESULT :
763 errorNumber = ldap_result2error(ldapContext, context.message, FALSE);
764 if (errorNumber == 0 && !context.found)
765 errorNumber = LDAP_NO_RESULTS_RETURNED;
766 context.completed = TRUE;
767 return FALSE;
769 // Ignore other result message types for now ...
772 ldap_msgfree(context.result);
773 } while (ldap_result(ldapContext, context.msgid, LDAP_MSG_ONE, tval, &context.result) > 0);
775 if (context.result)
776 errorNumber = ldap_result2error(ldapContext, context.result, FALSE);
777 if (errorNumber == 0)
778 errorNumber = LDAP_OTHER;
779 return FALSE;
783 PList<PStringToString> PLDAPSession::Search(const PString & filter,
784 const PStringArray & attributes,
785 const PString & base,
786 SearchScope scope)
788 PList<PStringToString> data;
790 SearchContext context;
791 if (!Search(context, filter, attributes, base, scope))
792 return data;
794 do {
795 PStringToString * entry = new PStringToString;
796 if (GetSearchResult(context, *entry))
797 data.Append(entry);
798 else {
799 delete entry;
800 break;
802 } while (GetNextSearchResult(context));
804 return data;
808 PString PLDAPSession::GetErrorText() const
810 return ldap_err2string(errorNumber);
814 ///////////////////////////////////////////////////////////////////////////////
816 PLDAPAttributeBase::PLDAPAttributeBase(const char * n, void * ptr, PINDEX sz)
817 : name(n),
818 pointer(ptr),
819 size(sz)
821 PLDAPStructBase::GetInitialiser().AddAttribute(this);
825 PString PLDAPAttributeBase::ToString() const
827 PStringStream stream;
828 PrintOn(stream);
829 return stream;
833 void PLDAPAttributeBase::FromString(const PString & str)
835 PStringStream stream(str);
836 ReadFrom(stream);
840 PBYTEArray PLDAPAttributeBase::ToBinary() const
842 return PBYTEArray((const BYTE *)pointer, size, FALSE);
846 void PLDAPAttributeBase::FromBinary(const PArray<PBYTEArray> & data)
848 if (data.GetSize() > 0 && data[0].GetSize() == size)
849 memcpy(pointer, data[0], size);
853 ///////////////////////////////////////////////////////////////////////////////
855 PMutex PLDAPStructBase::initialiserMutex;
856 PLDAPStructBase * PLDAPStructBase::initialiserInstance;
858 PLDAPStructBase::PLDAPStructBase()
860 attributes.DisallowDeleteObjects();
862 initialiserMutex.Wait();
863 initialiserStack = initialiserInstance;
864 initialiserInstance = this;
868 PLDAPStructBase & PLDAPStructBase::operator=(const PLDAPStructBase & other)
870 for (PINDEX i = 0; i < attributes.GetSize(); i++)
871 attributes.GetDataAt(i).Copy(other.attributes.GetDataAt(i));
873 return *this;
877 PLDAPStructBase & PLDAPStructBase::operator=(const PStringArray & array)
879 for (PINDEX i = 0; i < array.GetSize(); i++) {
880 PString str = array[i];
881 PINDEX equal = str.Find('=');
882 if (equal != P_MAX_INDEX) {
883 PLDAPAttributeBase * attr = GetAttribute(str.Left(equal));
884 if (attr != NULL)
885 attr->FromString(str.Mid(equal+1));
888 return *this;
892 PLDAPStructBase & PLDAPStructBase::operator=(const PStringToString & dict)
894 for (PINDEX i = 0; i < dict.GetSize(); i++) {
895 PLDAPAttributeBase * attr = GetAttribute(dict.GetKeyAt(i));
896 if (attr != NULL)
897 attr->FromString(dict.GetDataAt(i));
899 return *this;
903 void PLDAPStructBase::PrintOn(ostream & strm) const
905 strm << attributes << '\n';
909 void PLDAPStructBase::AddAttribute(PLDAPAttributeBase * attr)
911 attributes.SetAt(attr->GetName(), attr);
915 void PLDAPStructBase::EndConstructor()
917 initialiserInstance = initialiserStack;
918 initialiserMutex.Signal();
922 #endif // P_LDAP
925 // End of file ////////////////////////////////////////////////////////////////