
#include <pam_appl.h>
#include <stdio.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>


#define get_int16(s) ((((unsigned char*)  (s))[0] << 8) | \
                      (((unsigned char*)  (s))[1]))


#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
                        ((unsigned char*)(s))[1] = (i)         & 0xff;}

#ifndef D
/*#define D(str) fprintf(stderr, (str)) */
#define D(str)
#endif

static int read_fill(int fd, unsigned char *buf, int len)
{
    int i, got = 0;

    do {
        if ((i = read(fd, buf+got, len-got)) <= 0) {
            if (i == 0) return got;
            if (errno != EINTR)
                return got;
            i = 0;
        }
        got += i;
    } while (got < len);
    return (len);
}



static int write_fill(int fd, char *buf, int len)
{
    int i, done = 0;

    do {
        if ((i = write(fd, buf+done, len-done)) < 0) {
            if (errno != EINTR)
                return (i);
            i = 0;
        }
        done += i;
    } while (done < len);
    return (len);
}


#if 0
/*
 * These functions are for binary prompt manipulation.
 * The manner in which a binary prompt is processed is application
 * specific, so these function pointers are provided and can be
 * initialized by the application prior to the conversation function
 * being used.
 */

static void pam_misc_conv_delete_binary(void *appdata,
                                        pamc_bp_t *delete_me)
{
    PAM_BP_RENEW(delete_me, 0, 0);
}

int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
      = pam_misc_conv_delete_binary;
#endif

/*
 * This conversation function is supposed to be a generic PAM one.
 * Unfortunately, it is _not_ completely compatible with the Solaris PAM
 * codebase.
 *
 * Namely, for msgm's that contain multiple prompts, this function
 * interprets "const struct pam_message **msgm" as equivalent to
 * "const struct pam_message *msgm[]". The Solaris module
 * implementation interprets the **msgm object as a pointer to a
 * pointer to an array of "struct pam_message" objects (that is, a
 * confusing amount of pointer indirection).
 */

int misc_conv(int num_msg, const struct pam_message **msgm,
              struct pam_response **response, void *appdata_ptr)
{
    int count=0;
    struct pam_response *reply;

    if (num_msg <= 0)
        return PAM_CONV_ERR;

    D(("allocating empty response structure array."));

    reply = (struct pam_response *) calloc(num_msg,
                                           sizeof(struct pam_response));
    if (reply == NULL) {
        D(("no memory for responses"));
        return PAM_CONV_ERR;
    }

    D(("entering conversation function."));

    for (count=0; count < num_msg; ++count) {
        char *string=NULL;

        switch (msgm[count]->msg_style) {
        case PAM_PROMPT_ECHO_OFF:
            string = (char*)appdata_ptr;
            break;
        case PAM_PROMPT_ECHO_ON:
            string = (char*)appdata_ptr;
            break;
        case PAM_ERROR_MSG:
            if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
                goto failed_conversation;
            }
            break;
        case PAM_TEXT_INFO:
            if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
                goto failed_conversation;
            }
            break;
#if 0
        case PAM_BINARY_PROMPT:
        {
            pamc_bp_t binary_prompt = NULL;

            if (!msgm[count]->msg || !pam_binary_handler_fn) {
                goto failed_conversation;
            }

            PAM_BP_RENEW(&binary_prompt,
                         PAM_BP_RCONTROL(msgm[count]->msg),
                         PAM_BP_LENGTH(msgm[count]->msg));
            PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
                        PAM_BP_RDATA(msgm[count]->msg));

            if (pam_binary_handler_fn(appdata_ptr,
                                      &binary_prompt) != PAM_SUCCESS
                || (binary_prompt == NULL)) {
                goto failed_conversation;
            }
            string = (char *) binary_prompt;
            binary_prompt = NULL;

            break;
        }
#endif
        default:
            fprintf(stderr, "erroneous conversation (%d)\n"
                    ,msgm[count]->msg_style);
            goto failed_conversation;
        }

        if (string) {                         /* must add to reply array */
            /* add string to list of responses */

            reply[count].resp_retcode = 0;
            reply[count].resp = string;
            string = NULL;
        }
    }

    *response = reply;
    reply = NULL;

    return PAM_SUCCESS;

failed_conversation:

    D(("the conversation failed"));

    if (reply) {
        for (count=0; count<num_msg; ++count) {
            if (reply[count].resp == NULL) {
                continue;
            }
            switch (msgm[count]->msg_style) {
            case PAM_PROMPT_ECHO_ON:
            case PAM_PROMPT_ECHO_OFF:
#if 0
                _pam_overwrite(reply[count].resp);
#endif
                free(reply[count].resp);
                break;
#if 0
            case PAM_BINARY_PROMPT:
                pam_binary_handler_free(appdata_ptr,
                                        (pamc_bp_t *) &reply[count].resp);
                break;
#endif
            case PAM_ERROR_MSG:
            case PAM_TEXT_INFO:
                /* should not actually be able to get here... */
                free(reply[count].resp);
            }
            reply[count].resp = NULL;
        }
        /* forget reply too */
        free(reply);
        reply = NULL;
    }

    return PAM_CONV_ERR;
}






