/*
 * W-NEWS       A simple NEWS processing package for MINIX.
 *              This program takes care of the 'user-interface' part
 *              of posting articles to a newsgroup.  It actually is
 *              a message composer (derived from the W-MAIL Message
 *              Editor), which hands the finished article to 'inews'
 *              for the actual posting.
 *
 * Usage:       pnews [-d] [ newsgroup ]
 *
 * Version:     @(#)pnews.c	4.43	28-Feb-1994
 *
 * Authors:	Fred N. van Kempen, <waltje@Infomagic.COM>
 *		Edvard Tuinder, <ed@ow.org>
 *		Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *		Copyright 1988-1993 MicroWalt Corporation
 *		Copyright 1994,1995 Cistron Software C.V.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */
#include "wnews.h"


typedef struct _distr_ {
  struct _distr_ *next;
  char           *dist;
  char           *name;
} DISTRIB;
#define NILDIST ((DISTRIB *)NULL)
#define DISTSIZ (sizeof(DISTRIB))


char *Release = VERSION;
char *Version = "@(#) pnews 4.43 (28-Feb-1994)";


char *program;
char artname[128];                      /* path name of article file    */
FILE *artfp;                            /* file pointer of article file */
char subject[128];                      /* subject of the article       */
char keywords[128];                     /* some useful keywords         */
char newsgroups[128];                   /* the addressed newsgroups     */
char distribution[128];                 /* article's distribution       */
char *dflt_newsg;                       /* Given as argument            */
char *e_edit;                           /* line editor                  */
char *v_edit;                           /* screen editor                */
char *shell;                            /* shell to use                 */
DISTRIB *ldistrib = NILDIST;            /* a list of possible distr's   */


_PRO( int groups_ok, (char *groups)					);
_PRO( void fetch_file, (char *name, int quote, off_t *nchar, int *nlin)	);
_PRO( void app_signature, (void)					);
_PRO( int do_run, (char *input, char *prg, char **args)			);
_PRO( void dead_art, (void)						);
_PRO( int do_tilde, (char *text)					);
_PRO( char *legal, (char *dist)						);
_PRO( int edit_art, (FILE *infp)					);
_PRO( void do_environ, (void)						);
_PRO( void do_dists, (void)						);
_PRO( void cleanup, (int)						);


/* Check if all groups of this line are valid. Change spaces into comma's. */
int
groups_ok(gr)
char *gr;
{
  char group[256];
  char *s, *p, *comma;
  int status = OK;

  if (*gr == 0 || *gr == '\n') {
	fprintf(stderr, "*** please give one or more newsgroups\n");
	return(ERROR);
  }

  /* Delete trailing newline. */
  for(s = gr; *s && *s != '\n'; s++)
		;
  *s = 0;

  /* Loop over all groups. */
  s = gr;
  while(*s) {
	/* Look for the end of this group. */
	p = s;
	while(*p && *p != ' ' && *p != '\t' && *p != ',') p++;

	/* Group ends at p, test it. */
	strncpy(group, s, p - s);
	group[p - s] = 0;
	if (testgroup(group) == ERROR) {
		fprintf(stderr, "*** unknown newsgroup `%s'\n", group);
		status = ERROR;
	}

	/* Fiddle around a bit to get a comma separated list. */
	comma = (char *)NULL;
	if (*p) {
		comma = p++;
		s = p;
		while(*p == ' ' || *p == '\t' || *p == ',') p++;
		strcpy(s, p);
	} else s = p;
	if (comma && *s) *comma = ',';
  }
  return(status);
}


/*
 * Fetch the contents of file 'name' and copy them to the 'tempfp' file.
 * If 'quote' is 0, prefix each copied line with "> ".  Also note, that
 * we *must* quote any line beginning with "From_".
 */
