1# Net::NNTP.pm
2#
3# Copyright (C) 1995-1997 Graham Barr.  All rights reserved.
4# Copyright (C) 2013-2016, 2020 Steve Hay.  All rights reserved.
5# This module is free software; you can redistribute it and/or modify it under
6# the same terms as Perl itself, i.e. under the terms of either the GNU General
7# Public License or the Artistic License, as specified in the F<LICENCE> file.
8
9package Net::NNTP;
10
11use 5.008001;
12
13use strict;
14use warnings;
15
16use Carp;
17use IO::Socket;
18use Net::Cmd;
19use Net::Config;
20use Time::Local;
21
22our $VERSION = "3.13";
23
24# Code for detecting if we can use SSL
25my $ssl_class = eval {
26  require IO::Socket::SSL;
27  # first version with default CA on most platforms
28  no warnings 'numeric';
29  IO::Socket::SSL->VERSION(2.007);
30} && 'IO::Socket::SSL';
31
32my $nossl_warn = !$ssl_class &&
33  'To use SSL please install IO::Socket::SSL with version>=2.007';
34
35# Code for detecting if we can use IPv6
36my $family_key = 'Domain';
37my $inet6_class = eval {
38  require IO::Socket::IP;
39  no warnings 'numeric';
40  IO::Socket::IP->VERSION(0.25) || die;
41  $family_key = 'Family';
42} && 'IO::Socket::IP' || eval {
43  require IO::Socket::INET6;
44  no warnings 'numeric';
45  IO::Socket::INET6->VERSION(2.62);
46} && 'IO::Socket::INET6';
47
48
49sub can_ssl   { $ssl_class };
50sub can_inet6 { $inet6_class };
51
52our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET');
53
54
55sub new {
56  my $self = shift;
57  my $type = ref($self) || $self;
58  my ($host, %arg);
59  if (@_ % 2) {
60    $host = shift;
61    %arg  = @_;
62  }
63  else {
64    %arg  = @_;
65    $host = delete $arg{Host};
66  }
67  my $obj;
68
69  $host ||= $ENV{NNTPSERVER} || $ENV{NEWSHOST};
70
71  my $hosts = defined $host ? [$host] : $NetConfig{nntp_hosts};
72
73  @{$hosts} = qw(news)
74    unless @{$hosts};
75
76  my %connect = ( Proto => 'tcp');
77
78  if ($arg{SSL}) {
79    # SSL from start
80    die $nossl_warn if ! $ssl_class;
81    $arg{Port} ||= 563;
82    $connect{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg);
83  }
84
85  foreach my $o (qw(LocalAddr LocalPort Timeout)) {
86    $connect{$o} = $arg{$o} if exists $arg{$o};
87  }
88  $connect{$family_key} = $arg{Domain} || $arg{Family};
89  $connect{Timeout} = 120 unless defined $connect{Timeout};
90  $connect{PeerPort} = $arg{Port} || 'nntp(119)';
91  foreach my $h (@{$hosts}) {
92    $connect{PeerAddr} = $h;
93    $obj = $type->SUPER::new(%connect) or next;
94    ${*$obj}{'net_nntp_host'} = $h;
95    ${*$obj}{'net_nntp_arg'} = \%arg;
96    if ($arg{SSL}) {
97      Net::NNTP::_SSL->start_SSL($obj,%arg) or next;
98    }
99  }
100
101  return
102    unless defined $obj;
103
104  $obj->autoflush(1);
105  $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
106
107  unless ($obj->response() == CMD_OK) {
108    $obj->close;
109    return;
110  }
111
112  my $c = $obj->code;
113  my @m = $obj->message;
114
115  unless (exists $arg{Reader} && $arg{Reader} == 0) {
116
117    # if server is INN and we have transfer rights the we are currently
118    # talking to innd not nnrpd
119    if ($obj->reader) {
120
121      # If reader succeeds the we need to consider this code to determine postok
122      $c = $obj->code;
123    }
124    else {
125
126      # I want to ignore this failure, so restore the previous status.
127      $obj->set_status($c, \@m);
128    }
129  }
130
131  ${*$obj}{'net_nntp_post'} = $c == 200 ? 1 : 0;
132
133  $obj;
134}
135
136
137sub host {
138  my $me = shift;
139  ${*$me}{'net_nntp_host'};
140}
141
142
143sub debug_text {
144  my $nntp  = shift;
145  my $inout = shift;
146  my $text  = shift;
147
148  if ( (ref($nntp) and $nntp->code == 350 and $text =~ /^(\S+)/)
149    || ($text =~ /^(authinfo\s+pass)/io))
150  {
151    $text = "$1 ....\n";
152  }
153
154  $text;
155}
156
157
158sub postok {
159  @_ == 1 or croak 'usage: $nntp->postok()';
160  my $nntp = shift;
161  ${*$nntp}{'net_nntp_post'} || 0;
162}
163
164
165sub starttls {
166  my $self = shift;
167  $ssl_class or die $nossl_warn;
168  $self->_STARTTLS or return;
169  Net::NNTP::_SSL->start_SSL($self,
170    %{ ${*$self}{'net_nntp_arg'} }, # (ssl) args given in new
171    @_   # more (ssl) args
172  ) or return;
173  return 1;
174}
175
176
177sub article {
178  @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->article([{$msgid|$msgnum}[, $fh]])';
179  my $nntp = shift;
180  my @fh;
181
182  @fh = (pop) if @_ == 2 || (@_ && (ref($_[0]) || ref(\$_[0]) eq 'GLOB'));
183
184  $nntp->_ARTICLE(@_)
185    ? $nntp->read_until_dot(@fh)
186    : undef;
187}
188
189
190sub articlefh {
191  @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->articlefh([{$msgid|$msgnum}])';
192  my $nntp = shift;
193
194  return unless $nntp->_ARTICLE(@_);
195  return $nntp->tied_fh;
196}
197
198
199sub authinfo {
200  @_ == 3 or croak 'usage: $nntp->authinfo($user, $pass)';
201  my ($nntp, $user, $pass) = @_;
202
203  $nntp->_AUTHINFO("USER",      $user) == CMD_MORE
204    && $nntp->_AUTHINFO("PASS", $pass) == CMD_OK;
205}
206
207
208sub authinfo_simple {
209  @_ == 3 or croak 'usage: $nntp->authinfo_simple($user, $pass)';
210  my ($nntp, $user, $pass) = @_;
211
212  $nntp->_AUTHINFO('SIMPLE') == CMD_MORE
213    && $nntp->command($user, $pass)->response == CMD_OK;
214}
215
216
217sub body {
218  @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->body([{$msgid|$msgnum}[, $fh]])';
219  my $nntp = shift;
220  my @fh;
221
222  @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB');
223
224  $nntp->_BODY(@_)
225    ? $nntp->read_until_dot(@fh)
226    : undef;
227}
228
229
230sub bodyfh {
231  @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->bodyfh([{$msgid|$msgnum}])';
232  my $nntp = shift;
233  return unless $nntp->_BODY(@_);
234  return $nntp->tied_fh;
235}
236
237
238sub head {
239  @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->head([{$msgid|$msgnum}[, $fh]])';
240  my $nntp = shift;
241  my @fh;
242
243  @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB');
244
245  $nntp->_HEAD(@_)
246    ? $nntp->read_until_dot(@fh)
247    : undef;
248}
249
250
251sub headfh {
252  @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->headfh([{$msgid|$msgnum}])';
253  my $nntp = shift;
254  return unless $nntp->_HEAD(@_);
255  return $nntp->tied_fh;
256}
257
258
259sub nntpstat {
260  @_ == 1 || @_ == 2 or croak 'usage: $nntp->nntpstat([{$msgid|$msgnum}])';
261  my $nntp = shift;
262
263  $nntp->_STAT(@_) && $nntp->message =~ /(<[^>]+>)/o
264    ? $1
265    : undef;
266}
267
268
269sub group {
270  @_ == 1 || @_ == 2 or croak 'usage: $nntp->group([$group])';
271  my $nntp = shift;
272  my $grp  = ${*$nntp}{'net_nntp_group'};
273
274  return $grp
275    unless (@_ || wantarray);
276
277  my $newgrp = shift;
278
279  $newgrp = (defined($grp) and length($grp)) ? $grp : ""
280    unless defined($newgrp) and length($newgrp);
281
282  return
283    unless $nntp->_GROUP($newgrp) and $nntp->message =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/;
284
285  my ($count, $first, $last, $group) = ($1, $2, $3, $4);
286
287  # group may be replied as '(current group)'
288  $group = ${*$nntp}{'net_nntp_group'}
289    if $group =~ /\(/;
290
291  ${*$nntp}{'net_nntp_group'} = $group;
292
293  wantarray
294    ? ($count, $first, $last, $group)
295    : $group;
296}
297
298
299sub help {
300  @_ == 1 or croak 'usage: $nntp->help()';
301  my $nntp = shift;
302
303  $nntp->_HELP
304    ? $nntp->read_until_dot
305    : undef;
306}
307
308
309sub ihave {
310  @_ >= 2 or croak 'usage: $nntp->ihave($msgid[, $message])';
311  my $nntp  = shift;
312  my $msgid = shift;
313
314  $nntp->_IHAVE($msgid) && $nntp->datasend(@_)
315    ? @_ == 0 || $nntp->dataend
316    : undef;
317}
318
319
320sub last {
321  @_ == 1 or croak 'usage: $nntp->last()';
322  my $nntp = shift;
323
324  $nntp->_LAST && $nntp->message =~ /(<[^>]+>)/o
325    ? $1
326    : undef;
327}
328
329
330sub list {
331  @_ == 1 or croak 'usage: $nntp->list()';
332  my $nntp = shift;
333
334  $nntp->_LIST
335    ? $nntp->_grouplist
336    : undef;
337}
338
339
340sub newgroups {
341  @_ >= 2 or croak 'usage: $nntp->newgroups($since[, $distributions])';
342  my $nntp = shift;
343  my $since = _timestr(shift);
344  my $distributions = shift || "";
345
346  $distributions = join(",", @{$distributions})
347    if ref($distributions);
348
349  $nntp->_NEWGROUPS($since, $distributions)
350    ? $nntp->_grouplist
351    : undef;
352}
353
354
355sub newnews {
356  @_ >= 2 && @_ <= 4
357    or croak 'usage: $nntp->newnews($since[, $groups[, $distributions]])';
358  my $nntp = shift;
359  my $since = _timestr(shift);
360  my $groups = @_ ? shift : $nntp->group;
361  my $distributions = shift || "";
362
363  $groups ||= "*";
364  $groups = join(",", @{$groups})
365    if ref($groups);
366
367  $distributions = join(",", @{$distributions})
368    if ref($distributions);
369
370  $nntp->_NEWNEWS($groups, $since, $distributions)
371    ? $nntp->_articlelist
372    : undef;
373}
374
375
376sub next {
377  @_ == 1 or croak 'usage: $nntp->next()';
378  my $nntp = shift;
379
380  $nntp->_NEXT && $nntp->message =~ /(<[^>]+>)/o
381    ? $1
382    : undef;
383}
384
385
386sub post {
387  @_ >= 1 or croak 'usage: $nntp->post([$message])';
388  my $nntp = shift;
389
390  $nntp->_POST() && $nntp->datasend(@_)
391    ? @_ == 0 || $nntp->dataend
392    : undef;
393}
394
395
396sub postfh {
397  my $nntp = shift;
398  return unless $nntp->_POST();
399  return $nntp->tied_fh;
400}
401
402
403sub quit {
404  @_ == 1 or croak 'usage: $nntp->quit()';
405  my $nntp = shift;
406
407  $nntp->_QUIT;
408  $nntp->close;
409}
410
411
412sub slave {
413  @_ == 1 or croak 'usage: $nntp->slave()';
414  my $nntp = shift;
415
416  $nntp->_SLAVE;
417}
418
419##
420## The following methods are not implemented by all servers
421##
422
423
424sub active {
425  @_ == 1 || @_ == 2 or croak 'usage: $nntp->active([$pattern])';
426  my $nntp = shift;
427
428  $nntp->_LIST('ACTIVE', @_)
429    ? $nntp->_grouplist
430    : undef;
431}
432
433
434sub active_times {
435  @_ == 1 or croak 'usage: $nntp->active_times()';
436  my $nntp = shift;
437
438  $nntp->_LIST('ACTIVE.TIMES')
439    ? $nntp->_grouplist
440    : undef;
441}
442
443
444sub distributions {
445  @_ == 1 or croak 'usage: $nntp->distributions()';
446  my $nntp = shift;
447
448  $nntp->_LIST('DISTRIBUTIONS')
449    ? $nntp->_description
450    : undef;
451}
452
453
454sub distribution_patterns {
455  @_ == 1 or croak 'usage: $nntp->distribution_patterns()';
456  my $nntp = shift;
457
458  my $arr;
459  local $_;
460
461  ## no critic (ControlStructures::ProhibitMutatingListFunctions)
462  $nntp->_LIST('DISTRIB.PATS')
463    && ($arr = $nntp->read_until_dot)
464    ? [grep { /^\d/ && (chomp, $_ = [split /:/]) } @$arr]
465    : undef;
466}
467
468
469sub newsgroups {
470  @_ == 1 || @_ == 2 or croak 'usage: $nntp->newsgroups([$pattern])';
471  my $nntp = shift;
472
473  $nntp->_LIST('NEWSGROUPS', @_)
474    ? $nntp->_description
475    : undef;
476}
477
478
479sub overview_fmt {
480  @_ == 1 or croak 'usage: $nntp->overview_fmt()';
481  my $nntp = shift;
482
483  $nntp->_LIST('OVERVIEW.FMT')
484    ? $nntp->_articlelist
485    : undef;
486}
487
488
489sub subscriptions {
490  @_ == 1 or croak 'usage: $nntp->subscriptions()';
491  my $nntp = shift;
492
493  $nntp->_LIST('SUBSCRIPTIONS')
494    ? $nntp->_articlelist
495    : undef;
496}
497
498
499sub listgroup {
500  @_ == 1 || @_ == 2 or croak 'usage: $nntp->listgroup([$group])';
501  my $nntp = shift;
502
503  $nntp->_LISTGROUP(@_)
504    ? $nntp->_articlelist
505    : undef;
506}
507
508
509sub reader {
510  @_ == 1 or croak 'usage: $nntp->reader()';
511  my $nntp = shift;
512
513  $nntp->_MODE('READER');
514}
515
516
517sub xgtitle {
518  @_ == 1 || @_ == 2 or croak 'usage: $nntp->xgtitle([$pattern])';
519  my $nntp = shift;
520
521  $nntp->_XGTITLE(@_)
522    ? $nntp->_description
523    : undef;
524}
525
526
527sub xhdr {
528  @_ >= 2 && @_ <= 4 or croak 'usage: $nntp->xhdr($header[, $message_spec])';
529  my $nntp = shift;
530  my $header = shift;
531  my $arg = _msg_arg(@_);
532
533  $nntp->_XHDR($header, $arg)
534    ? $nntp->_description
535    : undef;
536}
537
538
539sub xover {
540  @_ == 2 || @_ == 3 or croak 'usage: $nntp->xover($message_spec)';
541  my $nntp = shift;
542  my $arg  = _msg_arg(@_);
543
544  $nntp->_XOVER($arg)
545    ? $nntp->_fieldlist
546    : undef;
547}
548
549
550sub xpat {
551  @_ == 4 || @_ == 5 or croak 'usage: $nntp->xpat($header, $pattern, $message_spec )';
552  my $nntp = shift;
553  my $header = shift;
554  my $pattern = shift;
555  my $arg = _msg_arg(@_);
556
557  $pattern = join(" ", @$pattern)
558    if ref($pattern);
559
560  $nntp->_XPAT($header, $arg, $pattern)
561    ? $nntp->_description
562    : undef;
563}
564
565
566sub xpath {
567  @_ == 2 or croak 'usage: $nntp->xpath($message_id)';
568  my ($nntp, $message_id) = @_;
569
570  return
571    unless $nntp->_XPATH($message_id);
572
573  my $m;
574  ($m = $nntp->message) =~ s/^\d+\s+//o;
575  my @p = split /\s+/, $m;
576
577  wantarray ? @p : $p[0];
578}
579
580
581sub xrover {
582  @_ == 2 || @_ == 3 or croak 'usage: $nntp->xrover($message_spec)';
583  my $nntp = shift;
584  my $arg  = _msg_arg(@_);
585
586  $nntp->_XROVER($arg)
587    ? $nntp->_description
588    : undef;
589}
590
591
592sub date {
593  @_ == 1 or croak 'usage: $nntp->date()';
594  my $nntp = shift;
595
596  $nntp->_DATE
597    && $nntp->message =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/
598    ? timegm($6, $5, $4, $3, $2 - 1, $1)
599    : undef;
600}
601
602
603##
604## Private subroutines
605##
606
607
608sub _msg_arg {
609  my $spec = shift;
610  my $arg  = "";
611
612  if (@_) {
613    carp "Depriciated passing of two message numbers, " . "pass a reference"
614      if $^W;
615    $spec = [$spec, $_[0]];
616  }
617
618  if (defined $spec) {
619    if (ref($spec)) {
620      $arg = $spec->[0];
621      if (defined $spec->[1]) {
622        $arg .= "-"
623          if $spec->[1] != $spec->[0];
624        $arg .= $spec->[1]
625          if $spec->[1] > $spec->[0];
626      }
627    }
628    else {
629      $arg = $spec;
630    }
631  }
632
633  $arg;
634}
635
636
637sub _timestr {
638  my $time = shift;
639  my @g    = reverse((gmtime($time))[0 .. 5]);
640  $g[1] += 1;
641  $g[0] %= 100;
642  sprintf "%02d%02d%02d %02d%02d%02d GMT", @g;
643}
644
645
646sub _grouplist {
647  my $nntp = shift;
648  my $arr  = $nntp->read_until_dot
649    or return;
650
651  my $hash = {};
652
653  foreach my $ln (@$arr) {
654    my @a = split(/[\s\n]+/, $ln);
655    $hash->{$a[0]} = [@a[1, 2, 3]];
656  }
657
658  $hash;
659}
660
661
662sub _fieldlist {
663  my $nntp = shift;
664  my $arr  = $nntp->read_until_dot
665    or return;
666
667  my $hash = {};
668
669  foreach my $ln (@$arr) {
670    my @a = split(/[\t\n]/, $ln);
671    my $m = shift @a;
672    $hash->{$m} = [@a];
673  }
674
675  $hash;
676}
677
678
679sub _articlelist {
680  my $nntp = shift;
681  my $arr  = $nntp->read_until_dot;
682
683  chomp(@$arr)
684    if $arr;
685
686  $arr;
687}
688
689
690sub _description {
691  my $nntp = shift;
692  my $arr  = $nntp->read_until_dot
693    or return;
694
695  my $hash = {};
696
697  foreach my $ln (@$arr) {
698    chomp($ln);
699
700    $hash->{$1} = $ln
701      if $ln =~ s/^\s*(\S+)\s*//o;
702  }
703
704  $hash;
705
706}
707
708##
709## The commands
710##
711
712
713sub _ARTICLE  { shift->command('ARTICLE',  @_)->response == CMD_OK }
714sub _AUTHINFO { shift->command('AUTHINFO', @_)->response }
715sub _BODY     { shift->command('BODY',     @_)->response == CMD_OK }
716sub _DATE      { shift->command('DATE')->response == CMD_INFO }
717sub _GROUP     { shift->command('GROUP', @_)->response == CMD_OK }
718sub _HEAD      { shift->command('HEAD', @_)->response == CMD_OK }
719sub _HELP      { shift->command('HELP', @_)->response == CMD_INFO }
720sub _IHAVE     { shift->command('IHAVE', @_)->response == CMD_MORE }
721sub _LAST      { shift->command('LAST')->response == CMD_OK }
722sub _LIST      { shift->command('LIST', @_)->response == CMD_OK }
723sub _LISTGROUP { shift->command('LISTGROUP', @_)->response == CMD_OK }
724sub _NEWGROUPS { shift->command('NEWGROUPS', @_)->response == CMD_OK }
725sub _NEWNEWS   { shift->command('NEWNEWS', @_)->response == CMD_OK }
726sub _NEXT      { shift->command('NEXT')->response == CMD_OK }
727sub _POST      { shift->command('POST', @_)->response == CMD_MORE }
728sub _QUIT      { shift->command('QUIT', @_)->response == CMD_OK }
729sub _SLAVE     { shift->command('SLAVE', @_)->response == CMD_OK }
730sub _STARTTLS  { shift->command("STARTTLS")->response() == CMD_MORE }
731sub _STAT      { shift->command('STAT', @_)->response == CMD_OK }
732sub _MODE      { shift->command('MODE', @_)->response == CMD_OK }
733sub _XGTITLE   { shift->command('XGTITLE', @_)->response == CMD_OK }
734sub _XHDR      { shift->command('XHDR', @_)->response == CMD_OK }
735sub _XPAT      { shift->command('XPAT', @_)->response == CMD_OK }
736sub _XPATH     { shift->command('XPATH', @_)->response == CMD_OK }
737sub _XOVER     { shift->command('XOVER', @_)->response == CMD_OK }
738sub _XROVER    { shift->command('XROVER', @_)->response == CMD_OK }
739sub _XTHREAD   { shift->unsupported }
740sub _XSEARCH   { shift->unsupported }
741sub _XINDEX    { shift->unsupported }
742
743##
744## IO/perl methods
745##
746
747
748sub DESTROY {
749  my $nntp = shift;
750  defined(fileno($nntp)) && $nntp->quit;
751}
752
753{
754  package Net::NNTP::_SSL;
755  our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::NNTP' );
756  sub starttls { die "NNTP connection is already in SSL mode" }
757  sub start_SSL {
758    my ($class,$nntp,%arg) = @_;
759    delete @arg{ grep { !m{^SSL_} } keys %arg };
760    ( $arg{SSL_verifycn_name} ||= $nntp->host )
761        =~s{(?<!:):[\w()]+$}{}; # strip port
762    $arg{SSL_hostname} = $arg{SSL_verifycn_name}
763        if ! defined $arg{SSL_hostname} && $class->can_client_sni;
764    my $ok = $class->SUPER::start_SSL($nntp,
765      SSL_verifycn_scheme => 'nntp',
766      %arg
767    );
768    $@ = $ssl_class->errstr if !$ok;
769    return $ok;
770  }
771}
772
773
774
775
7761;
777
778__END__
779
780=head1 NAME
781
782Net::NNTP - NNTP Client class
783
784=head1 SYNOPSIS
785
786    use Net::NNTP;
787
788    $nntp = Net::NNTP->new("some.host.name");
789    $nntp->quit;
790
791    # start with SSL, e.g. nntps
792    $nntp = Net::NNTP->new("some.host.name", SSL => 1);
793
794    # start with plain and upgrade to SSL
795    $nntp = Net::NNTP->new("some.host.name");
796    $nntp->starttls;
797
798
799=head1 DESCRIPTION
800
801C<Net::NNTP> is a class implementing a simple NNTP client in Perl as described
802in RFC977 and RFC4642.
803With L<IO::Socket::SSL> installed it also provides support for implicit and
804explicit TLS encryption, i.e. NNTPS or NNTP+STARTTLS.
805
806The Net::NNTP class is a subclass of Net::Cmd and (depending on avaibility) of
807IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET.
808
809=head2 Class Methods
810
811=over 4
812
813=item C<new([$host][, %options])>
814
815This is the constructor for a new Net::NNTP object. C<$host> is the
816name of the remote host to which a NNTP connection is required. If not
817given then it may be passed as the C<Host> option described below. If no host is passed
818then two environment variables are checked, first C<NNTPSERVER> then
819C<NEWSHOST>, then C<Net::Config> is checked, and if a host is not found
820then C<news> is used.
821
822C<%options> are passed in a hash like fashion, using key and value pairs.
823Possible options are:
824
825B<Host> - NNTP host to connect to. It may be a single scalar, as defined for
826the C<PeerAddr> option in L<IO::Socket::INET>, or a reference to
827an array with hosts to try in turn. The L</host> method will return the value
828which was used to connect to the host.
829
830B<Port> - port to connect to.
831Default - 119 for plain NNTP and 563 for immediate SSL (nntps).
832
833B<SSL> - If the connection should be done from start with SSL, contrary to later
834upgrade with C<starttls>.
835You can use SSL arguments as documented in L<IO::Socket::SSL>, but it will
836usually use the right arguments already.
837
838B<Timeout> - Maximum time, in seconds, to wait for a response from the
839NNTP server, a value of zero will cause all IO operations to block.
840(default: 120)
841
842B<Debug> - Enable the printing of debugging information to STDERR
843
844B<Reader> - If the remote server is INN then initially the connection
845will be to innd, by default C<Net::NNTP> will issue a C<MODE READER> command
846so that the remote server becomes nnrpd. If the C<Reader> option is given
847with a value of zero, then this command will not be sent and the
848connection will be left talking to innd.
849
850B<LocalAddr> and B<LocalPort> - These parameters are passed directly
851to IO::Socket to allow binding the socket to a specific local address and port.
852
853B<Domain> - This parameter is passed directly to IO::Socket and makes it
854possible to enforce IPv4 connections even if L<IO::Socket::IP> is used as super
855class. Alternatively B<Family> can be used.
856
857=back
858
859=head2 Object Methods
860
861Unless otherwise stated all methods return either a I<true> or I<false>
862value, with I<true> meaning that the operation was a success. When a method
863states that it returns a value, failure will be returned as I<undef> or an
864empty list.
865
866C<Net::NNTP> inherits from C<Net::Cmd> so methods defined in C<Net::Cmd> may
867be used to send commands to the remote NNTP server in addition to the methods
868documented here.
869
870=over 4
871
872=item C<host()>
873
874Returns the value used by the constructor, and passed to IO::Socket::INET,
875to connect to the host.
876
877=item C<starttls()>
878
879Upgrade existing plain connection to SSL.
880Any arguments necessary for SSL must be given in C<new> already.
881
882=item C<article([{$msgid|$msgnum}[, $fh]])>
883
884Retrieve the header, a blank line, then the body (text) of the
885specified article.
886
887If C<$fh> is specified then it is expected to be a valid filehandle
888and the result will be printed to it, on success a true value will be
889returned. If C<$fh> is not specified then the return value, on success,
890will be a reference to an array containing the article requested, each
891entry in the array will contain one line of the article.
892
893If no arguments are passed then the current article in the currently
894selected newsgroup is fetched.
895
896C<$msgnum> is a numeric id of an article in the current newsgroup, and
897will change the current article pointer.  C<$msgid> is the message id of
898an article as shown in that article's header.  It is anticipated that the
899client will obtain the C<$msgid> from a list provided by the C<newnews>
900command, from references contained within another article, or from the
901message-id provided in the response to some other commands.
902
903If there is an error then C<undef> will be returned.
904
905=item C<body([{$msgid|$msgnum}[, [$fh]])>
906
907Like C<article> but only fetches the body of the article.
908
909=item C<head([{$msgid|$msgnum}[, [$fh]])>
910
911Like C<article> but only fetches the headers for the article.
912
913=item C<articlefh([{$msgid|$msgnum}])>
914
915=item C<bodyfh([{$msgid|$msgnum}])>
916
917=item C<headfh([{$msgid|$msgnum}])>
918
919These are similar to article(), body() and head(), but rather than
920returning the requested data directly, they return a tied filehandle
921from which to read the article.
922
923=item C<nntpstat([{$msgid|$msgnum}])>
924
925The C<nntpstat> command is similar to the C<article> command except that no
926text is returned.  When selecting by message number within a group,
927the C<nntpstat> command serves to set the "current article pointer" without
928sending text.
929
930Using the C<nntpstat> command to
931select by message-id is valid but of questionable value, since a
932selection by message-id does B<not> alter the "current article pointer".
933
934Returns the message-id of the "current article".
935
936=item C<group([$group])>
937
938Set and/or get the current group. If C<$group> is not given then information
939is returned on the current group.
940
941In a scalar context it returns the group name.
942
943In an array context the return value is a list containing, the number
944of articles in the group, the number of the first article, the number
945of the last article and the group name.
946
947=item C<help()>
948
949Request help text (a short summary of commands that are understood by this
950implementation) from the server. Returns the text or undef upon failure.
951
952=item C<ihave($msgid[, $message])>
953
954The C<ihave> command informs the server that the client has an article
955whose id is C<$msgid>.  If the server desires a copy of that
956article and C<$message> has been given then it will be sent.
957
958Returns I<true> if the server desires the article and C<$message> was
959successfully sent, if specified.
960
961If C<$message> is not specified then the message must be sent using the
962C<datasend> and C<dataend> methods from L<Net::Cmd>
963
964C<$message> can be either an array of lines or a reference to an array
965and must be encoded by the caller to octets of whatever encoding is required,
966e.g. by using the Encode module's C<encode()> function.
967
968=item C<last()>
969
970Set the "current article pointer" to the previous article in the current
971newsgroup.
972
973Returns the message-id of the article.
974
975=item C<date()>
976
977Returns the date on the remote server. This date will be in a UNIX time
978format (seconds since 1970)
979
980=item C<postok()>
981
982C<postok> will return I<true> if the servers initial response indicated
983that it will allow posting.
984
985=item C<authinfo($user, $pass)>
986
987Authenticates to the server (using the original AUTHINFO USER / AUTHINFO PASS
988form, defined in RFC2980) using the supplied username and password.  Please
989note that the password is sent in clear text to the server.  This command
990should not be used with valuable passwords unless the connection to the server
991is somehow protected.
992
993=item C<authinfo_simple($user, $pass)>
994
995Authenticates to the server (using the proposed NNTP V2 AUTHINFO SIMPLE form,
996defined and deprecated in RFC2980) using the supplied username and password.
997As with L</authinfo> the password is sent in clear text.
998
999=item C<list()>
1000
1001Obtain information about all the active newsgroups. The results is a reference
1002to a hash where the key is a group name and each value is a reference to an
1003array. The elements in this array are:- the last article number in the group,
1004the first article number in the group and any information flags about the group.
1005
1006=item C<newgroups($since[, $distributions])>
1007
1008C<$since> is a time value and C<$distributions> is either a distribution
1009pattern or a reference to a list of distribution patterns.
1010The result is the same as C<list>, but the
1011groups return will be limited to those created after C<$since> and, if
1012specified, in one of the distribution areas in C<$distributions>.
1013
1014=item C<newnews($since[, $groups[, $distributions]])>
1015
1016C<$since> is a time value. C<$groups> is either a group pattern or a reference
1017to a list of group patterns. C<$distributions> is either a distribution
1018pattern or a reference to a list of distribution patterns.
1019
1020Returns a reference to a list which contains the message-ids of all news posted
1021after C<$since>, that are in a groups which matched C<$groups> and a
1022distribution which matches C<$distributions>.
1023
1024=item C<next()>
1025
1026Set the "current article pointer" to the next article in the current
1027newsgroup.
1028
1029Returns the message-id of the article.
1030
1031=item C<post([$message])>
1032
1033Post a new article to the news server. If C<$message> is specified and posting
1034is allowed then the message will be sent.
1035
1036If C<$message> is not specified then the message must be sent using the
1037C<datasend> and C<dataend> methods from L<Net::Cmd>
1038
1039C<$message> can be either an array of lines or a reference to an array
1040and must be encoded by the caller to octets of whatever encoding is required,
1041e.g. by using the Encode module's C<encode()> function.
1042
1043The message, either sent via C<datasend> or as the C<$message>
1044parameter, must be in the format as described by RFC822 and must
1045contain From:, Newsgroups: and Subject: headers.
1046
1047=item C<postfh()>
1048
1049Post a new article to the news server using a tied filehandle.  If
1050posting is allowed, this method will return a tied filehandle that you
1051can print() the contents of the article to be posted.  You must
1052explicitly close() the filehandle when you are finished posting the
1053article, and the return value from the close() call will indicate
1054whether the message was successfully posted.
1055
1056=item C<slave()>
1057
1058Tell the remote server that I am not a user client, but probably another
1059news server.
1060
1061=item C<quit()>
1062
1063Quit the remote server and close the socket connection.
1064
1065=item C<can_inet6()>
1066
1067Returns whether we can use IPv6.
1068
1069=item C<can_ssl()>
1070
1071Returns whether we can use SSL.
1072
1073=back
1074
1075=head2 Extension Methods
1076
1077These methods use commands that are not part of the RFC977 documentation. Some
1078servers may not support all of them.
1079
1080=over 4
1081
1082=item C<newsgroups([$pattern])>
1083
1084Returns a reference to a hash where the keys are all the group names which
1085match C<$pattern>, or all of the groups if no pattern is specified, and
1086each value contains the description text for the group.
1087
1088=item C<distributions()>
1089
1090Returns a reference to a hash where the keys are all the possible
1091distribution names and the values are the distribution descriptions.
1092
1093=item C<distribution_patterns()>
1094
1095Returns a reference to an array where each element, itself an array
1096reference, consists of the three fields of a line of the distrib.pats list
1097maintained by some NNTP servers, namely: a weight, a wildmat and a value
1098which the client may use to construct a Distribution header.
1099
1100=item C<subscriptions()>
1101
1102Returns a reference to a list which contains a list of groups which
1103are recommended for a new user to subscribe to.
1104
1105=item C<overview_fmt()>
1106
1107Returns a reference to an array which contain the names of the fields returned
1108by C<xover>.
1109
1110=item C<active_times()>
1111
1112Returns a reference to a hash where the keys are the group names and each
1113value is a reference to an array containing the time the groups was created
1114and an identifier, possibly an Email address, of the creator.
1115
1116=item C<active([$pattern])>
1117
1118Similar to C<list> but only active groups that match the pattern are returned.
1119C<$pattern> can be a group pattern.
1120
1121=item C<xgtitle($pattern)>
1122
1123Returns a reference to a hash where the keys are all the group names which
1124match C<$pattern> and each value is the description text for the group.
1125
1126=item C<xhdr($header, $message_spec)>
1127
1128Obtain the header field C<$header> for all the messages specified.
1129
1130The return value will be a reference
1131to a hash where the keys are the message numbers and each value contains
1132the text of the requested header for that message.
1133
1134=item C<xover($message_spec)>
1135
1136The return value will be a reference
1137to a hash where the keys are the message numbers and each value contains
1138a reference to an array which contains the overview fields for that
1139message.
1140
1141The names of the fields can be obtained by calling C<overview_fmt>.
1142
1143=item C<xpath($message_id)>
1144
1145Returns the path name to the file on the server which contains the specified
1146message.
1147
1148=item C<xpat($header, $pattern, $message_spec)>
1149
1150The result is the same as C<xhdr> except the is will be restricted to
1151headers where the text of the header matches C<$pattern>
1152
1153=item C<xrover($message_spec)>
1154
1155The XROVER command returns reference information for the article(s)
1156specified.
1157
1158Returns a reference to a HASH where the keys are the message numbers and the
1159values are the References: lines from the articles
1160
1161=item C<listgroup([$group])>
1162
1163Returns a reference to a list of all the active messages in C<$group>, or
1164the current group if C<$group> is not specified.
1165
1166=item C<reader()>
1167
1168Tell the server that you are a reader and not another server.
1169
1170This is required by some servers. For example if you are connecting to
1171an INN server and you have transfer permission your connection will
1172be connected to the transfer daemon, not the NNTP daemon. Issuing
1173this command will cause the transfer daemon to hand over control
1174to the NNTP daemon.
1175
1176Some servers do not understand this command, but issuing it and ignoring
1177the response is harmless.
1178
1179=back
1180
1181=head2 Unsupported
1182
1183The following NNTP command are unsupported by the package, and there are
1184no plans to do so.
1185
1186    AUTHINFO GENERIC
1187    XTHREAD
1188    XSEARCH
1189    XINDEX
1190
1191=head2 Definitions
1192
1193=over 4
1194
1195=item $message_spec
1196
1197C<$message_spec> is either a single message-id, a single message number, or
1198a reference to a list of two message numbers.
1199
1200If C<$message_spec> is a reference to a list of two message numbers and the
1201second number in a range is less than or equal to the first then the range
1202represents all messages in the group after the first message number.
1203
1204B<NOTE> For compatibility reasons only with earlier versions of Net::NNTP
1205a message spec can be passed as a list of two numbers, this is deprecated
1206and a reference to the list should now be passed
1207
1208=item $pattern
1209
1210The C<NNTP> protocol uses the C<WILDMAT> format for patterns.
1211The WILDMAT format was first developed by Rich Salz based on
1212the format used in the UNIX "find" command to articulate
1213file names. It was developed to provide a uniform mechanism
1214for matching patterns in the same manner that the UNIX shell
1215matches filenames.
1216
1217Patterns are implicitly anchored at the
1218beginning and end of each string when testing for a match.
1219
1220There are five pattern matching operations other than a strict
1221one-to-one match between the pattern and the source to be
1222checked for a match.
1223
1224The first is an asterisk C<*> to match any sequence of zero or more
1225characters.
1226
1227The second is a question mark C<?> to match any single character. The
1228third specifies a specific set of characters.
1229
1230The set is specified as a list of characters, or as a range of characters
1231where the beginning and end of the range are separated by a minus (or dash)
1232character, or as any combination of lists and ranges. The dash can
1233also be included in the set as a character it if is the beginning
1234or end of the set. This set is enclosed in square brackets. The
1235close square bracket C<]> may be used in a set if it is the first
1236character in the set.
1237
1238The fourth operation is the same as the
1239logical not of the third operation and is specified the same
1240way as the third with the addition of a caret character C<^> at
1241the beginning of the test string just inside the open square
1242bracket.
1243
1244The final operation uses the backslash character to
1245invalidate the special meaning of an open square bracket C<[>,
1246the asterisk, backslash or the question mark. Two backslashes in
1247sequence will result in the evaluation of the backslash as a
1248character with no special meaning.
1249
1250=over 4
1251
1252=item Examples
1253
1254=item C<[^]-]>
1255
1256matches any single character other than a close square
1257bracket or a minus sign/dash.
1258
1259=item C<*bdc>
1260
1261matches any string that ends with the string "bdc"
1262including the string "bdc" (without quotes).
1263
1264=item C<[0-9a-zA-Z]>
1265
1266matches any single printable alphanumeric ASCII character.
1267
1268=item C<a??d>
1269
1270matches any four character string which begins
1271with a and ends with d.
1272
1273=back
1274
1275=back
1276
1277=head1 EXPORTS
1278
1279I<None>.
1280
1281=head1 KNOWN BUGS
1282
1283See L<https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=libnet>.
1284
1285=head1 SEE ALSO
1286
1287L<Net::Cmd>,
1288L<IO::Socket::SSL>.
1289
1290=head1 AUTHOR
1291
1292Graham Barr E<lt>L<gbarr@pobox.com|mailto:gbarr@pobox.com>E<gt>.
1293
1294Steve Hay E<lt>L<shay@cpan.org|mailto:shay@cpan.org>E<gt> is now maintaining
1295libnet as of version 1.22_02.
1296
1297=head1 COPYRIGHT
1298
1299Copyright (C) 1995-1997 Graham Barr.  All rights reserved.
1300
1301Copyright (C) 2013-2016, 2020 Steve Hay.  All rights reserved.
1302
1303=head1 LICENCE
1304
1305This module is free software; you can redistribute it and/or modify it under the
1306same terms as Perl itself, i.e. under the terms of either the GNU General Public
1307License or the Artistic License, as specified in the F<LICENCE> file.
1308
1309=head1 VERSION
1310
1311Version 3.13
1312
1313=head1 DATE
1314
131523 Dec 2020
1316
1317=head1 HISTORY
1318
1319See the F<Changes> file.
1320
1321=cut
1322