mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-08-13 01:26:58 +00:00
FS-3110 --comment-only phase 1 to replace udns with ldns in mod_enum may need to rerun bootstrap
This commit is contained in:
576
libs/ldns/examples/ldns-testns.c
Normal file
576
libs/ldns/examples/ldns-testns.c
Normal file
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* ldns-testns. Light-weight DNS daemon, gives canned replies.
|
||||
*
|
||||
* Tiny dns server, that responds with specially crafted replies
|
||||
* to requests. For testing dns software.
|
||||
*
|
||||
* (c) NLnet Labs, 2005 - 2008
|
||||
* See the file LICENSE for the license
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is a debugging aid. It can is not efficient, especially
|
||||
* with a long config file, but it can give any reply to any query.
|
||||
* This can help the developer pre-script replies for queries.
|
||||
*
|
||||
* It listens to IP4 UDP and TCP by default.
|
||||
* You can specify a packet RR by RR with header flags to return.
|
||||
*
|
||||
* Missing features:
|
||||
* - matching content different from reply content.
|
||||
* - find way to adjust mangled packets?
|
||||
*/
|
||||
|
||||
/*
|
||||
The data file format is as follows:
|
||||
|
||||
; comment.
|
||||
; a number of entries, these are processed first to last.
|
||||
; a line based format.
|
||||
|
||||
$ORIGIN origin
|
||||
$TTL default_ttl
|
||||
|
||||
ENTRY_BEGIN
|
||||
; first give MATCH lines, that say what queries are matched
|
||||
; by this entry.
|
||||
; 'opcode' makes the query match the opcode from the reply
|
||||
; if you leave it out, any opcode matches this entry.
|
||||
; 'qtype' makes the query match the qtype from the reply
|
||||
; 'qname' makes the query match the qname from the reply
|
||||
; 'serial=1023' makes the query match if ixfr serial is 1023.
|
||||
MATCH [opcode] [qtype] [qname] [serial=<value>]
|
||||
MATCH [UDP|TCP]
|
||||
MATCH ...
|
||||
; Then the REPLY header is specified.
|
||||
REPLY opcode, rcode or flags.
|
||||
(opcode) QUERY IQUERY STATUS NOTIFY UPDATE
|
||||
(rcode) NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMPL YXDOMAIN
|
||||
YXRRSET NXRRSET NOTAUTH NOTZONE
|
||||
(flags) QR AA TC RD CD RA AD
|
||||
REPLY ...
|
||||
; any additional actions to do.
|
||||
; 'copy_id' copies the ID from the query to the answer.
|
||||
ADJUST copy_id
|
||||
; 'sleep=10' sleeps for 10 seconds before giving the answer (TCP is open)
|
||||
ADJUST [sleep=<num>] ; sleep before giving any reply
|
||||
ADJUST [packet_sleep=<num>] ; sleep before this packet in sequence
|
||||
SECTION QUESTION
|
||||
<RRs, one per line> ; the RRcount is determined automatically.
|
||||
SECTION ANSWER
|
||||
<RRs, one per line>
|
||||
SECTION AUTHORITY
|
||||
<RRs, one per line>
|
||||
SECTION ADDITIONAL
|
||||
<RRs, one per line>
|
||||
EXTRA_PACKET ; follow with SECTION, REPLY for more packets.
|
||||
HEX_ANSWER_BEGIN ; follow with hex data
|
||||
; this replaces any answer packet constructed
|
||||
; with the SECTION keywords (only SECTION QUERY
|
||||
; is used to match queries). If the data cannot
|
||||
; be parsed, ADJUST rules for the answer packet
|
||||
; are ignored
|
||||
HEX_ANSWER_END
|
||||
ENTRY_END
|
||||
*/
|
||||
|
||||
/* Example data file:
|
||||
$ORIGIN nlnetlabs.nl
|
||||
$TTL 3600
|
||||
|
||||
ENTRY_BEGIN
|
||||
MATCH qname
|
||||
REPLY NOERROR
|
||||
ADJUST copy_id
|
||||
SECTION QUESTION
|
||||
www.nlnetlabs.nl. IN A
|
||||
SECTION ANSWER
|
||||
www.nlnetlabs.nl. IN A 195.169.215.155
|
||||
SECTION AUTHORITY
|
||||
nlnetlabs.nl. IN NS www.nlnetlabs.nl.
|
||||
ENTRY_END
|
||||
|
||||
ENTRY_BEGIN
|
||||
MATCH qname
|
||||
REPLY NOERROR
|
||||
ADJUST copy_id
|
||||
SECTION QUESTION
|
||||
www2.nlnetlabs.nl. IN A
|
||||
HEX_ANSWER_BEGIN
|
||||
; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
||||
;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
00 bf 81 80 00 01 00 01 00 02 00 02 03 77 77 77 0b 6b 61 6e ; 1- 20
|
||||
61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 00 01 03 77 77 ; 21- 40
|
||||
77 0b 6b 61 6e 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 ; 41- 60
|
||||
00 01 00 01 50 8b 00 04 52 5e ed 32 0b 6b 61 6e 61 72 69 65 ; 61- 80
|
||||
70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 8b 00 11 03 ; 81- 100
|
||||
6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 0b 6b 61 6e ; 101- 120
|
||||
61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 ; 121- 140
|
||||
8b 00 11 03 6e 73 32 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 ; 141- 160
|
||||
03 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 00 01 00 ; 161- 180
|
||||
01 00 00 46 53 00 04 52 5e ed 02 03 6e 73 32 08 68 65 78 6f ; 181- 200
|
||||
6e 2d 69 73 02 6e 6c 00 00 01 00 01 00 00 46 53 00 04 d4 cc ; 201- 220
|
||||
db 5b
|
||||
HEX_ANSWER_END
|
||||
ENTRY_END
|
||||
|
||||
|
||||
*/
|
||||
|
||||
struct sockaddr_storage;
|
||||
#include "config.h"
|
||||
#include <ldns/ldns.h>
|
||||
#include "ldns-testpkts.h"
|
||||
|
||||
#ifdef HAVE_SYS_SOCKET_H
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#ifdef HAVE_TIME_H
|
||||
#include <time.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_TIME_H
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_SELECT_H
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
#ifdef HAVE_ARPA_INET_H
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#ifdef HAVE_NETINET_IN_H
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#ifdef HAVE_NETINET_UDP_H
|
||||
#include <netinet/udp.h>
|
||||
#endif
|
||||
#ifdef HAVE_NETINET_IGMP_H
|
||||
#include <netinet/igmp.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
|
||||
#define INBUF_SIZE 4096 /* max size for incoming queries */
|
||||
#define DEFAULT_PORT 53 /* default if no -p port is specified */
|
||||
#define CONN_BACKLOG 5 /* 5 connections queued up for tcp */
|
||||
static const char* prog_name = "ldns-testns";
|
||||
static FILE* logfile = 0;
|
||||
static int do_verbose = 0;
|
||||
|
||||
static void usage()
|
||||
{
|
||||
printf("Usage: %s [options] <datafile>\n", prog_name);
|
||||
printf(" -r listens on random port. Port number is printed.\n");
|
||||
printf(" -p listens on the specified port, default %d.\n", DEFAULT_PORT);
|
||||
printf(" -f forks given number extra instances, default none.\n");
|
||||
printf(" -v more verbose, prints queries, answers and matching.\n");
|
||||
printf(" -6 listen on IP6 any address, instead of IP4 any address.\n");
|
||||
printf("The program answers queries with canned replies from the datafile.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void log_msg(const char* msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
vfprintf(logfile, msg, args);
|
||||
fflush(logfile);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void error(const char* msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
fprintf(logfile, "%s error: ", prog_name);
|
||||
vfprintf(logfile, msg, args);
|
||||
fprintf(logfile, "\n");
|
||||
fflush(logfile);
|
||||
va_end(args);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void verbose(int lvl, const char* msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
if(do_verbose)
|
||||
vfprintf(logfile, msg, args);
|
||||
fflush(logfile);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static int bind_port(int sock, int port, int fam)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
#if defined(AF_INET6) && defined(HAVE_GETADDRINFO)
|
||||
if(fam == AF_INET6) {
|
||||
struct sockaddr_in6 addr6;
|
||||
memset(&addr6, 0, sizeof(addr6));
|
||||
addr6.sin6_family = AF_INET6;
|
||||
addr6.sin6_port = (in_port_t)htons((uint16_t)port);
|
||||
# if HAVE_DECL_IN6ADDR_ANY
|
||||
addr6.sin6_addr = in6addr_any;
|
||||
# else
|
||||
memset(&addr6.sin6_addr, 0, sizeof(addr6.sin6_addr));
|
||||
# endif
|
||||
return bind(sock, (struct sockaddr *)&addr6, (socklen_t) sizeof(addr6));
|
||||
}
|
||||
#endif
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = (in_port_t)htons((uint16_t)port);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
return bind(sock, (struct sockaddr *)&addr, (socklen_t) sizeof(addr));
|
||||
}
|
||||
|
||||
struct handle_udp_userdata {
|
||||
int udp_sock;
|
||||
struct sockaddr_storage addr_him;
|
||||
socklen_t hislen;
|
||||
};
|
||||
static void
|
||||
send_udp(uint8_t* buf, size_t len, void* data)
|
||||
{
|
||||
struct handle_udp_userdata *userdata = (struct handle_udp_userdata*)data;
|
||||
/* udp send reply */
|
||||
ssize_t nb;
|
||||
nb = sendto(userdata->udp_sock, (void*)buf, len, 0,
|
||||
(struct sockaddr*)&userdata->addr_him, userdata->hislen);
|
||||
if(nb == -1)
|
||||
log_msg("sendto(): %s\n", strerror(errno));
|
||||
else if((size_t)nb != len)
|
||||
log_msg("sendto(): only sent %d of %d octets.\n",
|
||||
(int)nb, (int)len);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_udp(int udp_sock, struct entry* entries, int *count)
|
||||
{
|
||||
ssize_t nb;
|
||||
uint8_t inbuf[INBUF_SIZE];
|
||||
struct handle_udp_userdata userdata;
|
||||
userdata.udp_sock = udp_sock;
|
||||
|
||||
userdata.hislen = (socklen_t)sizeof(userdata.addr_him);
|
||||
/* udp recv */
|
||||
nb = recvfrom(udp_sock, (void*)inbuf, INBUF_SIZE, 0,
|
||||
(struct sockaddr*)&userdata.addr_him, &userdata.hislen);
|
||||
if (nb < 1) {
|
||||
#ifndef USE_WINSOCK
|
||||
log_msg("recvfrom(): %s\n", strerror(errno));
|
||||
#else
|
||||
if(WSAGetLastError() != WSAEINPROGRESS &&
|
||||
WSAGetLastError() != WSAECONNRESET &&
|
||||
WSAGetLastError()!= WSAEWOULDBLOCK)
|
||||
log_msg("recvfrom(): %d\n", WSAGetLastError());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
handle_query(inbuf, nb, entries, count, transport_udp, send_udp,
|
||||
&userdata, do_verbose?logfile:0);
|
||||
}
|
||||
|
||||
static void
|
||||
read_n_bytes(int sock, uint8_t* buf, size_t sz)
|
||||
{
|
||||
size_t count = 0;
|
||||
while(count < sz) {
|
||||
ssize_t nb = recv(sock, (void*)(buf+count), sz-count, 0);
|
||||
if(nb < 0) {
|
||||
log_msg("recv(): %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
count += nb;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
write_n_bytes(int sock, uint8_t* buf, size_t sz)
|
||||
{
|
||||
size_t count = 0;
|
||||
while(count < sz) {
|
||||
ssize_t nb = send(sock, (void*)(buf+count), sz-count, 0);
|
||||
if(nb < 0) {
|
||||
log_msg("send(): %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
count += nb;
|
||||
}
|
||||
}
|
||||
|
||||
struct handle_tcp_userdata {
|
||||
int s;
|
||||
};
|
||||
static void
|
||||
send_tcp(uint8_t* buf, size_t len, void* data)
|
||||
{
|
||||
struct handle_tcp_userdata *userdata = (struct handle_tcp_userdata*)data;
|
||||
uint16_t tcplen;
|
||||
/* tcp send reply */
|
||||
tcplen = htons(len);
|
||||
write_n_bytes(userdata->s, (uint8_t*)&tcplen, sizeof(tcplen));
|
||||
write_n_bytes(userdata->s, buf, len);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_tcp(int tcp_sock, struct entry* entries, int *count)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_storage addr_him;
|
||||
socklen_t hislen;
|
||||
uint8_t inbuf[INBUF_SIZE];
|
||||
uint16_t tcplen;
|
||||
struct handle_tcp_userdata userdata;
|
||||
|
||||
/* accept */
|
||||
hislen = (socklen_t)sizeof(addr_him);
|
||||
if((s = accept(tcp_sock, (struct sockaddr*)&addr_him, &hislen)) < 0) {
|
||||
log_msg("accept(): %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
userdata.s = s;
|
||||
|
||||
/* tcp recv */
|
||||
read_n_bytes(s, (uint8_t*)&tcplen, sizeof(tcplen));
|
||||
tcplen = ntohs(tcplen);
|
||||
if(tcplen >= INBUF_SIZE) {
|
||||
log_msg("query %d bytes too large, buffer %d bytes.\n",
|
||||
tcplen, INBUF_SIZE);
|
||||
#ifndef USE_WINSOCK
|
||||
close(s);
|
||||
#else
|
||||
closesocket(s);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
read_n_bytes(s, inbuf, tcplen);
|
||||
|
||||
handle_query(inbuf, (ssize_t) tcplen, entries, count, transport_tcp,
|
||||
send_tcp, &userdata, do_verbose?logfile:0);
|
||||
#ifndef USE_WINSOCK
|
||||
close(s);
|
||||
#else
|
||||
closesocket(s);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/** shared by the service and main routine (forked and threaded) */
|
||||
static int udp_sock, tcp_sock;
|
||||
static struct entry* entries;
|
||||
|
||||
/**
|
||||
* Test DNS server service, uses global udpsock, tcpsock, reply entries
|
||||
* The signature is kept void so the function can be used as a thread function.
|
||||
*/
|
||||
static void
|
||||
service(void)
|
||||
{
|
||||
fd_set rset, wset, eset;
|
||||
struct timeval timeout;
|
||||
int count;
|
||||
int maxfd;
|
||||
|
||||
/* service */
|
||||
count = 0;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
while (1) {
|
||||
FD_ZERO(&rset);
|
||||
FD_ZERO(&wset);
|
||||
FD_ZERO(&eset);
|
||||
FD_SET(udp_sock, &rset);
|
||||
FD_SET(tcp_sock, &rset);
|
||||
maxfd = udp_sock;
|
||||
if(tcp_sock > maxfd)
|
||||
maxfd = tcp_sock;
|
||||
if(select(maxfd+1, &rset, &wset, &eset, NULL) < 0) {
|
||||
error("select(): %s\n", strerror(errno));
|
||||
}
|
||||
if(FD_ISSET(udp_sock, &rset)) {
|
||||
handle_udp(udp_sock, entries, &count);
|
||||
}
|
||||
if(FD_ISSET(tcp_sock, &rset)) {
|
||||
handle_tcp(tcp_sock, entries, &count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
forkit(int number)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<number; i++)
|
||||
{
|
||||
#ifndef HAVE_FORK
|
||||
#ifndef USE_WINSOCK
|
||||
log_msg("fork() not available.\n");
|
||||
exit(1);
|
||||
#else /* USE_WINSOCK */
|
||||
DWORD tid;
|
||||
HANDLE id = CreateThread(NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)service, NULL,
|
||||
0, &tid);
|
||||
if(id == NULL) {
|
||||
log_msg("error CreateThread: %d\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
log_msg("thread id: %d\n", (int)tid);
|
||||
#endif /* USE_WINSOCK */
|
||||
#else /* HAVE_FORK */
|
||||
pid_t pid = fork();
|
||||
if(pid == -1) {
|
||||
log_msg("error forking: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
if(pid == 0)
|
||||
return; /* child starts serving */
|
||||
log_msg("forked pid: %d\n", (int)pid);
|
||||
#endif /* HAVE_FORK */
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
/* arguments */
|
||||
int c;
|
||||
int port = DEFAULT_PORT;
|
||||
const char* datafile;
|
||||
int forknum = 0;
|
||||
|
||||
/* network */
|
||||
int fam = AF_INET;
|
||||
bool random_port_success;
|
||||
|
||||
#ifdef USE_WINSOCK
|
||||
WSADATA wsa_data;
|
||||
#endif
|
||||
|
||||
/* parse arguments */
|
||||
srandom(time(NULL) ^ getpid());
|
||||
logfile = stdout;
|
||||
prog_name = argv[0];
|
||||
log_msg("%s: start\n", prog_name);
|
||||
while((c = getopt(argc, argv, "6f:p:rv")) != -1) {
|
||||
switch(c) {
|
||||
case '6':
|
||||
#ifdef AF_INET6
|
||||
fam = AF_INET6;
|
||||
#else
|
||||
log_msg("cannot -6: no IP6 available\n");
|
||||
exit(1);
|
||||
#endif
|
||||
break;
|
||||
case 'r':
|
||||
port = 0;
|
||||
break;
|
||||
case 'f':
|
||||
forknum = atoi(optarg);
|
||||
if(forknum < 1)
|
||||
error("invalid forkno %s, give number", optarg);
|
||||
break;
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
if (port < 1) {
|
||||
error("Invalid port %s, use a number.", optarg);
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
do_verbose++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if(argc == 0 || argc > 1)
|
||||
usage();
|
||||
|
||||
datafile = argv[0];
|
||||
log_msg("Reading datafile %s\n", datafile);
|
||||
entries = read_datafile(datafile);
|
||||
|
||||
#ifdef USE_WINSOCK
|
||||
if(WSAStartup(MAKEWORD(2,2), &wsa_data) != 0)
|
||||
error("WSAStartup failed\n");
|
||||
#endif
|
||||
|
||||
if((udp_sock = socket(fam, SOCK_DGRAM, 0)) < 0) {
|
||||
error("udp socket(): %s\n", strerror(errno));
|
||||
}
|
||||
if((tcp_sock = socket(fam, SOCK_STREAM, 0)) < 0) {
|
||||
error("tcp socket(): %s\n", strerror(errno));
|
||||
}
|
||||
c = 1;
|
||||
if(setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&c, (socklen_t) sizeof(int)) < 0) {
|
||||
error("setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
/* bind ip4 */
|
||||
if (port > 0) {
|
||||
if (bind_port(udp_sock, port, fam)) {
|
||||
error("cannot bind(): %s\n", strerror(errno));
|
||||
}
|
||||
if (bind_port(tcp_sock, port, fam)) {
|
||||
error("cannot bind(): %s\n", strerror(errno));
|
||||
}
|
||||
if (listen(tcp_sock, CONN_BACKLOG) < 0) {
|
||||
error("listen(): %s\n", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
random_port_success = false;
|
||||
while (!random_port_success) {
|
||||
port = (random() % 64510) + 1025;
|
||||
log_msg("trying to bind to port %d\n", port);
|
||||
random_port_success = true;
|
||||
if (bind_port(udp_sock, port, fam)) {
|
||||
#ifdef EADDRINUSE
|
||||
if (errno != EADDRINUSE) {
|
||||
#elif defined(USE_WINSOCK)
|
||||
if (WSAGetLastError() != WSAEADDRINUSE) {
|
||||
#else
|
||||
if (1) {
|
||||
#endif
|
||||
perror("bind()");
|
||||
return -1;
|
||||
} else {
|
||||
random_port_success = false;
|
||||
}
|
||||
}
|
||||
if (random_port_success) {
|
||||
if (bind_port(tcp_sock, port, fam)) {
|
||||
#ifdef EADDRINUSE
|
||||
if (errno != EADDRINUSE) {
|
||||
#elif defined(USE_WINSOCK)
|
||||
if (WSAGetLastError()!=WSAEADDRINUSE){
|
||||
#else
|
||||
if (1) {
|
||||
#endif
|
||||
perror("bind()");
|
||||
return -1;
|
||||
} else {
|
||||
random_port_success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (random_port_success) {
|
||||
if (listen(tcp_sock, CONN_BACKLOG) < 0) {
|
||||
error("listen(): %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
log_msg("Listening on port %d\n", port);
|
||||
|
||||
/* forky! */
|
||||
if(forknum > 0)
|
||||
forkit(forknum);
|
||||
|
||||
service();
|
||||
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user