File: //var/packages/CMS/scripts/CMSDelegate_user_delete.py
#!/usr/bin/env python3
import logging
import os
import sqlite3
import sys
import syslog
import traceback
from ctypes import CDLL, c_char_p
NAME = 'CMS'
VERSION = '1.0'
VENDOR = 'Synology Inc.'
SDK_MODVER = '1.0'
HOOK_NAME = 'user_delete'
# The 'SYNO.CMS' prefix helps us to log into CMS's own log file.
LOG_IDENT = 'SYNO.CMS.hook.{}'.format(HOOK_NAME)
CMS_DB_PATH = '/var/packages/CMS/var/db/cms_db.sqlite'
CMS_LIB_PATH = '/var/packages/CMS/target/lib/libcms.so'
syslog.openlog(LOG_IDENT)
logger = logging.getLogger('MyLogger')
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
def log_info(msg: str):
new_msg = '[INFO] {}'.format(msg)
syslog.syslog(syslog.LOG_INFO, new_msg)
logger.info(new_msg)
def log_err(msg: str):
new_msg = '[ERR] {}'.format(msg)
syslog.syslog(syslog.LOG_ERR, new_msg)
logger.error(new_msg)
def log_err_n_raise(msg: str, base_exc: Exception):
log_err(msg)
raise Exception(msg) from base_exc
def log_progress(msg: str, task):
log_info(msg + ' [begin]')
try:
ret = task()
log_info(msg + ' [done]')
return ret
except Exception:
log_err(msg + ' [failed]')
raise
def print_help(bin_name: str):
print('Usage: {} --sdk-mod-ver|--name|--pkg-ver|--vendor|--pre|--post'
.format(bin_name))
def cmds():
return {
'--sdk-mod-ver': lambda: print(SDK_MODVER),
'--name': lambda: print(NAME),
'--pkg-ver': lambda: print(VERSION),
'--vendor': lambda: print(VENDOR),
'--pre': lambda: log_progress('Try running pre hook', pre_hook),
'--post': lambda: log_progress('Try running post hook', post_hook),
}
def main():
bin_name = sys.argv[0]
if len(sys.argv) < 2:
print_help(bin_name)
return
cmd = sys.argv[1]
cmds().get(cmd, lambda: print_help(bin_name))()
# Below is the interesting part of hook.
def pre_hook():
# Define your own pre hook here
pass
def post_hook():
try:
num_of_users = log_progress(
'Try reading number of users from hook',
lambda: get_num_of_users())
user_infos = log_progress(
'Try reading user infos from hook',
lambda: get_user_infos(num_of_users))
log_info('Users from hook: [{}]'.format(
', '.join(map(str, user_infos))))
log_progress(
'Try removing given users from CMS db',
lambda: remove_users_from_cms_db(user_infos))
except Exception as exc:
log_err_n_raise('Failed to run post hook', exc)
class UserInfo:
def __init__(self, uid, name):
self.uid = uid
self.name = name
def __str__(self):
return 'UserInfo{{uid={}, name={}}}'.format(self.uid, self.name)
def get_num_of_users() -> int:
try:
return int(os.environ['NITEMS'])
except Exception as exc:
log_err_n_raise("Failed to get num of deleted users from hook", exc)
def get_user_infos(num_of_users: int):
try:
return [
UserInfo(
int(os.environ['UID_{}'.format(i)]),
os.environ['USER_NAME_{}'.format(i)]
)
for i in range(1, num_of_users + 1)
]
except Exception as exc:
log_err_n_raise("Failed to get user infos from hook", exc)
def remove_users_from_cms_db(user_infos):
try:
query = read_query_string()
with sqlite3.connect(CMS_DB_PATH) as conn:
conn.cursor().executemany(query, [(u.uid,) for u in user_infos])
except Exception as exc:
str_user_infos = ', '.join(map(str, user_infos))
log_err_n_raise(
"Failed to remove users from cms db: [{}]".format(str_user_infos),
exc)
def read_query_string() -> str:
try:
libcms = CDLL(CMS_LIB_PATH)
query_fn = (
libcms.SYNOCMSDelegateRemoveLocalUser_sqlite_query
)
query_fn.restype = c_char_p
return query_fn().decode('utf-8')
except Exception as exc:
log_err_n_raise(
'Failed to read query string for'
' removing delegations of local user',
exc)
if __name__ == '__main__':
try:
main()
except Exception:
# Here we log call stack to syslog only since python would log to
# stdout for us.
syslog.syslog(syslog.LOG_ERR, traceback.format_exc())
raise