/*
* Copyright(C) 2008 Проект ИОК
*
* Этот файл содержит информацию, являющуюся
* собственностью компании Крипто Про.
*
* Любая часть этого файла не может быть скопирована,
* исправлена, переведена на другие языки,
* локализована или модифицирована любым способом,
* откомпилирована, передана по сети с или на
* любую компьютерную систему без предварительного
* заключения соглашения с компанией Крипто Про.
*/

#include "rdr_prj.h"

/* путь к паролю */
static const TCHAR RDR_DEFAULT_PASSWD_PATH_GLOBAL[] = _TEXT("\\global\\KeyDevices\\passwords");
static const TCHAR RDR_DEFAULT_PASSWD_PATH_LOCAL[] = _TEXT("\\local\\KeyDevices\\passwords");

static const TCHAR RDR_DEFAULT_PASSWD_PARAM[] = _TEXT("passwd");

DWORD rdr_password_type(const TSupSysEContext *context, TRdrLoginInfoType auth_type, TRdrLoginInfoType * auth_alg)
{
    int type = 0;
    int auth = 0;
    TRdrLoginInfoType send = auth_type;
    SUPSYS_PRE(context);
    SUPSYS_PRE_WRITE_PTR(auth_alg);
    const DWORD code = supsys_call(context, READER_FUN_PASSWORD_TYPE, &send);
    type = send & AUTH_TYPE_TYPE_MASK;
    SUPSYS_PRE(type == AUTH_TYPE_TYPE_CONT
	|| type == AUTH_TYPE_TYPE_USER_PIN1
	|| type == AUTH_TYPE_TYPE_USER_PIN2
	|| type == AUTH_TYPE_TYPE_PUK
	|| type == AUTH_TYPE_TYPE_ROOT);

    auth = send & AUTH_TYPE_AUTH_MASK;
    SUPSYS_PRE(auth == AUTH_TYPE_AUTH_DH
	|| auth == AUTH_TYPE_AUTH_NO
	|| auth == AUTH_TYPE_AUTH_SELF
	|| auth == AUTH_TYPE_AUTH_SESPAKE
	|| auth == AUTH_TYPE_AUTH_SIMPLE);
    if (code == ERROR_SUCCESS)
	*auth_alg = send;
    LOGRETURN(code);
}

DWORD rdr_clear_tries(TSupSysEContext *context, TRdrLoginInfoType auth_type)
{
    DWORD code = 0;
    SUPSYS_PRE(context);
    code = supsys_call(context, READER_FUN_CLEAR_TRIES, &auth_type);
    if (code)
	LOGRETURN(code);
    LOGRETURN(ERROR_SUCCESS);
}

DWORD rdr_passwd_phrase(TSupSysEContext *context, TRdrLoginInfoType auth_type, size_t *length, TCHAR *phrase)
{
    TReaderInfoPasswdText info_passwd_ptr = { 0 };
    DWORD code = ERROR_SUCCESS;
    SUPSYS_PRE_WRITE_PTR(length);
    if (phrase)
    {
	info_passwd_ptr.text.length = *length;
	info_passwd_ptr.text.text = phrase;
    }
    info_passwd_ptr.type = auth_type;

    if (context) {
	code = supsys_call(context, READER_FUN_PASSWD_PHRASE, &info_passwd_ptr);
	if (info_passwd_ptr.text.text)
	    SUPSYS_PRE(_tcslen(info_passwd_ptr.text.text) == info_passwd_ptr.text.length);
    }
    else {
	code = (DWORD)ERROR_NOT_SUPPORTED;
    }
    if(code)
	LOGRETURN(code);
    if (phrase == NULL)
    {
	*length = info_passwd_ptr.text.length;
	LOGRETURN(ERROR_SUCCESS);
    }
    *length = info_passwd_ptr.text.length;
    phrase[*length] = (TCHAR)'\0';
    LOGRETURN(ERROR_SUCCESS);
}

/* удалять не надо -- будет использоваться. Так сказал borodin 14-июля-2015 */
DWORD rdr_passwd_term(const TSupSysEContext *context, TRdrLoginInfoType auth_type, size_t *length, TCHAR *term)
{
    TReaderInfoPasswdText info_passwd_ptr = { 0 };
    DWORD code = ERROR_SUCCESS;
    SUPSYS_PRE(context);
    SUPSYS_PRE_WRITE_PTR(length);
    if (term)
    {
	info_passwd_ptr.text.length = *length;
	info_passwd_ptr.text.text = term;
    }
    info_passwd_ptr.type = auth_type;

    if (context == NULL)
	LOGRETURN((DWORD)ERROR_NOT_SUPPORTED);
    code = supsys_call(context, READER_FUN_PASSWD_TERM, &info_passwd_ptr);
    if (info_passwd_ptr.text.text) {
	SUPSYS_PRE(_tcslen(info_passwd_ptr.text.text) == info_passwd_ptr.text.length);
    }
    if (code) 
	LOGRETURN(code);

    if (term == NULL)
    {
	*length = info_passwd_ptr.text.length;
	LOGRETURN(ERROR_SUCCESS);
    }
    term[*length] = (TCHAR)'\0';
    *length = info_passwd_ptr.text.length;
    LOGRETURN(code);
}

