X-Git-Url: http://www.git.stargrave.org/?p=public-inbox.git;a=blobdiff_plain;f=lib%2FPublicInbox%2FNNTP.pm;h=ab6eb525106d9046ca5f1f898787582210dba7b5;hp=18822d3b014a097482718e29b4f627c2286f9478;hb=0c62cffc23894a2f5eb1d6c9ee29973f4863a365;hpb=9dfc0b670fc634b54998c3020f173b82de1915ac diff --git a/lib/PublicInbox/NNTP.pm b/lib/PublicInbox/NNTP.pm index 18822d3b..ab6eb525 100644 --- a/lib/PublicInbox/NNTP.pm +++ b/lib/PublicInbox/NNTP.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2021 all contributors +# Copyright (C) all contributors # License: AGPL-3.0+ # # Each instance of this represents a NNTP client socket @@ -9,6 +9,7 @@ # long_cb: long_response private data package PublicInbox::NNTP; use strict; +use v5.10.1; use parent qw(PublicInbox::DS); use PublicInbox::MID qw(mid_escape $MID_EXTRACT); use PublicInbox::Eml; @@ -23,12 +24,11 @@ use constant { LINE_MAX => 512, # RFC 977 section 2.3 r501 => '501 command syntax error', r502 => '502 Command unavailable', - r221 => '221 Header follows', + r221 => "221 Header follows\r\n", r224 => '224 Overview information follows (multi-line)', - r225 => '225 Headers follow (multi-line)', + r225 => "225 Headers follow (multi-line)\r\n", r430 => '430 No article with that message-id', }; -use PublicInbox::Syscall qw(EPOLLIN EPOLLONESHOT); use Errno qw(EAGAIN); my $ONE_MSGID = qr/\A$MID_EXTRACT\z/; my @OVERVIEW = qw(Subject From Date Message-ID References); @@ -46,32 +46,17 @@ HDR\r OVER\r COMPRESS DEFLATE\r -sub greet ($) { $_[0]->write($_[0]->{nntpd}->{greet}) }; - -sub new ($$$) { - my ($class, $sock, $nntpd) = @_; - my $self = bless { nntpd => $nntpd }, $class; - my $ev = EPOLLIN; - my $wbuf; - if ($sock->can('accept_SSL') && !$sock->accept_SSL) { - return CORE::close($sock) if $! != EAGAIN; - $ev = PublicInbox::TLS::epollbit() or return CORE::close($sock); - $wbuf = [ \&PublicInbox::DS::accept_tls_step, \&greet ]; - } - $self->SUPER::new($sock, $ev | EPOLLONESHOT); - if ($wbuf) { - $self->{wbuf} = $wbuf; - } else { - greet($self); - } - $self->update_idle_time; - $self; +sub do_greet ($) { $_[0]->write($_[0]->{nntpd}->{greet}) }; + +sub new { + my ($cls, $sock, $nntpd) = @_; + (bless { nntpd => $nntpd }, $cls)->greet($sock) } sub args_ok ($$) { my ($cb, $argc) = @_; my $tot = prototype $cb; - my ($nreq, undef) = split(';', $tot); + my ($nreq, undef) = split(/;/, $tot); $nreq = ($nreq =~ tr/$//) - 1; $tot = ($tot =~ tr/$//) - 1; ($argc <= $tot && $argc >= $nreq); @@ -82,8 +67,8 @@ sub process_line ($$) { my ($self, $l) = @_; my ($req, @args) = split(/[ \t]+/, $l); return 1 unless defined($req); # skip blank line - $req = $self->can('cmd_'.lc($req)); - return res($self, '500 command not recognized') unless $req; + $req = $self->can('cmd_'.lc($req)) // + return $self->write(\"500 command not recognized\r\n"); return res($self, r501) unless args_ok($req, scalar @args); my $res = eval { $req->($self, @args) }; @@ -117,7 +102,7 @@ sub cmd_slave ($) { '202 slave status noted' } sub cmd_xgtitle ($;$) { my ($self, $wildmat) = @_; - more($self, '282 list of groups and descriptions follows'); + $self->msg_more("282 list of groups and descriptions follows\r\n"); list_newsgroups($self, $wildmat); } @@ -150,7 +135,7 @@ sub list_active_times_i { for my $ngname (@window) { my $ibx = $groups->{$ngname} or next; my $c = eval { $ibx->uidvalidity } // time; - more($self, "$ngname $c <$ibx->{-primary_address}>"); + $self->msg_more("$ngname $c <$ibx->{-primary_address}>\r\n"); } scalar(@$groupnames); # continue if there's more } @@ -169,7 +154,7 @@ sub list_newsgroups_i { my $ibx; for my $ngname (@window) { $ibx = $groups->{$ngname} and - more($self, "$ngname ".$ibx->description); + $self->msg_more("$ngname ".$ibx->description."\r\n"); } scalar(@$groupnames); # continue if there's more } @@ -191,10 +176,10 @@ sub cmd_list ($;$$) { $arg = "list_$arg"; $arg = $self->can($arg); return r501 unless $arg && args_ok($arg, scalar @args); - more($self, '215 information follows'); + $self->msg_more("215 information follows\r\n"); $arg->($self, @args); } else { - more($self, '215 list of newsgroups follows'); + $self->msg_more("215 list of newsgroups follows\r\n"); long_response($self, \&list_active_i, [ # copy array @{$self->{nntpd}->{groupnames}} ]); } @@ -202,17 +187,17 @@ sub cmd_list ($;$$) { sub listgroup_range_i { my ($self, $beg, $end) = @_; - my $r = $self->{ibx}->mm->msg_range($beg, $end, 'num'); + my $r = $self->{ibx}->mm(1)->msg_range($beg, $end, 'num'); scalar(@$r) or return; - $self->msg_more(join('', map { "$_->[0]\r\n" } @$r)); + $self->msg_more(join("\r\n", @$r, '')); 1; } sub listgroup_all_i { my ($self, $num) = @_; - my $ary = $self->{ibx}->mm->ids_after($num); + my $ary = $self->{ibx}->over(1)->ids_after($num); scalar(@$ary) or return; - more($self, join("\r\n", @$ary)); + $self->msg_more(join("\r\n", @$ary, '')); 1; } @@ -221,7 +206,7 @@ sub cmd_listgroup ($;$$) { if (defined $group) { my $res = cmd_group($self, $group); return $res if ($res !~ /\A211 /); - more($self, $res); + $self->msg_more($res .= "\r\n"); } $self->{ibx} or return '412 no newsgroup selected'; if (defined $range) { @@ -241,7 +226,7 @@ sub parse_time ($$;$) { $gmt = 1; } my ($YYYY, $MM, $DD); - if (bytes::length($date) == 8) { # RFC 3977 allows YYYYMMDD + if (length($date) == 8) { # RFC 3977 allows YYYYMMDD ($YYYY, $MM, $DD) = unpack('A4A2A2', $date); } else { # legacy clients send YYMMDD my $YY; @@ -261,8 +246,8 @@ sub parse_time ($$;$) { sub group_line ($$) { my ($self, $ibx) = @_; - my ($min, $max) = $ibx->mm->minmax; - more($self, "$ibx->{newsgroup} $max $min n"); + my ($min, $max) = $ibx->mm(1)->minmax; + $self->msg_more("$ibx->{newsgroup} $max $min n\r\n"); } sub newgroups_i { @@ -284,7 +269,7 @@ sub cmd_newgroups ($$$;$$) { return r501 if $@; # TODO dists - more($self, '231 list of new newsgroups follows'); + $self->msg_more("231 list of new newsgroups follows\r\n"); long_response($self, \&newgroups_i, $ts, \(my $i = 0), $self->{nntpd}->{groupnames}); } @@ -348,8 +333,8 @@ sub cmd_newnews ($$$$;$$) { my ($self, $newsgroups, $date, $time, $gmt, $dists) = @_; my $ts = eval { parse_time($date, $time, $gmt) }; return r501 if $@; - more($self, '230 list of new articles by message-id follows'); - my ($keep, $skip) = split('!', $newsgroups, 2); + $self->msg_more("230 list of new articles by message-id follows\r\n"); + my ($keep, $skip) = split(/!/, $newsgroups, 2); ngpat2re($keep); ngpat2re($skip); my @names = grep(!/$skip/, grep(/$keep/, @@ -367,7 +352,7 @@ sub cmd_group ($$) { $nntpd->idler_start; $self->{ibx} = $ibx; - my ($min, $max) = $ibx->mm->minmax; + my ($min, $max) = $ibx->mm(1)->minmax; $self->{article} = $min; my $est_size = $max - $min; "211 $est_size $min $max $group"; @@ -381,11 +366,10 @@ sub article_adj ($$) { defined $n or return '420 no current article has been selected'; $n += $off; - my $mid = $ibx->mm->mid_for($n); - unless ($mid) { + my $mid = $ibx->mm(1)->mid_for($n) // do { $n = $off > 0 ? 'next' : 'previous'; return "421 no $n article in this group"; - } + }; $self->{article} = $n; "223 $n <$mid> article retrieved - request text separately"; } @@ -404,20 +388,11 @@ sub cmd_post ($) { sub cmd_quit ($) { my ($self) = @_; - res($self, '205 closing connection - goodbye!'); + $self->write(\"205 closing connection - goodbye!\r\n"); $self->shutdn; undef; } -sub header_append ($$$) { - my ($hdr, $k, $v) = @_; - my @v = $hdr->header_raw($k); - foreach (@v) { - return if $v eq $_; - } - $hdr->header_set($k, @v, $v); -} - sub xref_by_tc ($$$) { my ($xref, $pi_cfg, $smsg) = @_; my $by_addr = $pi_cfg->{-by_addr}; @@ -427,9 +402,8 @@ sub xref_by_tc ($$$) { $by_addr->{lc($_)} // () } (PublicInbox::Address::emails($smsg->{$f} // '')); for my $ibx (@ibxs) { - my $ngname = $ibx->{newsgroup} // next; - next if defined $xref->{$ngname}; - $xref->{$ngname} = eval { $ibx->mm->num_for($mid) }; + $xref->{$ibx->{newsgroup}} //= + $ibx->mm(1)->num_for($mid); } } } @@ -446,13 +420,15 @@ sub xref ($$$) { $xref = { $cur_ng => $smsg->{num} }; my $mid = $smsg->{mid}; for my $ibx (values %{$nntpd->{pi_cfg}->{-by_newsgroup}}) { - next if defined($xref->{$ibx->{newsgroup}}); - my $num = eval { $ibx->mm->num_for($mid) } // next; - $xref->{$ibx->{newsgroup}} = $num; + $xref->{$ibx->{newsgroup}} //= + $ibx->mm(1)->num_for($mid); } } my $ret = "$nntpd->{servername} $cur_ng:".delete($xref->{$cur_ng}); - $ret .= " $_:$xref->{$_}" for (sort keys %$xref); + for my $ng (sort keys %$xref) { + my $num = $xref->{$ng} // next; + $ret .= " $ng:$num"; + } $ret; } @@ -513,7 +489,7 @@ find_ibx: return '412 no newsgroup has been selected'; } found: - my $smsg = $ibx->over->get_art($n) or return $err; + my $smsg = $ibx->over(1)->get_art($n) or return $err; $smsg->{-ibx} = $ibx; if ($code == 223) { # STAT set_art($self, $n); @@ -524,7 +500,7 @@ found: $smsg->{nntp_code} = $code; set_art($self, $art); # this dereferences to `undef' - ${git_async_cat($ibx->git, $smsg->{blob}, \&blob_cb, $smsg)}; + ${ibx_async_cat($ibx, $smsg->{blob}, \&blob_cb, $smsg)}; } } @@ -558,7 +534,7 @@ sub msg_hdr_write ($$) { $smsg->{nntp}->msg_more($$hdr); } -sub blob_cb { # called by git->cat_async via git_async_cat +sub blob_cb { # called by git->cat_async via ibx_async_cat my ($bref, $oid, $type, $size, $smsg) = @_; my $self = $smsg->{nntp}; my $code = $smsg->{nntp_code}; @@ -574,15 +550,15 @@ sub blob_cb { # called by git->cat_async via git_async_cat my $r = "$code $smsg->{num} <$smsg->{mid}> article retrieved - "; my $eml = PublicInbox::Eml->new($bref); if ($code == 220) { - more($self, $r .= 'head and body follow'); + $self->msg_more($r .= "head and body follow\r\n"); msg_hdr_write($eml, $smsg); $self->msg_more("\r\n"); msg_body_write($self, $bref); } elsif ($code == 221) { - more($self, $r .= 'head follows'); + $self->msg_more($r .= "head follows\r\n"); msg_hdr_write($eml, $smsg); } elsif ($code == 222) { - more($self, $r .= 'body follows'); + $self->msg_more($r .= "body follows\r\n"); msg_body_write($self, $bref); } else { $self->close; @@ -618,7 +594,7 @@ sub cmd_date ($) { '111 '.strftime('%Y%m%d%H%M%S', gmtime(time)) } sub cmd_help ($) { my ($self) = @_; - more($self, '100 help text follows'); + $self->msg_more("100 help text follows\r\n"); '.' } @@ -627,7 +603,7 @@ sub get_range ($$) { my $ibx = $self->{ibx} or return '412 no news group has been selected'; defined $range or return '420 No article(s) selected'; my ($beg, $end); - my ($min, $max) = $ibx->mm->minmax; + my ($min, $max) = $ibx->mm(1)->minmax; if ($range =~ /\A([0-9]+)\z/) { $beg = $end = $1; } elsif ($range =~ /\A([0-9]+)-\z/) { @@ -659,22 +635,11 @@ sub long_step { out($self, " deferred[$fd] aborted - %0.6f", $elapsed); $self->close; } elsif ($more) { # $self->{wbuf}: - $self->update_idle_time; - - # COMPRESS users all share the same DEFLATE context. - # Flush it here to ensure clients don't see - # each other's data - $self->zflush; - - # no recursion, schedule another call ASAP, but only after - # all pending writes are done. autovivify wbuf: - my $new_size = push(@{$self->{wbuf}}, \&long_step); - - # wbuf may be populated by $cb, no need to rearm if so: - $self->requeue if $new_size == 1; + # control passed to ibx_async_cat if $more == \undef + $self->requeue_once if !ref($more); } else { # all done! delete $self->{long_cb}; - res($self, '.'); + $self->write(\".\r\n"); my $elapsed = now() - $t0; my $fd = fileno($self->{sock}); out($self, " deferred[$fd] done - %0.6f", $elapsed); @@ -697,7 +662,7 @@ sub long_response ($$;@) { sub hdr_msgid_range_i { my ($self, $beg, $end) = @_; - my $r = $self->{ibx}->mm->msg_range($beg, $end); + my $r = $self->{ibx}->mm(1)->msg_range($beg, $end); @$r or return; $self->msg_more(join('', map { "$_->[0] <$_->[1]>\r\n" } @$r)); 1; @@ -714,7 +679,7 @@ sub hdr_message_id ($$$) { # optimize XHDR Message-ID [range] for slrnpull. $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&hdr_msgid_range_i, @$r); } } @@ -723,7 +688,7 @@ sub mid_lookup ($$) { my ($self, $mid) = @_; my $cur_ibx = $self->{ibx}; if ($cur_ibx) { - my $n = $cur_ibx->mm->num_for($mid); + my $n = $cur_ibx->mm(1)->num_for($mid); return ($cur_ibx, $n) if defined $n; } my $pi_cfg = $self->{nntpd}->{pi_cfg}; @@ -753,7 +718,7 @@ EOF } else { # slow path for non-ALL users for my $ibx (values %{$pi_cfg->{-by_newsgroup}}) { next if defined $cur_ibx && $ibx eq $cur_ibx; - my $n = $ibx->mm->num_for($mid); + my $n = $ibx->mm(1)->num_for($mid); return ($ibx, $n) if defined $n; } } @@ -763,7 +728,7 @@ EOF sub xref_range_i { my ($self, $beg, $end) = @_; my $ibx = $self->{ibx}; - my $msgs = $ibx->over->query_xover($$beg, $end); + my $msgs = $ibx->over(1)->query_xover($$beg, $end); scalar(@$msgs) or return; $$beg = $msgs->[-1]->{num} + 1; $self->msg_more(join('', map { @@ -779,29 +744,28 @@ sub hdr_xref ($$$) { # optimize XHDR Xref [range] for rtin my $mid = $1; my ($ibx, $n) = mid_lookup($self, $mid); return r430 unless $n; - my $smsg = $ibx->over->get_art($n) or return; + my $smsg = $ibx->over(1)->get_art($n) or return; hdr_mid_response($self, $xhdr, $ibx, $n, $range, xref($self, $ibx, $smsg)); } else { # numeric range $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&xref_range_i, @$r); } } sub over_header_for { - my ($over, $num, $field) = @_; - my $smsg = $over->get_art($num) or return; + my ($ibx, $num, $field) = @_; + my $smsg = $ibx->over(1)->get_art($num) or return; return PublicInbox::Smsg::date($smsg) if $field eq 'date'; $smsg->{$field}; } sub smsg_range_i { my ($self, $beg, $end, $field) = @_; - my $over = $self->{ibx}->over; - my $msgs = $over->query_xover($$beg, $end); + my $msgs = $self->{ibx}->over(1)->query_xover($$beg, $end); scalar(@$msgs) or return; my $tmp = ''; @@ -825,13 +789,13 @@ sub hdr_smsg ($$$$) { if (defined $range && $range =~ $ONE_MSGID) { my ($ibx, $n) = mid_lookup($self, $1); return r430 unless defined $n; - my $v = over_header_for($ibx->over, $n, $field); + my $v = over_header_for($ibx, $n, $field); hdr_mid_response($self, $xhdr, $ibx, $n, $range, $v); } else { # numeric range $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, $xhdr ? r221 : r225); + $self->msg_more($xhdr ? r221 : r225); long_response($self, \&smsg_range_i, @$r, $field); } } @@ -849,7 +813,7 @@ sub do_hdr ($$$;$) { } elsif ($sub =~ /\A:(bytes|lines)\z/) { hdr_smsg($self, $xhdr, $1, $range); } else { - $xhdr ? (r221 . "\r\n.") : "503 HDR not permitted on $header"; + $xhdr ? (r221 . '.') : "503 HDR not permitted on $header"; } } @@ -879,23 +843,16 @@ sub hdr_mid_prefix ($$$$$) { sub hdr_mid_response ($$$$$$) { my ($self, $xhdr, $ibx, $n, $mid, $v) = @_; - my $res = ''; - if ($xhdr) { - $res .= r221 . "\r\n"; - $res .= "$mid $v\r\n"; - } else { - $res .= r225 . "\r\n"; - my $pfx = hdr_mid_prefix($self, $xhdr, $ibx, $n, $mid); - $res .= "$pfx $v\r\n"; - } - res($self, $res .= '.'); + $self->write(($xhdr ? r221.$mid : + r225.hdr_mid_prefix($self, $xhdr, $ibx, $n, $mid)) . + " $v\r\n.\r\n"); undef; } sub xrover_i { my ($self, $beg, $end) = @_; - my $h = over_header_for($self->{ibx}->over, $$beg, 'references'); - more($self, "$$beg $h") if defined($h); + my $h = over_header_for($self->{ibx}, $$beg, 'references'); + $self->msg_more("$$beg $h\r\n") if defined($h); $$beg++ < $end; } @@ -908,7 +865,7 @@ sub cmd_xrover ($;$) { $range = $self->{article} unless defined $range; my $r = get_range($self, $range); return $r unless ref $r; - more($self, '224 Overview information follows'); + $self->msg_more("224 Overview information follows\r\n"); long_response($self, \&xrover_i, @$r); } @@ -934,8 +891,9 @@ sub cmd_over ($;$) { if ($range && $range =~ $ONE_MSGID) { my ($ibx, $n) = mid_lookup($self, $1); defined $n or return r430; - my $smsg = $ibx->over->get_art($n) or return r430; - more($self, '224 Overview information follows (multi-line)'); + my $smsg = $ibx->over(1)->get_art($n) or return r430; + $self->msg_more( + "224 Overview information follows (multi-line)\r\n"); # Only set article number column if it's the current group # (RFC 3977 8.3.2) @@ -955,7 +913,7 @@ sub cmd_over ($;$) { sub xover_i { my ($self, $beg, $end) = @_; my $ibx = $self->{ibx}; - my $msgs = $ibx->over->query_xover($$beg, $end); + my $msgs = $ibx->over(1)->query_xover($$beg, $end); my $nr = scalar @$msgs or return; # OVERVIEW.FMT @@ -971,7 +929,8 @@ sub cmd_xover ($;$) { my $r = get_range($self, $range); return $r unless ref $r; my ($beg, $end) = @$r; - more($self, "224 Overview information follows for $$beg to $end"); + $self->msg_more( + "224 Overview information follows for $$beg to $end\r\n"); long_response($self, \&xover_i, @$r); } @@ -984,7 +943,7 @@ sub cmd_starttls ($) { return r502 if ($sock->can('accept_SSL') || $self->compressed); my $opt = $self->{nntpd}->{accept_tls} or return '580 can not initiate TLS negotiation'; - res($self, '382 Continue with TLS negotiation'); + $self->write(\"382 Continue with TLS negotiation\r\n"); $self->{sock} = IO::Socket::SSL->start_SSL($sock, %$opt); $self->requeue if PublicInbox::DS::accept_tls_step($self); undef; @@ -1000,8 +959,6 @@ sub cmd_compress ($$) { undef } -sub zflush {} # overridden by NNTPdeflate - sub cmd_xpath ($$) { my ($self, $mid) = @_; return r501 unless $mid =~ $ONE_MSGID; @@ -1023,7 +980,7 @@ sub cmd_xpath ($$) { } } else { # slow path, no point in using long_response for my $ibx (values %$groups) { - my $n = $ibx->mm->num_for($mid) // next; + my $n = $ibx->mm(1)->num_for($mid) // next; push @paths, "$ibx->{newsgroup}/$n"; } } @@ -1033,8 +990,6 @@ sub cmd_xpath ($$) { sub res ($$) { do_write($_[0], $_[1] . "\r\n") } -sub more ($$) { $_[0]->msg_more($_[1] . "\r\n") } - sub do_write ($$) { my $self = $_[0]; my $done = $self->write(\($_[1])); @@ -1059,7 +1014,6 @@ sub event_step { return unless $self->flush_write && $self->{sock} && !$self->{long_cb}; - $self->update_idle_time; # only read more requests if we've drained the write buffer, # otherwise we can be buffering infinitely w/o backpressure @@ -1081,17 +1035,15 @@ sub event_step { out($self, "[$fd] %s - %0.6f$pending", $line, now() - $t0); return $self->close if $r < 0; $self->rbuf_idle($rbuf); - $self->update_idle_time; # maybe there's more pipelined data, or we'll have # to register it for socket-readiness notifications $self->requeue unless $pending; } -# for graceful shutdown in PublicInbox::Daemon: -sub busy { - my ($self, $now) = @_; - ($self->{rbuf} || $self->{wbuf} || $self->not_idle_long($now)); +sub busy { # for graceful shutdown in PublicInbox::Daemon: + my ($self) = @_; + defined($self->{rbuf}) || defined($self->{wbuf}) } 1;