mirror of
https://github.com/FirebirdSQL/firebird-qa.git
synced 2025-01-22 21:43:06 +01:00
205 lines
7.9 KiB
Python
205 lines
7.9 KiB
Python
#coding:utf-8
|
|
|
|
"""
|
|
ID: issue-5995
|
|
ISSUE: 5995
|
|
TITLE: Connection to server may hang when working with encrypted databases over non-TCP protocol
|
|
DESCRIPTION:
|
|
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
|
|
|
|
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.
|
|
JIRA: CORE-5730
|
|
FBTEST: bugs.gh_5995
|
|
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.
|
|
"""
|
|
|
|
import shutil
|
|
import locale
|
|
import re
|
|
import time
|
|
import platform
|
|
import subprocess
|
|
|
|
import datetime as py_dt
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from firebird.qa import *
|
|
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
|
|
|
|
db = db_factory()
|
|
act = python_act('db', substitutions = [('After line \\d+.*', ''),('[ \t]+', ' ')])
|
|
|
|
kholder_cfg_bak = temp_file('fbSampleKeyHolder.bak')
|
|
tmp_sql = temp_file('tmp_5995.sql')
|
|
tmp_log = temp_file('tmp_5995.log')
|
|
|
|
#-----------------------------------------------------------------------
|
|
|
|
def run_encr_decr(act: Action, mode, max_wait_encr_thread_finish, capsys):
|
|
|
|
assert mode in ('encrypt', 'decrypt')
|
|
|
|
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')
|
|
@pytest.mark.platform('Windows')
|
|
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
|
|
|
|
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
|