2021-12-30 19:43:52 +01:00
#coding:utf-8
2022-01-27 20:08:36 +01:00
"""
ID : issue - 5995
ISSUE : 5995
TITLE : Connection to server may hang when working with encrypted databases over non - TCP protocol
DESCRIPTION :
2024-06-03 08:50:00 +02:00
Test implemented only to be run on Windows .
Folder % FIREBIRD_HOME % / plugins / must have files fbSampleKeyHolder . conf and fbSampleKeyHolder . dll which should be
copied there from % FIREBIRD_HOME % / examples / prebuilt / plugins / .
NB ! These files ABSENT in FB 3. x but they can be taken from FB 4. x snapshot .
File fbSampleKeyHolder . conf must have following lines :
Auto = true
KeyRed = 111
2022-01-27 20:08:36 +01:00
2024-06-03 08:50:00 +02:00
If we encrypt database and then make file fbSampleKeyHolder . conf EMPTY then usage of XNET and WNET protocols became
impossible before this ticket was fixed .
Great thanks to Alex for suggestions .
2022-01-27 20:08:36 +01:00
JIRA : CORE - 5730
2022-02-02 15:46:19 +01:00
FBTEST : bugs . gh_5995
2024-06-03 08:50:00 +02:00
NOTES :
[ 03.06 .2024 ] pzotov
Confirmed bug on 3.0 .1 .32609 , 4.0 .0 .853 : ISQL hangs on attempt to connect to database when file plugins / keyholder . conf is empty .
Checked on 6.0 .0 .366 , 5.0 .1 .1411 , 4.0 .5 .3103 ( all of them were checked for ServerMode = SS and CS ) .
ATTENTION : 3. x raises different SQLSTATE and error text , depending on ServerMode !
For 3. x value of SQLSTATE and error text depends on Servermode .
On Classic FB 3. x output will be almost like in FB 4. x + :
Statement failed , SQLSTATE = 08004
Missing correct crypt key
- Plugin fbSampleDbCrypt :
- Crypt key Red not set
- IProvider : : attachDatabase failed when loading mapping cache
On Super FB 3. x output is :
Statement failed , SQLSTATE = HY000
Missing correct crypt key
- Plugin fbSampleDbCrypt :
- Crypt key Red not set
Because of that , it was decided not to check FB 3. x as this version soon will be considered as obsolete .
2022-01-27 20:08:36 +01:00
"""
2021-12-30 19:43:52 +01:00
2024-06-03 08:50:00 +02:00
import shutil
import locale
import re
import time
import platform
import subprocess
import datetime as py_dt
from pathlib import Path
2021-12-30 19:43:52 +01:00
import pytest
2022-01-27 20:08:36 +01:00
from firebird . qa import *
2024-06-03 08:50:00 +02:00
from firebird . driver import DatabaseError , DbInfoCode , NetProtocol
import time
###########################
### S E T T I N G S ###
###########################
# QA_GLOBALS -- dict, is defined in qa/plugin.py, obtain settings
# from act.files_dir/'test_config.ini':
enc_settings = QA_GLOBALS [ ' encryption ' ]
# ACHTUNG: this must be carefully tuned on every new host:
#
MAX_WAITING_ENCR_FINISH = int ( enc_settings [ ' MAX_WAIT_FOR_ENCR_FINISH_WIN ' ] )
assert MAX_WAITING_ENCR_FINISH > 0
ENCRYPTION_PLUGIN = enc_settings [ ' encryption_plugin ' ] # fbSampleDbCrypt
ENCRYPTION_KEY = enc_settings [ ' encryption_key ' ] # Red
2021-12-30 19:43:52 +01:00
2022-01-27 20:08:36 +01:00
db = db_factory ( )
2024-06-03 08:50:00 +02:00
act = python_act ( ' db ' , substitutions = [ ( ' After line \\ d+.* ' , ' ' ) , ( ' [ \t ]+ ' , ' ' ) ] )
2021-12-30 19:43:52 +01:00
2024-06-03 08:50:00 +02:00
kholder_cfg_bak = temp_file ( ' fbSampleKeyHolder.bak ' )
tmp_sql = temp_file ( ' tmp_5995.sql ' )
tmp_log = temp_file ( ' tmp_5995.log ' )
2021-12-30 19:43:52 +01:00
2024-06-03 08:50:00 +02:00
#-----------------------------------------------------------------------
def run_encr_decr ( act : Action , mode , max_wait_encr_thread_finish , capsys ) :
assert mode in ( ' encrypt ' , ' decrypt ' )
2021-12-30 19:43:52 +01:00
2024-06-03 08:50:00 +02:00
if mode == ' encrypt ' :
alter_db_sttm = f ' alter database encrypt with " { ENCRYPTION_PLUGIN } " key " { ENCRYPTION_KEY } " '
wait_for_state = ' Database encrypted '
elif mode == ' decrypt ' :
alter_db_sttm = ' alter database decrypt '
wait_for_state = ' Database not encrypted '
e_thread_finished = False
d1 = py_dt . timedelta ( 0 )
with act . db . connect ( ) as con :
#cur = con.cursor()
#ps = cur.prepare('select mon$crypt_state from mon$database')
t1 = py_dt . datetime . now ( )
try :
d1 = t1 - t1
con . execute_immediate ( alter_db_sttm )
con . commit ( )
# Pattern to check for completed encryption thread:
completed_encr_pattern = re . compile ( f ' Attributes \\ s+encrypted, \\ s+plugin \\ s+ { ENCRYPTION_PLUGIN } ' , re . IGNORECASE )
while not e_thread_finished :
t2 = py_dt . datetime . now ( )
d1 = t2 - t1
if d1 . seconds * 1000 + d1 . microseconds / / 1000 > max_wait_encr_thread_finish :
break
#############################################
### C H E C K G S T A T A T T R. ###
#############################################
# Invoke 'gstat -h' and read its ouput.
# Encryption can be considered as COMPLETED when we will found:
# "Attributes encrypted, plugin fbSampleDbCrypt"
#
act . gstat ( switches = [ ' -h ' ] )
for line in act . stdout . splitlines ( ) :
if mode == ' encrypt ' and completed_encr_pattern . match ( line . strip ( ) ) :
e_thread_finished = True
if mode == ' decrypt ' and ' Attributes ' in line and not completed_encr_pattern . search ( line . strip ( ) ) :
e_thread_finished = True
if e_thread_finished :
break
time . sleep ( 0.5 )
except DatabaseError as e :
print ( e . __str__ ( ) )
assert e_thread_finished , f ' TIMEOUT EXPIRATION: { mode =} took { d1 . seconds * 1000 + d1 . microseconds / / 1000 } ms which greater than { max_wait_encr_thread_finish =} ms '
#-----------------------------------------------------------------------
@pytest.mark.encryption
@pytest.mark.version ( ' >=4.0 ' )
2022-01-27 20:08:36 +01:00
@pytest.mark.platform ( ' Windows ' )
2024-06-03 08:50:00 +02:00
def test_1 ( act : Action , kholder_cfg_bak : Path , tmp_sql : Path , tmp_log : Path , capsys ) :
kholder_cfg_file = act . vars [ ' home-dir ' ] / ' plugins ' / ' fbSampleKeyHolder.conf '
shutil . copy2 ( kholder_cfg_file , kholder_cfg_bak )
finish_encryption = False
2021-12-30 19:43:52 +01:00
2024-06-03 08:50:00 +02:00
protocols_list = [ NetProtocol . INET , NetProtocol . XNET , ]
if act . is_version ( ' <5 ' ) :
protocols_list . append ( NetProtocol . WNET )
expected_output = actual_output = test_sql = ' '
try :
run_encr_decr ( act , ' encrypt ' , MAX_WAITING_ENCR_FINISH , capsys )
finish_encryption = True
with open ( kholder_cfg_file , ' w ' ) as f :
pass
for protocol_name in protocols_list :
conn_str = f " connect { protocol_name . name . lower ( ) } :// { act . db . db_path } user { act . db . user } password ' { act . db . password } ' "
test_sql = f """
set list on ;
set bail on ;
set echo on ;
{ conn_str } ;
select mon $ remote_protocol from mon $ attachments where mon $ attachment_id = current_connection ;
"""
tmp_sql . write_text ( test_sql )
with open ( tmp_log , ' w ' ) as f_log :
# ISQL-4.x must issue:
# Statement failed, SQLSTATE = 08004
# Missing database encryption key for your attachment
# -Plugin fbSampleDbCrypt:
# -Crypt key Red not set
# Before fix, ISQL hanged on CONNECT, thus we have to use timeout here!
#
p = subprocess . run ( [ act . vars [ ' isql ' ] ,
' -q ' ,
' -i ' , str ( tmp_sql )
] ,
stdout = f_log , stderr = subprocess . STDOUT ,
timeout = 3
)
actual_output + = tmp_log . read_text ( )
expected_output + = f """
{ conn_str } ;
Statement failed , SQLSTATE = 08004
Missing database encryption key for your attachment
- Plugin { ENCRYPTION_PLUGIN } :
- Crypt key { ENCRYPTION_KEY } not set
"""
except Exception as e :
actual_output + = test_sql + ' \n ' + e . __str__ ( )
finally :
shutil . copy2 ( kholder_cfg_bak , kholder_cfg_file )
if finish_encryption :
run_encr_decr ( act , ' decrypt ' , MAX_WAITING_ENCR_FINISH , capsys )
act . expected_stdout = expected_output
act . stdout = actual_output # capsys.readouterr().out
assert act . clean_stdout == act . clean_expected_stdout