DWORD rdr_auth_change(TSupSysEContext *context, TRdrLoginInfoType auth_type, const char *new_passwd, TRdrLoginInfoType set_auth_type, const char *set_passwd)
{
    DWORD code = ERROR_SUCCESS;
    TReaderInfoPasswdChangeAscii info_change_ascii;
    SUPSYS_PRE(context);

    if (new_passwd)
    {
	if (strlen(new_passwd) > CRYPT_MAX_PIN_LENGTH)
	    LOGRETURN((DWORD)SCARD_E_INVALID_CHV);
	info_change_ascii.passwd.length = strlen(new_passwd);
	info_change_ascii.passwd.text = (void*)new_passwd;
    }
    else {
	info_change_ascii.passwd.length = 0;
	info_change_ascii.passwd.text = NULL;
    }
    info_change_ascii.auth_type = auth_type;
    info_change_ascii.set_auth_type = set_auth_type;
    if (set_passwd)
    {
	if (strlen(set_passwd) > CRYPT_MAX_PIN_LENGTH)
	    LOGRETURN((DWORD)SCARD_E_INVALID_CHV);
	info_change_ascii.set_passwd.length = strlen(set_passwd);
	info_change_ascii.set_passwd.text = (void*)set_passwd;
    }
    else {
	info_change_ascii.set_passwd.length = 0;
	info_change_ascii.set_passwd.text = NULL;
    }
    code = supsys_call(context, READER_FUN_PASSWD_CHANGE_ASCII, &info_change_ascii);
    if (code == (DWORD)ERROR_NOT_SUPPORTED || code == (DWORD)NTE_NOT_SUPPORTED) {
	TReaderInfoPasswdChange info_change;
	TCHAR tnew_passwd[CRYPT_MAX_PIN_LENGTH + 1] = { 0 };
	TCHAR tset_passwd[CRYPT_MAX_PIN_LENGTH + 1] = { 0 };

        /* В функцию могут прийти как CP_ACP, так и UTF8 пароли. Но последние приходят только тогда,
           когда зажжен признак utf8 в свойствах аутентификации, а это означает, что используется ASCII
           вариант функции смены, и в преобразование ниже мы никогда не попадаем. */
	if (new_passwd)
	{
	    _ascii2cpy(tnew_passwd, new_passwd);
	    info_change.passwd.length = strlen(new_passwd);
	    info_change.passwd.text = (void*)tnew_passwd;
	}
	else {
	    info_change.passwd.length = 0;
	    info_change.passwd.text = NULL;
	}
	info_change.auth_type = auth_type;
	info_change.set_auth_type = set_auth_type;
	if (set_passwd)
	{
	    _ascii2cpy(tset_passwd, set_passwd);
	    info_change.set_passwd.length = strlen(set_passwd);
	    info_change.set_passwd.text = (void*)tset_passwd;
	}
	else {
	    info_change.set_passwd.length = 0;
	    info_change.set_passwd.text = NULL;
	}
	code = supsys_call(context, READER_FUN_PASSWD_CHANGE_TEXT, &info_change);
	if (new_passwd) {
	    support_zero_memory(tnew_passwd, (CRYPT_MAX_PIN_LENGTH * sizeof(TCHAR)));
	}
	if (set_passwd) {
	    support_zero_memory(tset_passwd, (CRYPT_MAX_PIN_LENGTH * sizeof(TCHAR)));
	}
    }
    LOGRETURN(code);
}

