/*
 * security service wrapper to support pluggable security models 
 *
 * Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 *
 * Portions of this file are copyrighted by:
 * Copyright (c) 2016 VMware, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */

#include <net-snmp/net-snmp-config.h>
#include <stdio.h>
#include <ctype.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <net-snmp/types.h>
#include <net-snmp/output_api.h>
#include <net-snmp/config_api.h>
#include <net-snmp/utilities.h>

#include <net-snmp/library/snmp_api.h>
#include <net-snmp/library/snmp_enum.h>
#include <net-snmp/library/callback.h>
#include <net-snmp/library/snmp_secmod.h>
#include <net-snmp/library/snmpv3-security-includes.h>

#include <net-snmp/net-snmp-features.h>

static struct snmp_secmod_list *registered_services = NULL;

static SNMPCallback set_default_secmod;

void
init_secmod(void)
{
    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_SESSION_INIT, set_default_secmod,
                           NULL);

    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defSecurityModel",
			       NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SECMODEL);
    /*
     * this file is generated by configure for all the stuff we're using 
     */
#include "snmpsm_init.h"
}

void
shutdown_secmod(void)
{
    #include "snmpsm_shutdown.h"
}

int
register_sec_mod(int secmod, const char *modname,
                 struct snmp_secmod_def *newdef)
{
    int             result = 0;
    struct snmp_secmod_list *sptr;
    char           *othername, *modname2 = NULL;

    for (sptr = registered_services; sptr; sptr = sptr->next) {
        if (sptr->securityModel == secmod) {
            return SNMPERR_GENERR;
        }
    }
    sptr = SNMP_MALLOC_STRUCT(snmp_secmod_list);
    if (sptr == NULL)
        return SNMPERR_MALLOC;
    sptr->secDef = newdef;
    sptr->securityModel = secmod;
    sptr->next = registered_services;
    registered_services = sptr;
    modname2 = strdup(modname);
    if (!modname2)
        result = SE_NOMEM;
    else
        result = se_add_pair_to_slist("snmp_secmods", modname2, secmod);
    if (result != SE_OK) {
        switch (result) {
        case SE_NOMEM:
            snmp_log(LOG_CRIT, "snmp_secmod: no memory\n");
            break;

        case SE_ALREADY_THERE:
            othername = se_find_label_in_slist("snmp_secmods", secmod);
            if (strcmp(othername, modname) != 0) {
                snmp_log(LOG_ERR,
                         "snmp_secmod: two security modules %s and %s registered with the same security number\n",
                         modname, othername);
            }
            break;

        default:
            snmp_log(LOG_ERR,
                     "snmp_secmod: unknown error trying to register a new security module\n");
            break;
        }
        return SNMPERR_GENERR;
    }
    return SNMPERR_SUCCESS;
}

netsnmp_feature_child_of(unregister_sec_mod, netsnmp_unused);
#ifndef NETSNMP_FEATURE_REMOVE_UNREGISTER_SEC_MOD
int
unregister_sec_mod(int secmod)
{
    struct snmp_secmod_list *sptr, *lptr;

    for (sptr = registered_services, lptr = NULL; sptr;
         lptr = sptr, sptr = sptr->next) {
        if (sptr->securityModel == secmod) {
            if ( lptr )
                lptr->next = sptr->next;
            else
                registered_services = sptr->next;
	    SNMP_FREE(sptr->secDef);
            SNMP_FREE(sptr);
            return SNMPERR_SUCCESS;
        }
    }
    /*
     * not registered 
     */
    return SNMPERR_GENERR;
}
#endif /* NETSNMP_FEATURE_REMOVE_UNREGISTER_SEC_MOD */

void            
clear_sec_mod(void)
{
    struct snmp_secmod_list *tmp = registered_services, *next = NULL;

    while (tmp != NULL) {
	next = tmp->next;
	SNMP_FREE(tmp->secDef);
	SNMP_FREE(tmp);
	tmp = next;
    }
    registered_services = NULL;
}


struct snmp_secmod_def *
find_sec_mod(int secmod)
{
    struct snmp_secmod_list *sptr;

    for (sptr = registered_services; sptr; sptr = sptr->next) {
        if (sptr->securityModel == secmod) {
            return sptr->secDef;
        }
    }
    /*
     * not registered 
     */
    return NULL;
}

/* try to pick a reasonable security module default based on what was
   compiled into the net-snmp package */
#ifdef USM_SEC_MODEL_NUMBER
#define NETSNMP_SECMOD_DEFAULT_MODEL  USM_SEC_MODEL_NUMBER
#elif defined(TSM_SEC_MODEL_NUMBER)
#define NETSNMP_SECMOD_DEFAULT_MODEL  TSM_SEC_MODEL_NUMBER
#elif defined(KSM_SEC_MODEL_NUMBER)
#define NETSNMP_SECMOD_DEFAULT_MODEL  KSM_SEC_MODEL_NUMBER
#else
/* else we give up and leave it blank */
#define NETSNMP_SECMOD_DEFAULT_MODEL  -1
#endif

static int
set_default_secmod(int major, int minor, void *serverarg, void *clientarg)
{
    netsnmp_session *sess = (netsnmp_session *) serverarg;
    char           *cptr;
    int             model;

    if (!sess)
        return SNMPERR_GENERR;
    if (sess->securityModel == SNMP_DEFAULT_SECMODEL) {
        if ((cptr = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, 
					  NETSNMP_DS_LIB_SECMODEL)) != NULL) {
            if ((model = se_find_value_in_slist("snmp_secmods", cptr))
		!= SE_DNE) {
                sess->securityModel = model;
            } else {
                snmp_log(LOG_ERR,
                         "unknown security model name: %s.  Forcing USM instead.\n",
                         cptr);
                sess->securityModel = NETSNMP_SECMOD_DEFAULT_MODEL;
                return SNMPERR_GENERR;
            }
        } else {
            sess->securityModel = NETSNMP_SECMOD_DEFAULT_MODEL;
        }
    }
    return SNMPERR_SUCCESS;
}
