/* 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 © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 *
 * 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.
 */

/*
 * vacm.c
 *
 * SNMPv3 View-based Access Control Model
 */

#include <net-snmp/net-snmp-config.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 <sys/types.h>
#include <stdio.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

#include <ctype.h>

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

#include <net-snmp/library/snmp_api.h>
#include <net-snmp/library/system.h> /* strlcpy() */
#include <net-snmp/library/tools.h>
#include <net-snmp/library/vacm.h>

static struct vacm_viewEntry *viewList = NULL, *viewScanPtr = NULL;
static struct vacm_accessEntry *accessList = NULL, *accessScanPtr = NULL;
static struct vacm_groupEntry *groupList = NULL, *groupScanPtr = NULL;

/*
 * Macro to extend view masks with 1 bits when shorter than subtree lengths
 * REF: vacmViewTreeFamilyMask [RFC3415], snmpNotifyFilterMask [RFC3413]
 */

#define VIEW_MASK(viewPtr, idx, mask) \
    ((idx >= viewPtr->viewMaskLen) ? mask : (viewPtr->viewMask[idx] & mask))

/**
 * Initilizes the VACM code.
 * Specifically:
 *  - adds a set of enums mapping view numbers to human readable names
 */
void
init_vacm(void)
{
    /* views for access via get/set/send-notifications */
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("read"),
                         VACM_VIEW_READ);
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("write"),
                         VACM_VIEW_WRITE);
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("notify"),
                         VACM_VIEW_NOTIFY);

    /* views for permissions when receiving notifications */
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("log"),
                         VACM_VIEW_LOG);
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("execute"),
                         VACM_VIEW_EXECUTE);
    se_add_pair_to_slist(VACM_VIEW_ENUM_NAME, strdup("net"),
                         VACM_VIEW_NET);
}

void
vacm_save(const char *token, const char *type)
{
    struct vacm_viewEntry *vptr;
    struct vacm_accessEntry *aptr;
    struct vacm_groupEntry *gptr;
    int i;

    for (vptr = viewList; vptr != NULL; vptr = vptr->next) {
        if (vptr->viewStorageType == ST_NONVOLATILE)
            vacm_save_view(vptr, token, type);
    }

    for (aptr = accessList; aptr != NULL; aptr = aptr->next) {
        if (aptr->storageType == ST_NONVOLATILE) {
            /* Store the standard views (if set) */
            if ( aptr->views[VACM_VIEW_READ  ][0] ||
                 aptr->views[VACM_VIEW_WRITE ][0] ||
                 aptr->views[VACM_VIEW_NOTIFY][0] )
                vacm_save_access(aptr, token, type);
            /* Store any other (valid) access views */
            for ( i=VACM_VIEW_NOTIFY+1; i<VACM_MAX_VIEWS; i++ ) {
                if ( aptr->views[i][0] )
                    vacm_save_auth_access(aptr, token, type, i);
            }
        }
    }

    for (gptr = groupList; gptr != NULL; gptr = gptr->next) {
        if (gptr->storageType == ST_NONVOLATILE)
            vacm_save_group(gptr, token, type);
    }
}

/*
 * vacm_save_view(): saves a view entry to the persistent cache 
 */
void
vacm_save_view(struct vacm_viewEntry *view, const char *token,
               const char *type)
{
    char            line[4096];
    char           *cptr;

    memset(line, 0, sizeof(line));
    snprintf(line, sizeof(line), "%s%s %d %d %d ", token, "View",
            view->viewStatus, view->viewStorageType, view->viewType);
    line[ sizeof(line)-1 ] = 0;
    cptr = &line[strlen(line)]; /* the NULL */

    cptr =
        read_config_save_octet_string(cptr, (u_char *) view->viewName + 1,
                                      view->viewName[0]);
    *cptr++ = ' ';
    cptr =
        read_config_save_objid(cptr, view->viewSubtree+1,
                                     view->viewSubtreeLen-1);
    *cptr++ = ' ';
    cptr = read_config_save_octet_string(cptr, (u_char *) view->viewMask,
                                         view->viewMaskLen);

    read_config_store(type, line);
}

void
vacm_parse_config_view(const char *token, const char *line)
{
    struct vacm_viewEntry view;
    struct vacm_viewEntry *vptr;
    char           *viewName = (char *) &view.viewName;
    oid            *viewSubtree = (oid *) & view.viewSubtree;
    u_char         *viewMask;
    size_t          len;

    view.viewStatus = atoi(line);
    line = skip_token_const(line);
    view.viewStorageType = atoi(line);
    line = skip_token_const(line);
    view.viewType = atoi(line);
    line = skip_token_const(line);
    len = sizeof(view.viewName);
    line =
        read_config_read_octet_string(line, (u_char **) & viewName, &len);
    view.viewSubtreeLen = MAX_OID_LEN + 1;
    line =
        read_config_read_objid_const(line, (oid **) & viewSubtree,
                               &view.viewSubtreeLen);

    vptr =
        vacm_createViewEntry(view.viewName, view.viewSubtree,
                             view.viewSubtreeLen);
    if (!vptr) {
        return;
    }

    vptr->viewStatus = view.viewStatus;
    vptr->viewStorageType = view.viewStorageType;
    vptr->viewType = view.viewType;
    viewMask = vptr->viewMask;
    vptr->viewMaskLen = sizeof(vptr->viewMask);
    line =
        read_config_read_octet_string(line, &viewMask, &vptr->viewMaskLen);
}

