use strict;
use warnings;
use base qw(PublicInbox::DS);
-use fields qw(nntpd article rbuf ng);
+use fields qw(nntpd article ng);
use PublicInbox::Search;
use PublicInbox::Msgmap;
use PublicInbox::MID qw(mid_escape);
my $OVERVIEW_FMT = join(":\r\n", @OVERVIEW, qw(Bytes Lines)) . ":\r\n";
my $LIST_HEADERS = join("\r\n", @OVERVIEW,
qw(:bytes :lines Xref To Cc)) . "\r\n";
-
-# disable commands with easy DoS potential:
-my %DISABLED; # = map { $_ => 1 } qw(xover list_overview_fmt newnews xhdr);
+my $CAPABILITIES = <<"";
+101 Capability list:\r
+VERSION 2\r
+READER\r
+NEWNEWS\r
+LIST ACTIVE ACTIVE.TIMES NEWSGROUPS OVERVIEW.FMT\r
+HDR\r
+OVER\r
my $EXPMAP; # fd -> [ idle_time, $self ]
my $expt;
our $EXPTIME = 180; # 3 minutes
-my $nextt;
-
-my $nextq = [];
-sub next_tick () {
- $nextt = undef;
- my $q = $nextq;
- $nextq = [];
- event_step($_) for @$q;
-}
-
-sub requeue ($) {
- push @$nextq, $_[0];
- $nextt ||= PublicInbox::EvCleanup::asap(*next_tick);
-}
sub update_idle_time ($) {
my ($self) = @_;
my $exp = $EXPTIME;
my $old = $now - $exp;
my $nr = 0;
- my $closed = 0;
my %new;
while (my ($fd, $v) = each %$EXPMAP) {
my ($idle_time, $nntp) = @$v;
if ($idle_time < $old) {
- if ($nntp->shutdn) {
- $closed++;
- } else {
+ if (!$nntp->shutdn) {
++$nr;
$new{$fd} = $v;
}
}
}
$EXPMAP = \%new;
- if ($nr) {
- $expt = PublicInbox::EvCleanup::later(*expire_old);
- } else {
- $expt = undef;
- # noop to kick outselves out of the loop ASAP so descriptors
- # really get closed
- PublicInbox::EvCleanup::asap(sub {}) if $closed;
- }
+ $expt = PublicInbox::EvCleanup::later(*expire_old) if $nr;
}
sub greet ($) { $_[0]->write($_[0]->{nntpd}->{greet}) };
my $ev = EPOLLIN;
my $wbuf;
if (ref($sock) eq 'IO::Socket::SSL' && !$sock->accept_SSL) {
- $ev = PublicInbox::TLS::epollbit() or return CORE::close($sock);
+ return CORE::close($sock) if $! != EAGAIN;
+ $ev = PublicInbox::TLS::epollbit();
$wbuf = [ \&PublicInbox::DS::accept_tls_step, \&greet ];
}
$self->SUPER::new($sock, $ev | EPOLLONESHOT);
my ($self, $l) = @_;
my ($req, @args) = split(/[ \t]/, $l);
return 1 unless defined($req); # skip blank line
- $req = lc($req);
$req = eval {
no strict 'refs';
- $req = $DISABLED{$req} ? undef : *{'cmd_'.$req}{CODE};
+ *{'cmd_'.lc($req)}{CODE};
};
return res($self, '500 command not recognized') unless $req;
return res($self, r501) unless args_ok($req, scalar @args);
res($self, $res);
}
+# The keyword argument is not used (rfc3977 5.2.2)
+sub cmd_capabilities ($;$) {
+ my ($self, undef) = @_;
+ my $res = $CAPABILITIES;
+ if (ref($self->{sock}) ne 'IO::Socket::SSL' &&
+ $self->{nntpd}->{accept_tls}) {
+ $res .= "STARTTLS\r\n";
+ }
+ $res .= '.';
+}
+
sub cmd_mode ($$) {
my ($self, $arg) = @_;
$arg = uc $arg;
my $arg = shift @args;
$arg =~ tr/A-Z./a-z_/;
$arg = "list_$arg";
- return r501 if $DISABLED{$arg};
$arg = eval {
no strict 'refs';
sub msg_body_write ($$) {
my ($self, $msg) = @_;
+
+ # these can momentarily double the memory consumption :<
$$msg =~ s/^\./../smg;
- $$msg =~ s/(?<!\r)\n/\r\n/sg;
+ $$msg =~ s/(?<!\r)\n/\r\n/sg; # Alpine barfs without this
+ $$msg .= "\r\n" unless $$msg =~ /\r\n\z/s;
msg_more($self, $$msg);
- msg_more($self, "\r\n") unless $$msg =~ /\r\n\z/s;
'.'
}
$self->{article} = $art if defined $art && $art =~ /\A[0-9]+\z/;
}
-sub _header ($) {
- my $hdr = $_[0]->as_string;
+sub msg_hdr_write ($$$) {
+ my ($self, $hdr, $body_follows) = @_;
+ $hdr = $hdr->as_string;
utf8::encode($hdr);
- $hdr =~ s/(?<!\r)\n/\r\n/sg;
+ $hdr =~ s/(?<!\r)\n/\r\n/sg; # Alpine barfs without this
# for leafnode compatibility, we need to ensure Message-ID headers
# are only a single line. We can't subclass Email::Simple::Header
# and override _default_fold_at in here, either; since that won't
# affect messages already in the archive.
$hdr =~ s/^(Message-ID:)[ \t]*\r\n[ \t]+([^\r]+)\r\n/$1 $2\r\n/igsm;
-
- $hdr
+ $hdr .= "\r\n" if $body_follows;
+ msg_more($self, $hdr);
}
sub cmd_article ($;$) {
my ($n, $mid, $msg, $hdr) = @$r;
set_art($self, $art);
more($self, "220 $n <$mid> article retrieved - head and body follow");
- msg_more($self, _header($hdr));
- msg_more($self, "\r\n");
+ msg_hdr_write($self, $hdr, 1);
msg_body_write($self, $msg);
}
my ($n, $mid, undef, $hdr) = @$r;
set_art($self, $art);
more($self, "221 $n <$mid> article retrieved - head follows");
- msg_more($self, _header($hdr));
+ msg_hdr_write($self, $hdr, 0);
'.'
}
push @$wbuf, $long_cb;
# wbuf may be populated by $cb, no need to rearm if so:
- requeue($self) if scalar(@$wbuf) == 1;
+ $self->requeue if scalar(@$wbuf) == 1;
} else { # all done!
$long_cb = undef;
res($self, '.');
out($self, " deferred[$fd] done - %0.6f", now() - $t0);
- requeue($self) unless $self->{wbuf};
+ $self->requeue unless $self->{wbuf};
}
};
$self->write($long_cb); # kick off!
return '580 can not initiate TLS negotiation';
res($self, '382 Continue with TLS negotiation');
$self->{sock} = IO::Socket::SSL->start_SSL($sock, %$opt);
- requeue($self) if PublicInbox::DS::accept_tls_step($self);
+ $self->requeue if PublicInbox::DS::accept_tls_step($self);
undef;
}
return $self->close if $r < 0;
my $len = bytes::length($$rbuf);
return $self->close if ($len >= LINE_MAX);
- if ($len) {
- $self->{rbuf} = $rbuf;
- } else {
- delete $self->{rbuf};
- }
+ $self->rbuf_idle($rbuf);
update_idle_time($self);
# maybe there's more pipelined data, or we'll have
# to register it for socket-readiness notifications
- requeue($self) unless $self->{wbuf};
+ $self->requeue unless $self->{wbuf};
}
sub not_idle_long ($$) {