Article 3414 of comp.lang.perl: Xref: feenix.metronet.com alt.sources:1421 comp.lang.perl:3414 news.software.readers:1868 Path: feenix.metronet.com!news.ecn.bgu.edu!psuvax1!uwm.edu!cs.utexas.edu!usc!elroy.jpl.nasa.gov!ames!skates.gsfc.nasa.gov!usenet From: pat@jaameri.gsfc.nasa.gov (patrick m. ryan) Newsgroups: alt.sources,comp.lang.perl,news.software.readers Subject: pInews (beta), a Perl NNTP posting program Date: 14 Jun 1993 11:29:26 EDT Organization: Oceans & Ice Branch, Code 971, NASA/GSFC/Hughes STX Lines: 734 Message-ID: <1vi5jt$1ne@skates.gsfc.nasa.gov> Reply-To: pat@jaameri.gsfc.nasa.gov (patrick m. ryan) NNTP-Posting-Host: jaameri.gsfc.nasa.gov Mime-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit X-Posting-Version: pInews [v. 0.2] Included below is the beta version of my Perl NNTP posting program, pInews. Please let me know if it works for you. #!/bin/sh # This is a shell archive (produced by shar 3.49) # To extract the files from this archive, save it to a file, remove # everything above the "!/bin/sh" line above, and type "sh file_name". # # made 06/14/1993 15:28 UTC by pat@jaameri # Source directory /home/pat/perl # # existing files will NOT be overwritten unless -c is specified # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 6811 -rwxr-xr-x pInews # 2096 -rw-r--r-- README.pInews # 8535 -r--r--r-- date.pl # # ============= pInews ============== if test -f 'pInews' -a X"$1" != X"-c"; then echo 'x - skipping pInews (File already exists)' else echo 'x - extracting pInews (Text)' sed 's/^X//' << 'SHAR_EOF' > 'pInews' && #!/usr/local/bin/perl -- -*- Perl -*- # # $Header: /home/pat/perl/RCS/pInews,v 0.2 1993/06/14 14:41:31 pat Exp pat $ # # pInews - Perl Inews (or maybe Pat's Inews) # a simple NNTP news posting client # # references: # RFC 850 # RFC 977 # # # # server connection code taken from pgnews written by # Jeffrey B. McGough mcgough@wrdis01.af.mil # # # bug reports, fixes, fan mail, cash donations to: # pat@jaameri.gsfc.nasa.gov (patrick m. ryan) # X require 'getopts.pl'; require 'date.pl'; X &Getopts('h:vd'); X $rcsid = q!$Id: pInews,v 0.2 1993/06/14 14:41:31 pat Exp pat $!; $v = (split(/\s+/,$rcsid))[2]; $version = "pInews [v. $v]"; X # host and domain *must* be set!! chop($host = `hostname`); if (!$host) { die "could not determine hostname\n"; } X $domain = "gsfc.nasa.gov"; if (!$domain) { die "no domain set\n"; } X $fullname = (getpwuid($<))[6]; if ($fullname =~ /,/) # strip out the extra gcos stuff { X @f = split(/,/,$fullname); X $fullname=$f[0]; } X # get user name $user = getlogin() || (getpwuid($<))[0] || X $ENV{USER} || $ENV{LOGNAME} || X die "who are you?\n"; # get user's home directory $home = $ENV{HOME} || (getpwuid($<))[7] || X die "you are homeless!\n"; X $pIrc = $home . "/.pIrc"; X $port = 119; # for NNTP $nntpserver = $opt_h || $ENV{NNTPSERVER}; if (!$nntpserver && -f '/etc/nntpserver') { X chop ($nntpserver = `cat /etc/nntpserver`); } if (!$nntpserver) { $nntpserver='localhost'; } # last resort X # Pack format... $sockaddr = 'S n a4 x8'; X $DOMAIN = 2; $STYLE = 1; X $rin = $rout = ''; X ($name, $aliases, $proto) = getprotobyname('tcp'); ($name, $aliases, $type, $len, $hostaddr) = gethostbyname($nntpserver); X $sock = pack($sockaddr, $DOMAIN, $port, $hostaddr); X $SIG{'ALRM'} = 'handler'; alarm(60); X print "connecting to $nntpserver..."; socket(S, $DOMAIN, $STYLE, $proto) || die $!; connect(S, $sock) || die $!; select(S); $| = 1; select(STDOUT); alarm(0); print "\n"; X # set up for select vec($rin, fileno(S), 1) = 1; # this select will block until the server gives us something. $nfound = select($rout=$rin, undef, undef, 900); if ($nfound == 0) { X print "Socket timed out..."; X exit 1; } $_ = ; # read one line to see if we got a good connection. if ($opt_d) { print $_; } if ($_ !~ /^200/) { X print; X print S 'quit\n'; X print $_,"\n"; X die "Service unavailable"; } X $tmpdir = $ENV{TMPDIR} || '/tmp'; $tmp = $tmpdir . '/.pinews.'.$$; $editor = $ENV{VISUAL} || $ENV{EDITOR} || "vi"; $dead = $home."/dead.article"; X # list of required headers from RFC850 %header = ( X 'From','', X 'Date','', ## 'Relay-Version','', ## 'X-Newsreader','', X 'X-Posting-Version',$version, X 'Newsgroups','', X 'Subject','', X 'Message-ID','', X 'Path','' X ); X ###$header{'NNTP-Posting-Host'} = $host; $header{From} = "$user@$host.$domain"; if ($fullname) { $header{From} .= " ($fullname)"; } X # grab user's personal headers, if any %myheaders=(); if ( -f $pIrc ) { X (%myheaders) = &split_headers($pIrc); X if ($opt_d) X { X print "user headers:\n"; X while (($key,$value) = each %myheaders) X { X printf "%3d\t%s: %s",$i++,$key,$value; X } X } } X # now ask the user for a few headers @ask_headers = ('Newsgroups','Subject'); X foreach (@ask_headers) { X print "$_: "; X $r=""; X until ($r) X { X $r = ; X chop $r; X } X $header{$_} = $r; } X $header{Newsgroups} =~ s/\s//g; ##$header{"Message-ID"} = "<$$.$<@$host>"; ##$fmt = "%a, %d-%b-%Y %T %Z"; $fmt = "%d %h %Y %T %Z"; $header{Date} = &date(time(),$fmt); X if ($opt_d) { X print "\nheaders:\n"; X while (($key,$value) = each %header) X { X print "\t$key: $value\n"; X } } X # construct header X @req_headers = (From,Newsgroups,Subject,Date,'X-Posting-Version'); $head = ''; foreach (@req_headers) { X if ($header{$_}) X { X $head .= "$_: $header{$_}\n"; X } X # should probably be an error of one of these is missing } X # send user header lines, if any if (%myheaders) { X while (($key,$value) = each %myheaders) X { X unless (defined($header{$key})) # don't overwrite reserved headers X { X $head .= "$key: $value"; X } X } } X # touch the file so that it is not world readable open(TMP,">$tmp"); chmod(0600,$tmp); close TMP; X # invoke the editor for create the article X $cmd = "$editor $tmp"; if ($opt_d) { print $cmd,"\n"; } system $cmd; if ($?>>8) # error from system() { X print STDERR "error executing \"$cmd\"\n"; X if ( -s $tmp ) X { X &save_article($head,$tmp,$dead); X } X unlink $tmp; X print S "quit\n"; X exit 1; } X X print "post article? "; chop ($r = ); X if ($r !~ /^\s*y/i) { X print "ok. not posting\n"; X &save_article($head,$tmp,$dead); X unlink $tmp; X print S 'quit\n'; X exit 0; } X # now try to send the article X print "posting article...\n"; X print S "post\n"; $_ = ; if ($opt_d) { print $_; } # check reply value X X if ($opt_d) { X print "sending headers:\n"; X print $head; } X print S $head; print S "\n"; # blank line after header X open(TMP,"<$tmp") || die; while () { X if ($_ eq ".\n") X { print S "..\n"; } # this looks like an EOT marker X else X { print S $_; } } print S ".\n"; close TMP; $_ = ; ### if ($opt_d) { print $_; } if ($_ !~ /^240/) { X print STDERR $_; X &save_article($head,$tmp,$dead); } else { X print "article posted\n"; } X print S "quit\n"; unlink $tmp; X Xexit 0; X X sub handler { X local($sig) = @_; X print "Caught a SIG$sig--aborting\n"; X unlink $tmp; X exit(0); } X sub split_headers { X # generates an associative array containing all of the header # information from a mail message. # # bugs: # doesn't handle mutiple instances of the same field. # right now, it just concatenates them. # usually, this doesn't matter. X X local($file)=@_; X X # swallow the entire header file. yum, yum... X open(HEADER,"<$file"); X local(@lines)=
; X close(HEADER); X X local(%headers,$tmp); X X %headers=(); X while (@lines) X { X $_ = shift(@lines); X X if (/^\s*\n$/o) { last; } # this is an empty line X X # split header line as "field: value" ## ($field,$value) = /^([^:]+):\s*(.*\n)/o ; X ($field,$value) = split(/\s*:\s*/,$_,2); X X if (( !$field ) || (!$value)) { next; } # unrecognized header X X $tmp=''; # need to change field to all same case? # append multiply defined headers X $headers{$field} .= $value; X X # append any continuation lines X while ($lines[0] =~ /^\s+/o) X { X $headers{$field} .= shift(@lines); X } X } X X # X return (%headers); X } X sub save_article { X local($head,$tmp,$dead) = @_; X X $ok=1; X X open(DEAD,">$dead") || die "couldn't save article\n"; X print DEAD $head; X print DEAD "\n"; X X open(TMP,"<$tmp"); X while () X { X print DEAD $_; X } X close TMP; X close DEAD; X X print STDERR "saved article in $dead\n"; X return; } SHAR_EOF chmod 0755 pInews || echo 'restore of pInews failed' Wc_c="`wc -c < 'pInews'`" test 6811 -eq "$Wc_c" || echo 'pInews: original size 6811, current size' "$Wc_c" fi # ============= README.pInews ============== if test -f 'README.pInews' -a X"$1" != X"-c"; then echo 'x - skipping README.pInews (File already exists)' else echo 'x - extracting README.pInews (Text)' sed 's/^X//' << 'SHAR_EOF' > 'README.pInews' && X pInews - v.0.2 X X This is a beta release of pInews, my quick & dirty NNTP posting program written in Perl. I wrote it because I don't have a standalone posting program like Pnews and because I wanted to learn a little about networking. X X I'd appreciate it if someone who knows a lot about posting software could take a look at this program, specifically at the headers I set up for articles. I sat down with RFC850 and RFC977 and made my best guess as to which headers my program is required to send. X X *Please* let me know if this program causes anyone any problems. I'd rather not crash anyone's NNTP server. I strongly recommend that you try posting to your local *.test newsgroup before posting to the rest of the world. The program is pretty simple so it shouldn't be hard to track down any fatal errors. Bug reports and enhancement ideas are welcome. X X INSTALLATION X 1. Make sure you have 'date.pl' available somewhere. I've included it with X this distribution in case you don't have it. X 2. Edit the line which says "$domain = " and set it for your system. X 3. Put pInews /usr/local/bin or wherever you keep local software. X 4. Post away! X OPTIONS X -h hostname specify NNTP server machine X -d debug this program X NOTES X The identity of the NNTP server machine can be specified in several places. They are listed here in decreasing order of precedence: X X 1. command line - -h hostname X 2. environment - $NNTPSERVER X 3. file - /etc/nntpserver X 4. default - "localhost" X X X There is NO WARRANTY associated with this program. The author is not responsible for any loss which may occur as a result of using this program. X CREDITS X The server connection code taken from pgnews written by Jeffrey B. McGough mcgough@wrdis01.af.mil X X The address for bug reports is pat@jaameri.gsfc.nasa.gov. X -- "I have a cunning plan." -- Baldrick X patrick m. ryan X nasa / goddard space flight center / oceans and ice branch / hughes stx X pat@jaameri.gsfc.nasa.gov / patrick.m.ryan@x500.gsfc.nasa.gov X SHAR_EOF chmod 0644 README.pInews || echo 'restore of README.pInews failed' Wc_c="`wc -c < 'README.pInews'`" test 2096 -eq "$Wc_c" || echo 'README.pInews: original size 2096, current size' "$Wc_c" fi # ============= date.pl ============== if test -f 'date.pl' -a X"$1" != X"-c"; then echo 'x - skipping date.pl (File already exists)' else echo 'x - extracting date.pl (Text)' sed 's/^X//' << 'SHAR_EOF' > 'date.pl' && ;# ;# Name ;# date.pl - Perl emulation of (the output side of) date(1) ;# ;# Synopsis ;# requirelude "date.pl"; ;# $Date = &date(time); ;# $Date = &date(time, $format); ;# ;# Description ;# This package implements the output formatting functions of date(1) in ;# Perl. The format options are based on those supported by Ultrix 4.0 ;# plus a couple of additions: ;# ;# %a abbreviated weekday name - Sun to Sat ;# %A full weekday name - Sunday to Saturday ;# %b abbreviated month name - Jan to Dec ;# %B full month name - January to December ;# %c date and time in local format [+] ;# %d day of month - 01 to 31 ;# %D date as mm/dd/yy ;# %e day of month (space padded) - ` 1' to `31' ;# %h abbreviated month name - Jan to Dec ;# %H hour - 00 to 23 ;# %I hour - 01 to 12 ;# %j day of the year (Julian date) - 001 to 366 ;# %m month of year - 01 to 12 ;# %M minute - 00 to 59 ;# %n insert a newline character ;# %p AM or PM ;# %r time in AM/PM notation ;# %R time as HH:MM ;# %S second - 00 to 59 ;# %t insert a tab character ;# %T time as HH:MM:SS ;# %U week number, Sunday as first day of week - 00 to 53 ;# %w day of week - 0 (Sunday) to 6 ;# %W week number, Monday as first day of week - 00 to 53 ;# %x date in local format [+] ;# %X time in local format [+] ;# %y last 2 digits of year - 00 to 99 ;# %Y all 4 digits of year ~ 1700 to 2000 odd ? ;# %z time zone from TZ environment variable w/ a trailing space [*] ;# %Z time zone from TZ environment variable ;# %% insert a `%' character ;# %+ insert a `+' character [*] ;# ;# [*]: Not supported by date(1) but I wanted 'em. ;# [+]: These may need adjustment to fit local conventions, see below. ;# ;# For the sake of compatibility, a leading `+' in the format ;# specificaiton is removed if present. ;# ;# Remarks ;# An extension of `ctime.pl' by Waldemar Kebsch (kebsch.pad@nixpbe.UUCP), ;# as modified by Marion Hakanson (hakanson@ogicse.ogi.edu). ;# ;# Unlike date(1), unknown format tags are silently replaced by "". ;# ;# defaultTZ is a blatant hack, but I wanted to be able to get date(1) ;# like behaviour by default and there does'nt seem to be an easy (read ;# portable) way to get the local TZ name back... ;# ;# For a cheap date, try... ;# ;# #!/usr/local/bin/perl ;# require "date.pl"; ;# exit print (&date(time, shift @ARGV) . "\n") ? 0 : 1; ;# ;# This package is redistributable under the same terms as apply to ;# the Perl 3.0 release. See the COPYING file in your Perl kit for ;# more information. ;# ;# Please send any bug reports or comments to tmcgonigal@gvc.com ;# ;# Modification History ;# Nmemonic Version Date Who ;# ;# NONE none 02feb91 Terry McGonigal (tmcgonigal@gvc.com) ;# Created from ctime.pl ;# ;# NONE none 07feb91 tmcgonigal ;# Added some of Marion Hakanson (hakanson@ogicse.ogi.edu)'s ctime.pl ;# TZ handling changes. ;# ;# NONE none 09feb91 tmcgonigal ;# Corrected week number calculations. ;# ;# SccsId = "%W% %E%" ;# package date; X # Months of the year @MoY = ('January', 'Febuary', 'March', 'April', 'May', 'June', X 'July', 'August', 'September','October', 'November', 'December'); X # days of the week @DoW = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', X 'Thursday', 'Friday', 'Saturday'); X # defaults $defaultTZ = 'EST'; # time zone (hack!) $defaultFMT = '%a %h %e %T %z%Y'; # format (ala date(1)) X # `local' formats $locTF = '%T'; # time (as HH:MM:SS) $locDF = '%D'; # date (as mm/dd/yy) $locDTF = '%a %b %d %T %Y'; # date/time (as dow mon dd HH:MM:SS yyy) X # Time zone info $TZ; # wkno needs this info too X # define the known format tags as associative keys with their associated # replacement strings as values. Each replacement string should be # an eval-able expresion assigning a value to $rep. These expressions are # eval-ed, then the value of $rep is substituted into the supplied # format (if any). %Tags = ( '%a', '($rep = $DoW[$wday])=~ s/^(...).*/\1/',# abbr. weekday name - Sun to Sat X '%A', '$rep = $DoW[$wday]', # full weekday name - Sunday to Saturday X '%b', '($rep = $MoY[$mon]) =~ s/^(...).*/\1/',# abbr. month name - Jan to Dec X '%B', '$rep = $MoY[$mon]', # full month name - January to December X '%c', '$rep = $locDTF; 1', # date/time in local format X '%d', '$rep = &date\'pad($mday, 2, "0")', # day of month - 01 to 31 X '%D', '$rep = \'%m/%d/%y\'', # date as mm/dd/yy X '%e', '$rep = &date\'pad($mday, 2, " ")', # day of month (space padded) ` 1' to `31' X '%h', '$rep = \'%b\'', # abbr. month name (same as %b) X '%H', '$rep = &date\'pad($hour, 2, "0")', # hour - 00 to 23 X '%I', '$rep = &date\'ampmH($hour)', # hour - 01 to 12 X '%j', '$rep = &date\'pad($yday+1, 3, "0")', # Julian date 001 - 366 X '%m', '$rep = &date\'pad($mon+1, 2, "0")', # month of year - 01 to 12 X '%M', '$rep = &date\'pad($min, 2, "0")', # minute - 00 to 59 X '%n', '$rep = "\n"', # insert a newline X '%p', '$rep = &date\'ampmD($hour)', # insert `AM' or `PM' X '%r', '$rep = \'%I:%M:%S %p\'', # time in AM/PM notation X '%R', '$rep = \'%H:%M\'', # time as HH:MM X '%S', '$rep = &date\'pad($sec, 2, "0")', # second - 00 to 59 X '%t', '$rep = "\t"', # insert a tab X '%T', '$rep = \'%H:%M:%S\'', # time as HH:MM:SS X '%U', '$rep = &date\'wkno($yday, 0)', # week number (weeks start on Sun) - 00 to 53 X '%w', '$rep = $wday; 1', # day of week - Sunday = 0 X '%W', '$rep = &date\'wkno($yday, 1)', # week number (weeks start on Mon) - 00 to 53 X '%x', '$rep = $locDF; 1', # date in local format X '%X', '$rep = $locTF; 1', # time in local format X '%y', '($rep = "$year") =~ s/..(..)/\1/', # last 2 digits of year - 00 to 99 X '%Y', '$rep = "$year"', # full year ~ 1700 to 2000 odd X '%z', '$rep = $TZ eq "" ? "" : "$TZ "', # time zone from TZ env var (w/trail. space) X '%Z', '$rep = $TZ; 1', # time zone from TZ env. var. X '%%', '$rep = \'%\'; $adv=1', # insert a `%' X '%+', '$rep = \'+\'' # insert a `+' ); X sub main'date { X local($time, $format) = @_; X local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst); X local($pos, $tag, $rep, $adv) = (0, "", "", 0); X X X # default to date/ctime format or strip leading `+'... X if ($format eq "") { X $format = $defaultFMT; X } elsif ($format =~ /^\+/) { X $format = $'; X } X X # Use local time if can't find a TZ in the environment X $TZ = defined($ENV{'TZ'}) ? $ENV{'TZ'} : $defaultTZ; X ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = X &gettime ($TZ, $time); X X # Hack to deal with 'PST8PDT' format of TZ X # Note that this can't deal with all the esoteric forms, but it X # does recognize the most common: [:]STDoff[DST[off][,rule]] X if ($TZ =~ /^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2})([^\d+\-,]{3,})?/) { X $TZ = $isdst ? $4 : $1; X } X X # watch out in 2070... X $year += ($year < 70) ? 2000 : 1900; X X # now loop throught the supplied format looking for tags... X while (($pos = index ($format, '%')) != -1) { X X # grab the format tag X $tag = substr($format, $pos, 2); X $adv = 0; # for `%%' processing X X # do we have a replacement string? X if (defined $Tags{$tag}) { X X # trap dead evals... X if (! eval $Tags{$tag}) { X print STDERR "date.pl: internal error: eval for $tag failed.\n"; X return ""; X } X } else { X $rep = ""; X } X X # do the substitution X substr ($format, $pos, 2) =~ s/$tag/$rep/; X $pos++ if ($adv); X } X X $format; } X # weekno - figure out week number sub wkno { X local ($yday, $firstweekday) = @_; X local ($jan1, @jan1, $wks); X local ($now) = time; X X # figure out the `time' value for January 1 X $jan1 = $now - ((&gettime ($TZ, $now))[7] * 86400); # 86400 sec/day X X # figure out what day of the week January 1 was X @jan1= &gettime ($TZ, $jan1); X X # and calculate the week number X $wks = (($yday + ($jan1[6] - $firstweekday)) + 1)/ 7; X $wks += (($wks - int($wks) > 0.0) ? 1 : 0); X X # supply zero padding X &pad (int($wks), 2, "0"); } X # ampmH - figure out am/pm (1 - 12) mode hour value. sub ampmH { local ($h) = @_; &pad($h>12 ? $h-12 : $h, 2, "0"); } X # ampmD - figure out am/pm designator sub ampmD { shift @_ > 12 ? "PM" : "AM"; } X # gettime - get the time via {local,gmt}time sub gettime { ((shift @_) eq 'GMT') ? gmtime(shift @_) : localtime(shift @_); } X # pad - pad $in with leading $pad until lenght $len sub pad { X local ($in, $len, $pad) = @_; X local ($out) = "$in"; X X $out = $pad . $out until (length ($out) == $len); X return $out; } X 1; SHAR_EOF chmod 0444 date.pl || echo 'restore of date.pl failed' Wc_c="`wc -c < 'date.pl'`" test 8535 -eq "$Wc_c" || echo 'date.pl: original size 8535, current size' "$Wc_c" fi exit 0 -- "I have a cunning plan." -- Baldrick patrick m. ryan nasa / goddard space flight center / oceans and ice branch / hughes stx pat@jaameri.gsfc.nasa.gov / patrick.m.ryan@x500.gsfc.nasa.gov .