static DWORD rdr_passwd_default_malloc(int local_machine, const TCHAR *media_type, const TCHAR *unique, const TCHAR *folder, TCHAR ** out_key)
{
    TCHAR *path;
    const TCHAR *base;
    size_t length;
    if (local_machine)
	base = RDR_DEFAULT_PASSWD_PATH_GLOBAL;
    else
	base = RDR_DEFAULT_PASSWD_PATH_LOCAL;
    length = _tcslen(base);
    if (media_type)
	length += 1 + _tcslen(media_type);
    if (unique)
	length += 1 + _tcslen(unique);
    if (folder)
	length += 1 + _tcslen(folder);
    length += 1 + _tcslen(RDR_DEFAULT_PASSWD_PARAM);
	

    path = malloc((length + 1) * sizeof(TCHAR));
    if (path == NULL)
	LOGRETURN((DWORD)NTE_NO_MEMORY);
    _tcscpy(path, base);
    if (media_type)
    {
	_tcscat(path, _TEXT("\\"));
	_tcscat(path, media_type);
    }
    if (unique && *unique)
    {
	_tcscat(path, _TEXT("\\"));
	_tcscat(path, unique);
    }
    if (folder && *folder)
    {
	_tcscat(path, _TEXT("\\"));
	_tcscat(path, folder);
    }
    *out_key = path;
    LOGRETURN(ERROR_SUCCESS);
}


DWORD rdr_passwd_default_set(const TSupSysEContext *context, BOOL is_local_machine, TCHAR * carrier_type, TCHAR * unique, TCHAR * folder, const TCHAR *passwd)
{
    TCHAR *path = NULL;
    DWORD code;
    UNUSED(context);

    if (passwd)
	SUPSYS_PRE_STRING_PTR(passwd);

    code = rdr_passwd_default_malloc(is_local_machine, carrier_type, unique, folder, &path);
    if (code)
	LOGRETURN(code);

    _tcscat(path, _TEXT("\\"));
    _tcscat(path, RDR_DEFAULT_PASSWD_PARAM);

    support_registry_delete_param(path, NULL);
#ifdef UNIX 
    if (passwd)
	code = support_registry_put_string(path, passwd);
    else
	code = support_registry_put_string(path, _TEXT(""));
#else
    {
	DWORD dwLen = 0;
	DATA_BLOB DataIn = { 0 };
	DATA_BLOB DataOut = { 0 };
	if (passwd)
	{
	    dwLen = (DWORD)((_tcslen(passwd) + 1)*sizeof(TCHAR));
	    DataIn.pbData = (BYTE *)passwd;
	    DataIn.cbData = dwLen;
	    if (CryptProtectData(&DataIn, L"CryptoPro CSP 3.6", NULL, NULL, NULL, is_local_machine ? CRYPTPROTECT_LOCAL_MACHINE : 0, &DataOut))
	    {
		code = support_registry_put_hex(path, (size_t)DataOut.cbData, DataOut.pbData);
		LocalFree(DataOut.pbData);
	    }
	    else
	    {
		DbTrace(DB_WARN, (FTEXT(db_ctx, "CryptProtectData failed: LastError = 0x%X"), GetLastError()));
		code = support_registry_put_string(path, passwd);
	    }
	}
	else
	    code = support_registry_put_hex(path, 0, NULL);
    }
#endif

    free(path);
    if (code)
	LOGRETURN(code);
    LOGRETURN(ERROR_SUCCESS);
}



DWORD rdr_passwd_default_get(const TSupSysEContext *context, BOOL is_local_machine, TCHAR * carrier_type, TCHAR * unique, TCHAR * folder, size_t *passwd_length, TCHAR *passwd)
{
    TCHAR *path = NULL;
    DWORD code;
    SUPSYS_PRE_WRITE_PTR(passwd_length);
    UNUSED(context);


    code = rdr_passwd_default_malloc(is_local_machine, carrier_type, unique, folder, &path);
    if (code)
	LOGRETURN(code);

    _tcscat(path, _TEXT("\\"));
    _tcscat(path, RDR_DEFAULT_PASSWD_PARAM);
#ifndef UNIX
    {
	DATA_BLOB DataIn = { 0 };
	DATA_BLOB DataOut = { 0 };
	size_t data_in_len = 0;
	code = support_registry_get_hex(path, &data_in_len, NULL);
	if (!code)
	{
	    if (data_in_len)
	    {
		DataIn.pbData = malloc(data_in_len);
		if (DataIn.pbData == NULL)
		    LOGRETURN((DWORD)NTE_NO_MEMORY);
		code = support_registry_get_hex(path, &data_in_len, DataIn.pbData);
		if (!code)
		{
		    DataIn.cbData = (DWORD)data_in_len;
		    if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, is_local_machine ? CRYPTPROTECT_LOCAL_MACHINE : 0, &DataOut))
		    {
			if (DataOut.cbData)
			    *passwd_length = (size_t)DataOut.cbData / sizeof(TCHAR)-1;
			if (passwd)
			    memcpy(passwd, DataOut.pbData, DataOut.cbData);
			LocalFree(DataOut.pbData);
		    }
		    else
			code = GetLastError();
		}
		else
		    code = GetLastError();
		free(DataIn.pbData);
	    }
	    else
	    {
		*passwd_length = 0;
	    }
	}
	else if (code == (DWORD)ERROR_FILE_INVALID)
	{
#endif
	    code = support_registry_get_string(path, passwd_length, passwd);
#ifndef UNIX
	}
    }
