/* users.c  --  eurephiadm command - User Account Management
 *
 *  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   users.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-12-01
 *
 * @brief  eurephiadm users command.  User account management command.
 *
 */

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <assert.h>

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

#define MODULE "eurephia::Users" /**< 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 "../get_console_input.h"
#include "../parse_certificate_files.h"
#include "../xsltparser.h"


/**
 * Forward declaration of a function already found in certificates.c
 */
int register_certificate(eurephiaCTX *ctx, int depth, const char *digest,
                         const char *cname, const char *org, const char *email);


/**
 * Help screens for the users command
 *
 * @param page which help screen to display
 */
void display_users_help(int page) {
        printf("\n%s -- Administer user accounts\n\n", MODULE);

        switch( page ) {
        case 'l':
                printf("The list mode can accept one parameter:\n\n"
                       "     -S | --sort <sort key>     Decide the sort order of the user list\n"
                       "\n"
                       "Available sort keys are: uid, username, activated, deactivated and lastaccess\n\n");
                break;

        case 's':
                printf("The show mode shows more information about a user account.\n"
                       "One of the following arguments are required:\n\n"
                       "     -i | --uid              Numeric user ID\n"
                       "     -u | --username         User name\n"
                       "\n"
                       "Optional arguments are:\n\n"
                       "     -l | --lastlog          Show users lastlog entries\n"
                       "     -L | --lastlog-details  Show a more verbose lastlog\n"
                       "     -a | --attempts         Show users failed attempts and blacklisting\n"
                       "     -b | --blacklist        Alias for --attempts\n"
                       "\n");
                break;

        case 'a':
                printf("The activate mode activates an account which not activated or deactivated.\n"
                       "One of the following arguments are required:\n\n"
                       "     -i | --uid          Numeric user ID\n"
                       "     -u | --username     User name\n\n");
                break;

        case 'd':
                printf("The deactivate mode deactivates a user account.\n"
                       "One of the following arguments are required:\n\n"
                       "     -i | --uid          Numeric user ID\n"
                       "     -u | --username     User name\n\n");
                break;

        case 'A':
                printf("The add user mode registers a new user account.\n"
                       "Both of the following arguments are required:\n\n"
                       "     -u | --username     User name to use for the new account (required)\n"
                       "     -P | --password     Assign a new password via the command line.\n\n"
                       "To register this new account against a certificate you can use the\n"
                       "following arguments.  These arguments cannot be used together.\n\n"
                       "     -C | --certid       Use already registered certificate, identified by certid.\n"
                       "     -D | --digest       Use already registered certificate, identified by digest.\n"
                       "     -c | --certfile     Use the given certificate file and register it along with\n"
                       "                         the account.\n"
                       "     -2 | --pkcs12       Certificate file is using the PKCS#12 format.\n\n");
                break;

        case 'D':
                printf("The delete user mode will delete a user account from the database.\n"
                       "One of the following arguments are required:\n\n"
                       "     -i | --uid          Numeric user ID\n"
                       "     -u | --username     User name\n\n");
                break;

        case 'p':
                printf("The change password mode is used for changing password for other\n"
                       "persons than yourself.\n\n"
                       "One of the following arguments are required:\n\n"
                       "     -i | --uid          Numeric user ID\n"
                       "     -u | --username     User name\n\n");
                break;

        default:
                printf("Available modes:\n"
                       "     -l | --list         List all user accounts\n"
                       "     -s | --show         Show user account details\n"
                       "     -a | --activate     Activate a user account\n"
                       "     -d | --deactivate   Deactivate a user account\n"
                       "     -A | --add          Add a new user account\n"
                       "     -D | --delete       Delete a user account\n"
                       "     -p | --password     Change password on a users account\n"
                       "     -h | --help <mode>  Further help for these modes\n\n");
                break;
        }
}


