/*
 * 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);
    }
}

/*Answer the questions from modules; if a prompt is supplied, we
display it to the user and ask him to enter the value for the
item being asked about; otherwise we mark it the flag 
PNIAM_ITEM_ANSWER_UNAVAIL.*/
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);
        pniam_item_list_pop (prompts, item);
        if (item->data == NULL)
            item->flags |= PNIAM_ITEM_ANSWER_UNAVAIL;
        else {
            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;
            }
            if (!(item->flags & PNIAM_ITEM_STATIC_DATA))
                free (data); 
            item->data = data;
            item->len = len;
            item->flags = PNIAM_ITEM_DEFAULT;
        }
        pniam_item_list_push (answers, item);
        item = item_p;
    }
    return result;
}

static void _do_type (pniam_item_list_t **list)
{
    pniam_item_t *item;

    for (item = pniam_item_list_getfirst (*list); item != NULL;
        item = pniam_item_list_getnext (*list, item)) 
        write (STDOUT_FILENO, item->data, item->len);
    pniam_item_list_free (list);
}

/*This function displays the information about PNIAM success/failure
supplied by the modules.*/
void type_info (pniam_result_t res, pniam_request_t *request)
{
    if (res == PNIAM_OK) 
        _do_type (&(request->ok_msg));
    else
        _do_type (&(request->fail_msg));
}

/*Password modification for passwd.*/
pniam_result_t pas_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);
    }
    type_info (res, request);
    return res;
}

/*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);
    }
    type_info (res, 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);
    }
    type_info (res, request);
    return res;
}

/*Opening session*/
pniam_result_t open_session (struct pniam_handle *handle,
                          pniam_request_t *request, char *hostname, int flag)
{
    pniam_result_t res;
    pniam_item_t *item;

    item = pniam_item_list_find (request->ok_replies, "UID");
    if ((item == NULL) || (item->data == NULL))
        return PNIAM_SYSERR;
    res = pniam_item_add2list (&(request->input), "UID", item->data,
        item->len, PNIAM_ITEM_DEFAULT);
    if (res != PNIAM_OK)
        return PNIAM_SYSERR;
    res = pniam_item_add2list (&(request->input), "REMOTE_HOST", 
         (unsigned char *) hostname, 
         strlen (hostname), PNIAM_ITEM_DEFAULT);
    if (res != PNIAM_OK)
        return PNIAM_SYSERR;

    res = pniam_item_add2list (&(request->input), "WRITE_UTMP", 
         (unsigned char *) (&flag), 
         sizeof (flag), PNIAM_ITEM_DEFAULT);
    if (res != PNIAM_OK)
        return PNIAM_SYSERR;
    res = pniam_item_add2list (&(request->input), "WRITE_LASTLOG", 
         (unsigned char *) (&flag), 
         sizeof (flag), PNIAM_ITEM_DEFAULT);
    /*We do lastlog writing if and only if we do utmp writing.*/
    if (res != PNIAM_OK)
        return PNIAM_SYSERR;

    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);
    }
    type_info (res, 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);
    }
    type_info (res, request);
    return res;
}

/*Password modification for login*/
pniam_result_t change (struct pniam_handle *handle,
                            pniam_request_t *request)
{
    pniam_result_t res = PNIAM_SYSERR;
    pniam_item_t *item;

    for (item = pniam_item_list_getfirst (request->exp_tok);
        item != NULL;
        item = pniam_item_list_getnext (request->exp_tok, item))
    {
        res = pniam_item_add2list (&(request->input), "CHANGE_ITEM",
                item->data, item->len, PNIAM_ITEM_DEFAULT);
        if (res != PNIAM_OK)
            break; 
        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);
            if (res != PNIAM_OK)
                break;
        }
    }
    type_info (res, request);
    return res;
}

/*Put questions for authorization.*/
pniam_result_t prepare_author (pniam_item_list_t **list)
{
    pniam_result_t retval;

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