/*
 * snmpv3.c
 */

#include <net-snmp/net-snmp-config.h>
#include <errno.h>
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <stdio.h>
#include <sys/types.h>

#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_SYS_TIMES_H
#include <sys/times.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#if HAVE_STDLIB_H
#       include <stdlib.h>
#endif

/*
 * Stuff needed for getHwAddress(...) 
 */
#ifdef HAVE_SYS_IOCTL_H
#	include <sys/ioctl.h>
#endif
#ifdef HAVE_NET_IF_H
#	include <net/if.h>
#endif

#if HAVE_DMALLOC_H
#include <dmalloc.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/snmpv3.h>
#include <net-snmp/library/callback.h>
#include <net-snmp/library/snmp_api.h>
#include <net-snmp/library/lcd_time.h>
#include <net-snmp/library/scapi.h>
#include <net-snmp/library/keytools.h>
#include <net-snmp/library/lcd_time.h>
#include <net-snmp/library/snmp_secmod.h>
#include <net-snmp/library/snmpusm.h>
#include <net-snmp/library/transform_oids.h>

static u_long   engineBoots = 1;
static unsigned int engineIDType = ENGINEID_TYPE_NETSNMP_RND;
static unsigned char *engineID = NULL;
static size_t   engineIDLength = 0;
static unsigned char *engineIDNic = NULL;
static unsigned int engineIDIsSet = 0;  /* flag if ID set by config */
static unsigned char *oldEngineID = NULL;
static size_t   oldEngineIDLength = 0;
static struct timeval snmpv3starttime;

/*
 * Set up default snmpv3 parameter value storage.
 */
static const oid *defaultAuthType = NULL;
static size_t   defaultAuthTypeLen = 0;
static const oid *defaultPrivType = NULL;
static size_t   defaultPrivTypeLen = 0;

/* this is probably an over-kill ifdef, but why not */
#if defined(HAVE_SYS_TIMES_H) && defined(HAVE_UNISTD_H) && defined(HAVE_TIMES) && defined(_SC_CLK_TCK) && defined(HAVE_SYSCONF) && defined(UINT_MAX)

#define SNMP_USE_TIMES 1

static clock_t snmpv3startClock;
static long clockticks = 0;
static unsigned int lastcalltime = 0;
static unsigned int wrapcounter = 0;

#endif /* times() tests */

#if defined(IFHWADDRLEN) && defined(SIOCGIFHWADDR)
static int      getHwAddress(const char *networkDevice, char *addressOut);
#endif

void
snmpv3_authtype_conf(const char *word, char *cptr)
{
#ifndef NETSNMP_DISABLE_MD5
    if (strcasecmp(cptr, "MD5") == 0)
        defaultAuthType = usmHMACMD5AuthProtocol;
    else
#endif
        if (strcasecmp(cptr, "SHA") == 0)
        defaultAuthType = usmHMACSHA1AuthProtocol;
    else
        config_perror("Unknown authentication type");
    defaultAuthTypeLen = USM_LENGTH_OID_TRANSFORM;
    DEBUGMSGTL(("snmpv3", "set default authentication type: %s\n", cptr));
}

const oid      *
get_default_authtype(size_t * len)
{
    if (defaultAuthType == NULL) {
        defaultAuthType = SNMP_DEFAULT_AUTH_PROTO;
        defaultAuthTypeLen = SNMP_DEFAULT_AUTH_PROTOLEN;
    }
    if (len)
        *len = defaultAuthTypeLen;
    return defaultAuthType;
}

void
snmpv3_privtype_conf(const char *word, char *cptr)
{
    int testcase = 0;

#ifndef NETSNMP_DISABLE_DES
    if (strcasecmp(cptr, "DES") == 0) {
        testcase = 1;
        defaultPrivType = usmDESPrivProtocol;
    }
#endif

#if HAVE_AES
    /* XXX AES: assumes oid length == des oid length */
    if (strcasecmp(cptr, "AES128") == 0 ||
        strcasecmp(cptr, "AES") == 0) {
        testcase = 1;
        defaultPrivType = usmAES128PrivProtocol;
    }
#endif
    if (testcase == 0)
        config_perror("Unknown privacy type");
    defaultPrivTypeLen = SNMP_DEFAULT_PRIV_PROTOLEN;
    DEBUGMSGTL(("snmpv3", "set default privacy type: %s\n", cptr));
}

const oid      *
get_default_privtype(size_t * len)
{
    if (defaultPrivType == NULL) {
#ifndef NETSNMP_DISABLE_DES
        defaultPrivType = usmDESPrivProtocol;
#else
        defaultPrivType = usmAESPrivProtocol;
#endif
        defaultPrivTypeLen = USM_LENGTH_OID_TRANSFORM;
    }
    if (len)
        *len = defaultPrivTypeLen;
    return defaultPrivType;
}

/*******************************************************************-o-******
 * snmpv3_secLevel_conf
 *
 * Parameters:
 *	*word
 *	*cptr
 *
 * Line syntax:
 *	defSecurityLevel "noAuthNoPriv" | "authNoPriv" | "authPriv"
 */

int
parse_secLevel_conf(const char *word, char *cptr) {
    if (strcasecmp(cptr, "noAuthNoPriv") == 0 || strcmp(cptr, "1") == 0 ||
	strcasecmp(cptr, "nanp") == 0) {
        return SNMP_SEC_LEVEL_NOAUTH;
    } else if (strcasecmp(cptr, "authNoPriv") == 0 || strcmp(cptr, "2") == 0 ||
	       strcasecmp(cptr, "anp") == 0) {
        return SNMP_SEC_LEVEL_AUTHNOPRIV;
    } else if (strcasecmp(cptr, "authPriv") == 0 || strcmp(cptr, "3") == 0 ||
	       strcasecmp(cptr, "ap") == 0) {
        return SNMP_SEC_LEVEL_AUTHPRIV;
    } else {
        return -1;
    }
}

void
snmpv3_secLevel_conf(const char *word, char *cptr)
{
    char            buf[1024];
    int             secLevel;

    if ((secLevel = parse_secLevel_conf( word, cptr )) >= 0 ) {
        netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, 
			   NETSNMP_DS_LIB_SECLEVEL, secLevel);
    } else {
        snprintf(buf, sizeof(buf), "Unknown security level: %s", cptr);
        buf[ sizeof(buf)-1 ] = 0;
        config_perror(buf);
    }
    DEBUGMSGTL(("snmpv3", "default secLevel set to: %s = %d\n", cptr,
                netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, 
				   NETSNMP_DS_LIB_SECLEVEL)));
}


