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: //usr/syno/bin/synokmip
#!/usr/bin/python3

from kmip.core import enums
from kmip.core.enums import KeyFormatType
from kmip.core.enums import ObjectType
from kmip.core.enums import Operation
from kmip.core.enums import ResultStatus

from kmip.core.factories.attributes import AttributeFactory
from kmip.core.objects import TemplateAttribute

from kmip.synology import utils
from kmip.synology import get
from kmip.synology import deactivate
from kmip.synology import destroy
from kmip.synology import activate
from kmip.synology import register_opaque_object
from kmip.synology import register_secret_data
from kmip.synology import get_attributes
from kmip.synology import modify_name
from kmip.synology import get_attributes
from kmip.synology import locate
from kmip.synology import query

import logging
import sys
import argparse
import json
import time
import datetime
import subprocess
import xml.etree.ElementTree as xmlET
import fcntl
import hashlib
import random
import string

from os import listdir
from os.path import isfile, join, exists
from pathlib import Path

SZF_KMIP_CLIENT_ENABLED = "/etc/pykmip/kmip_client_enabled"
SZF_KMIP_RETRIVE_REC = "/etc/pykmip/retrive_rec.json"
SZF_SYNOKMIP_LOCK = "/run/pykmip/synokmip.lock"

lock_file = None

def parse_action_argument(input_arg):
    parser = argparse.ArgumentParser(description='Synology KMIP Tool')
    parser.add_argument(
        '--reg_opaque_obj',
        dest='reg_opaque_obj',
        action='store_true',
        help='register opaque object'
    )
    parser.add_argument(
        '--reg_sec_data',
        dest='reg_sec_data',
        action='store_true',
        help='register secret data'
    )
    parser.add_argument(
        '--get',
        dest='get',
        action='store_true',
        help='get'
    )
    parser.add_argument(
        '--deactivate',
        dest='deactivate',
        action='store_true',help='deactivate'
    )
    parser.add_argument(
        '--destroy',
        dest='destroy',
        action='store_true',
        help='destroy'
    )
    parser.add_argument(
        '--activate',
        dest='activate',
        action='store_true',
        help='activate'
    )
    parser.add_argument(
        '--get_attributes',
        dest='get_attributes',
        action='store_true',
        help='get_attributes'
    )
    parser.add_argument(
        '--modify_name',
        dest='modify_name',
        action='store_true',
        help='modify_name'
    )
    parser.add_argument(
        '--locate',
        dest='locate',
        action='store_true',
        help='locate'
    )
    parser.add_argument(
        '--get_with_name',
        dest='get_with_name',
        action='store_true',
        help='get with name'
    )
    parser.add_argument(
        '--activate_with_name',
        dest='activate_with_name',
        action='store_true',
        help='activate with name'
    )
    parser.add_argument(
        '--deactivate_with_name',
        dest='deactivate_with_name',
        action='store_true',
        help='deactivate with name'
    )
    parser.add_argument(
        '--destroy_with_name',
        dest='destroy_with_name',
        action='store_true',
        help='destroy with name'
    )
    parser.add_argument(
        '--destroy_all_in_vault',
        dest='destroy_all_in_vault',
        action='store_true',
        help='destroy all in vault'
    )
    parser.add_argument(
        '--query',
        dest='query',
        action='store_true',
        help='query'
    )
    parser.add_argument(
        '--get_key_attr_list',
        dest='get_key_attr_list',
        action='store_true',
        help='get_key_attr_list'
    )
    parser.add_argument(
        '--get_server_hostname',
        dest='get_server_hostname',
        action='store_true',
        help='get_server_hostname'
    )

    opts, left = parser.parse_known_args(input_arg)
    sys.argv = sys.argv[:1]+left

    return opts

