/* eurephiadb_common.c  --  Database field mapping between database and the C code
 *
 *  GPLv2 only - Copyright (C) 2008 - 2012
 *               David Sommerseth <dazo@users.sourceforge.net>
 *
 *  This program 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
 *  of the License.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file   eurephiadb_mapping.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-12-06
 *
 * @brief  Database field mapping between database and the C code/programs
 *
 * This feature makes it possible to let each database driver implementation to
 * use its own table and column/field names in the database, while having a
 * unified interface for table names and field names in the application.
 *
 */

#include <string.h>
#include <assert.h>

#include <libxml/tree.h>

#define EUREPHIADB_MAPPING_C
#include <eurephia_context.h>
#include <eurephia_log.h>
#include <eurephiadb_mapping.h>
#include <eurephia_nullsafe.h>
#include <passwd.h>

/**
 * Simple mapping table for table_id to string.  Must match the order of the
 * defined tables in eurephia_mapping.h
 */
const char *TABLE_NAME[] = {
        NULL,
        "users",                 // TABLE_USERS
        "certificates",          // TABLE_CERTS
        "usercerts",             // TABLE_USERCERTS
        "lastlog",               // TABLE_LASTLOG
        "attemptslog",           // TABLE_ATTEMPTS
        "blacklist",             // TABLE_BLACKLIST
        "eurephia_adminaccess",  // TABLE_EUREPHIAADMACC
        "firewall_profiles",     // TABLE_FWPROFILES
        NULL};

/**
 * Simple mapping table for session status
 */
const char *SESSION_STATUS[] = {
        "UNKNOWN",
        "STARTED",
        "OPEN",
        "CLOSING",
        "CLOSED",
        "CLEANEDUP",
        NULL};


// Extra forward declarations to avoid compiler warnings.  These functions are inline functions
// in eurephia_xml.h, which would then cause multiple declarations of these functions if that
// include file gets included here.
char *xmlGetAttrValue(xmlAttr *properties, const char *key);
char *xmlExtractContent(xmlNode *n);


/**
 * Frees the memory used by a eDBfieldMap structure
 *
 * @param p Pointer to the eDBfieldMap to be freed
 */
void eDBfreeMapping(eDBfieldMap *p) {
        if( p == NULL ) {
                return;
        }
        eDBfreeMapping(p->next);

        // Release value and this pointer.
        // Do not attempt to release field_name, as it is a constant char *
        free_nullsafe(NULL, p->value);
        free_nullsafe(NULL, p->table_alias);
        free(p);
}


/**
 * Internal function for mapping the unified eurephia mapping table from a table index.
 * The table index is defined in eurephiadb_mapping.h, prefixed with TABLE_
 *
 * @param table table index (integer)
 *
 * @return Returns a pointer to the corresponding eDBfieldMap if table index is found, otherwise
 *         NULL is returned.
 * @see eurephiadb_mapping.h
 */
eDBfieldMap *eDBgetTableFieldMapping(int table) {
        eDBfieldMap *srcmap, *newmap = NULL, *ptr = NULL;
        int i;

        switch( table ) {
        case TABLE_USERS:
                srcmap = eTblMap_user;
                break;

        case TABLE_CERTS:
                srcmap = eTblMap_certificates;
                break;

        case TABLE_LASTLOG:
                srcmap = eTblMap_lastlog;
                break;

        case TABLE_ATTEMPTS:
                srcmap = eTblMap_attempts;
                break;

        case TABLE_BLACKLIST:
                srcmap = eTblMap_blacklist;
                break;

        case TABLE_USERCERTS:
                srcmap = eTblMap_usercerts;
                break;

        case TABLE_EUREPHIAADMACC:
                srcmap = eTblMap_eurephiaadmacc;
                break;

        case TABLE_FWPROFILES:
                srcmap = eTblMap_fwprofiles;
                break;

        default:
                return NULL;
        }

        // Copy the mapping into a pointer chain
        for( i = 0; srcmap[i].field_id != FIELD_NONE; i++ ) {
                ptr = (eDBfieldMap *) malloc_nullsafe(NULL, sizeof(eDBfieldMap)+2);
                assert(ptr != NULL);

                ptr->tableid = srcmap[i].tableid;
                ptr->table_alias = NULL;
                ptr->field_id = srcmap[i].field_id;
                ptr->field_type = srcmap[i].field_type;
                ptr->filter_type = srcmap[i].filter_type;
                ptr->field_name = srcmap[i].field_name;
                ptr->value = NULL;
                ptr->next = newmap;
                newmap = ptr;
        }

        return newmap;
}


/**
 * Internal function for copying over a particular field from one eDBfieldMap to another one
 *
 * @param newmap The destination map to be updated
 * @param dbmap  The source map where to copy the data from
 * @param field  The ID of the field which is being copied over
 */
inline void eDBcopyMapAttribs(eDBfieldMap *newmap, eDBfieldMap *dbmap, int field) {
        int i = 0;

        for( i = 0; dbmap[i].field_name != NULL; i++ ) {
                if( dbmap[i].field_id == field ) {
                        newmap->field_name = dbmap[i].field_name;
                        if( newmap->field_type != ft_SETNULL ) {
                                newmap->field_type = dbmap[i].field_type;
                        }
                }
        }
}