int
snmpv3_options(char *optarg, netsnmp_session * session, char **Apsz,
               char **Xpsz, int argc, char *const *argv)
{
    char           *cp = optarg;
    int testcase;
    optarg++;
    /*
     * Support '... -3x=value ....' syntax
     */
    if (*optarg == '=') {
        optarg++;
    }
    /*
     * and '.... "-3x value" ....'  (*with* the quotes)
     */
    while (*optarg && isspace(*optarg)) {
        optarg++;
    }
    /*
     * Finally, handle ".... -3x value ...." syntax
     *   (*without* surrounding quotes)
     */
    if (!*optarg) {
        /*
         * We've run off the end of the argument
         *  so move on the the next.
         */
        optarg = argv[optind++];
        if (optind > argc) {
            fprintf(stderr,
                    "Missing argument after SNMPv3 '-3%c' option.\n", *cp);
            return (-1);
        }
    }

    switch (*cp) {

    case 'Z':
        errno=0;
        session->engineBoots = strtoul(optarg, &cp, 10);
        if (errno || cp == optarg) {
            fprintf(stderr, "Need engine boots value after -3Z flag.\n");
            return (-1);
        }
        if (*cp == ',') {
            char *endptr;
            cp++;
            session->engineTime = strtoul(cp, &endptr, 10);
            if (errno || cp == endptr) {
                fprintf(stderr, "Need engine time after \"-3Z engineBoot,\".\n");
                return (-1);
            }
        } else {
            fprintf(stderr, "Need engine time after \"-3Z engineBoot,\".\n");
            return (-1);
        }
        break;

    case 'e':{
            size_t          ebuf_len = 32, eout_len = 0;
            u_char         *ebuf = (u_char *) malloc(ebuf_len);

            if (ebuf == NULL) {
                fprintf(stderr, "malloc failure processing -3e flag.\n");
                return (-1);
            }
            if (!snmp_hex_to_binary
                (&ebuf, &ebuf_len, &eout_len, 1, optarg)) {
                fprintf(stderr, "Bad engine ID value after -3e flag.\n");
                SNMP_FREE(ebuf);
                return (-1);
            }
            session->securityEngineID = ebuf;
            session->securityEngineIDLen = eout_len;
            break;
        }

    case 'E':{
            size_t          ebuf_len = 32, eout_len = 0;
            u_char         *ebuf = (u_char *) malloc(ebuf_len);

            if (ebuf == NULL) {
                fprintf(stderr, "malloc failure processing -3E flag.\n");
                return (-1);
            }
            if (!snmp_hex_to_binary
                (&ebuf, &ebuf_len, &eout_len, 1, optarg)) {
                fprintf(stderr, "Bad engine ID value after -3E flag.\n");
                SNMP_FREE(ebuf);
                return (-1);
            }
            session->contextEngineID = ebuf;
            session->contextEngineIDLen = eout_len;
            break;
        }

    case 'n':
        session->contextName = optarg;
        session->contextNameLen = strlen(optarg);
        break;

    case 'u':
        session->securityName = optarg;
        session->securityNameLen = strlen(optarg);
        break;

    case 'l':
        if (!strcasecmp(optarg, "noAuthNoPriv") || !strcmp(optarg, "1") ||
            !strcasecmp(optarg, "nanp")) {
            session->securityLevel = SNMP_SEC_LEVEL_NOAUTH;
        } else if (!strcasecmp(optarg, "authNoPriv")
                   || !strcmp(optarg, "2") || !strcasecmp(optarg, "anp")) {
            session->securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
        } else if (!strcasecmp(optarg, "authPriv") || !strcmp(optarg, "3")
                   || !strcasecmp(optarg, "ap")) {
            session->securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
        } else {
            fprintf(stderr,
                    "Invalid security level specified after -3l flag: %s\n",
                    optarg);
            return (-1);
        }

        break;

    case 'a':
#ifndef NETSNMP_DISABLE_MD5
        if (!strcasecmp(optarg, "MD5")) {
            session->securityAuthProto = usmHMACMD5AuthProtocol;
            session->securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN;
        } else
#endif
            if (!strcasecmp(optarg, "SHA")) {
            session->securityAuthProto = usmHMACSHA1AuthProtocol;
            session->securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN;
        } else {
            fprintf(stderr,
                    "Invalid authentication protocol specified after -3a flag: %s\n",
                    optarg);
            return (-1);
        }
        break;

    case 'x':
        testcase = 0;
#ifndef NETSNMP_DISABLE_DES
        if (!strcasecmp(optarg, "DES")) {
            session->securityPrivProto = usmDESPrivProtocol;
            session->securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN;
            testcase = 1;
        }
#endif
#ifdef HAVE_AES
        if (!strcasecmp(optarg, "AES128") ||
            strcasecmp(optarg, "AES")) {
            session->securityPrivProto = usmAES128PrivProtocol;
            session->securityPrivProtoLen = USM_PRIV_PROTO_AES128_LEN;
            testcase = 1;
        }
#endif
        if (testcase == 0) {
            fprintf(stderr,
                    "Invalid privacy protocol specified after -3x flag: %s\n",
                    optarg);
            return (-1);
        }
        break;

    case 'A':
        *Apsz = optarg;
        break;

    case 'X':
        *Xpsz = optarg;
        break;

    case 'm': {
        size_t bufSize = sizeof(session->securityAuthKey);
        u_char *tmpp = session->securityAuthKey;
        if (!snmp_hex_to_binary(&tmpp, &bufSize,
                                &session->securityAuthKeyLen, 0, optarg)) {
            fprintf(stderr, "Bad key value after -3m flag.\n");
            return (-1);
        }
        break;
    }

    case 'M': {
        size_t bufSize = sizeof(session->securityPrivKey);
        u_char *tmpp = session->securityPrivKey;
        if (!snmp_hex_to_binary(&tmpp, &bufSize,
             &session->securityPrivKeyLen, 0, optarg)) {
            fprintf(stderr, "Bad key value after -3M flag.\n");
            return (-1);
        }
        break;
    }

    case 'k': {
        size_t          kbuf_len = 32, kout_len = 0;
        u_char         *kbuf = (u_char *) malloc(kbuf_len);

        if (kbuf == NULL) {
            fprintf(stderr, "malloc failure processing -3k flag.\n");
            return (-1);
        }
        if (!snmp_hex_to_binary
            (&kbuf, &kbuf_len, &kout_len, 1, optarg)) {
            fprintf(stderr, "Bad key value after -3k flag.\n");
            SNMP_FREE(kbuf);
            return (-1);
        }
        session->securityAuthLocalKey = kbuf;
        session->securityAuthLocalKeyLen = kout_len;
        break;
    }

    case 'K': {
        size_t          kbuf_len = 32, kout_len = 0;
        u_char         *kbuf = (u_char *) malloc(kbuf_len);

        if (kbuf == NULL) {
            fprintf(stderr, "malloc failure processing -3K flag.\n");
            return (-1);
        }
        if (!snmp_hex_to_binary
            (&kbuf, &kbuf_len, &kout_len, 1, optarg)) {
            fprintf(stderr, "Bad key value after -3K flag.\n");
            SNMP_FREE(kbuf);
            return (-1);
        }
        session->securityPrivLocalKey = kbuf;
        session->securityPrivLocalKeyLen = kout_len;
        break;
    }
        
    default:
        fprintf(stderr, "Unknown SNMPv3 option passed to -3: %c.\n", *cp);
        return -1;
    }
    return 0;
}

