/* useraccess.c  --  eurephiadm useraccess command:
 *                   Management of user access levels (eurephia_adminaccess table)
 *
 *  GPLv2 only - Copyright (C) 2009 - 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   adminaccess.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2009-03-24
 *
 * @brief  eurephiadm command 'useraccess'.  Management of user access levels
 *
 */

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

#ifdef HAVE_LIBXML2
#include <libxml/tree.h>
#include <libxml/xpath.h>
#endif

#define MODULE "eurephia::UserAdmin" /**< Need to define the active module before including argparser.h */
#include <eurephia_nullsafe.h>
#include <eurephia_context.h>
#include <eurephia_log.h>
#include <eurephia_xml.h>
#include <eurephia_values_struct.h>
#include <eurephiadb_session_struct.h>
#include <eurephiadb_mapping.h>
#include <eurephiadb_driver.h>
#include <certinfo.h>

#include "../argparser.h"
#include "../xsltparser.h"

/**
 * Help screens for the adminaccess command
 *
 * @param page which help screen to display
 */
void display_adminaccess_help(int page) {
        switch( page ) {
        case 'G':
                printf("The grant mode will grant a user access to a specified access level.\n"
                       "\n"
                       "     -i | --uid          User account ID\n"
                       "     -I | --interface    Grant access through which interface (default 'C')\n"
                       "     -a | --access-level Which access level to grant access to\n"
                       "\n"
                       );
                break;
        case 'R':
                printf("The revoke mode will remove an access from the desired user accounts.\n"
                       "\n"
                       "     -i | --uid          User account ID\n"
                       "     -I | --interface    Revoke access from interface (default 'C')\n"
                       "     -a | --access-level Which access level to revoke access from\n"
                       "\n"
                       );
                break;
        case 'l':
                printf("The list mode will show the granted user access levels\n"
                       "\n"
                       "     -i | --uid          User account ID\n"
                       "     -u | --username     User name\n"
                       "     -I | --interface    Which interfaces to show (default 'C')\n"
                       "     -a | --access-level Which access level to show\n"
                       "\n"
                       );
                break;
        default:
                printf("Available modes for the adminaccess command are:\n\n"
                       "     -G | --grant       Grant a specific access level to a user\n"
                       "     -R | --revoke      Revoke access levels\n"
                       "     -l | --list        List all granted accesses\n"
                       "     -h | --help <mode> Help about a specific mode\n\n");
                break;
        }
}

/**
 * Help screen wrapper.  Used by cmd_Help()
 */
void help_AdminAccess() {
        display_adminaccess_help(0);
}


/**
 * Help screen wrapper for the adminaccess help function.
 *
 * @param ctx   eurephiaCTX
 * @param sess  eurephiaSESSION of the current logged in user
 * @param cfg   eurephiaVALUES struct of the current configuration
 * @param argc  argument count for the eurephiadm command
 * @param argv  argument table for the eurephiadm command
 *
 * @return returns 0 on success, otherwise 1.
 */
int help_AdminAccess2(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        e_options helpargs[] = {
                {"--list", "-l", 0},
                {"--grant", "-G", 0},
                {"--revoke", "-R", 0},
                {NULL, NULL, 0}
        };

        int i = 1;
        display_adminaccess_help(eurephia_getopt(&i, argc, argv, helpargs));
        return 0;
}

/**
 * adminaccess list mode, lists all users access levels
 *
 * @param ctx   eurephiaCTX
 * @param sess  eurephiaSESSION of the current logged in user
 * @param cfg   eurephiaVALUES struct of the current configuration
 * @param argc  argument count for the eurephiadm command
 * @param argv  argument table for the eurephiadm command
 *
 * @return returns 0 on success, otherwise 1.
 */
