[comp.sources.misc] v07i117: oracle -- anonymous question/answer program

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (08/06/89)

Posting-number: Volume 7, Issue 117
Submitted-by: huttar@svax.cs.cornell.edu (Lars Huttar)
Archive-name: oracle

[Two potential problems.  First, it uses loooooong filenames and will
therefore have to be rewritten for System V.  Second, there is at least one
place where a system() command is made with "2>&-", ostensibly to send errors
to /dev/null -- in reality, this CLOSES standard error and can cause programs
using stdio to dump core, so be warned.  ++bsa]

Oracle is a fun sort of 'message base' program.
The idea is, you run oracle and ask it a question, on any subject.
Could be advice on how to make some decision, or the meaning of some word,
or anything.  Later, when someone else runs oracle, they will get your
question and answer it.  Oracle will mail the answer back to you, but
you don't know who it's from.  So it's really just an anonymous game of
questions and answers.

The Makefile tells how to build the program.  Notes tells a little about
the operation of the program, but you don't need to read it to run oracle.
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	oracle.man
#	Notes
#	oracle.c
#	Makefile
# This archive created: Thu Aug  3 08:21:44 1989
export PATH; PATH=/bin:$PATH
if test -f 'oracle.man'
then
	echo shar: will not over-write existing file "'oracle.man'"
else
sed 's/^X//' << \SHAR_EOF > 'oracle.man'
X.\" @(#)oracle.6 1.0 7/29/89 Lars Huttar
X.\" <huttar@occs.oberlin.edu>
X.TH ORACLE 6 "29 July 1989"
X.SH NAME
Xoracle \- answer difficult questions
X.SH SYNOPSIS
X.B oracle
X.SH DESCRIPTION
X.I Oracle
Xis a program that accept questions and gives answers; if you want
Xto know how to use it, just run it.  Its operation is pretty self-evident.
XBut if you want to spoil the secret of how it works, read on.
X.PP
XEvery time someone types in a question,
X.I oracle
Xsaves it in a file in the
X.I oracle
Xdirectory.  After someone has typed in a question,
X.I oracle
Xlooks in this directory for questions from other users.
XIt takes the oldest one in there and asks the user to answer it.
XThen it mails this answer, along with a copy of the question, to
Xthe person who first asked the question.
X
X.SH BUGS
XIt's not much fun if you're the only one playing.  If you see any other
Xbugs, please send them or any comments to:
X.in +2
X.nf
XLars Huttar
X<huttar@occs.oberlin.edu>
X<slh6559@oberlin.bitnet>
X
SHAR_EOF
fi # end of overwriting check
if test -f 'Notes'
then
	echo shar: will not over-write existing file "'Notes'"