/*******************************************************************-o-******
 * setup_engineID
 *
 * Parameters:
 *	**eidp
 *	 *text	Printable (?) text to be plugged into the snmpEngineID.
 *
 * Return:
 *	Length of allocated engineID string in bytes,  -OR-
 *	-1 on error.
 *
 *
 * Create an snmpEngineID using text and the local IP address.  If eidp
 * is defined, use it to return a pointer to the newly allocated data.
 * Otherwise, use the result to define engineID defined in this module.
 *
 * Line syntax:
 *	engineID <text> | NULL
 *
 * XXX	What if a node has multiple interfaces?
 * XXX	What if multiple engines all choose the same address?
 *      (answer:  You're screwed, because you might need a kul database
 *       which is dependant on the current engineID.  Enumeration and other
 *       tricks won't work). 
 */
int
setup_engineID(u_char ** eidp, const char *text)
{
    int             enterpriseid = htonl(NETSNMP_ENTERPRISE_OID),
        netsnmpoid = htonl(NETSNMP_OID),
        localsetup = (eidp) ? 0 : 1;

    /*
     * Use local engineID if *eidp == NULL.  
     */
#ifdef HAVE_GETHOSTNAME
    u_char          buf[SNMP_MAXBUF_SMALL];
    struct hostent *hent = NULL;
#endif
    u_char         *bufp = NULL;
    size_t          len;
    int             localEngineIDType = engineIDType;
    int             tmpint;
    time_t          tmptime;

    engineIDIsSet = 1;

#ifdef HAVE_GETHOSTNAME
#ifdef AF_INET6
    /*
     * see if they selected IPV4 or IPV6 support 
     */
    if ((ENGINEID_TYPE_IPV6 == localEngineIDType) ||
        (ENGINEID_TYPE_IPV4 == localEngineIDType)) {
        /*
         * get the host name and save the information 
         */
        gethostname((char *) buf, sizeof(buf));
        hent = gethostbyname((char *) buf);
        if (hent && hent->h_addrtype == AF_INET6) {
            localEngineIDType = ENGINEID_TYPE_IPV6;
        } else {
            /*
             * Not IPV6 so we go with default 
             */
            localEngineIDType = ENGINEID_TYPE_IPV4;
        }
    }
#else
    /*
     * No IPV6 support.  Check if they selected IPV6 engineID type.
     *  If so make it IPV4 instead 
     */
    if (ENGINEID_TYPE_IPV6 == localEngineIDType) {
        localEngineIDType = ENGINEID_TYPE_IPV4;
    }
    if (ENGINEID_TYPE_IPV4 == localEngineIDType) {
        /*
         * get the host name and save the information 
         */
        gethostname((char *) buf, sizeof(buf));
        hent = gethostbyname((char *) buf);
    }
#endif
#endif                          /* HAVE_GETHOSTNAME */

    /*
     * Determine if we have text and if so setup our localEngineIDType
     * * appropriately.  
     */
    if (NULL != text) {
        engineIDType = localEngineIDType = ENGINEID_TYPE_TEXT;
    }
    /*
     * Determine length of the engineID string. 
     */
    len = 5;                    /* always have 5 leading bytes */
    switch (localEngineIDType) {
    case ENGINEID_TYPE_TEXT:
        if (NULL == text) {
            snmp_log(LOG_ERR,
                     "Can't set up engineID of type text from an empty string.\n");
            return -1;
        }
        len += strlen(text);    /* 5 leading bytes+text. No NULL char */
        break;
#if defined(IFHWADDRLEN) && defined(SIOCGIFHWADDR)
    case ENGINEID_TYPE_MACADDR:        /* MAC address */
        len += 6;               /* + 6 bytes for MAC address */
        break;
#endif
    case ENGINEID_TYPE_IPV4:   /* IPv4 */
        len += 4;               /* + 4 byte IPV4 address */
        break;
    case ENGINEID_TYPE_IPV6:   /* IPv6 */
        len += 16;              /* + 16 byte IPV6 address */
        break;
    case ENGINEID_TYPE_NETSNMP_RND:        /* Net-SNMP specific encoding */
        if (engineID)           /* already setup, keep current value */
            return engineIDLength;
        if (oldEngineID) {
            len = oldEngineIDLength;
        } else {
            len += sizeof(int) + sizeof(time_t);
        }
        break;
    default:
        snmp_log(LOG_ERR,
                 "Unknown EngineID type requested for setup (%d).  Using IPv4.\n",
                 localEngineIDType);
        localEngineIDType = ENGINEID_TYPE_IPV4; /* make into IPV4 */
        len += 4;               /* + 4 byte IPv4 address */
        break;
    }                           /* switch */


    /*
     * Allocate memory and store enterprise ID.
     */
    if ((bufp = (u_char *) malloc(len)) == NULL) {
        snmp_log_perror("setup_engineID malloc");
        return -1;
    }
    if (localEngineIDType == ENGINEID_TYPE_NETSNMP_RND)
        /*
         * we must use the net-snmp enterprise id here, regardless 
         */
        memcpy(bufp, &netsnmpoid, sizeof(netsnmpoid));    /* XXX Must be 4 bytes! */
    else
        memcpy(bufp, &enterpriseid, sizeof(enterpriseid));      /* XXX Must be 4 bytes! */

    bufp[0] |= 0x80;


    /*
     * Store the given text  -OR-   the first found IP address
     *  -OR-  the MAC address  -OR-  random elements
     * (the latter being the recommended default)
     */
    switch (localEngineIDType) {
    case ENGINEID_TYPE_NETSNMP_RND:
        if (oldEngineID) {
            /*
             * keep our previous notion of the engineID 
             */
            memcpy(bufp, oldEngineID, oldEngineIDLength);
        } else {
            /*
             * Here we've desigend our own ENGINEID that is not based on
             * an address which may change and may even become conflicting
             * in the future like most of the default v3 engineID types
             * suffer from.
             * 
             * Ours is built from 2 fairly random elements: a random number and
             * the current time in seconds.  This method suffers from boxes
             * that may not have a correct clock setting and random number
             * seed at startup, but few OSes should have that problem.
             */
            bufp[4] = ENGINEID_TYPE_NETSNMP_RND;
            tmpint = random();
            memcpy(bufp + 5, &tmpint, sizeof(tmpint));
            tmptime = time(NULL);
            memcpy(bufp + 5 + sizeof(tmpint), &tmptime, sizeof(tmptime));
        }
        break;
    case ENGINEID_TYPE_TEXT:
        bufp[4] = ENGINEID_TYPE_TEXT;
        memcpy((char *) bufp + 5, (text), strlen(text));
        break;
#ifdef HAVE_GETHOSTNAME
#ifdef AF_INET6
    case ENGINEID_TYPE_IPV6:
        bufp[4] = ENGINEID_TYPE_IPV6;
        memcpy(bufp + 5, hent->h_addr_list[0], hent->h_length);
        break;
#endif
#endif
#if defined(IFHWADDRLEN) && defined(SIOCGIFHWADDR)
    case ENGINEID_TYPE_MACADDR:
        {
            int             x;
            bufp[4] = ENGINEID_TYPE_MACADDR;
            /*
             * use default NIC if none provided 
             */
            if (NULL == engineIDNic) {
	      x = getHwAddress(DEFAULT_NIC, (char *)&bufp[5]);
            } else {
	      x = getHwAddress((char *)engineIDNic, (char *)&bufp[5]);
            }
            if (0 != x)
                /*
                 * function failed fill MAC address with zeros 
                 */
            {
                memset(&bufp[5], 0, 6);
            }
        }
        break;
#endif
    case ENGINEID_TYPE_IPV4:
    default:
        bufp[4] = ENGINEID_TYPE_IPV4;
#ifdef HAVE_GETHOSTNAME
        if (hent && hent->h_addrtype == AF_INET) {
            memcpy(bufp + 5, hent->h_addr_list[0], hent->h_length);
        } else {                /* Unknown address type.  Default to 127.0.0.1. */

            bufp[5] = 127;
            bufp[6] = 0;
            bufp[7] = 0;
            bufp[8] = 1;
        }
#else                           /* HAVE_GETHOSTNAME */
        /*
         * Unknown address type.  Default to 127.0.0.1. 
         */
        bufp[5] = 127;
        bufp[6] = 0;
        bufp[7] = 0;
        bufp[8] = 1;
#endif                          /* HAVE_GETHOSTNAME */
        break;
    }

    /*
     * Pass the string back to the calling environment, or use it for
     * our local engineID.
     */
    if (localsetup) {
        SNMP_FREE(engineID);
        engineID = bufp;
        engineIDLength = len;

    } else {
        *eidp = bufp;
    }


    return len;

}                               /* end setup_engineID() */

