#!/usr/bin/perl

# Simple news poster.
#
# I wrote it as a replacement to rnews for one of tasks in my environment .
#
# Hopefully it may be usefull for others. Tested w/ innd 2.4.2,  MAY work w/ others.
# No any warranty, as w/ any other software. =) As for me this's pretty scrpit for
# people who wonn't dance around rnews. :-D It's easily configurable.
#
# License is GNU GPL. (c) Olli Artemjev http://www.livejournal.com/users/grey_olli .
# Thanks to:
#			Authors & developers of perl, Net::Telnet, Getopt::Long.
#			Special thanks to rserg (http://www.livejournal.com/users/rserg).
# 
# Please write to olli@digger.org.ru if your another (not innd) news server also
# work OK w/ this script.
# 
# 
# Difference w/ rnews:
#
#	1. Does NOT require msgid in your data - presumes it's generated by news server.
#      Though you may supply correct msgid in your text.
#   2. Does NOT log msgid of posted messages.
#      Though it'll be present in one of debug files, if you turned on debugging (after
#      'recommened ID') .
#   3. Does NOT store messages that where failed to post.
#   4. Accepts data to post ONLY from STDIN.
#   5. Does NOT require nor open any innd configs - you may have no innd and be able
#      to post w/ this script to other destinations.
#   6. Does NOT understand batch format: ALL data from STDIN 'll be posted as message.
#
#   
# Other bonuces:
#
#   1. Config options support customisation of
#		'Newsgroups:'
#		'From:'
#		'Subject:'
#		'X-Comment-To:'
#       'Content-Type:'
#		'Content-Transfer-Encoding:'
#       'X-Flags:'
#   2. ANY config file OPTION MAY be OVERRIDEn via command line switch (including above Keywords.
#	3. Adequate defaults for koi8-r users for 'Content-Type:','Content-Transfer-Encoding:' . =)
#   4. As much as it possible w/ perl ( huh! :) ) this script shouldn't eat memory on big files - 
#      it parses them line by line w/o loading all the file into memory. =) 
#
# Notes:
#
#      Write input to file on posting failure is useless for me option - I post to my LAN.
#      Though if script 'll get used by other people - I'll add supporting this to my
#      '2do' queue. =) Just mail me w/ adequate 'Subject:' (I review mailbox by Subjects
#      & most SPAM is being deleted w/o reading. Some times I may delete non-spam mail..
#      If you didn't get an answer in a week or two - just mail again).
#
#      No support for password based authentification on news server (I use host based). Though if
#      people'll ask for it - I'll implement.
#
# The homepage for this script 'll be below http://olli.digger.org.ru/files/pub/programming/perl/DONE/ .
# 
#
# ----------------------------> define variables (anyone may freely edit)
$port=119;				# default news port

$host="nntp.your.domain";		# Where we're connecting to (MUST be identical to
					# the server advert for itself).
					# Try 'telnet $host $port' to find out how it should
					# look like.

$timeout=8;				# timeout value for connection and for getting replies
					# from commands to server.

$newsgroup="blogs";			# Default newsgroup to post to.

$from="Script not configured <script\@your.domain>";	# The default 'From:' value of your post.

$lseparator="\r\n";					# '\r\n' works for my innd 2.4.2 news server. ;)

$defaultsubj="BLOG updated:";				# 'Subject:' field by default.

$postto="All";						# The 'X-Comment-To:' field by default.

$debug=0;						# if set to anything but 0 'll make loggs.
							# & also some noise on stdout.

$logdir=".";						# directory to store debugging files. "." is for current.

$guesheaders=1;						# if set to anything but 0 'll try to get headers from 1st
							# few strings of your data. The number of these set in $headerstrings .
							# Currently support searching for these:
							# ' Newsgroups:', 'From: ', ''

$headerstrings=10;					# Number of strings to search for more headers (AKA kludges)
							# For me usually 10 is enough. These strings are presumed to be
							# at the beginning of your data.

$DefCntntType="text/plain; charset=koi8-r";		# default value for 'Content-Type:' keyword.

$DefEncoding="8bit";					# Default value for 'Content-Transfer-Encoding:'

$DefXFlags="m";						# Default value for 'X-Flags:'