static struct pam_conv conv = {
    misc_conv,
    NULL
};

static void werr(pam_handle_t *pamh, int sid, int ecode, char *phase)
{
    char buf[BUFSIZ];
    int len;

    sprintf(&buf[2], "pam %d no %s %s",
            sid, phase, pam_strerror(pamh, ecode));
    len = strlen(&buf[2]);
    put_int16(len, &buf[0]);
    if (write_fill(1, buf, len+2) != len+2)
        exit(1);
}


static void wok(int sid)
{
    char buf[BUFSIZ];
    int len;

    sprintf(&buf[2], "pam %d yes", sid);
    len = strlen(&buf[2]);
    put_int16(len, &buf[0]);
    if (write_fill(1, buf, len+2) != len+2)
        exit(1);
}

static void wstart()
{
    char buf[5];


    sprintf(&buf[2], "ok");
    put_int16(2, &buf[0]);
    if (write_fill(1, buf, 4) != 4) {
        exit(1);
    }
}


struct session {
    pam_handle_t *pamh;
    int sid;
    int session_mode;
    struct session *next;
};

static struct session *sessions = NULL;

static struct session *del_session(struct session **sp, int sid)
{

    struct session *tmp;

    if (*sp == NULL) return NULL;
    if ((*sp)->sid == sid) {
        tmp = *sp;
        *sp = tmp->next;
        return tmp;
    }
    tmp = (*sp)->next;
    while (tmp != NULL) {
        if (tmp->sid == sid) {
            (*sp)->next = tmp->next;
            return tmp;
        }
        sp = &((*sp)->next);
        tmp = tmp->next;
    }
    return NULL;
}


static void do_auth(char *service, char*user, char*pwd, char* mode, int sid)
{
    pam_handle_t *pamh=NULL;
    int retval;
    struct session *sessp;

    conv.appdata_ptr = (void*)strdup(pwd);
    retval = pam_start(service, user, &conv, &pamh);

    if (retval != PAM_SUCCESS) {
        werr(pamh, sid, retval, "start");
        return;
    }
    pam_set_item(pamh, PAM_RUSER, user);

    retval = pam_authenticate(pamh, 0);
    if (retval != PAM_SUCCESS) {
        werr(pamh, sid, retval, "auth");
        return;
    }
    if (mode[0] == 'A') {
        retval = pam_acct_mgmt(pamh, 0);
        if (retval != PAM_SUCCESS) {
            werr(pamh, sid, retval, "accounting");
            return;
        }
        /*fprintf(stderr, "did ok acct \n\r");*/
    }
    if (mode[1] == 'S') {
        retval = pam_open_session(pamh, 0);
        if (retval != PAM_SUCCESS) {
            werr(pamh, sid, retval, "session");
            return;
        }
        /*fprintf(stderr, "did ok open sess \n\r"); */
    }
    if ((sessp = malloc(sizeof(struct session))) == NULL) {
        werr(pamh, sid, -1, "malloc");
        return;
    }
    if (mode[1] == 'S')
        sessp->session_mode = 1;
    else
        sessp->session_mode = 0;
    sessp->sid = sid;
    sessp->pamh = pamh;
    sessp->next = sessions;
    sessions = sessp;

    wok(sid);
}


int main(int argc, char *argv[])
{
    unsigned char lb[2];
    unsigned char buf[BUFSIZ];
    char *user;
    char *pwd;
    char *mode;
    int sid;
    int rval;
    struct session *sessp;

    // test clause
    if (argc == 4 ) {
        /* ./epam authmodule user passwd */
        printf("testing service=%s u=%s pwd=%s\n", argv[1],argv[2], argv[3]);
        do_auth(argv[1], argv[2], argv[3], "AS", 33);
        exit(0);
    }
    wstart();
    while (1) {
        if (read_fill(0, lb, 2) != 2)
            exit(1);
        rval = get_int16(lb);
        if (read_fill(0, buf, rval) != rval)
            exit(1);
        switch (buf[0]) {
        case 'a':
            // auth a user
            user = (char *)&buf[1];
            pwd = user + strlen(user) + 1;
            mode= pwd + strlen(pwd) + 1;
            sid = atoi(mode + strlen(mode) + 1);

            do_auth(argv[1], user, pwd, mode, sid);
            break;
        case 'c':
            // close session
            sid = atoi((char *)&buf[1]);
            if ((sessp = del_session(&sessions, sid)) == NULL) {
                fprintf(stderr, "Couldn't find session %d\r\n", sid);
                break;
            }
            if (sessp->session_mode == 1) {
                pam_close_session(sessp->pamh, 0);
                /*fprintf(stderr, "did ok close sess \n\r");*/
            }
            pam_end(sessp->pamh, PAM_SUCCESS);
            free(sessp);
            break;
        default:
            fprintf(stderr, "Bad op \n\r");
        }
    }
}