int
free_engineID(int majorid, int minorid, void *serverarg,
	      void *clientarg)
{
    SNMP_FREE(engineID);
    SNMP_FREE(engineIDNic);
    SNMP_FREE(oldEngineID);
    engineIDIsSet = 0;
    return 0;
}

int
free_enginetime_on_shutdown(int majorid, int minorid, void *serverarg,
			    void *clientarg)
{
    DEBUGMSGTL(("snmpv3", "free enginetime callback called\n"));
    if (engineID != NULL)
	free_enginetime(engineID, engineIDLength);
    return 0;
}

void
usm_parse_create_usmUser(const char *token, char *line)
{
    char           *cp;
    char            buf[SNMP_MAXBUF_MEDIUM];
    struct usmUser *newuser;
    u_char          userKey[SNMP_MAXBUF_SMALL], *tmpp;
    size_t          userKeyLen = SNMP_MAXBUF_SMALL;
    size_t          privKeyLen = 0;
    size_t          ret;
    int             ret2;
    int             testcase;

    newuser = usm_create_user();

    /*
     * READ: Security Name 
     */
    cp = copy_nword(line, buf, sizeof(buf));

    /*
     * might be a -e ENGINEID argument 
     */
    if (strcmp(buf, "-e") == 0) {
        size_t          ebuf_len = 32, eout_len = 0;
        u_char         *ebuf = (u_char *) malloc(ebuf_len);

        if (ebuf == NULL) {
            config_perror("malloc failure processing -e flag");
            usm_free_user(newuser);
            return;
        }

        /*
         * Get the specified engineid from the line.  
         */
        cp = copy_nword(cp, buf, sizeof(buf));
        if (!snmp_hex_to_binary(&ebuf, &ebuf_len, &eout_len, 1, buf)) {
            config_perror("invalid EngineID argument to -e");
            usm_free_user(newuser);
            SNMP_FREE(ebuf);
            return;
        }

        newuser->engineID = ebuf;
        newuser->engineIDLen = eout_len;
        cp = copy_nword(cp, buf, sizeof(buf));
    } else {
        newuser->engineID = snmpv3_generate_engineID(&ret);
        if (ret == 0) {
            usm_free_user(newuser);
            return;
        }
        newuser->engineIDLen = ret;
    }

    newuser->secName = strdup(buf);
    newuser->name = strdup(buf);

    if (!cp)
        goto add;               /* no authentication or privacy type */

    /*
     * READ: Authentication Type 
     */
#ifndef NETSNMP_DISABLE_MD5
    if (strncmp(cp, "MD5", 3) == 0) {
        memcpy(newuser->authProtocol, usmHMACMD5AuthProtocol,
               sizeof(usmHMACMD5AuthProtocol));
    } else
#endif
        if (strncmp(cp, "SHA", 3) == 0) {
        memcpy(newuser->authProtocol, usmHMACSHA1AuthProtocol,
               sizeof(usmHMACSHA1AuthProtocol));
    } else {
        config_perror("Unknown authentication protocol");
        usm_free_user(newuser);
        return;
    }

    cp = skip_token(cp);

    /*
     * READ: Authentication Pass Phrase or key
     */
    if (!cp) {
        config_perror("no authentication pass phrase");
        usm_free_user(newuser);
        return;
    }
    cp = copy_nword(cp, buf, sizeof(buf));
    if (strcmp(buf,"-m") == 0) {
        /* a master key is specified */
        cp = copy_nword(cp, buf, sizeof(buf));
        ret = sizeof(userKey);
        tmpp = userKey;
        userKeyLen = 0;
        if (!snmp_hex_to_binary(&tmpp, &ret, &userKeyLen, 0, buf)) {
            config_perror("invalid key value argument to -m");
            usm_free_user(newuser);
            return;
        }
    } else if (strcmp(buf,"-l") != 0) {
        /* a password is specified */
        userKeyLen = sizeof(userKey);
        ret2 = generate_Ku(newuser->authProtocol, newuser->authProtocolLen,
                          (u_char *) buf, strlen(buf), userKey, &userKeyLen);
        if (ret2 != SNMPERR_SUCCESS) {
            config_perror("could not generate the authentication key from the "
                          "supplied pass phrase.");
            usm_free_user(newuser);
            return;
        }
    }        
        
    /*
     * And turn it into a localized key 
     */
    ret2 = sc_get_properlength(newuser->authProtocol,
                               newuser->authProtocolLen);
    if (ret2 <= 0) {
        config_perror("Could not get proper authentication protocol key length");
	usm_free_user(newuser);
        return;
    }
    newuser->authKey = (u_char *) malloc(ret2);

    if (strcmp(buf,"-l") == 0) {
        /* a local key is directly specified */
        cp = copy_nword(cp, buf, sizeof(buf));
        newuser->authKeyLen = 0;
        ret = ret2;
        if (!snmp_hex_to_binary(&newuser->authKey, &ret,
                                &newuser->authKeyLen, 0, buf)) {
            config_perror("invalid key value argument to -l");
            usm_free_user(newuser);
            return;
        }
        if (ret != newuser->authKeyLen) {
            config_perror("improper key length to -l");
            usm_free_user(newuser);
            return;
        }
    } else {
        newuser->authKeyLen = ret2;
        ret2 = generate_kul(newuser->authProtocol, newuser->authProtocolLen,
                           newuser->engineID, newuser->engineIDLen,
                           userKey, userKeyLen,
                           newuser->authKey, &newuser->authKeyLen);
        if (ret2 != SNMPERR_SUCCESS) {
            config_perror("could not generate localized authentication key "
                          "(Kul) from the master key (Ku).");
            usm_free_user(newuser);
            return;
        }
    }

    if (!cp)
        goto add;               /* no privacy type (which is legal) */

    /*
     * READ: Privacy Type 
     */
    testcase = 0;
#ifndef NETSNMP_DISABLE_DES
    if (strncmp(cp, "DES", 3) == 0) {
        memcpy(newuser->privProtocol, usmDESPrivProtocol,
               sizeof(usmDESPrivProtocol));
        testcase = 1;
	/* DES uses a 128 bit key, 64 bits of which is a salt */
	privKeyLen = 16;
    }
#endif
#ifdef HAVE_AES
    if (strncmp(cp, "AES128", 6) == 0 ||
               strncmp(cp, "AES", 3) == 0) {
        memcpy(newuser->privProtocol, usmAESPrivProtocol,
               sizeof(usmAESPrivProtocol));
        testcase = 1;
	privKeyLen = 16;
    }
#endif
    if (testcase == 0) {
        config_perror("Unknown privacy protocol");
        usm_free_user(newuser);
        return;
    }

    cp = skip_token(cp);
    /*
     * READ: Encryption Pass Phrase or key
     */
    if (!cp) {
        /*
         * assume the same as the authentication key 
         */
        memdup(&newuser->privKey, newuser->authKey, newuser->authKeyLen);
        newuser->privKeyLen = newuser->authKeyLen;
    } else {
        cp = copy_nword(cp, buf, sizeof(buf));
        
        if (strcmp(buf,"-m") == 0) {
            /* a master key is specified */
            cp = copy_nword(cp, buf, sizeof(buf));
            ret = sizeof(userKey);
            tmpp = userKey;
            userKeyLen = 0;
            if (!snmp_hex_to_binary(&tmpp, &ret, &userKeyLen, 0, buf)) {
                config_perror("invalid key value argument to -m");
                usm_free_user(newuser);
                return;
            }
        } else if (strcmp(buf,"-l") != 0) {
            /* a password is specified */
            userKeyLen = sizeof(userKey);
            ret2 = generate_Ku(newuser->authProtocol, newuser->authProtocolLen,
                              (u_char *) buf, strlen(buf), userKey, &userKeyLen);
            if (ret2 != SNMPERR_SUCCESS) {
                config_perror("could not generate the privacy key from the "
                              "supplied pass phrase.");
                usm_free_user(newuser);
                return;
            }
        }        
        
        /*
         * And turn it into a localized key 
         */
        ret2 = sc_get_properlength(newuser->authProtocol,
                                   newuser->authProtocolLen);
        if (ret2 < 0) {
            config_perror("could not get proper key length to use for the "
                          "privacy algorithm.");
            usm_free_user(newuser);
            return;
        }
        newuser->privKey = (u_char *) malloc(ret2);

        if (strcmp(buf,"-l") == 0) {
            /* a local key is directly specified */
            cp = copy_nword(cp, buf, sizeof(buf));
            ret = ret2;
            newuser->privKeyLen = 0;
            if (!snmp_hex_to_binary(&newuser->privKey, &ret,
                                    &newuser->privKeyLen, 0, buf)) {
                config_perror("invalid key value argument to -l");
                usm_free_user(newuser);
                return;
            }
        } else {
            newuser->privKeyLen = ret2;
            ret2 = generate_kul(newuser->authProtocol, newuser->authProtocolLen,
                               newuser->engineID, newuser->engineIDLen,
                               userKey, userKeyLen,
                               newuser->privKey, &newuser->privKeyLen);
            if (ret2 != SNMPERR_SUCCESS) {
                config_perror("could not generate localized privacy key "
                              "(Kul) from the master key (Ku).");
                usm_free_user(newuser);
                return;
            }
        }
    }

    if ((newuser->privKeyLen >= privKeyLen) || (privKeyLen == 0)){
      newuser->privKeyLen = privKeyLen;
    }
    else {
      /* The privKey length is smaller than required by privProtocol */
      usm_free_user(newuser);
      return;
    }

  add:
    usm_add_user(newuser);
    DEBUGMSGTL(("usmUser", "created a new user %s at ", newuser->secName));
    DEBUGMSGHEX(("usmUser", newuser->engineID, newuser->engineIDLen));
    DEBUGMSG(("usmUser", "\n"));
}