/**
 * Help screen wrapper for the users 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_Users2(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        e_options helpargs[] = {
                {"--list", "-l", 0},
                {"--show", "-s", 0},
                {"--activate", "-a", 0},
                {"--deactivate", "-d", 0},
                {"--add", "-A", 0},
                {"--delete", "-D", 0},
                {"--password", "-p", 0},
                {NULL, NULL, 0}
        };

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

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


/**
 * users list mode.  Lists all registered users.
 *
 * @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_users(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *qrydoc = NULL, *userlist = NULL;
        xmlNode *qry_n = NULL, *fmap_n = NULL;
        int i = 0, rc = 0;
        char *sortkeys = NULL;
        const char *xsltparams[] = {"view", "'userlist'", NULL};

        e_options listargs[] = {
                {"--sort", "-S", 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 'S':
                        sortkeys = optargs[0];
                        i++;
                        break;

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

                default:
                        return 1;
                }
        }

        // Create a query document
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &qrydoc, &qry_n);
        assert( (qrydoc != NULL) && (qry_n != NULL) );

        xmlNewProp(qry_n, (xmlChar *) "mode", (xmlChar *) "view");
        xmlNewChild(qry_n, NULL, (xmlChar *) "sortkeys", (xmlChar *)sortkeys);
        xmlNewChild(qry_n, NULL, (xmlChar *) "extractFlags", (xmlChar *) "1");

        fmap_n = xmlNewChild(qry_n, NULL, (xmlChar *) "fieldMapping", NULL);
        assert( fmap_n != NULL );
        xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *) "users");

        // Retrieve the user list - and display it
        userlist = eDBadminUserAccount(ctx, qrydoc);
        if( eurephiaXML_IsResultMsg(ctx, userlist) ) {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, userlist);
                if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        rc = 1;
                } else {
                        rc = 2;
                }
                free_nullsafe(ctx, res);
        } else {
                xslt_print_xmldoc(stdout, cfg, userlist, "users.xsl", xsltparams);
                rc = 0;
        }
        xmlFreeDoc(userlist);
        xmlFreeDoc(qrydoc);

        return rc;
}


/**
 * users show mode.  Show account information for a particular user
 *
 * @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 show_user(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        int i, crit_set = 0, lastlog_verb = 0;
        long int show_info = USERINFO_user | USERINFO_certs;
        xmlDoc *user_xml = NULL, *srch_xml = NULL;
        xmlNode *srch_root = NULL, *fmap_n = NULL;
        xmlChar flags[34];

#ifdef FIREWALL
        const char *xsltparams[] = {"view", NULL, "firewall", "'1'", "view_digest", "'0'", NULL};
#else
        const char *xsltparams[] = {"view", NULL, "firewall", "'0'", "view_digest", "'0'", NULL};
#endif

        e_options activargs[] = {
                {"--uid", "-i", 1},
                {"--username", "-u", 1},
                {"--lastlog", "-l", 0},
                {"--lastlog-details", "-L", 0},
                {"--attempts", "-a", 0},
                {"--blacklist", "-b", 0}, // Alias for -a | --attempts
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

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


        // Create a UserAccount query XML document
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &srch_xml, &srch_root);
        xmlNewProp(srch_root, (xmlChar*) "mode", (xmlChar *) "view");

        // Setup the fieldMapping - to narrow the search
        fmap_n = xmlNewChild(srch_root, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *)"users");

        // Argument parsing
        crit_set = 0;
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, activargs) ) {
                case 'i':
                        if( atoi_nullsafe(optargs[0]) == 0 ) {
                                fprintf(stderr, "%s: Invalid user id\n", MODULE);
                                return 1;
                        }
                        xmlNewChild(fmap_n, NULL, (xmlChar *)"uid", (xmlChar *) optargs[0]);
                        crit_set++;
                        break;

                case 'u':
                        if( strlen_nullsafe(optargs[0]) < 3 ) {
                                fprintf(stderr, "%s: User name too short\n", MODULE);
                                return 1;
                        }
                        xmlNewChild(fmap_n, NULL, (xmlChar *)"username", (xmlChar *)optargs[0]);
                        crit_set++;
                        break;

                case 'a':
                case 'b':
                        show_info = USERINFO_attempts | USERINFO_blacklist;
                        break;

                case 'L':
                        lastlog_verb = 1;
                case 'l':
                        show_info = USERINFO_lastlog;
                        break;

                case 'h':
                        display_users_help('s');
                        xmlFreeDoc(srch_xml);
                        return 0;

                default:
                        xmlFreeDoc(srch_xml);
                        return 1;
                }
        }

        // Check if we have the needed search criteria
        if( crit_set == 0 ) {
                fprintf(stderr,
                        "%s:  Missing required parameter.  You must provide either user id or user name\n",
                        MODULE);
                xmlFreeDoc(srch_xml);
                return 1;
        }

        if( crit_set > 1 ) {
                fprintf(stderr,
                        "%s: You cannot have several search criteria (-i | -u)\n", MODULE);
                xmlFreeDoc(srch_xml);
                return 1;
        }

        xmlStrPrintf(flags, 32, "%ld%c", show_info, 0);
        xmlNewChild(srch_root, NULL, (xmlChar *) "extractFlags", flags);

        // Do the query against the database
        if( (user_xml = eDBadminUserAccount(ctx, srch_xml)) == NULL )  {
                xmlFreeDoc(srch_xml);
                fprintf(stderr, "%s: User not found\n", MODULE);
                return 1;
        }
        xmlFreeDoc(srch_xml);

        if( eurephiaXML_IsResultMsg(ctx, user_xml) ) {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, user_xml);
                if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        free_nullsafe(ctx, res);
                        xmlFreeDoc(user_xml);
                        return 1;
                }
                free_nullsafe(ctx, res);
        }

        if( show_info & USERINFO_user ) {
                xsltparams[1] = "'userinfo'";
                xslt_print_xmldoc(stdout, cfg, user_xml, "users.xsl", xsltparams);
        }

        // Show associated certificates if we was asked to show this info
        if( show_info & USERINFO_certs ) {
                xsltparams[1] = "''";
                xslt_print_xmldoc(stdout, cfg, user_xml, "certificates.xsl", xsltparams);
        }

        // Show lastlog for the user, if requested
        if( show_info & USERINFO_lastlog ) {
                xmlNode *user_n = NULL;

                xsltparams[1] = "'list'";
                if( lastlog_verb ) {
                        xsltparams[1] = "'details'";
                }

                user_n = eurephiaXML_getRoot(ctx, user_xml, "UserAccount", 1);
                user_n = xmlFindNode(user_n, "Account");
                if( user_n == NULL ) {
                        fprintf(stderr, "Could not retrieve valid data\n");
                        xmlFreeDoc(user_xml);
                        return 1;
                }

                printf("** Lastlog entries for %s\n\n", xmlGetNodeContent(user_n, "username"));

                xslt_print_xmldoc(stdout, cfg, user_xml, "lastlog.xsl", xsltparams);
        }

        // Show login attempt info and/or blacklist info
        if( (show_info & USERINFO_attempts) || (show_info & USERINFO_blacklist) ) {
                xsltparams[1] = "'attemptblacklist'";
                xslt_print_xmldoc(stdout, cfg, user_xml, "users.xsl", xsltparams);
        }

        // Clean up
        xmlFreeDoc(user_xml);

        return 0;
}


/**
 * This function handles activation, deactivation and deletion of an account
 *
 * @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 account_activation(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *user_xml = NULL, *update_xml = NULL, *srch_xml = NULL, *tmp_xml = NULL, *res_xml = NULL;
        xmlNode *user_n = NULL, *update_n = NULL, *srch_root = NULL, *tmp_n = NULL, *fmap_n = NULL;
        eurephiaRESULT *res = NULL;
        char *uid_str = NULL, *activated = NULL, *deactivated = NULL, *newpwd = NULL;
        int i, actmode = 0, rc = 0, crit_set = 0;
        xmlChar flags[34];

        e_options activargs[] = {
                {"--uid", "-i", 1},
                {"--username", "-u", 1},
                {"--new-password", "-P", 1},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

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

        if( (strcmp(argv[0], "--activate") == 0) || (strcmp(argv[0], "-a") == 0) ) {
                actmode = 'a';
        } else if( (strcmp(argv[0], "--deactivate") == 0) || (strcmp(argv[0], "-d") == 0) ) {
                actmode = 'd';
        } else if( (strcmp(argv[0], "--delete") == 0) || (strcmp(argv[0], "-D") == 0) ) {
                actmode = 'D';
        } else if( (strcmp(argv[0], "--password") == 0) || (strcmp(argv[0], "-p") == 0) ) {
                actmode = 'p';
        } else {
                fprintf(stderr, "%s: System error - illegal users mode'%s'\n", MODULE, argv[0]);
                return 1;
        }

        // Create a UserAccount query XML document
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &srch_xml, &srch_root);
        xmlNewProp(srch_root, (xmlChar*) "mode", (xmlChar *) "view");

        // Setup the fieldMapping - to narrow the search
        fmap_n = xmlNewChild(srch_root, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *)"users");

        xmlStrPrintf(flags, 32, "%u%c", USERINFO_user, 0);
        xmlNewChild(srch_root, NULL, (xmlChar *) "extractFlags", flags);

        // Argument parsing
        crit_set = 0;
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, activargs) ) {
                case 'i':
                        if( atoi_nullsafe(optargs[0]) == 0 ) {
                                fprintf(stderr, "%s: Invalid user id\n", MODULE);
                                return 1;
                        }
                        xmlNewChild(fmap_n, NULL, (xmlChar *)"uid", (xmlChar *) optargs[0]);
                        crit_set++;
                        break;

                case 'u':
                        if( strlen_nullsafe(optargs[0]) < 3 ) {
                                fprintf(stderr, "%s: User name too short\n", MODULE);
                                return 1;
                        }
                        xmlNewChild(fmap_n, NULL, (xmlChar *)"username", (xmlChar *)optargs[0]);
                        crit_set++;
                        break;

                case 'h':
                        display_users_help(actmode);
                        xmlFreeDoc(srch_xml);
                        return 0;

                case 'P':
                        newpwd = strdup_nullsafe(optargs[0]);
                        break;

                default:
                        xmlFreeDoc(srch_xml);
                        return 1;
                }
        }

        // Check if we have the needed search criteria
        if( crit_set == 0 ) {
                fprintf(stderr,
                        "%s:  Missing required parameter.  You must provide either user id or user name\n",
                        MODULE);
                xmlFreeDoc(srch_xml);
                return 1;
        }

        if( crit_set > 1 ) {
                fprintf(stderr,
                        "%s: You cannot have several search criteria (-i | -u)\n", MODULE);
                xmlFreeDoc(srch_xml);
                return 1;
        }

        // Do the query against the database
        if( (user_xml = eDBadminUserAccount(ctx, srch_xml)) == NULL )  {
                xmlFreeDoc(srch_xml);
                fprintf(stderr, "%s: User not found\n", MODULE);
                return 1;
        }
        xmlFreeDoc(srch_xml);

        if( eurephiaXML_IsResultMsg(ctx, user_xml) ) {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, user_xml);
                if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        free_nullsafe(ctx, res);
                        xmlFreeDoc(user_xml);
                        return 1;
                }
                free_nullsafe(ctx, res);
        }

        user_n = eurephiaXML_getRoot(ctx, user_xml, "UserAccount", 1);
        user_n = xmlFindNode(user_n, "Account");
        if( user_n == NULL ) {
                xmlFreeDoc(user_xml);
                fprintf(stderr, "%s: No user account information found\n", MODULE);
                return 1;
        }
        uid_str     = xmlGetAttrValue(user_n->properties, "uid");
        activated   = defaultValue(xmlGetNodeContent(user_n, "activated"), NULL);
        deactivated = defaultValue(xmlGetNodeContent(user_n, "deactivated"), NULL);

        // Create a new XML document which will be used to update the user account
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &update_xml, &update_n);
        assert( (update_xml != NULL) && (update_n != NULL) );
        xmlNewProp(update_n, (xmlChar *) "mode", (xmlChar *) "update");
        xmlNewProp(update_n, (xmlChar *) "uid", (xmlChar *) uid_str);

        // Add fieldMapping - to correctly map eurephia fields into the database fields
        update_n = xmlNewChild(update_n, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(update_n, (xmlChar *) "table", (xmlChar *) "users");

        switch( actmode ) {
        case 'a':  // Activate a user account
                if( (activated != NULL)  && (deactivated == NULL) ) {
                        printf("%s: User account is already active\n", MODULE);
                        goto exit;
                } else {
                        // Set activated field to now()
                        xmlNewChild(update_n, NULL,
                                     (xmlChar *) "activated", (xmlChar *) "CURRENT_TIMESTAMP");

                        // Remove deactivated flag
                        tmp_n = xmlNewChild(update_n, NULL,(xmlChar *) "deactivated", NULL);
                        xmlNewProp(tmp_n, (xmlChar *) "setnull", (xmlChar *) "1");
                }

                // Do the update of the user account
                res_xml = eDBadminUserAccount(ctx, update_xml);
                break;

        case 'd':  // Deactivate a user account
                if( (activated != NULL)  && (deactivated != NULL) ) {
                        printf("User account is already deactivated\n");
                        goto exit;
                } else if( (activated != NULL) ) {
                        // Set deactivated to now()
                        xmlNewChild(update_n, NULL,
                                     (xmlChar *) "deactivated", (xmlChar *) "CURRENT_TIMESTAMP");

                } else {
                        fprintf(stderr, "%s: User account is not activated yet\n", MODULE);
                        goto exit;
                }

                // Do the update of the user account
                res_xml = eDBadminUserAccount(ctx, update_xml);
                break;

        case 'p':  // Change password for a user

                // If we do not have a password .... ask for password via console
                if( newpwd == NULL ) {
                        char *chkpwd = NULL;

                        newpwd = (char *) malloc_nullsafe(ctx, 66);
                        assert(newpwd != NULL);
                        chkpwd = (char *) malloc_nullsafe(ctx, 66);
                        assert(chkpwd != NULL);

                        get_console_input(newpwd, 64, "Password for user:", 1);
                        if( strlen_nullsafe(newpwd) < 4 ) {
                                free_nullsafe(ctx, newpwd);
                                free_nullsafe(ctx, chkpwd);
                                fprintf(stderr, "%s: Password is too short\n", MODULE);
                                goto exit;
                        }

                        get_console_input(chkpwd, 64, "Verify password for user:", 1);
                        if( strcmp(newpwd, chkpwd) != 0 ) {
                                free_nullsafe(ctx, newpwd);
                                free_nullsafe(ctx, chkpwd);
                                fprintf(stderr, "%s: Passwords didn't match\n", MODULE);
                                goto exit;
                        }
                        free_nullsafe(ctx, chkpwd);
                }

                // Update with new password
                tmp_n = xmlNewChild(update_n, NULL, (xmlChar *) "password", (xmlChar *)newpwd);
                xmlNewProp(tmp_n, (xmlChar *) "pwhash", (xmlChar *) "none");
                res_xml = eDBadminUserAccount(ctx, update_xml);

                free_nullsafe(ctx, newpwd);
                break;

        case 'D':  // Delete user account
                xmlFreeDoc(update_xml); // We need another XML doc to delete users

                eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &update_xml, &update_n);
                assert( (update_xml != NULL) && (update_n != NULL) );
                xmlNewProp(update_n, (xmlChar *) "mode", (xmlChar *) "delete");
                xmlNewProp(update_n, (xmlChar *) "uid", (xmlChar *) uid_str);
                update_n = xmlNewChild(update_n, NULL, (xmlChar *) "fieldMapping", NULL);
                xmlNewProp(update_n, (xmlChar *) "table", (xmlChar *) "users");

                res_xml = eDBadminUserAccount(ctx, update_xml);

                // Check if this update failed and abort further operations if it did
                if( eurephiaXML_IsResultMsg(ctx, res_xml) ) {
                        res = eurephiaXML_ParseResultMsg(ctx, res_xml);
                        if( (res == NULL) || (res->resultType == exmlERROR) ) {
                                free_nullsafe(ctx, res);
                                break;
                        }
                        free_nullsafe(ctx, res);
                } else {
                        // No ResultMsg document was returned, which is also wrong.
                        break;
                }

                // Delete links between certificates associated to this user account
                xmlFreeDoc(update_xml);
                eurephiaXML_CreateDoc(ctx, 1, "usercerts", &update_xml, &update_n);
                assert( (update_xml != NULL) && (update_n != NULL) );

                xmlNewProp(update_n, (xmlChar *) "mode", (xmlChar *) "remove");
                update_n = xmlNewChild(update_n, NULL, (xmlChar *) "fieldMapping", NULL);
                xmlNewProp(update_n, (xmlChar *) "table", (xmlChar *) "usercerts");
                xmlNewChild(update_n, NULL, (xmlChar *) "uid", (xmlChar *) uid_str);

                tmp_xml = eDBadminUserCertsLink(ctx, update_xml);
                res = eurephiaXML_ParseResultMsg(ctx, tmp_xml);
                if( res == NULL ) {
                        fprintf(stderr, "%s: Failed to remove user <-> certificate link.  "
                                "No results received\n", MODULE);
                        rc = 0;
                } else if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        rc = 0;
                } else {
                        fprintf(stdout, "%s: %s\n", MODULE, res->message);
                        rc = 1;
                }

                // Remove all user access levels for the user.
                {
                        xmlDoc *acclvl_d = NULL, *acclvl_res_xml = NULL;
                        xmlNode *acclvl_n = NULL, *acclvl_fm = NULL;
                        eurephiaRESULT *acclvl_res = NULL;

                        eurephiaXML_CreateDoc(ctx, 1, "admin_access", &acclvl_d, &acclvl_n);
                        xmlNewProp(acclvl_n, (xmlChar *) "mode", (xmlChar *) "revoke");
                        acclvl_fm = xmlNewChild(acclvl_n, NULL, (xmlChar *) "fieldMapping", NULL);
                        xmlNewProp(acclvl_fm, (xmlChar *) "table", (xmlChar *) "eurephia_adminaccess");
                        xmlNewChild(acclvl_fm, NULL, (xmlChar *) "uid", (xmlChar *) uid_str);

                        acclvl_res_xml = eDBadminAccessLevel(ctx, acclvl_d);
                        acclvl_res = eurephiaXML_ParseResultMsg(ctx, acclvl_res_xml);
                        if( acclvl_res->resultType == exmlERROR ) {
                                fprintf(stderr, "%s: %s\n", MODULE, acclvl_res->message);
                                rc = 0;
                        } else {
                                fprintf(stdout, "%s: %s\n", MODULE, acclvl_res->message);
                                rc = rc ? 1 : 0;
                        }
                        free_nullsafe(ctx, res);
                        free_nullsafe(ctx, acclvl_res);
                        xmlFreeDoc(acclvl_res_xml);
                        xmlFreeDoc(acclvl_d);
                }
                free_nullsafe(ctx, res);
                xmlFreeDoc(tmp_xml);
                break;
        }

        if( eurephiaXML_IsResultMsg(ctx, res_xml) ) {
                eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, res_xml);
                if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                        rc = 0;
                } else {
                        fprintf(stdout, "%s: %s\n", MODULE, res->message);
                        rc = rc ? 1 : 0;
                }
                free_nullsafe(ctx, res);
        } else {
                fprintf(stderr, "%s: Unknown error\n", MODULE);
        }
        xmlFreeDoc(res_xml);

 exit:
        xmlFreeDoc(user_xml);
        xmlFreeDoc(update_xml);
        return (rc != 1);
}


/**
 * users add mode.  Creates a new user account.
 *
 * @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 add_user(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *user_xml = NULL, *resxml = NULL;
        xmlNode *node = NULL, *node2 = NULL;
        eurephiaRESULT *res = NULL;
        struct stat cert_stat;
        int i = 0, certid = 0, uid = 0, certfile_format = CERTFILE_PEM;
        char *uname = NULL, *passwd = NULL, *certfile = NULL, *digest = NULL;

        e_options addu_args[] = {
                {"--username", "-u", 1},
                {"--password", "-P", 1},
                {"--certid",   "-C", 1},
                {"--certfile", "-c", 1},
                {"--digest",   "-D", 1},
                {"--pkcs12",   "-2", 0},
                {"--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, addu_args) ) {
                case 'u':
                        if( strlen_nullsafe(optargs[0]) < 3 ) {
                                fprintf(stderr, "%s: username is too short\n", MODULE);
                                return 1;
                        }
                        uname = optargs[0];
                        break;

                case 'P':
                        if( strlen_nullsafe(optargs[0]) < 4 ) {
                                fprintf(stderr, "%s: password is too short\n", MODULE);
                                return 1;
                        }
                        passwd = strdup_nullsafe(optargs[0]);
                        break;

                case 'C':
                        if( (certid = atoi_nullsafe(optargs[0])) < 1 ) {
                                fprintf(stderr, "%s: Invalid certid (numeric value > 0)\n", MODULE);
                                return 1;
                        }
                        break;

                case 'c': // Check if certfile exists and is readable for us
                        if( strlen_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr, "%s: certfile is too short\n", MODULE);
                                return 1;
                        }
                        certfile = optargs[0];

                        if( stat(certfile, &cert_stat) == -1 ) {
                                fprintf(stderr, "%s: Could not access certfile: %s (%s)\n", MODULE,
                                        certfile, strerror(errno));
                                return 1;
                        }

                        if( cert_stat.st_size == 0 ) {
                                fprintf(stderr, "%s: certfile '%s' is empty\n", MODULE, certfile);
                                return 1;
                        }
                        break;

                case '2':
                        certfile_format = CERTFILE_PKCS12;
                        break;

                case 'D':
                        if( strlen_nullsafe(optargs[0]) < 59 ) {
                                fprintf(stderr, "%s: Certificate digest is too short\n", MODULE);
                                return 1;
                        }
                        digest = optargs[0];
                        break;

                case 'h':
                        display_users_help('A');
                        return 0;

                default:
                        return 1;
                }
        }

        // Make sure received arguments are sane
        if( uname == NULL ) {
                fprintf(stderr, "%s: Missing user name\n", MODULE);
                return 1;
        }

        if( ((certid > 0) && (digest != NULL))
            || ((certid > 0) && (certfile != NULL))
            || ((digest != NULL) && (certfile != NULL)) ) {
                fprintf(stderr, "%s: --certid, --certfile and --digest cannot be used together\n", MODULE);
                return 1;
        }

        // If we do not have a password .... ask for password via console
        if( passwd == NULL ) {
                char *chkpwd = NULL;

                passwd = (char *) malloc_nullsafe(ctx, 66);
                assert(passwd != NULL);

                chkpwd = (char *) malloc_nullsafe(ctx, 66);
                assert(chkpwd != NULL);

                get_console_input(passwd, 64, "Password for user:", 1);
                if( strlen_nullsafe(passwd) < 4 ) {
                        free_nullsafe(ctx, passwd);
                        free_nullsafe(ctx, chkpwd);
                        fprintf(stderr, "%s: Password is too short\n", MODULE);
                        return 1;
                }

                get_console_input(chkpwd, 64, "Verify password for user:", 1);
                if( strcmp(passwd, chkpwd) != 0 ) {
                        free_nullsafe(ctx, passwd);
                        free_nullsafe(ctx, chkpwd);
                        fprintf(stderr, "%s: Passwords didn't match\n", MODULE);
                        return 1;
                }
                free_nullsafe(ctx, chkpwd);
        }

        // Prepare add user XML document with fieldMapping
        eurephiaXML_CreateDoc(ctx, 1, "UserAccount", &user_xml, &node);
        xmlNewProp(node, (xmlChar *) "mode", (xmlChar *) "add");

        node = xmlNewChild(node, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(node, (xmlChar *) "table", (xmlChar *) "users");

        xmlNewChild(node, NULL, (xmlChar *) "username", (xmlChar *) uname);
        node2 = xmlNewChild(node, NULL, (xmlChar *) "password", (xmlChar *) passwd);
        xmlNewProp(node2, (xmlChar *) "pwhash", (xmlChar *) "none");

        // Add the user
        resxml = eDBadminUserAccount(ctx, user_xml);
        xmlFreeDoc(user_xml);
        memset(passwd, 0, strlen_nullsafe(passwd));
        free_nullsafe(ctx, passwd);
        if( (resxml == NULL) || !eurephiaXML_IsResultMsg(ctx, resxml) ) {
                fprintf(stderr, "%s: Failed to register the user.  Unknown failure.\n", MODULE);
                return 1;
        }

        res = eurephiaXML_ParseResultMsg(ctx, resxml);
        if( res == NULL ) {
                fprintf(stderr, "%s: Failed to register the user.  No results returned.\n", MODULE);
                xmlFreeDoc(resxml);
                return 1;
        } else if( res->resultType == exmlERROR ) {
                fprintf(stderr, "%s: %s\n", MODULE, res->message);
                xmlFreeDoc(resxml);
                free_nullsafe(ctx, res);
                return 1;
        }

        if( xmlStrcmp(res->details->children->name, (xmlChar *) "UserAccount") != 0 ) {
                fprintf(stderr, "%s: Invalid result value.  User account might be registered\n", MODULE);
                xmlFreeDoc(resxml);
                free_nullsafe(ctx, res);
                return 1;
        }

        fprintf(stdout, "%s: %s\n", MODULE, res->message);
        uid = atoi_nullsafe(xmlGetAttrValue(res->details->properties, "uid"));
        xmlFreeDoc(resxml);
        free_nullsafe(ctx, res);

        if( (digest != NULL) || (certfile != NULL) ) {
                if( digest != NULL ) {
                        xmlDoc *cert_xml = NULL, *certlist = NULL;
                        xmlNode *cert_n = NULL;
                        int certcount = 0;

                        // when we have certificate digest, look that up
                        eurephiaXML_CreateDoc(ctx, 1, "certificates", &cert_xml, &cert_n);
                        assert( (cert_xml != NULL) && (cert_n != NULL) );
                        xmlNewProp(cert_n, (xmlChar *) "mode", (xmlChar *) "list");
                        xmlNewChild(cert_n, NULL, (xmlChar *) "sortkeys", (xmlChar *) "certid");

                        cert_n = xmlNewChild(cert_n, NULL, (xmlChar *) "fieldMapping", NULL);
                        xmlNewProp(cert_n, (xmlChar *) "table", (xmlChar *) "certificates");
                        xmlNewChild(cert_n, NULL, (xmlChar *) "digest", (xmlChar *) digest);

                        certlist = eDBadminCertificate(ctx, cert_xml);
                        if( certlist == NULL ) {
                                fprintf(stderr, "%s: Error while looking up certificate info.\n"
                                        "%s: User account is not associated with any certificates\n",
                                        MODULE, MODULE);
                                xmlFreeDoc(cert_xml);
                                goto exit;
                        }
                        xmlFreeDoc(cert_xml);

                        cert_n = eurephiaXML_getRoot(ctx, certlist, "certificates", 1);
                        if( cert_n == NULL ) {
                                fprintf(stderr, "%s: Could not find certificates root node in XML document. "
                                        "No association done.\n", MODULE);
                                xmlFreeDoc(certlist);
                                goto exit;
                        }
                        certcount = atoi_nullsafe(xmlGetAttrValue(cert_n->properties, "certificates"));
                        if( certcount == 0) {
                                fprintf(stderr, "%s: No certificates was found. No association is done.\n",
                                        MODULE);
                                xmlFreeDoc(certlist);
                                goto exit;
                        } else if( certcount > 1 ) {
                                fprintf(stderr, "%s: More than one certificates was found. "
                                        "No association is done.\n", MODULE);
                                xmlFreeDoc(certlist);
                                goto exit;
                        }

                        // Get the first and only certificate node
                        cert_n = xmlFindNode(cert_n, "certificate");
                        if( cert_n == NULL ) {
                                fprintf(stderr, "%s: Could not find certificate node in XML document. "
                                        "No association done.\n", MODULE);
                                xmlFreeDoc(certlist);
                                goto exit;
                        }
                        // finally, we should have located the certid in the XML
                        certid = atoi_nullsafe(xmlGetAttrValue(cert_n->properties, "certid"));
                        xmlFreeDoc(certlist);
                } else if( certfile != NULL ) {
                        // when we have certfile - register the certificate and register the link
                        certinfo *ci = NULL;

                        ci = Cert_ParseFile(certfile, certfile_format);
                        if( ci == NULL ) {
                                fprintf(stderr, "%s: Could not parse the certificate file.  "
                                        "No association done.\n", MODULE);
                                goto exit;
                        }

                        certid = register_certificate(ctx, 0, ci->digest, ci->common_name, ci->org, ci->email);
                        if( certid == 0 ) {
                                fprintf(stderr, "%s: Failed to register certificate file.  No association done.",
                                        MODULE);
                        }
                }
        }

        // If we have a certificate id, register a certificate link to the user account
        if( (certid > 0) && (uid > 0) ) {
                xmlDoc *usercert_xml = NULL, *res_xml = NULL;
                xmlNode *usercert_n = NULL;
                eurephiaRESULT *res = NULL;
                xmlChar tmp[66];

                memset(&tmp, 0, 66);
                eurephiaXML_CreateDoc(ctx, 1, "usercerts", &usercert_xml, &usercert_n);
                assert( (usercert_xml != NULL) && (usercert_n != NULL) );

                xmlNewProp(usercert_n, (xmlChar *) "mode", (xmlChar *) "register");
                usercert_n = xmlNewChild(usercert_n, NULL, (xmlChar *) "fieldMapping", NULL);
                xmlNewProp(usercert_n, (xmlChar *) "table", (xmlChar *) "usercerts");

                xmlStrPrintf(tmp, 64, "%i%c", uid, '\0');
                xmlNewChild(usercert_n, NULL, (xmlChar *) "uid", tmp);
                xmlStrPrintf(tmp, 64, "%i%c", certid, '\0');
                xmlNewChild(usercert_n, NULL, (xmlChar *) "certid", tmp);

                res_xml = eDBadminUserCertsLink(ctx, usercert_xml);
                if( res_xml == NULL ) {
                        fprintf(stderr, "%s: Failed to register user <-> certificate link\n", MODULE);
                        goto exit;
                }

                res = eurephiaXML_ParseResultMsg(ctx, res_xml);
                if( res == NULL ) {
                        fprintf(stderr, "%s: Failed to register user <-> certificate link.  "
                                "No results received\n", MODULE);
                } else if( res->resultType == exmlERROR ) {
                        fprintf(stderr, "%s: %s\n", MODULE, res->message);
                }
                free_nullsafe(ctx, res);
                xmlFreeDoc(res_xml);
        }
 exit:
        return (uid > 0);
}


/**
 * Main function for the users 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_Users(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},
                {"--show", "-s", 0},
                {"--activate", "-a", 0},
                {"--deactivate", "-d", 0},
                {"--add", "-A", 0},
                {"--delete", "-D", 0},
                {"--password", "-p", 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_users;
                        break;

                case 's':
                        mode_fnc = show_user;
                        break;

                case 'a':
                        mode_fnc = account_activation;
                        break;

                case 'd':
                        mode_fnc = account_activation;
                        break;

                case 'h':
                        mode_fnc = help_Users2;
                        break;

                case 'A':
                        mode_fnc = add_user;
                        break;

                case 'D':
                        mode_fnc = account_activation;
                        break;

                case 'p':
                        mode_fnc = account_activation;
                        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, "Unknown argument.  No mode given\n");
                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;
}