# ----------------------------> code work start  (tech stuff - require some perl knowledge to edit )
use Net::Telnet;
use Getopt::Long;   # command line processing module.

GetOptions
('help|?!',\$cl_help,
'lt=s',\$cl_lineterm,
'h=s',\$cl_host,
'p=i',\$cl_port,
't=s',\$cl_tmout,
'g=s',\$cl_ngroup,
'f=s',\$cl_from,
's=s',\$cl_subj,
'x=s',\$cl_xcommto,
'd+',\$cl_debug,
'ld=s',\$cl_logdir,
'a+',\$cl_autohdrs,
'hlc=i',\$cl_hdrlns,
'ct=s',\$cl_conttype,
'e=s',\$cl_enc,
'xfl=s',\$cl_xflags,
'v+',\$cl_version,
'i+',\$cl_license
) || die "\nTry `$name --help`\n\n";

if ( $cl_help ) {
 print "\n\n";
 print "Options are avaliable in both short and long forms (i.e. --short is equivalent to -s)\n";
 print "  --option=s gets a string as value. Remember about shell escaping (i.e. spaces shoud be escaped w/ '\\').\n";
 print "  --option=i gets an integer as a value. \n";
 print "  --option+  has no value - it's a trigger inversing the default config value ( 0 -> 1 or 1 -> 0 ). \n";
 print "\n\n";
 print "The following options are known:\n\n";
 print "'-help|?!'\tgive overview on commandline options.\n";
 print "'-lt=s'\t\toverride default news proto line delimiter sequence. Default is \"\\r\\n\". Don't touch if unsure.\n";
 print "'-h=s'\t\toverride default host to connect to. Default is '$host' .\n";
 print "'-p=i'\t\toverride default port to connect to. Default is '$port' .\n";
 print "'-t=s'\t\toverride default timeoute value (connect & commands). Default is '$timeout' .\n";
 print "'-g=s'\t\toverride default 'Newsgroups:' to post to value. Default is '$newsgroup' .\n";
 print "'-f=s'\t\toverride default 'From:' in postings. Default is: '$from' .\n";
 print "'-s=s'\t\toverride default 'Subject:' in postings. Default is: '$defaultsubj' .\n";
 print "'-x=s'\t\toverride default 'X-Comment-To:' in postings. Default is:' $postto' .\n";
 print "'-d+'\t\tdebug switch (inverts default configuration value). Default is '$debug' .\n";
 print "'-ld=s'\t\toverride default logging directory path value. Default is '$logdir' .\n";
 print "'-a+'\t\tautodetect headers from head of input data switch. Default is '$guesheaders' .\n";
 print "'-hlc=i'\tnumber of lines from input used for autodetection(above). Default is '$headerstrings' .\n";
 print "'-ct=s'\t\toverride default 'Content-Type:' in postings. Default is '$DefCntntType' .\n";
 print "'-e=s'\t\toverride default 'Content-Transfer-Encoding:'. Default is '$DefEncoding' .\n";
 print "'-xfl=s'\toverride default 'X-Flags:'. Default is '$DefXFlags' .\n";
 print " '-v+'\t\tShow version of this script.\n";
 print " '-i+'\t\tShow information on license.\n";
 print "\n\n";
 print "NOTICE:\nAll these keys'll set DEFAULTS. ";
 print "If 'autodetect headers' is 1 (i.e. ON) all Keywords (i.e. 'Subject:','Newsgroups:') may get replaced";
 print " from 1st $headerstrings strings of input data. ";
 print "Set 'autodetect headers' to 0 (OFF) to avoid this (use '-a' switch to trigger value). ";
 print "Also note, that '-hlc' needs '-a' & is silently ignored w/o the '-a' key. ";
 print "Same w/ '-ld' w/o '-d' . \n\n";
 exit;
}

if ( $cl_debug ) { 
  # ($debug) && print "\$debug option change request from command line\n";
  if ( $debug ) {$debug=0;}
   else  {$debug=1;} # if set to anything but 0 'll do log . 
}


if ( $cl_license ) {
 ($debug) && print STDERR "license info request from command line.\n";
 print "\nLicense is GNU GENERAL PUBLIC LICENSE Version 2, June 1991\n";
 print "You should've one received w/ script. Try google for it if not present. =)\n\n";
 exit 0;
}