/**
 * Create a eDBfieldMap to be used by the database driver, with the values from an fieldMapping XML node.
 * This function will go through all fields in the XML tags, and associate the unified field names and
 * table name with the database specific field/table names.
 *
 * @param ctx      eurephiaCTX
 * @param dbmap    The database specific eDBfieldMap containing its own fields and table names
 * @param tblalias If the SQL needs to use an alias, this alias prefix will be used for the fields
 * @param fmapnode An xmlNode pointing at the fieldMapping node/tag.
 *
 * @return On success, a pointer to a eDBfieldMap specific for the database driver will be returned,
 *         otherwise NULL.
 */
eDBfieldMap *eDBxmlMapping(eurephiaCTX *ctx, eDBfieldMap *dbmap, const char *tblalias, xmlNode *fmapnode) {
        eDBfieldMap *map = NULL, *ptr = NULL;
        char *fmap_table = NULL;
        xmlNode *nptr = NULL;

        // Check that the node we got is the right one
        if( xmlStrcmp(fmapnode->name, (xmlChar *)"fieldMapping") != 0 ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Invalid fieldMapping XML node");
                return NULL;
        }

        // Make sure the fmapnode is suitable for the given dbmap
        assert( TABLE_NAME[dbmap->tableid] != NULL );
        fmap_table = xmlGetAttrValue(fmapnode->properties, "table");
        if( fmap_table == NULL ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Invalid fieldMapping XML document");
                return NULL;
        }
        if( strcmp(fmap_table, TABLE_NAME[dbmap->tableid]) != 0 ) {
                eurephia_log(ctx, LOG_ERROR, 0, "Mismatch between XML fieldMapping and eDBfieldMap");
                return NULL;
        }

        // Get a copy of the system map
        map = eDBgetTableFieldMapping(dbmap->tableid);

        // Loop through the XML, and register the different values to the system map fields
        nptr = fmapnode->children;
        while( nptr != NULL ) {
                // We are only interested in element nodes
                if( (nptr->type != XML_ELEMENT_NODE) ) {
                        nptr = nptr->next;
                        continue;
                }

                if( nptr->name != NULL ) {
                        int setnull = 0;
                        xmlAttr *atr;
                        char *pwhash = NULL;
                        eDBfieldFilterType filter = flt_NOTSET;

                        // Check attributes given.  Looking for: setnull, pwhash, filter
                        for( atr = nptr->properties; atr != NULL; atr = atr->next ) {
                                if( atr->name == NULL ) {
                                        // Skip nodes without name (just in case)
                                        continue;
                                }
                                if( xmlStrcmp(atr->name, (xmlChar *)"setnull") == 0 ) {
                                        xmlNode *n = atr->children;
                                        setnull = (((n != NULL) && (n->content != NULL)
                                                    && (xmlStrcmp(n->content,(xmlChar *)"1") == 0)) ? 1 : 0);
                                } else if( xmlStrcmp(atr->name, (xmlChar *)"pwhash") == 0 ) {
                                        pwhash = (atr->children != NULL ? (char *) atr->children->content : NULL);
                                } else if( xmlStrcmp(atr->name, (xmlChar *)"filter") == 0 ) {
                                        if( xmlStrcmp(atr->children->content,
                                                      (xmlChar *)"not-equals") == 0 ) {
                                                filter = flt_NEQ;
                                        } else if ( xmlStrcmp(atr->children->content,
                                                              (xmlChar *)"less-than") == 0 ) {
                                                filter = flt_LT;
                                        } else if ( xmlStrcmp(atr->children->content,
                                                              (xmlChar *)"less-than-equals") == 0 ) {
                                                filter = flt_LTE;
                                        } else if ( xmlStrcmp(atr->children->content,
                                                              (xmlChar *)"greater-than") == 0 ) {
                                                filter = flt_GT;
                                        } else if ( xmlStrcmp(atr->children->content,
                                                              (xmlChar *)"greater-than-equals") == 0 ) {
                                                filter = flt_GTE;
                                        } else {
                                                filter = flt_EQ;
                                        }
                                }
                        }

                        // Look up field in our copy of the system map
                        for( ptr = map; ptr != NULL; ptr = ptr->next ) {
                                // If we find the field, copy the value into our map
                                if( xmlStrcmp((xmlChar *)ptr->field_name, nptr->name) == 0 ) {
                                        if( setnull ) {
                                                // If flagged for being set to NULL, change the field type
                                                ptr->field_type = ft_SETNULL;
                                        }

                                        switch( ptr->field_type ) {
                                        case ft_SETNULL:
                                                // Don't set any values if we want to set it to NULL
                                                ptr->value = NULL;
                                                break;

                                        case ft_PASSWD: // If it is a password field type, hash the value
                                                if( (pwhash != NULL) && (strcmp(pwhash, "sha512") == 0) ) {
                                                        ptr->value = (nptr->children != NULL
                                                                      ? (char *)nptr->children->content
                                                                      : strdup(""));
                                                } else {
                                                        // Force hashing of value if it is an unknown hash type
                                                        ptr->value = (nptr->children != NULL
                                                                      ? eurephia_pwd_crypt(ctx,
                                                                                  (char *)nptr->children->content,
                                                                                  NULL)
                                                                      : strdup(""));
                                                }
                                                break;

                                        default:
                                                ptr->value = (nptr->children != NULL
                                                              ? strdup_nullsafe((char *)nptr->children->content)
                                                              : strdup(""));
                                                break;
                                        }
                                        if( filter != flt_NOTSET ) {
                                                ptr->filter_type = filter;
                                        }
                                        break;
                                }
                        }

                        // Go to next XML node
                        nptr = nptr->next;
                } else {
                        eurephia_log(ctx, LOG_ERROR, 0, "*** Illegal XML - unaccepted node: (%i) %s\n",
                                     nptr->type, nptr->name);
                        return NULL;
                }
        }

        for( ptr = map; ptr != NULL; ptr = ptr->next ) {
                // Copy over field name - translated via the db mapping table
                eDBcopyMapAttribs(ptr, dbmap, ptr->field_id);
                // Set correct table alias in the new map
                ptr->table_alias = strdup_nullsafe(tblalias);
        }
        return map;
}