int list_adminaccess(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *list_xml = NULL, *srch_xml = NULL;
        xmlNode *srch_n = NULL, *tmp_n = NULL;
        int i = 0, rc = -1;
        char *uid = NULL, *username = NULL, *intf = NULL, *acl = NULL;

        e_options listargs[] = {
                {"--uid", "-i", 1},
                {"--username", "-u", 1},
                {"--interface", "-I", 1},
                {"--access-level", "-a", 1},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

        assert( (ctx != NULL) && (ctx->dbc != NULL) && (ctx->dbc->config != NULL));

        // Parse arguments
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, listargs) ) {
                case 'i':
                        uid = optargs[0];
                        break;
                case 'u':
                        username = optargs[0];
                        break;
                case 'I':
                        intf = optargs[0];
                        break;
                case 'a':
                        acl = optargs[0];
                        break;

                case 'h':
                        display_adminaccess_help('l');
                        return 0;

                default:
                        return 1;
                }
        }

        // Build up search XML
        eurephiaXML_CreateDoc(ctx, 1, "admin_access", &srch_xml, &srch_n);
        assert( (srch_xml != NULL) && (srch_n != NULL) );
        xmlNewProp(srch_n, (xmlChar *) "mode", (xmlChar *) "list");

        tmp_n = xmlNewChild(srch_n, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(tmp_n, (xmlChar *) "table", (xmlChar *) "eurephia_adminaccess");

        if( uid != NULL ) {
                xmlNewChild(tmp_n, NULL, (xmlChar *) "uid", (xmlChar *) uid);
        }
        if( username != NULL ) {
                xmlNewChild(tmp_n, NULL, (xmlChar *) "username", (xmlChar *) username);
        }
        if( intf != NULL ) {
                xmlNewChild(tmp_n, NULL, (xmlChar *) "interface", (xmlChar *) intf);
        }
        if( acl != NULL ) {
                xmlNewChild(tmp_n, NULL, (xmlChar *) "accesslevel", (xmlChar *) acl);
        }

        list_xml = eDBadminAccessLevel(ctx, srch_xml);
        xmlFreeDoc(srch_xml);
        if( list_xml == NULL ) {
                fprintf(stderr, "%s: Error retrieving user access list\n", MODULE);
                return 1;
        }

        tmp_n = eurephiaXML_getRoot(ctx, list_xml, NULL, 1);
        if( tmp_n == NULL ) {
                fprintf(stderr, "%s: Error retrieving user access list results\n", MODULE);
                rc = 1;
        } else if( xmlStrcmp(tmp_n->name, (xmlChar *) "admin_access_list") == 0 ) {
                xslt_print_xmldoc(stdout, cfg, list_xml, "adminaccess.xsl", NULL);
                rc = 0;
        } else {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, list_xml);
                assert( res != NULL );
                fprintf(stderr, "%s: %s\n", MODULE, res->message);
                rc = 1;
                free_nullsafe(ctx, res);
        }
        xmlFreeDoc(list_xml);
        return rc;
}


/**
 * Function for granting and revoking user access levels.
 *
 * @param ctx   eurephiaCTX
 * @param sess  eurephiaSESSION of the current logged in user
 * @param cfg   eurephiaVALUES struct of the current configuration
 * @param argc  argument count for the eurephiadm command
 * @param argv  argument table for the eurephiadm command
 *
 * @return returns 0 on success, otherwise 1.
 */