if ( $cl_version ) {
 ($debug) && print STDERR "version info request from command line.\n";
 print "\nVersion 1.0, production, stable.\n";
 print "(c) Olli Artemjev <olli\@digger.org.ru>.\n";
 print "Report bugs to olli\@digger.org.ru .\n\n";
 exit 0;
}

if ( $cl_port ) {
 ($debug) &&  print "\$port change request from command line.\n";
 $port=$cl_port;							## default news port
}

if ( $cl_host ) {
 ($debug) &&  print "\$host change request from command line.\n";
 $host=$cl_host;		# Where we're connecting to (MUST be identical to
 chomp($host);
 chomp($host);
}

if ( $cl_tmout ) {
 ($debug) &&  print "\$timeout change request from command line.\n";
 $timeout=$cl_tmout;							# timeout value for connection and for getting replies
}

if ( $cl_ngroup ) {
 ($debug) &&  print "\$newsgroup change request from command line.\n";
 $newsgroup=$cl_ngroup;					# Default newsgroup to post to.
 chomp($newsgroup);
 chomp($newsgroup);
}

if ( $cl_from ) {
 ($debug) &&  print "\$from change request from command line.\n";
 $from=$cl_from;	# The default 'From:' value of your post.
 chomp($from);
 chomp($from);
}

if ( $cl_lineterm ) {
 ($debug) &&  print "\$lseparator change request from command line.\n";
 $lseparator=$cl_lineterm;				# '\r\n' works for my innd 2.4.2 news server. ;)
 chomp($lseparator);
 chomp($lseparator);
}

if ( $cl_subj ) {
 ($debug) && print "\$defaultsubj change request from command line.\n";
 $defaultsubj=$cl_subj;		# 'Subject:' field by default.
 chomp($defaultsubj);
 chomp($defaultsubj);
}

if ( $cl_xcommto ) {
  ($debug) && print "\$postto change request from command line.\n";
  $postto="$cl_xcommto";						# The 'To:' field by default.
  chomp($postto);
  chomp($postto);
}


if ( $cl_logdir ) {
 ($debug) && print "\$logdir change request from command line.\n";
 $logdir=$cl_logdir;						# directory to store debugging files. "." is for current.
 chomp($logdir);
 chomp($logdir);
}

if ( $cl_autohdrs ) {
 ($debug) && print "\$guesheaders change request from command line.\n";
 if ( $guesheaders ) {$guesheaders=0;}
   else {$guesheaders=1;} # if set to anything but 0 'll try to get headers from 1st
 print "\$guesheaders now $guesheaders .\n";

}

if ( $cl_hdrlns ) {
 ($debug) && print "\$headerstrings change request from command line.\n";
 $headerstrings=$cl_hdrlns;					# Number of strings to search for more headers (AKA kludges)
}

if ( $cl_conttype ) {
 ($debug) && print "\$DefCntntType change request from command line.\n";
 $DefCntntType=$cl_conttype; # default value for 'Content-Type:' keyword.
 chomp($DefCntntType);
 chomp($DefCntntType);
}

if ( $cl_enc ) {
 ($debug) && print "\$DefEncoding change request from command line.\n";
 $DefEncoding=$cl_enc;				# Default value for 'Content-Transfer-Encoding:'
 chomp($DefEncoding);
 chomp($DefEncoding);
}

if ( $cl_xflags ) {
 ($debug) && print "\$DefXFlags option change request from command line\n";
 $DefXFlags=$cl_xflags;						# Default value for 'X-Flags:'
 chomp($DefXFlags);
 chomp($DefXFlags);
}

if ( $debug ) {
	$to_us_logfile="$logdir/strings_from.$host.log";
	$from_us_logfile="$logdir/strings_to.$host.log";
}


### This some times gives duplicates, at least I had a luck to catch a duplicate rejecton from innd. :P
### Also msgid is not required - see comments below.
#@randinit = 0 .. 10;
#srand;
#@msgid = ();
#while (@randinit) {
#	push(@msgid, splice(@randinit, rand @randinit, 1));
#}
#$msgidstr=@msgid;

$t_hndl = new Net::Telnet (Timeout => $timeout,
							Host => $host,
							Port => $port,
							Input_record_separator => $lseparator,
							Output_record_separator => $lseparator 
							);
