/* usercerts.c  --  eurephiadm usercerts command:
 *                  Management of user account <-> certificate links
 *
 *  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   eurephiadm/commands/usercerts.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-12-28
 *
 * @brief  eurephiadm usercerts command.  Manages the user account and certificate links.
 *
 */

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

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

#define MODULE "eurephia::UserCerts" /**< Need to define the current 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 usercerts command
 *
 * @param page which help screen to display
 */
void display_usercerts_help(int page) {
        switch( page ) {
        case 'A':
                printf("The add mode will register a new link between a user account and a certificate.\n"
                       "\n"
                       "     -c | --certid         Required - Certificate ID\n"
                       "     -i | --uid            Required - User account ID\n"
#ifdef FIREWALL
                       "     -a | --accessprofile  Firewall profile ID to use for this access\n"
#endif
                       "\n"
                       );
                break;
        case 'D':
                printf("The delete mode will delete a link between a user account and a certificate.\n"
                       "\n"
                       "     -c | --certid         Certificate ID\n"
                       "     -i | --uid            User account ID\n"
                       "     -n | --uicid          Unique record id of certificate and user account link\n"
#ifdef FIREWALL
                       "     -a | --accessprofile  Firewall profile ID\n"
#endif
                       "\n"
                       );
                break;

        case 'l':
                printf("The list mode will list all registered links between user accounts and certificates.\n"
                       "\n"
                       "Options:\n"
                       "     -S | --sort           Define the sorting of the list\n"
                       "\n"
                       );
                break;

#ifdef FIREWALL
        case 'S':
                printf("The set-fwprofile mode will update the firewall access profile for "
                       "a given user-cert link\n\n"
                       "Options: (both required)\n"
                       "     -n | --uicid          Unique record id of certificate and user account link\n"
                       "     -a | --accessprofile  Firewall profile ID\n"
                       "\n");
                break;
#endif

        default:
                printf("Available modes for the usercerts command are:\n\n"
                       "     -A | --add            Register a new certificate and user-cert link\n"
                       "     -D | --delete         Delete a certificate and user-cert link\n"
#ifdef FIREWALL
                       "     -S | --set-fwprofile  Sets the firewall access profile for a user-cert link \n"
#endif
                       "     -l | --list           List all registered user-cert links\n"
                       "     -h | --help <mode>    Help about a specific mode\n\n");
                break;
        }
}


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


