/*
 *  Host Resources MIB - Installed Software group implementation - hr_swinst.c
 *
 */

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

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/stat.h>
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#else
# define dirent direct
# ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif
# ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif
# ifdef HAVE_NDIR_H
#  include <ndir.h>
# endif
#endif
#ifdef HAVE_PKGLOCS_H
#include <pkglocs.h>
#endif
#ifdef HAVE_PKGINFO_H
#include <pkginfo.h>
#endif

#ifdef HAVE_LIBRPM
#include <rpm/rpmlib.h>
#include <rpm/header.h>
#include <fcntl.h>

#ifdef HAVE_RPM_RPMFILEUTIL_H
#include <rpm/rpmfileutil.h>
#endif

#ifdef HAVE_RPMGETPATH
#include <rpm/rpmmacro.h>
#endif

#ifdef HAVE_RPM_RPMTS_H
#include <rpm/rpmts.h>
#include <rpm/rpmdb.h>
#endif
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "host_res.h"
#include "hr_swinst.h"
#include <net-snmp/utilities.h>

#define HRSWINST_MONOTONICALLY_INCREASING

netsnmp_feature_require(date_n_time);

        /*********************
	 *
	 *  Kernel & interface information,
	 *   and internal forward declarations
	 *
	 *********************/

/*
 * Reorganize the global data into a single static structure.
 *
 *      Old                     New
 *======================================================
 *      HRSW_directory          swi->swi_directory
 *      HRSW_name[100]          swi->swi_name[SNMP_MAXPATH]
 *      HRSW_index              swi->swi_index
 *
 *                              swi->swi_dbpath         (RPM only)
 *                              swi->swi_maxrec         (RPM only)
 *                              swi->swi_nrec           (RPM only)
 *                              swi->swi_recs           (RPM only)
 *      rpm_db                  swi->swi_rpmdb          (RPM only)
 *                              swi->swi_h              (RPM only)
 *                              swi->swi_prevx          (RPM only)
 *
 *      dp                      swi->swi_dp
 *      de_p                    swi->swi_dep
 */
typedef struct {
#ifdef HAVE_LIBRPM
    char           *swi_directory;
#else
    const char     *swi_directory;
#endif
    char            swi_name[SNMP_MAXPATH];     /* XXX longest file name */
    int             swi_index;

#ifdef HAVE_LIBRPM
    const char     *swi_dbpath;

    time_t          swi_timestamp;      /* modify time on database */
    int             swi_maxrec; /* no. of allocations */
    int             swi_nrec;   /* no. of valid offsets */
    int            *swi_recs;   /* db record offsets */
    rpmts           swi_rpmts;
    Header          swi_h;
    int             swi_prevx;
#else
    DIR            *swi_dp;
    struct dirent  *swi_dep;
#endif

} SWI_t;

static SWI_t    _myswi = { NULL, "", 0 };       /* XXX static for now */

int             header_hrswinst(struct variable *, oid *, size_t *, int,
                                size_t *, WriteMethod **);
int             header_hrswInstEntry(struct variable *, oid *, size_t *,
                                     int, size_t *, WriteMethod **);

#define starttime (*(const struct timeval*)netsnmp_get_agent_starttime())

        /*********************
	 *
	 *  Initialisation & common implementation functions
	 *
	 *********************/
static void     Init_HR_SWInst(void);
static int      Get_Next_HR_SWInst(void);
static void     End_HR_SWInst(void);
static int      Save_HR_SW_info(int ix);

static void     Mark_HRSW_token(void);
static void     Release_HRSW_token(void);


#define	HRSWINST_CHANGE		1
#define	HRSWINST_UPDATE		2
#define	HRSWINST_INDEX		3
#define	HRSWINST_NAME		4
#define	HRSWINST_ID		5
#define	HRSWINST_TYPE		6
#define	HRSWINST_DATE		7

struct variable4 hrswinst_variables[] = {
    {HRSWINST_CHANGE, ASN_TIMETICKS, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 1, {1}},
    {HRSWINST_UPDATE, ASN_TIMETICKS, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 1, {2}},
    {HRSWINST_INDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 3, {3, 1, 1}},
    {HRSWINST_NAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 3, {3, 1, 2}},
    {HRSWINST_ID, ASN_OBJECT_ID, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 3, {3, 1, 3}},
    {HRSWINST_TYPE, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 3, {3, 1, 4}},
    {HRSWINST_DATE, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
     var_hrswinst, 3, {3, 1, 5}}
};
oid             hrswinst_variables_oid[] = { 1, 3, 6, 1, 2, 1, 25, 6 };


