rpm/barry.spec file now assumes gui and opensync, with conditional checks
[barry/pauldeden.git] / src / ldif.cc
blob55ea631aaa0e3cf0ba6724e60360dbb06c051414
1 ///
2 /// \file ldif.cc
3 /// Routines for reading and writing LDAP LDIF data.
4 ///
6 /*
7 Copyright (C) 2005-2008, 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 "base64.h"
25 #include <stdexcept>
26 #include <iostream>
27 #include <iomanip>
28 #include <string.h>
30 #define __DEBUG_MODE__
31 #include "debug.h"
33 namespace Barry {
35 const ContactLdif::NameToFunc ContactLdif::FieldMap[] = {
36 { "Email", "Email address",
37 &ContactLdif::Email, &ContactLdif::SetEmail },
38 { "Phone", "Phone number",
39 &ContactLdif::Phone, &ContactLdif::SetPhone },
40 { "Fax", "Fax number",
41 &ContactLdif::Fax, &ContactLdif::SetFax },
42 { "WorkPhone", "Work phone number",
43 &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone },
44 { "HomePhone", "Home phone number",
45 &ContactLdif::HomePhone, &ContactLdif::SetHomePhone },
46 { "MobilePhone", "Mobile phone number",
47 &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone },
48 { "Pager", "Pager number",
49 &ContactLdif::Pager, &ContactLdif::SetPager },
50 { "PIN", "PIN",
51 &ContactLdif::PIN, &ContactLdif::SetPIN },
52 { "FirstName", "First name",
53 &ContactLdif::FirstName, &ContactLdif::SetFirstName },
54 { "LastName", "Last name",
55 &ContactLdif::LastName, &ContactLdif::SetLastName },
56 { "Company", "Company name",
57 &ContactLdif::Company, &ContactLdif::SetCompany },
58 { "DefaultCommunicationsMethod", "Default communications method",
59 &ContactLdif::DefaultCommunicationsMethod, &ContactLdif::SetDefaultCommunicationsMethod },
60 { "Address1", "Address, line 1",
61 &ContactLdif::Address1, &ContactLdif::SetAddress1 },
62 { "Address2", "Address, line 2",
63 &ContactLdif::Address2, &ContactLdif::SetAddress2 },
64 { "Address3", "Address, line 3",
65 &ContactLdif::Address3, &ContactLdif::SetAddress3 },
66 { "City", "City",
67 &ContactLdif::City, &ContactLdif::SetCity },
68 { "Province", "Province / State",
69 &ContactLdif::Province, &ContactLdif::SetProvince },
70 { "PostalCode", "Postal / ZIP code",
71 &ContactLdif::PostalCode, &ContactLdif::SetPostalCode },
72 { "Country", "Country",
73 &ContactLdif::Country, &ContactLdif::SetCountry },
74 { "JobTitle", "Job Title",
75 &ContactLdif::JobTitle, &ContactLdif::SetJobTitle },
76 { "PublicKey", "Public key",
77 &ContactLdif::PublicKey, &ContactLdif::SetPublicKey },
78 { "Notes", "Notes",
79 &ContactLdif::Notes, &ContactLdif::SetNotes },
80 { "PostalAddress", "Mailing address (includes address lines, city, province, country, and postal code)",
81 &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress },
82 { "FullName", "First + Last names",
83 &ContactLdif::FullName, &ContactLdif::SetFullName },
84 { "FQDN", "Fully qualified domain name",
85 &ContactLdif::FQDN, &ContactLdif::SetFQDN },
86 { 0, 0, 0 }
90 bool ContactLdif::LdifAttribute::operator<(const LdifAttribute &other) const
92 // the dn attribute always comes first in LDIF output
93 if( name == "dn" ) {
94 if( other.name == "dn" )
95 return false; // both dn, so equal
96 return true;
98 else if( other.name == "dn" )
99 return false;
101 return (order < other.order && name != other.name) ||
102 (order == other.order && name < other.name);
105 bool ContactLdif::LdifAttribute::operator==(const LdifAttribute &other) const
107 return name == other.name;
111 ///////////////////////////////////////////////////////////////////////////////
112 // ContactLdif class
114 ContactLdif::ContactLdif(const std::string &baseDN)
115 : m_baseDN(baseDN)
117 // setup some sane defaults
118 Map("mail", &ContactLdif::Email, &ContactLdif::SetEmail);
119 Map("facsimileTelephoneNumber", &ContactLdif::Fax, &ContactLdif::SetFax);
120 Map("telephoneNumber", &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone);
121 Map("homePhone", &ContactLdif::HomePhone, &ContactLdif::SetHomePhone);
122 Map("mobile", &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone);
123 Map("pager", &ContactLdif::Pager, &ContactLdif::SetPager);
124 Map("l", &ContactLdif::City, &ContactLdif::SetCity);
125 Map("st", &ContactLdif::Province, &ContactLdif::SetProvince);
126 Map("postalCode", &ContactLdif::PostalCode, &ContactLdif::SetPostalCode);
127 Map("o", &ContactLdif::Company, &ContactLdif::SetCompany);
128 Map("c", &ContactLdif::Country, &ContactLdif::SetCountry);
129 SetObjectClass("c", "country");
131 Map("title", &ContactLdif::JobTitle, &ContactLdif::SetJobTitle);
132 Map("dn", &ContactLdif::FQDN, &ContactLdif::SetFQDN);
133 Map("displayName", &ContactLdif::FullName, &ContactLdif::SetFullName);
134 Map("cn", &ContactLdif::FullName, &ContactLdif::SetFullName);
135 Map("sn", &ContactLdif::LastName, &ContactLdif::SetLastName);
136 Map("givenName", &ContactLdif::FirstName, &ContactLdif::SetFirstName);
137 Map("street", &ContactLdif::Address1, &ContactLdif::SetAddress1);
138 Map("postalAddress", &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress);
139 Map("note", &ContactLdif::Notes, &ContactLdif::SetNotes);
141 // add heuristics hooks
142 Hook("cn", &m_cn);
143 Hook("displayName", &m_displayName);
144 Hook("sn", &m_sn);
145 Hook("givenName", &m_givenName);
147 // set default DN attribute
148 SetDNAttr("cn");
151 ContactLdif::~ContactLdif()
155 void ContactLdif::DoWrite(Barry::Contact &con,
156 const std::string &attr,
157 const std::string &data)
159 // valid?
160 if( attr.size() == 0 || data.size() == 0 )
161 return;
163 // now have attr/data pair, check hooks:
164 HookMapType::iterator hook = m_hookMap.find(attr);
165 if( hook != m_hookMap.end() ) {
166 *(hook->second) = data;
169 // run according to map
170 AccessMapType::iterator acc = m_map.find(attr);
171 if( acc != m_map.end() ) {
172 (this->*(acc->second.write))(con, data);
176 void ContactLdif::Hook(const std::string &ldifname, std::string *var)
178 m_hookMap[ldifname] = var;
181 const ContactLdif::NameToFunc*
182 ContactLdif::GetField(const std::string &fieldname) const
184 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
185 if( fieldname == n->name )
186 return n;
188 return 0;
191 std::string ContactLdif::GetFieldReadName(GetFunctionType read) const
193 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
194 if( read == n->read )
195 return n->name;
197 return "<unknown>";
200 std::string ContactLdif::GetFieldWriteName(SetFunctionType write) const
202 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
203 if( write == n->write )
204 return n->name;
206 return "<unknown>";
209 bool ContactLdif::Map(const LdifAttribute &ldifname,
210 const std::string &readField,
211 const std::string &writeField)
213 const NameToFunc *read = GetField(readField);
214 const NameToFunc *write = GetField(writeField);
215 if( !read || !write )
216 return false;
217 Map(ldifname, read->read, write->write);
218 return true;
221 void ContactLdif::Map(const LdifAttribute &ldifname,
222 GetFunctionType read,
223 SetFunctionType write)
225 m_map[ldifname] = AccessPair(read, write);
228 void ContactLdif::Unmap(const LdifAttribute &ldifname)
230 m_map.erase(ldifname);
234 // SetDNAttr
236 /// Sets the LDIF attribute name to use when constructing the FQDN.
237 /// The FQDN field will take this name, and combine it with the
238 /// baseDN from the constructor to produce a FQDN for the record.
240 bool ContactLdif::SetDNAttr(const LdifAttribute &name)
242 // try to find the attribute in the map
243 AccessMapType::iterator i = m_map.find(name);
244 if( i == m_map.end() )
245 return false;
247 m_dnAttr = name;
248 return true;
251 bool ContactLdif::SetObjectClass(const LdifAttribute &name,
252 const std::string &objectClass)
254 AccessMapType::iterator i = m_map.find(name);
255 if( i == m_map.end() )
256 return false;
258 LdifAttribute key = i->first;
259 AccessPair pair = i->second;
260 m_map.erase(key);
261 key.objectClass = objectClass;
262 m_map[key] = pair;
263 return true;
266 bool ContactLdif::SetObjectOrder(const LdifAttribute &name, int order)
268 AccessMapType::iterator i = m_map.find(name);
269 if( i == m_map.end() )
270 return false;
272 LdifAttribute key = i->first;
273 AccessPair pair = i->second;
274 m_map.erase(key);
275 key.order = order;
276 m_map[key] = pair;
277 return true;
281 std::string ContactLdif::Email(const Barry::Contact &con) const
283 return con.Email;
286 std::string ContactLdif::Phone(const Barry::Contact &con) const
288 return con.Phone;
291 std::string ContactLdif::Fax(const Barry::Contact &con) const
293 return con.Fax;
296 std::string ContactLdif::WorkPhone(const Barry::Contact &con) const
298 return con.WorkPhone;
301 std::string ContactLdif::HomePhone(const Barry::Contact &con) const
303 return con.HomePhone;
306 std::string ContactLdif::MobilePhone(const Barry::Contact &con) const
308 return con.MobilePhone;
311 std::string ContactLdif::Pager(const Barry::Contact &con) const
313 return con.Pager;
316 std::string ContactLdif::PIN(const Barry::Contact &con) const
318 return con.PIN;
321 std::string ContactLdif::FirstName(const Barry::Contact &con) const
323 return con.FirstName;
326 std::string ContactLdif::LastName(const Barry::Contact &con) const
328 return con.LastName;
331 std::string ContactLdif::Company(const Barry::Contact &con) const
333 return con.Company;
336 std::string ContactLdif::DefaultCommunicationsMethod(const Barry::Contact &con) const
338 return con.DefaultCommunicationsMethod;
341 std::string ContactLdif::Address1(const Barry::Contact &con) const
343 return con.WorkAddress.Address1;
346 std::string ContactLdif::Address2(const Barry::Contact &con) const
348 return con.WorkAddress.Address2;
351 std::string ContactLdif::Address3(const Barry::Contact &con) const
353 return con.WorkAddress.Address3;
356 std::string ContactLdif::City(const Barry::Contact &con) const
358 return con.WorkAddress.City;
361 std::string ContactLdif::Province(const Barry::Contact &con) const
363 return con.WorkAddress.Province;
366 std::string ContactLdif::PostalCode(const Barry::Contact &con) const
368 return con.WorkAddress.PostalCode;
371 std::string ContactLdif::Country(const Barry::Contact &con) const
373 return con.WorkAddress.Country;
376 std::string ContactLdif::JobTitle(const Barry::Contact &con) const
378 return con.JobTitle;
381 std::string ContactLdif::PublicKey(const Barry::Contact &con) const
383 return con.PublicKey;
386 std::string ContactLdif::Notes(const Barry::Contact &con) const
388 return con.Notes;
391 std::string ContactLdif::PostalAddress(const Barry::Contact &con) const
393 return con.WorkAddress.GetLabel();
396 std::string ContactLdif::FullName(const Barry::Contact &con) const
398 return con.GetFullName();
401 std::string ContactLdif::FQDN(const Barry::Contact &con) const
403 std::string FQDN = m_dnAttr.name;
404 FQDN += "=";
406 AccessMapType::const_iterator i = m_map.find(m_dnAttr);
407 if( i != m_map.end() ) {
408 FQDN += (this->*(i->second.read))(con);
410 else {
411 FQDN += "unknown";
414 FQDN += ",";
415 FQDN += m_baseDN;
416 return FQDN;
419 void ContactLdif::SetEmail(Barry::Contact &con, const std::string &val) const
421 con.Email = val;
424 void ContactLdif::SetPhone(Barry::Contact &con, const std::string &val) const
426 con.Phone = val;
429 void ContactLdif::SetFax(Barry::Contact &con, const std::string &val) const
431 con.Fax = val;
434 void ContactLdif::SetWorkPhone(Barry::Contact &con, const std::string &val) const
436 con.WorkPhone = val;
439 void ContactLdif::SetHomePhone(Barry::Contact &con, const std::string &val) const
441 con.HomePhone = val;
444 void ContactLdif::SetMobilePhone(Barry::Contact &con, const std::string &val) const
446 con.MobilePhone = val;
449 void ContactLdif::SetPager(Barry::Contact &con, const std::string &val) const
451 con.Pager = val;
454 void ContactLdif::SetPIN(Barry::Contact &con, const std::string &val) const
456 con.PIN = val;
459 void ContactLdif::SetFirstName(Barry::Contact &con, const std::string &val) const
461 con.FirstName = val;
464 void ContactLdif::SetLastName(Barry::Contact &con, const std::string &val) const
466 con.LastName = val;
469 void ContactLdif::SetCompany(Barry::Contact &con, const std::string &val) const
471 con.Company = val;
474 void ContactLdif::SetDefaultCommunicationsMethod(Barry::Contact &con, const std::string &val) const
476 con.DefaultCommunicationsMethod = val;
479 void ContactLdif::SetAddress1(Barry::Contact &con, const std::string &val) const
481 con.WorkAddress.Address1 = val;
484 void ContactLdif::SetAddress2(Barry::Contact &con, const std::string &val) const
486 con.WorkAddress.Address2 = val;
489 void ContactLdif::SetAddress3(Barry::Contact &con, const std::string &val) const
491 con.WorkAddress.Address3 = val;
494 void ContactLdif::SetCity(Barry::Contact &con, const std::string &val) const
496 con.WorkAddress.City = val;
499 void ContactLdif::SetProvince(Barry::Contact &con, const std::string &val) const
501 con.WorkAddress.Province = val;
504 void ContactLdif::SetPostalCode(Barry::Contact &con, const std::string &val) const
506 con.WorkAddress.PostalCode = val;
509 void ContactLdif::SetCountry(Barry::Contact &con, const std::string &val) const
511 con.WorkAddress.Country = val;
514 void ContactLdif::SetJobTitle(Barry::Contact &con, const std::string &val) const
516 con.JobTitle = val;
519 void ContactLdif::SetPublicKey(Barry::Contact &con, const std::string &val) const
521 con.PublicKey = val;
524 void ContactLdif::SetNotes(Barry::Contact &con, const std::string &val) const
526 con.Notes = val;
529 void ContactLdif::SetPostalAddress(Barry::Contact &con, const std::string &val) const
531 // fixme;
532 // throw std::runtime_error("SetPostalAddress() not implemented");
533 // std::cout << "SetPostalAddress() not implemented: " << val << std::endl;
536 void ContactLdif::SetFullName(Barry::Contact &con, const std::string &val) const
538 std::string first, last;
539 Contact::SplitName(val, first, last);
540 con.FirstName = first;
541 con.LastName = last;
544 void ContactLdif::SetFQDN(Barry::Contact &con, const std::string &val) const
546 throw std::runtime_error("not implemented");
550 void ContactLdif::ClearHeuristics()
552 m_cn.clear();
553 m_displayName.clear();
554 m_sn.clear();
555 m_givenName.clear();
558 bool ContactLdif::RunHeuristics(Barry::Contact &con)
560 // start fresh
561 con.LastName.clear();
562 con.FirstName.clear();
564 // find the best match for name... prefer sn/givenName if available
565 if( m_sn.size() ) {
566 con.LastName = m_sn;
568 if( m_givenName.size() ) {
569 con.FirstName = m_givenName;
572 if( !con.LastName.size() || !con.FirstName.size() ) {
573 std::string first, last;
575 // still don't have a complete name, check cn first
576 if( m_cn.size() ) {
577 Contact::SplitName(m_cn, first, last);
578 if( !con.LastName.size() && last.size() )
579 con.LastName = last;
580 if( !con.FirstName.size() && first.size() )
581 con.FirstName = first;
584 // displayName is last chance
585 if( m_displayName.size() ) {
586 Contact::SplitName(m_displayName, first, last);
587 if( !con.LastName.size() && last.size() )
588 con.LastName = last;
589 if( !con.FirstName.size() && first.size() )
590 con.FirstName = first;
594 return con.LastName.size() && con.FirstName.size();
599 // DumpLdif
601 /// Output contact data to os in LDAP LDIF format.
603 void ContactLdif::DumpLdif(std::ostream &os,
604 const Barry::Contact &con) const
606 std::ios::fmtflags oldflags = os.setf(std::ios::left);
607 char fill = os.fill(' ');
609 if( FirstName(con).size() == 0 && LastName(con).size() == 0 )
610 return; // nothing to do
612 os << "# Contact 0x" << std::hex << con.GetID() << ", "
613 << FullName(con) << "\n";
615 // cycle through the map
616 for( AccessMapType::const_iterator b = m_map.begin();
617 b != m_map.end();
618 ++b )
620 // print only fields with data
621 const std::string field = (this->*(b->second.read))(con);
622 if( field.size() ) {
623 os << b->first.name << MakeLdifData(field) << "\n";
624 if( b->first.objectClass.size() )
625 os << "objectClass: " << b->first.objectClass << "\n";
629 os << "objectClass: inetOrgPerson\n";
631 // last line must be empty
632 os << "\n";
634 // cleanup the stream
635 os.flags(oldflags);
636 os.fill(fill);
639 bool ContactLdif::ReadLdif(std::istream &is, Barry::Contact &con)
641 std::string line;
643 // start fresh
644 con.Clear();
645 ClearHeuristics();
647 // search for beginning dn: line
648 bool found = false;
649 while( std::getline(is, line) ) {
650 if( strncmp(line.c_str(), "dn: ", 4) == 0 ) {
651 found = true;
652 break;
655 if( !found )
656 return false;
658 // storage for various name styles
659 std::string coded, decode, attr, data;
660 bool b64field = false;
662 // read ldif lines until empty line is found
663 while( getline(is, line) && line.size() ) {
665 if( b64field ) {
666 // processing a base64 encoded field
667 if( line[0] == ' ' ) {
668 coded += "\n";
669 coded += line;
670 continue;
672 else {
673 // end of base64 block... ignore errors,
674 // and attempt to save everything decodable...
675 // the LDAP server sometimes returns incomplete
676 // base64 encoding, but otherwise the data is fine
677 base64_decode(coded, decode);
678 DoWrite(con, attr, decode);
679 coded.clear();
680 b64field = false;
682 // fall through to process new line
686 // split into attribute / data
687 std::string::size_type delim = line.find(':'), dstart;
688 if( delim == std::string::npos )
689 continue;
691 attr.assign(line, 0, delim);
692 dstart = delim + 1;
693 while( line[dstart] == ' ' || line[dstart] == ':' )
694 dstart++;
695 data = line.substr(dstart);
697 // is this data base64 encoded?
698 if( line[delim + 1] == ':' ) {
699 coded = data;
700 b64field = true;
701 continue;
704 DoWrite(con, attr, data);
707 if( b64field ) {
708 // clean up base64 decoding... ignore errors, see above comment
709 base64_decode(coded, decode);
710 DoWrite(con, attr, decode);
711 coded.clear();
712 b64field = false;
715 return RunHeuristics(con);
718 void ContactLdif::DumpMap(std::ostream &os) const
720 std::ios::fmtflags oldflags = os.setf(std::ios::left);
721 char fill = os.fill(' ');
723 os << "ContactLdif Mapping:\n";
725 // cycle through the map
726 for( AccessMapType::const_iterator b = m_map.begin();
727 b != m_map.end();
728 ++b )
730 os << " " << std::left << std::setw(20) << b->first.name
731 << "-> " << GetFieldReadName(b->second.read)
732 << " / " << GetFieldWriteName(b->second.write) << "\n";
734 // find read/write names
736 if( b->first.objectClass.size() ) {
737 os << " " << std::setw(20) << " "
738 << "objectClass: " << b->first.objectClass << "\n";
742 os << " >>> DN attribute: " << m_dnAttr.name << "\n";
744 // cleanup the stream
745 os.flags(oldflags);
746 os.fill(fill);
749 std::string ContactLdif::MakeLdifData(const std::string &str)
751 std::string data = ":";
753 if( NeedsEncoding(str) ) {
754 std::string b64;
755 base64_encode(str, b64);
757 data += ": ";
758 data += b64;
760 else {
761 data += " ";
762 data += str;
765 return data;
769 // RFC 2849
771 // Must not contain:
772 // 0x00 (NUL), 0x0a (LF), 0x0d (CR), or anything greater than 0x7f
774 // First char must meet above criteria, plus must not be:
775 // 0x20 (SPACE), 0x3a (colon), 0x3c ('<')
777 bool ContactLdif::NeedsEncoding(const std::string &str)
779 for( std::string::size_type i = 0; i < str.size(); i++ ) {
780 unsigned char c = str[i];
782 switch( c )
784 case 0x00:
785 case 0x0a:
786 case 0x0d:
787 return true;
789 case 0x20:
790 case 0x3a:
791 case 0x3c:
792 if( i == 0 )
793 return true;
796 if( c > 0x7f )
797 return true;
799 return false;
802 } // namespace Barry