/*
 * vacm_save_access(): saves an access entry to the persistent cache 
 */
void
vacm_save_access(struct vacm_accessEntry *access_entry, const char *token,
                 const char *type)
{
    char            line[4096];
    char           *cptr;

    memset(line, 0, sizeof(line));
    snprintf(line, sizeof(line), "%s%s %d %d %d %d %d ",
            token, "Access", access_entry->status,
            access_entry->storageType, access_entry->securityModel,
            access_entry->securityLevel, access_entry->contextMatch);
    line[ sizeof(line)-1 ] = 0;
    cptr = &line[strlen(line)]; /* the NULL */
    cptr =
        read_config_save_octet_string(cptr,
                                      (u_char *) access_entry->groupName + 1,
                                      access_entry->groupName[0] + 1);
    *cptr++ = ' ';
    cptr =
        read_config_save_octet_string(cptr,
                                      (u_char *) access_entry->contextPrefix + 1,
                                      access_entry->contextPrefix[0] + 1);

    *cptr++ = ' ';
    cptr = read_config_save_octet_string(cptr, (u_char *) access_entry->views[VACM_VIEW_READ],
                                         strlen(access_entry->views[VACM_VIEW_READ]) + 1);
    *cptr++ = ' ';
    cptr =
        read_config_save_octet_string(cptr, (u_char *) access_entry->views[VACM_VIEW_WRITE],
                                      strlen(access_entry->views[VACM_VIEW_WRITE]) + 1);
    *cptr++ = ' ';
    cptr =
        read_config_save_octet_string(cptr, (u_char *) access_entry->views[VACM_VIEW_NOTIFY],
                                      strlen(access_entry->views[VACM_VIEW_NOTIFY]) + 1);

    read_config_store(type, line);
}

void
vacm_save_auth_access(struct vacm_accessEntry *access_entry,
                      const char *token, const char *type, int authtype)
{
    char            line[4096];
    char           *cptr;

    memset(line, 0, sizeof(line));
    snprintf(line, sizeof(line), "%s%s %d %d %d %d %d ",
            token, "AuthAccess", access_entry->status,
            access_entry->storageType, access_entry->securityModel,
            access_entry->securityLevel, access_entry->contextMatch);
    line[ sizeof(line)-1 ] = 0;
    cptr = &line[strlen(line)]; /* the NULL */
    cptr =
        read_config_save_octet_string(cptr,
                                      (u_char *) access_entry->groupName + 1,
                                      access_entry->groupName[0] + 1);
    *cptr++ = ' ';
    cptr =
        read_config_save_octet_string(cptr,
                                      (u_char *) access_entry->contextPrefix + 1,
                                      access_entry->contextPrefix[0] + 1);

    snprintf(cptr, sizeof(line)-(cptr-line), " %d ", authtype);
    while ( *cptr )
        cptr++;

    *cptr++ = ' ';
    cptr = read_config_save_octet_string(cptr,
                               (u_char *)access_entry->views[authtype],
                                  strlen(access_entry->views[authtype]) + 1);

    read_config_store(type, line);
}

char *
_vacm_parse_config_access_common(struct vacm_accessEntry **aptr,
                                 const char *line)
{
    struct vacm_accessEntry access;
    char           *cPrefix = (char *) &access.contextPrefix;
    char           *gName   = (char *) &access.groupName;
    size_t          len;

    access.status = atoi(line);
    line = skip_token_const(line);
    access.storageType = atoi(line);
    line = skip_token_const(line);
    access.securityModel = atoi(line);
    line = skip_token_const(line);
    access.securityLevel = atoi(line);
    line = skip_token_const(line);
    access.contextMatch = atoi(line);
    line = skip_token_const(line);
    len  = sizeof(access.groupName);
    line = read_config_read_octet_string(line, (u_char **) &gName,   &len);
    len  = sizeof(access.contextPrefix);
    line = read_config_read_octet_string(line, (u_char **) &cPrefix, &len);

    *aptr = vacm_getAccessEntry(access.groupName,
                                  access.contextPrefix,
                                  access.securityModel,
                                  access.securityLevel);
    if (!*aptr)
        *aptr = vacm_createAccessEntry(access.groupName,
                                  access.contextPrefix,
                                  access.securityModel,
                                  access.securityLevel);
    if (!*aptr)
        return NULL;

    (*aptr)->status = access.status;
    (*aptr)->storageType   = access.storageType;
    (*aptr)->securityModel = access.securityModel;
    (*aptr)->securityLevel = access.securityLevel;
    (*aptr)->contextMatch  = access.contextMatch;
    return NETSNMP_REMOVE_CONST(char *, line);
}

void
vacm_parse_config_access(const char *token, const char *line)
{
    struct vacm_accessEntry *aptr;
    char           *readView, *writeView, *notifyView;
    size_t          len;

    line = _vacm_parse_config_access_common(&aptr, line);
    if (!line)
        return;

    readView = (char *) aptr->views[VACM_VIEW_READ];
    len = sizeof(aptr->views[VACM_VIEW_READ]);
    line =
        read_config_read_octet_string(line, (u_char **) & readView, &len);
    writeView = (char *) aptr->views[VACM_VIEW_WRITE];
    len = sizeof(aptr->views[VACM_VIEW_WRITE]);
    line =
        read_config_read_octet_string(line, (u_char **) & writeView, &len);
    notifyView = (char *) aptr->views[VACM_VIEW_NOTIFY];
    len = sizeof(aptr->views[VACM_VIEW_NOTIFY]);
    line =
        read_config_read_octet_string(line, (u_char **) & notifyView,
                                      &len);
}

