| /* |
| * Copyright (C) 2009 Red Hat Inc. |
| * |
| * David Sommerseth <davids@redhat.com> |
| * |
| * Parses summary.xml reports from rteval into a standardised XML format |
| * which is useful when putting data into a database. |
| * |
| * This application is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; version 2. |
| * |
| * This application 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. |
| */ |
| |
| /** |
| * @file xmlparser.c |
| * @author David Sommerseth <davids@redhat.com> |
| * @date Wed Oct 21 10:58:53 2009 |
| * |
| * @brief Parses summary.xml reports from rteval into a standardised XML format |
| * which is useful when putting data into a database. |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| |
| #include <libxml/tree.h> |
| #include <libxslt/xsltInternals.h> |
| #include <libxslt/transform.h> |
| #include <libxslt/xsltutils.h> |
| #include <libexslt/exslt.h> |
| |
| #include <eurephia_nullsafe.h> |
| #include <eurephia_xml.h> |
| #include <xmlparser.h> |
| #include <sha1.h> |
| #include <log.h> |
| |
| static dbhelper_func const * xmlparser_dbhelpers = NULL; |
| |
| /** |
| * Simple strdup() function which encapsulates the string in single quotes, |
| * which is needed for XSLT parameter values |
| * |
| * @param str The string to be strdup'ed and encapsulated |
| * |
| * @return Returns a pointer to the new buffer. |
| */ |
| static char *encapsString(const char *str) { |
| char *ret = NULL; |
| |
| if( str == NULL ) { |
| return NULL; |
| } |
| |
| ret = (char *) calloc(1, strlen(str)+4); |
| assert( ret != NULL ); |
| |
| snprintf(ret, strlen(str)+3, "'%s'", str); |
| return ret; |
| } |
| |
| |
| /** |
| * Converts an integer to string an encapsulates the value in single quotes, |
| * which is needed for XSLT parameter values. |
| * |
| * @param val Integer value to encapsulate |
| * |
| * @return Returns a pointer to a new buffer with the encapsulated integer value. This |
| * buffer must be free'd after usage. |
| */ |
| static char *encapsInt(const unsigned int val) { |
| char *buf = NULL; |
| |
| buf = (char *) calloc(1, 130); |
| snprintf(buf, 128, "'%i'", val); |
| return buf; |
| } |
| |
| |
| /** |
| * Simple function to determine if the given string is a number or not |
| * |
| * @param str Pointer to the tring to be checked |
| * |
| * @returns Returns 0 if not a number and a non-null value if it is a number |
| */ |
| int isNumber(const char * str) |
| { |
| char *ptr = NULL; |
| |
| if (str == NULL || *str == '\0' || isspace(*str)) |
| return 0; |
| |
| strtod (str, &ptr); |
| return *ptr == '\0'; |
| } |
| |
| |
| /** |
| * Split a string into an array. |
| * Use strGet() to extract the results and strFree() to free the memory |
| * allocated by strSplit() |
| * |
| * @param str Input string |
| * @param sep Separator list (string) |
| * |
| * @returns Returns a pointer to a array_str_t struct on success, otherwise NULL |
| */ |
| array_str_t * strSplit(const char * str, const char * sep) |
| { |
| array_str_t * ret = calloc(1, sizeof(array_str_t)); |
| char * p = NULL, *cp = strdup(str); |
| |
| if( !ret || !cp ) { |
| fprintf(stderr, "Memory allocation error in strSplit()\n"); |
| return NULL; |
| } |
| |
| p = strtok(cp, sep); |
| while( p ) { |
| ret->data = realloc(ret->data, sizeof(char *) * ++(ret->size)); |
| if( !ret->data ) { |
| fprintf(stderr, "Memory allocation error in strSplit() while parsing\n"); |
| free(ret); |
| return NULL; |
| } |
| ret->data[ret->size-1] = strdup(p); |
| p = strtok(NULL, sep); |
| } |
| free(cp); |
| return ret; |
| } |
| |
| /** |
| * Retrieve an array memeber |
| * |
| * @param ar Pointer holding the array_str_t data |
| * @param el Element number to retrive |
| * |
| * @returns Returns a pointer to the array member. This value must never be freed. |
| * On failure NULL is returned. Only possible failures are out-of-range or a NULL |
| * array input. |
| */ |
| inline char * strGet(array_str_t * ar, unsigned int el) |
| { |
| return (!ar && (el > ar->size) ? NULL : ar->data[el]); |
| } |
| |
| /** |
| * Retrive number of accessible array members |
| * |
| * @param ar Pointer holding the array_str_t data |
| * |
| * @returns Returns the number of elements in the array_str_t struct. |
| * If a NULL pointer is given, it will return 0. |
| */ |
| inline unsigned int strSize(array_str_t * ar) |
| { |
| return (!ar ? 0 : ar->size); |
| } |
| |
| /** |
| * Frees up the memory held by an array_str_t struct. |
| * |
| * @params ar Pointer holding the array_str_t data to be freed |
| * |
| */ |
| void strFree(array_str_t * ar) |
| { |
| int i = 0; |
| |
| if( !ar ) { |
| return; |
| } |
| |
| for( i = 0; i < ar->size; i++ ) { |
| free(ar->data[i]); |
| ar->data[i] = NULL; |
| } |
| free(ar->data); ar->data = NULL; |
| free(ar); |
| } |
| |
| /** |
| * Initialise the XML parser, setting some global variables |
| */ |
| void init_xmlparser(dbhelper_func const * dbhelpers) |
| { |
| xmlparser_dbhelpers = dbhelpers; |
| |
| /* Init libxml2 and load all exslt functions */ |
| xmlInitMemory(); |
| exsltRegisterAll(); |
| } |
| |
| |
| /** |
| * Parses any XML input document into a sqldata XML format which can be used by pgsql_INSERT(). |
| * The transformation must be defined in the input XSLT template. |
| * |
| * @param log Log context |
| * @param xslt XSLT template defining the data transformation |
| * @param indata_d Input XML data to transform to a sqldata XML document |
| * @param params Parameters to be sent to the XSLT parser |
| * |
| * @return Returns a well formed sqldata XML document on success, otherwise NULL is returned. |
| */ |
| xmlDoc *parseToSQLdata(LogContext *log, xsltStylesheet *xslt, xmlDoc *indata_d, parseParams *params) { |
| xmlDoc *result_d = NULL; |
| char **xsltparams = NULL; |
| unsigned int idx = 0, idx_table = 0, idx_submid = 0, |
| idx_syskey = 0, idx_rterid = 0, idx_repfname = 0; |
| |
| xsltparams = calloc(10, sizeof(char *)); |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return NULL; |
| } |
| |
| if( params->table == NULL ) { |
| writelog(log, LOG_ERR, "Table is not defined"); |
| return NULL; |
| } |
| |
| // Prepare XSLT parameters |
| xsltparams[idx++] = "table\0"; |
| xsltparams[idx] = (char *) encapsString(params->table); |
| idx_table = idx++; |
| |
| if( params->submid > 0) { |
| xsltparams[idx++] = "submid\0"; |
| xsltparams[idx] = (char *) encapsInt(params->submid); |
| idx_submid = idx++; |
| } |
| |
| if( params->syskey > 0) { |
| xsltparams[idx++] = "syskey\0"; |
| xsltparams[idx] = (char *) encapsInt(params->syskey); |
| idx_syskey = idx++; |
| } |
| |
| if( params->rterid > 0 ) { |
| xsltparams[idx++] = "rterid"; |
| xsltparams[idx] = (char *) encapsInt(params->rterid); |
| idx_rterid = idx++; |
| } |
| |
| if( params->report_filename ) { |
| xsltparams[idx++] = "report_filename"; |
| xsltparams[idx] = (char *) encapsString(params->report_filename); |
| idx_repfname = idx++; |
| } |
| xsltparams[idx] = NULL; |
| |
| // Apply the XSLT template to the input XML data |
| result_d = xsltApplyStylesheet(xslt, indata_d, (const char **)xsltparams); |
| if( result_d == NULL ) { |
| writelog(log, LOG_CRIT, "Failed applying XSLT template to input XML"); |
| } |
| |
| // Free memory we allocated via encapsString()/encapsInt() |
| free(xsltparams[idx_table]); |
| if( params->submid ) { |
| free(xsltparams[idx_submid]); |
| } |
| if( params->syskey ) { |
| free(xsltparams[idx_syskey]); |
| } |
| if( params->rterid ) { |
| free(xsltparams[idx_rterid]); |
| } |
| if( params->report_filename ) { |
| free(xsltparams[idx_repfname]); |
| } |
| |
| free(xsltparams); |
| return result_d; |
| } |
| |
| |
| /** |
| * Internal xmlparser function. Extracts the value from a '//sqldata/records/record/value' |
| * node and hashes the value if the 'hash' attribute is set. Otherwise the value is extracted |
| * from the node directly. This function is only used by sqldataExtractContent(). |
| * |
| * @param sql_n sqldata values node containing the value to extract. |
| * |
| * @return Returns a pointer to a new buffer containing the value on success, otherwise NULL. |
| * This memory buffer must be free'd after usage. |
| */ |
| char * sqldataValueHash(LogContext *log, xmlNode *sql_n) { |
| const char *hash = NULL, *isnull = NULL; |
| SHA1Context shactx; |
| uint8_t shahash[SHA1_HASH_SIZE]; |
| char *ret = NULL, *ptr = NULL; |
| int i; |
| |
| if( !(sql_n && (xmlStrcmp(sql_n->name, (xmlChar *) "value") == 0) |
| && (xmlStrcmp(sql_n->parent->name, (xmlChar *) "record") == 0) |
| || (xmlStrcmp(sql_n->parent->name, (xmlChar *) "value") == 0)) ) { |
| return NULL; |
| } |
| |
| isnull = xmlGetAttrValue(sql_n->properties, "isnull"); |
| if( isnull && (strcmp(isnull, "1") == 0) ) { |
| return NULL; |
| } |
| |
| hash = xmlGetAttrValue(sql_n->properties, "hash"); |
| if( !hash ) { |
| // If no hash attribute is found, just use the raw data |
| ret = strdup_nullsafe(xmlExtractContent(sql_n)); |
| } else if( strcasecmp(hash, "sha1") == 0 ) { |
| const char *indata = xmlExtractContent(sql_n); |
| // SHA1 hashing requested |
| SHA1Init(&shactx); |
| SHA1Update(&shactx, indata, strlen_nullsafe(indata)); |
| SHA1Final(&shactx, shahash); |
| |
| // "Convert" to a readable format |
| ret = malloc_nullsafe(log, (SHA1_HASH_SIZE * 2) + 3); |
| ptr = ret; |
| for( i = 0; i < SHA1_HASH_SIZE; i++ ) { |
| sprintf(ptr, "%02x", shahash[i]); |
| ptr += 2; |
| } |
| } else { |
| ret = strdup("<Unsupported hashing algorithm>"); |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * Extract the content of a //sqldata/records/record/value[@type='array']/value node set |
| * and format it in suitable array format for the database backend. |
| * |
| * @param log Log context |
| * @param sql_n sqldata values node containing the value to extract and format as an array. |
| * |
| * @return Returns a pointer to a new memory buffer containing the value as a string. |
| * On errors, NULL is returned. This memory buffer must be free'd after usage. |
| */ |
| static char * sqldataValueArray(LogContext *log, xmlNode *sql_n) |
| { |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return NULL; |
| } |
| |
| return xmlparser_dbhelpers->dbh_FormatArray(log, sql_n); |
| } |
| |
| |
| /** |
| * Extract the content of a '//sqldata/records/record/value' node. It will consider |
| * both the 'hash' and 'type' attributes of the 'value' tag. |
| * |
| * @param log Log context |
| * @param sql_n Pointer to a value node of a sqldata XML document. |
| * |
| * @return Returns a pointer to a new memory buffer containing the value as a string. |
| * On errors, NULL is returned. This memory buffer must be free'd after usage. |
| */ |
| char *sqldataExtractContent(LogContext *log, xmlNode *sql_n) { |
| const char *valtype = xmlGetAttrValue(sql_n->properties, "type"); |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return NULL; |
| } |
| |
| if( !sql_n || (xmlStrcmp(sql_n->name, (xmlChar *) "value") != 0) |
| || (xmlStrcmp(sql_n->parent->name, (xmlChar *) "record") != 0) ) { |
| return NULL; |
| } |
| |
| if( valtype && (strcmp(valtype, "xmlblob") == 0) ) { |
| xmlNode *chld_n = sql_n->children; |
| |
| // Go to next "real" tag, skipping non-element nodes |
| while( chld_n && chld_n->type != XML_ELEMENT_NODE ){ |
| chld_n = chld_n->next; |
| } |
| return xmlNodeToString(log, chld_n); |
| } else if( valtype && (strcmp(valtype, "array") == 0) ) { |
| return sqldataValueArray(log, sql_n); |
| } else { |
| return sqldataValueHash(log, sql_n); |
| } |
| } |
| |
| |
| /** |
| * Return the 'fid' value of a given field in an sqldata XML document. |
| * |
| * @param log Log context |
| * @param sql_n Pointer to the root xmlNode element of a sqldata XML document |
| * @param fname String containing the field name to look up |
| * |
| * @return Returns a value >= 0 on success, containing the 'fid' value of the field. Otherwise |
| * a value < 0 is returned. -1 if the field is not found or -2 if there are some problems |
| * with the XML document. |
| */ |
| int sqldataGetFid(LogContext *log, xmlNode *sql_n, const char *fname) { |
| xmlNode *f_n = NULL; |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return -2; |
| } |
| |
| if( !sql_n || (xmlStrcmp(sql_n->name, (xmlChar *) "sqldata") != 0) ) { |
| writelog(log, LOG_ERR, |
| "sqldataGetFid: Input XML document is not a valid sqldata document"); |
| return -2; |
| } |
| |
| f_n = xmlFindNode(sql_n, "fields"); |
| if( !f_n || !f_n->children ) { |
| writelog(log, LOG_ERR, |
| "sqldataGetFid: Input XML document does not contain a fields section"); |
| return -2; |
| } |
| |
| foreach_xmlnode(f_n->children, f_n) { |
| if( (f_n->type != XML_ELEMENT_NODE) |
| || xmlStrcmp(f_n->name, (xmlChar *) "field") != 0 ) { |
| // Skip uninteresting nodes |
| continue; |
| } |
| |
| if( strcmp(xmlExtractContent(f_n), fname) == 0 ) { |
| char *fid = xmlGetAttrValue(f_n->properties, "fid"); |
| if( !fid ) { |
| writelog(log, LOG_ERR, |
| "sqldataGetFid: Field node is missing 'fid' attribute (field: %s)", |
| fname); |
| return -2; |
| } |
| return atoi_nullsafe(fid); |
| } |
| } |
| return -1; |
| } |
| |
| |
| /** |
| * Retrieves the value of a particular field in an sqldata XML document. |
| * |
| * @param log Log context |
| * @param sqld pointer to an sqldata XML document. |
| * @param fname String containing the field name to extract the value of. |
| * @param recid Integer containing the record ID of the record to extract the value. This starts |
| * on 0. |
| * |
| * @return Returns a pointer to a new memory buffer containing the extracted value. On errors or if |
| * recid is higher than available records, NULL is returned. |
| */ |
| char *sqldataGetValue(LogContext *log, xmlDoc *sqld, const char *fname, int recid ) { |
| xmlNode *r_n = NULL; |
| int fid = -3, rc = 0; |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return NULL; |
| } |
| |
| if( recid < 0 ) { |
| writelog(log, LOG_ERR, "sqldataGetValue: Invalid recid"); |
| return NULL; |
| } |
| |
| r_n = xmlDocGetRootElement(sqld); |
| if( !r_n || (xmlStrcmp(r_n->name, (xmlChar *) "sqldata") != 0) ) { |
| writelog(log, LOG_ERR, |
| "sqldataGetValue: Input XML document is not a valid sqldata document"); |
| return NULL; |
| } |
| |
| fid = sqldataGetFid(log, r_n, fname); |
| if( fid < 0 ) { |
| return NULL; |
| } |
| |
| r_n = xmlFindNode(r_n, "records"); |
| if( !r_n || !r_n->children ) { |
| writelog(log, LOG_ERR, |
| "sqldataGetValue: Input XML document does not contain a records section"); |
| return NULL; |
| } |
| |
| foreach_xmlnode(r_n->children, r_n) { |
| if( (r_n->type != XML_ELEMENT_NODE) |
| || xmlStrcmp(r_n->name, (xmlChar *) "record") != 0 ) { |
| // Skip uninteresting nodes |
| continue; |
| } |
| if( rc == recid ) { |
| xmlNode *v_n = NULL; |
| // The rigth record is found, find the field we're looking for |
| foreach_xmlnode(r_n->children, v_n) { |
| char *fid_s = NULL; |
| if( (v_n->type != XML_ELEMENT_NODE) |
| || (xmlStrcmp(v_n->name, (xmlChar *) "value") != 0) ) { |
| // Skip uninteresting nodes |
| continue; |
| } |
| fid_s = xmlGetAttrValue(v_n->properties, "fid"); |
| if( fid_s && (fid == atoi_nullsafe(fid_s)) ) { |
| return sqldataExtractContent(log, v_n); |
| } |
| } |
| } |
| rc++; |
| } |
| return NULL; |
| } |
| |
| |
| /** |
| * Helper function to parse an sqldata XML document for the systems_hostname table. In addition |
| * it will also return two strings containing hostname and ipaddress of the host. |
| * |
| * @param log Log context |
| * @param xslt Pointer to an xmlparser.xml XSLT template |
| * @param summaryxml rteval XML report document |
| * @param syskey Integer containing the syskey value corresponding to this host |
| * @param hostname Return pointer for where the hostname will be saved. |
| * @param ipaddr Return pointer for where the IP address will be saved. |
| * |
| * @return Returns a sqldata XML document on success. In this case the hostname and ipaddr will point |
| * at memory buffers containing hostname and ipaddress. These values must be free'd after usage. |
| * On errors the function will return NULL and hostname and ipaddr will not have been touched |
| * at all. |
| */ |
| xmlDoc *sqldataGetHostInfo(LogContext *log, xsltStylesheet *xslt, xmlDoc *summaryxml, |
| int syskey, char **hostname, char **ipaddr) |
| { |
| xmlDoc *hostinfo_d = NULL; |
| parseParams prms; |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return NULL; |
| } |
| |
| memset(&prms, 0, sizeof(parseParams)); |
| prms.table = "systems_hostname"; |
| prms.syskey = syskey; |
| |
| hostinfo_d = parseToSQLdata(log, xslt, summaryxml, &prms); |
| if( !hostinfo_d ) { |
| writelog(log, LOG_ERR, |
| "sqldatGetHostInfo: Could not parse input XML data"); |
| xmlFreeDoc(hostinfo_d); |
| goto exit; |
| } |
| |
| // Grab hostname from input XML |
| *hostname = sqldataGetValue(log, hostinfo_d, "hostname", 0); |
| if( !hostname ) { |
| writelog(log, LOG_ERR, |
| "sqldatGetHostInfo: Could not retrieve the hostname field from the input XML"); |
| xmlFreeDoc(hostinfo_d); |
| goto exit; |
| } |
| |
| // Grab ipaddr from input XML |
| *ipaddr = sqldataGetValue(log, hostinfo_d, "ipaddr", 0); |
| if( !ipaddr ) { |
| writelog(log, LOG_ERR, |
| "sqldatGetHostInfo: Could not retrieve the IP address field from the input XML"); |
| free_nullsafe(hostname); |
| xmlFreeDoc(hostinfo_d); |
| goto exit; |
| } |
| exit: |
| return hostinfo_d; |
| } |
| |
| int sqldataGetRequiredSchemaVer(LogContext *log, xmlNode *sqldata_root) |
| { |
| char *schver = NULL, *cp = NULL, *ptr = NULL; |
| int majv = 0, minv = 0; |
| |
| if( xmlparser_dbhelpers == NULL ) { |
| writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); |
| return -1; |
| } |
| |
| if( !sqldata_root || (xmlStrcmp(sqldata_root->name, (xmlChar *) "sqldata") != 0) ) { |
| writelog(log, LOG_ERR, "sqldataGetRequiredSchemaVer: Invalid document node"); |
| return -1; |
| } |
| |
| schver = xmlGetAttrValue(sqldata_root->properties, "schemaver"); |
| if( schver == NULL ) { |
| return 100; // If not defined, presume lowest available version. |
| } |
| cp = strdup(schver); |
| assert( cp != NULL ); |
| |
| if( (ptr = strpbrk(cp, ".")) != NULL ) { |
| *ptr = 0; |
| ptr++; |
| majv = atoi_nullsafe(cp); |
| minv = atoi_nullsafe(ptr); |
| } else { |
| majv = atoi_nullsafe(cp); |
| minv = 0; |
| } |
| free_nullsafe(cp); |
| |
| return (majv * 100) + minv; |
| } |