/*
 * Copyright © 2002  Networks Associates Technology, Inc.
 * All rights reserved.
 *
 * microb.c
 * This is a benchmark program.  It tests the speed of various priv_
 * operations in comparison to their non-priv versions.
 *
 * $Id: microb.c,v 1.3 2003/03/08 05:41:46 dougk Exp $
 */

#define REP_COUNT 100000
#define TEST_USER "testuser"
#define TEST_PASSWD "password"

#include "../config.h"

#include "privman.h"

#include <stdio.h>

#if   defined(HAVE_SECURITY_PAM_MISC_H)
#include <security/pam_misc.h>
#elif defined(HAVE_PAM_PAM_MISC_H)
#include <pam/pam_misc.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <math.h>

#if   defined(HAVE_SECURITY_PAM_APPL_H)
#include <security/pam_appl.h>
#elif defined(HAVE_PAM_PAM_APPL_H)
#include <pam/pam_appl.h>
#endif

#define USEC_IN_SEC 1000000
int ticks_per_sec = 0;

#ifdef __cplusplus
#define UNUSED(p)
#else
#define UNUSED(p) p __attribute((unused))
#endif

double tv_sub(struct timeval *tv1, struct timeval *tv2)
{
    return (tv1->tv_sec  - tv2->tv_sec) +
           (tv1->tv_usec - tv2->tv_usec) / (double)USEC_IN_SEC;
}


//Run this when you are done collecting data
double std_dev(double accum, double squared_accum, long n)
{
    if(0==n){
        //You may want to do something else instead of return 0
        return 0.0;
    } else
        return sqrt((squared_accum - ((accum * accum )/(n)))/(n-1));
}

#define testop( op )                                                    \
do {                                                                    \
    gettimeofday(&before, NULL);                                        \
    op;                                                                 \
    gettimeofday(&after, NULL);                                         \
} while (0)

#define compare_help(op, accum, accum_sq, accum_count, reps)            \
do {                                                                    \
    int i;                                                              \
    double rep_time;                                                    \
    for( i = 0 ; i < reps ; ++i) {                                      \
        testop( op );                                                   \
        rep_time = tv_sub(&after,&before);                              \
        accum += rep_time;                                              \
        accum_sq += (rep_time * rep_time);                              \
        accum_count += 1;                                               \
    }                                                                   \
} while (0)

#define compare( op1, op2, op1string, op2string, reps)                  \
do {                                                                    \
    double op1_total = 0.0, op1_sq_tot = 0.0; long op1_n = 0;           \
    double op2_total = 0.0, op2_sq_tot = 0.0; long op2_n = 0;           \
                                                                        \
    compare_help( op1, op1_total, op1_sq_tot, op1_n, reps);             \
    compare_help( op2, op2_total, op2_sq_tot, op2_n, reps);             \
                                                                        \
    fprintf(stderr, "%s\t%g +- %g\t%g\t%g\t%ld\n", op1string,           \
            op1_total / op1_n, std_dev(op1_total, op1_sq_tot, op1_n),   \
            op1_total, op1_sq_tot, op1_n);                              \
    fprintf(stderr, "%s\t%g +- %g\t%g\t%g\t%ld\n", op2string,           \
            op2_total / op2_n, std_dev(op2_total, op2_sq_tot, op2_n),   \
            op2_total, op2_sq_tot, op2_n);                              \
    fprintf(stderr,                                                     \
            "The privman version of %s takes %d%% as long as the original\n",\
            op2string, (int)(op1_total * 100.0 / op2_total));            \
} while (0)

void rerunfn()
{
    exit(0);
}

/*
 * Function to test the speed of rerun.
 */
void test_rerun(void)
{
    pid_t pid;
    pid = priv_rerunas(rerunfn, NULL, "nobody", 0, PRIV_RR_OLD_SLAVE_MONITORED);
    priv_wait4(pid, 0, 0, 0);
}
void test_fork(void)
{
    pid_t pid;
    pid = fork();
    if (pid == 0)
        _exit(0);
    else
        wait4(pid,0,0,0);
}

static int cheat_convert(int n, const struct pam_message **msg,
        struct pam_response **resp, void *UNUSED(data))
{
    struct pam_response *reply = NULL;
    int i;

    reply = malloc(sizeof(*reply) * n);
    for ( i = 0; i < n; ++i ) {
        switch(msg[i]->msg_style) {
        default:
            fprintf(stderr, "Unknown style %d\n", msg[i]->msg_style);
            goto failed;
        case PAM_PROMPT_ECHO_ON:
            /* Username! */
            reply[i].resp_retcode = PAM_SUCCESS;
            reply[i].resp = strdup(TEST_USER);
            break;
        case PAM_PROMPT_ECHO_OFF:
            /* Password */
            reply[i].resp_retcode = PAM_SUCCESS;
            reply[i].resp = strdup(TEST_PASSWD);
            break;
        case PAM_TEXT_INFO:
        case PAM_ERROR_MSG:
            reply[i].resp_retcode = PAM_SUCCESS;
            reply[i].resp = NULL;
            break;
        }
    }

