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