void
fetch_file(name, quote, nchar, nlin)
char *name;
int quote;
off_t *nchar;
int *nlin;
{
  char buff[1024];
  off_t nchars;
  int nlines;
  register FILE *ifp;

  nchars = 0L;
  nlines = 0;

  if ((ifp = fopen(name, "r")) != (FILE *)NULL) {
	nchars = 0L;
	nlines = 0;
	while (fgets(buff, 1024, ifp) != (char *)NULL) {
#if 0
		/*
		 * I just tried posting an article (via INN 1.4)
		 * with "From " as the first word on several lines.
		 * No adjustment, so why should we ? ET
		 */
		if (quote == 1 || !strncmp(buff, "From ", 5))
					fprintf(artfp, "> %s", buff);
		  else fputs(buff, artfp);
#else
		if (quote == 1) fprintf(artfp, "> %s", buff);
		  else fputs(buff, artfp);
#endif
		nchars += (off_t) strlen(buff);
		nlines++;
	}
	(void) fclose(ifp);
  } else fprintf(stderr, ": cannot open file \"%s\"\n", name);
  *nchar = nchars;
  *nlin = nlines;
}


/* Add the ".signature" file to the current message. */
void
app_signature()
{
  char fname[128];
  char buff[1024];
  struct passwd *pw;
  FILE *fp;
  char *sp;

  sp = getenv("SIGNATURE");
  if (sp == NULL) {
	pw = getpwuid(getuid());
	if (pw == (struct passwd *)NULL) {
		fputs("pnews: cannot get home directory!\n", stderr);
		return;
	}
	sprintf(fname, "%s/%s", pw->pw_dir, SIGNATURE);
  } else strcpy(fname, sp);

  if ((fp = fopen(fname, "r")) != (FILE *)NULL) {
	while (fgets(buff, 1024, fp) != (char *)NULL) {
		fputs (buff, artfp);
	}
	(void) fclose(fp);
  }
}


/* Run a program, while redirecting input if desired. */
int
do_run(input, prg, args)
char *input;
char *prg;
char *args[];
{
  int status, pid, fd;

  if (*input != '\0') {
	if ((fd = open(input, O_RDONLY)) < 0) {
		fputs("pnews: couldn't re-open temp article!\n", stderr);
		return(ERROR);
	}
  } else fd = 0;
  args[0] = prg;
  if ((pid = fork()) == 0) {
	if (fd != 0) {		/* Child */
		(void) close(0);
		(void) dup(fd);
	}
	(void) execv(prg, args);
	fprintf(stderr, "pnews: couldn't execute %s\n", prg);
	exit(ERROR);
  } else if (pid > 0) {
	if (fd != 0) (void) close(fd);
	while (wait(&status) != pid)
		    ;
  }
  return(WEXITSTATUS(status));
}


/* 
 * Save the current article to file 'dead.article'.
 *
 * If the environmental variable "DEADART" is defined,
 * use its value as the file name. Otherwise, use DEADART
 * in the user's home directory.
 */
void
dead_art()
{
  char buff[1024];
  char fname[128];
  struct passwd *pw;
  register FILE *fp;
  char *sp;
  char *home;

  if ((sp = getenv("DEADART")) == (char *)NULL) {
	if ((home = getenv("HOME")) == (char *)NULL) {
		if ((pw = getpwuid(getuid())) == (struct passwd *)NULL) {
			fputs("pnews: cannot get home directory!\n", stderr);
			fputs("\tthe article is lost!\n", stderr);
			return;
		}
		home = pw->pw_dir;
	}    
	sprintf(fname, "%s/%s", home, DEADART);
  } else strcpy(fname, sp);

  if ((fp = fopen(fname, "a")) != (FILE *)NULL) {
	artfp = fopen(artname, "r");
	while (fgets(buff, 1024, artfp) != (char *)NULL) {
		fputs (buff, fp);
	}
	(void) fclose(fp);
  }
  fprintf(stderr, "pnews: appended article to file %s\n", fname);
}


/*
 * Perform a "tilde" action, like in "W-MAIL" style.
 * Return 1 if the article should not be sent after all.
 */
