#ifdef WIN32
#pragma warning(disable:4996)
#endif
#include "test_common.h"

CK_UTF8CHAR *initial_user_pin = (CK_UTF8CHAR *)"12345678";

void usage(char *progname)
{
   printf("usage:\t%s <command>\n",
           progname);
   printf("Parameters:\n");
   printf("\t -api <library path>           - PKCS#11 library path\n");
   printf("\t -old_so_pin <old SO PIN>      - current SO PIN\n");
   printf("\t -label <label>                - new token label\n");
   printf("\t -new_so_pin <new SO PIN>      - new SO PIN\n");
   printf("\t -new_user_pin <new user PIN>  - new USER PIN\n");
   printf("\t -h							- display this info\n");
   printf("Copyright(C) LISSI-Soft, Ltd (http://soft.lissi.ru) 2017\n");
}

// Данная утилита загружает и инициализирует библиотеку,
// находит первый попавшийся токен, инициализирует его, 
// изменяет SO PIN, инициализирует начальный USER PIN 12345678 
// и сразу изменяет его на заданный новый USER PIN.
// Если новый SO PIN задан некорректно (например, совпадает со старым 
// или слишком короткий), то остается действующим старый SO PIN.
// Если новый USER PIN задан некорректно (например, совпадает с начальным
// или слишком короткий), то остается действующим начальный USER PIN,
// который должен быть затем изменен по требованиям стандарта.
int main(int argc, char *argv[])
{
    CK_RV rc;

#ifndef MANUAL_INIT_TOKEN_TEST
    CK_FLAGS          flags = 0;
    CK_SESSION_HANDLE sess;
#endif
    int               i;
	char *api_path = NULL;
    CK_UTF8CHAR       *old_so_pin = NULL;
    CK_UTF8CHAR       *label = NULL;
    CK_UTF8CHAR       *so_pin = NULL;
    CK_UTF8CHAR       *user_pin = NULL;
    CK_UTF8CHAR       *tmp_pin = NULL;

    // parse the command line parameters
    for (i = 1; i < argc; i++)
    {
        if (strcmp("-api", argv[i]) == 0)
        {
            i++;
			api_path = malloc(MAX_PATH);
            strcpy(api_path, argv[i]);
        }
        else if (strcmp("-old_so_pin", argv[i]) == 0)
        {
            i++;
			old_so_pin = malloc(64);
            strcpy(old_so_pin, argv[i]);
        }
        else if (strcmp("-so_pin", argv[i]) == 0)
        {
            i++;
			so_pin = malloc(64);
            strcpy(so_pin, argv[i]);
        }
        else if (strcmp("-new_user_pin", argv[i]) == 0)
        {
            i++;
			user_pin = malloc(64);
            strcpy(user_pin, argv[i]);
        }
        else if (strcmp("-label", argv[i]) == 0)
        {
            i++;
			label = malloc(64);
            strcpy(label, argv[i]);
        }
		else if (strcmp("-h", argv[i]) == 0)
		{   
			usage(argv[0]);
		}
    }
	// Если аргументы не заданы, то они запрашиваются у пользователя.
	if (api_path == NULL){
		api_path = malloc(MAX_PATH);
		printf("Enter PKCS#11 library path:\n");
		fgets(api_path, MAX_PATH, stdin);
		// Символ новой строки остается в стандартном вводе?
	}
	printf("Using PKCS#11 library path %s\n", api_path); 

	if (old_so_pin == NULL){
		old_so_pin = malloc(64);
		printf("Enter old SO PIN:\n");
		get_pin(&tmp_pin);
		strcpy(old_so_pin, tmp_pin);
		free(tmp_pin);
	}

	if (so_pin == NULL){
		so_pin = malloc(64);
		printf("Enter new SO PIN:\n");
		get_pin(&tmp_pin);
		strcpy(so_pin, tmp_pin);
		free(tmp_pin);
	}

	if (label == NULL){
		label = malloc(64);
		printf("Enter new token label:\n");
		fgets(label, 64, stdin);
	}
	printf("New token label: %s\n", label);

	if (user_pin == NULL){
		user_pin = malloc(64);
		printf("Enter new USER PIN:\n");
		get_pin(&tmp_pin);
		strcpy(user_pin, tmp_pin);
		free(tmp_pin);
	}

	printf("init PKCS#11 ...\n");
    rc = init(api_path, &funcs, NULL);
    if (rc != CKR_OK)
    {
        show_error("init", rc);
        goto done;
    }
    printf("init OK\n");

    printf("get_slot_list ...\n");
    rc = get_slot_list(funcs,
                       &SlotList,
                       &SlotCount);
    if (rc != CKR_OK)
    {
        show_error("init", rc);
        goto done;
    }
    printf("get_slot_list OK\n");

    printf("find_slot_with_token ...\n");
    rc = find_slot_with_token(funcs,
                              SlotList,
                              SlotCount,
                              &SlotId);
    if (rc != CKR_OK)
    {
        show_error("find_slot_with_token", rc);
        goto done;
    }
	printf("find_slot_with_token OK: SlotId = 0x%lx\n", SlotId);

    display_token_info(funcs, SlotId);

    printf("C_InitToken ...\n");
    // Если токен не был инициализирован, то SO PIN не проверяется,
    // а впервые назначается, поэтому в любом случае после успешного
    // выполнения данной функции на токене будет установлен
    // заданный sopin, а USER PIN установлен не будет.
    rc = funcs->C_InitToken(SlotId,
		old_so_pin, (CK_ULONG)strlen((char *)old_so_pin), label);
    if (rc != CKR_OK)
    {
        show_error("C_InitToken", rc);
        goto done;
    }
    printf("C_InitToken OK\n");
    display_token_info(funcs, SlotId);

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;

    printf("C_OpenSession ...\n");
    rc = funcs->C_OpenSession(SlotId, flags, NULL, NULL, &sess);
    if (rc != CKR_OK)
    {
        show_error("C_OpenSession", rc);
        goto done;
    }
    printf("C_OpenSession OK\n");

    display_token_info(funcs, SlotId);

    printf("C_Login ...\n");
    rc = funcs->C_Login(sess, CKU_SO, 
		old_so_pin, (CK_ULONG)strlen((char *)old_so_pin));
    if (rc != CKR_OK)
    {
        show_error("C_Login", rc);
        goto done;
    }
    printf("C_Login (CKU_SO) OK\n");
	
    printf("C_SetPIN ...\n");
    rc = funcs->C_SetPIN(sess, old_so_pin, 
		(CK_ULONG)strlen((char *)old_so_pin),
        so_pin, (CK_ULONG)strlen((char *)so_pin));
    if (rc != CKR_OK)
    {
        show_error("C_SetPIN", rc);
		// Если вернулась ошибка, то остался действующим старый SO PIN.
		strcpy(so_pin, old_so_pin);
        printf("Old SO PIN is unchanged\n", rc);
	} else {
		printf("C_SetPIN (CKU_SO) OK\n");
	}

    printf("C_Logout ...\n");
    rc = funcs->C_Logout(sess);
    if (rc != CKR_OK)
    {
        show_error("C_Logout", rc);
        goto done;
    }
    printf("C_Logout (CKU_SO) OK\n");

    printf("C_Login ...\n");
    rc = funcs->C_Login(sess, CKU_SO,
                        so_pin, (CK_ULONG)strlen((char *)so_pin));
    if (rc != CKR_OK)
    {
        show_error("C_Login", rc);
        display_token_info(funcs, SlotId);
        goto done;
    }
    printf("C_Login (CKU_SO) OK\n");

    printf("C_InitPIN ...\n");
    rc = funcs->C_InitPIN(sess,
        initial_user_pin, (CK_ULONG)strlen((char *)initial_user_pin));
    if (rc != CKR_OK)
    {
        show_error("C_InitPIN", rc);
        goto done;
    }
    printf("C_InitPIN OK\n");

    printf("C_Logout ...\n");
    rc = funcs->C_Logout(sess);
    if (rc != CKR_OK)
    {
        show_error("C_Logout", rc);
        goto done;
    }
    printf("C_Logout (CKU_SO) OK\n");

    // По стандарту, логин здесь можно не выполнять -
    // в неавторизованной сессии C_SetPIN должен устанавливать USER PIN.
    // Но некоторые библиотеки могут отклоняться от стандарта,
    // поэтому на всякий случай залогинимся.
    printf("C_Login ...\n");
    rc = funcs->C_Login(sess, CKU_USER,
        initial_user_pin, (CK_ULONG)strlen((char *)initial_user_pin));
    if (rc != CKR_OK)
    {
        show_error("C_Login", rc);
        goto done;
    }
    printf("C_Login (CKU_USER) OK\n");

    printf("C_SetPIN ...\n");
    rc = funcs->C_SetPIN(sess,
        initial_user_pin, (CK_ULONG)strlen((char *)initial_user_pin),
        user_pin, (CK_ULONG)strlen((char *)user_pin));
    if (rc != CKR_OK)
    {
        show_error("C_SetPIN", rc);
        goto done;
    }
    printf("C_SetPIN (CKU_USER) OK\n");

    printf("C_Logout ...\n");
    rc = funcs->C_Logout(sess);
    if (rc != CKR_OK)
    {
        show_error("C_Logout", rc);
        goto done;
    }
    printf("C_Logout (CKU_USER) OK\n");

    printf("C_CloseSession ...\n");
    rc = funcs->C_CloseSession(sess);
    if (rc != CKR_OK)
    {
        show_error("C_CloseSession", rc);
        goto done;
    }
    printf("C_CloseSession OK\n");

    display_token_info(funcs, SlotId);

    printf("init_token OK\n");
	rc = CKR_OK;
done:
	cleanup(funcs);
	if (rc != CKR_OK){
        show_error("init_token", rc);
	}
	if (api_path) free(api_path);
	if (old_so_pin) free(old_so_pin);
	if (so_pin) free(so_pin);
	if (user_pin) free(user_pin);
	if (label) free(label);
    return rc;
}

