
/* batnews.c */
/* Andrew Davison, May 1997 (ad@ratree.psu.ac.th) */

/* Batch-oriented News-reader. 

   The news-server specified in NEWS_SERVER is contacted and
   all new messages from the newsgroup in NEWS_GROUP are read.
   New messages are those numbered after the current value in
   NNUM_FNM.

   The articles are stored in NEW_FNM, and the news number
   file, NNUM_FNM, is updated to record the new number of the
   last article read. 

   The NEW_FNM contents are then posted to the address in 
   NEWS_RECEIVER. This relies on the UNIX utility /usr/bin/mail
   and its interactive option -I.
*/
   
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <bstring.h>     /* for bcopy() */
#include <unistd.h>      /* for read(), write(), close() */

#define CR 13              /* carriage return ASCII value */
#define MAXSTR 512         /* max length of a string */


/* Configuration values */
#define NEWS_SERVER "news.psu.ac.th"     /* location of news server */
#define NEWS_GROUP "rec.puzzles"         /* news group of interest */
#define NNUM_FNM  "/home/ad/ns/clp.num"  /* news number file for newsgroup */
#define NEWS_FNM "/home/ad/ns/clp.news"  /* file which stores articles */
#define NEWS_RECEIVER "ad@ratree.psu.ac.th"  /* address to send NEWS_FNM */



/* function prototypes */
int open_news_socket(char *host, char *cmd);
void set_group(int sd, int *last);
void get_articles(int sd, int start, int end);
void store_article(FILE *fp, int sd, int artnum);
int is_period_line(char *line, int len);
void store_error(FILE *fp, int artnum, int respno);
void quit_server(int sd);
int read_response(int sd, char response[], int success_code);
int sdrline(char line[], int maxlen, int sd);
int read_ninfo(void);
void write_ninfo(int last_read);
void mail_articles(void);


int main(void)
/* Open a socket link to news; set the newsgroup; store the 
   articles in a file; send the file to the specified person 
   by e-mail. 
   When the socket is openned, the first line returned should 
   start with 200 if the news link is good.
*/
{
  char response[MAXSTR];
  int sd, last_read, last_in_group;

  last_read = read_ninfo();
  sd = open_news_socket(NEWS_SERVER, "batnews");
  if (read_response(sd, response, 200) == 200) {
    set_group(sd, &last_in_group);
    if (last_in_group <= last_read)
      printf("No new messages in %s\n", NEWS_GROUP);
    else {
      get_articles(sd, last_read+1, last_in_group);
      write_ninfo(last_in_group);
      mail_articles(); 
    }
  }
  quit_server(sd);
  return 0;
}


int open_news_socket(char *host, char *cmd)
/* Create a socket to the news server host. */
{
  struct sockaddr_in name;
  struct hostent *hptr;
  int sd;

  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror(cmd);
    exit(1);
  }
  /* initialise the name struct to all zeros */
  bzero(&name, sizeof(name));
/*  memset(&name, 0, sizeof(name)); */
  name.sin_family = AF_INET;
  name.sin_port = htons(119);         /* host is a news-server */
  if ((hptr = gethostbyname(host)) == NULL) {
    perror("gethostbyname");
    exit(1);
  }
  /* copy the IP address of host into name */
  bcopy(hptr->h_addr, &name.sin_addr.s_addr, hptr->h_length);
/*  memcpy(&name.sin_addr.s_addr, hptr->h_addr, hptr->h_length); */
/*  name.sin_addr.s_addr = * (unsigned long *) hptr->h_addr; */

  printf("Trying to contact %s...\n", host);
  if (connect(sd, (struct sockaddr *) &name, sizeof(name)) < 0) {
    close(sd);
    perror(cmd);
    exit(1);
  }
  return sd;
}


void set_group(int sd, int *last)
/* Specify the newsgroup with the news server using the GROUP command:
        GROUP newsgroup-name
   The successful response has the form:
        211 num-articles first-art-num last-art-num newsgroup-name
   e.g.
        211 181 41289 43831 comp.protocols.tcp-ip
*/
{
  char msg[MAXSTR], response[MAXSTR];

  sprintf(msg, "GROUP %s%c\n", NEWS_GROUP, CR);      /* NNTP GROUP message */
  printf("Sending: %s", msg);
  write(sd, msg, strlen(msg));
  if (read_response(sd, response, 211) != 211) {
    quit_server(sd);
    exit(1);
  }
  if (sscanf(response, "%*d %*d %*d %d", last) != 1) {
    fprintf(stderr, "Error response: %s", response);
    quit_server(sd);
    exit(1);
  }
}



