Files correlati : Ricompilazione Demo : [ ] Commento : Riportata la versione 3.1 patch 766 git-svn-id: svn://10.65.10.50/trunk@14628 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			513 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			513 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #define __SMARTCARD_CPP
 | |
| 
 | |
| #ifdef WIN32
 | |
| #include <windows.h>
 | |
| #endif
 | |
| 
 | |
| #include <config.h>
 | |
| #include <diction.h>
 | |
| #include <recarray.h>
 | |
| #include <smartcard.h>
 | |
| #include <tabutil.h>
 | |
| #include <utility.h>
 | |
| 
 | |
| 
 | |
| #ifdef WIN32
 | |
| #include <ase/include/ASEMemCard.h>
 | |
| 
 | |
| typedef unsigned long (ASEMEM_API FASEListReaders)(char * readersList, unsigned long * length);
 | |
| typedef unsigned long (ASEMEM_API FASEWaitForCardEvent)(char * reader, unsigned char inOut, unsigned long timeOut);
 | |
| typedef unsigned long (ASEMEM_API FASEMemConnect)(ASEMEM_HANDLE * hCard, char * reader, unsigned long connectMode, unsigned long * cardType);
 | |
| typedef unsigned long (ASEMEM_API FASEMemDisconnect)(ASEMEM_HANDLE hCard, char mode);
 | |
| typedef unsigned long (ASEMEM_API FASEMemWrite)(ASEMEM_HANDLE hCard, unsigned long address, unsigned char * writeBuffer, unsigned long lenWrite, char protect);
 | |
| typedef unsigned long (ASEMEM_API FASEMemRead)(ASEMEM_HANDLE hCard, unsigned long address, unsigned char * readBuffer, unsigned char * protReadBuffer, unsigned long lenRead);
 | |
| typedef unsigned long (ASEMEM_API FASEMemVerifyPassword)(ASEMEM_HANDLE hCard, unsigned char * password, unsigned long length, int * attempts);
 | |
| typedef unsigned long (ASEMEM_API FASEMemChangePassword)(ASEMEM_HANDLE hCard,unsigned char * oldPassword, unsigned char * newPassword, unsigned long length, int * attempts);
 | |
| 
 | |
| typedef FASEListReaders * LPASEListReaders;
 | |
| typedef FASEWaitForCardEvent * LPASEWaitForCardEvent;
 | |
| typedef FASEMemConnect * LPASEMemConnect;
 | |
| typedef FASEMemDisconnect * LPASEMemDisconnect;
 | |
| typedef FASEMemWrite * LPASEMemWrite;
 | |
| typedef FASEMemRead * LPASEMemRead;
 | |
| typedef FASEMemVerifyPassword * LPASEMemVerifyPassword;
 | |
| typedef FASEMemChangePassword * LPASEMemChangePassword;
 | |
| 
 | |
| 
 | |
| HINSTANCE _hlibASE = NULL;
 | |
| 
 | |
| LPASEListReaders lpfnASEListReaders = NULL;
 | |
| LPASEWaitForCardEvent lpfnASEWaitForCardEvent = NULL;
 | |
| LPASEMemConnect lpfnASEMemConnect = NULL;
 | |
| LPASEMemDisconnect lpfnASEMemDisconnect = NULL;
 | |
| LPASEMemWrite lpfnASEMemWrite = NULL;
 | |
| LPASEMemRead lpfnASEMemRead = NULL;
 | |
| LPASEMemVerifyPassword lpfnASEMemVerifyPassword = NULL;
 | |
| LPASEMemChangePassword lpfnASEMemChangePassword = NULL;
 | |
| 
 | |
| #endif
 | |
| 
 | |
| bool TSmart_card::ASEloadlibrary()
 | |