#ifdef PKGLOC                   /* Description from HRSW_dir/.../pkginfo: DESC= */
#define	_PATH_HRSW_directory	PKGLOC
#endif
#ifdef hpux9                    /* Description from HRSW_dir/.../index:   fd: */
#define	_PATH_HRSW_directory	"/system"
#endif
#ifdef hpux10                   /* Description from HRSW_dir/.../pfiles/INDEX: title */
#define	_PATH_HRSW_directory	"/var/adm/sw/products"
#endif
#ifdef hpux11                   /* Description from HRSW_dir/.../pfiles/INDEX: title */
#define	_PATH_HRSW_directory	"/var/adm/sw/products"
#endif
#ifdef freebsd2
#define	_PATH_HRSW_directory	"/var/db/pkg"
#endif
#if defined(linux) && !defined(HAVE_LIBRPM)
#define	_PATH_HRSW_directory	"/var/cache/hrmib"
#endif

void
init_hr_swinst(void)
{
#if defined(HAVE_LIBRPM) || defined(_PATH_HRSW_directory)
    SWI_t          *swi = &_myswi;      /* XXX static for now */
#endif
#ifdef HAVE_LIBRPM
    struct stat     stat_buf;
#endif

    /*
     * Read settings from config file,
     * or take system-specific defaults 
     */

#ifdef HAVE_LIBRPM
    if (swi->swi_directory == NULL) {
        char            path[SNMP_MAXPATH];

        /*
         * XXX distinguish between rpm-2.5.x and rpm-2.9x 
         */
#ifdef HAVE_RPMGETPATH
        rpmReadConfigFiles(NULL, NULL);
        swi->swi_dbpath = rpmGetPath("%{_dbpath}", NULL);
#else
        swi->swi_dbpath = "/var/lib/rpm";  /* Most likely */
#endif
        if (swi->swi_directory != NULL)
            free(swi->swi_directory);
        snprintf(path, sizeof(path), "%s/Packages", swi->swi_dbpath);
        if (stat(path, &stat_buf) == -1)
            snprintf(path, sizeof(path), "%s/packages.rpm", swi->swi_dbpath);
        /* check for SQLite DB backend */
        if (stat(path, &stat_buf) == -1)
            snprintf(path, sizeof(path), "%s/rpmdb.sqlite", swi->swi_dbpath);
        path[ sizeof(path)-1 ] = 0;
        swi->swi_directory = strdup(path);
#ifdef HAVE_RPMGETPATH
        rpmFreeRpmrc();
#endif
    }
#else
#  ifdef _PATH_HRSW_directory
    if (swi->swi_directory == NULL) {
        swi->swi_directory = _PATH_HRSW_directory;
    }
    strcpy(swi->swi_name, "[installed name]");  /* default name */
#  else
    /*
     * XXX SunOS4 package directory is ?? -MJS 
     */
    return;                     /* packages not known - don't register */
#  endif
#endif

    REGISTER_MIB("host/hr_swinst", hrswinst_variables, variable4,
                 hrswinst_variables_oid);
}

/*
 * header_hrswinst(...
 * Arguments:
 * vp     IN      - pointer to variable entry that points here
 * name    IN/OUT  - IN/name requested, OUT/name found
 * length  IN/OUT  - length of IN/OUT oid's 
 * exact   IN      - TRUE if an exact match was requested
 * var_len OUT     - length of variable or 0 if function returned
 * write_method
 */