else
sed 's/^X//' << \SHAR_EOF > 'Notes'
XLib directory:
XORACLEDIR (defined in Makefile) is a
Xquestion directory with one file per question.
XFilename is q_username_time_hostname_processid for each file.
XOne file is called "record" and contains the directory listing.
XThe command system("ls -rt q_* > record") fills this file.
XAnother file is "lockfile", whose existence means an oracle process is
Xusing the directory to the exclusion of others.
X
XProcess when somebody runs *oracle*:
X	A. Say "Please type in your question."  Save question in a file in
X		question directory, with x_ prefix.
X	B. Lock questions directory.
X		Update "record" file.
X		Get least recent question file -- that is the first file listed
X		in "record" -- which did not come from the current user.
X		Move that file to t_blahblahblah. (t for temp)
X		Unlock directory.
X	C. Print the question and get answer from user.
X		Save to file a_blahblahblah.  Mail to original
X		questioner -- system("mail user < a_blablabla").
X		Move the x_ file (created in A) to q_whatever.
X		Remove the t_ and a_ files -- unless the LOG flag is #defined
X		in which case the a_ files are left around.
X	D. Tell user the answer will reach them in a short while.  Ask user
X		to encourage friends to Ask The Oracle.
X
XPrefix summary:
X	q_ A question file that has been stored by a previous oracle, or
X	   is created from the current user's question just before current
X	   oracle terminates.
X
X	t_ A question file from a different user that has been selected
X	   to be shown to and answered by the current user.
X
X	x_ Temporary file to which the question of the current user is written
X	   as it is being typed.  Gets moved to q_ if everything goes smoothly.
X
X	a_ Answer file, exactly as the final answer message will be sent
X	   to the Other User whose question was selected.  So it contains
X	   introductory remarks, a copy of the Other User's question,
X	   and the answer given by the current user.  This answer is written
X	   line-by-line as the user types it in.
X
X
XPermissions:
X	All files will be readable and writable only by the owner.
XThe *oracle* program will have the setuid bit set, so that
Xusers will "become" the owner while the program is running.
XIn order to have mail messages appear to be from the owner of oracle,
Xthe real uid of the process is set to the effective uid just before
Xmailing occurs.
X
X
X
XAccessing the directory:
X	Attempt to cd there before anything starts.  If we fail, quit.
X
XLog:
X	If a certain flag LOG is #defined, q_ and a_ files will
Xbe left undeleted in the oracle directory.
X
X
XTo do:
X	After the question is entered, the oracle should have the person
Xconfirm, "Do you still want to send this question to the oracle?"
X	If DEBUG is defined, check upon startup (a) whether the file
Xargv[0] has the setuid bit set, and (b) whether it has worked (on some
Xfilesystems it may be ignored for security reasons).
X	Enter cbreak mode so that if the user tries ^C, the program can
Xexit gracefully, restoring any q_file it might have moved to t_, and
Xremoving any created files that are to be abandoned (including lockfile,
Xx_whatever, and a_whatever).
X	Allow a file to be specified as a command-line argument, that oracle
Xwill read the question from.
X
SHAR_EOF
fi # end of overwriting check
if test -f 'oracle.c'
then
	echo shar: will not over-write existing file "'oracle.c'"