| {            
 | |
| #ifdef WIN32
 | |
|   if (_hlibASE)    // Check in already loaded
 | |
|     return TRUE;
 | |
| 
 | |
|   UINT fuError = SetErrorMode(SEM_NOOPENFILEERRORBOX);
 | |
|   _hlibASE = LoadLibrary("ASEMemory.dll");
 | |
|   SetErrorMode(fuError);
 | |
| 
 | |
|   if (_hlibASE == NULL)
 | |
|     return error_box(TR("ASEMemory.dll mancante"));
 | |
| 
 | |
|   if (_hlibASE < (HINSTANCE)HINSTANCE_ERROR)
 | |
|   {
 | |
|     DWORD err = *((DWORD*)_hlibASE) & 0xFFFF;
 | |
|     _hlibASE = NULL;
 | |
|     return error_box(FR("Errore di caricamento di ASEMemory.dll: %lu"), err);
 | |
|   }  
 | |
| 
 | |
|   if (!(lpfnASEListReaders       = (LPASEListReaders)GetProcAddress (_hlibASE, "ASEListReaders"))) return FALSE;
 | |
|   if (!(lpfnASEWaitForCardEvent  = (LPASEWaitForCardEvent)GetProcAddress (_hlibASE,  "ASEWaitForCardEvent"))) return FALSE;
 | |
|   if (!(lpfnASEMemConnect        = (LPASEMemConnect)GetProcAddress (_hlibASE,  "ASEMemConnect"))) return FALSE;
 | |
|   if (!(lpfnASEMemDisconnect     = (LPASEMemDisconnect)GetProcAddress (_hlibASE,   "ASEMemDisconnect"))) return FALSE;
 | |
|   if (!(lpfnASEMemWrite          = (LPASEMemWrite)GetProcAddress (_hlibASE,  "ASEMemWrite"))) return FALSE;
 | |
|   if (!(lpfnASEMemRead           = (LPASEMemRead)GetProcAddress (_hlibASE,  "ASEMemRead"))) return FALSE;
 | |
|   if (!(lpfnASEMemVerifyPassword = (LPASEMemVerifyPassword)GetProcAddress (_hlibASE,  "ASEMemVerifyPassword"))) return FALSE;
 | |
|   if (!(lpfnASEMemChangePassword = (LPASEMemChangePassword)GetProcAddress (_hlibASE,  "ASEMemChangePassword"))) return FALSE;
 | |
| 
 | |
| #endif
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| TSmart_card::TSmart_card()
 | |
| {
 | |
|   init();
 | |
| }
 | |
| 
 | |
| unsigned long TSmart_card::ASEsearch()
 | |
| {
 | |
| 	_ASE_reader_name.cut(0);
 | |
| 
 | |
| 	unsigned long len;
 | |
|   unsigned long status = lpfnASEListReaders(NULL, &len);
 | |
| 
 | |
| 	if (status != ASEMEM_S_SUCCESS)
 | |
| 		return status;
 | |
| 
 | |
|   char * readerList = new(char[len]);
 | |
| 
 | |
|   if (readerList == NULL)
 | |
| 		return ASEMEM_E_NO_MEMORY;
 | |
| 
 | |
|   status = lpfnASEListReaders(readerList, &len);
 | |
| 
 | |
|   if (status == ASEMEM_S_SUCCESS)
 | |
| 	{
 | |
| 		_ASE_reader_name = readerList;
 | |
| 		if (_ASE_reader_name.empty())
 | |
| 			status = ASEMEM_E_NO_READERS_AVAILABLE;
 | |
|   }
 | |
| 	_has_pwd = false;
 | |
| 	delete readerList;
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| unsigned long TSmart_card::ASEread(char ** buffer)
 | |
| {
 | |
| 	ASEMEM_HANDLE hcard;
 | |
| 	unsigned long type;
 | |
| 	unsigned long status = lpfnASEMemConnect(&hcard, (char *)(const char *)_ASE_reader_name, ASEMEM_SHARE_SHARED, &type);
 | |
|   unsigned char stdpassword[3] = {0xFF, 0xFF, 0xFF};
 | |
| 
 | |
| 	if (status != ASEMEM_S_SUCCESS)
 | |
| 		return status;
 | |
| 	_has_pwd =  type == ASEMEM_CARD_TYPE_2BUS || type != ASEMEM_CARD_TYPE_3BUS;
 | |
| 	if (_has_pwd)
 | |
| 	{
 | |
|     unsigned long passwordLength = (type == ASEMEM_CARD_TYPE_2BUS) ? ASEMEM_2BUS_PASSWORD_LENGTH : ASEMEM_3BUS_PASSWORD_LENGTH;
 | |
|     int attempts = 0;
 | |
| 
 | |
|     status = lpfnASEMemVerifyPassword(hcard, stdpassword, passwordLength, &attempts);
 | |
| 
 | |
|     if (status == ASEMEM_E_PASSWORD_VERIFICATION_FAILED)
 | |
| 		{
 | |
| 			lpfnASEMemDisconnect(&hcard, ASEMEM_RESET_CARD);
 | |
| 			return status;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	
 | |
| 	unsigned char len;
 | |
| 	const int offset = _has_pwd ? 32 : 0;
 | |
| 
 | |
| 	status = lpfnASEMemRead(hcard, offset, &len, NULL, 1); 
 | |
| 	if (status == ASEMEM_S_SUCCESS && len == 1)
 | |
| 	{
 | |
| 		status = lpfnASEMemRead(hcard, offset+1, &len, NULL, 1); 
 | |
| 		if (status == ASEMEM_S_SUCCESS)
 | |
| 		{
 | |
| 			if (*buffer == NULL)
 | |
| 				*buffer = new(char[len+1]);
 | |
| 			status = lpfnASEMemRead(hcard, offset+2, (unsigned char *) *buffer, NULL, (int) len); 
 | |
| 			if (status == ASEMEM_S_SUCCESS)
 | |
| 				(*buffer)[len] = '\0';
 | |
| 			else
 | |
| 				(*buffer)[0] = '\0';
 | |
| 		}
 | |
| 	}
 | |
| 	else
 | |
| 		ASEwrite("0");
 | |
| 	lpfnASEMemDisconnect(&hcard, ASEMEM_LEAVE_CARD);
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| unsigned long TSmart_card::ASEwrite(char * buffer) const
 | |
| {
 | |
| 	ASEMEM_HANDLE hcard;
 | |
| 	unsigned long type;
 | |
| 	unsigned long status = lpfnASEMemConnect(&hcard, (char *)(const char *)_ASE_reader_name, ASEMEM_SHARE_SHARED, &type);
 | |
| 	if (status != ASEMEM_S_SUCCESS)
 | |
| 		return status;
 | |
| 
 | |
|  	unsigned char len = 1;
 | |
| 	const int offset = _has_pwd ? 32 : 0;
 | |
| 	
 | |
| 	status = lpfnASEMemWrite(hcard, offset, &len, 1, ASEMEM_WRITE_MODE);
 | |
|   if (status == ASEMEM_S_SUCCESS)
 | |
| 	{
 | |
| 		len = strlen(buffer);
 | |
| 	  status = lpfnASEMemWrite(hcard, offset+1, &len, 1, ASEMEM_WRITE_MODE);
 | |
| 		if (status == ASEMEM_S_SUCCESS)
 | |
| 			status = lpfnASEMemWrite(hcard, offset+2, (unsigned char *)buffer, (int) len, ASEMEM_WRITE_MODE);
 | |
| 	}
 | |
| 	lpfnASEMemDisconnect(&hcard, ASEMEM_LEAVE_CARD);
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| static smartcard_error ASEmap_error(unsigned long error)
 | |
| {
 | |
| 	switch (error)
 | |
| 	{
 | |
| 		case ASEMEM_S_SUCCESS:
 | |
| 			return no_smarterror;
 | |
| 		case ASEMEM_E_FUNCTION_NOT_SUPPORTED:
 | |
| 			return wrong_function;
 | |
| 		case ASEMEM_E_REMOVED_CARD:
 | |
| 			return card_removed;
 | |
| 		case ASEMEM_E_UNPOWERED_CARD:
 | |
| 			return no_power;
 | |
| 		case ASEMEM_E_UNSUPPORTED_CARD:
 | |
| 			return wrong_card;
 | |
| 		case ASEMEM_E_PASSWORD_VERIFICATION_FAILED:
 | |
| 			return wrong_password;
 | |
| 		case ASEMEM_E_PASSWORD_BLOCKED:
 | |
| 			return password_blocked;
 | |
| 		case ASEMEM_E_INVALID_HANDLE:
 | |
| 			return invelid_handle;
 | |
| 		case ASEMEM_E_INVALID_PARAMETER:
 | |
| 			return invalid_parameter;
 | |
| 		case ASEMEM_E_NO_SMARTCARD:
 | |
| 			return no_card;
 | |
| 		case ASEMEM_E_RESET_CARD:
 | |
| 			return reset_card;
 | |
| 		case ASEMEM_E_SHARING_VIOLATION:
 | |
| 			return protection_error;
 | |
| 		case ASEMEM_E_UNKNOWN_READER:
 | |
| 			return unknown_reader;
 | |
| 		case ASEMEM_E_NO_READERS_AVAILABLE:
 | |
| 			return no_reader;
 | |
| 		case ASEMEM_E_INSUFFICIENT_BUFFER:
 | |
| 			return buffer_overflow;
 | |
| 		case ASEMEM_E_SERVICE_STOPPED:
 | |
| 			return sevice_stopped;
 | |
| 		case ASEMEM_E_TIMEOUT :
 | |
| 			return timeout_err;
 | |
| 		case ASEMEM_E_NO_MEMORY:
 | |
| 			return no_memory;
 | |
| 		case ASEMEM_E_UNKNOWN_ERROR:
 | |
| 		default:
 | |
| 			return unknown_err;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void TSmart_card::display_error(smartcard_error error) const 
 | |
| {
 | |
| 	switch (error)
 | |
| 	{
 | |
| 		case no_smarterror:
 | |
| 			break;
 | |
| 		case wrong_function:
 | |
| 			error_box(TR("Funzione errata"));
 | |
| 			break;
 | |
| 		case card_removed:
 | |
| 			error_box(TR("Smartcard rimossa"));
 | |
| 			break;
 | |
| 		case no_power:
 | |
| 			error_box(TR("Smartcard non alimentata"));
 | |
| 			break;
 | |
| 		case wrong_card:
 | |
| 			error_box(TR("Smartcard errata"));
 | |
| 			break;
 | |
| 		case wrong_password:
 | |
| 			error_box(TR("Password errata"));
 | |
| 			break;
 | |
| 		case password_blocked:
 | |
| 			error_box(TR("Password bloccata"));
 | |
| 			break;
 | |
| 		case invelid_handle:
 | |
| 			error_box(TR("Handle errata"));
 | |
| 			break;
 | |
| 		case invalid_parameter:
 | |
| 			error_box(TR("Parametro errato"));
 | |
| 			break;
 | |
| 		case no_card:
 | |
| 			error_box(TR("Smartcard rimossa"));
 | |
| 			break;
 | |
| 		case reset_card:
 | |
| 			error_box(TR("Smartcard rinizializzata"));
 | |
| 			break;
 | |
| 		case protection_error:
 | |
| 			error_box(TR("Errore di protezione"));
 | |
| 			break;
 | |
| 		case unknown_reader:
 | |
| 			error_box(TR("Lettore sconosciuto"));
 | |
| 			break;
 | |
| 		case no_reader:
 | |
| 			error_box(TR("Nessun lettore rilevato"));
 | |
| 			break;
 | |
| 		case buffer_overflow:
 | |
| 			error_box(TR("Buffer overflow"));
 | |
| 			break;
 | |
| 		case sevice_stopped:
 | |
| 			error_box(TR("Servizio fermato"));
 | |
| 			break;
 | |
| 		case timeout_err:
 | |
| 			error_box(TR("Timeout"));
 | |
| 			break;
 | |
| 		case no_memory:
 | |
| 			error_box(TR("No memory"));
 | |
| 			break;
 | |
| 		case new_card:
 | |
| 			message_box(TR("Smartcard nuova"));
 | |
| 			break;
 | |
| 		case unknown_err:
 | |
| 			error_box(TR("Errore sconosciuto"));
 | |
| 		default:
 | |
| 			break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| smartcard_error TSmart_card::read()
 | |
| {
 | |
| 	_values.destroy();
 | |
| 	smartcard_error error = no_smarterror;
 | |
| 
 | |
| 	if (_type == ase_smartcard)
 | |
| 	{
 | |
| 		char * buffer = NULL;
 | |
| 		error = ASEmap_error(ASEread(&buffer));
 | |
| 
 | |
| 		if (error == no_smarterror)
 | |
| 		{
 | |
| 			TToken_string t((const char *)buffer);
 | |
| 
 | |
| 			for (const char * s = t.get(); s && *s != '\0'; s = t.get())
 | |
| 				_values.add(s);
 | |
| 			if (t.empty())
 | |
| 				error = new_card;
 | |
| 		}
 | |
| 		
 | |
| 		int i;
 | |
| 		
 | |
| 		for (i = _values.items(); i < _last_smart_field; i++)
 | |
| 			_values.add("");
 | |
| 
 | |
| 		const TRectype & tabrec = cache().get("SMC", _values.row(_key_field));
 | |
| 
 | |
| 		for (; i < _num_fields; i++)
 | |
| 		{
 | |
| 			const TString & val = tabrec.get(_tab_field.row(i));
 | |
| 			_values.add(val);
 | |
| 		}
 | |
| 
 | |
| 		if (buffer != NULL)
 | |
| 			delete buffer;
 | |
| 	}
 | |
| 	
 | |
| 	_card_connected = (error == no_smarterror || error == new_card);
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| smartcard_error TSmart_card::connect_card()
 | |
| {
 | |
| 	return read();
 | |
| }
 | |
| 
 | |
| smartcard_error TSmart_card::disconnect_card()
 | |
| {
 | |
| 	_card_connected = false;
 | |
| 	return no_smarterror;
 | |
| }
 | |
| 
 | |
| smartcard_error TSmart_card::write() const 
 | |
| {
 | |
| 	TToken_string t;
 | |
| 	int i;
 | |
| 	smartcard_error error;
 | |
| 
 | |
| 	for (i = 0; i < _last_smart_field; i++)
 | |
| 		t.add(_values.row(i));
 | |
| 
 | |
| 	TTable tab("SMC");
 | |
| 	tab.put("CODTAB", _values.row(_key_field));
 | |
| 
 | |
| 	bool new_rec = tab.read(_isequal, _lock) != NOERR;
 | |
| 	
 | |
| 	for (i = 0 ; i < _num_fields; i++)
 | |
| 		if (!_tab_field.row(i).empty())
 | |
| 		{
 | |
| 			if (i == _anno_field || i == _ndoc_field)
 | |
| 			{
 | |
| 				if (tab.get_int(_tab_field.row(i)) < atoi(_values.row(i)))
 | |
| 					tab.put(_tab_field.row(i), _values.row(i));
 | |
| 			}
 | |
| 			else
 | |
| 				tab.put(_tab_field.row(i), _values.row(i));
 | |
| 		}
 | |
| 	
 | |
| 	if (new_rec)
 | |
| 		tab.write();
 | |
| 	else
 | |
| 		tab.rewrite();
 | |
| 
 | |
| 	if (_type == ase_smartcard)
 | |
| 		error = ASEmap_error(ASEwrite((char *)(const  char *)t));
 | |
| 	return error;
 | |
| }
 | |
| 
 | |
| void TSmart_card::mask2card(const TMask & m)
 | |
| {
 | |
| 	_values.destroy();
 | |
| 	for (int i = 0; i < _num_fields; i++)
 | |
| 	{
 | |
| 		const TString & val = m.get(_output_field[i]);
 | |
| 		_values.add(val, i);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| smartcard_error TSmart_card::check_key(const TMask & m)
 | |
| {
 | |
| 	const TString & card_key = _values.row(_key_field);
 | |
| 	const TString & mask_key = m.get(_input_field[_key_field]);
 | |
| 	const bool ok = (mask_key.empty() || card_key == mask_key);
 | |
| 
 | |
| 	if (!ok)
 | |
| 		disconnect_card();
 | |
| 
 | |
| 	return ok ? no_smarterror : wrong_card;
 | |
| }
 | |
| 
 | |
| void TSmart_card::card2mask(TMask & m)
 | |
| {
 | |
| 	for (int i = 0; i < _num_fields; i++)
 | |
| 		if (_input_field[i] >= 0)
 | |
| 		{
 | |
| 			if (_prog_field[i] > 0)
 | |
| 			{
 | |
| 				const int anno_fld = anno_field();
 | |
| 				const int ndoc_fld = ndoc_field();
 | |
| 				const int last_anno = get_int(anno_fld);
 | |
| 				const int last_ndoc = get_int(ndoc_fld);
 | |
| 				const int anno = m.get_int(_output_field[anno_fld]);
 | |
| 				const int ndoc = m.get_int(_output_field[ndoc_fld]);
 | |
| 
 | |
| 				if (anno > last_anno)
 | |
| 					sub2field(i, m.get_real(_prog_field[i]));
 | |
| 				if (anno == last_anno && ndoc <= last_ndoc)
 | |
| 					sub2field(i, m.get_real(_prog_field[i]));
 | |
| 			}
 | |
| 			const TString & val = _values.row(i);
 | |
| 			m.set(_input_field[i], val, 3);
 | |
| 		}
 | |
| 	m.set(_check_field, card_connected());
 | |
| }
 | |
| 
 | |
| void TSmart_card::enable_prot_fields(TMask & m, bool on) const
 | |
| {
 | |
| 	for (int i = 0; i < _num_fields; i++)
 | |
| 		if (_prot_field[i])
 | |
| 			m.enable(_input_field[i], on);
 | |
| 
 | |
| }
 | |
| 
 | |
| void TSmart_card::add2field(int field, real & val)
 | |
| {
 | |
| 	real field_val(_values.row(field));
 | |
| 		
 | |
| 	field_val += val;
 | |
| 	_values.add(field_val.string(), field);
 | |
| }
 | |
| 
 | |
| bool TSmart_card::init()
 | |
| {
 | |
| // Identify card 
 | |
| 	_card_connected = false;
 | |
| 
 | |
| 	TFilename f("smartcard.ini");
 | |
| 
 | |
| 	if (f.custom_path())
 | |
| 	{
 | |
| 		TConfig c(f, "ve0100");
 | |
| 
 | |
| 		switch (c.get_int("Type"))
 | |
| 		{
 | |
| 			case 0:
 | |
| 				_type = no_smartcard;
 | |
| 				break;
 | |
| 			case 1:
 | |
| 				{
 | |
| 					if (ASEloadlibrary())
 | |
| 					{
 | |
| 						smartcard_error err = ASEmap_error(ASEsearch());
 | |
| 
 | |
| 						if (err == no_reader)
 | |
| 							_type = no_smartcard;
 | |
| 						else
 | |
| 							_type = ase_smartcard;
 | |
| 					}
 | |
| 					else
 | |
| 						_type = no_smartcard;
 | |
| 				}
 | |
| 				break;
 | |
| 			default:
 | |
| 				_type = no_smartcard;
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		
 | |
| 		_last_smart_field = c.get_int("FieldsOnCard");
 | |
| 		_password = c.get("Password").left(3);
 | |
| 		_anno_field = c.get_int("AnnoField");
 | |
| 		_ndoc_field = c.get_int("NDocField");
 | |
| 		_check_field = c.get_int("CheckField");
 | |
| 		for (_num_fields = 0; _num_fields < MAX_SM_FIELDS; _num_fields++)
 | |
| 		{
 | |
| 			_input_field[_num_fields] = c.get_int("InputFld", NULL, _num_fields);
 | |
| 			if (_input_field[_num_fields] != 0)
 | |
| 			{
 | |
| 				_output_field[_num_fields] = c.get_int("OutputFld", NULL, _num_fields, _input_field[_num_fields]);
 | |
| 				_prog_field[_num_fields] = c.get_int("ProgFld", NULL, _num_fields);
 | |
| 				_prot_field.set(_num_fields, c.get_bool("ProtFld", NULL, _num_fields));
 | |
| 				_tab_field.add(c.get("TabFld", NULL, _num_fields, ""), _num_fields);
 | |
| 				if (_tab_field.row(_num_fields) == "CODTAB")
 | |
| 					_key_field = _num_fields;
 | |
| 			}
 | |
| 			else
 | |
| 				break;
 | |
| 		}
 | |
| 	}
 | |
| 	return true;
 | |
| }
 |