def parse_secondary_argument(input_arg, action):
    parser = argparse.ArgumentParser(description='Synology KMIP Tool')

    if (action.get or
        action.activate or
        action.deactivate or
        action.get_attributes or
        action.modify_name or
        action.destroy):
        parser.add_argument(
            '--uuid',
            dest='uuid',
            type=str,
            help='uuid for get/get_attributes/activate/deactivate/modify'
        )
    if (action.locate):
        parser.add_argument(
            '--name',
            dest='key_name',
            type=str,
            help='(optional) key name for locate'
        )
    if (action.get_with_name or
        action.reg_opaque_obj or
        action.reg_sec_data or
        action.modify_name or
        action.activate_with_name or
        action.deactivate_with_name or
        action.destroy_with_name):
        parser.add_argument(
            '--name',
            dest='key_name',
            type=str,
            help='key name for register/modify_name/get_with_name'
        )
    if (action.reg_opaque_obj or
        action.reg_sec_data):
            parser.add_argument(
            '--infile',
            dest='infile',
            type=str,
            help='data file path for register'
        )
    if (action.get or
        action.get_with_name or
        action.reg_opaque_obj or
        action.reg_sec_data or
        action.get_key_attr_list or
        action.get_server_hostname):
        parser.add_argument(
            '--outfile',
            dest='outfile',
            type=str,
            help='(optional) data file path for get/register, output to the file if specified'
        )
    if (action.destroy_all_in_vault):
        parser.add_argument(
            '--vault_path',
            dest='vault_path',
            type=str,
            help='vault path for destroy'
        )

    if (len(input_arg) == 0 and
        not action.locate and
        not action.query):
        input_arg = ['-h']

    opts, left = parser.parse_known_args(input_arg)
    sys.argv = sys.argv[:1]+left

    return opts

def read_json(file):
    if exists(file):
        try:
            with open(file, 'r') as f:
                obj = json.load(f)
        except Exception:
            raise Exception("Load {0} failed.".format(file))
        return obj
    else:
        Path(file).touch()
        return {}

def write_json(obj, file):
    try:
        with open(file, 'w') as f:
            json.dump(obj, f)
    except Exception:
        raise Exception("Write {0} failed.".format(file))

def getVolumeInfo():
    spaceMappingTree = xmlET.parse('/run/space/space_mapping.xml')
    spaceMappingRoot = spaceMappingTree.getroot()
    vols = []

    webapi_ret = subprocess.Popen("synowebapi --exec api=SYNO.Core.Storage.Volume method=list outfile=apitest limit=-1 offset=0 location=\"internal\" option=\"include_cold_storage\"", shell=True, stdout=subprocess.PIPE).stdout.read()
    webapi_ret_json = json.loads(webapi_ret)

    for volume in spaceMappingRoot.iter('volume'):
        new_vol = {}
        new_vol['path'] = volume.attrib['path']
        new_vol['uuid'] = volume.attrib['uuid']
        new_vol['dev_path'] = volume.attrib['dev_path']
        new_vol['luks_uuid'] = subprocess.Popen(["/usr/sbin/cryptsetup", "luksUUID", new_vol['dev_path']], stdout=subprocess.PIPE).stdout.read().strip().decode("utf-8")
        for volinfo in webapi_ret_json['data']['volumes']:
            if new_vol['path'] == volinfo['volume_path']:
                new_vol['display_name'] = volinfo['display_name']
                break;
            else:
                new_vol['display_name'] = ""
        vols.append(new_vol)
    return vols

