Davison

LISTING ONE



/* monit.c */
/* Andrew Davison, June 1996 (ad@cs.mu.oz.au) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>        /* for sleep() */
#define SIZE 256           /* max length of a string */
#define MIN_DELAY 1        /* min sleep time: 1 hour */
#define MAX_DELAY 336      /* max sleep time: 336 hours = 2 weeks 
*/
#define MAX_CHECKS 10000   /* max number of checks before 
stopping */
#define DAYMAX 10          /* length of a day name */
#define MONTHMAX 4         /* length of a month name */
struct date {
  char dayname[DAYMAX];
  char monthname[MONTHMAX];
  int sec; int min; int hour; 
  int day; int month; int year; 
};
typedef struct date Date;
void process_args(int argc, char *argv[], int *delay,
                                          char **url, int *verbose);
int read_delay(char *str);
void get_header(int verbose, char *host, char *rsrc, char *fnm);
int extract_parts(char url[], char host[], char fnm[]);
void locate_mod(char *url, char *temp, char **mod);
Date extract_date(char *date);
int day_num(char *s);
int month_num(char *s);
void get_page(int verbose, char *host, char *rsrc, char *fnm);
void display_page(char *temp);
int more_recent(Date curr, Date old);
void print_diffs(char *oldf, char *newf);
int main(int argc, char *argv[])
{
  char otemp[SIZE], ctemp[SIZE];
  char *url, host[SIZE], rsrc[SIZE];
  char *omod, *cmod;
  Date odate, cdate;
  int delay, num_checks = 0;
  int verbose = 0;   /* not verbose by default */
  process_args(argc, argv, &delay, &url, &verbose);
  if (!extract_parts(url, host, rsrc)) {
    printf("Unable to parse URL: %s\n", url);
    exit(1);
  }
  tmpnam(otemp);
  get_header(verbose, host, rsrc, otemp);
  locate_mod(url, otemp, &omod);
  odate = extract_date(omod);
  
  get_page(verbose, host, rsrc, otemp);
  display_page(otemp);
  while (num_checks < MAX_CHECKS) {
    if (verbose)
      printf("\nSleeping for %d hour(s)...\n", delay);
    fflush(NULL);       /* force output */
    sleep(delay*3600);  /* sleep is in seconds */
    tmpnam(ctemp);
    get_header(verbose, host, rsrc, ctemp);
    locate_mod(url, ctemp, &cmod);
    cdate = extract_date(cmod);
    if (more_recent(cdate, odate)) {
      printf("\nCHANGE DETECTED -- %s\n", cmod);
      get_page(verbose, host, rsrc, ctemp);
      print_diffs(otemp, ctemp);
      odate = cdate;
      remove(otemp);
      rename(ctemp, otemp);
    }
    else {
      if (verbose)
        printf("No modifications since last access\n");
      remove(ctemp);
    }
  }
  printf("Monitoring of %s terminated since %d checks made\n", 
                                    url, MAX_CHECKS);
  display_page(otemp);
  remove(otemp);
  return 0;
}
void process_args(int argc, char *argv[], int *delay,
                                          char **url, int *verbose)
/* Extract the delay time (in hours) between checks, the URL to 
   be observed, and set the verbose flag if "-v" is present. 
*/
{
  if ((argc < 3) || (argc > 4)) {
    printf("Usage: monit [-v] delay_hours URL\n");
    exit(1);
  }
  if (strcmp(argv[1], "-v") == 0) {
    *verbose = 1; 
    *delay = read_delay(argv[2]);
    *url = argv[3]; 
  }
  else {
    *delay = read_delay(argv[1]);
    *url = argv[2];
  }
}
int read_delay(char *str)
/* Extract the delay time (in hours) from the delay string */
{
  int d;
  d = atoi(str);
  if (d < MIN_DELAY) {
    printf("Delay cannot be less than %d hours\n", MIN_DELAY);
    d = MIN_DELAY;
  }
  else if (d > MAX_DELAY) {
    printf("Delay cannot be more than %d hours\n", MAX_DELAY);
    d = MAX_DELAY;
  }
  return d;
}
void get_header(int verbose, char *host, char *rsrc, char *fnm)
/* Use telnet to send a HEAD message to host to obtain header 
   information about rsrc. The response is stored in fnm.
*/
{
  char cmd[SIZE];
  FILE *out;
  sprintf(cmd, "telnet %s 80 > %s\n", host, fnm);
  if (verbose)
    printf("Trying to contact %s...\n", host);
  out = popen(cmd, "w");
  if (verbose)
    printf("Sending a HEAD request...\n");
  fprintf(out, "HEAD /%s HTTP/1.0\n\n", rsrc);
  if (verbose)
    printf("Waiting for a response\n");
  pclose(out);     
}
  