void
vacm_parse_config_auth_access(const char *token, const char *line)
{
    struct vacm_accessEntry *aptr;
    int             authtype;
    char           *view;
    size_t          len;

    line = _vacm_parse_config_access_common(&aptr, line);
    if (!line)
        return;

    authtype = atoi(line);
    line = skip_token_const(line);

    view = (char *) aptr->views[authtype];
    len  = sizeof(aptr->views[authtype]);
    line = read_config_read_octet_string(line, (u_char **) & view, &len);
}

/*
 * vacm_save_group(): saves a group entry to the persistent cache 
 */
void
vacm_save_group(struct vacm_groupEntry *group_entry, const char *token,
                const char *type)
{
    char            line[4096];
    char           *cptr;

    memset(line, 0, sizeof(line));
    snprintf(line, sizeof(line), "%s%s %d %d %d ",
            token, "Group", group_entry->status,
            group_entry->storageType, group_entry->securityModel);
    line[ sizeof(line)-1 ] = 0;
    cptr = &line[strlen(line)]; /* the NULL */

    cptr =
        read_config_save_octet_string(cptr,
                                      (u_char *) group_entry->securityName + 1,
                                      group_entry->securityName[0] + 1);
    *cptr++ = ' ';
    cptr = read_config_save_octet_string(cptr, (u_char *) group_entry->groupName,
                                         strlen(group_entry->groupName) + 1);

    read_config_store(type, line);
}

void
vacm_parse_config_group(const char *token, const char *line)
{
    struct vacm_groupEntry group;
    struct vacm_groupEntry *gptr;
    char           *securityName = (char *) &group.securityName;
    char           *groupName;
    size_t          len;

    group.status = atoi(line);
    line = skip_token_const(line);
    group.storageType = atoi(line);
    line = skip_token_const(line);
    group.securityModel = atoi(line);
    line = skip_token_const(line);
    len = sizeof(group.securityName);
    line =
        read_config_read_octet_string(line, (u_char **) & securityName,
                                      &len);

    gptr = vacm_createGroupEntry(group.securityModel, group.securityName);
    if (!gptr)
        return;

    gptr->status = group.status;
    gptr->storageType = group.storageType;
    groupName = (char *) gptr->groupName;
    len = sizeof(group.groupName);
    line =
        read_config_read_octet_string(line, (u_char **) & groupName, &len);
}

