mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-02 19:16:15 +00:00
Revert "res_pjsip_endpoint_identifier_ip: Add endpoint identifier transport address."
This reverts PR #602 Resolves: #GHSA-qqxj-v78h-hrf9
This commit is contained in:
@@ -197,47 +197,6 @@
|
|||||||
;tcp_keepalive_interval_time=10 ; The time in seconds between individual keepalive probes
|
;tcp_keepalive_interval_time=10 ; The time in seconds between individual keepalive probes
|
||||||
;tcp_keepalive_probe_count=5 ; The maximum number of keepalive probes TCP should send before dropping the connection
|
;tcp_keepalive_probe_count=5 ; The maximum number of keepalive probes TCP should send before dropping the connection
|
||||||
|
|
||||||
;===============ENDPOINT IDENTIFIER TRANSPORT EXAMPLE==========================
|
|
||||||
;
|
|
||||||
; When Asterisk has multiple bound IP addresses, and endpoints don't use any
|
|
||||||
; other means of identification (e.g.: username), the transports' bind addresses
|
|
||||||
; can be used to identify them. Can be useful in case you're connecting to the
|
|
||||||
; same ITSP multiple times on different IPs / NICs.
|
|
||||||
;
|
|
||||||
;[transport-eth0]
|
|
||||||
;type=transport
|
|
||||||
;protocol=tcp
|
|
||||||
;bind=192.168.1.1:5060
|
|
||||||
;
|
|
||||||
;[transport-eth1]
|
|
||||||
;type=transport
|
|
||||||
;protocol=udp
|
|
||||||
;bind=192.168.2.1:5060
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;[myprovider-a]
|
|
||||||
;type=endpoint
|
|
||||||
;transport=transport-eth0
|
|
||||||
;identify_by=transport
|
|
||||||
;
|
|
||||||
;[myprovider-b]
|
|
||||||
;type=endpoint
|
|
||||||
;transport=transport-eth1
|
|
||||||
;identify_by=transport
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;[identify-a]
|
|
||||||
;type=identify
|
|
||||||
;endpoint=myprovider-a
|
|
||||||
;match=192.168.1.1:5060 ; This is the bind address of [transport-eth0]
|
|
||||||
;;transport=tcp ; Optionally, this is the transport protocol of [transport-eth0]
|
|
||||||
;
|
|
||||||
;[identify-b]
|
|
||||||
;type=identify
|
|
||||||
;endpoint=myprovider-b
|
|
||||||
;match=192.168.2.1:5060 ; This is the bind address of [transport-eth1]
|
|
||||||
;;transport=udp ; Optionally, This is the transport protocol of [transport-eth1]
|
|
||||||
|
|
||||||
;===============OUTBOUND REGISTRATION WITH OUTBOUND AUTHENTICATION============
|
;===============OUTBOUND REGISTRATION WITH OUTBOUND AUTHENTICATION============
|
||||||
;
|
;
|
||||||
; This is a simple registration that works with some SIP trunking providers.
|
; This is a simple registration that works with some SIP trunking providers.
|
||||||
@@ -725,10 +684,9 @@
|
|||||||
; identified.
|
; identified.
|
||||||
; "username": Identify by the From or To username and domain
|
; "username": Identify by the From or To username and domain
|
||||||
; "auth_username": Identify by the Authorization username and realm
|
; "auth_username": Identify by the Authorization username and realm
|
||||||
; "ip": Identify by the source (remote) IP address
|
; "ip": Identify by the source IP address
|
||||||
; "header": Identify by a configured SIP header value.
|
; "header": Identify by a configured SIP header value.
|
||||||
; "request_uri": Identify by the configured SIP request URI.
|
; "request_uri": Identify by the configured SIP request URI.
|
||||||
; "transport": Identify by the bound (local) IP address
|
|
||||||
; In the username and auth_username cases, if an exact match
|
; In the username and auth_username cases, if an exact match
|
||||||
; on both username and domain/realm fails, the match is
|
; on both username and domain/realm fails, the match is
|
||||||
; retried with just the username.
|
; retried with just the username.
|
||||||
@@ -1313,10 +1271,9 @@
|
|||||||
; (default: "no")
|
; (default: "no")
|
||||||
;endpoint_identifier_order=ip,username,anonymous
|
;endpoint_identifier_order=ip,username,anonymous
|
||||||
; The order by which endpoint identifiers are given priority.
|
; The order by which endpoint identifiers are given priority.
|
||||||
; Currently, "ip", "header", "request_uri", "transport", "username",
|
; Currently, "ip", "header", "username", "auth_username" and "anonymous"
|
||||||
; "auth_username" and "anonymous" are valid identifiers as registered by
|
; are valid identifiers as registered by the res_pjsip_endpoint_identifier_*
|
||||||
; the res_pjsip_endpoint_identifier_* modules.
|
; modules. Some modules like res_pjsip_endpoint_identifier_user register
|
||||||
; Some modules like res_pjsip_endpoint_identifier_user register
|
|
||||||
; more than one identifier. Use the CLI command "pjsip show identifiers"
|
; more than one identifier. Use the CLI command "pjsip show identifiers"
|
||||||
; to see the identifiers currently available.
|
; to see the identifiers currently available.
|
||||||
; (default: ip,username,anonymous)
|
; (default: ip,username,anonymous)
|
||||||
@@ -1504,8 +1461,6 @@
|
|||||||
;match= ; Comma separated list of IP addresses, networks, or hostnames to match
|
;match= ; Comma separated list of IP addresses, networks, or hostnames to match
|
||||||
; against (default: "")
|
; against (default: "")
|
||||||
;match_header= ; SIP header with specified value to match against (default: "")
|
;match_header= ; SIP header with specified value to match against (default: "")
|
||||||
;match_request_uri= ; SIP request URI to match against (default: "")
|
|
||||||
;transport= ; Match ageinst the transport protocol (tcp or udp) (default: "")
|
|
||||||
;type= ; Must be of type identify (default: "")
|
;type= ; Must be of type identify (default: "")
|
||||||
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
"""Revert d5122576cca8 add transport attribute to identify
|
||||||
|
|
||||||
|
Revision ID: bd9c5159c7ea
|
||||||
|
Revises: 6c475a93f48a
|
||||||
|
Create Date: 2024-05-17 08:30:58.299083
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'bd9c5159c7ea'
|
||||||
|
down_revision = '6c475a93f48a'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.drop_column('ps_endpoint_id_ips', 'transport')
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.add_column('ps_endpoint_id_ips', sa.Column('transport', sa.String(128)))
|
@@ -617,8 +617,6 @@ enum ast_sip_endpoint_identifier_type {
|
|||||||
AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER = (1 << 3),
|
AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER = (1 << 3),
|
||||||
/*! Identify based on request uri */
|
/*! Identify based on request uri */
|
||||||
AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI = (1 << 4),
|
AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI = (1 << 4),
|
||||||
/*! Identify based on bound (local) IP address */
|
|
||||||
AST_SIP_ENDPOINT_IDENTIFY_BY_TRANSPORT = (1 << 5),
|
|
||||||
};
|
};
|
||||||
AST_VECTOR(ast_sip_identify_by_vector, enum ast_sip_endpoint_identifier_type);
|
AST_VECTOR(ast_sip_identify_by_vector, enum ast_sip_endpoint_identifier_type);
|
||||||
|
|
||||||
|
@@ -565,14 +565,6 @@
|
|||||||
but simply allowed by this configuration option.
|
but simply allowed by this configuration option.
|
||||||
</para>
|
</para>
|
||||||
</enum>
|
</enum>
|
||||||
<enum name="transport">
|
|
||||||
<para>Matches the endpoint based on the destination IP
|
|
||||||
address.
|
|
||||||
</para>
|
|
||||||
<para>This method of identification is not configured here
|
|
||||||
but simply allowed by this configuration option.
|
|
||||||
</para>
|
|
||||||
</enum>
|
|
||||||
</enumlist>
|
</enumlist>
|
||||||
</description>
|
</description>
|
||||||
</configOption>
|
</configOption>
|
||||||
|
@@ -426,9 +426,6 @@ static const char *sip_endpoint_identifier_type2str(enum ast_sip_endpoint_identi
|
|||||||
case AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI:
|
case AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI:
|
||||||
str = "request_uri";
|
str = "request_uri";
|
||||||
break;
|
break;
|
||||||
case AST_SIP_ENDPOINT_IDENTIFY_BY_TRANSPORT:
|
|
||||||
str = "transport";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -456,8 +453,6 @@ static int sip_endpoint_identifier_str2type(const char *str)
|
|||||||
method = AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER;
|
method = AST_SIP_ENDPOINT_IDENTIFY_BY_HEADER;
|
||||||
} else if (!strcasecmp(str, "request_uri")) {
|
} else if (!strcasecmp(str, "request_uri")) {
|
||||||
method = AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI;
|
method = AST_SIP_ENDPOINT_IDENTIFY_BY_REQUEST_URI;
|
||||||
} else if (!strcasecmp(str, "transport")) {
|
|
||||||
method = AST_SIP_ENDPOINT_IDENTIFY_BY_TRANSPORT;
|
|
||||||
} else {
|
} else {
|
||||||
method = -1;
|
method = -1;
|
||||||
}
|
}
|
||||||
|
@@ -124,16 +124,6 @@
|
|||||||
</para></note>
|
</para></note>
|
||||||
</description>
|
</description>
|
||||||
</configOption>
|
</configOption>
|
||||||
<configOption name="transport">
|
|
||||||
<synopsis>Match against a transport type.</synopsis>
|
|
||||||
<description>
|
|
||||||
<para>When using the ip or transport identifier, this option
|
|
||||||
can be used to match the transport type <literal>(udp or tcp)
|
|
||||||
</literal> as well.</para>
|
|
||||||
<para>When omitted, or left empty, which is the default, it
|
|
||||||
won't match against the transport type.</para>
|
|
||||||
</description>
|
|
||||||
</configOption>
|
|
||||||
<configOption name="type">
|
<configOption name="type">
|
||||||
<synopsis>Must be of type 'identify'.</synopsis>
|
<synopsis>Must be of type 'identify'.</synopsis>
|
||||||
</configOption>
|
</configOption>
|
||||||
@@ -161,8 +151,6 @@ struct ip_identify_match {
|
|||||||
AST_STRING_FIELD(match_header_name);
|
AST_STRING_FIELD(match_header_name);
|
||||||
/*! SIP header value of the match_header string */
|
/*! SIP header value of the match_header string */
|
||||||
AST_STRING_FIELD(match_header_value);
|
AST_STRING_FIELD(match_header_value);
|
||||||
/*! The name of the transport type */
|
|
||||||
AST_STRING_FIELD(transport);
|
|
||||||
);
|
);
|
||||||
/*! Compiled match_header regular expression when is_header_regex is non-zero */
|
/*! Compiled match_header regular expression when is_header_regex is non-zero */
|
||||||
regex_t regex_header_buf;
|
regex_t regex_header_buf;
|
||||||
@@ -180,12 +168,6 @@ struct ip_identify_match {
|
|||||||
unsigned int is_request_uri_regex:1;
|
unsigned int is_request_uri_regex:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*! \brief Structure for a socket address with transport */
|
|
||||||
struct ast_sockaddr_with_tp {
|
|
||||||
struct ast_sockaddr addr;
|
|
||||||
char tp[128];
|
|
||||||
};
|
|
||||||
|
|
||||||
/*! \brief Destructor function for a matching object */
|
/*! \brief Destructor function for a matching object */
|
||||||
static void ip_identify_destroy(void *obj)
|
static void ip_identify_destroy(void *obj)
|
||||||
{
|
{
|
||||||
@@ -321,29 +303,18 @@ static int request_identify_match_check(void *obj, void *arg, int flags)
|
|||||||
static int ip_identify_match_check(void *obj, void *arg, int flags)
|
static int ip_identify_match_check(void *obj, void *arg, int flags)
|
||||||
{
|
{
|
||||||
struct ip_identify_match *identify = obj;
|
struct ip_identify_match *identify = obj;
|
||||||
struct ast_sockaddr_with_tp *addr_with_tp = arg;
|
struct ast_sockaddr *addr = arg;
|
||||||
struct ast_sockaddr address = addr_with_tp->addr;
|
|
||||||
int sense;
|
int sense;
|
||||||
|
|
||||||
sense = ast_apply_ha(identify->matches, &address);
|
sense = ast_apply_ha(identify->matches, addr);
|
||||||
if (sense != AST_SENSE_ALLOW) {
|
if (sense != AST_SENSE_ALLOW) {
|
||||||
ast_debug(3, "Address %s matches identify '%s'\n",
|
ast_debug(3, "Source address %s matches identify '%s'\n",
|
||||||
ast_sockaddr_stringify(&address),
|
ast_sockaddr_stringify(addr),
|
||||||
ast_sorcery_object_get_id(identify));
|
ast_sorcery_object_get_id(identify));
|
||||||
if (ast_strlen_zero(identify->transport) || !strcasecmp(identify->transport, addr_with_tp->tp)) {
|
return CMP_MATCH;
|
||||||
ast_debug(3, "Transport %s matches identify '%s'\n",
|
|
||||||
addr_with_tp->tp,
|
|
||||||
ast_sorcery_object_get_id(identify));
|
|
||||||
return CMP_MATCH;
|
|
||||||
} else {
|
|
||||||
ast_debug(3, "Transport %s match not matched identify '%s'\n",
|
|
||||||
addr_with_tp->tp,
|
|
||||||
ast_sorcery_object_get_id(identify));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ast_debug(3, "Address %s does not match identify '%s'\n",
|
ast_debug(3, "Source address %s does not match identify '%s'\n",
|
||||||
ast_sockaddr_stringify(&address),
|
ast_sockaddr_stringify(addr),
|
||||||
ast_sorcery_object_get_id(identify));
|
ast_sorcery_object_get_id(identify));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -384,54 +355,18 @@ static struct ast_sip_endpoint *common_identify(ao2_callback_fn *identify_match_
|
|||||||
|
|
||||||
static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
|
static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
|
||||||
{
|
{
|
||||||
struct ast_sockaddr_with_tp addr_with_tp = { { { 0, } }, };
|
struct ast_sockaddr addr = { { 0, } };
|
||||||
pj_ansi_strxcpy(addr_with_tp.tp, rdata->tp_info.transport->type_name, sizeof(addr_with_tp.tp));
|
|
||||||
|
|
||||||
ast_sockaddr_parse(&addr_with_tp.addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
|
ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
|
||||||
ast_sockaddr_set_port(&addr_with_tp.addr, rdata->pkt_info.src_port);
|
ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
|
||||||
|
|
||||||
return common_identify(ip_identify_match_check, &addr_with_tp);
|
return common_identify(ip_identify_match_check, &addr);
|
||||||
}
|
|
||||||
|
|
||||||
static struct ast_sip_endpoint *transport_identify(pjsip_rx_data *rdata)
|
|
||||||
{
|
|
||||||
char buffer[PJ_INET6_ADDRSTRLEN];
|
|
||||||
pj_status_t status;
|
|
||||||
struct ast_sockaddr_with_tp addr_with_tp = { { { 0, } }, };
|
|
||||||
union pj_sockaddr sock = rdata->tp_info.transport->local_addr;
|
|
||||||
|
|
||||||
pj_ansi_strxcpy(addr_with_tp.tp, rdata->tp_info.transport->type_name, sizeof(addr_with_tp.tp));
|
|
||||||
|
|
||||||
if (sock.addr.sa_family == PJ_AF_INET6) {
|
|
||||||
status = pj_inet_ntop(PJ_AF_INET6, &(sock.ipv6.sin6_addr), buffer, PJ_INET6_ADDRSTRLEN);
|
|
||||||
if (status == PJ_SUCCESS && !strcmp(buffer, "::")) {
|
|
||||||
ast_log(LOG_WARNING, "Matching against '::' may be unpredictable.\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status = pj_inet_ntop(PJ_AF_INET, &(sock.ipv4.sin_addr), buffer, PJ_INET_ADDRSTRLEN);
|
|
||||||
if (status == PJ_SUCCESS && !strcmp(buffer, "0.0.0.0")) {
|
|
||||||
ast_log(LOG_WARNING, "Matching against '0.0.0.0' may be unpredictable.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == PJ_SUCCESS) {
|
|
||||||
ast_sockaddr_parse(&addr_with_tp.addr, buffer, PARSE_PORT_FORBID);
|
|
||||||
ast_sockaddr_set_port(&addr_with_tp.addr, rdata->tp_info.transport->local_name.port);
|
|
||||||
|
|
||||||
return common_identify(ip_identify_match_check, &addr_with_tp);
|
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_sip_endpoint_identifier ip_identifier = {
|
static struct ast_sip_endpoint_identifier ip_identifier = {
|
||||||
.identify_endpoint = ip_identify,
|
.identify_endpoint = ip_identify,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct ast_sip_endpoint_identifier transport_identifier = {
|
|
||||||
.identify_endpoint = transport_identify,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct ast_sip_endpoint *header_identify(pjsip_rx_data *rdata)
|
static struct ast_sip_endpoint *header_identify(pjsip_rx_data *rdata)
|
||||||
{
|
{
|
||||||
return common_identify(header_identify_match_check, rdata);
|
return common_identify(header_identify_match_check, rdata);
|
||||||
@@ -595,12 +530,6 @@ static int ip_identify_apply(const struct ast_sorcery *sorcery, void *obj)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ast_strlen_zero(identify->transport)) {
|
|
||||||
if (ast_string_field_set(identify, transport, identify->transport)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ast_strlen_zero(identify->match_header)) {
|
if (!ast_strlen_zero(identify->match_header)) {
|
||||||
char *c_header;
|
char *c_header;
|
||||||
char *c_value;
|
char *c_value;
|
||||||
@@ -950,13 +879,6 @@ static int cli_print_body(void *obj, void *arg, int flags)
|
|||||||
addr, ast_sockaddr_cidr_bits(&match->netmask));
|
addr, ast_sockaddr_cidr_bits(&match->netmask));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ast_strlen_zero(ident->transport)) {
|
|
||||||
ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
|
|
||||||
indent,
|
|
||||||
"Transport",
|
|
||||||
ident->transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ast_strlen_zero(ident->match_header)) {
|
if (!ast_strlen_zero(ident->match_header)) {
|
||||||
ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
|
ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
|
||||||
indent,
|
indent,
|
||||||
@@ -1032,13 +954,11 @@ static int load_module(void)
|
|||||||
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header));
|
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header));
|
||||||
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_request_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_request_uri));
|
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_request_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_request_uri));
|
||||||
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups));
|
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups));
|
||||||
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, transport));
|
|
||||||
ast_sorcery_load_object(ast_sip_get_sorcery(), "identify");
|
ast_sorcery_load_object(ast_sip_get_sorcery(), "identify");
|
||||||
|
|
||||||
ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip");
|
ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip");
|
||||||
ast_sip_register_endpoint_identifier_with_name(&header_identifier, "header");
|
ast_sip_register_endpoint_identifier_with_name(&header_identifier, "header");
|
||||||
ast_sip_register_endpoint_identifier_with_name(&request_identifier, "request_uri");
|
ast_sip_register_endpoint_identifier_with_name(&request_identifier, "request_uri");
|
||||||
ast_sip_register_endpoint_identifier_with_name(&transport_identifier, "transport");
|
|
||||||
ast_sip_register_endpoint_formatter(&endpoint_identify_formatter);
|
ast_sip_register_endpoint_formatter(&endpoint_identify_formatter);
|
||||||
|
|
||||||
cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
|
cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
|
||||||
@@ -1075,7 +995,6 @@ static int unload_module(void)
|
|||||||
ast_sip_unregister_endpoint_identifier(&header_identifier);
|
ast_sip_unregister_endpoint_identifier(&header_identifier);
|
||||||
ast_sip_unregister_endpoint_identifier(&request_identifier);
|
ast_sip_unregister_endpoint_identifier(&request_identifier);
|
||||||
ast_sip_unregister_endpoint_identifier(&ip_identifier);
|
ast_sip_unregister_endpoint_identifier(&ip_identifier);
|
||||||
ast_sip_unregister_endpoint_identifier(&transport_identifier);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user