home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.cs.arizona.edu
/
ftp.cs.arizona.edu.tar
/
ftp.cs.arizona.edu
/
icon
/
historic
/
v941.tgz
/
icon.v941src.tar
/
icon.v941src
/
ipl
/
progs
/
post.icn
< prev
next >
Wrap
Text File
|
2000-07-29
|
12KB
|
367 lines
############################################################################
#
# File: post.icn
#
# Subject: Program to post news
#
# Author: Ronald Florence
#
# Date: August 14, 1996
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Version: 1.5
#
############################################################################
#
# This program posts a news article to Usenet. Given an optional
# argument of the name of a file containing a news article, or an
# argument of "-" and a news article via stdin, post creates a
# follow-up article, with an attribution and quoted text. The
# newsgroups, subject, distribution, follow-up, and quote-prefix can
# optionally be specified on the command line.
#
# usage: post [options] [article | -]
# -n newsgroups
# -s subject
# -d distribution
# -f followup-to
# -p quote-prefix (default ` > ')
#
# See the site & system configuration options below. On systems
# posting via inews, post validates newsgroups and distributions in
# the `active' and `distributions' files in the news library directory.
#
############################################################################
#
# Bugs: Newsgroup validation assumes the `active' file is sorted.
# Non-UNIX sites need hardcoded system information.
#
############################################################################
#
# Links: options
#
############################################################################
link options
global mode, sysname, domain, tz, tmpfile, opts, console, newslib, org
procedure main(arg)
local usage, smarthost, editor, default_distribution, generic_from
local tmpdir, logname, fullname, sigfile, article, inf, edstr, outf, tmp2
usage := ["usage: post [options] [article]",
"\t-n newsgroups",
"\t-s subject",
"\t-d distribution",
"\t-f followup-to",
"\t-p quote-prefix (default ` > ')",
"\t- read article from stdin"]
# Site configuration. Mode can be
# "local" (post via inews),
# "uux" (post via rnews to an upstream host),
# "mail" (post via mail to an upstream host).
# For either uux or mail mode,
# smarthost := the uucp nodename of the upstream news feed.
# Use generic_from to force a generic address instead
# of the hostname provided by system commands.
mode := "local"
smarthost := ""
editor := "vi"
domain := ".UUCP"
default_distribution := "world"
generic_from := &null
# For UNIX, the rest of the configuration is automatic.
if find("UNIX", &features) then {
console := "/dev/tty"
newslib := "/usr/lib/news/"
tz := "unix"
tmpdir := "/tmp/"
logname := pipe("logname")
sysname := trim(pipe("hostname", "uname -n", "uuname -l"))
# BSD passwd: `:fullname[,...]:'
# SysV passwd: `-fullname('
\logname & every lookup("/etc/passwd") ? {
=(logname) & {
every tab(upto(':')+1) \4
fullname := (tab(upto('-')+1), tab(upto('(:'))) | tab(upto(',:'))
break
}
}
sigfile := getenv("HOME") || "/.signature"
}
# For non-UNIX systems, we need hard coded configuration:
# console := the system's name for the user's terminal.
# libdir := the directory for news configuration files, like
# an `organization' file.
# tmpdir := optional directory for temporary files; terminated
# with the appropriate path separator: `/' or `\\'.
# logname := user's login name.
# tz := local time zone (e.g., EST).
# fullname := user's full name.
# sigfile := full path of file with user's email signature.
else {
console := "CON"
newslib := ""
tmpdir := ""
logname := &null
tz := &null
fullname := &null
sigfile := &null
sysname := getenv("HOST") | &host
}
# End of user configuration.
(\logname & \sysname & \tz & (mode == "local" | *smarthost > 0)) |
stop("post: missing system information")
opts := options(arg, "n:s:d:f:p:h?")
\opts["h"] | \opts["?"] | arg[1] == "?" & {
every write(!usage)
exit(-1)
}
org := getenv("ORGANIZATION") | lookup(newslib || "organization")
article := open(tmpfile := tempname(tmpdir), "w") |
stop("post: cannot write temp file")
write(article, "Path: ", sysname, "!", logname)
writes(article, "From: ", logname, "@", \generic_from | sysname, domain)
\fullname & writes(article, " (", fullname, ")")
write(article)
# For a follow-up article, reply_headers() does the work.
if \arg[1] then {
inf := (arg[1] == "-" & &input) |
open(arg[1]) | (remove(tmpfile) & stop("post: cannot read " || arg[1]))
reply_headers(inf, article)
every write(article, \opts["p"] | " > ", !inf)
close(inf)
}
# Query if newsgroups, subject, and distribution have
# not been specified on the command line.
else {
write(article, "Newsgroups: ",
validate(\opts["n"] | query("Newsgroups: "), "active"))
write(article, "Subject: ", \opts["s"] | query("Subject: "))
write(article, "Distribution: ",
validate(\opts["d"] | query("Distribution: ", default_distribution),
"distributions"))
every write(article, req_headers())
write(article, "\n")
}
close(article)
edstr := (getenv("EDITOR") | editor) || " " || tmpfile || " < " || console
system(edstr)
upto('nN', query("Are you sure you want to post this to Usenet y/n? ")) & {
if upto('yY', query("Save your draft article y/n? ")) then
stop("Your article is saved in ", tmpfile)
else {
remove(tmpfile)
stop("Posting aborted.")
}
}
# For inews, we supply the headers, inews supplies the .signature.
if mode == "local" then mode := newslib || "inews -h"
else {
\sigfile & {
article := open(tmpfile, "a")
write(article, "--")
every write(article, lookup(sigfile))
}
# To post via sendnews (mail), we prefix lines with 'N'.
# For rnews, don't force an immediate poll.
case mode of {
"mail": {
mode ||:= " " || smarthost || "!rnews"
outf := open(tmp2 := tempname(tmpdir), "w")
every write(outf, "N", lookup(tmpfile))
remove(tmpfile)
rename(tmp2, tmpfile)
}
"uux": mode ||:= " - -r " || smarthost || "!rnews"
}
}
mode ||:= " < " || tmpfile
(system(mode) = 0) & write("Article posted!")
remove(tmpfile)
end
# To parse the original article, we use case-insensitive
# matches on the headers. The Reply-to and Followup-To
# headers usually appear later than From and Newsgroups, so
# they take precedence. By usenet convention, we query
# the user if Followup-To on the original is `poster'.
procedure reply_headers(infile, art)
local fullname, address, quoter, date, id, subject, distribution
local group, refs
every !infile ? {
tab(match("from: " | "reply-to: ", map(&subject))) & {
if find("<") then {
fullname := (trim(tab(upto('<'))) ~== "")
address := (move(1), tab(find(">")))
}
else {
address := trim(tab(upto('(') | 0))
fullname := (move(1), tab(find(")")))
}
quoter := (\fullname | address)
}
tab(match("date: ", map(&subject))) & date := tab(0)
tab(match("message-id: ", map(&subject))) & id := tab(0)
tab(match("subject: ", map(&subject))) & subject := tab(0)
tab(match("distribution: ", map(&subject))) & distribution := tab(0)
tab(match("newsgroups: " | "followup-to: ", map(&subject))) &
group := tab(0)
tab(match("references: ", map(&subject))) & refs := tab(0)
(\quoter & *&subject = 0) & {
find("poster", group) & {
write(quoter, " has requested followups by email.")
upto('yY', query("Do you want to abort this posting y/n? ")) & {
remove(tmpfile)
stop("Posting aborted.")
}
group := &null
}
write(art, "Newsgroups: ", \group |
validate(\opts["n"] | query("Newsgroups: "), "active"))
write(art, "Subject: ", \opts["s"] | \subject | query("Subject: "))
\distribution | distribution := validate(\opts["d"], "distributions") &
write(art, "Distribution: ", distribution)
write(art, "References: ", (\refs ||:= " ") | "", id)
every write(art, req_headers())
write(art, "In-reply-to: ", quoter, "'s message of ", date)
write(art, "\nIn ", id, ", ", quoter, " writes:\n")
return
}
}
end
# We need a unique message-id, and a date in RFC822 format.
# Easy with UNIX systems that support `date -u'; with the
# others, we leave the local timezone. The first inews site
# will correct it.
procedure req_headers()
local uniq, date, month, day, time, zone, year
uniq := "<"
&date || &clock ? while tab(upto(&digits)) do uniq ||:= tab(many(&digits))
uniq ||:= "@" || sysname || domain || ">"
if tz == "unix" then {
date := pipe("date -u", "date")
date ? {
month := (tab(find(" ") + 1), tab(many(&letters)))
day := (tab(upto(&digits)), tab(many(&digits)))
time := (tab(upto(&digits++':')), tab(many(&digits++':')))
zone := (tab(upto(&ucase)), tab(many(&ucase)))
year := (tab(upto(&digits)+ 2), tab(0))
}
date := day || " " || month || " " || year || " " || time || " " || zone
}
else {
&dateline ? {
month := left((tab(find(" ")+1), tab(many(&letters))), 3) || " "
date := (tab(upto(&digits)), tab(many(&digits))) || " " || month
date ||:= (tab(upto(&digits)), right(tab(many(&digits)), 2))
}
date ||:= " " || &clock || " " || tz
}
mode ~== "local" & suspend "Message-ID: " || uniq
suspend "Date: " || date
\org & suspend "Organization: " || org
\opts["f"] & return "Followup-To: " || ((opts["f"] == "poster") |
validate(opts["f"], "active"))
end
# Richard Goerwitz's generator.
procedure tempname(dir)
local temp_name
every temp_name := dir || "article." || right(1 to 999,3,"0") do {
close(open(temp_name)) & next
suspend \temp_name
}
end
# On systems with pipes, pipe() will read from the first
# successful command of the list given as arguments.
procedure pipe(cmd[])
local inf, got
initial find("pipes" | "compiled", &features) | stop("No pipes.")
while inf := open("(" || pop(cmd) || ") 2>&1", "pr") do {
got := []
every put(got, !inf)
close(inf) = 0 & {
suspend !got
break
}
}
end
# The dirty work of reading from a file.
procedure lookup(what)
local inf
inf := open(what, "r") | fail
suspend !inf
close(inf)
end
# Query opens stdin because the system call to the editor
# redirects input. The optional parameter is a default
# response if the user answers with <return>.
procedure query(prompt, def)
local ans
static stdin
initial stdin := open(console)
writes(prompt)
ans := read(stdin)
return (*ans = 0 & \def) | ans
end
# A quick and dirty kludge. Validate() builds a sorted list.
# When an element is found, it is popped and the search moves
# to the next item. The procedure assumes the file is also
# sorted.
procedure validate(what, where)
local valid, stuff, sf, a
mode ~== "local" & return what
valid := &letters ++ '.-' ++ &digits
stuff := []
what ? while tab(upto(valid)) do put(stuff,tab(many(valid)))
sf := open(newslib || where) | {
remove(tmpfile)
stop("post: cannot open ", newslib || where)
}
stuff := sort(stuff)
a := pop(stuff)
every !sf ? match(a) & (a := pop(stuff)) | return what
remove(tmpfile)
stop("`", a, "' is not in ", newslib || where)
end