/*******************************************************************-o-******
 * engineBoots_conf
 *
 * Parameters:
 *	*word
 *	*cptr
 *
 * Line syntax:
 *	engineBoots <num_boots>
 */
void
engineBoots_conf(const char *word, char *cptr)
{
    engineBoots = atoi(cptr) + 1;
    DEBUGMSGTL(("snmpv3", "engineBoots: %d\n", engineBoots));
}

/*******************************************************************-o-******
 * engineIDType_conf
 *
 * Parameters:
 *	*word
 *	*cptr
 *
 * Line syntax:
 *	engineIDType <1 or 3>
 *		1 is default for IPv4 engine ID type.  Will automatically
 *		    chose between IPv4 & IPv6 if either 1 or 2 is specified.
 *		2 is for IPv6.
 *		3 is hardware (MAC) address, currently supported under Linux
 */
void
engineIDType_conf(const char *word, char *cptr)
{
    engineIDType = atoi(cptr);
    /*
     * verify valid type selected 
     */
    switch (engineIDType) {
    case ENGINEID_TYPE_IPV4:   /* IPv4 */
    case ENGINEID_TYPE_IPV6:   /* IPv6 */
        /*
         * IPV? is always good 
         */
        break;
#if defined(IFHWADDRLEN) && defined(SIOCGIFHWADDR)
    case ENGINEID_TYPE_MACADDR:        /* MAC address */
        break;
#endif
    default:
        /*
         * unsupported one chosen 
         */
        config_perror("Unsupported enginedIDType, forcing IPv4");
        engineIDType = ENGINEID_TYPE_IPV4;
    }
    DEBUGMSGTL(("snmpv3", "engineIDType: %d\n", engineIDType));
}

