From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 2447F1FB05 for ; Fri, 30 Apr 2021 09:24:39 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 6/8] lei: IMAP .onion support via --proxy=s switch Date: Fri, 30 Apr 2021 09:24:36 +0000 Message-Id: <20210430092438.20926-7-e@80x24.org> In-Reply-To: <20210430092438.20926-1-e@80x24.org> References: <20210430092438.20926-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Mail::IMAPClient provides the ability to pass a pre-connected Socket to it. We can rely on this functionality to use IO::Socket::Socks in place whatever socket class Mail::IMAPClient chooses to use. The --proxy=s is shared with curl(1), though we only support socks5h:// at the moment. Is there any need for SOCKS4 or SOCKS5 without name resolution? Tor .onions require socks5h:// for name resolution and to prevent data leakage. --- lib/PublicInbox/LEI.pm | 12 ++++++++---- lib/PublicInbox/LeiInput.pm | 2 +- lib/PublicInbox/LeiToMail.pm | 4 ++-- lib/PublicInbox/NetReader.pm | 31 ++++++++++++++++++++++++++++--- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index 6a82d497..bb67fc0b 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -188,7 +188,8 @@ our %CMD = ( # sorted in order of importance/use: qw(stdin| threads|t from|f=s mid=s oid=s), @c_opt ], 'tag' => [ 'KEYWORDS...', 'set/unset keywords and/or labels on message(s)', - qw(stdin| in-format|F=s input|i=s@ oid=s@ mid=s@), @c_opt, + qw(stdin| in-format|F=s input|i=s@ oid=s@ mid=s@), + qw(no-torsocks torsocks=s), PublicInbox::LeiQuery::curl_opt(), @c_opt, pass_through('-kw:foo for delete') ], 'forget' => [ '[--stdin|--oid=OID|--by-mid=MID]', "exclude message(s) on stdin from `q' search results", @@ -211,11 +212,12 @@ our %CMD = ( # sorted in order of importance/use: 'import' => [ 'LOCATION...|--stdin', 'one-time import/update from URL or filesystem', qw(stdin| offset=i recursive|r exclude=s include|I=s - lock=s@ in-format|F=s kw! verbose|v+ incremental! mail-sync!), @c_opt ], + lock=s@ in-format|F=s kw! verbose|v+ incremental! mail-sync!), + qw(no-torsocks torsocks=s), PublicInbox::LeiQuery::curl_opt(), @c_opt ], 'convert' => [ 'LOCATION...|--stdin', 'one-time conversion from URL or filesystem to another format', - qw(stdin| in-format|F=s out-format|f=s output|mfolder|o=s - lock=s@ kw!), @c_opt ], + qw(stdin| in-format|F=s out-format|f=s output|mfolder|o=s lock=s@ kw!), + qw(no-torsocks torsocks=s), PublicInbox::LeiQuery::curl_opt(), @c_opt ], 'p2q' => [ 'FILE|COMMIT_OID|--stdin', "use a patch to generate a query for `lei q --stdin'", qw(stdin| want|w=s@ uri debug), @c_opt ], @@ -277,6 +279,8 @@ my %OPTDESC = ( 'path-a|a=s' => 'pre-image pathname associated with OID', 'path-b|b=s' => 'post-image pathname associated with OID', 'git-dir=s@' => 'additional git repository to scan', +'proxy=s' => [ 'PROTO://HOST[:PORT]', # shared with curl(1) + "proxy for (e.g. `socks5h://0:9050')" ], 'torsocks=s' => ['VAL|auto|no|yes', 'whether or not to wrap git and curl commands with torsocks'], 'no-torsocks' => 'alias for --torsocks=no', diff --git a/lib/PublicInbox/LeiInput.pm b/lib/PublicInbox/LeiInput.pm index 277ad88d..86f300c3 100644 --- a/lib/PublicInbox/LeiInput.pm +++ b/lib/PublicInbox/LeiInput.pm @@ -294,7 +294,7 @@ $input is `eml', not --in-format=$in_fmt } if ($net) { $net->{-can_die} = 1; - if (my $err = $net->errors) { + if (my $err = $net->errors($lei)) { return $lei->fail($err); } $net->{quiet} = $lei->{opt}->{quiet}; diff --git a/lib/PublicInbox/LeiToMail.pm b/lib/PublicInbox/LeiToMail.pm index fa3af710..eda4701c 100644 --- a/lib/PublicInbox/LeiToMail.pm +++ b/lib/PublicInbox/LeiToMail.pm @@ -351,14 +351,14 @@ sub new { require PublicInbox::MboxReader; $self->can("eml2$fmt") or die "bad mbox format: $fmt\n"; $self->{base_type} = 'mbox'; - } elsif ($fmt =~ /\Aimaps?\z/) { # TODO .onion support + } elsif ($fmt =~ /\Aimaps?\z/) { require PublicInbox::NetWriter; require PublicInbox::URIimap; my $net = PublicInbox::NetWriter->new; $net->{quiet} = $lei->{opt}->{quiet}; my $uri = PublicInbox::URIimap->new($dst)->canonical; $net->add_url($uri); - my $err = $net->errors; + my $err = $net->errors($lei); return $lei->fail($err) if $err; $uri->mailbox or return $lei->fail("No mailbox: $dst"); $self->{uri} = $uri; diff --git a/lib/PublicInbox/NetReader.pm b/lib/PublicInbox/NetReader.pm index b9365c05..ac23e701 100644 --- a/lib/PublicInbox/NetReader.pm +++ b/lib/PublicInbox/NetReader.pm @@ -7,6 +7,7 @@ use strict; use v5.10.1; use parent qw(Exporter PublicInbox::IPC); use PublicInbox::Eml; +use PublicInbox::Config; our %IMAPflags2kw = map {; "\\\u$_" => $_ } qw(seen answered flagged draft); $IMAPflags2kw{'$Forwarded'} = 'forwarded'; # RFC 5550 @@ -51,7 +52,16 @@ sub mic_for ($$$$) { # mic = Mail::IMAPClient %$common, # may set Starttls, Compress, Debug .... }; require PublicInbox::IMAPClient; - my $mic = PublicInbox::IMAPClient->new(%$mic_arg) or + my %socks; + if ($lei && $lei->{socks5h}) { + my %opt = %{$lei->{socks5h}}; + $opt{ConnectAddr} = delete $mic_arg->{Server}; + $opt{ConnectPort} = delete $mic_arg->{Port}; + $socks{Socket} = IO::Socket::Socks->new(%opt) or die + "E: <$url> ".eval('$IO::Socket::Socks::SOCKS_ERROR'); + $self->{mic_socks5h} = \%opt; + } + my $mic = PublicInbox::IMAPClient->new(%$mic_arg, %socks) or die "E: <$url> new: $@\n"; # default to using STARTTLS if it's available, but allow @@ -331,7 +341,7 @@ sub add_url { } sub errors { - my ($self) = @_; + my ($self, $lei) = @_; if (my $u = $self->{unsupported_url}) { return "Unsupported URL(s): @$u"; } @@ -343,6 +353,16 @@ sub errors { eval { require Net::NNTP } or die "Net::NNTP is required for NNTP:\n$@\n"; } + if ($lei && (($lei->{opt}->{proxy}//'') =~ m!\Asocks5h:// + (?: \[ ([^\]]+) \] | ([^:/]+) ) + (?::([0-9]+))?/?(?:,|\z)!ix)) { + my ($h, $p) = ($1 // $2, $3 + 0); + $h = '127.0.0.1' if $h eq '0'; + eval { require IO::Socket::Socks } or die <{socks5h} = { ProxyAddr => $h, ProxyPort => $p }; + } undef; } @@ -507,7 +527,12 @@ sub mic_get { $mic_arg->{Authcallback} = $self->can($cb_name); } } - my $mic = PublicInbox::IMAPClient->new(%$mic_arg); + my %socks; + if (my $s5h = $self->{mic_socks5h}) { + $socks{Socket} = IO::Socket::Socks->new(%$s5h) or die + "E: <$$uri> ".eval('$IO::Socket::Socks::SOCKS_ERROR'); + } + my $mic = PublicInbox::IMAPClient->new(%$mic_arg, %socks); $cached //= {}; # invalid placeholder if no cache enabled $mic && $mic->IsConnected ? ($cached->{$sec} = $mic) : undef; }