Uncommented beaudio code
[pwlib.git] / src / ptclib / pldap.cxx
blob91b391b8a53bfe440677262d0c6b34acbdd9427a
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.15 2004/02/23 23:52:19 csoutheren
28 * Added pragmas to avoid every Windows application needing to include libs explicitly
30 * Revision 1.14 2004/02/04 09:37:00 rjongbloed
31 * Fixed memory leak and race condition, thanks Rossano Ravelli
33 * Revision 1.13 2004/01/17 17:45:29 csoutheren
34 * Changed to use PString::MakeEmpty
36 * Revision 1.12 2003/07/15 12:12:11 csoutheren
37 * Added support for multiple values in a single attribute string
38 * Thanks to Ravelli Rossano
40 * Revision 1.11 2003/07/12 00:10:40 csoutheren
41 * Fixed problem where Modify routines were calling Add, thanks to Ravelli Rossano
43 * Revision 1.10 2003/06/06 09:14:01 dsandras
45 * Test that a search result has been returned before calling ldapresult2error.
47 * Revision 1.9 2003/06/05 23:17:52 rjongbloed
48 * Changed default operation timeout to 30 seconds.
50 * Revision 1.8 2003/06/05 05:29:30 rjongbloed
51 * Fixed LDAP bind authentication methods, thanks Ravelli Rossano
53 * Revision 1.7 2003/04/17 08:34:48 robertj
54 * Changed LDAP structure output so if field is empty it leaves it out
55 * altogether rather then encoding an empty string, some servers barf.
57 * Revision 1.6 2003/04/16 08:00:19 robertj
58 * Windoes psuedo autoconf support
60 * Revision 1.5 2003/04/07 11:59:52 robertj
61 * Fixed search function returning an error if can't find anything for filter.
63 * Revision 1.4 2003/04/01 07:05:16 robertj
64 * Added ability to specify host:port in opening an LDAP server
66 * Revision 1.3 2003/03/31 03:32:53 robertj
67 * Major addition of functionality.
71 #ifdef __GNUC__
72 #pragma implementation "pldap.h"
73 #endif
75 #include <ptlib.h>
77 #include <ptlib/sockets.h>
78 #include <ptclib/pldap.h>
80 #if P_LDAP
82 #include <ldap.h>
85 #if defined(_WIN32)
86 #pragma comment(lib, P_LDAP_LIBRARY)
88 #ifdef _DEBUG
89 #pragma comment(linker, "/delayload:openldapd.dll")
90 #else
91 #pragma comment(linker, "/delayload:openldap.dll")
92 #endif
93 #pragma comment(lib, "Delayimp.lib")
94 #endif
96 ///////////////////////////////////////////////////////////////////////////////
98 PLDAPSession::PLDAPSession(const PString & baseDN)
99 : ldapContext(NULL),
100 errorNumber(LDAP_SUCCESS),
101 protocolVersion(LDAP_VERSION3),
102 defaultBaseDN(baseDN),
103 searchLimit(UINT_MAX),
104 timeout(0, 30),
105 multipleValueSeparator('\n')
110 PLDAPSession::~PLDAPSession()
112 Close();
116 BOOL PLDAPSession::Open(const PString & server, WORD port)
118 Close();
120 PString host = server;
121 PINDEX colon = server.Find(':');
122 if (colon != P_MAX_INDEX) {
123 host = server.Left(colon);
124 port = PIPSocket::GetPortByService(server.Mid(colon+1), "tcp");
127 ldapContext = ldap_init(server, port);
128 if (!IsOpen())
129 return FALSE;
131 SetOption(LDAP_OPT_PROTOCOL_VERSION, protocolVersion);
132 return TRUE;
136 BOOL PLDAPSession::Close()
138 if (!IsOpen())
139 return FALSE;
141 ldap_unbind(ldapContext);
142 ldapContext = NULL;
143 return TRUE;
147 BOOL PLDAPSession::SetOption(int optcode, int value)
149 if (!IsOpen())
150 return FALSE;
152 return ldap_set_option(ldapContext, optcode, &value);
156 BOOL PLDAPSession::SetOption(int optcode, void * value)
158 if (!IsOpen())
159 return FALSE;
161 return ldap_set_option(ldapContext, optcode, value);
165 BOOL PLDAPSession::Bind(const PString & who,
166 const PString & passwd,
167 AuthenticationMethod authMethod)
169 if (!IsOpen())
170 return FALSE;
172 const char * whoPtr;
173 if (who.IsEmpty())
174 whoPtr = NULL;
175 else
176 whoPtr = who;
178 static const int AuthMethodCode[NumAuthenticationMethod] = {
179 LDAP_AUTH_SIMPLE, LDAP_AUTH_SASL, LDAP_AUTH_KRBV4
181 errorNumber = ldap_bind_s(ldapContext, whoPtr, passwd, AuthMethodCode[authMethod]);
182 return errorNumber == LDAP_SUCCESS;
186 PLDAPSession::ModAttrib::ModAttrib(const PString & n, Operation o)
187 : name(n),
188 op(o)
193 void PLDAPSession::ModAttrib::SetLDAPMod(struct ldapmod & mod, Operation defaultOp)
195 mod.mod_type = (char *)(const char *)name;
197 Operation realOp = op == NumOperations ? defaultOp : op;
198 static const int OpCode[NumOperations] = {
199 LDAP_MOD_ADD, LDAP_MOD_REPLACE, LDAP_MOD_DELETE
201 mod.mod_op = OpCode[realOp];
203 if (IsBinary())
204 mod.mod_op |= LDAP_MOD_BVALUES;
206 SetLDAPModVars(mod);
210 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
211 Operation op)
212 : ModAttrib(name, op)
217 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
218 const PString & value,
219 Operation op)
220 : ModAttrib(name, op)
222 AddValue(value);
226 PLDAPSession::StringModAttrib::StringModAttrib(const PString & name,
227 const PStringList & vals,
228 Operation op)
229 : ModAttrib(name, op),
230 values(vals)
235 void PLDAPSession::StringModAttrib::SetValue(const PString & value)
237 values.RemoveAll();
238 values.AppendString(value);
242 void PLDAPSession::StringModAttrib::AddValue(const PString & value)
244 values.AppendString(value);
248 BOOL PLDAPSession::StringModAttrib::IsBinary() const
250 return FALSE;
254 void PLDAPSession::StringModAttrib::SetLDAPModVars(struct ldapmod & mod)
256 pointers.SetSize(values.GetSize()+1);
257 PINDEX i;
258 for (i = 0; i < values.GetSize(); i++)
259 pointers[i] = values[i].GetPointer();
260 pointers[i] = NULL;
261 mod.mod_values = pointers.GetPointer();
265 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
266 Operation op)
267 : ModAttrib(name, op)
272 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
273 const PBYTEArray & value,
274 Operation op)
275 : ModAttrib(name, op)
277 AddValue(value);
281 PLDAPSession::BinaryModAttrib::BinaryModAttrib(const PString & name,
282 const PList<PBYTEArray> & vals,
283 Operation op)
284 : ModAttrib(name, op),
285 values(vals)
290 void PLDAPSession::BinaryModAttrib::SetValue(const PBYTEArray & value)
292 values.RemoveAll();
293 values.Append(new PBYTEArray(value));
297 void PLDAPSession::BinaryModAttrib::AddValue(const PBYTEArray & value)
299 values.Append(new PBYTEArray(value));
303 BOOL PLDAPSession::BinaryModAttrib::IsBinary() const
305 return TRUE;
309 void PLDAPSession::BinaryModAttrib::SetLDAPModVars(struct ldapmod & mod)
311 pointers.SetSize(values.GetSize()+1);
312 bervals.SetSize(values.GetSize()*sizeof(berval));
313 berval * ber = (berval *)bervals.GetPointer();
314 PINDEX i;
315 for (i = 0; i < values.GetSize(); i++) {
316 ber[i].bv_val = (char *)values[i].GetPointer();
317 ber[i].bv_len = values[i].GetSize();
318 pointers[i] = &ber[i];
320 pointers[i] = NULL;
321 mod.mod_bvalues = pointers.GetPointer();
325 static LDAPMod ** CreateLDAPModArray(const PList<PLDAPSession::ModAttrib> & attributes,
326 PLDAPSession::ModAttrib::Operation defaultOp,
327 PBYTEArray & storage)
329 PINDEX count = attributes.GetSize();
330 storage.SetSize(count*sizeof(LDAPMod) + (count+1)*sizeof(LDAPMod *));
332 LDAPMod ** attrs = (LDAPMod **)storage.GetPointer();
333 LDAPMod * attr = (LDAPMod * )&attrs[count+1];
334 for (PINDEX i = 0; i < count; i++) {
335 attrs[i] = &attr[i];
336 attributes[i].SetLDAPMod(attr[i], defaultOp);
339 return attrs;
343 static PList<PLDAPSession::ModAttrib> AttribsFromDict(const PStringToString & attributes)
345 PList<PLDAPSession::ModAttrib> attrs;
347 for (PINDEX i = 0; i < attributes.GetSize(); i++)
348 attrs.Append(new PLDAPSession::StringModAttrib(attributes.GetKeyAt(i),
349 attributes.GetDataAt(i).Lines()));
351 return attrs;
355 static PList<PLDAPSession::ModAttrib> AttribsFromArray(const PStringArray & attributes)
357 PList<PLDAPSession::ModAttrib> attrs;
359 for (PINDEX i = 0; i < attributes.GetSize(); i++) {
360 PString attr = attributes[i];
361 PINDEX equal = attr.Find('=');
362 if (equal != P_MAX_INDEX)
363 attrs.Append(new PLDAPSession::StringModAttrib(attr.Left(equal),
364 attr.Mid(equal+1).Lines()));
367 return attrs;
371 static PList<PLDAPSession::ModAttrib> AttribsFromStruct(const PLDAPStructBase & attributes)
373 PList<PLDAPSession::ModAttrib> attrs;
375 for (PINDEX i = 0; i < attributes.GetNumAttributes(); i++) {
376 PLDAPAttributeBase & attr = attributes.GetAttribute(i);
377 if (attr.IsBinary())
378 attrs.Append(new PLDAPSession::BinaryModAttrib(attr.GetName(), attr.ToBinary()));
379 else {
380 PString str = attr.ToString();
381 if (!str)
382 attrs.Append(new PLDAPSession::StringModAttrib(attr.GetName(), str));
386 return attrs;
390 BOOL PLDAPSession::Add(const PString & dn, const PList<ModAttrib> & attributes)
392 if (!IsOpen())
393 return FALSE;
395 PBYTEArray storage;
396 int msgid;
397 errorNumber = ldap_add_ext(ldapContext,
399 CreateLDAPModArray(attributes, ModAttrib::Add, storage),
400 NULL,
401 NULL,
402 &msgid);
403 if (errorNumber != LDAP_SUCCESS)
404 return FALSE;
406 P_timeval tval = timeout;
407 LDAPMessage * result = NULL;
408 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
409 if (result)
410 errorNumber = ldap_result2error(ldapContext, result, TRUE);
412 return errorNumber == LDAP_SUCCESS;
416 BOOL PLDAPSession::Add(const PString & dn, const PStringToString & attributes)
418 return Add(dn, AttribsFromDict(attributes));
422 BOOL PLDAPSession::Add(const PString & dn, const PStringArray & attributes)
424 return Add(dn, AttribsFromArray(attributes));
428 BOOL PLDAPSession::Add(const PString & dn, const PLDAPStructBase & attributes)
430 return Add(dn, AttribsFromStruct(attributes));
434 BOOL PLDAPSession::Modify(const PString & dn, const PList<ModAttrib> & attributes)
436 if (!IsOpen())
437 return FALSE;
439 PBYTEArray storage;
440 int msgid;
441 errorNumber = ldap_modify_ext(ldapContext,
443 CreateLDAPModArray(attributes, ModAttrib::Replace, storage),
444 NULL,
445 NULL,
446 &msgid);
447 if (errorNumber != LDAP_SUCCESS)
448 return FALSE;
450 P_timeval tval = timeout;
451 LDAPMessage * result = NULL;
452 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
453 if (result)
454 errorNumber = ldap_result2error(ldapContext, result, TRUE);
456 return errorNumber == LDAP_SUCCESS;
460 BOOL PLDAPSession::Modify(const PString & dn, const PStringToString & attributes)
462 return Modify(dn, AttribsFromDict(attributes));
466 BOOL PLDAPSession::Modify(const PString & dn, const PStringArray & attributes)
468 return Modify(dn, AttribsFromArray(attributes));
472 BOOL PLDAPSession::Modify(const PString & dn, const PLDAPStructBase & attributes)
474 return Modify(dn, AttribsFromStruct(attributes));
478 BOOL PLDAPSession::Delete(const PString & dn)
480 if (!IsOpen())
481 return FALSE;
483 int msgid;
484 errorNumber = ldap_delete_ext(ldapContext, dn, NULL, NULL, &msgid);
485 if (errorNumber != LDAP_SUCCESS)
486 return FALSE;
488 P_timeval tval = timeout;
489 LDAPMessage * result = NULL;
490 ldap_result(ldapContext, msgid, LDAP_MSG_ALL, tval, &result);
491 if (result)
492 errorNumber = ldap_result2error(ldapContext, result, TRUE);
494 return errorNumber == LDAP_SUCCESS;
498 PLDAPSession::SearchContext::SearchContext()
500 result = NULL;
501 message = NULL;
502 found = FALSE;
503 completed = FALSE;
507 PLDAPSession::SearchContext::~SearchContext()
509 if (message != NULL)
510 ldap_msgfree(message);
512 if (result != NULL && result != message)
513 ldap_msgfree(result);
517 BOOL PLDAPSession::Search(SearchContext & context,
518 const PString & filter,
519 const PStringArray & attributes,
520 const PString & baseDN,
521 SearchScope scope)
523 if (!IsOpen())
524 return FALSE;
526 PCharArray storage;
527 char ** attribs = attributes.ToCharArray(&storage);
529 PString base = baseDN;
530 if (base.IsEmpty())
531 base = defaultBaseDN;
533 static const int ScopeCode[NumSearchScope] = {
534 LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
537 P_timeval tval = timeout;
539 errorNumber = ldap_search_ext(ldapContext,
540 base,
541 ScopeCode[scope],
542 filter,
543 attribs,
544 FALSE,
545 NULL,
546 NULL,
547 tval,
548 searchLimit,
549 &context.msgid);
551 if (errorNumber != LDAP_SUCCESS)
552 return FALSE;
554 if (ldap_result(ldapContext, context.msgid, LDAP_MSG_ONE, tval, &context.result) > 0)
555 return GetNextSearchResult(context);
557 if (context.result)
558 errorNumber = ldap_result2error(ldapContext, context.result, TRUE);
559 if (errorNumber == 0)
560 errorNumber = LDAP_OTHER;
561 return FALSE;
565 BOOL PLDAPSession::GetSearchResult(SearchContext & context, PStringToString & data)
567 data.RemoveAll();
569 if (!IsOpen())
570 return FALSE;
572 if (context.result == NULL || context.message == NULL || context.completed)
573 return FALSE;
575 // Extract the resulting data
577 data.SetAt("dn", GetSearchResultDN(context));
579 BerElement * ber = NULL;
580 char * attrib = ldap_first_attribute(ldapContext, context.message, &ber);
581 while (attrib != NULL) {
583 struct berval ** bvals = ldap_get_values_len(ldapContext, context.message, attrib);
584 if (bvals != NULL) {
585 PString value = data(attrib);
587 for (PINDEX i = 0; bvals[i] != NULL; i++ ) {
588 if (!value)
589 value += multipleValueSeparator;
590 value += PString(bvals[i]->bv_val, bvals[i]->bv_len);
592 ber_bvecfree(bvals);
594 data.SetAt(attrib, value);
597 ldap_memfree(attrib);
598 attrib = ldap_next_attribute(ldapContext, context.message, ber);
601 if (ber != NULL)
602 ber_free (ber, 0);
604 return TRUE;
608 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
609 const PString & attribute,
610 PString & data)
612 data.MakeEmpty();
614 if (!IsOpen())
615 return FALSE;
617 if (context.result == NULL || context.message == NULL || context.completed)
618 return FALSE;
620 if (attribute == "dn") {
621 data = GetSearchResultDN(context);
622 return TRUE;
625 char ** values = ldap_get_values(ldapContext, context.message, attribute);
626 if (values == NULL)
627 return FALSE;
629 PINDEX count = ldap_count_values(values);
630 for (PINDEX i = 0; i < count; i++) {
631 if (!data)
632 data += multipleValueSeparator;
633 data += values[i];
636 ldap_value_free(values);
637 return TRUE;
641 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
642 const PString & attribute,
643 PStringArray & data)
645 data.RemoveAll();
647 if (!IsOpen())
648 return FALSE;
650 if (context.result == NULL || context.message == NULL || context.completed)
651 return FALSE;
653 if (attribute == "dn") {
654 data.SetSize(1);
655 data[0] = GetSearchResultDN(context);
656 return TRUE;
659 char ** values = ldap_get_values(ldapContext, context.message, attribute);
660 if (values == NULL)
661 return FALSE;
663 PINDEX count = ldap_count_values(values);
664 data.SetSize(count);
665 for (PINDEX i = 0; i < count; i++)
666 data[i] = values[i];
668 ldap_value_free(values);
669 return TRUE;
673 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
674 const PString & attribute,
675 PArray<PBYTEArray> & data)
677 data.RemoveAll();
679 if (!IsOpen())
680 return FALSE;
682 if (attribute == "dn") {
683 char * dn = ldap_get_dn(ldapContext, context.message);
684 data.Append(new PBYTEArray((const BYTE *)dn, ::strlen(dn)));
685 ldap_memfree(dn);
686 return TRUE;
689 struct berval ** values = ldap_get_values_len(ldapContext, context.message, attribute);
690 if (values == NULL)
691 return FALSE;
693 PINDEX count = ldap_count_values_len(values);
694 data.SetSize(count);
695 for (PINDEX i = 0; i < count; i++)
696 data[i] = PBYTEArray((const BYTE *)values[i]->bv_val, values[i]->bv_len);
698 ldap_value_free_len(values);
699 return TRUE;
703 BOOL PLDAPSession::GetSearchResult(SearchContext & context,
704 PLDAPStructBase & data)
706 if (!IsOpen())
707 return FALSE;
709 BOOL atLeastOne = FALSE;
711 for (PINDEX i = 0; i < data.GetNumAttributes(); i++) {
712 PLDAPAttributeBase & attr = data.GetAttribute(i);
713 if (attr.IsBinary()) {
714 PArray<PBYTEArray> bin;
715 if (GetSearchResult(context, attr.GetName(), bin)) {
716 attr.FromBinary(bin);
717 atLeastOne = TRUE;
720 else {
721 PString str;
722 if (GetSearchResult(context, attr.GetName(), str)) {
723 attr.FromString(str);
724 atLeastOne = TRUE;
729 return atLeastOne;
733 PString PLDAPSession::GetSearchResultDN(SearchContext & context)
735 PString str;
737 if (context.message != NULL) {
738 char * dn = ldap_get_dn(ldapContext, context.message);
739 if (dn != NULL) {
740 str = dn;
741 ldap_memfree(dn);
745 return str;
749 BOOL PLDAPSession::GetNextSearchResult(SearchContext & context)
751 if (!IsOpen())
752 return FALSE;
754 if (context.result == NULL || context.completed)
755 return FALSE;
757 P_timeval tval = timeout;
758 do {
759 if (context.message == NULL)
760 context.message = ldap_first_message(ldapContext, context.result);
761 else
762 context.message = ldap_next_message(ldapContext, context.message);
764 if (context.message != NULL) {
765 switch (ldap_msgtype(context.message)) {
766 case LDAP_RES_SEARCH_ENTRY :
767 context.found = TRUE;
768 errorNumber = LDAP_SUCCESS;
769 return TRUE;
771 case LDAP_RES_SEARCH_RESULT :
772 errorNumber = ldap_result2error(ldapContext, context.message, FALSE);
773 if (errorNumber == 0 && !context.found)
774 errorNumber = LDAP_NO_RESULTS_RETURNED;
775 context.completed = TRUE;
776 return FALSE;
778 // Ignore other result message types for now ...
781 ldap_msgfree(context.result);
782 } while (ldap_result(ldapContext, context.msgid, LDAP_MSG_ONE, tval, &context.result) > 0);
784 if (context.result)
785 errorNumber = ldap_result2error(ldapContext, context.result, FALSE);
786 if (errorNumber == 0)
787 errorNumber = LDAP_OTHER;
788 return FALSE;
792 PList<PStringToString> PLDAPSession::Search(const PString & filter,
793 const PStringArray & attributes,
794 const PString & base,
795 SearchScope scope)
797 PList<PStringToString> data;
799 SearchContext context;
800 if (!Search(context, filter, attributes, base, scope))
801 return data;
803 do {
804 PStringToString * entry = new PStringToString;
805 if (GetSearchResult(context, *entry))
806 data.Append(entry);
807 else {
808 delete entry;
809 break;
811 } while (GetNextSearchResult(context));
813 return data;
817 PString PLDAPSession::GetErrorText() const
819 return ldap_err2string(errorNumber);
823 ///////////////////////////////////////////////////////////////////////////////
825 PLDAPAttributeBase::PLDAPAttributeBase(const char * n, void * ptr, PINDEX sz)
826 : name(n),
827 pointer(ptr),
828 size(sz)
830 PLDAPStructBase::GetInitialiser().AddAttribute(this);
834 PString PLDAPAttributeBase::ToString() const
836 PStringStream stream;
837 PrintOn(stream);
838 return stream;
842 void PLDAPAttributeBase::FromString(const PString & str)
844 PStringStream stream(str);
845 ReadFrom(stream);
849 PBYTEArray PLDAPAttributeBase::ToBinary() const
851 return PBYTEArray((const BYTE *)pointer, size, FALSE);
855 void PLDAPAttributeBase::FromBinary(const PArray<PBYTEArray> & data)
857 if (data.GetSize() > 0 && data[0].GetSize() == size)
858 memcpy(pointer, data[0], size);
862 ///////////////////////////////////////////////////////////////////////////////
864 PMutex PLDAPStructBase::initialiserMutex;
865 PLDAPStructBase * PLDAPStructBase::initialiserInstance;
867 PLDAPStructBase::PLDAPStructBase()
869 attributes.DisallowDeleteObjects();
871 initialiserMutex.Wait();
872 initialiserStack = initialiserInstance;
873 initialiserInstance = this;
877 PLDAPStructBase & PLDAPStructBase::operator=(const PLDAPStructBase & other)
879 for (PINDEX i = 0; i < attributes.GetSize(); i++)
880 attributes.GetDataAt(i).Copy(other.attributes.GetDataAt(i));
882 return *this;
886 PLDAPStructBase & PLDAPStructBase::operator=(const PStringArray & array)
888 for (PINDEX i = 0; i < array.GetSize(); i++) {
889 PString str = array[i];
890 PINDEX equal = str.Find('=');
891 if (equal != P_MAX_INDEX) {
892 PLDAPAttributeBase * attr = GetAttribute(str.Left(equal));
893 if (attr != NULL)
894 attr->FromString(str.Mid(equal+1));
897 return *this;
901 PLDAPStructBase & PLDAPStructBase::operator=(const PStringToString & dict)
903 for (PINDEX i = 0; i < dict.GetSize(); i++) {
904 PLDAPAttributeBase * attr = GetAttribute(dict.GetKeyAt(i));
905 if (attr != NULL)
906 attr->FromString(dict.GetDataAt(i));
908 return *this;
912 void PLDAPStructBase::PrintOn(ostream & strm) const
914 strm << attributes << '\n';
918 void PLDAPStructBase::AddAttribute(PLDAPAttributeBase * attr)
920 attributes.SetAt(attr->GetName(), attr);
924 void PLDAPStructBase::EndConstructor()
926 initialiserInstance = initialiserStack;
927 initialiserMutex.Signal();
931 #endif // P_LDAP
934 // End of file ////////////////////////////////////////////////////////////////