HEX
Server: Apache/2.4.63 (Unix)
System: Linux TOMS-220NAS 4.4.302+ #86009 SMP Wed Nov 26 18:19:17 CST 2025 x86_64
User: flavio87 (1026)
PHP: 8.3.27
Disabled: NONE
Upload Files
File: //proc/23796/root/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