/*
 * Written by Alexei V. Galatenko <agalat@castle.nmd.msu.ru>
 * and Andrey V. Savochkin <saw@msu.ru>
 */

#include <strings.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <malloc.h>

#include <pniam.h>

/* readstr: display the prompt, allocate and read an answer.
 *          Maximum answer length is unlimited.
 *          Input echo is controlled by `echooff' parameter.
 *          Return 0 on success and !=0 on fail.
 */
int readstr(const char *prompt, int promptlen, unsigned char **answer,
        int *len, int echooff)
{
    struct termios t1, t2;
    unsigned char *str, *p;
    int l, res, allocated;
        
    for (l = promptlen; l > 0; l -= res, prompt += res) {
        res = write(2, prompt, l);
        if (res < 0) return (1);
    }
    if (echooff) {
        if (tcgetattr(0, &t1) == -1) return (1);
        t2 = t1;
        t2.c_lflag &= ~ECHO;
        if (tcsetattr(0, TCSANOW, &t2) == -1) return (1);
     }
     /* read answer; negative `res' means fail, zero or positive - success */
     for (allocated = 0, l = 0, str = NULL;;) {
        if (l == allocated) {
            res = -1;
            allocated += 1024;
            p = realloc(str, allocated);
            if (p == NULL) break;
            str = p;
        }
        res = read(0, str + l, allocated - l);
        if (res <= 0) break;
        l += res;
        p = memchr(str + l - res, '\n', res);
        if (p != NULL) { l = p - str; if(echooff) printf ("\n"); break; }
    }
    if (echooff)
        if (tcsetattr(0, TCSANOW, &t1) == -1)
            res = -1;
                 
    if (res >= 0) {
        *answer = str;
        *len = l;
        return (0);
    } else {
        if (str != NULL) free(str);
            return (1);
    }
}

pniam_result_t item_add (pniam_item_list_t **list, const char *name,
unsigned char *data, int len, unsigned int flags)
{
    pniam_item_t *item;

    item = (pniam_item_t *) malloc (sizeof (pniam_item_t));
    if (item == NULL)
        return PNIAM_SYSERR;
    item->name = name;

    if ((data != NULL)&&(!(flags&PNIAM_ITEM_STATIC)))
    {
        item->data = malloc(len);
        if (item->data == NULL)
        {
            free (item);
            return (PNIAM_SYSERR);
        }
        memcpy(item->data, data, len);
    }
    else
        item->data = data;
    item->len = len;
    item->flags = flags;
    pniam_item_list_push (list, item);
    return PNIAM_OK;
}
pniam_result_t conv_pniam (pniam_item_list_t **prompts,
                        pniam_item_list_t **answers)
{
    pniam_item_t *item, *item_p;
    int len;
    unsigned char *data;
    int res, echoof;
    pniam_result_t result = PNIAM_OK;

    item = pniam_item_list_getfirst (*prompts);
    item_p = item;
    while (item != NULL)
    {
        item_p = pniam_item_list_getnext (*prompts, item_p);
	if (item->data == NULL)
            item->flags |= PNIAM_ITEM_ANSWER_UNAVAIL;
        else
        {
            pniam_item_list_pop (prompts, item);
            echoof = 0;
            if (item->flags & PNIAM_ITEM_NOECHO_PROMPT)
                echoof = 1;
            res = readstr ((const char *)item->data, item->len, &data, 
                           &len, echoof);
            if (res)
            {
                result = PNIAM_SYSERR;
                break;
            }
            item->data = data;
            item->len = len;
            pniam_item_list_push (answers, item);
        }
        item = item_p;
    }
    return result;
}

/*Performing authentication*/
pniam_result_t authen (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res;

    res = pniam_authenticate (handle, request);
    while (res == PNIAM_AGAIN)
    {
        res = conv_pniam (&(request->prompts), &(request->input));
        if (res != PNIAM_OK)
            break;
        res = pniam_authenticate (handle, request);
    }
    return res;
}
/*Performing authorization*/
pniam_result_t author (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res;

    res = pniam_authorize (handle, request);
    while (res == PNIAM_AGAIN)
    {
        res = conv_pniam (&(request->prompts), &(request->input));
        if (res != PNIAM_OK)
            break;
        res = pniam_authorize (handle, request);
    }
    return res;
}
/*Opening session*/
pniam_result_t open_session (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res;

    res = pniam_account_start (handle, request);
    while (res == PNIAM_AGAIN)
    {
        res = conv_pniam (&(request->prompts), &(request->input));
        if (res != PNIAM_OK)
            break;
        res = pniam_account_start (handle, request);
    }
    return res;
}
/*Closing sessoin*/
pniam_result_t close_session (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res;

    res = pniam_account_end (handle, request);
    while (res == PNIAM_AGAIN)
    {
        res = conv_pniam (&(request->prompts), &(request->input));
        if (res != PNIAM_OK)
            break;
        res = pniam_account_end (handle, request);
    }
    return res;
}
/*Change authentication token*/
pniam_result_t change (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res;

    res = pniam_change (handle, request);
    while (res == PNIAM_AGAIN)
    {
        res = conv_pniam (&(request->prompts), &(request->input));
        if (res != PNIAM_OK)
            break;
        res = pniam_change (handle, request);
    }
    return res;
}
pniam_result_t prepare_author (pniam_item_list_t **list)
{
    pniam_result_t retval;

    do
    {
        /*Adding items we want to ask for*/
        retval = item_add (list, "USER", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
        retval = item_add (list, "UID", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
        retval = item_add (list, "HOME", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
        retval = item_add (list, "SHELL", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
        retval = item_add (list, "GID", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
        retval = item_add (list, "SUP_GROUPS", NULL, 0, PNIAM_ITEM_DEFAULT);
        if (retval != PNIAM_OK)
            break;
    }while (0);
    return retval;
}
