IPv6 support from issue #7.
git-svn-id: https://svn.perl.org/qpsmtpd/trunk@740 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
parent
2db48784fe
commit
ccf990e032
@ -29,24 +29,6 @@ my %return_codes = (
|
|||||||
YIELD => 911,
|
YIELD => 911,
|
||||||
);
|
);
|
||||||
|
|
||||||
my $has_ipv6;
|
|
||||||
|
|
||||||
if (
|
|
||||||
eval {require Socket6;} &&
|
|
||||||
# INET6 prior to 2.01 will not work; sorry.
|
|
||||||
eval {require IO::Socket::INET6; IO::Socket::INET6->VERSION("2.00");}
|
|
||||||
) {
|
|
||||||
import Socket6;
|
|
||||||
$has_ipv6=1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$has_ipv6=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub has_ipv6 {
|
|
||||||
return $has_ipv6;
|
|
||||||
}
|
|
||||||
|
|
||||||
use vars qw(@ISA @EXPORT);
|
use vars qw(@ISA @EXPORT);
|
||||||
@ISA = qw(Exporter);
|
@ISA = qw(Exporter);
|
||||||
@EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");
|
@EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");
|
||||||
|
@ -8,6 +8,23 @@ use strict;
|
|||||||
|
|
||||||
use POSIX ();
|
use POSIX ();
|
||||||
|
|
||||||
|
my $has_ipv6;
|
||||||
|
if (
|
||||||
|
eval {require Socket6;} &&
|
||||||
|
# INET6 prior to 2.01 will not work; sorry.
|
||||||
|
eval {require IO::Socket::INET6; IO::Socket::INET6->VERSION("2.00");}
|
||||||
|
) {
|
||||||
|
import Socket6;
|
||||||
|
$has_ipv6=1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$has_ipv6=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub has_ipv6 {
|
||||||
|
return $has_ipv6;
|
||||||
|
}
|
||||||
|
|
||||||
my $first_0;
|
my $first_0;
|
||||||
|
|
||||||
sub start_connection {
|
sub start_connection {
|
||||||
@ -104,4 +121,43 @@ sub disconnect {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# local/remote port and ip address
|
||||||
|
sub lrpip {
|
||||||
|
my ($server, $client, $hisaddr) = @_;
|
||||||
|
|
||||||
|
my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr));
|
||||||
|
my $localsockaddr = getsockname($client);
|
||||||
|
my ($lport, $laddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($localsockaddr)) : (sockaddr_in6($localsockaddr));
|
||||||
|
|
||||||
|
my $nto_iaddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($iaddr)) : (inet_ntop(AF_INET6(), $iaddr));
|
||||||
|
my $nto_laddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($laddr)) : (inet_ntop(AF_INET6(), $laddr));
|
||||||
|
$nto_iaddr =~ s/::ffff://;
|
||||||
|
$nto_laddr =~ s/::ffff://;
|
||||||
|
|
||||||
|
return ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tcpenv {
|
||||||
|
my ($nto_laddr, $nto_iaddr, $no_rdns) = @_;
|
||||||
|
|
||||||
|
my $TCPLOCALIP = $nto_laddr;
|
||||||
|
my $TCPREMOTEIP = $nto_iaddr;
|
||||||
|
|
||||||
|
if ($no_rdns) {
|
||||||
|
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEIP ? "[$ENV{TCPREMOTEIP}]" : "[noip!]");
|
||||||
|
}
|
||||||
|
my $res = new Net::DNS::Resolver;
|
||||||
|
$res->tcp_timeout(3);
|
||||||
|
$res->udp_timeout(3);
|
||||||
|
my $query = $res->query($nto_iaddr);
|
||||||
|
my $TCPREMOTEHOST;
|
||||||
|
if($query) {
|
||||||
|
foreach my $rr ($query->answer) {
|
||||||
|
next unless $rr->type eq "PTR";
|
||||||
|
$TCPREMOTEHOST = $rr->ptrdname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEHOST || "Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -2,25 +2,70 @@
|
|||||||
# $ENV{RELAYCLIENT} to see if relaying is allowed.
|
# $ENV{RELAYCLIENT} to see if relaying is allowed.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
use Net::IP qw(:PROC);
|
||||||
|
|
||||||
sub hook_connect {
|
sub hook_connect {
|
||||||
my ($self, $transaction) = @_;
|
my ($self, $transaction) = @_;
|
||||||
my $connection = $self->qp->connection;
|
my $connection = $self->qp->connection;
|
||||||
|
|
||||||
# Check if this IP is allowed to relay
|
# Check if this IP is allowed to relay
|
||||||
my @relay_clients = $self->qp->config("relayclients");
|
|
||||||
my $more_relay_clients = $self->qp->config("morerelayclients", "map");
|
|
||||||
my %relay_clients = map { $_ => 1 } @relay_clients;
|
|
||||||
my $client_ip = $self->qp->connection->remote_ip;
|
my $client_ip = $self->qp->connection->remote_ip;
|
||||||
while ($client_ip) {
|
|
||||||
if (exists($ENV{RELAYCLIENT}) or
|
# @crelay... for comparing, @srelay... for stripping
|
||||||
exists($relay_clients{$client_ip}) or
|
my (@crelay_clients, @srelay_clients);
|
||||||
exists($more_relay_clients->{$client_ip}))
|
|
||||||
{
|
my @relay_clients = $self->qp->config("relayclients");
|
||||||
$connection->relay_client(1);
|
for (@relay_clients) {
|
||||||
last;
|
my ($range_ip, $range_prefix) = ip_splitprefix($_);
|
||||||
|
if($range_prefix){
|
||||||
|
# has a prefix, so due for comparing
|
||||||
|
push @crelay_clients, $_;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# has no prefix, so due for splitting
|
||||||
|
push @srelay_clients, $_;
|
||||||
}
|
}
|
||||||
$client_ip =~ s/(\d|\w|::)+(:|\.)?$//; # strip off another 8 bits
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (@crelay_clients){
|
||||||
|
my ($range_ip, $range_prefix, $rversion, $begin, $end, $bin_client_ip);
|
||||||
|
my $cversion = ip_get_version($client_ip);
|
||||||
|
for (@crelay_clients) {
|
||||||
|
# Get just the IP from the CIDR range, to get the IP version, so we can
|
||||||
|
# get the start and end of the range
|
||||||
|
($range_ip, $range_prefix) = ip_splitprefix($_);
|
||||||
|
$rversion = ip_get_version($range_ip);
|
||||||
|
($begin, $end) = ip_normalize($_, $rversion);
|
||||||
|
|
||||||
|
# expand the client address (zero pad it) before converting to binary
|
||||||
|
$bin_client_ip = ip_iptobin(ip_expand_address($client_ip, $cversion), $cversion);
|
||||||
|
|
||||||
|
if (ip_bincomp($bin_client_ip, 'gt', ip_iptobin($begin, $rversion))
|
||||||
|
&& ip_bincomp($bin_client_ip, 'lt', ip_iptobin($end, $rversion)))
|
||||||
|
{
|
||||||
|
$connection->relay_client(1);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If relay_client is already set, no point checking again
|
||||||
|
if (@srelay_clients && !$connection->relay_client) {
|
||||||
|
my $more_relay_clients = $self->qp->config("morerelayclients", "map");
|
||||||
|
my %srelay_clients = map { $_ => 1 } @srelay_clients;
|
||||||
|
$client_ip =~ s/::/:/;
|
||||||
|
($connection->relay_client(1) && undef($client_ip)) if $client_ip eq ":1";
|
||||||
|
|
||||||
|
while ($client_ip) {
|
||||||
|
if (exists($ENV{RELAYCLIENT}) or
|
||||||
|
exists($srelay_clients{$client_ip}) or
|
||||||
|
exists($more_relay_clients->{$client_ip}))
|
||||||
|
{
|
||||||
|
$connection->relay_client(1);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
$client_ip =~ s/(\d|\w)+(:|\.)?$//; # strip off another 8 bits
|
||||||
|
}
|
||||||
|
}
|
||||||
return (DECLINED);
|
return (DECLINED);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use Qpsmtpd::DSN;
|
use Qpsmtpd::DSN;
|
||||||
use Net::DNS qw(mx);
|
use Net::DNS qw(mx);
|
||||||
use Socket;
|
use Socket;
|
||||||
|
use Net::IP qw(:PROC);
|
||||||
|
|
||||||
my %invalid = ();
|
my %invalid = ();
|
||||||
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
|
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||||
|
|
||||||
sub hook_mail {
|
sub hook_mail {
|
||||||
my ($self, $transaction, $sender, %param) = @_;
|
my ($self, $transaction, $sender, %param) = @_;
|
||||||
@ -99,6 +100,9 @@ sub is_valid {
|
|||||||
sub mx_valid {
|
sub mx_valid {
|
||||||
my ($self, $name, $host) = @_;
|
my ($self, $name, $host) = @_;
|
||||||
my $res = new Net::DNS::Resolver;
|
my $res = new Net::DNS::Resolver;
|
||||||
|
# IP in MX
|
||||||
|
return is_valid($name) if ip_is_ipv4($name) or ip_is_ipv6($name);
|
||||||
|
|
||||||
my @mx_answers;
|
my @mx_answers;
|
||||||
my $query = $res->search($name, 'A');
|
my $query = $res->search($name, 'A');
|
||||||
if ($query) {
|
if ($query) {
|
||||||
|
@ -18,7 +18,7 @@ use Net::DNS::Header;
|
|||||||
use strict;
|
use strict;
|
||||||
$| = 1;
|
$| = 1;
|
||||||
|
|
||||||
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
|
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||||
|
|
||||||
if ($has_ipv6) {
|
if ($has_ipv6) {
|
||||||
eval 'use Socket6';
|
eval 'use Socket6';
|
||||||
@ -40,8 +40,10 @@ sub usage {
|
|||||||
print <<"EOT";
|
print <<"EOT";
|
||||||
usage: qpsmtpd-forkserver [ options ]
|
usage: qpsmtpd-forkserver [ options ]
|
||||||
-l, --listen-address addr : listen on specific address(es); can be specified
|
-l, --listen-address addr : listen on specific address(es); can be specified
|
||||||
multiple times for multiple bindings. Default is
|
multiple times for multiple bindings. IPv6
|
||||||
0.0.0.0 (all interfaces).
|
addresses must be inside square brackets [], and
|
||||||
|
don't need to be zero padded.
|
||||||
|
Default is [::] (if has_ipv6) or 0.0.0.0 (if not)
|
||||||
-p, --port P : listen on a specific port; default 2525; can be
|
-p, --port P : listen on a specific port; default 2525; can be
|
||||||
specified multiple times for multiple bindings.
|
specified multiple times for multiple bindings.
|
||||||
-c, --limit-connections N : limit concurrent connections to N; default 15
|
-c, --limit-connections N : limit concurrent connections to N; default 15
|
||||||
@ -234,14 +236,8 @@ while (1) {
|
|||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
IO::Handle::blocking($client, 1);
|
IO::Handle::blocking($client, 1);
|
||||||
my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr));
|
# get local/remote hostname, port and ip address
|
||||||
my $localsockaddr = getsockname($client);
|
my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($server, $client, $hisaddr);
|
||||||
my ($lport, $laddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($localsockaddr)) : (sockaddr_in6($localsockaddr));
|
|
||||||
my $nto_iaddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($iaddr)) : (inet_ntop(AF_INET6(), $iaddr));
|
|
||||||
my $ton_iaddr = ($server->sockdomain == AF_INET) ? (inet_aton($iaddr)) : (inet_pton(AF_INET6(), $iaddr));
|
|
||||||
my $nto_laddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($laddr)) : (inet_ntop(AF_INET6(), $laddr));
|
|
||||||
$nto_iaddr =~ s/::ffff://;
|
|
||||||
$nto_laddr =~ s/::ffff://;
|
|
||||||
|
|
||||||
my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
|
my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
|
||||||
remote_ip => $nto_iaddr,
|
remote_ip => $nto_iaddr,
|
||||||
@ -293,27 +289,8 @@ while (1) {
|
|||||||
::log(LOGINFO, "Connection Timed Out");
|
::log(LOGINFO, "Connection Timed Out");
|
||||||
exit; };
|
exit; };
|
||||||
|
|
||||||
$ENV{TCPLOCALIP} = $nto_laddr;
|
# set enviroment variables
|
||||||
# my ($port, $iaddr) = sockaddr_in($hisaddr);
|
($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
|
||||||
$ENV{TCPREMOTEIP} = $nto_iaddr;
|
|
||||||
|
|
||||||
if ($NORDNS) {
|
|
||||||
$ENV{TCPREMOTEHOST} = $ENV{TCPREMOTEIP} ? "[$ENV{TCPREMOTEIP}]" : "[noip!]";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my $zero = $0;
|
|
||||||
$0 = "$zero (gethostbyname $ENV{TCPREMOTEIP})";
|
|
||||||
|
|
||||||
if ($server->sockdomain == AF_INET) {
|
|
||||||
$ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my ($family, $socktype, $proto, $saddr, $canonname, @res) = getaddrinfo($iaddr, $port, AF_UNSPEC);
|
|
||||||
$ENV{TCPREMOTEHOST} = $canonname || "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
$0 = $zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
# don't do this!
|
# don't do this!
|
||||||
#$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";
|
#$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";
|
||||||
|
@ -19,6 +19,12 @@ use Qpsmtpd::TcpServer::Prefork;
|
|||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
use Getopt::Long;
|
use Getopt::Long;
|
||||||
|
|
||||||
|
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||||
|
|
||||||
|
if ($has_ipv6) {
|
||||||
|
use Socket6;
|
||||||
|
}
|
||||||
|
|
||||||
#use Time::HiRes qw(gettimeofday tv_interval);
|
#use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
|
|
||||||
# secure shell
|
# secure shell
|
||||||
@ -47,7 +53,14 @@ my $d; # socket
|
|||||||
my $pid_path = '/var/run/qpsmtpd/';
|
my $pid_path = '/var/run/qpsmtpd/';
|
||||||
my $PID = $pid_path . "/qpsmtpd.pid";
|
my $PID = $pid_path . "/qpsmtpd.pid";
|
||||||
my $d_port = 25;
|
my $d_port = 25;
|
||||||
my $d_addr = "0.0.0.0";
|
my $d_addr;
|
||||||
|
if ($has_ipv6) {
|
||||||
|
$d_addr = "[::]";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$d_addr = "0.0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
my $debug = 0;
|
my $debug = 0;
|
||||||
my $max_children = 15; # max number of child processes to spawn
|
my $max_children = 15; # max number of child processes to spawn
|
||||||
my $idle_children = 5; # number of idle child processes to spawn
|
my $idle_children = 5; # number of idle child processes to spawn
|
||||||
@ -67,7 +80,7 @@ sub usage {
|
|||||||
print <<"EOT";
|
print <<"EOT";
|
||||||
Usage: qpsmtpd-prefork [ options ]
|
Usage: qpsmtpd-prefork [ options ]
|
||||||
--quiet : Be quiet (even errors are suppressed)
|
--quiet : Be quiet (even errors are suppressed)
|
||||||
--version : Show version information
|
--version : Show version information
|
||||||
--debug : Enable debug output
|
--debug : Enable debug output
|
||||||
--interface addr : Interface daemon should listen on (default: $d_addr)
|
--interface addr : Interface daemon should listen on (default: $d_addr)
|
||||||
--port int : TCP port daemon should listen on (default: $d_port)
|
--port int : TCP port daemon should listen on (default: $d_port)
|
||||||
@ -134,15 +147,20 @@ sub run {
|
|||||||
if (!$uuid || !$ugid);
|
if (!$uuid || !$ugid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my @Socket_opts = (
|
||||||
|
LocalPort => $d_port,
|
||||||
|
LocalAddr => $d_addr,
|
||||||
|
Proto => 'tcp',
|
||||||
|
Listen => SOMAXCONN,
|
||||||
|
Reuse => 1,
|
||||||
|
);
|
||||||
# create new socket (used by clients to communicate with daemon)
|
# create new socket (used by clients to communicate with daemon)
|
||||||
$d =
|
if ($has_ipv6) {
|
||||||
new IO::Socket::INET(
|
$d = IO::Socket::INET6->new(@Socket_opts);
|
||||||
LocalPort => $d_port,
|
}
|
||||||
LocalAddr => $d_addr,
|
else {
|
||||||
Proto => 'tcp',
|
$d = IO::Socket::INET(@Socket_opts);
|
||||||
Listen => SOMAXCONN,
|
}
|
||||||
Reuse => 1,
|
|
||||||
);
|
|
||||||
die "FATAL: Failed to start daemon.\nReason: $!\n(It may be nessesary to "
|
die "FATAL: Failed to start daemon.\nReason: $!\n(It may be nessesary to "
|
||||||
. "wait 20 secs before starting daemon again)\n"
|
. "wait 20 secs before starting daemon again)\n"
|
||||||
unless $d;
|
unless $d;
|
||||||
@ -361,7 +379,7 @@ sub new_child {
|
|||||||
my $sigset = block_signal(SIGHUP);
|
my $sigset = block_signal(SIGHUP);
|
||||||
|
|
||||||
# start a session if connection looks valid
|
# start a session if connection looks valid
|
||||||
qpsmtpd_session($client, $qpsmtpd) if ($iinfo);
|
qpsmtpd_session($client, $iinfo, $qpsmtpd) if ($iinfo);
|
||||||
|
|
||||||
# close connection and cleanup
|
# close connection and cleanup
|
||||||
$client->shutdown(2);
|
$client->shutdown(2);
|
||||||
@ -512,15 +530,16 @@ sub info {
|
|||||||
|
|
||||||
# start qpmstpd session
|
# start qpmstpd session
|
||||||
# arg0: ref to socket object
|
# arg0: ref to socket object
|
||||||
# arg1: ref to qpsmtpd instance
|
# arg1: ref to socket object
|
||||||
|
# arg2: ref to qpsmtpd instance
|
||||||
# ret0: void
|
# ret0: void
|
||||||
sub qpsmtpd_session {
|
sub qpsmtpd_session {
|
||||||
my $client = shift; #arg0
|
my $client = shift; #arg0
|
||||||
my $qpsmtpd = shift; #arg1
|
my $iinfo = shift; #arg1
|
||||||
|
my $qpsmtpd = shift; #arg2
|
||||||
|
|
||||||
# get local/remote hostname, port and ip address
|
# get local/remote hostname, port and ip address
|
||||||
my ($port, $iaddr) = sockaddr_in(getpeername($client)); #remote
|
my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($d, $client, $iinfo);
|
||||||
my ($lport, $laddr) = sockaddr_in(getsockname($client)); #local
|
|
||||||
|
|
||||||
# get current connected ip addresses (from shared memory)
|
# get current connected ip addresses (from shared memory)
|
||||||
my %children;
|
my %children;
|
||||||
@ -529,9 +548,9 @@ sub qpsmtpd_session {
|
|||||||
my ($rc, @msg) =
|
my ($rc, @msg) =
|
||||||
$qpsmtpd->run_hooks(
|
$qpsmtpd->run_hooks(
|
||||||
"pre-connection",
|
"pre-connection",
|
||||||
remote_ip => inet_ntoa($iaddr),
|
remote_ip => $nto_iaddr,
|
||||||
remote_port => $port,
|
remote_port => $port,
|
||||||
local_ip => inet_ntoa($laddr),
|
local_ip => $nto_laddr,
|
||||||
local_port => $lport,
|
local_port => $lport,
|
||||||
max_conn_ip => $maxconnip,
|
max_conn_ip => $maxconnip,
|
||||||
child_addrs => [values %children],
|
child_addrs => [values %children],
|
||||||
@ -574,9 +593,7 @@ sub qpsmtpd_session {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# set enviroment variables
|
# set enviroment variables
|
||||||
$ENV{TCPLOCALIP} = inet_ntoa($laddr);
|
($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
|
||||||
$ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
|
|
||||||
$ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
|
|
||||||
|
|
||||||
# run qpmsptd functions
|
# run qpmsptd functions
|
||||||
$SIG{__DIE__} = 'DEFAULT';
|
$SIG{__DIE__} = 'DEFAULT';
|
||||||
|
Loading…
Reference in New Issue
Block a user