home *** CD-ROM | disk | FTP | other *** search
- ############################################################################
- #
- # File: post.icn
- #
- # Subject: Program to post news
- #
- # Author: Ronald Florence
- #
- # Date: 2 October 1991
- #
- ###########################################################################
- #
- # 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.
- #
- ############################################################################
- #
- # Link: options
- #
- ############################################################################
- #
- # Bugs: Newsgroup validation assumes the `active' file is sorted.
- # Non-UNIX sites need hardcoded system information.
- #
- ############################################################################
-
- 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
-