int
do_tilde(text)
char *text;
{
  char *args[4];
  register char *sp;
  int nlin;
  off_t nchar;

  /* First off, kill the newline. */
  sp = strchr(text, '\n');
  if (sp != (char *)NULL) *sp = '\0';

  switch(*text) {
	case '?':       /* editor help */
	case 'h':
		puts("\n  ** W-NEWS PostNews Article Editor Help **\n");
		puts("~?\t\tThis Help Screen");
		puts("~!\t\tEscape into Shell");
		puts("~a\t\tAppend .signature to article");
		puts("~e\t\tInvoke line editor on article");
		puts("~h\t\tSame as ? (Help Screen)");
		puts("~q\t\tQuit (with save) sending this article");
		puts("~r filename\tInsert file into article");
		puts("~R filename\tInsert quoted file into article");
		puts("~v\t\tInvoke visual editor on article");
		puts("~x\t\tExit (abort) sending this article");
		putchar('\n');
		break;

	case '!':       /* shell escape */
		sp = ++text;
		while (*sp == ' ' || *sp == '\t') sp++;
		if (*sp != '\0') {
			args[1] = "-c";
			args[2] = sp;
			args[3] = (char *)NULL;
		} else  args[1] = (char *)NULL;
		do_run("", shell, args);
		puts("\n[continue]");
		break;

	case 'a':       /* append SIGNATURE */
		app_signature();
		puts("[continue]");
		break;

	case 'e':       /* invoke Line Editor (ED) */
		(void) fclose(artfp);
		args[1] = artname;
		args[2] = (char *)NULL;
		do_run("", e_edit, args);
		artfp = fopen(artname, "a");
		puts("[continue]");
		break;

	case 'q':       /* quit (abort) sending article (save article) */
		(void) fclose(artfp);
		dead_art();
		/* FALL_THROUGH */

	case 'x':       /* exit this article (no save) */
		return(1);

	case 'r':       /* read and insert file */
		text++;
		while (*text == ' ' || *text == '\t') text++;
		fprintf(stderr, "\"%s\"", text);
		fetch_file(text, 0, &nchar, &nlin);
		fprintf(stderr, " %d/%ld\n", nlin, (long) nchar);
		puts("[continue]");
		break;

	case 'R':       /* read and insert quoted file */
		text++;
		while (*text == ' ' || *text == '\t') text++;
		fprintf(stderr, "\"%s\"", text);
		fetch_file(text, 1, &nchar, &nlin);
		fprintf(stderr, " %d/%ld\n", nlin, (long) nchar);
		puts("[continue]");
		break;

	case 'v':       /* invoke Visual Editor (VI) */
		(void) fclose(artfp);
		args[1] = artname;
		args[2] = (char *)NULL;
		do_run("", v_edit, args);
		artfp = fopen(artname, "a");
		puts("[continue]");
		break;

	default:
		puts("*** Unknown escape.  Please use ~? for help.");
		break;
  }
  return(OK);
}


/* Check if a distribution is legal, and if it is, get some info. */
char *
legal(dist)
char *dist;
{
  register DISTRIB *dp;

  dp = ldistrib;
  while (dp != NILDIST) {
	if (!strcmp(dp->dist, dist)) return(dp->name);
	dp = dp->next;
  }

  puts("Possible distributions are:");
  dp = ldistrib;
  while (dp != NILDIST) {
	printf(" %-16.16s - %s\n", dp->dist, dp->name);
	dp = dp->next;
  }
  return((char *)NULL);
}


/*
 * Create a article, including the Article-header and the final
 * signature.  This routine copies lines of text from 'stdin' to
 * 'tempfp', until an EOF is typed, or a line containing only a
 *  '.'.  Return 0 if all is OK, < 0 for errors or 1 for abort.
 */
