3 /// Routines for reading and writing LDAP LDIF data.
7 Copyright (C) 2005-2012, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
25 #include "r_contact.h"
31 #include "ios_state.h"
33 #define __DEBUG_MODE__
38 const ContactLdif::NameToFunc
ContactLdif::FieldMap
[] = {
39 { "Email", N_("Email address"),
40 &ContactLdif::Email
, &ContactLdif::SetEmail
},
41 { "Phone", N_("Phone number"),
42 &ContactLdif::Phone
, &ContactLdif::SetPhone
},
43 { "Fax", N_("Fax number"),
44 &ContactLdif::Fax
, &ContactLdif::SetFax
},
45 { "WorkPhone", N_("Work phone number"),
46 &ContactLdif::WorkPhone
, &ContactLdif::SetWorkPhone
},
47 { "HomePhone", N_("Home phone number"),
48 &ContactLdif::HomePhone
, &ContactLdif::SetHomePhone
},
49 { "MobilePhone", N_("Mobile phone number"),
50 &ContactLdif::MobilePhone
, &ContactLdif::SetMobilePhone
},
51 { "Pager", N_("Pager number"),
52 &ContactLdif::Pager
, &ContactLdif::SetPager
},
54 &ContactLdif::PIN
, &ContactLdif::SetPIN
},
55 { "FirstName", N_("First name"),
56 &ContactLdif::FirstName
, &ContactLdif::SetFirstName
},
57 { "LastName", N_("Last name"),
58 &ContactLdif::LastName
, &ContactLdif::SetLastName
},
59 { "Company", N_("Company name"),
60 &ContactLdif::Company
, &ContactLdif::SetCompany
},
61 { "DefaultCommunicationsMethod", N_("Default communications method"),
62 &ContactLdif::DefaultCommunicationsMethod
, &ContactLdif::SetDefaultCommunicationsMethod
},
63 { "WorkAddress1", N_("Work Address, line 1"),
64 &ContactLdif::WorkAddress1
, &ContactLdif::SetWorkAddress1
},
65 { "WorkAddress2", N_("Work Address, line 2"),
66 &ContactLdif::WorkAddress2
, &ContactLdif::SetWorkAddress2
},
67 { "WorkAddress3", N_("Work Address, line 3"),
68 &ContactLdif::WorkAddress3
, &ContactLdif::SetWorkAddress3
},
69 { "WorkCity", N_("WorkCity"),
70 &ContactLdif::WorkCity
, &ContactLdif::SetWorkCity
},
71 { "WorkProvince", N_("WorkProvince / State"),
72 &ContactLdif::WorkProvince
, &ContactLdif::SetWorkProvince
},
73 { "WorkPostalCode", N_("Work Postal / ZIP code"),
74 &ContactLdif::WorkPostalCode
, &ContactLdif::SetWorkPostalCode
},
75 { "WorkCountry", N_("WorkCountry"),
76 &ContactLdif::WorkCountry
, &ContactLdif::SetWorkCountry
},
77 { "JobTitle", N_("Job Title"),
78 &ContactLdif::JobTitle
, &ContactLdif::SetJobTitle
},
79 { "PublicKey", N_("Public key"),
80 &ContactLdif::PublicKey
, &ContactLdif::SetPublicKey
},
81 { "Notes", N_("Notes"),
82 &ContactLdif::Notes
, &ContactLdif::SetNotes
},
83 { "Image", N_("Contact photo"),
84 &ContactLdif::Image
, &ContactLdif::SetImage
},
85 { "WorkPostalAddress", N_("Mailing Work address (includes address lines, city, province, country, and postal code)"),
86 &ContactLdif::WorkPostalAddress
, &ContactLdif::SetWorkPostalAddress
},
87 { "HomePostalAddress", N_("Mailing home address (includes address lines, city, province, country, and postal code)"),
88 &ContactLdif::HomePostalAddress
, &ContactLdif::SetHomePostalAddress
},
89 { "FullName", N_("First + Last names"),
90 &ContactLdif::FullName
, &ContactLdif::SetFullName
},
91 { "FQDN", N_("Fully qualified domain name"),
92 &ContactLdif::FQDN
, &ContactLdif::SetFQDN
},
97 bool ContactLdif::LdifAttribute::operator<(const LdifAttribute
&other
) const
99 // the dn attribute always comes first in LDIF output
101 if( other
.name
== "dn" )
102 return false; // both dn, so equal
105 else if( other
.name
== "dn" )
108 return (order
< other
.order
&& name
!= other
.name
) ||
109 (order
== other
.order
&& name
< other
.name
);
112 bool ContactLdif::LdifAttribute::operator==(const LdifAttribute
&other
) const
114 return name
== other
.name
;
118 ///////////////////////////////////////////////////////////////////////////////
121 ContactLdif::ContactLdif(const std::string
&baseDN
)
124 // setup some sane defaults
125 Map("mail", &ContactLdif::Email
, &ContactLdif::SetEmail
);
126 Map("facsimileTelephoneNumber", &ContactLdif::Fax
, &ContactLdif::SetFax
);
127 Map("telephoneNumber", &ContactLdif::WorkPhone
, &ContactLdif::SetWorkPhone
);
128 Map("homePhone", &ContactLdif::HomePhone
, &ContactLdif::SetHomePhone
);
129 Map("mobile", &ContactLdif::MobilePhone
, &ContactLdif::SetMobilePhone
);
130 Map("pager", &ContactLdif::Pager
, &ContactLdif::SetPager
);
131 Map("l", &ContactLdif::WorkCity
, &ContactLdif::SetWorkCity
);
132 Map("st", &ContactLdif::WorkProvince
, &ContactLdif::SetWorkProvince
);
133 Map("postalCode", &ContactLdif::WorkPostalCode
, &ContactLdif::SetWorkPostalCode
);
134 Map("o", &ContactLdif::Company
, &ContactLdif::SetCompany
);
135 Map("c", &ContactLdif::WorkCountry
, &ContactLdif::SetWorkCountry
);
136 SetObjectClass("c", "country");
138 Map("title", &ContactLdif::JobTitle
, &ContactLdif::SetJobTitle
);
139 Map("dn", &ContactLdif::FQDN
, &ContactLdif::SetFQDN
);
140 Map("displayName", &ContactLdif::FullName
, &ContactLdif::SetFullName
);
141 Map("cn", &ContactLdif::FullName
, &ContactLdif::SetFullName
);
142 Map("sn", &ContactLdif::LastName
, &ContactLdif::SetLastName
);
143 Map("givenName", &ContactLdif::FirstName
, &ContactLdif::SetFirstName
);
144 Map("street", &ContactLdif::WorkAddress1
, &ContactLdif::SetWorkAddress1
);
145 Map("postalAddress", &ContactLdif::WorkPostalAddress
, &ContactLdif::SetWorkPostalAddress
);
146 Map("homePostalAddress", &ContactLdif::HomePostalAddress
, &ContactLdif::SetHomePostalAddress
);
147 Map("note", &ContactLdif::Notes
, &ContactLdif::SetNotes
);
148 // FIXME - jpegPhoto looks like the only LDIF field for photo
149 // images... it is unknown which format will come from the
150 // BlackBerry in the Image field, so we can't guarantee
151 // that Image will be in JPG. This mapping can be done manually
152 // from the btool command line with "-m jpegPhoto,Image,Image"
153 // Reading photos from LDIF should be fine, since the BlackBerry
154 // seems to handle most formats.
155 // Map("jpegPhoto", &ContactLdif::Image, &ContactLdif::SetImage);
157 // add heuristics hooks
159 Hook("displayName", &m_displayName
);
161 Hook("givenName", &m_givenName
);
163 // set default DN attribute
167 ContactLdif::~ContactLdif()
171 void ContactLdif::DoWrite(Barry::Contact
&con
,
172 const std::string
&attr
,
173 const std::string
&data
)
176 if( attr
.size() == 0 || data
.size() == 0 )
179 // now have attr/data pair, check hooks:
180 HookMapType::iterator hook
= m_hookMap
.find(attr
);
181 if( hook
!= m_hookMap
.end() ) {
182 *(hook
->second
) = data
;
185 // run according to map
186 AccessMapType::iterator acc
= m_map
.find(attr
);
187 if( acc
!= m_map
.end() ) {
188 (this->*(acc
->second
.write
))(con
, data
);
192 void ContactLdif::Hook(const std::string
&ldifname
, std::string
*var
)
194 m_hookMap
[ldifname
] = var
;
197 const ContactLdif::NameToFunc
* ContactLdif::GetFieldNames() const
202 const ContactLdif::NameToFunc
*
203 ContactLdif::GetField(const std::string
&fieldname
) const
205 for( const NameToFunc
*n
= FieldMap
; n
->name
; n
++ ) {
206 if( fieldname
== n
->name
)
212 std::string
ContactLdif::GetFieldReadName(GetFunctionType read
) const
214 for( const NameToFunc
*n
= FieldMap
; n
->name
; n
++ ) {
215 if( read
== n
->read
)
218 return _("<unknown>");
221 std::string
ContactLdif::GetFieldWriteName(SetFunctionType write
) const
223 for( const NameToFunc
*n
= FieldMap
; n
->name
; n
++ ) {
224 if( write
== n
->write
)
227 return _("<unknown>");
230 bool ContactLdif::Map(const LdifAttribute
&ldifname
,
231 const std::string
&readField
,
232 const std::string
&writeField
)
234 const NameToFunc
*read
= GetField(readField
);
235 const NameToFunc
*write
= GetField(writeField
);
236 if( !read
|| !write
)
238 Map(ldifname
, read
->read
, write
->write
);
242 void ContactLdif::Map(const LdifAttribute
&ldifname
,
243 GetFunctionType read
,
244 SetFunctionType write
)
246 m_map
[ldifname
] = AccessPair(read
, write
);
249 void ContactLdif::Unmap(const LdifAttribute
&ldifname
)
251 m_map
.erase(ldifname
);
257 /// Sets the LDIF attribute name to use when constructing the FQDN.
258 /// The FQDN field will take this name, and combine it with the
259 /// baseDN from the constructor to produce a FQDN for the record.
261 bool ContactLdif::SetDNAttr(const LdifAttribute
&name
)
263 // try to find the attribute in the map
264 AccessMapType::iterator i
= m_map
.find(name
);
265 if( i
== m_map
.end() )
272 bool ContactLdif::SetObjectClass(const LdifAttribute
&name
,
273 const std::string
&objectClass
)
275 AccessMapType::iterator i
= m_map
.find(name
);
276 if( i
== m_map
.end() )
279 LdifAttribute key
= i
->first
;
280 AccessPair pair
= i
->second
;
282 key
.objectClass
= objectClass
;
287 bool ContactLdif::SetObjectOrder(const LdifAttribute
&name
, int order
)
289 AccessMapType::iterator i
= m_map
.find(name
);
290 if( i
== m_map
.end() )
293 LdifAttribute key
= i
->first
;
294 AccessPair pair
= i
->second
;
302 std::string
ContactLdif::Email(const Barry::Contact
&con
) const
304 return con
.GetEmail(m_emailIndex
++);
307 std::string
ContactLdif::Phone(const Barry::Contact
&con
) const
312 std::string
ContactLdif::Fax(const Barry::Contact
&con
) const
317 std::string
ContactLdif::WorkPhone(const Barry::Contact
&con
) const
319 return con
.WorkPhone
;
322 std::string
ContactLdif::HomePhone(const Barry::Contact
&con
) const
324 return con
.HomePhone
;
327 std::string
ContactLdif::MobilePhone(const Barry::Contact
&con
) const
329 return con
.MobilePhone
;
332 std::string
ContactLdif::Pager(const Barry::Contact
&con
) const
337 std::string
ContactLdif::PIN(const Barry::Contact
&con
) const
342 std::string
ContactLdif::FirstName(const Barry::Contact
&con
) const
344 return con
.FirstName
;
347 std::string
ContactLdif::LastName(const Barry::Contact
&con
) const
352 std::string
ContactLdif::Company(const Barry::Contact
&con
) const
357 std::string
ContactLdif::DefaultCommunicationsMethod(const Barry::Contact
&con
) const
359 return con
.DefaultCommunicationsMethod
;
362 std::string
ContactLdif::WorkAddress1(const Barry::Contact
&con
) const
364 return con
.WorkAddress
.Address1
;
367 std::string
ContactLdif::WorkAddress2(const Barry::Contact
&con
) const
369 return con
.WorkAddress
.Address2
;
372 std::string
ContactLdif::WorkAddress3(const Barry::Contact
&con
) const
374 return con
.WorkAddress
.Address3
;
377 std::string
ContactLdif::WorkCity(const Barry::Contact
&con
) const
379 return con
.WorkAddress
.City
;
382 std::string
ContactLdif::WorkProvince(const Barry::Contact
&con
) const
384 return con
.WorkAddress
.Province
;
387 std::string
ContactLdif::WorkPostalCode(const Barry::Contact
&con
) const
389 return con
.WorkAddress
.PostalCode
;
392 std::string
ContactLdif::WorkCountry(const Barry::Contact
&con
) const
394 return con
.WorkAddress
.Country
;
397 std::string
ContactLdif::JobTitle(const Barry::Contact
&con
) const
402 std::string
ContactLdif::PublicKey(const Barry::Contact
&con
) const
404 return con
.PublicKey
;
407 std::string
ContactLdif::Notes(const Barry::Contact
&con
) const
412 std::string
ContactLdif::Image(const Barry::Contact
&con
) const
417 std::string
ContactLdif::WorkPostalAddress(const Barry::Contact
&con
) const
419 return con
.WorkAddress
.GetLabel();
422 std::string
ContactLdif::HomePostalAddress(const Barry::Contact
&con
) const
424 return con
.HomeAddress
.GetLabel();
427 std::string
ContactLdif::FullName(const Barry::Contact
&con
) const
429 return con
.GetFullName();
432 std::string
ContactLdif::FQDN(const Barry::Contact
&con
) const
434 std::string FQDN
= m_dnAttr
.name
;
437 AccessMapType::const_iterator i
= m_map
.find(m_dnAttr
);
438 if( i
!= m_map
.end() ) {
439 FQDN
+= (this->*(i
->second
.read
))(con
);
442 FQDN
+= _("unknown");
450 bool ContactLdif::IsArrayFunc(GetFunctionType getf
) const
452 // Currently, only the Email getter has array data
453 if( getf
== &ContactLdif::Email
)
458 void ContactLdif::ClearArrayState() const
463 void ContactLdif::SetEmail(Barry::Contact
&con
, const std::string
&val
) const
465 con
.EmailAddresses
.push_back(val
);
468 void ContactLdif::SetPhone(Barry::Contact
&con
, const std::string
&val
) const
473 void ContactLdif::SetFax(Barry::Contact
&con
, const std::string
&val
) const
478 void ContactLdif::SetWorkPhone(Barry::Contact
&con
, const std::string
&val
) const
483 void ContactLdif::SetHomePhone(Barry::Contact
&con
, const std::string
&val
) const
488 void ContactLdif::SetMobilePhone(Barry::Contact
&con
, const std::string
&val
) const
490 con
.MobilePhone
= val
;
493 void ContactLdif::SetPager(Barry::Contact
&con
, const std::string
&val
) const
498 void ContactLdif::SetPIN(Barry::Contact
&con
, const std::string
&val
) const
503 void ContactLdif::SetFirstName(Barry::Contact
&con
, const std::string
&val
) const
508 void ContactLdif::SetLastName(Barry::Contact
&con
, const std::string
&val
) const
513 void ContactLdif::SetCompany(Barry::Contact
&con
, const std::string
&val
) const
518 void ContactLdif::SetDefaultCommunicationsMethod(Barry::Contact
&con
, const std::string
&val
) const
520 con
.DefaultCommunicationsMethod
= val
;
523 void ContactLdif::SetWorkAddress1(Barry::Contact
&con
, const std::string
&val
) const
525 con
.WorkAddress
.Address1
= val
;
528 void ContactLdif::SetWorkAddress2(Barry::Contact
&con
, const std::string
&val
) const
530 con
.WorkAddress
.Address2
= val
;
533 void ContactLdif::SetWorkAddress3(Barry::Contact
&con
, const std::string
&val
) const
535 con
.WorkAddress
.Address3
= val
;
538 void ContactLdif::SetWorkCity(Barry::Contact
&con
, const std::string
&val
) const
540 con
.WorkAddress
.City
= val
;
543 void ContactLdif::SetWorkProvince(Barry::Contact
&con
, const std::string
&val
) const
545 con
.WorkAddress
.Province
= val
;
548 void ContactLdif::SetWorkPostalCode(Barry::Contact
&con
, const std::string
&val
) const
550 con
.WorkAddress
.PostalCode
= val
;
553 void ContactLdif::SetWorkCountry(Barry::Contact
&con
, const std::string
&val
) const
555 con
.WorkAddress
.Country
= val
;
558 void ContactLdif::SetJobTitle(Barry::Contact
&con
, const std::string
&val
) const
563 void ContactLdif::SetPublicKey(Barry::Contact
&con
, const std::string
&val
) const
568 void ContactLdif::SetNotes(Barry::Contact
&con
, const std::string
&val
) const
573 void ContactLdif::SetImage(Barry::Contact
&con
, const std::string
&val
) const
578 void ContactLdif::SetWorkPostalAddress(Barry::Contact
&con
, const std::string
&val
) const
581 // throw std::runtime_error("SetWorkPostalAddress() not implemented");
582 // std::cout << "SetWorkPostalAddress() not implemented: " << val << std::endl;
585 void ContactLdif::SetHomePostalAddress(Barry::Contact
&con
, const std::string
&val
) const
588 // throw std::runtime_error("SetHomePostalAddress() not implemented");
589 // std::cout << "SetHomePostalAddress() not implemented: " << val << std::endl;
592 void ContactLdif::SetFullName(Barry::Contact
&con
, const std::string
&val
) const
594 std::string first
, last
;
595 Contact::SplitName(val
, first
, last
);
596 con
.FirstName
= first
;
600 void ContactLdif::SetFQDN(Barry::Contact
&con
, const std::string
&val
) const
602 throw std::runtime_error("not implemented");
606 void ContactLdif::ClearHeuristics()
609 m_displayName
.clear();
614 bool ContactLdif::RunHeuristics(Barry::Contact
&con
)
617 con
.LastName
.clear();
618 con
.FirstName
.clear();
620 // find the best match for name... prefer sn/givenName if available
624 if( m_givenName
.size() ) {
625 con
.FirstName
= m_givenName
;
628 if( !con
.LastName
.size() || !con
.FirstName
.size() ) {
629 std::string first
, last
;
631 // still don't have a complete name, check cn first
633 Contact::SplitName(m_cn
, first
, last
);
634 if( !con
.LastName
.size() && last
.size() )
636 if( !con
.FirstName
.size() && first
.size() )
637 con
.FirstName
= first
;
640 // displayName is last chance
641 if( m_displayName
.size() ) {
642 Contact::SplitName(m_displayName
, first
, last
);
643 if( !con
.LastName
.size() && last
.size() )
645 if( !con
.FirstName
.size() && first
.size() )
646 con
.FirstName
= first
;
650 return con
.LastName
.size() && con
.FirstName
.size();
657 /// Output contact data to os in LDAP LDIF format.
659 void ContactLdif::DumpLdif(std::ostream
&os
,
660 const Barry::Contact
&con
) const
662 ios_format_state
state(os
);
667 // setup stream state
668 os
.setf(std::ios::left
);
671 if( FirstName(con
).size() == 0 && LastName(con
).size() == 0 )
672 return; // nothing to do
674 os
<< "# Contact 0x" << std::hex
<< con
.GetID() << ", "
675 << FullName(con
) << "\n";
677 // cycle through the map
678 for( AccessMapType::const_iterator b
= m_map
.begin();
682 // print only fields with data
686 field
= (this->*(b
->second
.read
))(con
);
688 os
<< b
->first
.name
<< MakeLdifData(field
) << "\n";
689 if( b
->first
.objectClass
.size() )
690 os
<< "objectClass: " << b
->first
.objectClass
<< "\n";
692 } while( IsArrayFunc(b
->second
.read
) && field
.size() );
695 os
<< "objectClass: inetOrgPerson\n";
697 // last line must be empty
701 bool ContactLdif::ReadLdif(std::istream
&is
, Barry::Contact
&con
)
709 // search for beginning dn: line
711 while( std::getline(is
, line
) ) {
712 if( strncmp(line
.c_str(), "dn: ", 4) == 0 ) {
720 // storage for various name styles
721 std::string coded
, decode
, attr
, data
;
722 bool b64field
= false;
724 // read ldif lines until empty line is found
725 while( getline(is
, line
) && line
.size() ) {
728 // processing a base64 encoded field
729 if( line
[0] == ' ' ) {
735 // end of base64 block... ignore errors,
736 // and attempt to save everything decodable...
737 // the LDAP server sometimes returns incomplete
738 // base64 encoding, but otherwise the data is fine
739 base64_decode(coded
, decode
);
740 DoWrite(con
, attr
, decode
);
744 // fall through to process new line
748 // split into attribute / data
749 std::string::size_type delim
= line
.find(':'), dstart
;
750 if( delim
== std::string::npos
)
753 attr
.assign(line
, 0, delim
);
755 while( line
[dstart
] == ' ' || line
[dstart
] == ':' )
757 data
= line
.substr(dstart
);
759 // is this data base64 encoded?
760 if( line
[delim
+ 1] == ':' ) {
766 DoWrite(con
, attr
, data
);
770 // clean up base64 decoding... ignore errors, see above comment
771 base64_decode(coded
, decode
);
772 DoWrite(con
, attr
, decode
);
777 return RunHeuristics(con
);
780 void ContactLdif::DumpMap(std::ostream
&os
) const
782 ios_format_state
state(os
);
784 os
.setf(std::ios::left
);
787 os
<< _("ContactLdif Mapping:\n");
789 // cycle through the map
790 for( AccessMapType::const_iterator b
= m_map
.begin();
794 os
<< " " << std::left
<< std::setw(20) << b
->first
.name
795 << "-> " << GetFieldReadName(b
->second
.read
)
796 << " / " << GetFieldWriteName(b
->second
.write
) << "\n";
798 // find read/write names
800 if( b
->first
.objectClass
.size() ) {
801 os
<< " " << std::setw(20) << " "
802 << "objectClass: " << b
->first
.objectClass
<< "\n";
806 os
<< _(" >>> DN attribute: ") << m_dnAttr
.name
<< "\n";
809 std::string
ContactLdif::MakeLdifData(const std::string
&str
)
811 std::string data
= ":";
813 if( NeedsEncoding(str
) ) {
815 base64_encode(str
, b64
);
832 // 0x00 (NUL), 0x0a (LF), 0x0d (CR), or anything greater than 0x7f
834 // First char must meet above criteria, plus must not be:
835 // 0x20 (SPACE), 0x3a (colon), 0x3c ('<')
837 bool ContactLdif::NeedsEncoding(const std::string
&str
)
839 for( std::string::size_type i
= 0; i
< str
.size(); i
++ ) {
840 unsigned char c
= str
[i
];