Tree-wide cleanup of trailing whitespace
[barry.git] / src / ldif.cc
blob04f052d4d33529967a2db44e33de663e053af0ee
1 ///
2 /// \file ldif.cc
3 /// Routines for reading and writing LDAP LDIF data.
4 ///
6 /*
7 Copyright (C) 2005-2009, 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.
22 #include "ldif.h"
23 #include "record.h"
24 #include "r_contact.h"
25 #include "base64.h"
26 #include <stdexcept>
27 #include <iostream>
28 #include <iomanip>
29 #include <string.h>
31 #define __DEBUG_MODE__
32 #include "debug.h"
34 namespace Barry {
36 const ContactLdif::NameToFunc ContactLdif::FieldMap[] = {
37 { "Email", "Email address",
38 &ContactLdif::Email, &ContactLdif::SetEmail },
39 { "Phone", "Phone number",
40 &ContactLdif::Phone, &ContactLdif::SetPhone },
41 { "Fax", "Fax number",
42 &ContactLdif::Fax, &ContactLdif::SetFax },
43 { "WorkPhone", "Work phone number",
44 &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone },
45 { "HomePhone", "Home phone number",
46 &ContactLdif::HomePhone, &ContactLdif::SetHomePhone },
47 { "MobilePhone", "Mobile phone number",
48 &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone },
49 { "Pager", "Pager number",
50 &ContactLdif::Pager, &ContactLdif::SetPager },
51 { "PIN", "PIN",
52 &ContactLdif::PIN, &ContactLdif::SetPIN },
53 { "FirstName", "First name",
54 &ContactLdif::FirstName, &ContactLdif::SetFirstName },
55 { "LastName", "Last name",
56 &ContactLdif::LastName, &ContactLdif::SetLastName },
57 { "Company", "Company name",
58 &ContactLdif::Company, &ContactLdif::SetCompany },
59 { "DefaultCommunicationsMethod", "Default communications method",
60 &ContactLdif::DefaultCommunicationsMethod, &ContactLdif::SetDefaultCommunicationsMethod },
61 { "Address1", "Address, line 1",
62 &ContactLdif::Address1, &ContactLdif::SetAddress1 },
63 { "Address2", "Address, line 2",
64 &ContactLdif::Address2, &ContactLdif::SetAddress2 },
65 { "Address3", "Address, line 3",
66 &ContactLdif::Address3, &ContactLdif::SetAddress3 },
67 { "City", "City",
68 &ContactLdif::City, &ContactLdif::SetCity },
69 { "Province", "Province / State",
70 &ContactLdif::Province, &ContactLdif::SetProvince },
71 { "PostalCode", "Postal / ZIP code",
72 &ContactLdif::PostalCode, &ContactLdif::SetPostalCode },
73 { "Country", "Country",
74 &ContactLdif::Country, &ContactLdif::SetCountry },
75 { "JobTitle", "Job Title",
76 &ContactLdif::JobTitle, &ContactLdif::SetJobTitle },
77 { "PublicKey", "Public key",
78 &ContactLdif::PublicKey, &ContactLdif::SetPublicKey },
79 { "Notes", "Notes",
80 &ContactLdif::Notes, &ContactLdif::SetNotes },
81 { "PostalAddress", "Mailing address (includes address lines, city, province, country, and postal code)",
82 &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress },
83 { "FullName", "First + Last names",
84 &ContactLdif::FullName, &ContactLdif::SetFullName },
85 { "FQDN", "Fully qualified domain name",
86 &ContactLdif::FQDN, &ContactLdif::SetFQDN },
87 { 0, 0, 0 }
91 bool ContactLdif::LdifAttribute::operator<(const LdifAttribute &other) const
93 // the dn attribute always comes first in LDIF output
94 if( name == "dn" ) {
95 if( other.name == "dn" )
96 return false; // both dn, so equal
97 return true;
99 else if( other.name == "dn" )
100 return false;
102 return (order < other.order && name != other.name) ||
103 (order == other.order && name < other.name);
106 bool ContactLdif::LdifAttribute::operator==(const LdifAttribute &other) const
108 return name == other.name;
112 ///////////////////////////////////////////////////////////////////////////////
113 // ContactLdif class
115 ContactLdif::ContactLdif(const std::string &baseDN)
116 : m_baseDN(baseDN)
118 // setup some sane defaults
119 Map("mail", &ContactLdif::Email, &ContactLdif::SetEmail);
120 Map("facsimileTelephoneNumber", &ContactLdif::Fax, &ContactLdif::SetFax);
121 Map("telephoneNumber", &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone);
122 Map("homePhone", &ContactLdif::HomePhone, &ContactLdif::SetHomePhone);
123 Map("mobile", &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone);
124 Map("pager", &ContactLdif::Pager, &ContactLdif::SetPager);
125 Map("l", &ContactLdif::City, &ContactLdif::SetCity);
126 Map("st", &ContactLdif::Province, &ContactLdif::SetProvince);
127 Map("postalCode", &ContactLdif::PostalCode, &ContactLdif::SetPostalCode);
128 Map("o", &ContactLdif::Company, &ContactLdif::SetCompany);
129 Map("c", &ContactLdif::Country, &ContactLdif::SetCountry);
130 SetObjectClass("c", "country");
132 Map("title", &ContactLdif::JobTitle, &ContactLdif::SetJobTitle);
133 Map("dn", &ContactLdif::FQDN, &ContactLdif::SetFQDN);
134 Map("displayName", &ContactLdif::FullName, &ContactLdif::SetFullName);
135 Map("cn", &ContactLdif::FullName, &ContactLdif::SetFullName);
136 Map("sn", &ContactLdif::LastName, &ContactLdif::SetLastName);
137 Map("givenName", &ContactLdif::FirstName, &ContactLdif::SetFirstName);
138 Map("street", &ContactLdif::Address1, &ContactLdif::SetAddress1);
139 Map("postalAddress", &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress);
140 Map("note", &ContactLdif::Notes, &ContactLdif::SetNotes);
142 // add heuristics hooks
143 Hook("cn", &m_cn);
144 Hook("displayName", &m_displayName);
145 Hook("sn", &m_sn);
146 Hook("givenName", &m_givenName);
148 // set default DN attribute
149 SetDNAttr("cn");
152 ContactLdif::~ContactLdif()
156 void ContactLdif::DoWrite(Barry::Contact &con,
157 const std::string &attr,
158 const std::string &data)
160 // valid?
161 if( attr.size() == 0 || data.size() == 0 )
162 return;
164 // now have attr/data pair, check hooks:
165 HookMapType::iterator hook = m_hookMap.find(attr);
166 if( hook != m_hookMap.end() ) {
167 *(hook->second) = data;
170 // run according to map
171 AccessMapType::iterator acc = m_map.find(attr);
172 if( acc != m_map.end() ) {
173 (this->*(acc->second.write))(con, data);
177 void ContactLdif::Hook(const std::string &ldifname, std::string *var)
179 m_hookMap[ldifname] = var;
182 const ContactLdif::NameToFunc*
183 ContactLdif::GetField(const std::string &fieldname) const
185 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
186 if( fieldname == n->name )
187 return n;
189 return 0;
192 std::string ContactLdif::GetFieldReadName(GetFunctionType read) const
194 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
195 if( read == n->read )
196 return n->name;
198 return "<unknown>";
201 std::string ContactLdif::GetFieldWriteName(SetFunctionType write) const
203 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
204 if( write == n->write )
205 return n->name;
207 return "<unknown>";
210 bool ContactLdif::Map(const LdifAttribute &ldifname,
211 const std::string &readField,
212 const std::string &writeField)
214 const NameToFunc *read = GetField(readField);
215 const NameToFunc *write = GetField(writeField);
216 if( !read || !write )
217 return false;
218 Map(ldifname, read->read, write->write);
219 return true;
222 void ContactLdif::Map(const LdifAttribute &ldifname,
223 GetFunctionType read,
224 SetFunctionType write)
226 m_map[ldifname] = AccessPair(read, write);
229 void ContactLdif::Unmap(const LdifAttribute &ldifname)
231 m_map.erase(ldifname);
235 // SetDNAttr
237 /// Sets the LDIF attribute name to use when constructing the FQDN.
238 /// The FQDN field will take this name, and combine it with the
239 /// baseDN from the constructor to produce a FQDN for the record.
241 bool ContactLdif::SetDNAttr(const LdifAttribute &name)
243 // try to find the attribute in the map
244 AccessMapType::iterator i = m_map.find(name);
245 if( i == m_map.end() )
246 return false;
248 m_dnAttr = name;
249 return true;
252 bool ContactLdif::SetObjectClass(const LdifAttribute &name,
253 const std::string &objectClass)
255 AccessMapType::iterator i = m_map.find(name);
256 if( i == m_map.end() )
257 return false;
259 LdifAttribute key = i->first;
260 AccessPair pair = i->second;
261 m_map.erase(key);
262 key.objectClass = objectClass;
263 m_map[key] = pair;
264 return true;
267 bool ContactLdif::SetObjectOrder(const LdifAttribute &name, int order)
269 AccessMapType::iterator i = m_map.find(name);
270 if( i == m_map.end() )
271 return false;
273 LdifAttribute key = i->first;
274 AccessPair pair = i->second;
275 m_map.erase(key);
276 key.order = order;
277 m_map[key] = pair;
278 return true;
282 std::string ContactLdif::Email(const Barry::Contact &con) const
284 return con.GetEmail(m_emailIndex++);
287 std::string ContactLdif::Phone(const Barry::Contact &con) const
289 return con.Phone;
292 std::string ContactLdif::Fax(const Barry::Contact &con) const
294 return con.Fax;
297 std::string ContactLdif::WorkPhone(const Barry::Contact &con) const
299 return con.WorkPhone;
302 std::string ContactLdif::HomePhone(const Barry::Contact &con) const
304 return con.HomePhone;
307 std::string ContactLdif::MobilePhone(const Barry::Contact &con) const
309 return con.MobilePhone;
312 std::string ContactLdif::Pager(const Barry::Contact &con) const
314 return con.Pager;
317 std::string ContactLdif::PIN(const Barry::Contact &con) const
319 return con.PIN;
322 std::string ContactLdif::FirstName(const Barry::Contact &con) const
324 return con.FirstName;
327 std::string ContactLdif::LastName(const Barry::Contact &con) const
329 return con.LastName;
332 std::string ContactLdif::Company(const Barry::Contact &con) const
334 return con.Company;
337 std::string ContactLdif::DefaultCommunicationsMethod(const Barry::Contact &con) const
339 return con.DefaultCommunicationsMethod;
342 std::string ContactLdif::Address1(const Barry::Contact &con) const
344 return con.WorkAddress.Address1;
347 std::string ContactLdif::Address2(const Barry::Contact &con) const
349 return con.WorkAddress.Address2;
352 std::string ContactLdif::Address3(const Barry::Contact &con) const
354 return con.WorkAddress.Address3;
357 std::string ContactLdif::City(const Barry::Contact &con) const
359 return con.WorkAddress.City;
362 std::string ContactLdif::Province(const Barry::Contact &con) const
364 return con.WorkAddress.Province;
367 std::string ContactLdif::PostalCode(const Barry::Contact &con) const
369 return con.WorkAddress.PostalCode;
372 std::string ContactLdif::Country(const Barry::Contact &con) const
374 return con.WorkAddress.Country;
377 std::string ContactLdif::JobTitle(const Barry::Contact &con) const
379 return con.JobTitle;
382 std::string ContactLdif::PublicKey(const Barry::Contact &con) const
384 return con.PublicKey;
387 std::string ContactLdif::Notes(const Barry::Contact &con) const
389 return con.Notes;
392 std::string ContactLdif::PostalAddress(const Barry::Contact &con) const
394 return con.WorkAddress.GetLabel();
397 std::string ContactLdif::FullName(const Barry::Contact &con) const
399 return con.GetFullName();
402 std::string ContactLdif::FQDN(const Barry::Contact &con) const
404 std::string FQDN = m_dnAttr.name;
405 FQDN += "=";
407 AccessMapType::const_iterator i = m_map.find(m_dnAttr);
408 if( i != m_map.end() ) {
409 FQDN += (this->*(i->second.read))(con);
411 else {
412 FQDN += "unknown";
415 FQDN += ",";
416 FQDN += m_baseDN;
417 return FQDN;
420 bool ContactLdif::IsArrayFunc(GetFunctionType getf) const
422 // Currently, only the Email getter has array data
423 if( getf == &ContactLdif::Email )
424 return true;
425 return false;
428 void ContactLdif::ClearArrayState() const
430 m_emailIndex = 0;
433 void ContactLdif::SetEmail(Barry::Contact &con, const std::string &val) const
435 con.EmailAddresses.push_back(val);
438 void ContactLdif::SetPhone(Barry::Contact &con, const std::string &val) const
440 con.Phone = val;
443 void ContactLdif::SetFax(Barry::Contact &con, const std::string &val) const
445 con.Fax = val;
448 void ContactLdif::SetWorkPhone(Barry::Contact &con, const std::string &val) const
450 con.WorkPhone = val;
453 void ContactLdif::SetHomePhone(Barry::Contact &con, const std::string &val) const
455 con.HomePhone = val;
458 void ContactLdif::SetMobilePhone(Barry::Contact &con, const std::string &val) const
460 con.MobilePhone = val;
463 void ContactLdif::SetPager(Barry::Contact &con, const std::string &val) const
465 con.Pager = val;
468 void ContactLdif::SetPIN(Barry::Contact &con, const std::string &val) const
470 con.PIN = val;
473 void ContactLdif::SetFirstName(Barry::Contact &con, const std::string &val) const
475 con.FirstName = val;
478 void ContactLdif::SetLastName(Barry::Contact &con, const std::string &val) const
480 con.LastName = val;
483 void ContactLdif::SetCompany(Barry::Contact &con, const std::string &val) const
485 con.Company = val;
488 void ContactLdif::SetDefaultCommunicationsMethod(Barry::Contact &con, const std::string &val) const
490 con.DefaultCommunicationsMethod = val;
493 void ContactLdif::SetAddress1(Barry::Contact &con, const std::string &val) const
495 con.WorkAddress.Address1 = val;
498 void ContactLdif::SetAddress2(Barry::Contact &con, const std::string &val) const
500 con.WorkAddress.Address2 = val;
503 void ContactLdif::SetAddress3(Barry::Contact &con, const std::string &val) const
505 con.WorkAddress.Address3 = val;
508 void ContactLdif::SetCity(Barry::Contact &con, const std::string &val) const
510 con.WorkAddress.City = val;
513 void ContactLdif::SetProvince(Barry::Contact &con, const std::string &val) const
515 con.WorkAddress.Province = val;
518 void ContactLdif::SetPostalCode(Barry::Contact &con, const std::string &val) const
520 con.WorkAddress.PostalCode = val;
523 void ContactLdif::SetCountry(Barry::Contact &con, const std::string &val) const
525 con.WorkAddress.Country = val;
528 void ContactLdif::SetJobTitle(Barry::Contact &con, const std::string &val) const
530 con.JobTitle = val;
533 void ContactLdif::SetPublicKey(Barry::Contact &con, const std::string &val) const
535 con.PublicKey = val;
538 void ContactLdif::SetNotes(Barry::Contact &con, const std::string &val) const
540 con.Notes = val;
543 void ContactLdif::SetPostalAddress(Barry::Contact &con, const std::string &val) const
545 // fixme;
546 // throw std::runtime_error("SetPostalAddress() not implemented");
547 // std::cout << "SetPostalAddress() not implemented: " << val << std::endl;
550 void ContactLdif::SetFullName(Barry::Contact &con, const std::string &val) const
552 std::string first, last;
553 Contact::SplitName(val, first, last);
554 con.FirstName = first;
555 con.LastName = last;
558 void ContactLdif::SetFQDN(Barry::Contact &con, const std::string &val) const
560 throw std::runtime_error("not implemented");
564 void ContactLdif::ClearHeuristics()
566 m_cn.clear();
567 m_displayName.clear();
568 m_sn.clear();
569 m_givenName.clear();
572 bool ContactLdif::RunHeuristics(Barry::Contact &con)
574 // start fresh
575 con.LastName.clear();
576 con.FirstName.clear();
578 // find the best match for name... prefer sn/givenName if available
579 if( m_sn.size() ) {
580 con.LastName = m_sn;
582 if( m_givenName.size() ) {
583 con.FirstName = m_givenName;
586 if( !con.LastName.size() || !con.FirstName.size() ) {
587 std::string first, last;
589 // still don't have a complete name, check cn first
590 if( m_cn.size() ) {
591 Contact::SplitName(m_cn, first, last);
592 if( !con.LastName.size() && last.size() )
593 con.LastName = last;
594 if( !con.FirstName.size() && first.size() )
595 con.FirstName = first;
598 // displayName is last chance
599 if( m_displayName.size() ) {
600 Contact::SplitName(m_displayName, first, last);
601 if( !con.LastName.size() && last.size() )
602 con.LastName = last;
603 if( !con.FirstName.size() && first.size() )
604 con.FirstName = first;
608 return con.LastName.size() && con.FirstName.size();
613 // DumpLdif
615 /// Output contact data to os in LDAP LDIF format.
617 void ContactLdif::DumpLdif(std::ostream &os,
618 const Barry::Contact &con) const
620 // start fresh
621 ClearArrayState();
623 // setup stream state
624 std::ios::fmtflags oldflags = os.setf(std::ios::left);
625 char fill = os.fill(' ');
627 if( FirstName(con).size() == 0 && LastName(con).size() == 0 )
628 return; // nothing to do
630 os << "# Contact 0x" << std::hex << con.GetID() << ", "
631 << FullName(con) << "\n";
633 // cycle through the map
634 for( AccessMapType::const_iterator b = m_map.begin();
635 b != m_map.end();
636 ++b )
638 // print only fields with data
639 std::string field;
641 do {
642 field = (this->*(b->second.read))(con);
643 if( field.size() ) {
644 os << b->first.name << MakeLdifData(field) << "\n";
645 if( b->first.objectClass.size() )
646 os << "objectClass: " << b->first.objectClass << "\n";
648 } while( IsArrayFunc(b->second.read) && field.size() );
651 os << "objectClass: inetOrgPerson\n";
653 // last line must be empty
654 os << "\n";
656 // cleanup the stream
657 os.flags(oldflags);
658 os.fill(fill);
661 bool ContactLdif::ReadLdif(std::istream &is, Barry::Contact &con)
663 std::string line;
665 // start fresh
666 con.Clear();
667 ClearHeuristics();
669 // search for beginning dn: line
670 bool found = false;
671 while( std::getline(is, line) ) {
672 if( strncmp(line.c_str(), "dn: ", 4) == 0 ) {
673 found = true;
674 break;
677 if( !found )
678 return false;
680 // storage for various name styles
681 std::string coded, decode, attr, data;
682 bool b64field = false;
684 // read ldif lines until empty line is found
685 while( getline(is, line) && line.size() ) {
687 if( b64field ) {
688 // processing a base64 encoded field
689 if( line[0] == ' ' ) {
690 coded += "\n";
691 coded += line;
692 continue;
694 else {
695 // end of base64 block... ignore errors,
696 // and attempt to save everything decodable...
697 // the LDAP server sometimes returns incomplete
698 // base64 encoding, but otherwise the data is fine
699 base64_decode(coded, decode);
700 DoWrite(con, attr, decode);
701 coded.clear();
702 b64field = false;
704 // fall through to process new line
708 // split into attribute / data
709 std::string::size_type delim = line.find(':'), dstart;
710 if( delim == std::string::npos )
711 continue;
713 attr.assign(line, 0, delim);
714 dstart = delim + 1;
715 while( line[dstart] == ' ' || line[dstart] == ':' )
716 dstart++;
717 data = line.substr(dstart);
719 // is this data base64 encoded?
720 if( line[delim + 1] == ':' ) {
721 coded = data;
722 b64field = true;
723 continue;
726 DoWrite(con, attr, data);
729 if( b64field ) {
730 // clean up base64 decoding... ignore errors, see above comment
731 base64_decode(coded, decode);
732 DoWrite(con, attr, decode);
733 coded.clear();
734 b64field = false;
737 return RunHeuristics(con);
740 void ContactLdif::DumpMap(std::ostream &os) const
742 std::ios::fmtflags oldflags = os.setf(std::ios::left);
743 char fill = os.fill(' ');
745 os << "ContactLdif Mapping:\n";
747 // cycle through the map
748 for( AccessMapType::const_iterator b = m_map.begin();
749 b != m_map.end();
750 ++b )
752 os << " " << std::left << std::setw(20) << b->first.name
753 << "-> " << GetFieldReadName(b->second.read)
754 << " / " << GetFieldWriteName(b->second.write) << "\n";
756 // find read/write names
758 if( b->first.objectClass.size() ) {
759 os << " " << std::setw(20) << " "
760 << "objectClass: " << b->first.objectClass << "\n";
764 os << " >>> DN attribute: " << m_dnAttr.name << "\n";
766 // cleanup the stream
767 os.flags(oldflags);
768 os.fill(fill);
771 std::string ContactLdif::MakeLdifData(const std::string &str)
773 std::string data = ":";
775 if( NeedsEncoding(str) ) {
776 std::string b64;
777 base64_encode(str, b64);
779 data += ": ";
780 data += b64;
782 else {
783 data += " ";
784 data += str;
787 return data;
791 // RFC 2849
793 // Must not contain:
794 // 0x00 (NUL), 0x0a (LF), 0x0d (CR), or anything greater than 0x7f
796 // First char must meet above criteria, plus must not be:
797 // 0x20 (SPACE), 0x3a (colon), 0x3c ('<')
799 bool ContactLdif::NeedsEncoding(const std::string &str)
801 for( std::string::size_type i = 0; i < str.size(); i++ ) {
802 unsigned char c = str[i];
804 switch( c )
806 case 0x00:
807 case 0x0a:
808 case 0x0d:
809 return true;
811 case 0x20:
812 case 0x3a:
813 case 0x3c:
814 if( i == 0 )
815 return true;
818 if( c > 0x7f )
819 return true;
821 return false;
824 } // namespace Barry