mirror of
https://github.com/polhenarejos/pico-fido.git
synced 2025-12-19 10:54:42 +08:00
Add support to PIN POLICY URL via VendorConfig.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -40,7 +40,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
|
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
|
||||||
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
|
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
|
||||||
CborCharString minPinLengthRPIDs[32] = { 0 };
|
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
|
||||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||||
CborEncoder encoder;
|
CborEncoder encoder;
|
||||||
//CborEncoder mapEncoder;
|
//CborEncoder mapEncoder;
|
||||||
@@ -79,6 +79,9 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
else if (subpara == 0x03) {
|
else if (subpara == 0x03) {
|
||||||
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
|
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
|
||||||
}
|
}
|
||||||
|
else if (subpara == 0x04) {
|
||||||
|
CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (subcommand == 0x03) { // Extensions
|
else if (subcommand == 0x03) { // Extensions
|
||||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||||
@@ -224,6 +227,21 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
low_flash_available();
|
low_flash_available();
|
||||||
}
|
}
|
||||||
|
else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
|
||||||
|
file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
|
||||||
|
if (ef_pin_policy) {
|
||||||
|
uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
|
||||||
|
if (val) {
|
||||||
|
// Not ready yet for integer param
|
||||||
|
// val[0] = (uint8_t)(vendorParamInt >> 8);
|
||||||
|
// val[1] = (uint8_t)(vendorParamInt & 0xFF);
|
||||||
|
memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
|
||||||
|
file_put_data(ef_pin_policy, val, 2 + vendorParamByteString.len);
|
||||||
|
free(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
low_flash_available();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||||
}
|
}
|
||||||
@@ -275,6 +293,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
err:
|
err:
|
||||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||||
CBOR_FREE_BYTE_STRING(vendorParamByteString);
|
CBOR_FREE_BYTE_STRING(vendorParamByteString);
|
||||||
|
CBOR_FREE_BYTE_STRING(vendorParamTextString);
|
||||||
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ int cbor_get_info() {
|
|||||||
#else
|
#else
|
||||||
lfields++;
|
lfields++;
|
||||||
#endif
|
#endif
|
||||||
|
file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||||
|
if (file_has_data(ef_pin_policy)) {
|
||||||
|
lfields += 2;
|
||||||
|
}
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||||
@@ -164,7 +168,7 @@ int cbor_get_info() {
|
|||||||
if (phy_data.vid != 0x1050) {
|
if (phy_data.vid != 0x1050) {
|
||||||
#endif
|
#endif
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||||
uint8_t enabled_cmds = 3;
|
uint8_t enabled_cmds = 4;
|
||||||
#ifndef ENABLE_EMULATION
|
#ifndef ENABLE_EMULATION
|
||||||
enabled_cmds += 4;
|
enabled_cmds += 4;
|
||||||
#endif
|
#endif
|
||||||
@@ -172,6 +176,7 @@ int cbor_get_info() {
|
|||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
|
||||||
#ifndef ENABLE_EMULATION
|
#ifndef ENABLE_EMULATION
|
||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
|
||||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
|
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
|
||||||
@@ -181,6 +186,13 @@ int cbor_get_info() {
|
|||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||||
#ifndef ENABLE_EMULATION
|
#ifndef ENABLE_EMULATION
|
||||||
}
|
}
|
||||||
|
if (file_has_data(ef_pin_policy)) {
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
|
||||||
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
err:
|
err:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
int64_t kty = 2, hmac_alg = 0, crv = 0;
|
int64_t kty = 2, hmac_alg = 0, crv = 0;
|
||||||
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
||||||
bool hmac_secret_mc = false;
|
bool hmac_secret_mc = false;
|
||||||
|
const bool *pin_complexity_policy = NULL;
|
||||||
uint8_t *aut_data = NULL;
|
uint8_t *aut_data = NULL;
|
||||||
size_t resp_size = 0;
|
size_t resp_size = 0;
|
||||||
CredExtensions extensions = { 0 };
|
CredExtensions extensions = { 0 };
|
||||||
@@ -162,6 +163,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
||||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
||||||
|
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
|
||||||
|
|
||||||
CBOR_ADVANCE(2);
|
CBOR_ADVANCE(2);
|
||||||
}
|
}
|
||||||
CBOR_PARSE_MAP_END(_f1, 2);
|
CBOR_PARSE_MAP_END(_f1, 2);
|
||||||
@@ -440,6 +443,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
if (hmac_secret_mc) {
|
if (hmac_secret_mc) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
|
if (pin_complexity_policy == ptrue) {
|
||||||
|
l++;
|
||||||
|
}
|
||||||
if (l > 0) {
|
if (l > 0) {
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||||
if (extensions.credBlob.present == true) {
|
if (extensions.credBlob.present == true) {
|
||||||
@@ -451,12 +457,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||||
}
|
}
|
||||||
if (extensions.hmac_secret == ptrue) {
|
if (extensions.hmac_secret == ptrue) {
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
}
|
}
|
||||||
if (minPinLen > 0) {
|
if (minPinLen > 0) {
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||||
}
|
}
|
||||||
@@ -511,6 +515,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
||||||
}
|
}
|
||||||
|
if (pin_complexity_policy == ptrue) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
|
||||||
|
file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
|
||||||
|
}
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ typedef struct {
|
|||||||
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
||||||
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
||||||
#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
|
#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
|
||||||
|
#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897
|
||||||
#ifndef ENABLE_EMULATION
|
#ifndef ENABLE_EMULATION
|
||||||
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
|
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
|
||||||
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
|
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#define EF_AUTHTOKEN 0x1090
|
#define EF_AUTHTOKEN 0x1090
|
||||||
#define EF_PAUTHTOKEN 0x1091
|
#define EF_PAUTHTOKEN 0x1091
|
||||||
#define EF_MINPINLEN 0x1100
|
#define EF_MINPINLEN 0x1100
|
||||||
|
#define EF_PIN_COMPLEXITY_POLICY 0x1102
|
||||||
#define EF_DEV_CONF 0x1122
|
#define EF_DEV_CONF 0x1122
|
||||||
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
||||||
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import argparse
|
|||||||
import platform
|
import platform
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Mapping, Any, Optional, Callable
|
from typing import List, Mapping, Any, Optional, Callable
|
||||||
import struct
|
import struct
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import json
|
import json
|
||||||
@@ -77,6 +77,7 @@ class VendorConfig(Config):
|
|||||||
VENDOR_COMMAND_ID = 0x01
|
VENDOR_COMMAND_ID = 0x01
|
||||||
VENDOR_PARAM_BYTESTRING = 0x02
|
VENDOR_PARAM_BYTESTRING = 0x02
|
||||||
VENDOR_PARAM_INT = 0x03
|
VENDOR_PARAM_INT = 0x03
|
||||||
|
VENDOR_PARAM_TEXTSTRING = 0x04
|
||||||
|
|
||||||
class CMD(IntEnum):
|
class CMD(IntEnum):
|
||||||
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
|
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
|
||||||
@@ -86,6 +87,7 @@ class VendorConfig(Config):
|
|||||||
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
|
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
|
||||||
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
|
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
|
||||||
CONFIG_PHY_OPTS = 0x969f3b09eceb805f
|
CONFIG_PHY_OPTS = 0x969f3b09eceb805f
|
||||||
|
CONFIG_PIN_POLICY = 0x6c07d70fe96c3897
|
||||||
|
|
||||||
class RESP(IntEnum):
|
class RESP(IntEnum):
|
||||||
KEY_AGREEMENT = 0x01
|
KEY_AGREEMENT = 0x01
|
||||||
@@ -155,6 +157,20 @@ class VendorConfig(Config):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def pin_policy(self, url: bytes|str = None, policy: int = None):
|
||||||
|
if (url is not None or policy is not None):
|
||||||
|
params = { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PIN_POLICY }
|
||||||
|
if (url is not None):
|
||||||
|
if (isinstance(url, str)):
|
||||||
|
url = url.encode()
|
||||||
|
params[VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING] = url
|
||||||
|
if (policy is not None):
|
||||||
|
params[VendorConfig.PARAM.VENDOR_PARAM_INT] = policy
|
||||||
|
self._call(
|
||||||
|
Config.CMD.VENDOR_PROTOTYPE,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
class Ctap2Vendor(Ctap2):
|
class Ctap2Vendor(Ctap2):
|
||||||
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
|
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
|
||||||
super().__init__(device=device, strict_cbor=strict_cbor)
|
super().__init__(device=device, strict_cbor=strict_cbor)
|
||||||
@@ -486,6 +502,18 @@ class Vendor:
|
|||||||
def enable_enterprise_attestation(self):
|
def enable_enterprise_attestation(self):
|
||||||
self.vcfg.enable_enterprise_attestation()
|
self.vcfg.enable_enterprise_attestation()
|
||||||
|
|
||||||
|
def set_min_pin_length(self, length, rpids: list[str] = None, url=None):
|
||||||
|
params = {
|
||||||
|
Config.PARAM.NEW_MIN_PIN_LENGTH: length,
|
||||||
|
Config.PARAM.MIN_PIN_LENGTH_RPIDS: rpids if rpids else None,
|
||||||
|
}
|
||||||
|
self.vcfg.set_min_pin_length(
|
||||||
|
min_pin_length=length,
|
||||||
|
rp_ids=rpids if rpids else None,
|
||||||
|
)
|
||||||
|
self.vcfg.pin_policy(url=url.encode() if url else None)
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparser = parser.add_subparsers(title="commands", dest="command")
|
subparser = parser.add_subparsers(title="commands", dest="command")
|
||||||
@@ -516,6 +544,11 @@ def parse_args():
|
|||||||
|
|
||||||
parser_mem = subparser.add_parser('memory', help='Get current memory usage.')
|
parser_mem = subparser.add_parser('memory', help='Get current memory usage.')
|
||||||
|
|
||||||
|
parser_pin_policy = subparser.add_parser('pin_policy', help='Manage PIN policy.')
|
||||||
|
parser_pin_policy.add_argument('length', type=int, help='Minimum PIN length (4-63).')
|
||||||
|
parser_pin_policy.add_argument('--rpids', help='Comma separated list of Relying Party IDs that have authorization to receive minimum PIN length.')
|
||||||
|
parser_pin_policy.add_argument('--url', help='URL where the user can consult PIN policy.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -584,6 +617,14 @@ def memory(vdr: Vendor, args):
|
|||||||
print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
|
print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
|
||||||
print(f'\tFiles: {mem["files"]}')
|
print(f'\tFiles: {mem["files"]}')
|
||||||
|
|
||||||
|
|
||||||
|
def pin_policy(vdr: Vendor, args):
|
||||||
|
rpids = None
|
||||||
|
if (args.rpids):
|
||||||
|
rpids = args.rpids.split(',')
|
||||||
|
vdr.set_min_pin_length(args.length, rpids, args.url)
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
print('Pico Fido Tool v1.10')
|
print('Pico Fido Tool v1.10')
|
||||||
print('Author: Pol Henarejos')
|
print('Author: Pol Henarejos')
|
||||||
@@ -607,6 +648,9 @@ def main(args):
|
|||||||
phy(vdr, args)
|
phy(vdr, args)
|
||||||
elif (args.command == 'memory'):
|
elif (args.command == 'memory'):
|
||||||
memory(vdr, args)
|
memory(vdr, args)
|
||||||
|
elif (args.command == 'pin_policy'):
|
||||||
|
pin_policy(vdr, args)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|||||||
Reference in New Issue
Block a user