- added item to gui's todo list
[barry.git] / src / ldif.cc
blobbb5a36cf34db67597898f54688aa80a52858431c
1 ///
2 /// \file ldif.cc
3 /// Routines for reading and writing LDAP LDIF data.
4 ///
6 /*
7 Copyright (C) 2005-2007, 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>
29 #define __DEBUG_MODE__
30 #include "debug.h"
32 namespace Barry {
34 const ContactLdif::NameToFunc ContactLdif::FieldMap[] = {
35 { "Email", "Email address",
36 &ContactLdif::Email, &ContactLdif::SetEmail },
37 { "Phone", "Phone number",
38 &ContactLdif::Phone, &ContactLdif::SetPhone },
39 { "Fax", "Fax number",
40 &ContactLdif::Fax, &ContactLdif::SetFax },
41 { "WorkPhone", "Work phone number",
42 &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone },
43 { "HomePhone", "Home phone number",
44 &ContactLdif::HomePhone, &ContactLdif::SetHomePhone },
45 { "MobilePhone", "Mobile phone number",
46 &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone },
47 { "Pager", "Pager number",
48 &ContactLdif::Pager, &ContactLdif::SetPager },
49 { "PIN", "PIN",
50 &ContactLdif::PIN, &ContactLdif::SetPIN },
51 { "FirstName", "First name",
52 &ContactLdif::FirstName, &ContactLdif::SetFirstName },
53 { "LastName", "Last name",
54 &ContactLdif::LastName, &ContactLdif::SetLastName },
55 { "Company", "Company name",
56 &ContactLdif::Company, &ContactLdif::SetCompany },
57 { "DefaultCommunicationsMethod", "Default communications method",
58 &ContactLdif::DefaultCommunicationsMethod, &ContactLdif::SetDefaultCommunicationsMethod },
59 { "Address1", "Address, line 1",
60 &ContactLdif::Address1, &ContactLdif::SetAddress1 },
61 { "Address2", "Address, line 2",
62 &ContactLdif::Address2, &ContactLdif::SetAddress2 },
63 { "Address3", "Address, line 3",
64 &ContactLdif::Address3, &ContactLdif::SetAddress3 },
65 { "City", "City",
66 &ContactLdif::City, &ContactLdif::SetCity },
67 { "Province", "Province / State",
68 &ContactLdif::Province, &ContactLdif::SetProvince },
69 { "PostalCode", "Postal / ZIP code",
70 &ContactLdif::PostalCode, &ContactLdif::SetPostalCode },
71 { "Country", "Country",
72 &ContactLdif::Country, &ContactLdif::SetCountry },
73 { "Title", "Title",
74 &ContactLdif::Title, &ContactLdif::SetTitle },
75 { "PublicKey", "Public key",
76 &ContactLdif::PublicKey, &ContactLdif::SetPublicKey },
77 { "Notes", "Notes",
78 &ContactLdif::Notes, &ContactLdif::SetNotes },
79 { "PostalAddress", "Mailing address (includes address lines, city, province, country, and postal code)",
80 &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress },
81 { "FullName", "First + Last names",
82 &ContactLdif::FullName, &ContactLdif::SetFullName },
83 { "FQDN", "Fully qualified domain name",
84 &ContactLdif::FQDN, &ContactLdif::SetFQDN },
85 { 0, 0, 0 }
89 bool ContactLdif::LdifAttribute::operator<(const LdifAttribute &other) const
91 // the dn attribute always comes first in LDIF output
92 if( name == "dn" ) {
93 if( other.name == "dn" )
94 return false; // both dn, so equal
95 return true;
97 else if( other.name == "dn" )
98 return false;
100 return (order < other.order && name != other.name) ||
101 (order == other.order && name < other.name);
104 bool ContactLdif::LdifAttribute::operator==(const LdifAttribute &other) const
106 return name == other.name;
110 ///////////////////////////////////////////////////////////////////////////////
111 // ContactLdif class
113 ContactLdif::ContactLdif(const std::string &baseDN)
114 : m_baseDN(baseDN)
116 // setup some sane defaults
117 Map("mail", &ContactLdif::Email, &ContactLdif::SetEmail);
118 Map("facsimileTelephoneNumber", &ContactLdif::Fax, &ContactLdif::SetFax);
119 Map("telephoneNumber", &ContactLdif::WorkPhone, &ContactLdif::SetWorkPhone);
120 Map("homePhone", &ContactLdif::HomePhone, &ContactLdif::SetHomePhone);
121 Map("mobile", &ContactLdif::MobilePhone, &ContactLdif::SetMobilePhone);
122 Map("pager", &ContactLdif::Pager, &ContactLdif::SetPager);
123 Map("l", &ContactLdif::City, &ContactLdif::SetCity);
124 Map("st", &ContactLdif::Province, &ContactLdif::SetProvince);
125 Map("postalCode", &ContactLdif::PostalCode, &ContactLdif::SetPostalCode);
126 Map("o", &ContactLdif::Company, &ContactLdif::SetCompany);
127 Map("c", &ContactLdif::Country, &ContactLdif::SetCountry);
128 SetObjectClass("c", "country");
130 Map("title", &ContactLdif::Title, &ContactLdif::SetTitle);
131 Map("dn", &ContactLdif::FQDN, &ContactLdif::SetFQDN);
132 Map("displayName", &ContactLdif::FullName, &ContactLdif::SetFullName);
133 Map("cn", &ContactLdif::FullName, &ContactLdif::SetFullName);
134 Map("sn", &ContactLdif::LastName, &ContactLdif::SetLastName);
135 Map("givenName", &ContactLdif::FirstName, &ContactLdif::SetFirstName);
136 Map("street", &ContactLdif::Address1, &ContactLdif::SetAddress1);
137 Map("postalAddress", &ContactLdif::PostalAddress, &ContactLdif::SetPostalAddress);
138 Map("note", &ContactLdif::Notes, &ContactLdif::SetNotes);
140 // add heuristics hooks
141 Hook("cn", &m_cn);
142 Hook("displayName", &m_displayName);
143 Hook("sn", &m_sn);
144 Hook("givenName", &m_givenName);
146 // set default DN attribute
147 SetDNAttr("cn");
150 ContactLdif::~ContactLdif()
154 void ContactLdif::DoWrite(Barry::Contact &con,
155 const std::string &attr,
156 const std::string &data)
158 // valid?
159 if( attr.size() == 0 || data.size() == 0 )
160 return;
162 // now have attr/data pair, check hooks:
163 HookMapType::iterator hook = m_hookMap.find(attr);
164 if( hook != m_hookMap.end() ) {
165 *(hook->second) = data;
168 // run according to map
169 AccessMapType::iterator acc = m_map.find(attr);
170 if( acc != m_map.end() ) {
171 (this->*(acc->second.write))(con, data);
175 void ContactLdif::Hook(const std::string &ldifname, std::string *var)
177 m_hookMap[ldifname] = var;
180 const ContactLdif::NameToFunc*
181 ContactLdif::GetField(const std::string &fieldname) const
183 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
184 if( fieldname == n->name )
185 return n;
187 return 0;
190 std::string ContactLdif::GetFieldReadName(GetFunctionType read) const
192 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
193 if( read == n->read )
194 return n->name;
196 return "<unknown>";
199 std::string ContactLdif::GetFieldWriteName(SetFunctionType write) const
201 for( const NameToFunc *n = FieldMap; n->name; n++ ) {
202 if( write == n->write )
203 return n->name;
205 return "<unknown>";
208 bool ContactLdif::Map(const LdifAttribute &ldifname,
209 const std::string &readField,
210 const std::string &writeField)
212 const NameToFunc *read = GetField(readField);
213 const NameToFunc *write = GetField(writeField);
214 if( !read || !write )
215 return false;
216 Map(ldifname, read->read, write->write);
217 return true;
220 void ContactLdif::Map(const LdifAttribute &ldifname,
221 GetFunctionType read,
222 SetFunctionType write)
224 m_map[ldifname] = AccessPair(read, write);
227 void ContactLdif::Unmap(const LdifAttribute &ldifname)
229 m_map.erase(ldifname);
233 // SetDNAttr
235 /// Sets the LDIF attribute name to use when constructing the FQDN.
236 /// The FQDN field will take this name, and combine it with the
237 /// baseDN from the constructor to produce a FQDN for the record.
239 bool ContactLdif::SetDNAttr(const LdifAttribute &name)
241 // try to find the attribute in the map
242 AccessMapType::iterator i = m_map.find(name);
243 if( i == m_map.end() )
244 return false;
246 m_dnAttr = name;
247 return true;
250 bool ContactLdif::SetObjectClass(const LdifAttribute &name,
251 const std::string &objectClass)
253 AccessMapType::iterator i = m_map.find(name);
254 if( i == m_map.end() )
255 return false;
257 LdifAttribute key = i->first;
258 AccessPair pair = i->second;
259 m_map.erase(key);
260 key.objectClass = objectClass;
261 m_map[key] = pair;
262 return true;
265 bool ContactLdif::SetObjectOrder(const LdifAttribute &name, int order)
267 AccessMapType::iterator i = m_map.find(name);
268 if( i == m_map.end() )
269 return false;
271 LdifAttribute key = i->first;
272 AccessPair pair = i->second;
273 m_map.erase(key);
274 key.order = order;
275 m_map[key] = pair;
276 return true;
280 std::string ContactLdif::Email(const Barry::Contact &con) const
282 return con.Email;
285 std::string ContactLdif::Phone(const Barry::Contact &con) const
287 return con.Phone;
290 std::string ContactLdif::Fax(const Barry::Contact &con) const
292 return con.Fax;
295 std::string ContactLdif::WorkPhone(const Barry::Contact &con) const
297 return con.WorkPhone;
300 std::string ContactLdif::HomePhone(const Barry::Contact &con) const
302 return con.HomePhone;
305 std::string ContactLdif::MobilePhone(const Barry::Contact &con) const
307 return con.MobilePhone;
310 std::string ContactLdif::Pager(const Barry::Contact &con) const
312 return con.Pager;
315 std::string ContactLdif::PIN(const Barry::Contact &con) const
317 return con.PIN;
320 std::string ContactLdif::FirstName(const Barry::Contact &con) const
322 return con.FirstName;
325 std::string ContactLdif::LastName(const Barry::Contact &con) const
327 return con.LastName;
330 std::string ContactLdif::Company(const Barry::Contact &con) const
332 return con.Company;
335 std::string ContactLdif::DefaultCommunicationsMethod(const Barry::Contact &con) const
337 return con.DefaultCommunicationsMethod;
340 std::string ContactLdif::Address1(const Barry::Contact &con) const
342 return con.Address1;
345 std::string ContactLdif::Address2(const Barry::Contact &con) const
347 return con.Address2;
350 std::string ContactLdif::Address3(const Barry::Contact &con) const
352 return con.Address3;
355 std::string ContactLdif::City(const Barry::Contact &con) const
357 return con.City;
360 std::string ContactLdif::Province(const Barry::Contact &con) const
362 return con.Province;
365 std::string ContactLdif::PostalCode(const Barry::Contact &con) const
367 return con.PostalCode;
370 std::string ContactLdif::Country(const Barry::Contact &con) const
372 return con.Country;
375 std::string ContactLdif::Title(const Barry::Contact &con) const
377 return con.Title;
380 std::string ContactLdif::PublicKey(const Barry::Contact &con) const
382 return con.PublicKey;
385 std::string ContactLdif::Notes(const Barry::Contact &con) const
387 return con.Notes;
390 std::string ContactLdif::PostalAddress(const Barry::Contact &con) const
392 return con.GetPostalAddress();
395 std::string ContactLdif::FullName(const Barry::Contact &con) const
397 std::string Full = con.FirstName;
398 if( Full.size() && con.LastName.size() )
399 Full += " ";
400 Full += con.LastName;
401 return Full;
404 std::string ContactLdif::FQDN(const Barry::Contact &con) const
406 std::string FQDN = m_dnAttr.name;
407 FQDN += "=";
409 AccessMapType::const_iterator i = m_map.find(m_dnAttr);
410 if( i != m_map.end() ) {
411 FQDN += (this->*(i->second.read))(con);
413 else {
414 FQDN += "unknown";
417 FQDN += ",";
418 FQDN += m_baseDN;
419 return FQDN;
422 void ContactLdif::SetEmail(Barry::Contact &con, const std::string &val) const
424 con.Email = val;
427 void ContactLdif::SetPhone(Barry::Contact &con, const std::string &val) const
429 con.Phone = val;
432 void ContactLdif::SetFax(Barry::Contact &con, const std::string &val) const
434 con.Fax = val;
437 void ContactLdif::SetWorkPhone(Barry::Contact &con, const std::string &val) const
439 con.WorkPhone = val;
442 void ContactLdif::SetHomePhone(Barry::Contact &con, const std::string &val) const
444 con.HomePhone = val;
447 void ContactLdif::SetMobilePhone(Barry::Contact &con, const std::string &val) const
449 con.MobilePhone = val;
452 void ContactLdif::SetPager(Barry::Contact &con, const std::string &val) const
454 con.Pager = val;
457 void ContactLdif::SetPIN(Barry::Contact &con, const std::string &val) const
459 con.PIN = val;
462 void ContactLdif::SetFirstName(Barry::Contact &con, const std::string &val) const
464 con.FirstName = val;
467 void ContactLdif::SetLastName(Barry::Contact &con, const std::string &val) const
469 con.LastName = val;
472 void ContactLdif::SetCompany(Barry::Contact &con, const std::string &val) const
474 con.Company = val;
477 void ContactLdif::SetDefaultCommunicationsMethod(Barry::Contact &con, const std::string &val) const
479 con.DefaultCommunicationsMethod = val;
482 void ContactLdif::SetAddress1(Barry::Contact &con, const std::string &val) const
484 con.Address1 = val;
487 void ContactLdif::SetAddress2(Barry::Contact &con, const std::string &val) const
489 con.Address2 = val;
492 void ContactLdif::SetAddress3(Barry::Contact &con, const std::string &val) const
494 con.Address3 = val;
497 void ContactLdif::SetCity(Barry::Contact &con, const std::string &val) const
499 con.City = val;
502 void ContactLdif::SetProvince(Barry::Contact &con, const std::string &val) const
504 con.Province = val;
507 void ContactLdif::SetPostalCode(Barry::Contact &con, const std::string &val) const
509 con.PostalCode = val;
512 void ContactLdif::SetCountry(Barry::Contact &con, const std::string &val) const
514 con.Country = val;
517 void ContactLdif::SetTitle(Barry::Contact &con, const std::string &val) const
519 con.Title = val;
522 void ContactLdif::SetPublicKey(Barry::Contact &con, const std::string &val) const
524 con.PublicKey = val;
527 void ContactLdif::SetNotes(Barry::Contact &con, const std::string &val) const
529 con.Notes = val;
532 void ContactLdif::SetPostalAddress(Barry::Contact &con, const std::string &val) const
534 // fixme;
535 // throw std::runtime_error("SetPostalAddress() not implemented");
536 // std::cout << "SetPostalAddress() not implemented: " << val << std::endl;
539 void ContactLdif::SetFullName(Barry::Contact &con, const std::string &val) const
541 std::string first, last;
542 Contact::SplitName(val, first, last);
543 con.FirstName = first;
544 con.LastName = last;
547 void ContactLdif::SetFQDN(Barry::Contact &con, const std::string &val) const
549 throw std::runtime_error("not implemented");
553 void ContactLdif::ClearHeuristics()
555 m_cn.clear();
556 m_displayName.clear();
557 m_sn.clear();
558 m_givenName.clear();
561 bool ContactLdif::RunHeuristics(Barry::Contact &con)
563 // start fresh
564 con.LastName.clear();
565 con.FirstName.clear();
567 // find the best match for name... prefer sn/givenName if available
568 if( m_sn.size() ) {
569 con.LastName = m_sn;
571 if( m_givenName.size() ) {
572 con.FirstName = m_givenName;
575 if( !con.LastName.size() || !con.FirstName.size() ) {
576 std::string first, last;
578 // still don't have a complete name, check cn first
579 if( m_cn.size() ) {
580 Contact::SplitName(m_cn, first, last);
581 if( !con.LastName.size() && last.size() )
582 con.LastName = last;
583 if( !con.FirstName.size() && first.size() )
584 con.FirstName = first;
587 // displayName is last chance
588 if( m_displayName.size() ) {
589 Contact::SplitName(m_displayName, first, last);
590 if( !con.LastName.size() && last.size() )
591 con.LastName = last;
592 if( !con.FirstName.size() && first.size() )
593 con.FirstName = first;
597 return con.LastName.size() && con.FirstName.size();
602 // DumpLdif
604 /// Output contact data to os in LDAP LDIF format.
606 void ContactLdif::DumpLdif(std::ostream &os,
607 const Barry::Contact &con) const
609 std::ios::fmtflags oldflags = os.setf(std::ios::left);
610 char fill = os.fill(' ');
612 if( FirstName(con).size() == 0 && LastName(con).size() == 0 )
613 return; // nothing to do
615 os << "# Contact 0x" << std::hex << con.GetID() << ", "
616 << FullName(con) << "\n";
618 // cycle through the map
619 for( AccessMapType::const_iterator b = m_map.begin();
620 b != m_map.end();
621 ++b )
623 // print only fields with data
624 const std::string field = (this->*(b->second.read))(con);
625 if( field.size() ) {
626 os << b->first.name << MakeLdifData(field) << "\n";
627 if( b->first.objectClass.size() )
628 os << "objectClass: " << b->first.objectClass << "\n";
632 os << "objectClass: inetOrgPerson\n";
634 // last line must be empty
635 os << "\n";
637 // cleanup the stream
638 os.flags(oldflags);
639 os.fill(fill);
642 bool ContactLdif::ReadLdif(std::istream &is, Barry::Contact &con)
644 std::string line;
646 // start fresh
647 con.Clear();
648 ClearHeuristics();
650 // search for beginning dn: line
651 bool found = false;
652 while( std::getline(is, line) ) {
653 if( strncmp(line.c_str(), "dn: ", 4) == 0 ) {
654 found = true;
655 break;
658 if( !found )
659 return false;
661 // storage for various name styles
662 std::string coded, decode, attr, data;
663 bool b64field = false;
665 // read ldif lines until empty line is found
666 while( getline(is, line) && line.size() ) {
668 if( b64field ) {
669 // processing a base64 encoded field
670 if( line[0] == ' ' ) {
671 coded += "\n";
672 coded += line;
673 continue;
675 else {
676 // end of base64 block... ignore errors,
677 // and attempt to save everything decodable...
678 // the LDAP server sometimes returns incomplete
679 // base64 encoding, but otherwise the data is fine
680 base64_decode(coded, decode);
681 DoWrite(con, attr, decode);
682 coded.clear();
683 b64field = false;
685 // fall through to process new line
689 // split into attribute / data
690 std::string::size_type delim = line.find(':'), dstart;
691 if( delim == std::string::npos )
692 continue;
694 attr.assign(line, 0, delim);
695 dstart = delim + 1;
696 while( line[dstart] == ' ' || line[dstart] == ':' )
697 dstart++;
698 data = line.substr(dstart);
700 // is this data base64 encoded?
701 if( line[delim + 1] == ':' ) {
702 coded = data;
703 b64field = true;
704 continue;
707 DoWrite(con, attr, data);
710 if( b64field ) {
711 // clean up base64 decoding... ignore errors, see above comment
712 base64_decode(coded, decode);
713 DoWrite(con, attr, decode);
714 coded.clear();
715 b64field = false;
718 return RunHeuristics(con);
721 void ContactLdif::DumpMap(std::ostream &os) const
723 std::ios::fmtflags oldflags = os.setf(std::ios::left);
724 char fill = os.fill(' ');
726 os << "ContactLdif Mapping:\n";
728 // cycle through the map
729 for( AccessMapType::const_iterator b = m_map.begin();
730 b != m_map.end();
731 ++b )
733 os << " " << std::left << std::setw(20) << b->first.name
734 << "-> " << GetFieldReadName(b->second.read)
735 << " / " << GetFieldWriteName(b->second.write) << "\n";
737 // find read/write names
739 if( b->first.objectClass.size() ) {
740 os << " " << std::setw(20) << " "
741 << "objectClass: " << b->first.objectClass << "\n";
745 os << " >>> DN attribute: " << m_dnAttr.name << "\n";
747 // cleanup the stream
748 os.flags(oldflags);
749 os.fill(fill);
752 std::string ContactLdif::MakeLdifData(const std::string &str)
754 std::string data = ":";
756 if( NeedsEncoding(str) ) {
757 std::string b64;
758 base64_encode(str, b64);
760 data += ": ";
761 data += b64;
763 else {
764 data += " ";
765 data += str;
768 return data;
772 // RFC 2849
774 // Must not contain:
775 // 0x00 (NUL), 0x0a (LF), 0x0d (CR), or anything greater than 0x7f
777 // First char must meet above criteria, plus must not be:
778 // 0x20 (SPACE), 0x3a (colon), 0x3c ('<')
780 bool ContactLdif::NeedsEncoding(const std::string &str)
782 for( std::string::size_type i = 0; i < str.size(); i++ ) {
783 unsigned char c = str[i];
785 switch( c )
787 case 0x00:
788 case 0x0a:
789 case 0x0d:
790 return true;
792 case 0x20:
793 case 0x3a:
794 case 0x3c:
795 if( i == 0 )
796 return true;
799 if( c > 0x7f )
800 return true;
802 return false;
805 } // namespace Barry