/*******************************************************************-o-******
 * engineIDNic_conf
 *
 * Parameters:
 *	*word
 *	*cptr
 *
 * Line syntax:
 *	engineIDNic <string>
 *		eth0 is default
 */
void
engineIDNic_conf(const char *word, char *cptr)
{
    /*
     * Make sure they haven't already specified the engineID via the
     * * configuration file 
     */
    if (0 == engineIDIsSet)
        /*
         * engineID has NOT been set via configuration file 
         */
    {
        /*
         * See if already set if so erase & release it 
         */
        if (NULL != engineIDNic) {
            SNMP_FREE(engineIDNic);
        }
        engineIDNic = (u_char *) malloc(strlen(cptr) + 1);
        if (NULL != engineIDNic) {
            strcpy((char *) engineIDNic, cptr);
            DEBUGMSGTL(("snmpv3", "Initializing engineIDNic: %s\n",
                        engineIDNic));
        } else {
            DEBUGMSGTL(("snmpv3",
                        "Error allocating memory for engineIDNic!\n"));
        }
    } else {
        DEBUGMSGTL(("snmpv3",
                    "NOT setting engineIDNic, engineID already set\n"));
    }
}

/*******************************************************************-o-******
 * engineID_conf
 *
 * Parameters:
 *	*word
 *	*cptr
 *
 * This function reads a string from the configuration file and uses that
 * string to initialize the engineID.  It's assumed to be human readable.
 */
void
engineID_conf(const char *word, char *cptr)
{
    setup_engineID(NULL, cptr);
    DEBUGMSGTL(("snmpv3", "initialized engineID with: %s\n", cptr));
}

void
version_conf(const char *word, char *cptr)
{
    int valid = 0;
#ifndef NETSNMP_DISABLE_SNMPV1
    if ((strcmp(cptr,  "1") == 0) ||
        (strcmp(cptr, "v1") == 0)) {
        netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SNMPVERSION, 
			   NETSNMP_DS_SNMP_VERSION_1);       /* bogus value */
        valid = 1;
    }
#endif
#ifndef NETSNMP_DISABLE_SNMPV2C
    if ((strcasecmp(cptr,  "2c") == 0) ||
               (strcasecmp(cptr, "v2c") == 0)) {
        netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SNMPVERSION, 
			   NETSNMP_DS_SNMP_VERSION_2c);
        valid = 1;
    }
#endif
    if ((strcasecmp(cptr,  "3" ) == 0) ||
               (strcasecmp(cptr, "v3" ) == 0)) {
        netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SNMPVERSION, 
			   NETSNMP_DS_SNMP_VERSION_3);
        valid = 1;
    }
    if (!valid) {
        config_perror("Unknown version specification");
        return;
    }
    DEBUGMSGTL(("snmpv3", "set default version to %d\n",
                netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, 
				   NETSNMP_DS_LIB_SNMPVERSION)));
}

/*
 * engineID_old_conf(const char *, char *):
 * 
 * Reads a octet string encoded engineID into the oldEngineID and
 * oldEngineIDLen pointers.
 */
void
oldengineID_conf(const char *word, char *cptr)
{
    read_config_read_octet_string(cptr, &oldEngineID, &oldEngineIDLength);
}

/*
 * merely call 
 */
void
get_enginetime_alarm(unsigned int regnum, void *clientargs)
{
    /* we do this every so (rarely) often just to make sure we watch
       wrapping of the times() output */
    snmpv3_local_snmpEngineTime();
}