int
edit_art(infp)
FILE *infp;
{
  register char *sp;
  char buff[1024];
  char *dest;
  int c;

  /* Create the temporary article. */
  if ((artfp = fopen(artname, "w")) == (FILE *)NULL) {
	fprintf(stderr, "pnews: cannot create temp file %s\n", artname);
	return(ERROR);
  }

  /* An Article Header must contain some vital information.  Ask for it. */
  subject[0] = '\0';
  do {  /* we MUST get a Subject: line !!! */
	fputs("Subject: ", stderr);
	fgets(subject, 128, stdin);
	if (feof(stdin)) cleanup(0);
  } while (subject[0] == '\0' || subject[0] == '\n');
  if ((sp = strchr(subject, '\n')) != (char *)NULL) *sp = '\0';

  fputs("Keywords: ", stderr);
  fgets(keywords, 128, stdin);
  if ((sp = strchr(keywords, '\n')) != (char *)NULL) *sp = '\0';

  /* Fill in default groups, if we have them. */
  strcpy(newsgroups, "");
  if (dflt_newsg) strcpy(newsgroups, dflt_newsg);

  do {
	/* Ask for the newsgroups, perhaps with  a default. */
	if (strlen(newsgroups))
		fprintf(stderr, "Newsgroups [%s]: ", newsgroups);
	  else fputs("Newsgroups: ", stderr);
	fflush(stderr);

	fgets(buff, 1024, stdin);
	if (*buff != '\n') strcpy(newsgroups, buff);

	/* Now check if all groups are valid. */
  } while(groups_ok(newsgroups) == ERROR);

  do {
	fprintf(stderr, "Distribution [%s]: ", DFLT_DIST);
	fgets(distribution, 1024, stdin);
	if ((sp = strchr(distribution, '\n')) != (char *)NULL) *sp = '\0';
	if (distribution[0] == '\0') strcpy(distribution, DFLT_DIST);
  } while ((dest = legal(distribution)) == (char *)NULL);

  if (strcmp(distribution, "local") && isatty(0)) {
	printf("This machine posts news %s.\n", dest);
	fputs("Are you absolutely sure you want to do this [n]? ", stdout);
	fflush(stdout);
	buff[0] = getchar();
	if (*buff != 'y' && *buff != 'Y') cleanup(0);
  }
  putc ('\n', stderr);

  /* We now have enough information to set up the article header. */
  fprintf(artfp, "Subject: %s\n", subject);
  if (keywords[0] != '\0')
	fprintf(artfp, "Keywords: %s\n", keywords);
  fprintf(artfp, "Newsgroups: %s\n", newsgroups);
  fprintf(artfp, "Distribution: %s\n", distribution);
  putc ('\n', artfp);

  /*
   * We now have a complete header.  Add the article body.
   * If this stdIN is a terminal, we should process the in-
   * put in "editor" mode; if the stdIN is a file (redirecttion!)
   * then we can just copy the data to the temp. file.
   */
  c = 0;
  if (isatty(fileno(infp))) {
	while (c == 0) {
		if (fgets(buff, 1024, infp) == (char *)NULL) break;
		if (buff[0] == '.' && buff[1] == '\n') break;
			else if (buff[0] == '~' && buff[1] != '~')
						c = do_tilde(&buff[1]);
			else fputs(buff, artfp);
	}
  } else {
	while (fgets(buff, 1024, infp) != (char *)NULL) {
		fputs(buff, artfp);
	}
  }

  /* Close the article file. */
  if (ferror(artfp) || fclose(artfp)) {
	fputs("pnews: could not close temp article\n", stderr);
	c = -2;
  }
  return(c);
}


/* Read environment EDITOR, VISUAL and SHELL variables. */
void
do_environ()
{
  char *e;
  
  e_edit  = (e = getenv("EDITOR")) == (char *)NULL ? E_EDIT : e;
  v_edit  = (e = getenv("VISUAL")) == (char *)NULL ? V_EDIT : e;
  shell   = (e = getenv("SHELL"))  == (char *)NULL ? SHELL  : e;
}


