// $OpenLDAP$ /* * Copyright 2008-2022 The OpenLDAP Foundation, All Rights Reserved. * COPYING RESTRICTIONS APPLY, see COPYRIGHT file */ #include "LdifReader.h" #include "LDAPMessage.h" #include "LDAPEntry.h" #include "LDAPAttributeList.h" #include "LDAPAttribute.h" #include "LDAPUrl.h" #include "debug.h" #include #include #include #include // For base64 routines typedef std::pair stringpair; LdifReader::LdifReader( std::istream &input ) : m_ldifstream(input), m_lineNumber(0) { DEBUG(LDAP_DEBUG_TRACE, "<> LdifReader::LdifReader()" << std::endl); this->m_version = 0; // read the first record to find out version and type of the LDIF this->readNextRecord(true); this->m_currentIsFirst = true; } int LdifReader::readNextRecord( bool first ) { DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::readRecord()" << std::endl); std::string line; std::string type; std::string value; int numLine = 0; int recordType = 0; if ( (! first) && this->m_currentIsFirst == true ) { this->m_currentIsFirst = false; return m_curRecType; } m_currentRecord.clear(); while ( !this->getLdifLine(line) ) { DEBUG(LDAP_DEBUG_TRACE, " Line: " << line << std::endl ); // skip comments and empty lines between entries if ( line[0] == '#' || ( numLine == 0 && line.size() == 0 ) ) { DEBUG(LDAP_DEBUG_TRACE, "skipping empty line or comment" << std::endl ); continue; } if ( line.size() == 0 ) { // End of Entry break; } this->splitLine(line, type, value); if ( numLine == 0 ) { if ( type == "version" ) { std::istringstream valuestream(value); valuestream >> this->m_version; if ( this->m_version != 1 ) // there is no other Version than LDIFv1 { std::ostringstream err; err << "Line " << this->m_lineNumber << ": Unsupported LDIF Version"; throw( std::runtime_error(err.str()) ); } continue; } if ( type == "dn" ) // Record should start with the DN ... { DEBUG(LDAP_DEBUG_TRACE, " Record DN:" << value << std::endl); } else if ( type == "include" ) // ... or it might be an "include" line { DEBUG(LDAP_DEBUG_TRACE, " Include directive: " << value << std::endl); if ( this->m_version == 1 ) { std::ostringstream err; err << "Line " << this->m_lineNumber << ": \"include\" not allowed in LDIF version 1."; throw( std::runtime_error(err.str()) ); } else { std::ostringstream err; err << "Line " << this->m_lineNumber << ": \"include\" not yet supported."; throw( std::runtime_error(err.str()) ); } } else { DEBUG(LDAP_DEBUG_TRACE, " Record doesn't start with a DN" << std::endl); std::ostringstream err; err << "Line " << this->m_lineNumber << ": LDIF record does not start with a DN."; throw( std::runtime_error(err.str()) ); } } if ( numLine == 1 ) // might contain "changtype" to indicate a change request { if ( type == "changetype" ) { if ( first ) { this->m_ldifTypeRequest = true; } else if (! this->m_ldifTypeRequest ) { // Change Request in Entry record LDIF, should we accept it? std::ostringstream err; err << "Line " << this->m_lineNumber << ": Change Request in an entry-only LDIF."; throw( std::runtime_error(err.str()) ); } if ( value == "modify" ) { recordType = LDAPMsg::MODIFY_REQUEST; } else if ( value == "add" ) { recordType = LDAPMsg::ADD_REQUEST; } else if ( value == "delete" ) { recordType = LDAPMsg::DELETE_REQUEST; } else if ( value == "modrdn" ) { recordType = LDAPMsg::MODRDN_REQUEST; } else { DEBUG(LDAP_DEBUG_TRACE, " Unknown change request <" << value << ">" << std::endl); std::ostringstream err; err << "Line " << this->m_lineNumber << ": Unknown changetype: \"" << value << "\"."; throw( std::runtime_error(err.str()) ); } } else { if ( first ) { this->m_ldifTypeRequest = false; } else if (this->m_ldifTypeRequest ) { // Entry record in Change record LDIF, should we accept // it (e.g. as AddRequest)? } recordType = LDAPMsg::SEARCH_ENTRY; } } m_currentRecord.push_back( stringpair(type, value) ); numLine++; } DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::readRecord() return: " << recordType << std::endl); m_curRecType = recordType; return recordType; } LDAPEntry LdifReader::getEntryRecord() { std::list::const_iterator i = m_currentRecord.begin(); if ( m_curRecType != LDAPMsg::SEARCH_ENTRY ) { throw( std::runtime_error( "The LDIF record: '" + i->second + "' is not a valid LDAP Entry" )); } LDAPEntry resEntry(i->second); i++; LDAPAttribute curAttr(i->first); LDAPAttributeList *curAl = new LDAPAttributeList(); for ( ; i != m_currentRecord.end(); i++ ) { if ( i->first == curAttr.getName() ) { curAttr.addValue(i->second); } else { const LDAPAttribute* existing = curAl->getAttributeByName( i->first ); if ( existing ) { // Attribute exists already (handle gracefully) curAl->addAttribute( curAttr ); curAttr = LDAPAttribute( *existing ); curAttr.addValue(i->second); curAl->delAttribute( i->first ); } else { curAl->addAttribute( curAttr ); curAttr = LDAPAttribute( i->first, i->second ); } } } curAl->addAttribute( curAttr ); resEntry.setAttributes( curAl ); return resEntry; } int LdifReader::getLdifLine(std::string &ldifline) { DEBUG(LDAP_DEBUG_TRACE, "-> LdifReader::getLdifLine()" << std::endl); this->m_lineNumber++; if ( ! getline(m_ldifstream, ldifline) ) { return -1; } while ( m_ldifstream && (m_ldifstream.peek() == ' ' || m_ldifstream.peek() == '\t')) { std::string cat; m_ldifstream.ignore(); getline(m_ldifstream, cat); ldifline += cat; this->m_lineNumber++; } DEBUG(LDAP_DEBUG_TRACE, "<- LdifReader::getLdifLine()" << std::endl); return 0; } void LdifReader::splitLine( const std::string& line, std::string &type, std::string &value) const { std::string::size_type pos = line.find(':'); if ( pos == std::string::npos ) { DEBUG(LDAP_DEBUG_ANY, "Invalid LDIF line. No `:` separator" << std::endl ); std::ostringstream err; err << "Line " << this->m_lineNumber << ": Invalid LDIF line. No `:` separator"; throw( std::runtime_error( err.str() )); } type = line.substr(0, pos); if ( pos == line.size() ) { // empty value value = ""; return; } pos++; char delim = line[pos]; if ( delim == ':' || delim == '<' ) { pos++; } for( ; pos < line.size() && isspace(line[pos]); pos++ ) { /* empty */ } value = line.substr(pos); if ( delim == ':' ) { // Base64 encoded value DEBUG(LDAP_DEBUG_TRACE, " base64 encoded value" << std::endl ); char outbuf[value.size()]; int rc = sasl_decode64(value.c_str(), value.size(), outbuf, value.size(), NULL); if( rc == SASL_OK ) { value = std::string(outbuf); } else if ( rc == SASL_BADPROT ) { value = ""; DEBUG( LDAP_DEBUG_TRACE, " invalid base64 content" << std::endl ); std::ostringstream err; err << "Line " << this->m_lineNumber << ": Can't decode Base64 data"; throw( std::runtime_error( err.str() )); } else if ( rc == SASL_BUFOVER ) { value = ""; DEBUG( LDAP_DEBUG_TRACE, " not enough space in output buffer" << std::endl ); std::ostringstream err; err << "Line " << this->m_lineNumber << ": Can't decode Base64 data. Buffer too small"; throw( std::runtime_error( err.str() )); } } else if ( delim == '<' ) { // URL value DEBUG(LDAP_DEBUG_TRACE, " url value" << std::endl ); std::ostringstream err; err << "Line " << this->m_lineNumber << ": URLs are currently not supported"; throw( std::runtime_error( err.str() )); } else { // "normal" value DEBUG(LDAP_DEBUG_TRACE, " string value" << std::endl ); } DEBUG(LDAP_DEBUG_TRACE, " Type: <" << type << ">" << std::endl ); DEBUG(LDAP_DEBUG_TRACE, " Value: <" << value << ">" << std::endl ); return; } std::string LdifReader::readIncludeLine( const std::string& line ) const { std::string::size_type pos = sizeof("file:") - 1; std::string scheme = line.substr( 0, pos ); std::string file; // only file:// URLs supported currently if ( scheme != "file:" ) { DEBUG( LDAP_DEBUG_TRACE, "unsupported scheme: " << scheme << std::endl); } else if ( line[pos] == '/' ) { if ( line[pos+1] == '/' ) { pos += 2; } file = line.substr(pos, std::string::npos); DEBUG( LDAP_DEBUG_TRACE, "target file: " << file << std::endl); } return file; }