int
header_hrswinst(struct variable *vp,
                oid * name,
                size_t * length,
                int exact, size_t * var_len, WriteMethod ** write_method)
{
#define HRSWINST_NAME_LENGTH	9
    oid             newname[MAX_OID_LEN];
    int             result;

    DEBUGMSGTL(("host/hr_swinst", "var_hrswinst: "));
    DEBUGMSGOID(("host/hr_swinst", name, *length));
    DEBUGMSG(("host/hr_swinst", " %d\n", exact));

    memcpy((char *) newname, (char *) vp->name, vp->namelen * sizeof(oid));
    newname[HRSWINST_NAME_LENGTH] = 0;
    result = snmp_oid_compare(name, *length, newname, vp->namelen + 1);
    if ((exact && (result != 0)) || (!exact && (result >= 0)))
        return (MATCH_FAILED);
    memcpy((char *) name, (char *) newname,
           (vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;

    *write_method = (WriteMethod*)0;
    *var_len = sizeof(long);    /* default to 'long' results */
    return (MATCH_SUCCEEDED);
}

int
header_hrswInstEntry(struct variable *vp,
                     oid * name,
                     size_t * length,
                     int exact,
                     size_t * var_len, WriteMethod ** write_method)
{
#define HRSWINST_ENTRY_NAME_LENGTH	11
    oid             newname[MAX_OID_LEN];
    int             swinst_idx, LowIndex = -1;
    int             result;
    int             err = 0, errcount = 0;

    DEBUGMSGTL(("host/hr_swinst", "var_hrswinstEntry: "));
    DEBUGMSGOID(("host/hr_swinst", name, *length));
    DEBUGMSG(("host/hr_swinst", " %d\n", exact));

    memcpy((char *) newname, (char *) vp->name, vp->namelen * sizeof(oid));
    /*
     * Find "next" installed software entry 
     */

    do {
        Init_HR_SWInst();
        while ((swinst_idx = Get_Next_HR_SWInst()) != -1) {
            DEBUGMSG(("host/hr_swinst", "(index %d ....", swinst_idx));

            newname[HRSWINST_ENTRY_NAME_LENGTH] = swinst_idx;
            DEBUGMSGOID(("host/hr_swinst", newname, *length));
            DEBUGMSG(("host/hr_swinst", "\n"));
            result = snmp_oid_compare(name, *length, newname, vp->namelen + 1);
            if (exact && (result == 0)) {
                LowIndex = swinst_idx;
                err = Save_HR_SW_info(LowIndex);
                break;
            }
            if ((!exact && (result < 0)) &&
                (LowIndex == -1 || swinst_idx < LowIndex)) {
                LowIndex = swinst_idx;
                err = Save_HR_SW_info(LowIndex);
#ifdef HRSWINST_MONOTONICALLY_INCREASING
                break;
#endif
            }
        }
        if (err != 0 )
            errcount++;
    /* restart until Save_HR_SW_info() succeeds,max. 3 times */
    } while (err != 0 && errcount < 3); 
    if (err != 0) {
        DEBUGMSGTL(("host/hr_swinst", "restart did not help, bailing out\n"));
        return (MATCH_FAILED);
    }
	    
    Mark_HRSW_token();
    End_HR_SWInst();

    if (LowIndex == -1) {
        DEBUGMSGTL(("host/hr_swinst", "... index out of range\n"));
        return (MATCH_FAILED);
    }

    memcpy((char *) name, (char *) newname,
           (vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;
    *write_method = (WriteMethod*)0;
    *var_len = sizeof(long);    /* default to 'long' results */

    DEBUGMSGTL(("host/hr_inst", "... get installed S/W stats "));
    DEBUGMSGOID(("host/hr_inst", name, *length));
    DEBUGMSG(("host/hr_inst", "\n"));
    return LowIndex;
}

        /*********************
	 *
	 *  System specific implementation functions
	 *
	 *********************/


u_char         *
var_hrswinst(struct variable * vp,
             oid * name,
             size_t * length,
             int exact, size_t * var_len, WriteMethod ** write_method)
{
    SWI_t          *swi = &_myswi;      /* XXX static for now */
    int             sw_idx = 0;
    static char     string[SNMP_MAXPATH];
    u_char         *ret = NULL;
    struct stat     stat_buf;

    if (vp->magic < HRSWINST_INDEX) {
        if (header_hrswinst(vp, name, length, exact, var_len, write_method)
            == MATCH_FAILED)
            return NULL;
    } else {

        sw_idx =
            header_hrswInstEntry(vp, name, length, exact, var_len,
                                 write_method);
        if (sw_idx == MATCH_FAILED)
            return NULL;
    }

    switch (vp->magic) {
    case HRSWINST_CHANGE:
    case HRSWINST_UPDATE:
        string[0] = '\0';

        if (swi->swi_directory != NULL)
            strlcpy(string, swi->swi_directory, sizeof(string));

        if (*string && (stat(string, &stat_buf) != -1)) {
            if (stat_buf.st_mtime > starttime.tv_sec)
                /*
                 * changed 'recently' - i.e. since this agent started 
                 */
                long_return = (stat_buf.st_mtime - starttime.tv_sec) * 100;
            else
                long_return = 0;        /* predates this agent */
        } else
#ifdef NETSNMP_NO_DUMMY_VALUES
            return NULL;
#else
            long_return = 363136200;
#endif
        ret = (u_char *) & long_return;
        break;

    case HRSWINST_INDEX:
        long_return = sw_idx;
        ret = (u_char *) & long_return;
        break;
    case HRSWINST_NAME:
        {
#ifdef HAVE_PKGINFO
            char *pver;
# endif
            strlcpy(string, swi->swi_name, sizeof(string));

/* If we are on a solaris machine, the package names do not include versioning info,
 * so we must add it manually
 */
#ifdef HAVE_PKGINFO
            pver = pkgparam(swi->swi_name, "VERSION");
            /* 1 spot for the terminating null and one for the dash */
            if (pver && 
               (strlen(pver) + 2 + strlen(string) <= sizeof(string))) {
                strcat(string, "-");
                strcat(string, pver);
            }
# endif

            /*
             * This will be unchanged from the initial "null"
             * value, if swi->swi_name is not defined 
             */
            string[sizeof(string) - 1] = '\0';
            *var_len = strlen(string);
            ret = (u_char *) string;
        }
        break;
    case HRSWINST_ID:
        *var_len = nullOidLen;
        ret = (u_char *) nullOid;
        break;
    case HRSWINST_TYPE:
        {
#ifdef HAVE_PKGINFO
            /*
             * at least on solaris2 this works 
             */
            char           *catg = pkgparam(swi->swi_name, "CATEGORY");

            if (catg == NULL) {
                long_return = 1;        /*  unknown  */
            } else {
                if (strstr(catg, "system") != NULL) {
                    long_return = 2;    /*  operatingSystem  */
                } else if (strstr(catg, "application") != NULL) {
                    long_return = 4;    /*  applcation  */
                } else {
                    long_return = 1;    /*  unknown  */
                }
                free(catg);
            }
#else
# ifdef HAVE_LIBRPM
            char *rpm_groups;
            if ( headerGetEntry(swi->swi_h, RPMTAG_GROUP, NULL, (void **) &rpm_groups, NULL) ) {
                if ( strstr(rpm_groups, "System Environment") != NULL )
                    long_return = 2;	/* operatingSystem */
                else
                    long_return = 4;	/* applcation */
            } else {
                long_return = 1;    /* unknown */
            }
# else
            long_return = 1;    /* unknown */
# endif
#endif
            ret = (u_char *) & long_return;
        }
        break;
    case HRSWINST_DATE:
        {
#ifdef HAVE_LIBRPM
            int32_t         *rpm_data;
            if ( headerGetEntry(swi->swi_h, RPMTAG_INSTALLTIME, NULL, (void **) &rpm_data, NULL) ) {
                time_t          installTime = *rpm_data;
                ret = date_n_time(&installTime, var_len);
            } else {
                ret = date_n_time(NULL, var_len);
            }
#else
            if (swi->swi_directory != NULL) {
                snprintf(string, sizeof(string), "%s/%s",
                         swi->swi_directory, swi->swi_name);
                string[ sizeof(string)-1 ] = 0;
                if (stat(string, &stat_buf) >= 0)
                    ret = date_n_time(&stat_buf.st_mtime, var_len);
                else
                    goto err;
            } else {
err:
#ifdef NETSNMP_NO_DUMMY_VALUES
                ret = NULL;
#else
                sprintf(string, "back in the mists of time");
                *var_len = strlen(string);
                ret = (u_char *) string;
#endif
            }
#endif
        }
        break;
    default:
        DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_hrswinst\n",
                    vp->magic));
        ret = NULL;
        break;
    }
    Release_HRSW_token();
    return ret;
}


        /*********************
	 *
	 *  Internal implementation functions
	 *
	 *********************/

#ifdef	HAVE_LIBRPM
static void
Check_HRSW_cache(void *xxx)
{
    SWI_t          *swi = (SWI_t *) xxx;

    /*
     * Make sure cache is up-to-date 
     */
    if (swi->swi_recs != NULL) {
        struct stat     sb;
        lstat(swi->swi_directory, &sb);
        if (swi->swi_timestamp == sb.st_mtime)
            return;
        swi->swi_timestamp = sb.st_mtime;
        swi->swi_maxrec = 0;
    }

    /*
     * Get header offsets 
     */
    {
        int             ix = 0;
        int             offset;

        rpmdbMatchIterator mi = NULL;
        Header          h;
        mi = rpmtsInitIterator(swi->swi_rpmts, RPMDBI_PACKAGES, NULL, 0);
        while ((h = rpmdbNextIterator(mi)) != NULL) {
            offset = rpmdbGetIteratorOffset(mi);

            if (ix >= swi->swi_maxrec) {
                swi->swi_maxrec += 256;
                swi->swi_recs = (swi->swi_recs == NULL)
                    ? (int *) malloc(swi->swi_maxrec * sizeof(int))
                    : (int *) realloc(swi->swi_recs,
                                      swi->swi_maxrec * sizeof(int));
            }
            swi->swi_recs[ix++] = offset;
        }
        rpmdbFreeIterator(mi);

        swi->swi_nrec = ix;
    }
}
#endif                          /* HAVE_LIBRPM */

void
Init_HR_SWInst(void)
{
    SWI_t          *swi = &_myswi;      /* XXX static for now */
    swi->swi_index = 0;

#ifdef HAVE_LIBRPM
    if (swi->swi_rpmts != NULL)
        return;
    swi->swi_rpmts = rpmtsCreate();
    rpmtsSetVSFlags( swi->swi_rpmts, (_RPMVSF_NOSIGNATURES|_RPMVSF_NODIGESTS));
    Check_HRSW_cache(swi);
#else
    if (swi->swi_directory != NULL) {
        if (swi->swi_dp != NULL) {
            closedir(swi->swi_dp);
            swi->swi_dp = NULL;
        }
        if ((swi->swi_dp = opendir(swi->swi_directory)) == NULL)
            swi->swi_index = -1;
    } else
        swi->swi_index = -1;
#endif
}

int
Get_Next_HR_SWInst(void)
{
    SWI_t          *swi = &_myswi;      /* XXX static for now */

    if (swi->swi_index == -1)
        return -1;

#ifdef HAVE_LIBRPM
    /*
     * XXX Watchout: index starts with 1 
     */
    if (0 <= swi->swi_index && swi->swi_index < swi->swi_nrec)
        return ++swi->swi_index;
#else
    if (swi->swi_directory != NULL) {
        while ((swi->swi_dep = readdir(swi->swi_dp)) != NULL) {
            if (swi->swi_dep->d_name[0] == '.')
                continue;

            /*
             * Ought to check for "properly-formed" entry 
             */

            return ++swi->swi_index;
        }
    }
#endif

    return -1;
}

int
Save_HR_SW_info(int ix)
{
    SWI_t          *swi = &_myswi;      /* XXX static for now */

#ifdef HAVE_LIBRPM
    /*
     * XXX Watchout: ix starts with 1 
     */
    if (1 <= ix && ix <= swi->swi_nrec && ix != swi->swi_prevx) {
        int             offset;
        Header          h;
        char           *n, *v, *r;

        offset = swi->swi_recs[ix - 1];

        {
            rpmdbMatchIterator mi;
            mi = rpmtsInitIterator(swi->swi_rpmts, RPMDBI_PACKAGES,
                                   &offset, sizeof(offset));
            if ((h = rpmdbNextIterator(mi)) != NULL)
                h = headerLink(h);
            rpmdbFreeIterator(mi);
        }

        if (h == NULL) {
            DEBUGMSGTL(("host/hr_swinst",
                    "RPM cache has probably expired when reading entry %d, "
                    "reloading...\n", ix));
            swi->swi_timestamp = 0;
            return -1;
        }
        if (swi->swi_h != NULL)
            headerFree(swi->swi_h);
        swi->swi_h = h;
        swi->swi_prevx = ix;

        headerGetEntry(swi->swi_h, RPMTAG_NAME, NULL, (void **) &n, NULL);
        headerGetEntry(swi->swi_h, RPMTAG_VERSION, NULL, (void **) &v,
                       NULL);
        headerGetEntry(swi->swi_h, RPMTAG_RELEASE, NULL, (void **) &r,
                       NULL);
        snprintf(swi->swi_name, sizeof(swi->swi_name), "%s-%s-%s", n, v, r);
        swi->swi_name[ sizeof(swi->swi_name)-1 ] = 0;
    }
#else
    snprintf(swi->swi_name, sizeof(swi->swi_name), "%s", swi->swi_dep->d_name);
    swi->swi_name[ sizeof(swi->swi_name)-1 ] = 0;
#endif
    return 0;
}

void
Mark_HRSW_token(void)
{
}

void
Release_HRSW_token(void)
{
#ifdef	HAVE_LIBRPM
    SWI_t          *swi = &_myswi;      /* XXX static for now */
    if (swi != NULL && swi->swi_h) {
        headerFree(swi->swi_h);
        swi->swi_h = NULL;
        swi->swi_prevx = -1;
    }
#endif                          /* HAVE_LIBRPM */
}

void
End_HR_SWInst(void)
{
    SWI_t          *swi = &_myswi;      /* XXX static for now */

#ifdef HAVE_LIBRPM
    rpmtsFree(swi->swi_rpmts); /* or only on finishing ? */
    swi->swi_rpmts = NULL;
#else
    if (swi->swi_dp != NULL)
        closedir(swi->swi_dp);
    swi->swi_dp = NULL;
#endif
}
