# -*-Perl-*-
################################################################
###
### Nntp.pm
###
### Author: Internet Message Group
### Created: Apr 23, 1997
### Revised: Jul 4, 2004
###
my $PM_VERSION = "IM::Nntp.pm version 20031028(IM146)";
package IM::Nntp;
require 5.003;
require Exporter;
use Fcntl;
use IM::Config qw(nntphistoryfile nntpservers nntpauthuser set_nntpauthuser
nntp_timeout);
use IM::TcpTransaction;
use IM::Util;
use integer;
use strict;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(
nntp_open
nntp_close
nntp_transaction
nntp_article
nntp_list
nntp_command
nntp_command_response
nntp_next_response
nntp_get_message
nntp_get_msg
nntp_head_as_string
nntp_spec
);
use vars qw($Nntp_opened *NNTPd $NntpErrTitle);
##### NNTP SESSION OPENING #####
#
# nntp_open(server_list)
# server_list:
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub nntp_open($$) {
my($servers, $logging) = @_;
my $rc;
if ($Nntp_opened) {
return 0 if (grep(&get_cur_server_original_form() eq $_, @$servers));
&nntp_close;
}
&tcp_logging($logging);
*NNTPd = &connect_server($servers, 'nntp', 0);
return 1 if ($NNTPd eq '');
$NntpErrTitle = "(while talking to " . &get_cur_server() . " with nntp)\n";
if ($rc = &tcp_command(\*NNTPd, '', '')) {
return $rc;
}
my(@resp) = &command_response;
if ($resp[0] =~ /InterNetNews server INN/) {
return 1 if (&tcp_command(\*NNTPd, 'MODE reader', ''));
}
$Nntp_opened = 1;
return 0;
}
##### NNTP SESSION CLOSING #####
#
# nntp_close()
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub nntp_close() {
return 0 unless ($Nntp_opened);
$Nntp_opened = 0;
im_notice("closing NNTP session.\n");
return 1 if (&tcp_command(\*NNTPd, 'QUIT', ''));
close(NNTPd);
return 0;
}
##### NNTP TRANSACTION MANAGEMENT #####
#
# nntp_transaction(server_list, header, body, group, part, total, authuser)
# server_list: list of NNTP servers
# group: news group to be posted in
# part: part number to be sent in partial message mode
# total: total number of partial messages
# authuser: User name for NNTP authentication
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub nntp_transaction($$$$$$$) {
my($servers, $Header, $Body, $group, $part, $total, $authuser) = @_;
my $rc;
require IM::Log && import IM::Log;
&set_nntpauthuser($authuser);
do {
$rc = &nntp_transact_sub($servers, $Header, $Body, $part, $total);
my(@resp) = &command_response;
if ($rc) {
&im_warn($NntpErrTitle . join("\n", @resp) . "\n");
$NntpErrTitle = '';
&nntp_close;
&log_action('nntp', &get_cur_server(), $group,
($#$servers >= 0) ? 'skipped' : 'failed', @resp);
return -1 if ($rc < 0);
return -1 if (grep(/^(435|437|440|441)/, @resp) > 0);
} else {
&log_action('nntp', &get_cur_server(), $group, 'sent', @resp);
}
} while ($rc > 0 && $#$servers >= 0);
return $rc;
}
##### NNTP TRANSACTION MANAGEMENT SUBROUTINE #####
#
# nntp_transact_sub(server_list, part, total)
# server_list: list of NNTP servers
# part: part number to be sent in partial message mode
# total: total number of partial messages
# return value:
# 0: success
# 1: recoverable error (should be retried)
# -1: unrecoverable error
#
sub nntp_transact_sub($$$$$) {
my($servers, $Header, $Body, $part, $total) = @_;
my $rc;
return $rc if ($rc = &nntp_open($servers, 1));
return -1 if (($rc = &nntp_command("POST")) < 0);
select (NNTPd); $| = 0; select (STDOUT);
require IM::Message && import IM::Message;
&set_crlf("\r\n");
if ($part == 0) {
return 1 if (&put_header(\*NNTPd, $Header, 'nntp', 'all') < 0);
return 1 if (&put_body(\*NNTPd, $Body, 1, 0) < 0);
} else {
return 1 if (&put_mimed_partial(\*NNTPd, $Header, $Body,
'nntp', 1, $part, $total) < 0);
}
select (NNTPd); $| = 1; select (STDOUT);
return $rc if ($rc = &tcp_command(\*NNTPd, '.', ''));
return 0;
}
sub nntp_head_as_string($) {
my $i = shift;
my($rc, $count) = ('', 0);
local $_;
im_notice("getting article $i.\n");
$rc = &tcp_command(\*NNTPd, "HEAD $i", '');
if ($rc != 0) {
im_warn("HEAD command failed.\n");
return -1;
}
$count++;
my($found, $f) = (0, '');
alarm(nntp_timeout()) unless win95p();
while () {
alarm(0) unless win95p();
s/\r\n$/\n/;
last if ($_ =~ /^\.\n$/);
s/^\.//;
im_debug($_) if (&debug('nntp'));
$f .= $_;
}
alarm(0) unless win95p();
if (!defined($_)) {
# may be channel trouble
im_warn("lost connection for HEAD.\n");
return -1;
}
return $f;
}
sub nntp_head($$) {
my($art_start, $art_end) = @_;
local $_;
my $count = 0;
my $i;
for ($i = $art_start; $i <= $art_end; $i++) {
im_notice("getting article $i.\n");
my $rc = &tcp_command(\*NNTPd, "HEAD $i", '');
next if ($rc > 0);
if ($rc < 0) {
im_warn("HEAD command failed.\n");
return -1;
}
$count++;
my($found, $f) = (0, '');
alarm(nntp_timeout()) unless win95p();
while () {
alarm(0) unless win95p();
s/\r\n$/\n/;
last if ($_ =~ /^\.\n$/);
s/^\.//;
im_debug($_) if (&debug('nntp'));
if ($f eq '' && /^From:\s*(.*)/i) {
$found = 1;
$f = $1;
} elsif (/^\s/ && $found) {
$f .= $_;
} else {
$found = 0;
}
}
alarm(0) unless win95p();
if (!defined($_)) {
# may be channel trouble
im_warn("lost connection for HEAD.\n");
return -1;
}
$f =~ s/\n[ \t]*/ /g;
$f = '(sender unknown)' unless ($f);
print "From $f\n";
}
return $count;
}
sub nntp_xover($$) {
my($art_start, $art_end) = @_;
my $rc = &tcp_command(\*NNTPd, "XOVER $art_start-$art_end", '');
if ($rc) {
im_warn("XOVER command failed.\n");
return -1;
}
my $count = 0;
my($resp);
while (($resp = &next_response(\*NNTPd)) !~ /^\.$/) {
$count++;
my @overview = split('\t', $resp);
# 0: article number
# 1: Subject:
# 2: From:
# 3: Date:
# 4: Message-ID:
# 5: References:
# 6: Bytes:
# 7: Lines:
print "From $overview[2]\n";
}
return $count;
}
sub nntp_article($) {
my $num = shift;
local $_;
# local(@Article);
im_debug("getting article $num.\n") if (&debug('nntp'));
my $rc = &tcp_command(\*NNTPd, "ARTICLE $num", '');
return(1, '') if ($rc > 0);
if ($rc < 0) {
im_warn("ARTICLE command failed.\n");
return(-1, '');
}
my @Article = ();
alarm(nntp_timeout()) unless win95p();
while () {
alarm(0) unless win95p();
s/\r\n$/\n/;
last if ($_ =~ /^\.\n$/);
s/^\.//;
push (@Article, $_);
im_debug($_) if (&debug('nntp'));
}
alarm(0) unless win95p();
if (!defined($_)) {
# may be channel trouble
im_warn("lost connection for ARTICLE.\n");
return(-1, '');
}
return(0, \@Article);
}
sub nntp_articles($$$$) {
my($art_start, $art_end, $dst, $limit) = @_;
my($rc, $article);
my $count = 0;
my $last = 0;
my $i;
require IM::MsgStore && import IM::MsgStore;
for ($i = $art_start; $i <= $art_end; $i++) {
($rc, $article) = &nntp_article($i);
next if ($rc > 0);
if ($rc < 0) {
return -1 if ($i == $art_start);
im_warn("some articles left due to failure.\n");
$last = $i-1;
nntp_close();
last;
}
$count++;
return -1 if (&store_message($article, $dst) < 0);
$last = $i;
last if ($limit && --$limit == 0);
}
&exec_getsbrfile($dst);
return($count, $last);
}
sub nntp_list($) {
my $group = shift;
local $_;
my $rc;
return -1 if (($rc = &nntp_command("LIST ACTIVE")) < 0);
if ($rc) {
im_warn("LIST command failed.\n");
return -1;
}
my $count = 0;
my $resp;
while (($resp = &next_response(\*NNTPd)) !~ /^\.$/) {
next unless (/^$group/);
$count++;
print "$resp\n";
}
return $count;
}
sub nntp_command($) {
my $cmd = shift;
my $rc = &tcp_command(\*NNTPd, $cmd, '');
return -1 if ($rc < 0);
if ($rc > 0) {
my($res) = &command_response();
if ($res =~ /^480/) {
require IM::GetPass && import IM::GetPass;
# print "Username: ";
# my $user = ;
# chomp($user);
my $user = &nntpauthuser() ||
$ENV{'USER'} || $ENV{'LOGNAME'} || im_getlogin();
my $host = get_cur_server();
my($pass, $agtfound, $interact)
= getpass('nntp', 'PASS', $host, $user);
# authenticate for posting
return $rc
if ($rc = &tcp_command(\*NNTPd, "AUTHINFO USER $user", ''));
return $rc
if ($rc = &tcp_command(\*NNTPd, "AUTHINFO PASS $pass",
"AUTHINFO PASS " . "*" x length($pass)));
$rc = &tcp_command(\*NNTPd, $cmd, '');
return -1 if ($rc < 0);
}
}
return $rc;
}
sub nntp_command_response() {
return &command_response;
}
sub nntp_next_response() {
return &next_response(\*NNTPd);
}
sub set_last_article_number($$$) {
my($server, $group, $number) = @_;
my($pos, $last, $size) = (0, 0, 0);
$server =~ s!\%\d+$!!;
$server =~ s!/\d+$!!;
my $nntphist = &nntphistoryfile() . '-' . $server;
if (-f $nntphist) {
im_open(\*NEWSHIST, "+<$nntphist");
while ($pos = tell(NEWSHIST), $_ = ) {
/^([^:]+):\s*(\d+)/;
if ($group eq $1) {
$last = $2;
im_debug("$last articles in $group ($nntphist)\n")
if (&debug('nntp'));
seek(NEWSHIST, $pos, 0);
$size = length($_) - length("$group: 0000000\n");
if ($size < 0) {
# no room to rewrite it
s/^./#/;
print NEWSHIST $_;
seek(NEWSHIST, 0, 2);
$size = 0;
}
printf NEWSHIST "$group: %${size}s%07d\n", '', $number;
close (NEWSHIST);
return $last;
}
}
} else {
# open (NEWSHIST, ">$nntphist");
im_sysopen(\*NEWSHIST, $nntphist, O_RDWR()|O_CREAT());
}
seek(NEWSHIST, 0, 2);
printf NEWSHIST "$group: %${size}s%07d\n", '', $number;
close (NEWSHIST);
return $last;
}
sub get_last_article_number($$) {
my($server, $group) = @_;
local $_;
my $number = 0;
$server =~ s!\%\d+$!!;
$server =~ s!/\d+$!!;
my $nntphist = &nntphistoryfile() . '-' . $server;
if (im_open(\*NEWSHIST, "<$nntphist")) {
while () {
/^([^:]+):\s*(\d+)/;
if ($group eq $1) {
$number = $2;
last;
}
}
close (NEWSHIST);
}
return $number;
}
sub nntp_get_message($$) {
my($src, $msg) = @_;
my($rc, $art);
my($group, $srvs) = nntp_spec($src, nntpservers());
my @servers = split(',', $srvs);
im_notice("accessing to $group on $srvs.\n");
do {
if (($rc = nntp_open(\@servers, 0)) < 0) {
return(-1, "can not connect $srvs.\n");
}
if (($group ne '') && ($rc = nntp_command("GROUP $group")) < 0) {
return(-1, "can not access $group.\n");
}
} while (@servers > 0 && $rc > 0);
return(-1, "can not access $group on $srvs.\n") if ($rc);
($rc, $art) = nntp_article($msg);
nntp_close();
return(-1, "no message $msg in -$group.\n") if ($rc);
return(0, $art);
}
# returns number of got articles
# -1 if error
sub nntp_get_msg($$$$) {
my($src, $dst, $how, $limit) = @_;
my($rc, $group, $error, $art_start, $art_end);
my($servers, @servers);
if ($src =~ /^nntp:(.*)/i || $src =~ /^news:(.*)/i) {
($group, $servers) = &nntp_spec($1, nntpservers());
@servers = split(',', $servers);
} else {
im_warn("no news group specified ($src).\n");
return -1;
}
im_notice("accessing to $group at $servers.\n");
do {
if (($rc = &nntp_open(\@servers, 0)) < 0) {
im_warn("Connection failed to $servers.\n");
return -1;
}
return -1 if (($rc = &nntp_command("GROUP $group")) < 0);
} while (@servers > 0 && $rc > 0);
return -1 if ($rc);
my(@resp) = &command_response;
$error = 0;
my $i;
for ($i = 0; $i <= $#resp; $i++) {
if ($resp[0] =~ /^211 (\d+) (\d+) (\d+) (\S+)/) {
if ($4 ne $group) {
# Should not occur
$error = 1;
} else {
$art_start = $2;
$art_end = $3;
}
last;
}
}
if ($error) {
&nntp_close;
return -1;
}
my($art_last, $msgs);
$art_last = &get_last_article_number($servers, $group);
if ($art_end > $art_last) {
# new articles
if ($art_start < $art_last) {
$art_start = $art_last + 1;
}
$msgs = $art_end - $art_start + 1;
} else {
$msgs = 0;
}
if ($how eq 'skip') {
# &nntp_close;
my $last = &set_last_article_number($servers, $group, $art_end);
if ($last < $art_end) {
my $num = $art_end - $last;
im_info("$num article(s) have been marked "
."as read in $group at $servers.\n");
} else {
im_info("no news in $group at $servers.\n");
}
return $msgs;
}
if ($how eq 'check') {
if ($msgs > 0) {
im_info("$msgs news in $group at $servers.\n");
} else {
im_info("no news in $group at $servers.\n");
}
# &nntp_close;
return $msgs;
}
if ($how eq 'from') {
if ($msgs > 0) {
$msgs = &nntp_xover($art_start, $art_end);
$msgs = &nntp_head($art_start, $art_end) if ($msgs < 0);
if ($msgs < 0) {
im_warn("can not get article poster information.\n");
return -1;
}
im_info("$msgs article(s) in $group at $servers.\n");
} else {
im_info("no news in $group at $servers.\n");
}
# &nntp_close;
return $msgs;
}
if ($how eq 'get') {
my($last);
if ($msgs > 0) {
im_info("Getting new messages from $group at $servers into $dst...\n");
($msgs, $last) = &nntp_articles($art_start, $art_end, $dst, $limit);
if ($msgs < 0) {
im_warn("can not get articles.\n");
return -1;
}
im_info("$msgs message(s).\n");
} else {
im_info("no messages in $group at $servers.\n");
}
# &nntp_close;
&set_last_article_number($servers, $group, $last) if ($last);
return $msgs;
}
return -1;
}
# News group (-group[@server])
sub nntp_spec($$) {
my($spec, $server) = @_;
my $group;
if ($spec =~ /^-(.*)/) {
$group = $1;
} elsif ($spec =~ /([^@]*)\@(.*)/) {
$group = $1;
$server = $2;
} else {
$group = $spec;
}
return($group, $server);
}
1;
__END__
=head1 NAME
IM::Nntp - NNTP hanlder
=head1 SYNOPSIS
use IM::Nntp;
$return_code = &nntp_transaction(server_list, newsgroups,
part_current, part_total, authuser);
$return_code = &nntp_close;
Other subroutines:
nntp_open
nntp_article
nntp_list
nntp_command
nntp_command_response
nntp_next_response
nntp_get_message
nntp_get_msg
nntp_head_as_string
nntp_spec
=head1 DESCRIPTION
The I module handles NNTP.
This modules is provided by IM (Internet Message).
=head1 COPYRIGHT
IM (Internet Message) is copyrighted by IM developing team.
You can redistribute it and/or modify it under the modified BSD
license. See the copyright file for more details.
=cut
### Copyright (C) 1997, 1998, 1999 IM developing team
### All rights reserved.
###
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions
### are met:
###
### 1. Redistributions of source code must retain the above copyright
### notice, this list of conditions and the following disclaimer.
### 2. Redistributions in binary form must reproduce the above copyright
### notice, this list of conditions and the following disclaimer in the
### documentation and/or other materials provided with the distribution.
### 3. Neither the name of the team nor the names of its contributors
### may be used to endorse or promote products derived from this software
### without specific prior written permission.
###
### THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS'' AND
### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
### PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TEAM OR CONTRIBUTORS BE
### LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
### CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
### SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
### BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
### WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
### OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
### IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.