diff options
| author | Guilhem Moulin <guilhem@fripost.org> | 2020-12-10 15:26:46 +0100 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@fripost.org> | 2020-12-11 11:20:41 +0100 | 
| commit | 84d1829fd0f955cf9fb7add54f60fc314b0d42b1 (patch) | |
| tree | 8525a7e966cf29e2a61d83d425f26674531289f0 | |
| parent | 26e5c04abfb81bdcbd4d89d9f9329b8433920b26 (diff) | |
libinterimap: factor out hostname/IP parsing.
Also, document that enclosing 'host' value in square brackets forces its
interpretation as an IP literal (hence skips name resolution).
| -rw-r--r-- | Changelog | 3 | ||||
| -rw-r--r-- | doc/interimap.1.md | 4 | ||||
| -rw-r--r-- | doc/pullimap.1.md | 4 | ||||
| -rw-r--r-- | lib/Net/IMAP/InterIMAP.pm | 61 | 
4 files changed, 51 insertions, 21 deletions
@@ -4,6 +4,9 @@ interimap (0.5.4) upstream;   + test suite: always generate new certificates on `make test`.  Hence     running `make test` now requires OpenSSL 1.1.1 or later.   + test suite: sign all test certificates with the same root CA. + + libinterimap: factor out hostname/IP parsing. + + document that enclosing 'host' value in square brackets forces its +   interpretation as an IP literal (hence skips name resolution).   - documentation: replace example.org with example.net for consistency.   -- Guilhem Moulin <guilhem@fripost.org>  Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 2c9ddc6..ab35275 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -317,7 +317,9 @@ Valid options are:  *host* -:   Server hostname, for `type=imap` and `type=imaps`. +:   Server hostname or IP address, for `type=imap` and `type=imaps`. +    The value can optionally be enclosed in square brackets to force its +    interpretation as an IP literal (hence skip name resolution).      (Default: `localhost`.)  *port* diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index 42ea282..57790a6 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -139,7 +139,9 @@ Valid options are:  *host* -:   Server hostname, for `type=imap` and `type=imaps`. +:   Server hostname or IP address, for `type=imap` and `type=imaps`. +    The value can optionally be enclosed in square brackets to force its +    interpretation as an IP literal (hence skip name resolution).      (Default: `localhost`.)  *port* diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 849dc0f..e3a539d 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1446,23 +1446,39 @@ my $RE_IPv6 = do {        | (?: (?: $h16 : ){0,6} $h16 )? ::        /x }; +# Parse an IPv4 or IPv6.  In list context, return a pair (IP, family), +# otherwise only the IP.  If the argument is not an IP (for instance if +# it's a hostname), then return (undef, undef) resp. undef.  The input +# can optionaly be enclosed in square brackets which forces its +# interpretation as an IP literal: an error is raised if it is not the +# case. +my $RE_IPv4_anchored = qr/\A($RE_IPv4)\z/; +my $RE_IPv6_anchored = qr/\A($RE_IPv6)\z/; +sub _parse_hostip($) { +    my $v = shift // return; +    my $literal = $v =~ s/\A\[(.*)\]\z/$1/ ? 1 : 0; +    my ($ip, $af) = $v =~ $RE_IPv4_anchored ? ($1, AF_INET) +                  : $v =~ $RE_IPv6_anchored ? ($1, AF_INET6) +                  : (undef, undef); +    die "Invalid IP literal: $v\n" if $literal and !defined($ip); +    return wantarray ? ($ip, $af) : $ip; +}  # Opens a TCP socket to the given $host and $port.  sub _tcp_connect($$$) {      my ($self, $host, $port) = @_;      my %hints = (socktype => SOCK_STREAM, protocol => IPPROTO_TCP); -    if ($host =~ qr/\A$RE_IPv4\z/) { -        $hints{family} = AF_INET; -        $hints{flags} |= AI_NUMERICHOST; -    } elsif ($host =~ qr/\A\[($RE_IPv6)\]\z/) { -        $host = $1; -        $hints{family} = AF_INET6; +    my ($host2, $family) = _parse_hostip($host); +    if (defined $family) { +        $hints{family} = $family;          $hints{flags} |= AI_NUMERICHOST; +    } else { +        $host2 = $host;      } -    my ($err, @res) = getaddrinfo($host, $port, \%hints); -    $self->fail("Can't getaddrinfo: $err") if $err ne ''; +    my ($err, @res) = getaddrinfo($host2, $port, \%hints); +    $self->fail("getaddrinfo($host2): $err") if $err ne '';      SOCKETS:      foreach my $ai (@res) { @@ -1520,7 +1536,7 @@ sub _proxify($$$$) {      $port = getservbyname($port, 'tcp') // $self->fail("Can't getservbyname $port")          unless $port =~ /\A[0-9]+\z/; -    $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?($RE_IPv4|\[$RE_IPv6\]|[^:]+)(:[A-Za-z0-9]+)?\z/ +    $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?([^:]+|\[[^\]]+\])(:[A-Za-z0-9]+)?\z/          or $self->fail("Invalid proxy URI $proxy");      my ($proto, $userpass, $proxyhost, $proxyport) = ($1, $2, $3, $4);      $userpass =~ s/\@\z// if defined $userpass; @@ -1553,23 +1569,30 @@ sub _proxify($$$$) {              $self->fail('SOCKSv5', 'No acceptable authentication methods');          } -        if ($host !~ /\A(?:$RE_IPv4|\[$RE_IPv6\])\z/ and !$resolv) { +        my ($hostip, $fam) = _parse_hostip($host); +        unless (defined($fam) or $resolv) {              # resove the hostname $host locally              my ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_RAW}); -            $self->fail("Can't getaddrinfo: $err") if $err ne ''; -            ($host) = first { defined $_ } map { +            $self->fail("getaddrinfo($host): $err") if $err ne ''; +            my ($addr) = first { defined($_) } map {                  my ($err, $ipaddr) = getnameinfo($_->{addr}, NI_NUMERICHOST, NIx_NOSERV); -                $err eq '' ? $ipaddr : undef +                $err eq '' ? [$ipaddr,$_->{family}] : undef              } @res; -            $self->fail("Can't getnameinfo") unless defined $host; +            $self->fail("Can't getnameinfo") unless defined $addr; +            ($hostip, $fam) = @$addr;          }          # send a CONNECT command (CMD 0x01) -        my ($typ, $addr) = -            $host =~ /\A$RE_IPv4\z/                                      ? (0x01, Socket::inet_pton(AF_INET, $host)) -          : ($host =~ /\A\[($RE_IPv6)\]\z/ or $host =~ /\A($RE_IPv6)\z/) ? (0x04, Socket::inet_pton(AF_INET6, $1)) -          :                                                                (0x03, pack('C',length($host)).$host); -        $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ).$addr.pack('n', $port)); +        my ($typ, $addr); +        if (defined $fam) { +            $typ = $fam == AF_INET ? 0x01 : $fam == AF_INET6 ? 0x04 : $self->panic(); +            $addr = Socket::inet_pton($fam, $hostip); +        } else { +            # let the SOCKS server do the resolution +            $typ = 0x03; +            $addr = pack('C',length($host)) . $host; +        } +        $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ) . $addr . pack('n', $port));          ($v2, my $r, my $rsv, $typ) = unpack('C4', $self->_xread($socket, 4));          $self->fail('SOCKSv5', 'Invalid protocol') unless $v == $v2 and $rsv == 0x00;  | 
