1 // $OpenLDAP$
2 /*
3 * Copyright 2008-2021 The OpenLDAP Foundation, All Rights Reserved.
4 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
5 */
6
7 #include "LdifReader.h"
8 #include "LDAPMessage.h"
9 #include "LDAPEntry.h"
10 #include "LDAPAttributeList.h"
11 #include "LDAPAttribute.h"
12 #include "LDAPUrl.h"
13 #include "debug.h"
14
15 #include <string>
16 #include <sstream>
17 #include <stdexcept>
18
19 #include <sasl/saslutil.h> // For base64 routines
20
21 typedef std::pair<std::string, std::string> stringpair;
22
LdifReader(std::istream & input)23 LdifReader::LdifReader( std::istream &input )
24 : m_ldifstream(input), m_lineNumber(0)
25 {
26 DEBUG(LDAP_DEBUG_TRACE, "<> LdifReader::LdifReader()" << std::endl);
27 this->m_version = 0;
28 // read the first record to find out version and type of the LDIF
29 this->readNextRecord(true);
30 this->m_currentIsFirst = true;
31 }
32
readNextRecord(bool first)33 int LdifReader::readNextRecord( bool first )
34 {
35 DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::readRecord()" << std::endl);
36 std::string line;
37 std::string type;
38 std::string value;
39 int numLine = 0;
40 int recordType = 0;
41
42 if ( (! first) && this->m_currentIsFirst == true )
43 {
44 this->m_currentIsFirst = false;
45 return m_curRecType;
46 }
47
48 m_currentRecord.clear();
49
50 while ( !this->getLdifLine(line) )
51 {
52 DEBUG(LDAP_DEBUG_TRACE, " Line: " << line << std::endl );
53
54 // skip comments and empty lines between entries
55 if ( line[0] == '#' || ( numLine == 0 && line.size() == 0 ) )
56 {
57 DEBUG(LDAP_DEBUG_TRACE, "skipping empty line or comment" << std::endl );
58 continue;
59 }
60 if ( line.size() == 0 )
61 {
62 // End of Entry
63 break;
64 }
65
66 this->splitLine(line, type, value);
67
68 if ( numLine == 0 )
69 {
70 if ( type == "version" )
71 {
72 std::istringstream valuestream(value);
73 valuestream >> this->m_version;
74 if ( this->m_version != 1 ) // there is no other Version than LDIFv1
75 {
76 std::ostringstream err;
77 err << "Line " << this->m_lineNumber
78 << ": Unsupported LDIF Version";
79 throw( std::runtime_error(err.str()) );
80 }
81 continue;
82 }
83 if ( type == "dn" ) // Record should start with the DN ...
84 {
85 DEBUG(LDAP_DEBUG_TRACE, " Record DN:" << value << std::endl);
86 }
87 else if ( type == "include" ) // ... or it might be an "include" line
88 {
89 DEBUG(LDAP_DEBUG_TRACE, " Include directive: " << value << std::endl);
90 if ( this->m_version == 1 )
91 {
92 std::ostringstream err;
93 err << "Line " << this->m_lineNumber
94 << ": \"include\" not allowed in LDIF version 1.";
95 throw( std::runtime_error(err.str()) );
96 }
97 else
98 {
99 std::ostringstream err;
100 err << "Line " << this->m_lineNumber
101 << ": \"include\" not yet supported.";
102 throw( std::runtime_error(err.str()) );
103 }
104 }
105 else
106 {
107 DEBUG(LDAP_DEBUG_TRACE, " Record doesn't start with a DN"
108 << std::endl);
109 std::ostringstream err;
110 err << "Line " << this->m_lineNumber
111 << ": LDIF record does not start with a DN.";
112 throw( std::runtime_error(err.str()) );
113 }
114 }
115 if ( numLine == 1 ) // might contain "changtype" to indicate a change request
116 {
117 if ( type == "changetype" )
118 {
119 if ( first )
120 {
121 this->m_ldifTypeRequest = true;
122 }
123 else if (! this->m_ldifTypeRequest )
124 {
125 // Change Request in Entry record LDIF, should we accept it?
126 std::ostringstream err;
127 err << "Line " << this->m_lineNumber
128 << ": Change Request in an entry-only LDIF.";
129 throw( std::runtime_error(err.str()) );
130 }
131 if ( value == "modify" )
132 {
133 recordType = LDAPMsg::MODIFY_REQUEST;
134 }
135 else if ( value == "add" )
136 {
137 recordType = LDAPMsg::ADD_REQUEST;
138 }
139 else if ( value == "delete" )
140 {
141 recordType = LDAPMsg::DELETE_REQUEST;
142 }
143 else if ( value == "modrdn" )
144 {
145 recordType = LDAPMsg::MODRDN_REQUEST;
146 }
147 else
148 {
149 DEBUG(LDAP_DEBUG_TRACE, " Unknown change request <"
150 << value << ">" << std::endl);
151 std::ostringstream err;
152 err << "Line " << this->m_lineNumber
153 << ": Unknown changetype: \"" << value << "\".";
154 throw( std::runtime_error(err.str()) );
155 }
156 }
157 else
158 {
159 if ( first )
160 {
161 this->m_ldifTypeRequest = false;
162 }
163 else if (this->m_ldifTypeRequest )
164 {
165 // Entry record in Change record LDIF, should we accept
166 // it (e.g. as AddRequest)?
167 }
168 recordType = LDAPMsg::SEARCH_ENTRY;
169 }
170 }
171 m_currentRecord.push_back( stringpair(type, value) );
172 numLine++;
173 }
174 DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::readRecord() return: "
175 << recordType << std::endl);
176 m_curRecType = recordType;
177 return recordType;
178 }
179
getEntryRecord()180 LDAPEntry LdifReader::getEntryRecord()
181 {
182 std::list<stringpair>::const_iterator i = m_currentRecord.begin();
183 if ( m_curRecType != LDAPMsg::SEARCH_ENTRY )
184 {
185 throw( std::runtime_error( "The LDIF record: '" + i->second +
186 "' is not a valid LDAP Entry" ));
187 }
188 LDAPEntry resEntry(i->second);
189 i++;
190 LDAPAttribute curAttr(i->first);
191 LDAPAttributeList *curAl = new LDAPAttributeList();
192 for ( ; i != m_currentRecord.end(); i++ )
193 {
194 if ( i->first == curAttr.getName() )
195 {
196 curAttr.addValue(i->second);
197 }
198 else
199 {
200 const LDAPAttribute* existing = curAl->getAttributeByName( i->first );
201 if ( existing )
202 {
203 // Attribute exists already (handle gracefully)
204 curAl->addAttribute( curAttr );
205 curAttr = LDAPAttribute( *existing );
206 curAttr.addValue(i->second);
207 curAl->delAttribute( i->first );
208 }
209 else
210 {
211 curAl->addAttribute( curAttr );
212 curAttr = LDAPAttribute( i->first, i->second );
213 }
214 }
215 }
216 curAl->addAttribute( curAttr );
217 resEntry.setAttributes( curAl );
218 return resEntry;
219 }
220
getLdifLine(std::string & ldifline)221 int LdifReader::getLdifLine(std::string &ldifline)
222 {
223 DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::getLdifLine()" << std::endl);
224
225 this->m_lineNumber++;
226 if ( ! getline(m_ldifstream, ldifline) )
227 {
228 return -1;
229 }
230 while ( m_ldifstream &&
231 (m_ldifstream.peek() == ' ' || m_ldifstream.peek() == '\t'))
232 {
233 std::string cat;
234 m_ldifstream.ignore();
235 getline(m_ldifstream, cat);
236 ldifline += cat;
237 this->m_lineNumber++;
238 }
239
240 DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::getLdifLine()" << std::endl);
241 return 0;
242 }
243
splitLine(const std::string & line,std::string & type,std::string & value) const244 void LdifReader::splitLine(
245 const std::string& line,
246 std::string &type,
247 std::string &value) const
248 {
249 std::string::size_type pos = line.find(':');
250 if ( pos == std::string::npos )
251 {
252 DEBUG(LDAP_DEBUG_ANY, "Invalid LDIF line. No `:` separator"
253 << std::endl );
254 std::ostringstream err;
255 err << "Line " << this->m_lineNumber << ": Invalid LDIF line. No `:` separator";
256 throw( std::runtime_error( err.str() ));
257 }
258
259 type = line.substr(0, pos);
260 if ( pos == line.size() )
261 {
262 // empty value
263 value = "";
264 return;
265 }
266
267 pos++;
268 char delim = line[pos];
269 if ( delim == ':' || delim == '<' )
270 {
271 pos++;
272 }
273
274 for( ; pos < line.size() && isspace(line[pos]); pos++ )
275 { /* empty */ }
276
277 value = line.substr(pos);
278
279 if ( delim == ':' )
280 {
281 // Base64 encoded value
282 DEBUG(LDAP_DEBUG_TRACE, " base64 encoded value" << std::endl );
283 char outbuf[value.size()];
284 int rc = sasl_decode64(value.c_str(), value.size(),
285 outbuf, value.size(), NULL);
286 if( rc == SASL_OK )
287 {
288 value = std::string(outbuf);
289 }
290 else if ( rc == SASL_BADPROT )
291 {
292 value = "";
293 DEBUG( LDAP_DEBUG_TRACE, " invalid base64 content" << std::endl );
294 std::ostringstream err;
295 err << "Line " << this->m_lineNumber << ": Can't decode Base64 data";
296 throw( std::runtime_error( err.str() ));
297 }
298 else if ( rc == SASL_BUFOVER )
299 {
300 value = "";
301 DEBUG( LDAP_DEBUG_TRACE, " not enough space in output buffer"
302 << std::endl );
303 std::ostringstream err;
304 err << "Line " << this->m_lineNumber
305 << ": Can't decode Base64 data. Buffer too small";
306 throw( std::runtime_error( err.str() ));
307 }
308 }
309 else if ( delim == '<' )
310 {
311 // URL value
312 DEBUG(LDAP_DEBUG_TRACE, " url value" << std::endl );
313 std::ostringstream err;
314 err << "Line " << this->m_lineNumber
315 << ": URLs are currently not supported";
316 throw( std::runtime_error( err.str() ));
317 }
318 else
319 {
320 // "normal" value
321 DEBUG(LDAP_DEBUG_TRACE, " string value" << std::endl );
322 }
323 DEBUG(LDAP_DEBUG_TRACE, " Type: <" << type << ">" << std::endl );
324 DEBUG(LDAP_DEBUG_TRACE, " Value: <" << value << ">" << std::endl );
325 return;
326 }
327
readIncludeLine(const std::string & line) const328 std::string LdifReader::readIncludeLine( const std::string& line ) const
329 {
330 std::string::size_type pos = sizeof("file:") - 1;
331 std::string scheme = line.substr( 0, pos );
332 std::string file;
333
334 // only file:// URLs supported currently
335 if ( scheme != "file:" )
336 {
337 DEBUG( LDAP_DEBUG_TRACE, "unsupported scheme: " << scheme
338 << std::endl);
339 }
340 else if ( line[pos] == '/' )
341 {
342 if ( line[pos+1] == '/' )
343 {
344 pos += 2;
345 }
346 file = line.substr(pos, std::string::npos);
347 DEBUG( LDAP_DEBUG_TRACE, "target file: " << file << std::endl);
348 }
349 return file;
350 }
351