int grant_revoke(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *upd_xml = NULL, *res_xml = NULL;
        xmlNode *root_n = NULL, *fmap_n = NULL;
        xmlAttr *mode_a = NULL;
        char actmode = '-';
        int i = 0, rc = 0;
        int f_uid = 0, f_acl = 0, f_intf = 0;

        e_options addargs[] = {
                {"--uid", "-i", 1},
                {"--access-level", "-a", 1},
                {"--interface", "-I", 1},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

        assert( (ctx != NULL) && (ctx->dbc != NULL) && (ctx->dbc->config != NULL));

        eurephiaXML_CreateDoc(ctx, 1, "admin_access", &upd_xml, &root_n);
        fmap_n = xmlNewChild(root_n, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *) "eurephia_adminaccess");

        if( (strcmp(argv[0], "--grant") == 0) || (strcmp(argv[0], "-G") == 0) ) {
                actmode = 'G';
        } else if( (strcmp(argv[0], "--revoke") == 0) || (strcmp(argv[0], "-R") == 0) ) {
                actmode = 'R';
        }

        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, addargs) ) {
                case 'i':
                        if( f_uid > 0 ) {
                                fprintf(stderr, "%s: User id can only be set once\n", MODULE);
                                rc = 1;
                                goto error;
                        }
                        if( atoi_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr, "%s: User ID must be a positive number (>0)\n", MODULE);
                                rc = 1;
                                goto error;
                        }
                        f_uid++;
                        xmlNewChild(fmap_n, NULL, (xmlChar *) "uid", (xmlChar *) optargs[0]);
                        break;

                case 'a':
                        if( f_acl > 0 ) {
                                fprintf(stderr, "%s: Access level can only be set once\n", MODULE);
                                rc = 1;
                                goto error;
                        }
                        f_acl++;
                        xmlNewChild(fmap_n, NULL, (xmlChar *) "accesslevel", (xmlChar *) optargs[0]);
                        break;

                case 'I':
                        if( f_intf > 0 ) {
                                fprintf(stderr, "%s: Admin interface indicator can only be set once\n", MODULE);
                                rc = 1;
                                goto error;
                        }
                        f_intf++;
                        xmlNewChild(fmap_n, NULL, (xmlChar *) "interface", (xmlChar *) optargs[0]);
                        break;

                case 'h':
                        display_adminaccess_help(actmode);
                        rc = 0;
                        goto error;

                default:
                        rc = 1;
                        goto error;
               }
        }

        if( (f_uid != 1) || (f_acl != 1) ) {
                fprintf(stderr, "%s: You must provide both a user ID (--uid) "
                        "and an access level (--access-level)\n",
                        MODULE);
                return 1;
        }

        if( f_intf == 0 ) {
                // Default interface value, if not set.
                xmlNewChild(fmap_n, NULL, (xmlChar *) "interface", (xmlChar *) "C");
        }

        // Check if this access level has already been granted
        mode_a = xmlNewProp(root_n, (xmlChar *) "mode", (xmlChar *) "list");
        res_xml = eDBadminAccessLevel(ctx, upd_xml);
        if( res_xml == NULL ) {
                fprintf(stderr, "%s: Failed to check the access level\n", MODULE);
                rc = 1;
        } else {
                xmlNode *ptr_n = eurephiaXML_getRoot(ctx, res_xml, "admin_access_list", 1);
                if( (ptr_n == NULL) || (xmlStrcmp(ptr_n->name, (xmlChar *) "admin_access_list") != 0) ) {
                        if( !eurephiaXML_IsResultMsg(ctx, res_xml) ) {
                                fprintf(stderr, "%s: Failed to check the access level (unknown error)\n", MODULE);
                        } else {
                                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, res_xml);
                                fprintf(stderr, "%s: %s\n", MODULE, res->message);
                                free_nullsafe(ctx, res);
                        }
                        rc = 1;
                } else {
                        xmlNode *ptr2_n = xmlFindNode(ptr_n, "user_access");

                        // If ptr2_n is not NULL, it means this access already exists.
                        // That is expected for REVOKE operations, but not expected
                        // for GRANT operations
                        rc = ((actmode == 'G' && ptr2_n != NULL) || (actmode == 'R' && ptr2_n == NULL) ? 1 : 0);
                        if( rc > 0 ) {
                                fprintf(stderr, "%s: This access level is %s to the user\n", MODULE,
                                        (actmode == 'G' ? "already granted" : "not granted"));
                        }
                }
                xmlFreeDoc(res_xml);
        }
        if( rc > 0 ) {
                goto error;
        }

        // Prepare for the real update.  Replace the 'mode' attribute to contain the proper action.
        xmlRemoveProp(mode_a);
        xmlNewProp(root_n, (xmlChar *) "mode", (xmlChar *) (actmode == 'G' ? "grant" : "revoke"));

        res_xml = eDBadminAccessLevel(ctx, upd_xml);
        if( res_xml == NULL ) {
                fprintf(stderr, "%s: Failed to update the access level\n", MODULE);
                rc = 1;
        } else {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, res_xml);
                if( res == NULL ) {
                        fprintf(stderr, "%s: Failed to update the access level.  No results returned\n",
                                MODULE);
                        rc = 1;
                } else {
                        if( res->resultType == exmlERROR ) {
                                fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        rc = 1;
                        } else {
                                fprintf(stdout, "%s: %s\n", MODULE, res->message);
                                rc = 0;
                        }
                }
                free_nullsafe(ctx, res);
                xmlFreeDoc(res_xml);
        }

 error:
        xmlFreeDoc(upd_xml);
        return rc;
}


/**
 * Main function for the the adminaccess command
 *
 * @param ctx   eurephiaCTX
 * @param sess  eurephiaSESSION of the current logged in user
 * @param cfg   eurephiaVALUES struct of the current configuration
 * @param argc  argument count for the eurephiadm command
 * @param argv  argument table for the eurephiadm command
 *
 * @return returns 0 on success, otherwise 1.
 */
int cmd_AdminAccess(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        char **mode_argv;
        int i, mode_argc = 0, rc = 0;
        int (*mode_fnc) (eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv);

        e_options modeargs[] = {
                {"--list", "-l", 0},
                {"--grant", "-G", 0},
                {"--revoke", "-R", 0},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

        assert((ctx != NULL) && (ctx->dbc != NULL) && (ctx->dbc->config != NULL));
        mode_fnc = NULL;
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, modeargs) ) {
                case 'l':
                        mode_fnc = list_adminaccess;
                        break;

                case 'h':
                        mode_fnc = help_AdminAccess2;
                        break;

                case 'G':
                        mode_fnc = grant_revoke;
                        break;

                case 'R':
                        mode_fnc = grant_revoke;
                        break;

                default:
                        break;
                }
                if( mode_fnc != NULL ) {
                        break;
                }
        }

        // If we do not have any known mode defined, exit with error
        if( mode_fnc == NULL )  {
                fprintf(stderr, "%s: Unknown argument.  No mode given\n", MODULE);
                return 1;
        }

        // Allocate memory for our arguments being sent to the mode function
        mode_argv = (char **) calloc(sizeof(char *), (argc - i)+2);
        assert(mode_argv != NULL);

        // Copy over only the arguments needed for the mode
        mode_argc = eurephia_arraycp(i, argc, argv, mode_argv, (argc - i));

        // Call the mode function
        rc = mode_fnc(ctx, sess, cfg, mode_argc, mode_argv);
        free_nullsafe(ctx, mode_argv);

        return rc;
}