else
sed 's/^X//' << \SHAR_EOF > 'oracle.c'
X/*
X * Copyright 1989 Lars Huttar
X *
X * Permission to use, copy, modify, and distribute this software and its
X * documentation for any purpose and without fee is hereby granted, provided
X * that the above copyright notice appear in all copies and that both that
X * copyright notice and this permission notice appear in supporting
X * documentation, and that the name of Lars Huttar not be used in advertising
X * or publicity pertaining to distribution of the software without specific,
X * written prior permission.  Lars Huttar makes no representations about the
X * suitability of this software for any purpose.  It is provided "as is"
X * without express or implied warranty.
X *
X * LARS HUTTAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
X * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
X * SHALL LARS HUTTAR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
X * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
X * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
X * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
X * THIS SOFTWARE.
X *
X * Author:  Lars Huttar, Oberlin College
X *          huttar@occs.oberlin.edu
X *          slh6559@oberlin.bitnet
X */
X
X#include <stdio.h>
X#include <sys/param.h>
X   /*  ^^ defines MAXHOSTNAMELEN */
X#include <sys/time.h>
X#include <errno.h>
X#include <pwd.h>
X
X#ifndef L_cuserid
X#define L_cuserid 50
X#endif
Xint errno;
X
X#define MAXPNLEN 50  /* That's being generous. */
Xstatic char progname[MAXPNLEN], loginname[L_cuserid];
X   /* L_cuserid is defined in stdio.h on some systems,
X      and is the max length of login names. */
Xint lognamlen;
X
X
Xusage()
X{  fprintf(stderr, "Usage: %s\n", progname);
X   exit(1);
X}
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{  char *ks, *getlogin();
X   struct passwd *kp;
X
X#ifdef DEBUG
X   kp=getpwuid(geteuid());
X   printf("euid: %d %s\n", geteuid(), kp->pw_name);
X#endif
X
X   if (chdir(ORACLEDIR))   /* Try to go to the directory. */
X      my_error("error in main(): Couldn't chdir to ", ORACLEDIR);
X
X   if (!(ks = getlogin()))
X   {  if (!(kp = getpwuid(getuid())))
X         my_error("error in main(): Couldn't get username.", "");
X      else strcpy(loginname, kp->pw_name);
X   }
X   else strcpy(loginname, ks);
X   lognamlen=strlen(loginname);
X
X   strncpy(progname, argv[0], MAXPNLEN-1);
X   progname[MAXPNLEN-1]=NULL;
X
X   if (argc>1) usage();
X
X   get_question();
X   get_answer();
X   update_records();
X   farewell();
X
X
X}
X
X
X#define MAXDATESIZE 30
X#define MAXFNSIZE (MAXHOSTNAMELEN+L_cuserid+MAXDATESIZE+6)
Xstatic char q_filename[MAXFNSIZE], a_filename[MAXFNSIZE];
X
X#define MAXLINELEN 141
X
X/** This function, get_question(), asks the user to type in a question.
X ** The question is written line by line to a temporary file x_blahblahblah.
X */
X
Xget_question()
X{  char line[MAXLINELEN];
X   FILE *outfp;
X   register int lines=0;
X
X   make_filename();
X   q_filename[0]='x';   /* This means it's a temporary question file. */
X
X   if (!(outfp=fopen(q_filename, "w")))
X      my_error("error in get_question(): Couldn't open file for writing: ",
X        q_filename);
X
X   puts("I am the oracle.  I can answer any question");
X   puts("in roughly constant time.  Please type in");
X   puts("your question.  Enter a blank line when");
X   puts("you are finished (don't use ^D!).\n");
X
X   do
X   {  putchar('>'); putchar(' ');
X      if (!fgets(line, MAXLINELEN-1, stdin) || line[0]=='\n') break;
X      lines=1;
X      line[MAXLINELEN-1]=0;
X      fputs(line, outfp);
X   } while (!feof(stdin));
X
X   clearerr(stdin);
X   fclose(outfp);
X
X   if (!lines)
X   {  puts("Ask a null question, get a null answer.");
X      if (unlink(q_filename))
X         my_error("error in get_question: Couldn't unlink ", q_filename);
X      exit(1);
X   }
X}
X
X
Xint got_answer=0;
X
Xget_answer()
X{  FILE *recfp, *quesfp, *ansfp;
X   char line[MAXLINELEN];
X   register int lines=0, num;
X
X   puts("\nHmmm.  I'll have to think about that one for a while.");
X
X  /** Look for a suitable question file. */
X
X   if (lock_directory()) return;
X      /* Couldn't lock the directory, so don't require an answer. */
X
X   system("ls -rt q_* > record 2>&-"); /* stderr goes to /dev/null */
X   if (!(recfp=fopen("record", "r")))  /* Open the file containing list of
X                                        * question files */
X      my_error("in get_answer(): fopen(\"record\", \"r\") gave error.", "");
X
X   do
X   {  
X      fgets(a_filename, MAXFNSIZE, recfp);
X      if (feof(recfp)) break;
X
X      if (strncmp(a_filename+2, loginname, lognamlen))
X         break;   /* found a question from a different user */
X   } while (!feof(recfp));
X
X   if (feof(recfp))  /* Didn't find a suitable question file. */
X   {  fclose(recfp);
X      unlock_directory();
X      return;
X   }
X   fclose(recfp);
X
X   a_filename[strlen(a_filename)-1]=(char)NULL; /* Take away final newline */
X
X   {  char nufilename[MAXFNSIZE];
X
X      strcpy(nufilename, a_filename);
X      nufilename[0]='t';   /* Rename it so other oracles will not answer it. */
X      if (rename(a_filename, nufilename))
X         my_error("Error in get_answer() renaming file ", a_filename);
X   }
X
X
X   unlock_directory();
X
X
X  /** Ask user to answer question. */
X
X   a_filename[0]='t';
X   if (!(quesfp=fopen(a_filename, "r")))
X      my_error("Error in get_answer() opening file for input: ", a_filename);
X
X   a_filename[0]='a';
X   if (!(ansfp=fopen(a_filename, "w")))
X      my_error("Error in get_answer() opening file for output: ", a_filename);
X
X   fputs("The oracle has pondered your question deeply.\n", ansfp);
X   fputs("Your question was:\n\n", ansfp);
X
X   puts("Meanwhile, I'd like you to answer a question for me:\n");
X
X   while (!feof(quesfp))
X   {  static int i=0;
X
X      fgets(line, MAXLINELEN, quesfp);
X      if (!feof(quesfp))
X      {  fputs("> ", stdout); fputs(line, stdout);
X         fputs("> ", ansfp); fputs(line, ansfp);
X      }
X      if (++i % 15 == 0) get_return("Press RETURN for More--");
X   }
X   fclose(quesfp);
X   puts("\nWhat would you say?\n");
X   fputs("\nAnd in response, thus spake the oracle:\n\n", ansfp);
X
X   do
X   {  putchar(')'); putchar(' ');
X      fgets(line, MAXLINELEN-1, stdin);
X      if (line[0]=='\n' && lines)
X         break;
X      lines++;
X      line[MAXLINELEN-1]=0;
X      fputs(lines%2 ? "} " : "{ ", ansfp); fputs(line, ansfp);
X   } while (!feof(stdin));
X
X   {  static char *objects[]=
X         {"cents", "of your children", "dollars", "big kisses",
X          "years of slavery", "minutes of life", "newt's eyes",
X          "quarts of soy sauce", "cases of root beer"};
X      
X      num=rand()+getpid()+getuid()+getppid();
X      fprintf(ansfp, "\n\nYou owe the oracle %d %s.\n",
X           num%4+2, objects[num%9]);
X   }
X
X   fclose(ansfp);
X
X   switch(num%5)
X   {  case 1: puts("\nHa!  What kind of answer is that?"); break;
X      case 2: puts("\nNow why couldn't I think of that?"); break;
X      case 3: puts("\nOk, good enough.  You pass."); break;
X      default: puts("\nThank you!  That one's been troubling me.");
X   }
X
X   got_answer=1;
X}
X
X
X#define WAITLIMIT 5
X
X/** This function, lock_directory, tries to lock the current directory
X ** by creating a file "lockfile",
X ** and returns 0 on success, 1 on failure. */
Xlock_directory()
X{  register int lockfd, i=0;
X
X   for (i=0; i<WAITLIMIT; i++)
X
X   {  if ((lockfd=creat("lockfile", 0)) == -1)
X      {  if (errno != EACCES)
X            my_error("Error in lock_directory(): creat(\"lockfile\");", "");
X      }
X      else
X      {  if (close(lockfd) == -1)
X            my_error("Error in lock_directory(): closing lockfile","");
X         else return(0);
X      }
X
X#ifdef DEBUG
X      if (i==0) printf("Waiting for lock... 1");
X      else printf(", %d", i+1);
X      fflush(stdout);
X#endif
X      sleep(1);   /* Is this too long?  usleep() may be more appropriate. */
X   }
X
X   return(1);
X}
X
X
X/** This function unlocks the current directory. */
Xunlock_directory()
X{
X#ifdef DEBUG
X   get_return("Press RETURN to unlock the directory.");
X#endif
X
X   if (unlink("lockfile") == -1)
X      my_error("Error in unlink() while trying to unlock directory.", "");
X}
X
X
Xupdate_records()
X{  char command[L_cuserid+MAXFNSIZE+10], olduser[L_cuserid], newqfn[MAXFNSIZE];
X   register int i;
X
X   strcpy(newqfn, q_filename);
X   newqfn[0]='q';
X   if (rename(q_filename, newqfn))
X      my_error("update_records(): Error renaming file ", newqfn);
X
X   if (!got_answer) return;
X   
X   for (i=2; a_filename[i]!='_'; i++)
X      olduser[i-2]=a_filename[i];
X   olduser[i-2]=0;
X
X   if (setreuid(geteuid(), -1))  /* Make message be From the oracle owner. */
X      my_error("Error in update_records(), setreuid();", "");
X   sprintf(command, "mail %s < %s", olduser, a_filename);
X   if (i=system(command))
X   {  static char exit_code[5];
X      sprintf(exit_code, "%d", i);
X      my_error("Trouble mailing reply.  Exit code: ", exit_code);
X   }
X
X
X#ifndef LOG
X   if (unlink(a_filename))    /* Remove the answer file (which includes
X             a copy of the question)*/
X      my_error("update_records(): Error unlinking ", a_filename);
X#endif
X   a_filename[0]='t';
X   if (unlink(a_filename))    /* Remove the old question file */
X      my_error("update_records(): Error unlinking ", a_filename);
X
X}
X
X
X/** The function make_filename constructs a string of the form
X ** q_user_date_host_processid and writes it the global string "q_filename". */
Xmake_filename()
X{  char hostname[MAXHOSTNAMELEN], date[MAXDATESIZE];
X   long time; /* no see. */
X   
X   int pid;
X   struct timeval tv;
X   struct timezone tz;
X
X   if (gethostname(hostname, MAXHOSTNAMELEN)) 
X      my_error("make_filename(): Had trouble getting hostname;", "");
X   
X
X   pid=getpid();  /* Get the process id. */
X   
X   time=gettimeofday(&tv, &tz);  /* Get the number of seconds since
X                                    that unforgettable New Year's party */
X   if (time == -1) tv.tv_sec=rand();   /* Sure this isn't error-proof... */
X   strncpy(date, ctime(&(tv.tv_sec))+4, 20); /* +4 to avoid day of the week. */
X   date[20]=NULL; space_to__(date); /* Change spaces to _. */
X
X   sprintf(q_filename, "q_%s_%s_%s_%d", loginname, date, hostname, pid);
X      /* Just trying to make a unique filename. */
X}
X
Xspace_to__(string)
Xchar *string;
X{  register char *p;
X
X   for (p=string; *p; p++)
X      if (*p==' ') *p='_';
X}
X
X
X
X/** The following function says goodbye and advertises
X ** the program. */
Xfarewell()
X{
X   puts("\nYour answer (and your bill) will be mailed to you shortly.");
X   puts("Tell all your friends to Ask the Oracle!");
X}
X
X
Xmy_error(mess1, mess2)
Xchar *mess1, *mess2;
X{  printf("%s%s\n", mess1, mess2);
X
X#ifdef DEBUG
X   printf("errno: %d\n", errno);
X#endif
X
X/* printf("EACCES %d  EBADF %d  EAGAIN %d  EINTR %d  ENOLCK %d\n",/**deleteme*/
X/*    EACCES, EBADF, EAGAIN, EINTR, ENOLCK);/**deleteme*/
X   perror(progname);
X   exit(1);
X}
X
X
Xget_return(prompt)
Xchar *prompt;
X{  fputs(prompt, stdout);
X   while (getchar()!='\n');
X}
X
SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X# Makefile for oracle
X
X# To make this program, first set TARGETDIR to something appropriate.
X# You may want to change LIBDIR and CFLAGS as well.
X# Then 'make all'.
X
XTARGETDIR = /mu/huttar/oracle
XLIBDIR = ${TARGETDIR}/oraclelib
XCFLAGS = -DLOG -DORACLEDIR=\"${LIBDIR}\"
X# -DDEBUG and -DLOG are optional.
X# -DDEBUG causes extra diagnostics to be run, and
X# -DLOG causes old files to be left around.
X
X
Xall: ${TARGETDIR}/oracle ${LIBDIR}
X
X
X${TARGETDIR}/oracle : oracle.c
X	cc -o ${TARGETDIR}/oracle ${CFLAGS} oracle.c
X	chmod u+s ${TARGETDIR}/oracle
X
X${LIBDIR} :
X	mkdir ${LIBDIR}
X
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
Lars Huttar ---------------------------- rhymes exactly with "sparse butter"
huttar@svax.cs.cornell.edu ---------- The opinions and decisions are my own.
After Aug 15: huttar@occs.oberlin.edu or slh6559@oberlin.bitnet -- vi rules!
You can talk the talk, but can you walk the dog?