if ( $debug ) {
$t_hndl->input_log($to_us_logfile);
$t_hndl->output_log($from_us_logfile);
}

$t_hndl->binmode(1); # turn OFF @!cking convertion. =)
#$t_hndl->binmode(0); # turn ON @!cking convertion. =)

$t_hndl->waitfor("/^200 $host/i");
$t_hndl->print("group $newsgroup");
$t_hndl->waitfor('/211\s/');
$t_hndl->print('post');

$t_hndl->waitfor('/340 Ok/i');

# If msgid is absent server 'll generate. Thus the block getting that id is commented out.
#($prematch, $match) = $t_hndl->waitfor('/340 Ok, recommended ID <\S+>/');
#chomp($match);
#chomp($match);
#if ( $match =~ m'340 Ok, recommended ID (<\S+>)$' ) {$msgidstr=$1;}
# else {die ("can't extract recommended msgid.")}

if ($guesheaders) {
	undef $Newsgroups;
	undef $From;
	undef $Subject;
	undef $XCommentTo;
	undef $ContentType;
	undef $XFlags;
	undef $CntTrnsfEnc;

    $count=0;
	while($count<=$headerstrings) {
	 $_=<STDIN>;
     chomp; chomp;
	 $preread[$count]=$_;
	 $count++;
	}
	$num=0;
    for ( $count=0 ; $count <= $headerstrings ; $count++ ) {
		$_=$preread[$count];
		$varfound=0;
		if ( /^Newsgroups:/	) {
			$Newsgroups = $preread[$count];
			$varfound=1;
		 	next;
		}
		if ( /^From:/) 			{ $From = $preread[$count];			$varfound=1; next;}
		if ( /^Subject:/) 		{ $Subject = $preread[$count];		$varfound=1; next;}
		if ( /^X-Comment-To:/) 	{ $XCommentTo = $preread[$count];	$varfound=1; next;}
		if ( /^Content-Type:/)  { $ContentType = $preread[$count];	$varfound=1; next;}
		if ( /^X-Flags:/)		{ $XFlags = $preread[$count];		$varfound=1; next;}
		if ( /^Content-Transfer-Encoding:/)  {
			$CntTrnsfEnc = $preread[$count];
			$varfound=1;
			next;
		}

		if ( !$varfound ) { $reprint[$num]=$preread[$count]; $num++;}
    }

}

if ( ! defined ( $Newsgroups ) ) {
	$t_hndl->print("Newsgroups: $newsgroup");
} else {$t_hndl->print("$Newsgroups")}

if ( ! defined ( $From ) ) {
	$t_hndl->print("From: $from");
} else {$t_hndl->print("$From")}

if ( ! defined ( $XCommentTo ) ) {
	$t_hndl->print("X-Comment-To: $postto");
} else {$t_hndl->print("$XCommentTo")}

if ( ! defined ( $XFlags ) ) {
	$t_hndl->print("X-Flags: $DefXFlags");
} else {$t_hndl->print("$XFlags")}

if ( ! defined ( $Subject ) ) {
	$t_hndl->print("Subject: $defaultsubj");
} else {$t_hndl->print("$Subject")}

# If absent server 'll generate - see above comments.
#$t_hndl->print("Message-ID: $msgidstr");

if ( ! defined ( $ContentType ) ) {
	$t_hndl->print("Content-Type: $DefCntntType");
} else {$t_hndl->print("$ContentType")}

if ( ! defined ( $CntTrnsfEnc ) ) {
	$t_hndl->print("Content-Transfer-Encoding: $DefEncoding");
} else {$t_hndl->print("$CntTrnsfEnc")}

$t_hndl->print("\r\n");

# reprint those strings that were preread and didn't contain known Keywords(Kludges)
if ( $num > 0 ) {
	for ($count=0 ; $count < $num ; $count++ ) {
		$t_hndl->print("$reprint[$count]");
	}
}

while(<STDIN>) {
	chomp; chomp;
	$t_hndl->print("$_");
}

$t_hndl->print("\r\n");
$t_hndl->print(".\r\n");
$t_hndl->waitfor('/240 Article posted/i');

if ( $debug) {
	print "Posted!\n";
}

$t_hndl->close;