/*******************************************************************-o-******
 * init_snmpv3
 *
 * Parameters:
 *	*type	Label for the config file "type" used by calling entity.
 *      
 * Set time and engineID.
 * Set parsing functions for config file tokens.
 * Initialize SNMP Crypto API (SCAPI).
 */
void
init_snmpv3(const char *type)
{
#if SNMP_USE_TIMES
  struct tms dummy;

  /* fixme: -1 is fault code... */
  snmpv3startClock = times(&dummy);

  /* remember how many ticks per second there are, since times() returns this */

  clockticks = sysconf(_SC_CLK_TCK);

#endif /* SNMP_USE_TIMES */

    gettimeofday(&snmpv3starttime, NULL);

    if (!type)
        type = "__snmpapp__";

    /*
     * we need to be called back later 
     */
    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_POST_READ_CONFIG,
                           init_snmpv3_post_config, NULL);

    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_POST_PREMIB_READ_CONFIG,
                           init_snmpv3_post_premib_config, NULL);
    /*
     * we need to be called back later 
     */
    snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
                           snmpv3_store, (void *) strdup(type));

    /*
     * Free stuff at shutdown time
     */
    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_SHUTDOWN,
                           free_enginetime_on_shutdown, NULL);

    /*
     * initialize submodules 
     */
    /*
     * NOTE: this must be after the callbacks are registered above,
     * since they need to be called before the USM callbacks. 
     */
    init_secmod();

    /*
     * register all our configuration handlers (ack, there's a lot) 
     */

    /*
     * handle engineID setup before everything else which may depend on it 
     */
    register_prenetsnmp_mib_handler(type, "engineID", engineID_conf, NULL,
                                    "string");
    register_prenetsnmp_mib_handler(type, "oldEngineID", oldengineID_conf,
                                    NULL, NULL);
    register_prenetsnmp_mib_handler(type, "engineIDType",
                                    engineIDType_conf, NULL, "num");
    register_prenetsnmp_mib_handler(type, "engineIDNic", engineIDNic_conf,
                                    NULL, "string");
    register_config_handler(type, "engineBoots", engineBoots_conf, NULL,
                            NULL);

    /*
     * default store config entries 
     */
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defSecurityName",
			       NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_SECNAME);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defContext", 
			       NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_CONTEXT);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defPassphrase",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_PASSPHRASE);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defAuthPassphrase",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_AUTHPASSPHRASE);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defPrivPassphrase",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_PRIVPASSPHRASE);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defAuthMasterKey",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_AUTHMASTERKEY);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defPrivMasterKey",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_PRIVMASTERKEY);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defAuthLocalizedKey",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_AUTHLOCALIZEDKEY);
    netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defPrivLocalizedKey",
                               NETSNMP_DS_LIBRARY_ID,
                               NETSNMP_DS_LIB_PRIVLOCALIZEDKEY);
    register_config_handler("snmp", "defVersion", version_conf, NULL,
                            "1|2c|3");

    register_config_handler("snmp", "defAuthType", snmpv3_authtype_conf,
                            NULL, "MD5|SHA");
    register_config_handler("snmp", "defPrivType", snmpv3_privtype_conf,
                            NULL,
#ifdef HAVE_AES
                            "DES|AES");
#else
                            "DES (AES support not available)");
#endif
    register_config_handler("snmp", "defSecurityLevel",
                            snmpv3_secLevel_conf, NULL,
                            "noAuthNoPriv|authNoPriv|authPriv");
    register_config_handler(type, "userSetAuthPass", usm_set_password,
                            NULL, NULL);
    register_config_handler(type, "userSetPrivPass", usm_set_password,
                            NULL, NULL);
    register_config_handler(type, "userSetAuthKey", usm_set_password, NULL,
                            NULL);
    register_config_handler(type, "userSetPrivKey", usm_set_password, NULL,
                            NULL);
    register_config_handler(type, "userSetAuthLocalKey", usm_set_password,
                            NULL, NULL);
    register_config_handler(type, "userSetPrivLocalKey", usm_set_password,
                            NULL, NULL);
}

/*
 * initializations for SNMPv3 to be called after the configuration files
 * have been read.
 */

int
init_snmpv3_post_config(int majorid, int minorid, void *serverarg,
                        void *clientarg)
{

    size_t          engineIDLen;
    u_char         *c_engineID;

    c_engineID = snmpv3_generate_engineID(&engineIDLen);

    if (engineIDLen == 0 || !c_engineID) {
        /*
         * Somethine went wrong - help! 
         */
        SNMP_FREE(c_engineID);
        return SNMPERR_GENERR;
    }

    /*
     * if our engineID has changed at all, the boots record must be set to 1 
     */
    if (engineIDLen != (int) oldEngineIDLength ||
        oldEngineID == NULL || c_engineID == NULL ||
        memcmp(oldEngineID, c_engineID, engineIDLen) != 0) {
        engineBoots = 1;
    }

    /*
     * set our local engineTime in the LCD timing cache 
     */
    set_enginetime(c_engineID, engineIDLen,
                   snmpv3_local_snmpEngineBoots(),
                   snmpv3_local_snmpEngineTime(), TRUE);

    SNMP_FREE(c_engineID);
    return SNMPERR_SUCCESS;
}

int
init_snmpv3_post_premib_config(int majorid, int minorid, void *serverarg,
                               void *clientarg)
{
    if (!engineIDIsSet)
        setup_engineID(NULL, NULL);

    return SNMPERR_SUCCESS;
}

/*******************************************************************-o-******
 * store_snmpv3
 *
 * Parameters:
 *	*type
 */
int
snmpv3_store(int majorID, int minorID, void *serverarg, void *clientarg)
{
    char            line[SNMP_MAXBUF_SMALL];
    u_char          c_engineID[SNMP_MAXBUF_SMALL];
    int             engineIDLen;
    const char     *type = (const char *) clientarg;

    if (type == NULL)           /* should never happen, since the arg is ours */
        type = "unknown";

    sprintf(line, "engineBoots %ld", engineBoots);
    read_config_store(type, line);

    engineIDLen = snmpv3_get_engineID(c_engineID, SNMP_MAXBUF_SMALL);

    if (engineIDLen) {
        /*
         * store the engineID used for this run 
         */
        sprintf(line, "oldEngineID ");
        read_config_save_octet_string(line + strlen(line), c_engineID,
                                      engineIDLen);
        read_config_store(type, line);
    }
    return SNMPERR_SUCCESS;
}                               /* snmpv3_store() */