/* Read the 'distributions' file. */
void
do_dists()
{
  char buff[128];
  char fname[128];
  FILE *fp;
  DISTRIB *dp;
  register char *sp;

  sprintf(fname, "%s/%s", CONFDIR, DISTRIBS);
  dp = ldistrib;
  if ((fp = fopen(fname, "r")) == (FILE *)NULL) {
	fprintf(stderr, "Warning: I cannot read the %s file!\n", fname);
	fputs("         Only \"local\" distribution allowed.\n", stderr);
	dp = (DISTRIB *)malloc(DISTSIZ);
	if (dp == NILDIST) {
		fputs("pnews: out of memory.\n", stderr);
		return;
	}
	dp->dist = (char *)malloc(6);
	dp->name = (char *)malloc(19);
	if (dp->dist == (char *)NULL || dp->name == (char *)NULL) {
		fputs("pnews: out of memory.\n", stderr);
		return;
	}
	strcpy(dp->dist, "local");
	strcpy(dp->name, "local to this site");
	return;
  }

  /* Now read the file and allocate the structures. */
  while (fgets(buff, 128, fp) != (char *)NULL) {
	if ((sp = strchr(buff, '\n')) != (char *)NULL) *sp = '\0';
	sp = buff;
	while (*sp && *sp != ' ' && *sp != '\t') sp++;
	*sp++ = '\0';
	while (*sp == ' ' || *sp == '\0') sp++;

	if (ldistrib == NILDIST) {
		ldistrib = (DISTRIB *)malloc(DISTSIZ);
		dp = ldistrib;
	} else {
		dp = ldistrib;
		while (dp->next != NILDIST) dp = dp->next;
		dp->next = (DISTRIB *)malloc(DISTSIZ);
		dp = dp->next;
	}
	if (dp == NILDIST) {
		fputs("pnews: out of memory.\n", stderr);
		(void) fclose(fp);
		return;
	}
	dp->next = NILDIST;
	dp->dist = (char *)malloc(strlen(buff) + 1);
	dp->name = (char *)malloc(strlen(sp) + 1);
	if (dp->dist == (char *)NULL || dp->name == (char *)NULL) {
		fputs("pnews: out of memory.\n", stderr);
		(void) fclose(fp);
		return;
	}
	strcpy(dp->dist, buff);
	strcpy(dp->name, sp);
  }
  (void) fclose(fp);
}


void
usage()
{
  fputs("Usage: pnews [-d] [ newsgroup,newsgroup,.. ]\n", stderr);
  exit(ERROR);
}


void
cleanup(sig)
int sig;
{
  (void) unlink(artname);
  sig = 0;	/* to shut up the compiler! */
  exit(ERROR);
}


int
main(argc, argv)
int argc;
char *argv[];
{
  char cmd[128];
  int st;
  char *args[4];

  program = strrchr(*argv, '/');
  if (program == (char *)NULL) program = *argv;
    else program++;

  signal(SIGINT, cleanup);
  signal(SIGQUIT, cleanup);
#ifndef COHERENT
  opterr = 0;
#endif
  while ((st = getopt(argc, argv, "d")) != EOF) switch(st) {
	case 'd':
		debug = 1;
		break;

	default:
		usage();
  }

  /* We may have one more argument. */
  if (argc > optind) {
	if (optind == argc - 1) dflt_newsg = argv[optind];
	  else usage();
  } else dflt_newsg = (char *)NULL;

  (void) umask(0133);           /* set protection mask to -rw-r--r-- */

  do_environ();
  do_dists();

  strcpy(artname, "/tmp/pn.XXXXXX");
  mktemp(artname);

  if ((st = edit_art(stdin)) == 0) {
	sprintf(cmd, "%s/inews", LIBDIR);
	fputs("Posting article...\n", stderr);
	st = 1;
	args[st++] = "-h";
	if (debug > 0) args[st++] = "-v";
	args[st] = (char *)NULL;

	if ((st = do_run(artname, cmd, args)) != 0) {
		fputs("Article could not be posted !\n", stderr);
		dead_art();
	} else fputs("Article has been posted.\n", stderr);
  }

  (void) unlink(artname);

  exit(st);
}