struct vacm_viewEntry *
netsnmp_view_get(struct vacm_viewEntry *head, const char *viewName,
                  oid * viewSubtree, size_t viewSubtreeLen, int mode)
{
    struct vacm_viewEntry *vp, *vpret = NULL;
    char            view[VACMSTRINGLEN];
    int             found, glen;
    int count=0;

    glen = (int) strlen(viewName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return NULL;
    view[0] = glen;
    strlcpy(view + 1, viewName, sizeof(view) - 1);
    for (vp = head; vp; vp = vp->next) {
        if (!memcmp(view, vp->viewName, glen + 1)
            && viewSubtreeLen >= (vp->viewSubtreeLen - 1)) {
            int             mask = 0x80;
            unsigned int    oidpos, maskpos = 0;
            found = 1;

            for (oidpos = 0;
                 found && oidpos < vp->viewSubtreeLen - 1;
                 oidpos++) {
                if (mode==VACM_MODE_IGNORE_MASK || (VIEW_MASK(vp, maskpos, mask) != 0)) {
                    if (viewSubtree[oidpos] !=
                        vp->viewSubtree[oidpos + 1])
                        found = 0;
                }
                if (mask == 1) {
                    mask = 0x80;
                    maskpos++;
                } else
                    mask >>= 1;
            }

            if (found) {
                /*
                 * match successful, keep this node if its longer than
                 * the previous or (equal and lexicographically greater
                 * than the previous). 
                 */
                count++;
                if (mode == VACM_MODE_CHECK_SUBTREE) {
                    vpret = vp;
                } else if (vpret == NULL
                           || vp->viewSubtreeLen > vpret->viewSubtreeLen
                           || (vp->viewSubtreeLen == vpret->viewSubtreeLen
                               && snmp_oid_compare(vp->viewSubtree + 1,
                                                   vp->viewSubtreeLen - 1,
                                                   vpret->viewSubtree + 1,
                                                   vpret->viewSubtreeLen - 1) >
                               0)) {
                    vpret = vp;
                }
            }
        }
    }
    DEBUGMSGTL(("vacm:getView", ", %s\n", (vpret) ? "found" : "none"));
    if (mode == VACM_MODE_CHECK_SUBTREE && count > 1) {
        return NULL;
    }
    return vpret;
}

/*******************************************************************o-o******
 * netsnmp_view_exists
 *
 * Check to see if a view with the given name exists.
 *
 * Parameters:
 *    viewName           - Name of view to check
 *
 * Returns 0 if the view does not exist. Otherwise, it returns the number
 *         of OID rows for the given name.
 */
int
netsnmp_view_exists(struct vacm_viewEntry *head, const char *viewName)
{
    struct vacm_viewEntry *vp;
    char                   view[VACMSTRINGLEN];
    int                    len, count = 0;

    len = (int) strlen(viewName);
    if (len < 0 || len > VACM_MAX_STRING)
        return 0;
    view[0] = len;
    strcpy(view + 1, viewName);
    DEBUGMSGTL(("9:vacm:view_exists", "checking %s\n", viewName));
    for (vp = head; vp; vp = vp->next) {
        if (memcmp(view, vp->viewName, len + 1) == 0)
            ++count;
    }

    return count;
}

/*******************************************************************o-o******
 * vacm_checkSubtree
 *
 * Check to see if everything within a subtree is in view, not in view,
 * or possibly both.
 *
 * Parameters:
 *   *viewName           - Name of view to check
 *   *viewSubtree        - OID of subtree
 *    viewSubtreeLen     - length of subtree OID
 *      
 * Returns:
 *   VACM_SUCCESS          The OID is included in the view.
 *   VACM_NOTINVIEW        If no entry in the view list includes the
 *                         provided OID, or the OID is explicitly excluded
 *                         from the view. 
 *   VACM_SUBTREE_UNKNOWN  The entire subtree has both allowed and disallowed
 *                         portions.
 */
int
netsnmp_view_subtree_check(struct vacm_viewEntry *head, const char *viewName,
                           oid * viewSubtree, size_t viewSubtreeLen)
{
    struct vacm_viewEntry *vp, *vpShorter = NULL, *vpLonger = NULL;
    char            view[VACMSTRINGLEN];
    int             found, glen;

    glen = (int) strlen(viewName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return VACM_NOTINVIEW;
    view[0] = glen;
    strlcpy(view + 1, viewName, sizeof(view) - 1);
    DEBUGMSGTL(("9:vacm:checkSubtree", "view %s\n", viewName));
    for (vp = head; vp; vp = vp->next) {
        if (!memcmp(view, vp->viewName, glen + 1)) {
            /*
             * If the subtree defined in the view is shorter than or equal
             * to the subtree we are comparing, then it might envelop the
             * subtree we are comparing against.
             */
            if (viewSubtreeLen >= (vp->viewSubtreeLen - 1)) {
                int             mask = 0x80;
                unsigned int    oidpos, maskpos = 0;
                found = 1;

                /*
                 * check the mask
                 */
                for (oidpos = 0;
                     found && oidpos < vp->viewSubtreeLen - 1;
                     oidpos++) {
                    if (VIEW_MASK(vp, maskpos, mask) != 0) {
                        if (viewSubtree[oidpos] !=
                            vp->viewSubtree[oidpos + 1])
                            found = 0;
                    }
                    if (mask == 1) {
                        mask = 0x80;
                        maskpos++;
                    } else
                        mask >>= 1;
                }

                if (found) {
                    /*
                     * match successful, keep this node if it's longer than
                     * the previous or (equal and lexicographically greater
                     * than the previous). 
                     */
                    DEBUGMSGTL(("9:vacm:checkSubtree", " %s matched?\n", vp->viewName));
    
                    if (vpShorter == NULL
                        || vp->viewSubtreeLen > vpShorter->viewSubtreeLen
                        || (vp->viewSubtreeLen == vpShorter->viewSubtreeLen
                           && snmp_oid_compare(vp->viewSubtree + 1,
                                               vp->viewSubtreeLen - 1,
                                               vpShorter->viewSubtree + 1,
                                               vpShorter->viewSubtreeLen - 1) >
                                   0)) {
                        vpShorter = vp;
                    }
                }
            }
            /*
             * If the subtree defined in the view is longer than the
             * subtree we are comparing, then it might ambiguate our
             * response.
             */
            else {
                int             mask = 0x80;
                unsigned int    oidpos, maskpos = 0;
                found = 1;

                /*
                 * check the mask up to the length of the provided subtree
                 */
                for (oidpos = 0;
                     found && oidpos < viewSubtreeLen;
                     oidpos++) {
                    if (VIEW_MASK(vp, maskpos, mask) != 0) {
                        if (viewSubtree[oidpos] !=
                            vp->viewSubtree[oidpos + 1])
                            found = 0;
                    }
                    if (mask == 1) {
                        mask = 0x80;
                        maskpos++;
                    } else
                        mask >>= 1;
                }

                if (found) {
                    /*
                     * match successful.  If we already found a match
                     * with a different view type, then parts of the subtree 
                     * are included and others are excluded, so return UNKNOWN.
                     */
                    DEBUGMSGTL(("9:vacm:checkSubtree", " %s matched?\n", vp->viewName));
                    if (vpLonger != NULL
                        && (vpLonger->viewType != vp->viewType)) {
                        DEBUGMSGTL(("vacm:checkSubtree", ", %s\n", "unknown"));
                        return VACM_SUBTREE_UNKNOWN;
                    }
                    else if (vpLonger == NULL) {
                        vpLonger = vp;
                    }
                }
            }
        }
    }
    DEBUGMSGTL(("9:vacm:checkSubtree", " %s matched\n", viewName));

    /*
     * If we found a matching view subtree with a longer OID than the provided
     * OID, check to see if its type is consistent with any matching view
     * subtree we may have found with a shorter OID than the provided OID.
     *
     * The view type of the longer OID is inconsistent with the shorter OID in
     * either of these two cases:
     *  1) No matching shorter OID was found and the view type of the longer
     *     OID is INCLUDE.
     *  2) A matching shorter ID was found and its view type doesn't match
     *     the view type of the longer OID.
     */
    if (vpLonger != NULL) {
        if ((!vpShorter && vpLonger->viewType != SNMP_VIEW_EXCLUDED)
            || (vpShorter && vpLonger->viewType != vpShorter->viewType)) {
            DEBUGMSGTL(("vacm:checkSubtree", ", %s\n", "unknown"));
            return VACM_SUBTREE_UNKNOWN;
        }
    }

    if (vpShorter && vpShorter->viewType != SNMP_VIEW_EXCLUDED) {
        DEBUGMSGTL(("vacm:checkSubtree", ", %s\n", "included"));
        return VACM_SUCCESS;
    }

    DEBUGMSGTL(("vacm:checkSubtree", ", %s\n", "excluded"));
    return VACM_NOTINVIEW;
}

void
vacm_scanViewInit(void)
{
    viewScanPtr = viewList;
}

struct vacm_viewEntry *
vacm_scanViewNext(void)
{
    struct vacm_viewEntry *returnval = viewScanPtr;
    if (viewScanPtr)
        viewScanPtr = viewScanPtr->next;
    return returnval;
}

struct vacm_viewEntry *
netsnmp_view_create(struct vacm_viewEntry **head, const char *viewName,
                     oid * viewSubtree, size_t viewSubtreeLen)
{
    struct vacm_viewEntry *vp, *lp, *op = NULL;
    int             cmp, cmp2, glen;

    glen = (int) strlen(viewName);
    if (glen < 0 || glen > VACM_MAX_STRING || viewSubtreeLen > MAX_OID_LEN)
        return NULL;
    vp = (struct vacm_viewEntry *) calloc(1,
                                          sizeof(struct vacm_viewEntry));
    if (vp == NULL)
        return NULL;
    vp->reserved =
        (struct vacm_viewEntry *) calloc(1, sizeof(struct vacm_viewEntry));
    if (vp->reserved == NULL) {
        free(vp);
        return NULL;
    }

    vp->viewName[0] = glen;
    strlcpy(vp->viewName + 1, viewName, sizeof(vp->viewName) - 1);
    vp->viewSubtree[0] = viewSubtreeLen;
    memcpy(vp->viewSubtree + 1, viewSubtree, viewSubtreeLen * sizeof(oid));
    vp->viewSubtreeLen = viewSubtreeLen + 1;

    lp = *head;
    while (lp) {
        cmp = memcmp(lp->viewName, vp->viewName, glen + 1);
        cmp2 = snmp_oid_compare(lp->viewSubtree, lp->viewSubtreeLen,
                                vp->viewSubtree, vp->viewSubtreeLen);
        if (cmp == 0 && cmp2 > 0)
            break;
        if (cmp > 0)
            break;
        op = lp;
        lp = lp->next;
    }
    vp->next = lp;
    if (op)
        op->next = vp;
    else
        *head = vp;
    return vp;
}

void
netsnmp_view_destroy(struct vacm_viewEntry **head, const char *viewName,
                      oid * viewSubtree, size_t viewSubtreeLen)
{
    struct vacm_viewEntry *vp, *lastvp = NULL;

    if ((*head) && !strcmp((*head)->viewName + 1, viewName)
        && (*head)->viewSubtreeLen == viewSubtreeLen
        && !memcmp((char *) (*head)->viewSubtree, (char *) viewSubtree,
                   viewSubtreeLen * sizeof(oid))) {
        vp = (*head);
        (*head) = (*head)->next;
    } else {
        for (vp = (*head); vp; vp = vp->next) {
            if (!strcmp(vp->viewName + 1, viewName)
                && vp->viewSubtreeLen == viewSubtreeLen
                && !memcmp((char *) vp->viewSubtree, (char *) viewSubtree,
                           viewSubtreeLen * sizeof(oid)))
                break;
            lastvp = vp;
        }
        if (!vp || !lastvp)
            return;
        lastvp->next = vp->next;
    }
    if (vp->reserved)
        free(vp->reserved);
    free(vp);
    return;
}

void
netsnmp_view_clear(struct vacm_viewEntry **head)
{
    struct vacm_viewEntry *vp;
    while ((vp = (*head))) {
        (*head) = vp->next;
        if (vp->reserved)
            free(vp->reserved);
        free(vp);
    }
}

struct vacm_groupEntry *
vacm_getGroupEntry(int securityModel, const char *securityName)
{
    struct vacm_groupEntry *vp;
    char            secname[VACMSTRINGLEN];
    int             glen;

    glen = (int) strlen(securityName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return NULL;
    secname[0] = glen;
    strlcpy(secname + 1, securityName, sizeof(secname) - 1);

    for (vp = groupList; vp; vp = vp->next) {
        if ((securityModel == vp->securityModel
             || vp->securityModel == SNMP_SEC_MODEL_ANY)
            && !memcmp(vp->securityName, secname, glen + 1))
            return vp;
    }
    return NULL;
}

void
vacm_scanGroupInit(void)
{
    groupScanPtr = groupList;
}

struct vacm_groupEntry *
vacm_scanGroupNext(void)
{
    struct vacm_groupEntry *returnval = groupScanPtr;
    if (groupScanPtr)
        groupScanPtr = groupScanPtr->next;
    return returnval;
}

struct vacm_groupEntry *
vacm_createGroupEntry(int securityModel, const char *securityName)
{
    struct vacm_groupEntry *gp, *lg, *og;
    int             cmp, glen;

    glen = (int) strlen(securityName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return NULL;
    gp = (struct vacm_groupEntry *) calloc(1,
                                           sizeof(struct vacm_groupEntry));
    if (gp == NULL)
        return NULL;
    gp->reserved =
        (struct vacm_groupEntry *) calloc(1,
                                          sizeof(struct vacm_groupEntry));
    if (gp->reserved == NULL) {
        free(gp);
        return NULL;
    }

    gp->securityModel = securityModel;
    gp->securityName[0] = glen;
    strlcpy(gp->securityName + 1, securityName, sizeof(gp->securityName) - 1);

    lg = groupList;
    og = NULL;
    while (lg) {
        if (lg->securityModel > securityModel)
            break;
        if (lg->securityModel == securityModel &&
            (cmp =
             memcmp(lg->securityName, gp->securityName, glen + 1)) > 0)
            break;
        /*
         * if (lg->securityModel == securityModel && cmp == 0) abort(); 
         */
        og = lg;
        lg = lg->next;
    }
    gp->next = lg;
    if (og == NULL)
        groupList = gp;
    else
        og->next = gp;
    return gp;
}

void
vacm_destroyGroupEntry(int securityModel, const char *securityName)
{
    struct vacm_groupEntry *vp, *lastvp = NULL;

    if (groupList && groupList->securityModel == securityModel
        && !strcmp(groupList->securityName + 1, securityName)) {
        vp = groupList;
        groupList = groupList->next;
    } else {
        for (vp = groupList; vp; vp = vp->next) {
            if (vp->securityModel == securityModel
                && !strcmp(vp->securityName + 1, securityName))
                break;
            lastvp = vp;
        }
        if (!vp || !lastvp)
            return;
        lastvp->next = vp->next;
    }
    if (vp->reserved)
        free(vp->reserved);
    free(vp);
    return;
}


void
vacm_destroyAllGroupEntries(void)
{
    struct vacm_groupEntry *gp;
    while ((gp = groupList)) {
        groupList = gp->next;
        if (gp->reserved)
            free(gp->reserved);
        free(gp);
    }
}

struct vacm_accessEntry *
_vacm_choose_best( struct vacm_accessEntry *current,
                   struct vacm_accessEntry *candidate)
{
    /*
     * RFC 3415: vacmAccessTable:
     *    2) if this set has [more than] one member, ...
     *       it comes down to deciding how to weight the
     *       preferences between ContextPrefixes,
     *       SecurityModels, and SecurityLevels
     */
    if (( !current ) ||
        /* a) if the subset of entries with securityModel
         *    matching the securityModel in the message is
         *    not empty, then discard the rest
         */
        (  current->securityModel == SNMP_SEC_MODEL_ANY &&
         candidate->securityModel != SNMP_SEC_MODEL_ANY ) ||
        /* b) if the subset of entries with vacmAccessContextPrefix
         *    matching the contextName in the message is
         *    not empty, then discard the rest
         */
        (  current->contextMatch  == CONTEXT_MATCH_PREFIX &&
         candidate->contextMatch  == CONTEXT_MATCH_EXACT ) ||
        /* c) discard all entries with ContextPrefixes shorter
         *    than the longest one remaining in the set
         */
        (  current->contextMatch  == CONTEXT_MATCH_PREFIX &&
           current->contextPrefix[0] < candidate->contextPrefix[0] ) ||
        /* d) select the entry with the highest securityLevel
         */
        (  current->securityLevel < candidate->securityLevel )) {

        return candidate;
    }

    return current;
}

struct vacm_accessEntry *
vacm_getAccessEntry(const char *groupName,
                    const char *contextPrefix,
                    int securityModel, int securityLevel)
{
    struct vacm_accessEntry *vp, *best=NULL;
    char            group[VACMSTRINGLEN];
    char            context[VACMSTRINGLEN];
    int             glen, clen;

    glen = (int) strlen(groupName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return NULL;
    clen = (int) strlen(contextPrefix);
    if (clen < 0 || clen > VACM_MAX_STRING)
        return NULL;

    group[0] = glen;
    strlcpy(group + 1, groupName, sizeof(group) - 1);
    context[0] = clen;
    strlcpy(context + 1, contextPrefix, sizeof(context) - 1);
    for (vp = accessList; vp; vp = vp->next) {
        if ((securityModel == vp->securityModel
             || vp->securityModel == SNMP_SEC_MODEL_ANY)
            && securityLevel >= vp->securityLevel
            && !memcmp(vp->groupName, group, glen + 1)
            &&
            ((vp->contextMatch == CONTEXT_MATCH_EXACT
              && clen == vp->contextPrefix[0]
              && (memcmp(vp->contextPrefix, context, clen + 1) == 0))
             || (vp->contextMatch == CONTEXT_MATCH_PREFIX
                 && clen >= vp->contextPrefix[0]
                 && (memcmp(vp->contextPrefix + 1, context + 1,
                            vp->contextPrefix[0]) == 0))))
            best = _vacm_choose_best( best, vp );
    }
    return best;
}

void
vacm_scanAccessInit(void)
{
    accessScanPtr = accessList;
}

struct vacm_accessEntry *
vacm_scanAccessNext(void)
{
    struct vacm_accessEntry *returnval = accessScanPtr;
    if (accessScanPtr)
        accessScanPtr = accessScanPtr->next;
    return returnval;
}

struct vacm_accessEntry *
vacm_createAccessEntry(const char *groupName,
                       const char *contextPrefix,
                       int securityModel, int securityLevel)
{
    struct vacm_accessEntry *vp, *lp, *op = NULL;
    int             cmp, glen, clen;

    glen = (int) strlen(groupName);
    if (glen < 0 || glen > VACM_MAX_STRING)
        return NULL;
    clen = (int) strlen(contextPrefix);
    if (clen < 0 || clen > VACM_MAX_STRING)
        return NULL;
    vp = (struct vacm_accessEntry *) calloc(1,
                                            sizeof(struct
                                                   vacm_accessEntry));
    if (vp == NULL)
        return NULL;
    vp->reserved =
        (struct vacm_accessEntry *) calloc(1,
                                           sizeof(struct
                                                  vacm_accessEntry));
    if (vp->reserved == NULL) {
        free(vp);
        return NULL;
    }

    vp->securityModel = securityModel;
    vp->securityLevel = securityLevel;
    vp->groupName[0] = glen;
    strlcpy(vp->groupName + 1, groupName, sizeof(vp->groupName) - 1);
    vp->contextPrefix[0] = clen;
    strlcpy(vp->contextPrefix + 1, contextPrefix,
            sizeof(vp->contextPrefix) - 1);

    lp = accessList;
    while (lp) {
        cmp = memcmp(lp->groupName, vp->groupName, glen + 1);
        if (cmp > 0)
            break;
        if (cmp < 0)
            goto next;
        cmp = memcmp(lp->contextPrefix, vp->contextPrefix, clen + 1);
        if (cmp > 0)
            break;
        if (cmp < 0)
            goto next;
        if (lp->securityModel > securityModel)
            break;
        if (lp->securityModel < securityModel)
            goto next;
        if (lp->securityLevel > securityLevel)
            break;
      next:
        op = lp;
        lp = lp->next;
    }
    vp->next = lp;
    if (op == NULL)
        accessList = vp;
    else
        op->next = vp;
    return vp;
}

void
vacm_destroyAccessEntry(const char *groupName,
                        const char *contextPrefix,
                        int securityModel, int securityLevel)
{
    struct vacm_accessEntry *vp, *lastvp = NULL;

    if (accessList && accessList->securityModel == securityModel
        && accessList->securityLevel == securityLevel
        && !strcmp(accessList->groupName + 1, groupName)
        && !strcmp(accessList->contextPrefix + 1, contextPrefix)) {
        vp = accessList;
        accessList = accessList->next;
    } else {
        for (vp = accessList; vp; vp = vp->next) {
            if (vp->securityModel == securityModel
                && vp->securityLevel == securityLevel
                && !strcmp(vp->groupName + 1, groupName)
                && !strcmp(vp->contextPrefix + 1, contextPrefix))
                break;
            lastvp = vp;
        }
        if (!vp || !lastvp)
            return;
        lastvp->next = vp->next;
    }
    if (vp->reserved)
        free(vp->reserved);
    free(vp);
    return;
}

void
vacm_destroyAllAccessEntries(void)
{
    struct vacm_accessEntry *ap;
    while ((ap = accessList)) {
        accessList = ap->next;
        if (ap->reserved)
            free(ap->reserved);
        free(ap);
    }
}

int
store_vacm(int majorID, int minorID, void *serverarg, void *clientarg)
{
    /*
     * figure out our application name 
     */
    char           *appname = (char *) clientarg;
    if (appname == NULL) {
        appname = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, 
					NETSNMP_DS_LIB_APPTYPE);
    }

    /*
     * save the VACM MIB 
     */
    vacm_save("vacm", appname);
    return SNMPERR_SUCCESS;
}

/*
 * returns 1 if vacm has *any* (non-built-in) configuration entries,
 * regardless of whether or not there is enough to make a decision,
 * else return 0 
 */
int
vacm_is_configured(void)
{
    if (accessList == NULL && groupList == NULL) {
        return 0;
    }
    return 1;
}

/*
 * backwards compatability
 */
struct vacm_viewEntry *
vacm_getViewEntry(const char *viewName,
                  oid * viewSubtree, size_t viewSubtreeLen, int mode)
{
    return netsnmp_view_get( viewList, viewName, viewSubtree, viewSubtreeLen,
                             mode);
}

int
vacm_checkSubtree(const char *viewName,
                  oid * viewSubtree, size_t viewSubtreeLen)
{
    return netsnmp_view_subtree_check( viewList, viewName, viewSubtree,
                                       viewSubtreeLen);
}

struct vacm_viewEntry *
vacm_createViewEntry(const char *viewName,
                     oid * viewSubtree, size_t viewSubtreeLen)
{
    return netsnmp_view_create( &viewList, viewName, viewSubtree,
                                viewSubtreeLen);
}

void
vacm_destroyViewEntry(const char *viewName,
                      oid * viewSubtree, size_t viewSubtreeLen)
{
    netsnmp_view_destroy( &viewList, viewName, viewSubtree, viewSubtreeLen);
}

void
vacm_destroyAllViewEntries(void)
{
    netsnmp_view_clear( &viewList );
}

/*
 * vacm simple api
 */

int
netsnmp_vacm_simple_usm_add(const char *user, int rw, int authLevel,
                            const char *view, oid *oidView, size_t oidViewLen,
                            const char *context)
{
    struct vacm_viewEntry   *vacmEntry = NULL;
    struct vacm_groupEntry  *groupEntry = NULL;
    struct vacm_accessEntry *accessEntry = NULL;
    char                    *tmp, localContext[VACMSTRINGLEN];
    int                      exact = 1;  /* exact context match */

    if (NULL == user)
        return SNMPERR_GENERR;

    if (authLevel < SNMP_SEC_LEVEL_NOAUTH ||
        authLevel > SNMP_SEC_LEVEL_AUTHPRIV)
        return SNMPERR_GENERR;

    if (NULL != view) {
        /*
         * if we are given a view name, it is an error if
         *   - it exists and we have an oid
         *   - it doesn't exist and we don't have an oid
         */
        if (netsnmp_view_exists(viewList, view) != 0) {
            if (NULL != oidView || oidViewLen > 0) {
                DEBUGMSGTL(("vacm:simple_usm", "can't modify existing view"));
                return SNMPERR_GENERR;
            }
        } else {
            if (NULL == oidView || oidViewLen == 0) {
                DEBUGMSGTL(("vacm:simple_usm", "can't create view w/out oid"));
                return SNMPERR_GENERR;
            }
            /** try and create view for oid */
            vacmEntry = vacm_createViewEntry(view, oidView, oidViewLen);
            if (NULL == vacmEntry) {
                DEBUGMSGTL(("vacm:simple_usm", "createViewEntry failed"));
                return SNMPERR_GENERR;
            }
            SNMP_FREE(vacmEntry->reserved);
        }
    } else if (0 == oidViewLen || NULL == oidView) {
        view = "_all_"; /* no oid either, just use _all_ */
    } else {
        DEBUGMSGTL(("vacm:simple_usm", "need view name for new views"));
        return SNMPERR_GENERR;
    }

    /*
     * group
     * grpv3user usm \"v3user\"\000prefix\000_all_\000_all_\000_all_\000\060"
     * vacm_createGroupEntry() also automatically inserts into group list.
     */
    groupEntry = vacm_createGroupEntry(SNMP_SEC_MODEL_USM, user);
    if (NULL == groupEntry) {
        DEBUGMSGTL(("vacm:simple_usm", "createViewEntry failed"));
        goto bail;
    }
    snprintf(groupEntry->groupName, sizeof(groupEntry->groupName)-2,
             "grp%.28s", user);
    for (tmp=groupEntry->groupName; *tmp; tmp++)
        if (!isalnum((unsigned char)(*tmp)))
            *tmp = '_';
    groupEntry->storageType = SNMP_STORAGE_PERMANENT;
    groupEntry->status = SNMP_ROW_ACTIVE;
    SNMP_FREE(groupEntry->reserved);

    /*
     * access
     * grpv3user myctx usm noauth exact _all_ none none
     */
    if (NULL == context) {
        localContext[0] = 0;
        context = localContext;
    } else {
        /** check for wildcard in context */
        int contextLen = strlen(context);
        if ('*' == context[contextLen - 1]) {
            strlcpy(localContext, context, sizeof(localContext));
            localContext[contextLen - 1] = 0;
            context = localContext;
            exact = 2; /* not exact, have context prefix */
        }
    }
    accessEntry = vacm_createAccessEntry(groupEntry->groupName, context,
                                         SNMP_SEC_MODEL_USM, authLevel);
    if (NULL == accessEntry) {
        DEBUGMSGTL(("vacm:simple_usm", "createViewEntry failed"));
        goto bail;
    }
    strlcpy(accessEntry->views[VACM_VIEW_READ], view,
            sizeof(accessEntry->views[VACM_VIEW_READ]));
    if (0 == rw)
        view = "none";
    strlcpy(accessEntry->views[VACM_VIEW_WRITE], view,
            sizeof(accessEntry->views[VACM_VIEW_WRITE]));
    strlcpy(accessEntry->views[VACM_VIEW_NOTIFY], view,
            sizeof(accessEntry->views[VACM_VIEW_NOTIFY]));

    accessEntry->contextMatch = exact;
    accessEntry->storageType = SNMP_STORAGE_PERMANENT;
    accessEntry->status = SNMP_ROW_ACTIVE;
    SNMP_FREE(accessEntry->reserved);

    return SNMPERR_SUCCESS;

bail:
    if (NULL != groupEntry)
        vacm_destroyGroupEntry(SNMP_SEC_MODEL_USM, user);

    if (NULL != vacmEntry)
        vacm_destroyViewEntry(vacmEntry->viewName+1, vacmEntry->viewSubtree,
                              vacmEntry->viewSubtreeLen);

    return SNMPERR_GENERR;
}

int
netsnmp_vacm_simple_usm_del(const char *user, int authLevel,
                            const char *view, oid *oidView, size_t oidViewLen,
                            const char *context)
{
    char                     localContext[VACMSTRINGLEN];
    char                     group[VACMSTRINGLEN];

    /*
     * only delete simple views (one OID) for which we have an OID.
     * never delete '_all_'.
     */
    if ((NULL != view) && (NULL != oidView) && (oidViewLen > 0) &&
        (strcmp(view, "_all_") != 0) &&
        (netsnmp_view_exists(viewList, view) == 1)) {
        vacm_destroyViewEntry(view, oidView, oidViewLen);
    }

    vacm_destroyGroupEntry(SNMP_SEC_MODEL_USM, user);

    snprintf(group, sizeof(group)-2, "grp%.28s", user);
    if (NULL == context) {
        localContext[0] = 0;
        context = localContext;
    } else {
        /** check for wildcard in context */
        int contextLen = strlen(context);
        if ('*' == context[contextLen - 1]) {
            strlcpy(localContext, context, sizeof(localContext));
            localContext[contextLen - 1] = 0;
            context = localContext;
        }
    }

    vacm_destroyAccessEntry(group, context, SNMP_SEC_MODEL_USM, authLevel);

    return SNMPERR_SUCCESS;
}