void get_articles(int sd, int start, int end)
/* Get the text of the new articles numbered between start
   and end, and store them in NEWS_FNM.
   The NNTP command used is ARTICLE:
      ARTICLE news-number
   The response is the article, with the first line starting
   with a response number which is 220 for success.
*/
{
  FILE *fp;
  char msg[MAXSTR], response[MAXSTR];
  int i, respno;

  if ((fp = fopen(NEWS_FNM, "w")) == NULL)
    fprintf(stderr, "Cannot write to %s\n", NEWS_FNM);
  else {
    printf("Will save %d articles in %s\n", end-start+1, NEWS_FNM);
    for (i=start; i <= end; i++) {
      sprintf(msg, "ARTICLE %d%c\n", i, CR); /* NNTP ARTICLE message */
      printf("Sending: %s", msg);
      write(sd, msg, strlen(msg));
      if ((respno = read_response(sd, response, 220)) == 220)
        store_article(fp, sd, i);
      else
        store_error(fp, i, respno);
    }
    fclose(fp);
  }
}


void store_article(FILE *fp, int sd, int artnum)
/* Add the retrieved article to the end of the NEWS_FNM file. */
{
  char response[MAXSTR];
  int rlen;

  printf("Saving article %d\n", artnum);
  fprintf(fp, "\n\n------------- %d -----------\n\n", artnum);
  rlen = sdrline(response, MAXSTR, sd);
  while (!is_period_line(response, rlen)) {
    response[rlen-2] = ' ';    /* replace CR by space */
    fprintf(fp, "%s", response);
    rlen = sdrline(response, MAXSTR, sd);
  }
}


int is_period_line(char *line, int len)
/* A line consisting of ".CR\n" is the termination marker for
   variable length NNTP output. */
{
  if (len != 3)
    return 0;
  if ((line[0] == '.') && (line[1] == CR) && (line[2] == '\n'))
    return 1;
  return 0;
}


void store_error(FILE *fp, int artnum, int respno)
/* Store the error code (in respno) obtained when reading an article */
{
  printf("Recording read error for article %d\n", artnum);
  fprintf(fp, "\n\n------------- %d -----------\n\n", artnum);
  fprintf(fp, "ARTICLE error: %d\n", respno);
}



void quit_server(int sd)
/* Send the NNTP QUIT message */
{
  printf("Quiting %s\n", NEWS_SERVER);
  write(sd, "QUIT\015\n", 6);      /* \015 is the octal for CR */
  close(sd);
}



/* Input functions */

int read_response(int sd, char response[], int success_code)
/* Read the response to a NNTP message and check it for errors. */
/* Return codenum (or -1) */
{
  int rlen, codenum;

  rlen = sdrline(response, MAXSTR, sd);
  if (rlen <= 0) {
    fprintf(stderr, "No response\n");
    return -1;
  }
  if (sscanf(response, "%d", &codenum) != 1) {
    fprintf(stderr, "Incorrect response: %s", response);
    return -1;
  }
  if (codenum != success_code)
    fprintf(stderr, "Error response: %s", response);
  else
    printf("Response: %s", response);
  return codenum;
}



int sdrline(char line[], int maxlen, int sd)
/* Read characters from a socket up (and including) the newline,
   or until there are maxlen-1 characters in line[]. 
   Return the number of characters in line[].
*/
{
  int posn = 0, rc;
  char ch;

  while (posn < (maxlen-1)) {
    rc = read(sd, &ch, 1);
    if (rc == 1) {      /* character read successfully */
      line[posn++] = ch;
      if (ch == '\n')
        break;
    }
    else if (rc == 0)   /* socket closure */
      break;
    else                /* socket error */
      return -1;
  }
  line[posn]  = '\0';
  return posn;
}



/* Interface functions for the news number file */

int read_ninfo(void)
/* Read the number of the last article read from NNUM_FNM */
{
  int last_read;
  FILE *fp;

  if ((fp = fopen(NNUM_FNM, "r")) == NULL) {
    fprintf(stderr, "Cannot read %s\n", NNUM_FNM);
    exit(1);
  }
  printf("Reading %s\n", NNUM_FNM);
  fscanf(fp, "%d", &last_read);
  fclose(fp);

  printf("Last article read in %s: %d\n", NEWS_GROUP, last_read);

  return last_read;
}


void write_ninfo(int last_read)
/* Write the last_read value into NNUM_FNM */
{
  FILE *fp;

  if ((fp = fopen(NNUM_FNM, "w")) == NULL)
    fprintf(stderr, "Cannot write to %s\n", NNUM_FNM);
  else {
    printf("Writing %d to %s\n", last_read, NNUM_FNM);
    fprintf(fp, "%d\n", last_read);
    fclose(fp);
  }
}


/* Mailer Component */

void mail_articles(void)                                              
/* Send articles in NEWS_FNM to NEWS_RECEIVER */
/* Use -I to run in interactive mode */
{
  char line[MAXSTR];
  FILE *mfp;                                       
        
  sprintf(line, "/usr/bin/mail -I -s \'%s News Articles\' %s", 
                                 NEWS_GROUP, NEWS_RECEIVER);
  if ((mfp = popen(line, "w")) == NULL)
    fprintf(stderr, "popen() error for sending e-mail\n");
  else {
    printf("Sending %s as e-mail to %s\n", NEWS_FNM, NEWS_RECEIVER);
    fprintf(mfp, "~r %s\n", NEWS_FNM);      /* read news file */
    fflush(mfp);
    pclose(mfp);
  }
}