/**
 * Generates a database specific list which is comma separated for the database driver to
 * use in the ORDER BY section of SQL queries.  The unified database fields are being translated
 * into the database specific field names.
 *
 * @param tfmap     eDBfieldMap specific to the database driver.
 * @param skeys_str Comma separated string containing the field names for the ORDER BY clause
 *
 * @return Returns a comma separated string with translated field names on success, otherwise NULL.
 * @remark The resulting pointer is pointing at a static char pointer and should not be freed.
 */
const char *eDBmkSortKeyString(eDBfieldMap *tfmap, const char *skeys_str) {
        eDBfieldMap *sk_map = NULL, *ptr1 = NULL, *tfmp = NULL;
        char *cp = NULL, *tok = NULL, *delims = ",";
        static char sortkeys[8194];

        if( skeys_str == NULL ) {
                return NULL;
        }

        // Make sure we have table field map
        assert( tfmap != NULL );

        // Get the correct table mapping for user input
        sk_map = eDBgetTableFieldMapping(tfmap->tableid);
        assert( sk_map != NULL );

        // Split up the skeys_str (sort keys string) and build up a map
        cp = strdup_nullsafe(skeys_str);
        tok = strtok(cp, delims);
        memset(&sortkeys, 0, 8194);
        while( tok != NULL ) {
                for( ptr1 = sk_map; ptr1 != NULL; ptr1 = ptr1->next) {
                        // If we find the the field in the unified mapping table ...
                        if( strcmp(tok, ptr1->field_name) == 0 ) {
                                // look up the proper field name for the current database
                                for( tfmp = tfmap; tfmp != NULL; tfmp = tfmp->next ) {
                                        if( ptr1->field_id == tfmp->field_id ) {
                                                if( tfmp->table_alias != NULL ) {
                                                        append_str(sortkeys, tfmp->table_alias, 8192);
                                                        append_str(sortkeys, ".", 8192);
                                                }
                                                append_str(sortkeys, tfmp->field_name, 8192);
                                                append_str(sortkeys, ",", 8192);
                                        }
                                }
                        }
                }
                tok = strtok(NULL, delims);
        }
        free_nullsafe(NULL, cp);
        sortkeys[strlen(sortkeys)-1] = '\0';
        eDBfreeMapping(sk_map);

        return (strlen_nullsafe(sortkeys) > 0 ? sortkeys : NULL);
}


/**
 * Tells which database fields in a eDBfieldMap are set and available.
 *
 * @param map eDBfieldMap containing the database specific mapping and field values set
 *
 * @return Returns a bit-wise OR'ed value of all fields in an eDBfieldMap which have their values
 * set to something else than NULL.
 */
long int eDBmappingFieldsPresent(eDBfieldMap *map) {
        long int ret = 0;
        eDBfieldMap *p = NULL;

        // Loops through all elements and flags those
        // fields with a value set.
        for( p = map; p != NULL; p = p->next ) {
                if( p->value != NULL ) {
                        ret |= p->field_id;
                }
        }
        return ret;
}


/**
 * Retrieves the value of a field in a eDBfieldMap pointer chain.
 *
 * @param map       eDBfieldMap with the values
 * @param field_id  The field ID to retrieve the value from
 *
 * @return Returns const char * to the value in the eDBfieldMap on success, or NULL if either the
 *         value is not found or if the value is not set.
 */
const char *eDBmappingGetValue(eDBfieldMap *map, long field_id) {
        eDBfieldMap *ptr = NULL;

        if( map == NULL ) {
                return NULL;
        }

        for( ptr = map; ptr != NULL; ptr = ptr->next ) {
                if( ptr->field_id == field_id ) {
                        return ptr->value;
                }
        }
        return NULL;
}

