#!/usr/local/bin/perl use strict; use warnings; use Net::NNTP::Client; use GDBM_File; use Email::Send; use Data::Dumper; use constant DEBUG => 0; select(STDERR); $| = 1; select(STDOUT); my $confdir = "$ENV{HOME}/.news2mail"; unless(-d $confdir && -f $confdir.'/config.pl') { mkdir $confdir || die("Couldn't create $confdir\n"); open(FILE, ">$confdir/config.pl") || die("Couldn't create $confdir/config.pl\n"); print FILE join("\n", "smtphost => 'localhost',", "address => 'myaddress\@example.com',", "'user:pass\@news.example.com:119' => {", " 'misc.test' => { post => 1 },", " 'alt.config' => { post => 1 },", "},", "'privateserver.localdomain' => {", " 'my.secret.newsgroup' => { post => 1 },", "}" ); close(FILE); print "You need to configure me! Take a look in $confdir\n"; exit; } tie my %seen, 'GDBM_File', "$confdir/seen.dbm", GDBM_WRCREAT, 0640; my $subscriptions = { do $confdir.'/config.pl' }; my $smtphost = $subscriptions->{smtphost}; my $address = $subscriptions->{address}; delete $subscriptions->{smtphost}; delete $subscriptions->{address}; SERVERS: foreach my $server (keys %{$subscriptions}) { unless(-d "$confdir/$server") { mkdir("$confdir/$server") || die("Can't create $confdir/$server\n"); } my($auth, $host) = split('@', $server); ($auth, $host) = (':', $auth) if(!$host); my($user, $pass) = split(':', $auth); ($host, my $port) = split(':', $host); $port ||= 119; print "Connecting to $host:$port with credentials [$user,$pass]\n" if(DEBUG); my $client = Net::NNTP::Client->new( "$host:$port", server => $host, port => $port, (($user) ? (user => $user, pass => $pass) : ()), debug => 0, # set to 1 by default - hate! ); # refresh groups list if this is a new server or the list is more # than 7 days old if(!-f "$confdir/groups.$server" || -M "$confdir/groups.$server" > 7) { my $list = eval { $client->list() }; if($@) { print STDERR "$@\n"; next SERVERS; } open(FILE, ">$confdir/groups.$server") || die("Can't write $confdir/groups.$server\n"); print FILE "$_\n" foreach(sort keys %{$list}); close(FILE); print "Updated groups list, see $confdir/groups.$server\n"; } GROUPS: foreach my $group (keys %{$subscriptions->{$server}}) { my($articles, $firstarticle) = eval { $client->group($group); }; if($@) { print STDERR "$@\n"; next SERVERS; } if(!-e "$confdir/$server/$group.lastretrieved") { # new subscription, so set lastretrieved to current open(FOO, ">$confdir/$server/$group.lastretrieved") || die("Can't write $confdir/$server/$group.lastretrieved\n"); print FOO $firstarticle + $articles - 1; close(FOO); print "New subscription: $server/$group\n first: $firstarticle\n articles: $articles\n" if(DEBUG); next GROUPS; } open(FOO, "$confdir/$server/$group.lastretrieved") || die("Can't read $confdir/$server/$group.lastretrieved\n"); chomp(my $lastretrieved = ); $client->nntpstat($lastretrieved); close(FOO); FETCHNEWS: while(my $msgid = $client->next()) { if(exists($seen{$msgid})) { # already got this article print "$group: $msgid already seen\n" if(DEBUG); $seen{$msgid} = time(); # update timestamp $lastretrieved++; open(FOO, ">$confdir/$server/$group.lastretrieved") || die("Can't write $confdir/$server/$group.lastretrieved\n"); print FOO $lastretrieved; close(FOO); next FETCHNEWS; } my $article = [map { chomp; $_; } @{$client->head($msgid)}]; # filter based on headers here push @{$article}, '', map { chomp; $_; } @{$client->body($msgid)}; print "$group: fetched $msgid\n" if(DEBUG); my @newsheaders; my @headers = ( "To: $address", "Reply-To: ". ($subscriptions->{$server}->{$group}->{post} || 'dave.null'), "X-Newsgroup: $group" ); while((my $line = shift(@{$article})) ne '') { if($line =~ /^\s+/) { $newsheaders[-1] .= $line; } else { push @newsheaders, $line; } } foreach my $line (@newsheaders) { if($line =~ /^(Message-Id|Date|Subject|From)/i) { # headers common to mail and news push @headers, $line; } elsif($line =~ /^Path: (.*)$/i) { push @headers, "Received: from $host with NNTP path $1"; } elsif($line =~ /^References: (.*)$/i) { my @refs = split(/\s+/, $1); push @headers, $line, "In-Reply-To: $refs[-1]"; } else { push @headers, "X-NNTP-Header-$line"; } } my $body = $article; my $sender = Email::Send->new({mail => 'SMTP'}); $sender->mailer_args([Host => $smtphost]); $sender->send( join("\n\n", join("\n", sort { $a cmp $b } @headers), join("\n", @{$body}) ) ); $seen{$msgid} = time(); # so we never fetch this one again $lastretrieved++; open(FOO, ">$confdir/$server/$group.lastretrieved") || die("Can't write $confdir/$server/$group.lastretrieved\n"); print FOO $lastretrieved; close(FOO); } } } # while(my($msgid, $time) = each(%seen)) { # if(time() - $time > 2 * 86400) { # delete $seen{$msgid}; # } # } # now optimise seendb # delete anything from 'seen' more than a year old. This is partially for # dropping cross-posts, (we use lastretrieved to avoid going back in time) # but also for when a server shits its pants my %newseen; $newseen{$_} = $seen{$_} foreach( grep { time() - $seen{$_} < 365 * 86400 } keys %seen ); untie %seen; unlink "$confdir/seen.dbm"; tie %seen, 'GDBM_File', "$confdir/seen.dbm", GDBM_WRCREAT, 0640; $seen{$_} = $newseen{$_} foreach(keys %newseen); untie %seen;