    *resp = reply;
    return PAM_SUCCESS;
failed:
    free(reply);
    return PAM_CONV_ERR;
}

static struct pam_conv conv = {
    cheat_convert,
    NULL
};


void test_pam(void)
{
    pam_handle_t *pamh=NULL;
    int retval;

    /* Use "login" cause I don't feel like copying check_user to pam.d */
    retval = pam_start("login", NULL, &conv, &pamh);

    if (retval != PAM_SUCCESS)
        goto finished;
        
    retval = pam_authenticate(pamh, 0); /* is user really user? */
    if (retval != PAM_SUCCESS)
        goto finished;

    retval = pam_acct_mgmt(pamh, 0);    /* permitted access? */

finished:
    if (retval != PAM_SUCCESS)
        fprintf(stderr, "pam failed?\n");
    pam_end(pamh,retval);     /* close Linux-PAM */
}

void test_priv_pam(void)
{
    pam_handle_t *pamh=NULL;
    int retval;

    /* Use "login" cause I don't feel like copying check_user to pam.d */
    retval = priv_pam_start("login", NULL, &conv, &pamh);

    if (retval != PAM_SUCCESS)
        goto finished;
        
    retval = priv_pam_authenticate(pamh, 0); /* is user really user? */
    if (retval != PAM_SUCCESS)
        goto finished;

    retval = priv_pam_acct_mgmt(pamh, 0);    /* permitted access? */

finished:
    if (retval != PAM_SUCCESS)
        fprintf(stderr, "pam failed?\n");
    priv_pam_end(pamh,retval);     /* close Linux-PAM */
}

int main()
{
    struct timeval      before;
    struct timeval      after;
    struct sockaddr_in  addr;
    int                 fd;
    FILE               *f;
    double              pam_auth_time = 0.0, priv_pam_auth_time = 0.0;
    double              pam_auth_time_sq = 0.0, priv_pam_auth_time_sq = 0.0;
    long                pam_auth_time_n = 0, priv_pam_auth_time_n = 0;

    /* Init global constant */
    ticks_per_sec = sysconf(_SC_CLK_TCK);

    fprintf(stderr, "running benchmarks\n");
    /* Setup the libraries to take this out of the equation. */
    test_pam();
    /* Have to do this before priv_init */
    compare_help( test_pam(),
            pam_auth_time, pam_auth_time_sq, pam_auth_time_n,
            REP_COUNT / 10);

    /* Now priv_sep. */
    testop( priv_init("microb") );
    fprintf(stderr, "priv_init took 0.%6.6lu seconds\n",
            (long)(tv_sub(&after,&before) * USEC_IN_SEC));

    /* Test 0: pam_auth cycle. */

    compare_help( test_priv_pam(),
            priv_pam_auth_time, priv_pam_auth_time_sq, priv_pam_auth_time_n,
            REP_COUNT / 10);
    fprintf(stderr, "%s\t%g +- %g\t%g\t%g\t%ld\n", "pam_auth",
            pam_auth_time / pam_auth_time_n,
            std_dev(pam_auth_time, pam_auth_time_sq, pam_auth_time_n),
            pam_auth_time, pam_auth_time_sq, pam_auth_time_n);
    fprintf(stderr, "%s\t%g +- %g\t%g\t%g\t%ld\n", "priv_pam",
            priv_pam_auth_time / priv_pam_auth_time_n,
            std_dev(priv_pam_auth_time,
                priv_pam_auth_time_sq, priv_pam_auth_time_n),
            priv_pam_auth_time, priv_pam_auth_time_sq, priv_pam_auth_time_n);
    fprintf(stderr,
            "The privman version of %s takes %d%% as long as the original\n",
            "pam_authenticate",
            (int)(priv_pam_auth_time * 100.0 / pam_auth_time));

    /*
     * Test 1: open, fopen.
     */
    /* Cache the inodes. */
    fd = open("/etc/passwd", O_RDONLY); close(fd);

    compare( fd = priv_open("/etc/passwd", O_RDONLY); close(fd),
            fd = open("/etc/passwd", O_RDONLY); close(fd),
            "priv_open", "open    ", REP_COUNT);

    compare( f = priv_fopen("/etc/passwd", "r"); fclose(f),
             f = fopen("/etc/passwd", "r"); fclose(f),
             "priv_fopen", "fopen    ", REP_COUNT);

    addr.sin_family     = AF_INET;
    addr.sin_port       = htons(1234);
    addr.sin_addr.s_addr= INADDR_ANY;

    compare (
        fd = socket(PF_INET, SOCK_STREAM, 0);
        priv_bind(fd, (struct sockaddr *)&addr, sizeof(addr)); close(fd),
        fd = socket(PF_INET, SOCK_STREAM, 0);
        bind(fd, (struct sockaddr *)&addr, sizeof(addr)); close(fd),
        "priv_bind", "bind    ", REP_COUNT);

    compare (test_rerun(), test_fork(),
            "rerun    ", "fork+exit", REP_COUNT / 10);

    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1