u_long
snmpv3_local_snmpEngineBoots(void)
{
    return engineBoots;
}


/*******************************************************************-o-******
 * snmpv3_get_engineID
 *
 * Parameters:
 *	*buf
 *	 buflen
 *      
 * Returns:
 *	Length of engineID	On Success
 *	SNMPERR_GENERR		Otherwise.
 *
 *
 * Store engineID in buf; return the length.
 *
 */
size_t
snmpv3_get_engineID(u_char * buf, size_t buflen)
{
    /*
     * Sanity check.
     */
    if (!buf || (buflen < engineIDLength)) {
        return 0;
    }
    if (!engineID) {
        return 0;
    }

    memcpy(buf, engineID, engineIDLength);
    return engineIDLength;

}                               /* end snmpv3_get_engineID() */

/*******************************************************************-o-******
 * snmpv3_clone_engineID
 *
 * Parameters:
 *	**dest
 *       *dest_len
 *       src
 *	 srclen
 *      
 * Returns:
 *	Length of engineID	On Success
 *	0		        Otherwise.
 *
 *
 * Clones engineID, creates memory
 *
 */
int
snmpv3_clone_engineID(u_char ** dest, size_t * destlen, u_char * src,
                      size_t srclen)
{
    if (!dest || !destlen)
        return 0;

    if (*dest) {
        SNMP_FREE(*dest);
        *dest = NULL;
    }
    *destlen = 0;

    if (srclen && src) {
        *dest = (u_char *) malloc(srclen);
        if (*dest == NULL)
            return 0;
        memmove(*dest, src, srclen);
        *destlen = srclen;
    }
    return *destlen;
}                               /* end snmpv3_clone_engineID() */


/*******************************************************************-o-******
 * snmpv3_generate_engineID
 *
 * Parameters:
 *	*length
 *      
 * Returns:
 *	Pointer to copy of engineID	On Success.
 *	NULL				If malloc() or snmpv3_get_engineID()
 *						fail.
 *
 * Generates a malloced copy of our engineID.
 *
 * 'length' is set to the length of engineID  -OR-  < 0 on failure.
 */
u_char         *
snmpv3_generate_engineID(size_t * length)
{
    u_char         *newID;
    newID = (u_char *) malloc(engineIDLength);

    if (newID) {
        *length = snmpv3_get_engineID(newID, engineIDLength);
    }

    if (*length == 0) {
        SNMP_FREE(newID);
        newID = NULL;
    }

    return newID;

}                               /* end snmpv3_generate_engineID() */

/*
 * snmpv3_local_snmpEngineTime(): return the number of seconds since the
 * snmpv3 engine last incremented engine_boots 
 */
u_long
snmpv3_local_snmpEngineTime(void)
{
#ifdef SNMP_USE_TIMES
  struct tms dummy;
  clock_t now = times(&dummy);
  /* fixme: -1 is fault code... */
  unsigned int result;

  if (now < snmpv3startClock) {
      result = UINT_MAX - (snmpv3startClock - now);
  } else {
      result = now - snmpv3startClock;
  }
  if (result < lastcalltime) {
      /* wrapped */
      wrapcounter++;
  }
  lastcalltime = result;
  result =  (UINT_MAX/clockticks)*wrapcounter + result/clockticks;

  return result;
#else /* !SNMP_USE_TIMES */
    struct timeval  now;

    gettimeofday(&now, NULL);
    return calculate_sectime_diff(&now, &snmpv3starttime);
#endif /* HAVE_SYS_TIMES_H */
}



/*
 * Code only for Linux systems 
 */
#if defined(IFHWADDRLEN) && defined(SIOCGIFHWADDR)
static int
getHwAddress(const char *networkDevice, /* e.g. "eth0", "eth1" */
             char *addressOut)
{                               /* return address. Len=IFHWADDRLEN */
    /*
     * getHwAddress(...)
     * *
     * *  This function will return a Network Interfaces Card's Hardware
     * *  address (aka MAC address).
     * *
     * *  Input Parameter(s):
     * *      networkDevice - a null terminated string with the name of a network
     * *                      device.  Examples: eth0, eth1, etc...
     * *
     * *  Output Parameter(s):
     * *      addressOut -    This is the binary value of the hardware address.
     * *                      This value is NOT converted into a hexadecimal string.
     * *                      The caller must pre-allocate for a return value of
     * *                      length IFHWADDRLEN
     * *
     * *  Return value:   This function will return zero (0) for success.  If
     * *                  an error occurred the function will return -1.
     * *
     * *  Caveats:    This has only been tested on Ethernet networking cards.
     */
    int             sock;       /* our socket */
    struct ifreq    request;    /* struct which will have HW address */

    if ((NULL == networkDevice) || (NULL == addressOut)) {
        return -1;
    }
    /*
     * In order to find out the hardware (MAC) address of our system under
     * * Linux we must do the following:
     * * 1.  Create a socket
     * * 2.  Do an ioctl(...) call with the SIOCGIFHWADDRLEN operation.
     */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        return -1;
    }
    /*
     * erase the request block 
     */
    memset(&request, 0, sizeof(request));
    /*
     * copy the name of the net device we want to find the HW address for 
     */
    strncpy(request.ifr_name, networkDevice, IFNAMSIZ - 1);
    /*
     * Get the HW address 
     */
    if (ioctl(sock, SIOCGIFHWADDR, &request)) {
        close(sock);
        return -1;
    }
    close(sock);
    memcpy(addressOut, request.ifr_hwaddr.sa_data, IFHWADDRLEN);
    return 0;
}
#endif

#ifdef NETSNMP_ENABLE_TESTING_CODE
/*
 * snmpv3_set_engineBootsAndTime(): this function does not exist.  Go away. 
 */
/*
 * It certainly should never be used, unless in a testing scenero,
 * which is why it was created 
 */
void
snmpv3_set_engineBootsAndTime(int boots, int ttime)
{
    engineBoots = boots;
    gettimeofday(&snmpv3starttime, NULL);
    snmpv3starttime.tv_sec -= ttime;
}
#endif