int extract_parts(char url[], char host[], char fnm[])
/*  url should be of the form:
        http://host[/fnm]
    host and fnm are extracted from url; fnm may be empty
*/
{
  int i = 0, j, k;
  while ((url[i] != '\0') && (url[i] != '/'))
    i++;
  if (url[i] == '\0') {
    printf("Illegal URL format\n");
    return 0;
  }
  i = i + 2;    /* skip both '/'s */
  j = 0;
  while ((url[i] != '\0') && (url[i] != '/'))
    host[j++] = url[i++];      /* build host */
  host[j] = '\0';
  if (url[i] == '\0')          /* no file part */
    fnm[0] = '\0';
  else {
    i++;     /* skip the '/' */
    k = 0;
    while (url[i] != '\0')
      fnm[k++] = url[i++];     /* build fnm */
    fnm[k] = '\0';
  }
  return 1;
}
void locate_mod(char *url, char *temp, char **mod)
/* Check if temp contains a successful response (a HTTP line
   with a status code of 200). If the response is successful
   then attempt to find the Last-Modified (or Last-modified)
   field, saving it in *mod.
*/
{
  char ln[SIZE];
  FILE *fp;
  int is_success = 0, has_mod = 0;
  if ((fp = fopen(temp, "r")) == NULL) {
    printf("Cannot open temporary file %s for reading\n", temp);
    return;
  }
  while ((!is_success) && (fgets(ln, SIZE, fp) != NULL))
    if (strncmp(ln, "HTTP/1.0 200", 12) == 0)
      is_success = 1;
  if (!is_success) {
    printf("Page header request unsuccessful for: %s\n", url);
    exit(1);
  }
  while ((!has_mod) && (fgets(ln, SIZE, fp) != NULL)) {
    if (strncmp(ln, "Last-Modified:", 14) == 0) {
      *mod = (char *)malloc(sizeof(char)*(strlen(ln)+1));
      strcpy(*mod, ln);
      has_mod = 1;
    }
    /* lower-case version of field name */
    else if (strncmp(ln, "Last-modified:", 14) == 0) {
      *mod = (char *)malloc(sizeof(char)*(strlen(ln)+1));
      strcpy(*mod, ln);
      (*mod)[5] = 'M';  /* make field name "Last-Modified:" */
      has_mod = 1;
    }
  }
  if (!has_mod) {
    printf("Page header does not contain a \"Last-Modified\" field\n");
    exit(1);
  }
  fclose(fp);
}
Date extract_date(char *date)
/* Extract dates in one of the following formats:
     RFC 1123, RFC 850, or asctime()
*/
{
  Date d;
  /* RFC 1123 format */
  if (sscanf(date, "Last-Modified: %[^,], %d %s %d %d:%d:%d",
                d.dayname, &d.day, d.monthname, &d.year,
                &d.hour, &d.min, &d.sec) == 7) {
    d.day = day_num(d.dayname);
    d.month = month_num(d.monthname);
  }
  /* RFC 850 format */
  else if (sscanf(date, "Last-Modified: %[^,], %d-%[^-]-%d %d:%d:%d",
                d.dayname, &d.day, d.monthname, &d.year,
                &d.hour, &d.min, &d.sec) == 7) {
    d.day = day_num(d.dayname);
    d.month = month_num(d.monthname);
    d.year = 1900 + d.year;   /* convert to 4 digit year */
                              /* will FAIL in the year 2000 */
  }
  /* asctime() format */
  else if (sscanf(date, "Last-Modified: %s %s %d %d:%d:%d %d",
                d.dayname, d.monthname, &d.day, 
                &d.hour, &d.min, &d.sec, &d.year) == 7) {
    d.day = day_num(d.dayname);
    d.month = month_num(d.monthname);
  }
  else {
    printf("Incorrect date format in %s\n", date);
    d.year = 0;  /* default value */
  }
  return d;
}
int day_num(char *s)
/* Return a numerical value corresponding to the day name.
   Deals with full day names (RFC 850) or the 3 letter 
   abbreviations used in RFC 1123 and asctime().
*/
{
  int i;
  static char *days[] = {
     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };
  for (i=0; i < 7; i++)
    if (strncmp(s, days[i], 3) == 0)
      return i;
  printf("%s is not a recognised day name\n", s);
  return 0;
}
int month_num(char *s)
/* Return a numerical value corresponding to the month name */
{
  int i;
  static char *months[] = {
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
  for (i=1; i < 13; i++)   /* start at 1 */
    if (strcmp(s, months[i-1]) == 0)
      return i;
  printf("%s is not a recognised month name\n", s);
  return 1;
}
void get_page(int verbose, char *host, char *rsrc, char *fnm)
/* Use telnet to send a GET message to host to obtain the page
   in rsrc. The response is stored in fnm. 
*/
{
  char cmd[SIZE];
  FILE *out;
  sprintf(cmd, "telnet %s 80 > %s\n", host, fnm);
  if (verbose)
    printf("Trying to contact %s...\n", host);
  out = popen(cmd, "w");
  if (verbose)
    printf("Sending a GET request...\n");
  fprintf(out, "GET /%s HTTP/1.0\n\n", rsrc);
  if (verbose)
    printf("Waiting for a response\n");
  pclose(out);
}
void display_page(char *temp)
/* Read the page text from the temporary file and print it */
{
  FILE *fp;
  char ln[SIZE];
  if ((fp = fopen(temp, "r")) == NULL) {
    printf("Cannot open temporary file %s for reading\n", temp);
    exit(1);
  }
  printf("\n\n---------------------------\nPage Download:\n\n");
  while (fgets(ln, SIZE, fp) != NULL)
      fputs(ln, stdout);
  printf("\n---------------------------\n\n");
  fflush(NULL);
  fclose(fp);
}
int more_recent(Date curr, Date old)
/* See if the current date is more recent than the old date */
{
  if (curr.year > old.year)
    return 1;
  if (curr.month > old.month)
    return 1;
  if (curr.day > old.day)
    return 1;
  if (curr.hour > old.hour)
    return 1;
  if (curr.min > old.min)
    return 1;
  if (curr.sec > old.sec)
    return 1;
  return 0;
}
void print_diffs(char *oldf, char *newf)
/* Use UNIX diff to find the differences between oldf and newf.
   Ignore differences involving blanks. The output goes to stdout.
 */
{
  char cmd[SIZE];
  printf("\nDifferences between last version and this version:\n\n");
  fflush(NULL);
  sprintf(cmd, "diff -b %s %s", oldf, newf);
  system(cmd);
  printf("\n\n");
}