mirror of https://github.com/tp4a/teleport
592 lines
21 KiB
Python
592 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import configparser
|
|
import os
|
|
import json
|
|
|
|
from app.const import *
|
|
from .logger import log
|
|
from .utils import AttrDict, tp_convert_to_attr_dict, tp_make_dir
|
|
|
|
__all__ = ['get_cfg']
|
|
|
|
|
|
class BaseAppConfig(dict):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
import builtins
|
|
if '__app_cfg__' in builtins.__dict__:
|
|
raise RuntimeError('AppConfig instance already exists.')
|
|
|
|
self['_cfg_default'] = {}
|
|
self['_cfg_loaded'] = {}
|
|
self['_kvs'] = {'_': AttrDict()}
|
|
self['_cfg_file'] = ''
|
|
|
|
self._on_init()
|
|
|
|
def __getattr__(self, name):
|
|
_name = name.replace('-', '_')
|
|
if _name in self['_kvs']:
|
|
return self['_kvs'][_name]
|
|
else:
|
|
if _name in self['_kvs']['_']:
|
|
return self['_kvs']['_'][_name]
|
|
else:
|
|
return AttrDict()
|
|
|
|
def __setattr__(self, key, val):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
raise RuntimeError('invalid name.')
|
|
|
|
if _sec not in self['_kvs']:
|
|
self['_kvs'][_sec] = {}
|
|
self['_kvs'][_sec][_key] = val
|
|
|
|
def _on_init(self):
|
|
raise RuntimeError('can not create instance for base class.')
|
|
|
|
def _on_get_save_info(self):
|
|
raise RuntimeError('can not create instance for base class.')
|
|
|
|
def _on_load(self, cfg_parser):
|
|
raise RuntimeError('can not create instance for base class.')
|
|
|
|
def reload(self):
|
|
self['_cfg_default'] = {}
|
|
self['_cfg_loaded'] = {}
|
|
self['_kvs'] = {'_': self['_kvs']['_']}
|
|
self._on_init()
|
|
return self.load(self['_cfg_file'])
|
|
|
|
def set_kv(self, key, val):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
raise RuntimeError('invalid name.')
|
|
|
|
if _sec not in self['_cfg_loaded']:
|
|
self['_cfg_loaded'][_sec] = {}
|
|
self['_cfg_loaded'][_sec][_key] = val
|
|
self._update_kvs(_sec, _key, val)
|
|
|
|
def set_default(self, key, val, comment=None):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
raise RuntimeError('invalid name.')
|
|
|
|
if _sec not in self['_cfg_default']:
|
|
self['_cfg_default'][_sec] = {}
|
|
if _key not in self['_cfg_default'][_sec]:
|
|
self['_cfg_default'][_sec][_key] = {}
|
|
self['_cfg_default'][_sec][_key]['value'] = val
|
|
self['_cfg_default'][_sec][_key]['comment'] = comment
|
|
else:
|
|
self['_cfg_default'][_sec][_key]['value'] = val
|
|
|
|
if comment is not None:
|
|
self['_cfg_default'][_sec][_key]['comment'] = comment
|
|
elif 'comment' not in self['_cfg_default'][_sec][_key]:
|
|
self['_cfg_default'][_sec][_key]['comment'] = None
|
|
|
|
self._update_kvs(_sec, _key, val)
|
|
|
|
def load(self, cfg_file):
|
|
if not os.path.exists(cfg_file):
|
|
log.e('configuration file does not exists: [{}]\n'.format(cfg_file))
|
|
return False
|
|
try:
|
|
_cfg = configparser.ConfigParser()
|
|
_cfg.read(cfg_file)
|
|
except:
|
|
log.e('can not load configuration file: [{}]\n'.format(cfg_file))
|
|
return False
|
|
|
|
if not self._on_load(_cfg):
|
|
return False
|
|
|
|
self['_cfg_file'] = cfg_file
|
|
return True
|
|
|
|
def save(self, cfg_file=None):
|
|
if cfg_file is None:
|
|
cfg_file = self['_cfg_file']
|
|
_save = self._on_get_save_info()
|
|
|
|
cnt = ['; codec: utf-8\n']
|
|
|
|
is_first_section = True
|
|
for sections in _save:
|
|
for sec_name in sections:
|
|
sec_name = sec_name.replace('-', '_')
|
|
if sec_name in self['_cfg_default'] or sec_name in self['_cfg_loaded']:
|
|
if not is_first_section:
|
|
cnt.append('\n')
|
|
cnt.append('[{}]'.format(sec_name))
|
|
is_first_section = False
|
|
for k in sections[sec_name]:
|
|
_k = k.replace('-', '_')
|
|
have_comment = False
|
|
if sec_name in self['_cfg_default'] and _k in self['_cfg_default'][sec_name] and 'comment' in self['_cfg_default'][sec_name][_k]:
|
|
comments = self['_cfg_default'][sec_name][_k]['comment']
|
|
if comments is not None:
|
|
comments = self['_cfg_default'][sec_name][_k]['comment'].split('\n')
|
|
cnt.append('')
|
|
have_comment = True
|
|
for comment in comments:
|
|
cnt.append('; {}'.format(comment))
|
|
|
|
if sec_name in self['_cfg_loaded'] and _k in self['_cfg_loaded'][sec_name]:
|
|
if not have_comment:
|
|
cnt.append('')
|
|
cnt.append('{}={}'.format(k, self['_cfg_loaded'][sec_name][_k]))
|
|
|
|
cnt.append('\n')
|
|
tmp_file = '{}.tmp'.format(cfg_file)
|
|
|
|
try:
|
|
with open(tmp_file, 'w', encoding='utf8') as f:
|
|
f.write('\n'.join(cnt))
|
|
if os.path.exists(cfg_file):
|
|
os.unlink(cfg_file)
|
|
os.rename(tmp_file, cfg_file)
|
|
return True
|
|
except Exception as e:
|
|
print(e.__str__())
|
|
return False
|
|
|
|
def _update_kvs(self, section, key, val):
|
|
if section not in self['_kvs']:
|
|
self['_kvs'][section] = AttrDict()
|
|
self['_kvs'][section][key] = val
|
|
|
|
def get_str(self, key, def_value=None):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
return def_value, False
|
|
|
|
if _sec not in self['_kvs']:
|
|
return def_value, False
|
|
if _key not in self['_kvs'][_sec]:
|
|
return def_value, False
|
|
|
|
if self['_kvs'][_sec][_key] is None:
|
|
return def_value, False
|
|
|
|
return str(self['_kvs'][_sec][_key]), True
|
|
|
|
def get_int(self, key, def_value=-1):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
return def_value, False
|
|
|
|
if _sec not in self['_kvs']:
|
|
return def_value, False
|
|
if _key not in self['_kvs'][_sec]:
|
|
return def_value, False
|
|
|
|
if self['_kvs'][_sec][_key] is None:
|
|
return def_value, False
|
|
|
|
try:
|
|
return int(self['_kvs'][_sec][_key]), True
|
|
except ValueError as e:
|
|
print(e.__str__())
|
|
return def_value, False
|
|
|
|
def get_bool(self, key, def_value=False):
|
|
x = key.split('::')
|
|
if 1 == len(x):
|
|
_sec = '_'
|
|
_key = x[0].replace('-', '_')
|
|
elif 2 == len(x):
|
|
_sec = x[0].replace('-', '_')
|
|
_key = x[1].replace('-', '_')
|
|
else:
|
|
return def_value, False
|
|
|
|
if _sec not in self['_kvs']:
|
|
return def_value, False
|
|
if _key not in self['_kvs'][_sec]:
|
|
return def_value, False
|
|
|
|
if self['_kvs'][_sec][_key] is None:
|
|
return def_value, False
|
|
|
|
tmp = str(self['_kvs'][_sec][_key]).lower()
|
|
|
|
if tmp in ['yes', 'true', '1']:
|
|
return True, True
|
|
elif tmp in ['no', 'false', '0']:
|
|
return False, True
|
|
else:
|
|
return def_value, False
|
|
|
|
|
|
class AppConfig(BaseAppConfig):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
# 核心服务的配置信息,通过核心服务的接口获取
|
|
self.core = AttrDict()
|
|
self.core.detected = False
|
|
|
|
# 系统默认配置,通过数据库查询得到
|
|
self.sys = AttrDict()
|
|
self.sys.loaded = False
|
|
self.sys_smtp_password = '' # 密码单独处理,避免无意中传递给前端页面了
|
|
|
|
def _on_init(self):
|
|
self.set_default('common::ip', '0.0.0.0', 'ip=0.0.0.0')
|
|
self.set_default('common::port', 7190,
|
|
'port listen by web server, default to 7190.\n'
|
|
'DO NOT FORGET update common::web-server-rpc in base.ini if you modified this setting.\n'
|
|
'port=7190')
|
|
self.set_default('common::log-file', None,
|
|
'log file of web server, default to /var/log/teleport/tpweb.log\n'
|
|
'log-file=/var/log/teleport/tpweb.log'
|
|
)
|
|
self.set_default('common::log-level', 2,
|
|
'`log-level` can be 0 ~ 4, default to 2.\n'
|
|
'LOG_LEVEL_DEBUG 0 log every-thing.\n'
|
|
'LOG_LEVEL_VERBOSE 1 log every-thing but without debug message.\n'
|
|
'LOG_LEVEL_INFO 2 log information/warning/error message.\n'
|
|
'LOG_LEVEL_WARN 3 log warning and error message.\n'
|
|
'LOG_LEVEL_ERROR 4 log error message only.\n'
|
|
'log-level=2'
|
|
)
|
|
self.set_default('common::debug-mode', 0,
|
|
'0/1. default to 0.\n'
|
|
'in debug mode, `log-level` force to 0 and trace call stack when exception raised.\n'
|
|
'debug-mode=0'
|
|
)
|
|
self.set_default('common::core-server-rpc', 'http://127.0.0.1:52080/rpc',
|
|
'`core-server-rpc` is the rpc interface of base server.\n'
|
|
'DO NOT FORGET update this setting if you modified rpc::bind-port in core.ini.\n'
|
|
'core-server-rpc=http://127.0.0.1:52080/rpc'
|
|
)
|
|
self.set_default('database::type', 'sqlite',
|
|
'database in use, should be sqlite/mysql, default to sqlite.\n'
|
|
'type=sqlite'
|
|
)
|
|
self.set_default('database::sqlite-file', None,
|
|
'sqlite-file=/var/lib/teleport/data/ts_db.db'
|
|
)
|
|
self.set_default('database::mysql-host', '127.0.0.1', 'mysql-host=127.0.0.1')
|
|
self.set_default('database::mysql-port', 3306, 'mysql-port=3306')
|
|
self.set_default('database::mysql-db', 'teleport', 'mysql-db=teleport')
|
|
self.set_default('database::mysql-prefix', 'tp_', 'mysql-prefix=tp_')
|
|
self.set_default('database::mysql-user', 'teleport', 'mysql-user=teleport')
|
|
self.set_default('database::mysql-password', 'password', 'mysql-password=password')
|
|
|
|
def _on_get_save_info(self):
|
|
return [
|
|
{'common': ['ip', 'port', 'log-file', 'log-level', 'debug-mode', 'base-server-rpc']},
|
|
{'database': ['type', 'sqlite-file', 'mysql-host', 'mysql-port', 'mysql-db', 'mysql-prefix', 'mysql-user', 'mysql-password']}
|
|
]
|
|
|
|
def _on_load(self, cfg_parser):
|
|
if 'common' not in cfg_parser:
|
|
log.e('invalid config file, need `common` section.\n')
|
|
return False
|
|
if 'database' not in cfg_parser:
|
|
log.e('invalid config file, need `database` section.\n')
|
|
return False
|
|
|
|
_sec = cfg_parser['common']
|
|
|
|
_tmp_int = _sec.getint('log-level', -1)
|
|
if log.LOG_DEBUG <= _tmp_int <= log.LOG_ERROR:
|
|
self.set_kv('common::log-level', _tmp_int)
|
|
log.set_attribute(min_level=self.common.log_level)
|
|
|
|
_tmp_bool = _sec.getint('debug-mode', False)
|
|
self.set_kv('common::debug-mode', _tmp_bool)
|
|
if _tmp_bool:
|
|
log.set_attribute(min_level=log.LOG_DEBUG, trace_error=log.TRACE_ERROR_FULL)
|
|
|
|
_tmp_str = _sec.get('ip', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('common::ip', _tmp_str)
|
|
|
|
_tmp_int = _sec.getint('port', -1)
|
|
if -1 != _tmp_int:
|
|
self.set_kv('common::port', _tmp_int)
|
|
|
|
_tmp_str = _sec.get('log-file', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('common::log-file', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('base-server-rpc', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('common::base-server-rpc', _tmp_str)
|
|
|
|
_sec = cfg_parser['database']
|
|
|
|
_tmp_str = _sec.get('type', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::type', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('sqlite-file', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::sqlite-file', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('mysql-host', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::mysql-host', _tmp_str)
|
|
|
|
_tmp_int = _sec.getint('mysql-port', -1)
|
|
if _tmp_int != -1:
|
|
self.set_kv('database::mysql-port', _tmp_int)
|
|
|
|
_tmp_str = _sec.get('mysql-db', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::mysql-db', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('mysql-prefix', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::mysql-prefix', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('mysql-user', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::mysql-user', _tmp_str)
|
|
|
|
_tmp_str = _sec.get('mysql-password', None)
|
|
if _tmp_str is not None:
|
|
self.set_kv('database::mysql-password', _tmp_str)
|
|
|
|
_log_file, ok = self.get_str('common::log-file')
|
|
if ok and _log_file:
|
|
self.log_path = os.path.abspath(os.path.dirname(_log_file))
|
|
else:
|
|
_log_file = os.path.join(self.log_path, 'tpweb.log')
|
|
self.set_default('common::log-file', _log_file)
|
|
|
|
if not os.path.exists(self.log_path):
|
|
tp_make_dir(self.log_path)
|
|
if not os.path.exists(self.log_path):
|
|
log.e('Can not create log path:{}\n'.format(self.log_path))
|
|
return False
|
|
|
|
log.set_attribute(filename=_log_file)
|
|
|
|
return True
|
|
|
|
def update_core(self, conf_data):
|
|
self.core = AttrDict()
|
|
self.core.detected = False
|
|
|
|
if conf_data is None:
|
|
log.w('core server config info is empty.\n')
|
|
return True
|
|
|
|
try:
|
|
self.core.ssh = AttrDict()
|
|
self.core.ssh.enable = False
|
|
self.core.ssh.port = 52189
|
|
if 'ssh' in conf_data:
|
|
self.core.ssh.enable = conf_data['ssh']['enable']
|
|
self.core.ssh.port = conf_data['ssh']['port']
|
|
|
|
self.core.rdp = AttrDict()
|
|
self.core.rdp.enable = False
|
|
self.core.rdp.port = 52089
|
|
if 'rdp' in conf_data:
|
|
self.core.rdp.enable = conf_data['rdp']['enable']
|
|
self.core.rdp.port = conf_data['rdp']['port']
|
|
|
|
self.core.telnet = AttrDict()
|
|
self.core.telnet.enable = False
|
|
self.core.telnet.port = 52389
|
|
if 'telnet' in conf_data:
|
|
self.core.telnet.enable = conf_data['telnet']['enable']
|
|
self.core.telnet.port = conf_data['telnet']['port']
|
|
|
|
if 'replay-path' in conf_data:
|
|
self.core.replay_path = conf_data['replay-path']
|
|
|
|
if 'web-server-rpc' in conf_data:
|
|
self.core.web_server_rpc = conf_data['web-server-rpc']
|
|
if 'version' in conf_data:
|
|
self.core.version = conf_data['version']
|
|
|
|
self.core.detected = True
|
|
|
|
except IndexError:
|
|
log.e('invalid core server config.\n')
|
|
return False
|
|
|
|
return True
|
|
|
|
def update_sys(self, conf_data):
|
|
self.sys = AttrDict()
|
|
self.sys.loaded = False
|
|
|
|
if conf_data is None:
|
|
log.w('system default config info is empty.\n')
|
|
# return True
|
|
|
|
# =====================================
|
|
# 密码策略相关
|
|
# =====================================
|
|
# conf_data['password'] = '{"find":false,"strong":true}'
|
|
try:
|
|
_password = json.loads(conf_data['password'])
|
|
except:
|
|
log.w('invalid password config, use default.\n')
|
|
_password = {}
|
|
|
|
self.sys.password = tp_convert_to_attr_dict(_password)
|
|
if not self.sys.password.is_exists('allow_reset'):
|
|
self.sys.password.allow_reset = True
|
|
if not self.sys.password.is_exists('force_strong'):
|
|
self.sys.password.force_strong = True
|
|
if not self.sys.password.is_exists('timeout'):
|
|
self.sys.password.timeout = 0
|
|
|
|
# =====================================
|
|
# 登录相关
|
|
# =====================================
|
|
try:
|
|
_login = json.loads(conf_data['login'])
|
|
except:
|
|
log.w('invalid login config, use default.\n')
|
|
_login = {}
|
|
|
|
self.sys.login = tp_convert_to_attr_dict(_login)
|
|
if not self.sys.login.is_exists('session_timeout'):
|
|
self.sys.login.session_timeout = 60 # 1 hour
|
|
if not self.sys.login.is_exists('retry'):
|
|
self.sys.login.retry = 0
|
|
if not self.sys.login.is_exists('lock_timeout'):
|
|
self.sys.login.lock_timeout = 30 # 30 min
|
|
if not self.sys.login.is_exists('auth'):
|
|
self.sys.login.auth = TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA | TP_LOGIN_AUTH_USERNAME_OATH | TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH
|
|
# print('==login==', json.dumps(self.sys.login, separators=(',', ':')))
|
|
|
|
# =====================================
|
|
# SMTP相关
|
|
# =====================================
|
|
self.sys_smtp_password = ''
|
|
try:
|
|
_smtp = json.loads(conf_data['smtp'])
|
|
except:
|
|
log.w('invalid smtp config, use default.\n')
|
|
_smtp = {}
|
|
|
|
self.sys.smtp = tp_convert_to_attr_dict(_smtp)
|
|
if not self.sys.smtp.is_exists('server'):
|
|
self.sys.smtp.server = ''
|
|
if not self.sys.smtp.is_exists('port'):
|
|
self.sys.smtp.port = 25
|
|
if not self.sys.smtp.is_exists('ssl'):
|
|
self.sys.smtp.ssl = False
|
|
if not self.sys.smtp.is_exists('sender'):
|
|
self.sys.smtp.sender = ''
|
|
if self.sys.smtp.is_exists('password'):
|
|
self.sys_smtp_password = self.sys.smtp.password
|
|
self.sys.smtp.password = '********'
|
|
# del self.sys.smtp.password
|
|
|
|
# =====================================
|
|
# 存储相关
|
|
# =====================================
|
|
try:
|
|
_storage = json.loads(conf_data['storage'])
|
|
except:
|
|
log.w('invalid storage config, use default.\n')
|
|
_storage = {}
|
|
|
|
self.sys.storage = tp_convert_to_attr_dict(_storage)
|
|
if not self.sys.storage.is_exists('keep_log'):
|
|
self.sys.storage.keep_log = 0
|
|
if not self.sys.storage.is_exists('keep_record'):
|
|
self.sys.storage.keep_record = 0
|
|
if not self.sys.storage.is_exists('cleanup_hour'):
|
|
self.sys.storage.cleanup_hour = 4
|
|
if not self.sys.storage.is_exists('cleanup_minute'):
|
|
self.sys.storage.cleanup_minute = 30
|
|
|
|
self.sys.loaded = True
|
|
|
|
return True
|
|
|
|
|
|
def get_cfg():
|
|
"""
|
|
:rtype: app.base.configs.AppConfig
|
|
"""
|
|
import builtins
|
|
if '__app_cfg__' not in builtins.__dict__:
|
|
builtins.__dict__['__app_cfg__'] = AppConfig()
|
|
return builtins.__dict__['__app_cfg__']
|
|
|
|
# def app_cfg():
|
|
# import builtins
|
|
# if '__web_config__' not in builtins.__dict__:
|
|
# builtins.__dict__['__web_config__'] = ConfigFile()
|
|
# return builtins.__dict__['__web_config__']
|
|
|
|
|
|
# if __name__ == '__main__':
|
|
# cfg = AppConfig()
|
|
# cfg.set_default('common::log-file', 'E:/test/log/web.log')
|
|
# cfg.load('E:/test/config/web.ini')
|
|
# cfg.aaa = 'this is aaa'
|
|
# cfg.bbb = 123
|
|
# cfg.ccc = False
|
|
#
|
|
# print('----usage--------------------')
|
|
# print(cfg.common.port)
|
|
# print(cfg.get_str('aaa'))
|
|
# print(cfg.get_str('bbb'))
|
|
# print(cfg.get_str('ccc'))
|
|
# print('----usage--------------------')
|
|
# print(cfg.get_int('aaa'))
|
|
# print(cfg.get_int('bbb'))
|
|
# print(cfg.get_int('ccc'))
|
|
# print('----usage--------------------')
|
|
# print(cfg.get_bool('aaa'))
|
|
# print(cfg.get_bool('bbb'))
|
|
# print(cfg.get_bool('ccc'))
|
|
# print('----usage--------------------')
|
|
# print(cfg.common)
|
|
# print('----usage--------------------')
|
|
# print(cfg.aaa)
|
|
# print(cfg.bbb)
|
|
# print(cfg.ccc)
|
|
#
|
|
# cfg.save('E:/test/config/web-new.ini')
|
|
# cfg.save()
|