/**
 * Help screen wrapper for the usercerts 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_UserCerts2(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        e_options helpargs[] = {
                {"--list", "-l", 0},
                {"--add", "-A", 0},
                {"--delete", "-D", 0},
#ifdef FIREWALL
                {"--set-fwprofile", "-S", 0},
#endif
                {NULL, NULL, 0}
        };

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


/**
 * usercerts list mode.  Lists all registered user account and certificate links.
 *
 * @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_usercerts(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *list_xml = NULL, *srch_xml = NULL;
        xmlNode *srch_n = NULL, *fmap_n = NULL;
        int i = 0;
#ifdef FIREWALL
        const char *xsltparams[] = { "firewall", "'1'", NULL};
#else
        const char *xsltparams[] = { "firewall", "'0'", NULL};
#endif

        e_options listargs[] = {
                {"--sort", "-S", 1},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

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

        eurephiaXML_CreateDoc(ctx, 1, "usercerts", &srch_xml, &srch_n);
        assert( srch_xml != NULL && srch_n != NULL );
        xmlNewProp(srch_n, (xmlChar *) "mode", (xmlChar *) "search");

        fmap_n = xmlNewChild(srch_n, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(fmap_n, (xmlChar *) "table", (xmlChar *) "usercerts");


        // Parse arguments
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, listargs) ) {
                case 'S':
                        xmlNewChild(srch_n, NULL, (xmlChar *) "sortfields", (xmlChar *)optargs[0]);
                        break;

                case 'h':
                        display_usercerts_help('l');
                        xmlFreeDoc(srch_xml);
                        return 0;

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

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

        xslt_print_xmldoc(stdout, cfg, list_xml, "usercerts.xsl", xsltparams);
        xmlFreeDoc(list_xml);
        return 0;
}


/**
 * usercerts add and delete mode.  Modifies the user-certs links.
 *
 * @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_del_usercert(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *usercert_xml = NULL, *resxml = NULL;
        xmlNode *usercert_n = NULL;
        eurephiaRESULT *res = NULL;
        int i = 0, rc = 0, actmode = 0;
        char *certid = NULL, *uid = NULL, *uicid = NULL, *accessprofile = NULL;

        e_options addargs[] = {
                {"--uid", "-i", 1},
                {"--certid", "-c", 1},
                {"--uicid", "-n", 1},
#ifdef FIREWALL
                {"--accessprofile", "-a", 1},
#endif
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

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

        if( (strcmp(argv[0], "--add") == 0) || (strcmp(argv[0], "-A") == 0) ) {
                actmode = 'A';
        } else if( (strcmp(argv[0], "--delete") == 0) || (strcmp(argv[0], "-D") == 0) ) {
                actmode = 'D';
        }

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

        xmlNewProp(usercert_n, (xmlChar *) "mode", (xmlChar *) (actmode == 'D' ? "remove" : "register"));
        usercert_n = xmlNewChild(usercert_n, NULL, (xmlChar *) "fieldMapping", NULL);
        xmlNewProp(usercert_n, (xmlChar *) "table", (xmlChar *) "usercerts");

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

                case 'c':
                        if( atoi_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr,"%s: Certificate ID must be a positive number (>0)\n",MODULE);
                                rc = 1;
                                goto exit;
                        }
                        xmlNewChild(usercert_n, NULL, (xmlChar *) "certid", (xmlChar *) optargs[0]);
                        certid = optargs[0];
                        break;

#ifdef FIREWALL
                case 'a':
                        if( atoi_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr, "%s: Firewall profile ID must be a positive number (>0)\n",
                                        MODULE);
                                rc = 1;
                                goto exit;
                        }
                        xmlNewChild(usercert_n, NULL, (xmlChar *) "accessprofile", (xmlChar *) optargs[0]);
                        accessprofile = optargs[0];
                        break;
#endif
                case 'n':
                        if( actmode != 'D' ) {
                                fprintf(stderr, "%s: --uicid cannot be used with --add\n", MODULE);
                                rc = 1;
                                goto exit;
                        }
                        if( atoi_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr, "%s: uicid must be a positive number (>0)\n",
                                        MODULE);
                                rc = 1;
                                goto exit;
                        }
                        xmlNewChild(usercert_n, NULL, (xmlChar *) "uicid", (xmlChar *) optargs[0]);
                        uicid = optargs[0];

                        break;

                case 'h':
                        display_usercerts_help(actmode);
                        rc = 0;
                        goto exit;

                default:
                        rc = 1;
                        goto exit;
               }
        }

        if( (actmode == 'A') && ((certid == NULL) || (uid == NULL)) ) {
                fprintf(stderr, "%s: You must provide both a user ID (--uid) and "
                        "a certificate ID (--certid)\n", MODULE);
                rc = 1;
                goto exit;
        }

        if( (actmode == 'D') && (certid == NULL) && (uid == NULL)
            && (uicid == NULL) && (accessprofile == NULL)) {
                fprintf(stderr, "%s: You must provide at least --uid, --certid, "
                        "--uicid or --accessprofile\n", MODULE);
                rc = 1;
                goto exit;
        }

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

        res = eurephiaXML_ParseResultMsg(ctx, resxml);
        if( res == NULL ) {
                fprintf(stderr, "%s: Failed to update user <-> certificate link.  No result available.",
                        MODULE);
        } 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(resxml);

 exit:
        xmlFreeDoc(usercert_xml);
        return rc;
}


#ifdef FIREWALL
/**
 * usercerts set-fwprofile mode.  Changes the firewall profile for a specific user-certs link
 *
 * @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 set_fwprofile(eurephiaCTX *ctx, eurephiaSESSION *sess, eurephiaVALUES *cfg, int argc, char **argv) {
        xmlDoc *usercert_xml = NULL, *res_xml = NULL;
        xmlNode *usercert_n = NULL, *fmap_n = NULL;
        int rc = 0, i = 0;
        int accprf = 0, uicid = 0;

        e_options updateargs[] = {
                {"--uicid", "-n", 1},
                {"--accessprofile", "-a", 1},
                {"--help", "-h", 0},
                {NULL, NULL, 0}
        };

        // Setup a XML doc which contains information for the update
        eurephiaXML_CreateDoc(ctx, 1, "usercerts", &usercert_xml, &usercert_n);
        assert( (usercert_xml != NULL) && (usercert_n != NULL) );

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

        // Parse arguments
        for( i = 1; i < argc; i++ ) {
                switch( eurephia_getopt(&i, argc, argv, updateargs) ) {
                case 'a':
                        if( atoi_nullsafe(optargs[0]) < 1 ) {
                                fprintf(stderr, "%s: Firewall profile ID must be a positive number (>0)\n",
                                        MODULE);
                                rc = 1;
                                goto exit;
                        }
                        xmlNewChild(fmap_n, NULL, (xmlChar *) "accessprofile", (xmlChar *) optargs[0]);
                        accprf = 1; // Access profile is set
                        break;

                case 'n':
                        // The uicid value must not be used as a value in the <fieldMapping> tag, but
                        // must be an uucid attribute in the <usercerts> tag.  This is the
                        // ID to the record we will update.
                        uicid = atoi_nullsafe(optargs[0]);
                        if( uicid < 1 ) {
                                fprintf(stderr, "%s: uicid must be a positive number (>0)\n",
                                        MODULE);
                                rc = 1;
                                goto exit;
                        }
                        xmlNewProp(usercert_n, (xmlChar *) "uicid", (xmlChar *) optargs[0]);
                        break;

                case 'h':
                        display_usercerts_help('S');
                        rc = 0;
                        goto exit;

                default:
                        rc = 1;
                        goto exit;
               }
        }

        if( (uicid < 1) || (accprf != 1) ) {
                fprintf(stderr, "%s: You must provide --uicid and --accessprofile\n", MODULE);
                rc = 1;
                goto exit;
        }

        res_xml = eDBadminUserCertsLink(ctx, usercert_xml);
        if( res_xml == NULL ) {
                fprintf(stderr, "%s: Failed to update firewall access profile for user-cert link\n", MODULE);
                rc = 1;
                goto exit;
        } else {
		eurephiaRESULT *res = eurephiaXML_ParseResultMsg(ctx, res_xml);
		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;
		}
	}
        xmlFreeDoc(res_xml);

 exit:
        xmlFreeDoc(usercert_xml);
        return rc;
}
#endif


/**
 * Main function for the usercerts 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_UserCerts(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},
                {"--add", "-A", 0},
                {"--delete", "-D", 0},
#ifdef FIREWALL
                {"--set-fwprofile", "-S", 0},
#endif
                {"--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_usercerts;
                        break;

                case 'h':
                        mode_fnc = help_UserCerts2;
                        break;

                case 'A':
                        mode_fnc = add_del_usercert;
                        break;

                case 'D':
                        mode_fnc = add_del_usercert;
                        break;

#ifdef FIREWALL
                case 'S':
                        mode_fnc = set_fwprofile;
                        break;
#endif

                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;
}