#endif
    free(path);
    LOGRETURN(code);
}

DWORD rdr_passwd_default_clear_all(int local_machine)
{
    DWORD code;
    code = support_registry_delete_section(local_machine ? RDR_DEFAULT_PASSWD_PATH_GLOBAL : RDR_DEFAULT_PASSWD_PATH_LOCAL, NULL);
    if (code != (DWORD)ERROR_FILE_NOT_FOUND) {
	LOGRETURN(code);
    }
    LOGRETURN(ERROR_SUCCESS);
}

DWORD rdr_passwd_default_clear(const TSupSysEContext *context, BOOL is_local_machine, TCHAR * carrier_type, TCHAR * unique, TCHAR * folder)
{
    TCHAR *path;
    DWORD code;
    UNUSED(context);

    code = rdr_passwd_default_malloc(is_local_machine, carrier_type, unique, folder, &path);
    if (code)
	LOGRETURN(code);
    code = support_registry_delete_section(path, NULL);
    free(path);
    if (code != (DWORD)ERROR_FILE_NOT_FOUND)
	LOGRETURN(code);
    LOGRETURN(ERROR_SUCCESS);
}

DWORD rdr_login_ex(TSupSysEContext *context, TRdrLoginInfoType auth_type, const char *passwd, int *retries)
{
    TReaderInfoLoginAscii info_struct_ascii;
    DWORD code;
    SUPSYS_PRE(context);
    SUPSYS_PRE_WRITE_PTR(retries);
    if (passwd)
	SUPSYS_PRE_STRING_PTR(passwd);
   
    info_struct_ascii.auth_type = auth_type;
    info_struct_ascii.passwd.text = NULL;
    info_struct_ascii.passwd.length = 0;
    info_struct_ascii.retries = *retries;
    if (passwd)
    {
	info_struct_ascii.passwd.length = strlen(passwd);
	info_struct_ascii.passwd.text = (void*)passwd;
    }
    code = supsys_call(context, READER_FUN_SIMPLE_LOGIN_ASCII, &info_struct_ascii);
    if (code == (DWORD)ERROR_NOT_SUPPORTED || code == (DWORD)NTE_NOT_SUPPORTED) {
	TReaderInfoLogin info_struct;
	TCHAR pass[CRYPT_MAX_PIN_LENGTH + 1] = {0};

	info_struct.auth_type = auth_type;
	info_struct.passwd.text = NULL;
	info_struct.passwd.length = 0;
	info_struct.retries = *retries;
	if (passwd)
	{
	    if (strlen(passwd) > CRYPT_MAX_PIN_LENGTH)
		LOGRETURN((DWORD)SCARD_E_INVALID_CHV);
            /* В функцию могут прийти как CP_ACP, так и UTF8 пароли. Но последние приходят только тогда, 
               когда зажжен признак utf8 в свойствах аутентификации, а это означает, что используется ASCII
               вариант функции предъявления, и в преобразование ниже мы никогда не попадаем. */
	    _ascii2cpy(pass, passwd);
	    info_struct.passwd.length = _tcslen(pass);
	    info_struct.passwd.text = (void*)pass;
	}
	code = supsys_call(context, READER_FUN_SIMPLE_LOGIN_TEXT, &info_struct);
	support_zero_memory(pass, (CRYPT_MAX_PIN_LENGTH * sizeof(TCHAR)));
	*retries = info_struct.retries;
	LOGRETURN(code);
    }
    *retries = info_struct_ascii.retries;
    LOGRETURN(code);
}

 DWORD rdr_restore_default_login(TSupSysEContext *context)
{
    SUPSYS_PRE(context);   
    LOGRETURN(supsys_call(context, READER_FUN_RESTORE_DEFAULT_ROOT, NULL));
}


DWORD rdr_self_login_ex(TSupSysEContext *context, TRdrLoginInfoType auth_type)
{
    TRdrLoginInfoType it = auth_type;
    SUPSYS_PRE(context);
    LOGRETURN(supsys_call(context, READER_FUN_SELF_LOGIN, &it));
}

DWORD rdr_self_change(TSupSysEContext *context, TRdrLoginInfoType auth_type)
{
    TRdrLoginInfoType it = auth_type;
    SUPSYS_PRE(context);
    LOGRETURN(supsys_call(context, READER_FUN_SELF_CHANGE, &it));
}

DWORD rdr_logout(TSupSysEContext *context)
{
    LOGRETURN(supsys_call(context, READER_FUN_LOGOUT, NULL));
}
