Commit c2c7b058 authored by IKEDA Soji's avatar IKEDA Soji
Browse files

[feature] Improve include_remote_sympa_list data source.

  - HTTP/1.1 is supported thanks to LWP::UserAgent.
  - Now it has user, passwd, timeout, ssl_version, ssl_ciphers and ca_verify parameters.
  - host, port and path parameters were obsoleted.  Use url instead.
  - cert parameter was obsoleted.
parent 117e2a2d
......@@ -250,7 +250,7 @@ feature 'Encode::Locale', 'Useful when running command line utilities in the con
};
feature 'remote-list-including', 'Required when including members of a remote list.' => sub {
requires 'IO::Socket::SSL', '>= 0.90';
requires 'LWP::Protocol::https';
};
feature 'Mail::DKIM::Verifier', 'Required in order to use DKIM features (both for signature verification and signature insertion).' => sub {
......@@ -269,10 +269,17 @@ feature 'ldap', 'Required to query LDAP directories. Sympa can do LDAP-based aut
# openldap-devel is needed to build the Perl code
requires 'Net::LDAP', '>= 0.40';
# Note: 'Net::LDAP::Entry', 'Net::LDAP::Util' and 'Net::LDAPS' are also
# Note: 'Net::LDAP::Entry' and 'Net::LDAP::Util' are also
# included in perl-ldap.
};
feature 'ldap-secure', 'Required to query LDAP directories over TLS.' => sub {
requires 'Net::LDAP', '>= 0.40';
requires 'IO::Socket::SSL', '>= 0.90';
# Note: 'Net::LDAPS' is also included in perl-ldap.
};
feature 'Net::SMTP', 'This is required if you set "list_check_smtp" sympa.conf parameter, used to check existing aliases before mailing list creation.' => sub {
requires 'Net::SMTP';
};
......
......@@ -25,134 +25,67 @@ package Sympa::DataSource::RemoteDump;
use strict;
use warnings;
use Sympa;
use Conf;
use Sympa::Log;
use Sympa::Regexps;
use base qw(Sympa::DataSource);
use base qw(Sympa::DataSource::RemoteFile); # Derived class
my $log = Sympa::Log->instance;
use constant required_modules => [qw(IO::Socket::SSL)];
use constant required_modules => [qw(LWP::Protocol::https)];
# Old name: (part of) Sympa::Fetch::get_https(), Sympa::List::_get_https().
sub _open {
my $self = shift;
my $list = $self->{context};
my $dir = $list->{'dir'};
my $host = $self->{host};
my $port = $self->{port} || '443';
my $path = $self->{path};
my $cert = $self->{cert} || 'list';
my $cert_file = $list->{'dir'} . '/cert.pem';
my $key_file = $list->{'dir'} . '/private_key';
if ($cert eq 'list') {
$cert_file = $dir . '/cert.pem';
$key_file = $dir . '/private_key';
} elsif ($cert eq 'robot') {
$cert_file = Sympa::search_fullpath($list, 'cert.pem');
$key_file = Sympa::search_fullpath($list, 'private_key');
}
unless (-r $cert_file and -r $key_file) {
$log->syslog(
'err',
'Include remote list https://%s:%s/%s using cert %s, unable to open %s or %s',
$host,
$port,
$path,
$cert,
$cert_file,
$key_file
);
return undef;
}
unless ($self->{url}) {
my $host_re = Sympa::Regexps::host();
my $host = $self->{host};
return undef unless $host and $host =~ /\A$host_re\z/;
my $key_passwd = $Conf::Conf{'key_passwd'}; #FIXME
my $trusted_ca_file = $Conf::Conf{'cafile'};
my $trusted_ca_path = $Conf::Conf{'capath'};
my $ssl_socket = IO::Socket::SSL->new(
SSL_use_cert => 1,
SSL_verify_mode => 0x01,
SSL_cert_file => $cert_file,
SSL_key_file => $key_file,
SSL_passwd_cb => sub { return ($key_passwd) },
($trusted_ca_file ? (SSL_ca_file => $trusted_ca_file) : ()),
($trusted_ca_path ? (SSL_ca_path => $trusted_ca_path) : ()),
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
);
unless ($ssl_socket) {
$log->syslog('err', 'Error %s unable to connect https://%s:%s/',
IO::Socket::SSL::errstr(), $host, $port);
return undef;
}
$log->syslog('debug', 'Connected to https://%s:%s/',
IO::Socket::SSL::errstr(), $host, $port);
if (ref($ssl_socket) eq "IO::Socket::SSL") {
my $subject_name = $ssl_socket->peer_certificate("subject");
my $issuer_name = $ssl_socket->peer_certificate("issuer");
my $cipher = $ssl_socket->get_cipher();
$log->syslog('debug',
'SSL peer certificate %s issued by %s. Cipher used %s',
$subject_name, $issuer_name, $cipher);
}
my $port = $self->{port} || '443';
my $path = $self->{path};
$path = '' unless defined $path;
$path = "/$path" unless 0 == index $path, '/';
print $ssl_socket "GET $path HTTP/1.0\nHost: $host\n\n";
$log->syslog('debug3', 'https://%s:%s/%s', $host, $port, $path);
$self->{url} = sprintf 'https://%s:%s%s', $host, $port, $path;
}
return $ssl_socket;
my $fh = $self->SUPER::_open(use_cert => 1);
#FIXME: Log subject, issuer and cipher of peer.
return $fh;
}
sub _next {
my $self = shift;
my $list = $self->{context};
my $ssl_socket = $self->__dsh;
my ($email, $gecos);
my $getting_headers = 1;
while (my $line = $ssl_socket->getline) {
$line =~ s/\r?\n\z//;
if ($getting_headers) { # ignore http headers
next
unless $line =~
/^(date|update_date|email|reception|visibility)/;
}
undef $getting_headers;
if ($line =~ /^\s*email\s+(.+)\s*$/o) {
$email = $1;
} elsif ($line =~ /^\s*gecos\s+(.+)\s*$/o) {
$gecos = $1;
} elsif ($line ne '') {
next;
my $fh = $self->__dsh;
my %entry;
while (my $line = <$fh>) {
$line =~ s/\s+\z//;
if ($line eq '') {
last if defined $entry{email} and length $entry{email};
%entry = ();
} elsif ($line =~ /\A\s*(\w+)(?:\s+(.*))?\z/) {
$entry{$1} = $2;
} else {
$log->syslog(
'err',
'%s: Illegal line %.128s. Source file probably corrupted. Aborting',
$self,
$line
);
return;
}
next unless defined $email and length $email;
return [$email, $gecos];
}
return [@entry{qw(email gecos)}]
if defined $entry{email} and length $entry{email};
return;
}
sub _close {
my $self = shift;
my $socket = $self->__dsh;
return unless ref $socket;
return $socket->close(SSL_no_shutdown => 1);
}
1;
__END__
......
......@@ -40,7 +40,8 @@ use constant required_modules => [qw(LWP::Protocol::https)];
# Old name: (part of) Sympa::List::_include_users_remote_file().
sub _open {
my $self = shift;
my $self = shift;
my %options = @_;
my $list = $self->{context};
......@@ -54,6 +55,20 @@ sub _open {
my $ca_file = $Conf::Conf{'cafile'};
my $ca_path = $Conf::Conf{'capath'};
if ($options{use_cert}) {
unless ($cert_file
and -r $cert_file
and $key_file
and -r $key_file) {
$log->syslog('err',
'%s: Unable to open client certificate or private key',
$self);
return undef;
} else {
$ua->ssl_opts(SSL_use_cert => 1);
}
}
$ua->ssl_opts(SSL_version => $self->{ssl_version})
if $self->{ssl_version} and $self->{ssl_version} ne 'ssl_any';
$ua->ssl_opts(SSL_cipher_list => $self->{ssl_ciphers})
......
......@@ -1239,35 +1239,101 @@ our %pinfo = (
'format' => '.+',
'length' => 15
},
'url' => {
'order' => 2,
'gettext_id' => "data location URL",
'format' => '.+',
'occurrence' => '0-1', # Backward compat. <= 6.2.38
'length' => 50
},
'user' => {
'order' => 3,
'gettext_id' => "remote user",
'format' => '.+',
'occurrence' => '0-1'
},
'passwd' => {
'order' => 4,
'gettext_id' => "remote password",
'format' => '.+',
'field_type' => 'password',
'occurrence' => '0-1',
'length' => 10,
},
'host' => {
'order' => 1.5,
'gettext_id' => "remote host",
format_s => '$host',
'occurrence' => '1'
'order' => 4.5,
'gettext_id' => "remote host",
'gettext_comment' => 'obsoleted. Use "data location URL".',
format_s => '$host',
'occurrence' => '1'
},
'port' => {
'order' => 2,
'gettext_id' => "remote port",
'format' => '\d+',
'default' => 443,
'length' => 4
'order' => 4.6,
'gettext_id' => "remote port",
'gettext_comment' => 'obsoleted. Use "data location URL".',
'format' => '\d+',
'default' => 443,
'length' => 4
},
'path' => {
'order' => 3,
'gettext_id' => "remote path of sympa list dump",
'format' => '\S+',
'occurrence' => '1',
'length' => 20
'order' => 4.7,
'gettext_id' => "remote path of sympa list dump",
'gettext_comment' => 'obsoleted. Use "data location URL".',
'format' => '\S+',
'occurrence' => '1',
'length' => 20
},
'cert' => {
'order' => 4,
'order' => 4.8,
'gettext_id' =>
"certificate for authentication by remote Sympa",
'format' => ['robot', 'list'],
'default' => 'list'
'format' => ['robot', 'list'],
'default' => 'list',
'obsolete' => 1,
},
'timeout' => {
'order' => 5,
'gettext_id' => "idle timeout",
'gettext_unit' => 'seconds',
'format' => '\d+',
'length' => 6,
'default' => 180,
},
'ssl_version' => {
'order' => 6,
'gettext_id' => 'SSL version',
'format' => [
'ssl_any', 'sslv2', 'sslv3', 'tlsv1',
'tlsv1_1', 'tlsv1_2', 'tlsv1_3'
],
'synonym' => {'tls' => 'tlsv1'},
'occurrence' => '0-1',
'default' => 'ssl_any',
},
'ssl_ciphers' => {
'order' => 7,
'gettext_id' => 'SSL ciphers used',
'format' => '.+',
'default' => 'ALL'
},
# ssl_cert # Use cert.pem in list directory
# ssl_key # Use private_key in list directory
# NOTE: The default of ca_verify is "none" that is different from
# include_ldap_query (required) or include_remote_file (none).
'ca_verify' => {
'order' => 8,
'gettext_id' => 'Certificate verification',
'format' => ['none', 'optional', 'required'],
'synonym' => {'require' => 'required'},
'occurrence' => '0-1',
'default' => 'optional',
},
# ca_path # Not yet implemented
# ca_file # Not yet implemented
'nosync_time_ranges' => {
'order' => 5,
'order' => 10,
'gettext_id' => "Time ranges when inclusion is not allowed",
format_s => '$time_ranges',
'occurrence' => '0-1'
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment