/* Firewall Builder Copyright (C) 2000 NetCitadel, LLC Author: Vadim Kurland vadim@vk.crocodile.org $Id: XMLTools.cpp,v 1.8 2006/08/22 04:12:49 vkurland Exp $ This program is free software which we release under the GNU General Public License. You may redistribute and/or modify this program under the terms of that license as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. To get a copy of the GNU General Public License, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include // for va_start and friends #include #include #include #include #ifndef _WIN32 # include // need this for read(2) #else # include // for access # define R_OK 4 // for access #endif #ifdef HAVE_LIBXSLT_XSLTCONFIG_H # include #endif #include #include #include #include #include #undef FW_XMLTOOLS_VERBOSE // #define FW_XMLTOOLS_VERBOSE #define DTD_LOAD_BITS (1|XML_DETECT_IDS|XML_COMPLETE_ATTRS) using namespace std; using namespace libfwbuilder; #ifndef __MINGW32__ extern int xmlDoValidityCheckingDefaultValue ; extern int xmlLoadExtDtdDefaultValue ; #else extern __declspec(dllimport) int xmlDoValidityCheckingDefaultValue ; extern __declspec(dllimport) int xmlLoadExtDtdDefaultValue ; #endif /* * This mutex protects access to XML parser. * since we change DTD validation flags and error * handling function pointers, access should be * synchronized. */ Mutex xml_parser_mutex; /* * This mutex protects access to XSLT processor. * since we error handling function pointers, access should be * synchronized. */ Mutex xslt_processor_mutex; static void xslt_error_handler(void *ctx, const char *msg, ...) { char buf[4096]; va_list args; assert(ctx!=NULL); va_start(args, msg); VSNPRINTF(buf, sizeof(buf)-1, msg, args); va_end(args); #ifdef FW_XMLTOOLS_VERBOSE cerr << "XSLT ERR: " << buf << endl; #endif *((string*)ctx)+=buf; } xmlNodePtr XMLTools::getXmlChildNode(xmlNodePtr r,const char *child_name) { xmlNodePtr cur; for(cur=r->xmlChildrenNode; cur; cur=cur->next) { if ( xmlIsBlankNode(cur) ) continue; if (strcmp(child_name,FROMXMLCAST(cur->name))==SAME) return cur; } return NULL; } xmlNodePtr XMLTools::getXmlNodeByPath(xmlNodePtr r, const string &path) { return getXmlNodeByPath(r, path.c_str()); } xmlNodePtr XMLTools::getXmlNodeByPath(xmlNodePtr r, const char *path) { char *s1, *cptr; char *path_copy; xmlNodePtr cur, res; res=NULL; path_copy= cxx_strdup( path ); s1=path_copy+strlen(path_copy)-1; while (*s1=='/') { *s1='\0'; s1--; } s1=path_copy; if (*s1=='/') { res=getXmlNodeByPath(r,s1+1); delete[] path_copy; return(res); } cptr=strchr(s1,'/'); if (cptr!=NULL) { *cptr='\0'; cptr++; } if (strcmp(FROMXMLCAST(r->name), s1)==0) { if (cptr) { for(cur=r->xmlChildrenNode; cur; cur=cur->next) { if ( xmlIsBlankNode(cur) ) continue; res=getXmlNodeByPath(cur,cptr); if (res) { delete[] path_copy; return(res); } } } else res=r; } delete[] path_copy; return(res); } xmlExternalEntityLoader XMLTools::defaultLoader = NULL; /** * This is global variable used in 'fwbExternalEntityLoader' * parser callback. It is protected by 'xml_parser_mutex'. */ static char* current_template_dir=NULL; xmlParserInputPtr fwbExternalEntityLoader(const char *URL, const char *ID, xmlParserCtxtPtr ctxt) { xmlParserInputPtr ret; #ifdef FW_XMLTOOLS_VERBOSE cerr << "ENTITY: " << URL << " " << string((ID)?ID:"(null)") << endl; #endif string fname; fname=string(current_template_dir) + FS_SEPARATOR; string url=URL; string::size_type pos=url.find_last_of("/\\"); fname+=(pos==string::npos)?url:url.substr(pos+1); #ifdef FW_XMLTOOLS_VERBOSE cerr << "ENTITY FNAME: " << fname << endl; #endif ret = xmlNewInputFromFile(ctxt, fname.c_str()); if(ret) return(ret); else if(XMLTools::defaultLoader) return XMLTools::defaultLoader(URL, ID, ctxt); else return NULL; } void XMLTools::initXMLTools() { // xml_parser_mutex = PTHREAD_MUTEX_INITIALIZER; // xslt_processor_mutex = PTHREAD_MUTEX_INITIALIZER; defaultLoader = xmlGetExternalEntityLoader(); current_template_dir=cxx_strdup(""); xmlSetExternalEntityLoader(fwbExternalEntityLoader); } string XMLTools::readFile(const std::string &rfile) throw(FWException) { string buf; struct stat stt; int fd; if (rfile=="-") { string s; while (!cin.eof()) { getline(cin,s); buf += s; buf += '\n'; } return buf; } else { #ifdef _WIN32 if (stat( rfile.c_str() , &stt )!=0 || (fd=_open(rfile.c_str(),O_RDONLY|_O_BINARY))<0) #else if (stat( rfile.c_str() , &stt )!=0 || (fd=open(rfile.c_str(),O_RDONLY))<0) #endif throw FWException("Could not read file "+rfile); } int chunk_size = 65536; char *chunk = (char*)malloc(chunk_size); if (!chunk) throw FWException("Out of memory"); int n = 0; while(1) { #ifdef _WIN32 n=_read(fd,chunk,chunk_size-1); #else n=read(fd,chunk,chunk_size-1); #endif if (n<=0) break; chunk[n] = '\0'; buf = buf + chunk; } free(chunk); int errn=errno; #ifdef _WIN32 _close(fd); #else close(fd); #endif if (n<0) { string s; s = "Error reading from file " + rfile + " : " + string(strerror(errn)); throw FWException(s); } return buf; } xmlDocPtr XMLTools::parseFile(const string &file_name, const string &buffer, bool use_dtd, const string &template_dir) throw(FWException) { xml_parser_mutex.lock(); if (current_template_dir!=NULL) delete[] current_template_dir; current_template_dir=cxx_strdup(template_dir.c_str()); xmlDoValidityCheckingDefaultValue = use_dtd?1:0; xmlLoadExtDtdDefaultValue = use_dtd?DTD_LOAD_BITS:0; string errors; xmlSetGenericErrorFunc (&errors, xslt_error_handler); // xmlDocPtr doc = xmlParseFile(file_name.c_str()); xmlDocPtr doc = xmlParseMemory(buffer.c_str(), buffer.length()); xmlSetGenericErrorFunc (NULL, NULL); xml_parser_mutex.unlock(); if(!doc || errors.length()) { throw FWException("Error parsing XML from file '"+file_name+ "' "+ "(use_dtd="+ (use_dtd?string("1"):string("0"))+") "+ (errors.length()?(string("\nXML Parser reported:\n")+errors):string("")) ); } return doc; } xmlDocPtr XMLTools::loadFile(const string &data_file , const string &type , const string &dtd_file , const UpgradePredicate *upgrade, const string &template_dir, const string ¤t_version ) throw(FWException) { #ifdef FW_XMLTOOLS_VERBOSE cerr << "Loading file: " << data_file << endl << " type: " << type << endl << " dtd_file: " << dtd_file << endl << " template_dir: " << template_dir << endl << " current_version: " << current_version << endl; #endif if (data_file!="-" && access(data_file.c_str() , R_OK )!=0) throw FWException(string("Could not access data file: ")+data_file); string buf = readFile(data_file); // First load without using DTD to check version xmlDocPtr doc = parseFile(data_file, buf, false, template_dir); #ifdef FW_XMLTOOLS_VERBOSE cerr << "Parsed file: " << data_file << endl; #endif // normally we load the file twice, first time to check the version and // upgrade it and the second time to generate doc that will be // used in the program. We can't do this if data_file is '-' (stdin) // 'cause we can't read stdin twice. So in this case we do not // upgrade. if (data_file=="-") return doc; xmlDocPtr newdoc=convert(doc, data_file, type, template_dir, current_version); if(newdoc) { const string upgrade_msg="The file '"+data_file+"' was saved with\n\ an older version of Firewall Builder. Opening it in this version will\n\ cause it to be upgraded, which may prevent older versions of the program\n\ from reading it. Backup copy of your file in the old format will be made\n\ in the same directory with extension '.bak'. Are you sure you want to open it?"; if(!(*upgrade)(upgrade_msg)) { xmlFreeDoc(newdoc); throw FWException("Load operation cancelled for file: '"+data_file); } #ifdef FW_XMLTOOLS_VERBOSE cerr << "Saving updated file: " << data_file << endl; #endif // file was changed save it doc=newdoc; string backup_file = data_file+".bak"; // on windows rename fails if target file already exists unlink(backup_file.c_str()); if(rename(data_file.c_str(), backup_file.c_str())) { xmlFreeDoc(doc); throw FWException("Error making backup copy of file: '"+data_file+"' as '"+backup_file+"'"); } try { saveFile(doc, data_file, type, dtd_file); } catch(FWException &ex) { // Saving converted copy failed // let's restore backup if(rename(backup_file.c_str(), data_file.c_str())) { throw FWException(ex.toString()+"\nRestoring backup copy failed "+ "your old data could be found in the file: '"+backup_file+"'"); } else throw; } } assert(doc!=NULL); xmlFreeDoc(doc); // Now we know the version is OK, // let us load for real, checking DTD. doc = parseFile(data_file, readFile(data_file), true, template_dir); return doc; } void XMLTools::setDTD(xmlDocPtr doc, const string &type_name, const string &dtd_file) throw(FWException) { #ifdef FW_XMLTOOLS_VERBOSE cerr << "XMLTools::setDTD: type_name=" << type_name << " dtd_file=" << dtd_file << endl; #endif xmlCreateIntSubset(doc, STRTOXMLCAST(type_name), NULL, STRTOXMLCAST(dtd_file) ); xml_parser_mutex.lock(); xmlDoValidityCheckingDefaultValue = 1; xmlLoadExtDtdDefaultValue = DTD_LOAD_BITS; xmlSubstituteEntitiesDefaultValue = 1; string errors; xmlSetGenericErrorFunc (&errors, xslt_error_handler); try { /* * This broke with libxml 2.6.4. Tests seem to rule out bug inside * libxml2 (used their example program "tree2.c" and added similar * fragment for validation, it worked), so it must be something in our * code. I can't seem to find the problem though. * * We recreate the tree from the objects in the memory, so doing * validation here is mostly a double check. It should be relatively * safe to just skip validation until I figure out what's wrong with * it. xmlValidCtxt vctxt; vctxt.userData = &errors; vctxt.error = xslt_error_handler; vctxt.warning = xslt_error_handler; if(xmlValidateDocument(&vctxt, doc)!=1) throw FWException(string("DTD validation stage 2 failed with following errors:\n")+errors); */ xmlSetGenericErrorFunc (NULL, NULL); xml_parser_mutex.unlock(); } catch(...) { xmlSetGenericErrorFunc (NULL, NULL); xml_parser_mutex.unlock(); throw; } } void XMLTools::saveFile(xmlDocPtr doc, const string &file_name, const string &type_name, const string &dtd_file) throw(FWException) { #ifdef FW_XMLTOOLS_VERBOSE cerr << "SAVE: " << file_name << " " <name || type_name!=FROMXMLCAST(root->name)) { xmlFreeDoc(doc); throw FWException("XML file '"+file_name+ "' has invalid structure."); } string vers; const char *v=FROMXMLCAST(xmlGetProp(root,TOXMLCAST("version"))); if(v==NULL) { // no version. v="0.8.7"; // at this version attribute has been introduced xmlNewProp(root, TOXMLCAST("version") , TOXMLCAST(v)); res=doc; // changed vers=v; } else { vers=v; FREEXMLBUFF(v); } #ifdef FW_XMLTOOLS_VERBOSE cerr << "File reports version : " << vers << endl; #endif int c; while(!vers.empty() && (c=version_compare(current_version,vers))!=0) { if(c<0) throw FWException(string("Data file '"+file_name+ "' was created by the future version of Firewall Builder.")); string oldversion=vers; #ifdef FW_XMLTOOLS_VERBOSE cerr << "Converting from version: " << oldversion << endl; #endif string fname; fname = template_dir; fname = fname+FS_SEPARATOR+"migration"+FS_SEPARATOR+type_name+"_"+vers+".xslt"; if(access(fname.c_str() , R_OK )!=0) { xmlFreeDoc(doc); throw FWException(string("File '"+file_name+ "' conversion error: no converter found for version: ")+oldversion+".\n"+ string("Supposed to be a file ")+fname ); } try { res=transformDocument(doc, fname, NULL); } catch(FWException &ex) { ex.getProperties()["failed_transformation"]=fname; xmlFreeDoc(doc); throw; } xmlFreeDoc(doc); doc = res; root=xmlDocGetRootElement(doc); if(!root || !root->name || type_name!=FROMXMLCAST(root->name)) { xmlFreeDoc(doc); throw FWException("File '"+file_name+ "' conversion Error: conversion produced file with invalid structure."); } v=FROMXMLCAST(xmlGetProp(root, TOXMLCAST("version"))); if(v==NULL) { xmlFreeDoc(doc); throw FWException("File '"+file_name+ "' conversion error: converted to unknown version."); } vers=v; FREEXMLBUFF(v); if(version_compare(vers, oldversion) <= 0) { xmlFreeDoc(doc); throw FWException("File '"+file_name+ "' conversion error: conversion did not advance version number!."); } } return res; } int XMLTools::major_number(const string &v, string &rest) { string a; string::size_type pos=v.find('.'); if(pos==string::npos) { a = v; rest = ""; } else { a = v.substr(0,pos); rest = v.substr(pos+1); } //TODO: handle conversion errors, by using 'strtol' return atoi(v.c_str()); } int XMLTools::version_compare(const string &v1, const string &v2) { string rest1, rest2; int x1=major_number(v1, rest1); int x2=major_number(v2, rest2); if(x1!=x2 || rest1.length()==0 || rest2.length()==0) return x1-x2; else return version_compare(rest1, rest2); } string XMLTools::quote_linefeeds(const string &s) { string res; for(string::size_type i=0;i127) res[i]='?'; } return res; } #undef DTD_LOAD_BITS #undef FW_XMLTOOLS_VERBOSE