/*
 * $ld: $
 *
 * chfn.c
 *
 * Written for PNIAM by Alexander A. Naumov <aln@castle.nmd.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>

#define WRONG_USAGE  { usage (); return 1;}

static int parse_args (int, char **, char **, int*, char **, int*);
static int _authenticate (struct pniam_handle *, pniam_request_t *,
                          char *, int, int *);
static int _authorize (struct pniam_handle *, pniam_request_t *);
static int _change (struct pniam_handle *, pniam_request_t *,
                    char *, int, int *);
static int conv (pniam_request_t *);
static void show_pniam_messages (pniam_request_t *, pniam_result_t, char *);
static int readstr(const char *, int, unsigned char **, int *, int);
static void usage (void);



void main (int argc, char *argv[])
{
    pniam_request_t *request;
    struct pniam_handle *pniamh;
    pniam_result_t res;
    char *user, *gecos;
    int userlen, gecoslen, pniam_stage;
    int user_added, gecos_added;
    
    user = NULL;
    gecos = NULL;
    pniam_stage = 0;
    gecos_added = 0;
    user_added = 0;
    if (parse_args (argc, argv, &user, &userlen, &gecos, &gecoslen))
    {
        if (user) free (user);
        if (gecos) free (gecos);
        return;
    }

    do {
        res = pniam_start ("chfn", &pniamh);
        if (res != PNIAM_OK)
        {
            printf ("Starting pniam error\n");
            break;
        }
        pniam_stage = 1;
        res = pniam_create_request (pniamh, &request);
        if (res != PNIAM_OK)
        {
            printf ("Creating pniam_request error\n");
            break;
        }
        pniam_stage = 2;
        if (_authenticate (pniamh, request, user, userlen, &user_added))
            break;
        if (_authorize (pniamh, request))
            break;
        if (_change (pniamh, request, gecos, gecoslen, &gecos_added))
            break;
        printf ("Token was changed successufully\n");
    } while (0);
    if (pniam_stage > 1)
        pniam_destroy_request (pniamh, request, PNIAM_PARENT);
    if (pniam_stage > 0)
        pniam_end (pniamh);
    if ((user) && (!user_added))
        free (user);
    if ((gecos) && (!gecos_added))
        free (gecos);

    return;
}

static int parse_args (int argc, char *argv[], char **user, int *userlen,
                        char **gecos, int *gecoslen)
{
    int pos, user_flag, gecos_flag, len;
    char *string, *data;
    
    pos = 1;
    user_flag = 0;
    gecos_flag = 0;
    while (pos < argc)
    {
        string = argv[pos];
        if (*string != '-')
            WRONG_USAGE
        string ++;
        if (*string == 'u')
        {
            if ((user_flag) || (pos == argc - 1) || (*(string + 1) != '\0'))
                WRONG_USAGE
            len = strlen (argv[pos+1]);
            data = (char *) malloc (len);
            if (data == NULL)
            {
                printf ("System error.\n");
                return 2;
            }
            memcpy (data, argv[pos+1], len);
            *user = data;
            *userlen = len;
            user_flag = 1;
            pos += 2;
        }
        else if (*string == 'g')
        {
            if ((gecos_flag) || (pos == argc - 1) || (*(string + 1) != '\0'))
                WRONG_USAGE
            len = strlen (argv[pos+1]);
            data = (char *) malloc (len);
            if (data == NULL)
            {
                printf ("System error.\n");
                return 2;
            }
            memcpy (data, argv[pos+1], len);
            *gecos = data;
            *gecoslen = len;
            gecos_flag = 1;
            pos += 2;
        }
        else
            WRONG_USAGE
    }
    return 0;
}

static void usage (void)
{
    printf ("Usage : [-u username] [-g gecos-string]\n");
    return;
}

static int _authenticate (struct pniam_handle *pniamh, pniam_request_t *pniam_req,
                          char *username, int userlen, int *user)
{
    pniam_result_t res;
    int result;

    result = 1;
    do {
        res = pniam_item_add2list (&(pniam_req->input), "USER", (unsigned char*)
                                   username, userlen, PNIAM_ITEM_DEFAULT);
        if (res != PNIAM_OK)
        {
            printf ("Authentication failed due to system error\n");
            break;
        }
        *user = 1;
        res = pniam_item_add2list (&(pniam_req->input), "PLAIN_PASSWORD", NULL, 0,
                                   PNIAM_ITEM_DEFAULT);
        if (res != PNIAM_OK)
        {
            printf ("Authentication failed due to system error\n");
            break;
        }
        res = pniam_authenticate (pniamh, pniam_req);
        while (res == PNIAM_AGAIN)
        {
            if (conv (pniam_req))
                res = PNIAM_SYSERR;
            else 
                res = pniam_authenticate (pniamh, pniam_req);
        }
        show_pniam_messages (pniam_req, res, "Authentication failed\n");
        if ((res == PNIAM_OK) || (res == PNIAM_AUTHTOKERR))
            result = 0;
    } while (0);
    return result;
}

static int _authorize (struct pniam_handle *pniamh, pniam_request_t *pniam_req)
{
    pniam_result_t res;

    res = pniam_authorize (pniamh, pniam_req);
    while (res == PNIAM_AGAIN)
    {
        if (conv (pniam_req))
            res = PNIAM_SYSERR;
        else
            res = pniam_authorize (pniamh, pniam_req);
    }
    show_pniam_messages (pniam_req, res, "Authorization failed\n");
    if ((res == PNIAM_OK) || (res == PNIAM_AUTHTOKERR))
        return 0;
    return 1;
}

static int _change (struct pniam_handle *pniamh, pniam_request_t *pniam_req,
                    char *gecos, int geclen, int *gec)
{
    pniam_result_t res;

    res = pniam_item_add2list (&(pniam_req->input), "CHANGE_ITEM", (unsigned char *)
                               "GECOS", 5, PNIAM_ITEM_STATIC);
    if (res != PNIAM_OK)
    {
        printf ("System error\n");
        return 1;
    }
    res = pniam_item_add2list (&(pniam_req->input), "CHANGE_VALUE", (unsigned char*)
                               gecos, geclen, PNIAM_ITEM_DEFAULT);
    if (res != PNIAM_OK)
    {
        printf ("System error\n");
        return 1;
    }
    *gec = 1;
   
    res = pniam_change (pniamh, pniam_req);
    while (res == PNIAM_AGAIN)
    {
        if (conv (pniam_req))
            res = PNIAM_SYSERR;
        else
            res = pniam_change (pniamh, pniam_req);
    }
    show_pniam_messages (pniam_req, res, NULL);
    if (res == PNIAM_OK)
        return 0;
    return 1; 
}

static 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);
    }
}

static int conv (pniam_request_t *request)
{
    pniam_item_t *item, *item2;
    unsigned char *data;
    int datalen, echooff, result;

    result = 0;
    item = pniam_item_list_getfirst (request->prompts);
    pniam_item_list_pop (&(request->prompts), item);
    while (item != NULL)
    {
        if (item->data == NULL) 
        {
            item->flags |= PNIAM_ITEM_ANSWER_UNAVAIL;
            item->len = 0;
            pniam_item_list_push (&(request->input), item);
        }
        else
        {   
            echooff = 0;
            if (item->flags & PNIAM_ITEM_NOECHO_PROMPT)
                echooff = 1;
            if (readstr ((const char*) item->data, item->len,
                &data, &datalen, echooff))
            {
                result = 1;
                break;
            }
            item2 = pniam_item_list_find (request->input, item->name);
            if (item2 != NULL)
            {
                if (item2->data != NULL)
                {
                    result = 1;
                    free (data);
                    break;
                }
                item2->data = data;
                item2->len = datalen;
                item2->flags = PNIAM_ITEM_DEFAULT;
                item2 = item;
                item = pniam_item_list_getnext (request->prompts, item);
                pniam_item_list_delitem (&(request->prompts), item2);
            }
            else
            {
                item2 = pniam_item_list_getnext (request->prompts, item);
                pniam_item_list_pop (&(request->prompts), item);
                if (!(item->flags & PNIAM_ITEM_STATIC_DATA))
                    free (item->data);
                item->data = data;
                item->len = datalen;
                item->flags = PNIAM_ITEM_DEFAULT;
                pniam_item_list_push (&(request->input), item);
                item = item2;
            }     
        }
    }
    return result;
}

static void show_pniam_messages (pniam_request_t *request, pniam_result_t res, char *msg)
{
    pniam_item_t *item;
    pniam_item_list_t **list;

    if ((res == PNIAM_OK) || (res == PNIAM_AUTHTOKERR))
        list = &(request->ok_msg);
    else
        list = &(request->fail_msg);

    item = pniam_item_list_getfirst (*list);
    if ((item == NULL) && (msg != NULL) && (res != PNIAM_OK) && (res != PNIAM_AUTHTOKERR))
        printf (msg);
    while (item != NULL )
    {
        printf ("%.*s\n", item->len, item->data);
        item = pniam_item_list_getnext (*list, item);
    }
    pniam_item_list_free (list);
    return;
}
