mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-22 01:31:55 +00:00
BFD: Authentication
Implement BFD authentication (part of RFC 5880). Supports plaintext passwords and cryptographic MD5 / SHA-1 authentication. Based on former commit from Pavel Tvrdik
This commit is contained in:
parent
29239ba2bb
commit
e03dc6a984
@ -672,7 +672,7 @@ agreement").
|
||||
authentication is enabled, authentication can be enabled by separate,
|
||||
protocol-dependent <cf/authentication/ option.
|
||||
|
||||
This option is allowed in OSPF and RIP protocols. BGP has also
|
||||
This option is allowed in BFD, OSPF and RIP protocols. BGP has also
|
||||
<cf/password/ option, but it is slightly different and described
|
||||
separately.
|
||||
Default: none.
|
||||
@ -1637,6 +1637,19 @@ protocol bfd [<name>] {
|
||||
idle tx interval <time>;
|
||||
multiplier <num>;
|
||||
passive <switch>;
|
||||
authentication none;
|
||||
authentication simple;
|
||||
authentication [meticulous] keyed md5|sha1;
|
||||
password "<text>";
|
||||
password "<text>" {
|
||||
id <num>;
|
||||
generate from "<date>";
|
||||
generate to "<date>";
|
||||
accept from "<date>";
|
||||
accept to "<date>";
|
||||
from "<date>";
|
||||
to "<date>";
|
||||
};
|
||||
};
|
||||
multihop {
|
||||
interval <time>;
|
||||
@ -1720,6 +1733,32 @@ protocol bfd [<name>] {
|
||||
sending control packets to the other side. This option allows to enable
|
||||
passive mode, which means that the router does not send BFD packets
|
||||
until it has received one from the other side. Default: disabled.
|
||||
|
||||
<tag>authentication none</tag>
|
||||
No passwords are sent in BFD packets. This is the default value.
|
||||
|
||||
<tag>authentication simple</tag>
|
||||
Every packet carries 16 bytes of password. Received packets lacking this
|
||||
password are ignored. This authentication mechanism is very weak.
|
||||
|
||||
<tag>authentication [meticulous] keyed md5|sha1</tag>
|
||||
An authentication code is appended to each packet. The cryptographic
|
||||
algorithm is keyed MD5 or keyed SHA-1. Note that the algorithm is common
|
||||
for all keys (on one interface), in contrast to OSPF or RIP, where it
|
||||
is a per-key option. Passwords (keys) are not sent open via network.
|
||||
|
||||
The <cf/meticulous/ variant means that cryptographic sequence numbers
|
||||
are increased for each sent packet, while in the basic variant they are
|
||||
increased about once per second. Generally, the <cf/meticulous/ variant
|
||||
offers better resistance to replay attacks but may require more
|
||||
computation.
|
||||
|
||||
<tag>password "<M>text</M>"</tag>
|
||||
Specifies a password used for authentication. See <ref id="dsc-pass"
|
||||
name="password"> common option for detailed description. Note that
|
||||
password option <cf/algorithm/ is not available in BFD protocol. The
|
||||
algorithm is selected by <cf/authentication/ option for all passwords.
|
||||
|
||||
</descrip>
|
||||
|
||||
<sect1>Example
|
||||
|
@ -316,6 +316,7 @@ bfd_session_timeout(struct bfd_session *s)
|
||||
s->rem_min_rx_int = 1;
|
||||
s->rem_demand_mode = 0;
|
||||
s->rem_detect_mult = 0;
|
||||
s->rx_csn_known = 0;
|
||||
|
||||
s->poll_active = 0;
|
||||
s->poll_scheduled = 0;
|
||||
@ -429,6 +430,7 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
|
||||
s->rem_min_rx_int = 1;
|
||||
s->detect_mult = ifa->cf->multiplier;
|
||||
s->passive = ifa->cf->passive;
|
||||
s->tx_csn = random_u32();
|
||||
|
||||
s->tx_timer = tm2_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
|
||||
s->hold_timer = tm2_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "nest/iface.h"
|
||||
#include "nest/protocol.h"
|
||||
#include "nest/route.h"
|
||||
#include "nest/password.h"
|
||||
#include "conf/conf.h"
|
||||
#include "lib/hash.h"
|
||||
#include "lib/resource.h"
|
||||
@ -52,6 +53,8 @@ struct bfd_iface_config
|
||||
u32 idle_tx_int;
|
||||
u8 multiplier;
|
||||
u8 passive;
|
||||
u8 auth_type; /* Authentication type (BFD_AUTH_*) */
|
||||
list *passwords; /* Passwords for authentication */
|
||||
};
|
||||
|
||||
struct bfd_neighbor
|
||||
@ -141,6 +144,11 @@ struct bfd_session
|
||||
list request_list; /* List of client requests (struct bfd_request) */
|
||||
bird_clock_t last_state_change; /* Time of last state change */
|
||||
u8 notify_running; /* 1 if notify hooks are running */
|
||||
|
||||
u8 rx_csn_known; /* Received crypto sequence number is known */
|
||||
u32 rx_csn; /* Last received crypto sequence number */
|
||||
u32 tx_csn; /* Last transmitted crypto sequence number */
|
||||
u32 tx_csn_time; /* Timestamp of last tx_csn change */
|
||||
};
|
||||
|
||||
|
||||
@ -172,6 +180,15 @@ extern const char *bfd_state_names[];
|
||||
#define BFD_FLAG_DEMAND (1 << 1)
|
||||
#define BFD_FLAG_MULTIPOINT (1 << 0)
|
||||
|
||||
#define BFD_AUTH_NONE 0
|
||||
#define BFD_AUTH_SIMPLE 1
|
||||
#define BFD_AUTH_KEYED_MD5 2
|
||||
#define BFD_AUTH_METICULOUS_KEYED_MD5 3
|
||||
#define BFD_AUTH_KEYED_SHA1 4
|
||||
#define BFD_AUTH_METICULOUS_KEYED_SHA1 5
|
||||
|
||||
extern const u8 bfd_auth_type_to_hash_alg[];
|
||||
|
||||
|
||||
static inline void bfd_lock_sessions(struct bfd_proto *p) { pthread_spin_lock(&p->lock); }
|
||||
static inline void bfd_unlock_sessions(struct bfd_proto *p) { pthread_spin_unlock(&p->lock); }
|
||||
|
@ -22,11 +22,12 @@ extern struct bfd_config *bfd_cf;
|
||||
CF_DECLS
|
||||
|
||||
CF_KEYWORDS(BFD, MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE,
|
||||
INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL)
|
||||
INTERFACE, MULTIHOP, NEIGHBOR, DEV, LOCAL, AUTHENTICATION,
|
||||
NONE, SIMPLE, METICULOUS, KEYED, MD5, SHA1)
|
||||
|
||||
%type <iface> bfd_neigh_iface
|
||||
%type <a> bfd_neigh_local
|
||||
%type <i> bfd_neigh_multihop
|
||||
%type <i> bfd_neigh_multihop bfd_auth_type
|
||||
|
||||
CF_GRAMMAR
|
||||
|
||||
@ -62,12 +63,35 @@ bfd_proto:
|
||||
bfd_iface_start:
|
||||
{
|
||||
this_ipatt = cfg_allocz(sizeof(struct bfd_iface_config));
|
||||
add_tail(&BFD_CFG->patt_list, NODE this_ipatt);
|
||||
init_list(&this_ipatt->ipn_list);
|
||||
|
||||
BFD_IFACE->min_rx_int = BFD_DEFAULT_MIN_RX_INT;
|
||||
BFD_IFACE->min_tx_int = BFD_DEFAULT_MIN_TX_INT;
|
||||
BFD_IFACE->idle_tx_int = BFD_DEFAULT_IDLE_TX_INT;
|
||||
BFD_IFACE->multiplier = BFD_DEFAULT_MULTIPLIER;
|
||||
|
||||
reset_passwords();
|
||||
};
|
||||
|
||||
bfd_iface_finish:
|
||||
{
|
||||
BFD_IFACE->passwords = get_passwords();
|
||||
|
||||
if (!BFD_IFACE->auth_type != !BFD_IFACE->passwords)
|
||||
log(L_WARN "Authentication and password options should be used together");
|
||||
|
||||
if (BFD_IFACE->passwords)
|
||||
{
|
||||
struct password_item *pass;
|
||||
WALK_LIST(pass, *BFD_IFACE->passwords)
|
||||
{
|
||||
if (pass->alg)
|
||||
cf_error("Password algorithm option not available in BFD protocol");
|
||||
|
||||
pass->alg = bfd_auth_type_to_hash_alg[BFD_IFACE->auth_type];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bfd_iface_item:
|
||||
@ -77,6 +101,17 @@ bfd_iface_item:
|
||||
| IDLE TX INTERVAL expr_us { BFD_IFACE->idle_tx_int = $4; }
|
||||
| MULTIPLIER expr { BFD_IFACE->multiplier = $2; }
|
||||
| PASSIVE bool { BFD_IFACE->passive = $2; }
|
||||
| AUTHENTICATION bfd_auth_type { BFD_IFACE->auth_type = $2; }
|
||||
| password_list {}
|
||||
;
|
||||
|
||||
bfd_auth_type:
|
||||
NONE { $$ = BFD_AUTH_NONE; }
|
||||
| SIMPLE { $$ = BFD_AUTH_SIMPLE; }
|
||||
| KEYED MD5 { $$ = BFD_AUTH_KEYED_MD5; }
|
||||
| KEYED SHA1 { $$ = BFD_AUTH_KEYED_SHA1; }
|
||||
| METICULOUS KEYED MD5 { $$ = BFD_AUTH_METICULOUS_KEYED_MD5; }
|
||||
| METICULOUS KEYED SHA1 { $$ = BFD_AUTH_METICULOUS_KEYED_SHA1; }
|
||||
;
|
||||
|
||||
bfd_iface_opts:
|
||||
@ -89,10 +124,11 @@ bfd_iface_opt_list:
|
||||
| '{' bfd_iface_opts '}'
|
||||
;
|
||||
|
||||
bfd_iface: bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list
|
||||
{ add_tail(&BFD_CFG->patt_list, NODE this_ipatt); };
|
||||
bfd_iface:
|
||||
bfd_iface_start iface_patt_list_nopx bfd_iface_opt_list bfd_iface_finish;
|
||||
|
||||
bfd_multihop: bfd_iface_start bfd_iface_opt_list
|
||||
bfd_multihop:
|
||||
bfd_iface_start bfd_iface_opt_list bfd_iface_finish
|
||||
{ BFD_CFG->multihop = BFD_IFACE; };
|
||||
|
||||
|
||||
|
@ -5,24 +5,60 @@
|
||||
*/
|
||||
|
||||
#include "bfd.h"
|
||||
#include "lib/mac.h"
|
||||
|
||||
|
||||
struct bfd_ctl_packet
|
||||
{
|
||||
u8 vdiag; /* version and diagnostic */
|
||||
u8 flags; /* state and flags */
|
||||
u8 vdiag; /* Version and diagnostic */
|
||||
u8 flags; /* State and flags */
|
||||
u8 detect_mult;
|
||||
u8 length;
|
||||
u32 snd_id; /* sender ID, aka 'my discriminator' */
|
||||
u32 rcv_id; /* receiver ID, aka 'your discriminator' */
|
||||
u8 length; /* Whole packet length */
|
||||
u32 snd_id; /* Sender ID, aka 'my discriminator' */
|
||||
u32 rcv_id; /* Receiver ID, aka 'your discriminator' */
|
||||
u32 des_min_tx_int;
|
||||
u32 req_min_rx_int;
|
||||
u32 req_min_echo_rx_int;
|
||||
};
|
||||
|
||||
struct bfd_auth
|
||||
{
|
||||
u8 type; /* Authentication type (BFD_AUTH_*) */
|
||||
u8 length; /* Authentication section length */
|
||||
};
|
||||
|
||||
struct bfd_simple_auth
|
||||
{
|
||||
u8 type; /* BFD_AUTH_SIMPLE */
|
||||
u8 length; /* Length of bfd_simple_auth + pasword length */
|
||||
u8 key_id; /* Key ID */
|
||||
byte password[0]; /* Password itself, variable length */
|
||||
};
|
||||
|
||||
#define BFD_MAX_PASSWORD_LENGTH 16
|
||||
|
||||
struct bfd_crypto_auth
|
||||
{
|
||||
u8 type; /* BFD_AUTH_*_MD5 or BFD_AUTH_*_SHA1 */
|
||||
u8 length; /* Length of bfd_crypto_auth + hash length */
|
||||
u8 key_id; /* Key ID */
|
||||
u8 zero; /* Reserved, zero on transmit */
|
||||
u32 csn; /* Cryptographic sequence number */
|
||||
byte data[0]; /* Authentication key/hash, length 16 or 20 */
|
||||
};
|
||||
|
||||
#define BFD_BASE_LEN sizeof(struct bfd_ctl_packet)
|
||||
#define BFD_MAX_LEN 64
|
||||
|
||||
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
|
||||
|
||||
#define LOG_PKT(msg, args...) \
|
||||
log(L_REMOTE "%s: " msg, p->p.name, args)
|
||||
|
||||
#define LOG_PKT_AUTH(msg, args...) \
|
||||
log(L_AUTH "%s: " msg, p->p.name, args)
|
||||
|
||||
|
||||
static inline u8 bfd_pack_vdiag(u8 version, u8 diag)
|
||||
{ return (version << 5) | diag; }
|
||||
|
||||
@ -59,6 +95,189 @@ bfd_format_flags(u8 flags, char *buf)
|
||||
return buf;
|
||||
}
|
||||
|
||||
const u8 bfd_auth_type_to_hash_alg[] = {
|
||||
[BFD_AUTH_NONE] = ALG_UNDEFINED,
|
||||
[BFD_AUTH_SIMPLE] = ALG_UNDEFINED,
|
||||
[BFD_AUTH_KEYED_MD5] = ALG_MD5,
|
||||
[BFD_AUTH_METICULOUS_KEYED_MD5] = ALG_MD5,
|
||||
[BFD_AUTH_KEYED_SHA1] = ALG_SHA1,
|
||||
[BFD_AUTH_METICULOUS_KEYED_SHA1] = ALG_SHA1,
|
||||
};
|
||||
|
||||
|
||||
/* Fill authentication section and modifies final length in control section packet */
|
||||
static void
|
||||
bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
|
||||
{
|
||||
struct bfd_iface_config *cf = s->ifa->cf;
|
||||
struct password_item *pass = password_find(cf->passwords, 0);
|
||||
uint meticulous = 0;
|
||||
|
||||
if (!pass)
|
||||
{
|
||||
/* FIXME: This should not happen */
|
||||
log(L_ERR "%s: No suitable password found for authentication", p->p.name);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cf->auth_type)
|
||||
{
|
||||
case BFD_AUTH_SIMPLE:
|
||||
{
|
||||
struct bfd_simple_auth *auth = (void *) (pkt + 1);
|
||||
uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
|
||||
|
||||
auth->type = BFD_AUTH_SIMPLE;
|
||||
auth->length = sizeof(struct bfd_simple_auth) + pass_len;
|
||||
auth->key_id = pass->id;
|
||||
|
||||
pkt->flags |= BFD_FLAG_AP;
|
||||
pkt->length += auth->length;
|
||||
|
||||
memcpy(auth->password, pass->password, pass_len);
|
||||
return;
|
||||
}
|
||||
|
||||
case BFD_AUTH_METICULOUS_KEYED_MD5:
|
||||
case BFD_AUTH_METICULOUS_KEYED_SHA1:
|
||||
meticulous = 1;
|
||||
|
||||
case BFD_AUTH_KEYED_MD5:
|
||||
case BFD_AUTH_KEYED_SHA1:
|
||||
{
|
||||
struct bfd_crypto_auth *auth = (void *) (pkt + 1);
|
||||
uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
|
||||
uint hash_len = mac_type_length(pass->alg);
|
||||
|
||||
/* Increase CSN about one time per second */
|
||||
u32 new_time = (u64) current_time() >> 20;
|
||||
if ((new_time != s->tx_csn_time) || meticulous)
|
||||
{
|
||||
s->tx_csn++;
|
||||
s->tx_csn_time = new_time;
|
||||
}
|
||||
|
||||
DBG("[%I] CSN: %u\n", s->addr, s->last_tx_csn);
|
||||
|
||||
auth->type = cf->auth_type;
|
||||
auth->length = sizeof(struct bfd_crypto_auth) + hash_len;
|
||||
auth->key_id = pass->id;
|
||||
auth->zero = 0;
|
||||
auth->csn = htonl(s->tx_csn);
|
||||
|
||||
pkt->flags |= BFD_FLAG_AP;
|
||||
pkt->length += auth->length;
|
||||
|
||||
strncpy(auth->data, pass->password, hash_len);
|
||||
mac_fill(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth->data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
bfd_check_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt)
|
||||
{
|
||||
struct bfd_iface_config *cf = s->ifa->cf;
|
||||
const char *err_dsc = NULL;
|
||||
uint err_val = 0;
|
||||
uint auth_type = 0;
|
||||
uint meticulous = 0;
|
||||
|
||||
if (pkt->flags & BFD_FLAG_AP)
|
||||
{
|
||||
struct bfd_auth *auth = (void *) (pkt + 1);
|
||||
|
||||
if ((pkt->length < (BFD_BASE_LEN + sizeof(struct bfd_auth))) ||
|
||||
(pkt->length < (BFD_BASE_LEN + auth->length)))
|
||||
DROP("packet length mismatch", pkt->length);
|
||||
|
||||
/* Zero is reserved, we use it as BFD_AUTH_NONE internally */
|
||||
if (auth->type == 0)
|
||||
DROP("reserved authentication type", 0);
|
||||
|
||||
auth_type = auth->type;
|
||||
}
|
||||
|
||||
if (auth_type != cf->auth_type)
|
||||
DROP("authentication method mismatch", auth_type);
|
||||
|
||||
switch (auth_type)
|
||||
{
|
||||
case BFD_AUTH_NONE:
|
||||
return 1;
|
||||
|
||||
case BFD_AUTH_SIMPLE:
|
||||
{
|
||||
struct bfd_simple_auth *auth = (void *) (pkt + 1);
|
||||
|
||||
if (auth->length < sizeof(struct bfd_simple_auth))
|
||||
DROP("wrong authentication length", auth->length);
|
||||
|
||||
struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
|
||||
if (!pass)
|
||||
DROP("no suitable password found", auth->key_id);
|
||||
|
||||
uint pass_len = MIN(pass->length, BFD_MAX_PASSWORD_LENGTH);
|
||||
uint auth_len = sizeof(struct bfd_simple_auth) + pass_len;
|
||||
|
||||
if ((auth->length != auth_len) || memcmp(auth->password, pass->password, pass_len))
|
||||
DROP("wrong password", pass->id);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
case BFD_AUTH_METICULOUS_KEYED_MD5:
|
||||
case BFD_AUTH_METICULOUS_KEYED_SHA1:
|
||||
meticulous = 1;
|
||||
|
||||
case BFD_AUTH_KEYED_MD5:
|
||||
case BFD_AUTH_KEYED_SHA1:
|
||||
{
|
||||
struct bfd_crypto_auth *auth = (void *) (pkt + 1);
|
||||
uint hash_alg = bfd_auth_type_to_hash_alg[cf->auth_type];
|
||||
uint hash_len = mac_type_length(hash_alg);
|
||||
|
||||
if (auth->length != (sizeof(struct bfd_crypto_auth) + hash_len))
|
||||
DROP("wrong authentication length", auth->length);
|
||||
|
||||
struct password_item *pass = password_find_by_id(cf->passwords, auth->key_id);
|
||||
if (!pass)
|
||||
DROP("no suitable password found", auth->key_id);
|
||||
|
||||
/* BFD CSNs are in 32-bit circular number space */
|
||||
u32 csn = ntohl(auth->csn);
|
||||
if (s->rx_csn_known &&
|
||||
(((csn - s->rx_csn) > (3 * s->detect_mult)) ||
|
||||
(meticulous && (csn == s->rx_csn))))
|
||||
{
|
||||
/* We want to report both new and old CSN */
|
||||
LOG_PKT_AUTH("Authentication failed for %I - "
|
||||
"wrong sequence number (rcv %u, old %u)",
|
||||
s->addr, csn, s->rx_csn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte *auth_data = alloca(hash_len);
|
||||
memcpy(auth_data, auth->data, hash_len);
|
||||
strncpy(auth->data, pass->password, hash_len);
|
||||
|
||||
if (!mac_verify(hash_alg, NULL, 0, (byte *) pkt, pkt->length, auth_data))
|
||||
DROP("wrong authentication code", pass->id);
|
||||
|
||||
s->rx_csn = csn;
|
||||
s->rx_csn_known = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
drop:
|
||||
LOG_PKT_AUTH("Authentication failed for %I - %s (%u)",
|
||||
s->addr, err_dsc, err_val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
|
||||
{
|
||||
@ -85,6 +304,9 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
|
||||
else if (s->poll_active)
|
||||
pkt->flags |= BFD_FLAG_POLL;
|
||||
|
||||
if (s->ifa->cf->auth_type)
|
||||
bfd_fill_authentication(p, s, pkt);
|
||||
|
||||
if (sk->tbuf != sk->tpos)
|
||||
log(L_WARN "%s: Old packet overwritten in TX buffer", p->p.name);
|
||||
|
||||
@ -94,8 +316,6 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final)
|
||||
sk_send_to(sk, pkt->length, s->addr, sk->dport);
|
||||
}
|
||||
|
||||
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
|
||||
|
||||
static int
|
||||
bfd_rx_hook(sock *sk, uint len)
|
||||
{
|
||||
@ -151,10 +371,9 @@ bfd_rx_hook(sock *sk, uint len)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* FIXME: better authentication handling and message */
|
||||
if (pkt->flags & BFD_FLAG_AP)
|
||||
DROP("authentication not supported", 0);
|
||||
|
||||
/* bfd_check_authentication() has its own error logging */
|
||||
if (!bfd_check_authentication(p, s, pkt))
|
||||
return 1;
|
||||
|
||||
u32 old_tx_int = s->des_min_tx_int;
|
||||
u32 old_rx_int = s->rem_min_rx_int;
|
||||
@ -173,8 +392,8 @@ bfd_rx_hook(sock *sk, uint len)
|
||||
bfd_session_process_ctl(s, pkt->flags, old_tx_int, old_rx_int);
|
||||
return 1;
|
||||
|
||||
drop:
|
||||
log(L_REMOTE "%s: Bad packet from %I - %s (%u)", p->p.name, sk->faddr, err_dsc, err_val);
|
||||
drop:
|
||||
LOG_PKT("Bad packet from %I - %s (%u)", sk->faddr, err_dsc, err_val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user