def acquire_lock():
    wait_count = 0
    locked = True

    if not exists(SZF_SYNOKMIP_LOCK):
        Path(SZF_SYNOKMIP_LOCK).touch()

    lock_file = open(SZF_SYNOKMIP_LOCK, 'w')

    while locked:
        try:
            fcntl.lockf(lock_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
            locked = False
        except IOError:
            if wait_count > 100:
                raise Exception("Get lock timeout.")
            time.sleep(0.1)
            wait_count += 1
    return True

def release_lock():
    if lock_file is not None:
        fcntl.lockf(lock_file, fcntl.LOCK_UN)
        lock_file.close()

def query_server_hostname(logger):
    ret = {"hostname": "", "is_synology_server": False}
    vendor_id = query.query(logger, 'VENDOR_ID')
    if vendor_id.startswith('Synology KMIP Server'):
        ret['hostname'] = vendor_id.split(':')[1].strip()
        ret['is_synology_server'] = True
        ret['vendor'] = 'Synology'
    else:
        ret['hostname'] = vendor_id
        ret['is_synology_server'] = False
        ret['vendor'] = vendor_id
    return ret

def check_is_synology_server(logger):
    return query_server_hostname(logger)['is_synology_server']

def get_policy_name_for_reg_operation(logger):
    if check_is_synology_server(logger) is True:
        return 'synology'
    else:
        return 'default'

def get_random_string(length):
    return ''.join(random.choice(string.ascii_letters) for i in range(length))

def get_key_name_for_reg_operation(logger, key_name):
    if check_is_synology_server(logger) is True:
        return key_name
    else:
        seed_str = get_random_string(1024)
        return hashlib.sha512(seed_str.encode()).hexdigest()

def check_same_name_key(logger, key_name):
    if check_is_synology_server(logger) is not True:
        uuids = locate.locate(logger, key_name)

        if len(uuids) > 0:
            for id in uuids:
                seed_str = get_random_string(1024)
                name_postfix = hashlib.sha512(seed_str.encode()).hexdigest()[:8]
                modify_name.modify_name(logger, name=key_name + '-deprecated-' + name_postfix, uuid=id)

def main():
    action_arg = parse_action_argument(sys.argv[1:])
    arg = parse_secondary_argument(sys.argv[1:], action_arg)
    logger = utils.build_console_logger(logging.INFO)

    acquire_lock()
    if action_arg.reg_opaque_obj:
        with open(arg.infile, 'rb') as f:
            data = f.read()
        policy_name = get_policy_name_for_reg_operation(logger)

        check_same_name_key(logger, arg.key_name)
        key_name = get_key_name_for_reg_operation(logger, arg.key_name)
        uuid = register_opaque_object.register_opaque_object(logger, data, key_name, policy_name)

        if isinstance(arg.outfile, str):
            with open(arg.outfile, 'w') as f:
                f.write(uuid)
                logger.info("uuid write to {0}".format(arg.outfile))

    if action_arg.reg_sec_data:
        with open(arg.infile, 'rb') as f:
            data = f.read()
        policy_name = get_policy_name_for_reg_operation(logger)

        check_same_name_key(logger, arg.key_name)
        key_name = get_key_name_for_reg_operation(logger, arg.key_name)
        uuid = register_secret_data.register_secret_data(logger, data, key_name, policy_name)
        if key_name != arg.key_name:
            modify_name.modify_name(logger, name=arg.key_name, uuid=uuid)

        if isinstance(arg.outfile, str):
            with open(arg.outfile, 'w') as f:
                f.write(uuid)
                logger.info("uuid write to {0}".format(arg.outfile))

    elif action_arg.get:
        data = get.get(logger, arg.uuid)
        if isinstance(arg.outfile, str):
            with open(arg.outfile, 'wb') as f:
                f.write(data.value)
                logger.info("data write to {0}".format(arg.outfile))
        
    elif action_arg.destroy:
        destroy.destroy(logger, arg.uuid)

    elif action_arg.deactivate:
        deactivate.deactivate(logger, arg.uuid)

    elif action_arg.activate:
        activate.activate(logger, arg.uuid)
    
    elif action_arg.get_attributes:
        get_attributes.get_attributes(logger, arg.uuid)

    elif action_arg.modify_name:
        modify_name.modify_name(logger, name=arg.key_name, uuid=arg.uuid)

    elif action_arg.locate:
        locate.locate(logger, arg.key_name)

    elif action_arg.query:
        query.query(logger)

    elif (action_arg.get_with_name or
          action_arg.activate_with_name or
          action_arg.deactivate_with_name or
          action_arg.destroy_with_name):
        if not isinstance(arg.key_name, str):
            logger.info("key name not provided")
            sys.exit(-1)
        uuid = locate.locate(logger, arg.key_name)
        if len(uuid) == 0:
            logger.info("cannot find key with name '{0}'".format(arg.key_name))
            sys.exit(-1)
        if len(uuid) > 1:
            logger.warning("multiple key with name '{0}' found, action on the latest result".format(arg.key_name))

        if action_arg.get_with_name:
            data = get.get(logger, uuid[0])
            if isinstance(arg.outfile, str):
                with open(arg.outfile, 'wb') as f:
                    f.write(data.value)
                    logger.info("data write to {0}".format(arg.outfile))
            rec_obj = read_json(SZF_KMIP_RETRIVE_REC)
            time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            rec_obj[arg.key_name] = time
            write_json(rec_obj, SZF_KMIP_RETRIVE_REC)

        elif action_arg.activate_with_name:
            activate.activate(logger, uuid[0])

        elif action_arg.deactivate_with_name:
            deactivate.deactivate(logger, uuid[0])

        elif action_arg.destroy_with_name:
            destroy.destroy(logger, uuid[0])

        elif action_arg.destroy_all_in_vault:
            files = listdir(arg.vault_path)
            for f in files:
                fullpath = join(arg.vault_path, f)
                if isfile(fullpath) and f.endswith(".wkey.shadow"):
                    key_name = f.split('.')[0]
                    uuid = locate.locate(logger, key_name)
                    if len(uuid) == 0:
                        logger.info("cannot find key with name '{0}'".format(key_name))
                        sys.exit(-1)
                    if len(uuid) > 1:
                        logger.warning("multiple key with name '{0}' found, action on the latest result".format(key_name))
                    destroy.destroy(logger, uuid[0])

    elif action_arg.get_key_attr_list:
        uuids = locate.locate(logger, None)
        output_data = []
        volume_info = getVolumeInfo()
        for uid in uuids:
            data = get_attributes.get_attributes(logger, uid)
            dict = {}
            rec_obj = read_json(SZF_KMIP_RETRIVE_REC)
            for attr in data:
                if "Unique Identifier" == str(attr.attribute_name):
                    dict['uuid'] = str(attr.attribute_value)
                elif "Name" == str(attr.attribute_name):
                    dict['name'] = str(attr.attribute_value)
                elif "Object Type" == str(attr.attribute_name):
                    dict['obj_type'] = str(attr.attribute_value)
                elif "Operation Policy Name" == str(attr.attribute_name):
                    dict['policy_name'] = str(attr.attribute_value)
                elif "Cryptographic Usage Mask" == str(attr.attribute_name):
                    dict['crypto_usage_mask'] = str(attr.attribute_value)
                elif "State" == str(attr.attribute_name):
                    dict['state'] = str(attr.attribute_value)
                elif "Initial Date" == str(attr.attribute_name):
                    dict['init_date'] = str(attr.attribute_value)
            if dict['name'] in rec_obj:
                dict['last_retrive'] = rec_obj[dict['name']]
            else:
                dict['last_retrive'] = ''

            if 0 < len(volume_info):
                for i in range(len(volume_info)):
                    if dict['name'] == volume_info[i]['luks_uuid']:
                        dict['vol_path'] = volume_info[i]['path']
                        dict['luks_uuid'] = volume_info[i]['luks_uuid']
                        dict['display_name'] = volume_info[i]['display_name']
                        break;
                    else:
                        dict['vol_path'] = ""
                        dict['luks_uuid'] = ""
                        dict['display_name'] = ""

            init_time = datetime.datetime.strptime(dict['init_date'], "%a %b %d %H:%M:%S %Y")
            dict['init_date'] = init_time.strftime("%Y-%m-%d %H:%M:%S")
            output_data.append(dict)
        if isinstance(arg.outfile, str):
            with open(arg.outfile, 'w') as f:
                f.write(json.dumps(output_data))
                logger.info("data write to {0}".format(arg.outfile))

    elif action_arg.get_server_hostname:
        query_ret = query_server_hostname(logger)
        if isinstance(arg.outfile, str):
            with open(arg.outfile, 'w') as f:
                f.write("server_hostname=\"{0}\"\n".format(query_ret['hostname']))
                f.write("vendor=\"{0}\"\n".format(query_ret['vendor']))
                logger.info("data write to {0}".format(arg.outfile))
    else:
        logger.info("invalid action")

    release_lock()

if __name__ == '__main__':
    Path("/run/pykmip").mkdir(parents=True, exist_ok=True)
    try:
        main()
    except Exception:
        with open("/etc/pykmip/client_conn_record", 'w') as f:
            f.write("status=\"{0}\"\n".format("failed"))
            f.write("time=\"{0}\"\n".format(time.ctime()))
        sys.exit(1)
    else:
        with open("/etc/pykmip/client_conn_record", 'w') as f:
            f.write("status=\"{0}\"\n".format("success"))
            f.write("time=\"{0}\"\n".format(time.ctime()))
        sys.exit(0)