Konstantin Ryabitsev <[email protected]> wrote:
> On Wed, Jun 14, 2023 at 11:50:15PM +0000, Eric Wong wrote:
> > affects WWW and NNTP, but not IMAP/POP3. I'm not sure if I want
> > to reintroduce header injection in case there's some conflict
> > with DKIM or other signature mechanisms[1]
>
> I don't think we need to worry about it if we pick a header that's almost
> certain to not be included in the default DKIM signature set.
> X-Originally-Archived-At: or some other header is guaranteed to never be
> signed.
*shrug* I'm not sure how useful this is, actually.
-----------8<---------
Subject: [PATCH] support publicinbox.$FOO.appendHeader in read-only endpoints
This may be used to inject arbitrary headers in the raw message
from any read-only endpoints. This may be useful for mirrors to
indicate they're mirroring another source and can't edit/remove
messages easily.
This is set per-inbox and supports multiple key:value pairs:
[publicinbox "foo"]
appendheader = KEY1:VALUE1
appendheader = KEY2:VALUE2
No variable expansion is currently supported as it's unclear if
it's necessary (e.g. for Message-ID in URLs).
Link: https://public-inbox.org/meta/20230615-focal-erosion-poop-df8246@meerkat/
---
lib/PublicInbox/Config.pm | 2 +-
lib/PublicInbox/Eml.pm | 20 +++++++++++++++
lib/PublicInbox/IMAP.pm | 17 ++++++++++---
lib/PublicInbox/Inbox.pm | 8 ++++++
lib/PublicInbox/Mbox.pm | 8 +++---
lib/PublicInbox/NNTP.pm | 2 ++
lib/PublicInbox/POP3.pm | 2 +-
t/netd.t | 53 ++++++++++++++++++++++++++++++++-------
8 files changed, 94 insertions(+), 18 deletions(-)
diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm
index 2f1b4122..c3ec1793 100644
--- a/lib/PublicInbox/Config.pm
+++ b/lib/PublicInbox/Config.pm
@@ -457,7 +457,7 @@ sub _fill_ibx {
# more things to encourage decentralization
for my $k (qw(address altid nntpmirror imapmirror
coderepo hide listid url
- infourl watchheader
+ infourl watchheader appendheader
nntpserver imapserver pop3server)) {
my $v = $self->{"$pfx.$k"} // next;
$ibx->{$k} = _array($v);
diff --git a/lib/PublicInbox/Eml.pm b/lib/PublicInbox/Eml.pm
index 8b999e1a..3f7e27e0 100644
--- a/lib/PublicInbox/Eml.pm
+++ b/lib/PublicInbox/Eml.pm
@@ -392,6 +392,26 @@ sub header_str_set {
header_set($self, $name, @vals);
}
+# only used for publicinbox.$NAME.appendHeader
+sub header_append {
+ my ($self, $pfx, @vals) = @_;
+ $pfx .= ': ';
+ my $len = 78 - length($pfx);
+ @vals = map {;
+ utf8::encode(my $v = $_); # to bytes, support SMTPUTF8
+ # folding differs from Email::Simple::Header,
+ # we favor tabs for visibility (and space savings :P)
+ if (length($_) >= $len && (/\n[^ \t]/s || !/\n/s)) {
+ local $Text::Wrap::columns = $len;
+ local $Text::Wrap::huge = 'overflow';
+ $pfx . wrap('', "\t", $v) . $self->{crlf};
+ } else {
+ $pfx . $v . $self->{crlf};
+ }
+ } @vals;
+ ${$self->{hdr}} .= join('', @vals);
+}
+
sub mhdr_decode ($) {
eval { $MIME_Header->decode($_[0], Encode::FB_DEFAULT) } // $_[0];
}
diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index 00f99ef7..dd55a2e6 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -642,7 +642,7 @@ sub emit_rfc822_header {
$self->msg_more(${$eml->{hdr}});
}
-# n.b. this is sorted to be after any emit_eml_new ops
+# n.b. this is sorted to be after any op_eml_new ops
sub emit_rfc822_text {
my ($self, $k, undef, $bref) = @_;
$self->msg_more(" $k {".length($$bref)."}\r\n");
@@ -668,9 +668,20 @@ sub to_crlf_full {
${$_[0]} =~ s/\A[\r\n]*From [^\r\n]*\r\n//s;
}
-sub op_crlf_bref { to_crlf_full($_[3]) }
+# used by PublicInbox::POP3, too
+sub op_crlf_bref {
+ if ($_[0]->{ibx}->{appendheader}) {
+ my $eml = PublicInbox::Eml->new($_[3]);
+ $_[0]->{ibx}->append_headers($eml);
+ ${$_[3]} = $eml->as_string; # replace bref
+ }
+ to_crlf_full($_[3]);
+}
-sub op_crlf_hdr { to_crlf_full($_[4]->{hdr}) }
+sub op_crlf_hdr { # $_[4] = eml
+ $_[0]->{ibx}->append_headers($_[4]) if $_[0]->{ibx}->{appendheader};
+ to_crlf_full($_[4]->{hdr});
+}
sub op_crlf_bdy { ${$_[4]->{bdy}} =~ s/(?<!\r)\n/\r\n/sg if $_[4]->{bdy} }
diff --git a/lib/PublicInbox/Inbox.pm b/lib/PublicInbox/Inbox.pm
index 9afbb478..d5108e4a 100644
--- a/lib/PublicInbox/Inbox.pm
+++ b/lib/PublicInbox/Inbox.pm
@@ -410,4 +410,12 @@ sub mailboxid { # rfc 8474, 8620, 8621
sub thing_type { 'public inbox' }
+sub append_headers {
+ my ($self, $eml) = @_;
+ # TODO: do we need MSGID expansions?
+ for (@{$self->{appendheader}}) {
+ $eml->header_append(split(/\s*:\s*/, $_, 2));
+ }
+}
+
1;
diff --git a/lib/PublicInbox/Mbox.pm b/lib/PublicInbox/Mbox.pm
index bf61bb0e..0b47cb8c 100644
--- a/lib/PublicInbox/Mbox.pm
+++ b/lib/PublicInbox/Mbox.pm
@@ -89,15 +89,15 @@ sub emit_raw {
sub msg_hdr ($$) {
my ($ctx, $eml) = @_;
- my $header_obj = $eml->header_obj;
# drop potentially confusing headers, ssoma already should've dropped
# Lines and Content-Length
foreach my $d (qw(Lines Bytes Content-Length Status)) {
- $header_obj->header_set($d);
+ $eml->header_set($d);
}
- my $crlf = $header_obj->crlf;
- my $buf = $header_obj->as_string;
+ $ctx->{ibx}->append_headers($eml) if $ctx->{ibx}->{appendheader};
+ my $crlf = $eml->{crlf};
+ my $buf = ${$eml->{hdr}};
# fixup old bug from import (pre-a0c07cba0e5d8b6a)
$buf =~ s/\A[\r\n]*From [^\r\n]*\r?\n//s;
"From mboxrd\@z Thu Jan 1 00:00:00 1970" . $crlf . $buf . $crlf;
diff --git a/lib/PublicInbox/NNTP.pm b/lib/PublicInbox/NNTP.pm
index 316b7775..91e6357f 100644
--- a/lib/PublicInbox/NNTP.pm
+++ b/lib/PublicInbox/NNTP.pm
@@ -462,6 +462,8 @@ sub set_nntp_headers ($$) {
# *something* here is required for leafnode, try to follow
# RFC 5536 3.1.5...
$hdr->header_set('Path', $server_name . '!not-for-mail');
+
+ $ibx->append_headers($hdr) if $ibx->{appendheader};
}
sub art_lookup ($$$) {
diff --git a/lib/PublicInbox/POP3.pm b/lib/PublicInbox/POP3.pm
index d32793e4..863cb201 100644
--- a/lib/PublicInbox/POP3.pm
+++ b/lib/PublicInbox/POP3.pm
@@ -229,7 +229,7 @@ sub retr_cb { # called by git->cat_async via ibx_async_cat
$self->close;
die "BUG: $hex != $oid";
}
- PublicInbox::IMAP::to_crlf_full($bref);
+ PublicInbox::IMAP::op_crlf_bref($self, undef, undef, $bref);
if (defined $top_nr) {
my ($hdr, $bdy) = split(/\r\n\r\n/, $$bref, 2);
$bref = \$hdr;
diff --git a/t/netd.t b/t/netd.t
index abdde124..47a0182f 100644
--- a/t/netd.t
+++ b/t/netd.t
@@ -6,7 +6,8 @@ use Socket qw(IPPROTO_TCP SOL_SOCKET);
use PublicInbox::TestCommon;
# IO::Poll and Net::NNTP are part of the standard library, but
# distros may split them off...
-require_mods(qw(-imapd IO::Socket::SSL Mail::IMAPClient IO::Poll Net::NNTP));
+require_mods(qw(-imapd IO::Socket::SSL Mail::IMAPClient IO::Poll Net::NNTP
+ Net::POP3));
my $imap_client = 'Mail::IMAPClient';
$imap_client->can('starttls') or
plan skip_all => 'Mail::IMAPClient does not support TLS';
@@ -21,6 +22,7 @@ unless (-r $key && -r $cert) {
use_ok 'PublicInbox::TLS';
use_ok 'IO::Socket::SSL';
require_git('2.6');
+require_mods(qw(File::FcntlLock)) if $^O !~ /\A(?:linux|freebsd)\z/;
my ($tmpdir, $for_destroy) = tmpdir();
my $err = "$tmpdir/stderr.log";
@@ -35,7 +37,8 @@ for (1..3) {
pipe(my ($r, $w)) or xbail "pipe: $!";
push @pad_pipes, $r, $w;
};
-my %srv = map { $_ => tcp_server() } qw(imap nntp imaps nntps);
+my %srv = map { $_ => tcp_server() } qw(imap nntp imaps nntps pop3 http);
+my ($hdr_key, $hdr_val) = qw(x-archive-source https://example.com/);
my $ibx = create_inbox 'netd', version => 2,
-primary_address => $addr, indexlevel => 'basic', sub {
my ($im, $ibx) = @_;
@@ -43,11 +46,14 @@ my $ibx = create_inbox 'netd', version => 2,
$pi_config = "$ibx->{inboxdir}/pi_config";
open my $fh, '>', $pi_config or BAIL_OUT "open: $!";
print $fh <<EOF or BAIL_OUT "print: $!";
+[publicinbox]
+ pop3state = $tmpdir/p3state
[publicinbox "netd"]
inboxdir = $ibx->{inboxdir}
address = $addr
indexlevel = basic
newsgroup = $group
+ appendHeader = $hdr_key:$hdr_val
EOF
close $fh or BAIL_OUT "close: $!\n";
};
@@ -70,16 +76,45 @@ my %o = (
SSL_verify_mode => SSL_VERIFY_PEER(),
SSL_ca_file => 'certs/test-ca.pem',
);
+
+my $ok_inject = sub {
+ my ($blob, $msg) = @_;
+ my $eml = PublicInbox::Eml->new($blob);
+ is_deeply([$eml->header($hdr_key)], [ $hdr_val ], "$msg header added");
+};
+
+{
+ my ($host, $port) = tcp_host_port($srv{imap});
+ my %mic_opt = (Server => $host, Port => $port, Uid => 1);
+ $mic_opt{Authmechanism} = 'ANONYMOUS';
+ $mic_opt{Authcallback} = sub { '' };
+ my $mic = $imap_client->new(%mic_opt);
+ ok($mic && $mic->examine("$group.0"), 'IMAP connected');
+ my $ret = $mic->fetch_hash(1, 'RFC822');
+ $ok_inject->($ret->{1}->{RFC822}, 'IMAP RFC822 (full)');
+ $ret = $mic->fetch_hash(1, 'RFC822.HEADER');
+ $ok_inject->($ret->{1}->{'RFC822.HEADER'}, 'IMAP RFC822.HEADER');
+}
+{
+ my $nntp = Net::NNTP->new(my $host_port = tcp_host_port($srv{nntp}));
+ ok($nntp && $nntp->group($group), 'NNTP group');
+ $ok_inject->(join('', @{$nntp->article(1)}), 'NNTP ->article');
+ $ok_inject->(join('', @{$nntp->head(1)}), 'NNTP ->head');
+}
{
- my $c = tcp_connect($srv{imap});
- my $msg = <$c>;
- like($msg, qr/IMAP4rev1/, 'connected to IMAP');
+ my ($host, $port) = tcp_host_port($srv{pop3});
+ my $pop3 = Net::POP3->new($host, Port => $port);
+ my $locked_mb = ('e'x32)."\@$group";
+ ok($pop3 && $pop3->apop("$locked_mb.0", 'anonymous'), 'APOP connected');
+ $ok_inject->(join('', @{$pop3->get(1)}), 'POP3 ->get');
}
{
- my $c = tcp_connect($srv{nntp});
- my $msg = <$c>;
- like($msg, qr/^201 .*? ready - post via email/, 'connected to NNTP');
+ my $c = tcp_connect($srv{http});
+ ok($c and print $c <<EOM, 'HTTP connected');
+GET /netd/20180720072141.GA15957\@example/raw HTTP/1.0\r\n\r
+EOM
+ my $s = do { local $/; <$c> };
+ $ok_inject->((split(/\r\n\r\n/, $s, 2))[1], 'HTTP $MSGID/raw');
}
-# TODO: more tests
done_testing;